2장. 개발 환경 준비
실습 5 — 오류를 잡아 친절하게 알리기
일부러 틀린 키와 틀린 모델 이름으로 모델을 불러 401 과 404 오류를 받고, try/except 로 잡아 무엇을 고치면 되는지 한국어로 알려 줍니다. 429 가 나면 잠시 기다렸다가 다시 묻는 함수도 만듭니다. 틀린 키도 서버에 보내 봐야 401 을 받으므로 인터넷이 있어야 하고, 마지막 단계는 .env 의 진짜 키가 있어야 합니다. 이 절의 출력은 API 키를 받기 전에 실행하지 않고 유추한 예상 출력입니다.
1단계. 오류 안내 함수 만들기
import os
import openai
from dotenv import find_dotenv, load_dotenv
from langchain_openai import ChatOpenAI
# 과목 폴더의 .env 에서 OPENAI_API_KEY 를 읽어 환경 변수에 넣는다
load_dotenv(find_dotenv(usecwd=True))
# 모델 이름도 .env 에서 바꿀 수 있게 하고, 없으면 gpt-4.1-mini 를 쓴다
MODEL = os.getenv("OPENAI_MODEL", "gpt-4.1-mini")
def explain(e):
"""오류 객체를 받아 무엇을 고치면 되는지 한국어로 돌려준다. 좁은 오류부터 차례로 견준다."""
# isinstance(e, 종류): e 가 그 종류의 오류이거나 그것을 이어받은 오류면 True
if isinstance(e, openai.AuthenticationError):
return "401 인증 실패: API 키가 틀렸거나 지운 키입니다. .env 의 키를 다시 붙여 넣으세요."
if isinstance(e, openai.RateLimitError):
return "429 한도 초과: 너무 자주 보냈거나 사용 한도를 넘었습니다. 잠시 뒤 다시 하세요."
if isinstance(e, openai.NotFoundError):
return "404 찾을 수 없음: 모델 이름이 틀렸거나 이 키로 쓸 수 없는 모델입니다."
if isinstance(e, openai.APIConnectionError):
return "연결 오류: 인터넷이나 학교·회사의 방화벽을 확인하세요."
if isinstance(e, openai.OpenAIError):
return "그 밖의 오류: 키를 못 읽었다면 .env 의 위치와 OPENAI_API_KEY 이름을 확인하세요."
return f"모르는 오류: {type(e).__name__}"
print("안내할 오류:", ["401", "429", "404", "연결", "그 밖"])안내할 오류: ['401', '429', '404', '연결', '그 밖']코드가 의미하는 것 explain() 은 오류 객체를 받아 고칠 곳을 한국어로 알려 주는 함수로, 챗봇이 오류를 만났을 때 사용자에게 보여 줄 안내문을 한곳에 모아 둔 것입니다. isinstance 로 좁은 오류부터 견주고 모든 OpenAI 오류의 조상인 openai.OpenAIError 를 맨 끝에 둔 것은, 넓은 것을 앞에 두면 모든 오류가 "그 밖의 오류"로 빠지기 때문입니다.
2단계. 틀린 키로 불러 보기
# 일부러 틀린 키(0 을 늘어놓아 지어낸 시험용 글자)를 넣는다
# max_retries=0: 실패해도 다시 보내지 않는다
bad_key = ChatOpenAI(model=MODEL, api_key="sk-test-0000000000000000", max_retries=0)
try:
bad_key.invoke("안녕하세요")
except Exception as e:
# langchain-openai 가 바꿔 낸 이름이 찍히지만, openai.AuthenticationError 로도 잡힌다
print("오류 이름:", type(e).__name__)
print("상태 코드:", e.status_code)
print(explain(e))오류 이름: OpenAIAuthenticationError
상태 코드: 401
401 인증 실패: API 키가 틀렸거나 지운 키입니다. .env 의 키를 다시 붙여 넣으세요.코드가 의미하는 것 자리표시 키를 넣은 ChatOpenAI 로 실제로 요청을 보내 401 을 받아 보는 단계입니다. except Exception 으로 넓게 잡은 뒤 e.status_code 와 explain(e) 로 읽어, 오류 이름이 langchain-openai 의 것이어도 이어받은 종류에 맞춰 같은 안내가 나오게 합니다. max_retries=0 으로 다시 보내기를 꺼서 틀린 키로 여러 번 보내지 않습니다.
3단계. 틀린 모델 이름으로 불러 보기
# 키는 .env 의 진짜 키를 쓰고, 모델 이름에 일부러 i 를 하나 더 붙인다
typo = ChatOpenAI(model="gpt-4.1-minii", max_retries=0)
try:
typo.invoke("안녕하세요")
except Exception as e:
print("오류 이름:", type(e).__name__)
print("상태 코드:", e.status_code)
print(explain(e))오류 이름: OpenAIModelNotFoundError
상태 코드: 404
404 찾을 수 없음: 모델 이름이 틀렸거나 이 키로 쓸 수 없는 모델입니다.코드가 의미하는 것 키는 맞고 모델 이름만 틀린 경우입니다. 서버는 그 모델을 찾지 못하면 404 를 돌려주고, explain() 은 그 번호에 맞춰 모델 이름을 확인하라는 안내를 고릅니다. 교수자가 정한 모델 이름을 .env 의 OPENAI_MODEL 에 옮겨 적다가 한 글자를 틀리는 일을 이 단계로 미리 겪어 봅니다.
4단계. 429 면 기다렸다가 다시 묻기
import time
def ask_with_retry(model, question, tries=3):
"""429 가 나면 1초, 2초 기다렸다가 다시 묻는다. 다른 오류는 안내만 하고 멈춘다."""
for k in range(1, tries + 1):
try:
reply = model.invoke(question)
print(f"{k}번째 시도에 성공")
return reply.content
except openai.RateLimitError as e:
print(f"{k}번째 시도:", explain(e))
# 시도할수록 조금씩 더 오래 기다린다(1초, 2초, …)
time.sleep(k)
except openai.OpenAIError as e:
print(explain(e))
return None
return None
# 진짜 키와 맞는 모델 이름으로 부른다. 다시 보내기는 우리 함수가 맡으므로 max_retries=0
model = ChatOpenAI(model=MODEL, temperature=0, max_retries=0)
print(ask_with_retry(model, "제주도를 한 문장으로 소개해 줘."))1번째 시도에 성공
제주도는 맑은 바다와 오름, 한라산을 함께 즐길 수 있는 한국의 대표적인 섬 여행지입니다.코드가 의미하는 것 ask_with_retry 는 429 일 때만 잠시 기다렸다가 다시 묻고, 다른 오류는 다시 보내도 같은 결과이므로 안내만 하고 멈춥니다. time.sleep(k) 로 기다리는 시간을 늘려 가는 것은 붐비는 창구에 곧바로 다시 줄 서지 않으려는 것입니다. 예상 출력은 오류가 없어 첫 시도에 끝난 경우이고, 답의 글은 모델마다 달라집니다.
이 실습이 뜻하는 것
이 실습은 거래처 창구에서 주문이 막혔을 때 직원이 번호표 대신 "출입증이 틀렸어요, 다시 확인해 주세요"라고 말해 주게 만드는 일과 같습니다. 번호마다 할 말을 정해 두고(1단계), 틀린 출입증을 내밀어 보고(2단계), 없는 물건을 주문해 보고(3단계), 창구가 붐비면 잠깐 기다렸다 다시 줄 서는 규칙을 만듭니다(4단계). 여기서 번호가 상태 코드, 할 말이 explain(), 다시 줄 서는 규칙이 ask_with_retry() 입니다. 비유와 다른 점은, 창구 직원은 사정을 봐주기도 하지만 서버는 같은 잘못에 언제나 같은 번호를 돌려준다는 것입니다.
| 단계 | 출력 | 뜻 |
|---|---|---|
| 1. 안내 함수 | 안내할 오류 다섯 가지 | 좁은 오류(401, 429, 404, 연결)를 먼저, 넓은 OpenAIError 를 나중에 견줍니다 |
| 2. 틀린 키 | OpenAIAuthenticationError, 401 | 이름은 langchain-openai 가 바꾼 것이지만 openai.AuthenticationError 로 잡혔습니다 |
| 3. 틀린 모델 | OpenAIModelNotFoundError, 404 | 이름 한 글자만 틀려도 서버는 그 모델을 찾지 못합니다 |
| 4. 다시 묻기 | 1번째 시도에 성공 | 오류가 없으면 한 번에 끝나고, 429 일 때만 기다렸다가 다시 묻습니다 |
한 줄로 줄이면, 오류를 숨기지 않고 번호에 맞춰 고칠 곳을 알려 주는 코드를 만든 실습입니다. 2단계와 3단계의 오류 이름은 langchain-openai 1.7.3 이 쓰는 이름을 코드에서 확인해 적은 것이고, 상태 코드 401 과 404 는 서버가 그 잘못에 돌려주는 번호입니다. 4단계에서 모델이 쓰는 답의 글은 실행할 때마다, 모델마다 조금씩 달라집니다. 429 는 일부러 만들기 어려워 이 실습에서는 보지 못했고, 짧은 시간에 아주 많이 보내거나 한도를 다 쓰면 1번째 시도의 안내 줄이 먼저 찍힙니다.
코드 설명
isinstance(e, openai.AuthenticationError)는 e 가 그 오류이거나 그것을 이어받은 오류면 True 입니다. langchain-openai 의OpenAIAuthenticationError는openai.AuthenticationError를 이어받았으므로 True 가 됩니다.explain()안에서는 순서를 지킵니다.openai.OpenAIError는 모든 OpenAI 오류의 조상이라 맨 위에 두면 401 도 404 도 "그 밖의 오류"가 됩니다. 좁은 것부터 견줍니다.e.status_code는 서버가 돌려준 상태 코드입니다. 연결 오류처럼 서버에 닿지 못한 오류에는 이 값이 없습니다.max_retries=0은 openai 패키지가 스스로 다시 보내는 것을 끕니다. 4단계는 우리 함수가 다시 보내기를 맡으므로 두 쪽이 겹쳐 너무 많이 보내지 않게 끈 것입니다.time.sleep(k)는 k 초 동안 멈춥니다. 시도할수록 기다리는 시간을 늘리면 붐빌 때 서버와 한도를 덜 괴롭힙니다.