LangChain 챗봇2장 · 개발 환경 준비17 / 20← 이전목차다음 →MetaContents

2장. 개발 환경 준비

실습 5 — 오류를 잡아 친절하게 알리기

일부러 틀린 키와 틀린 모델 이름으로 모델을 불러 401 과 404 오류를 받고, try/except 로 잡아 무엇을 고치면 되는지 한국어로 알려 줍니다. 429 가 나면 잠시 기다렸다가 다시 묻는 함수도 만듭니다. 틀린 키도 서버에 보내 봐야 401 을 받으므로 인터넷이 있어야 하고, 마지막 단계는 .env 의 진짜 키가 있어야 합니다. 이 절의 출력은 API 키를 받기 전에 실행하지 않고 유추한 예상 출력입니다.

1단계. 오류 안내 함수 만들기

Python
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단계. 틀린 키로 불러 보기

Python
# 일부러 틀린 키(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단계. 틀린 모델 이름으로 불러 보기

Python
# 키는 .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 면 기다렸다가 다시 묻기

Python
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 초 동안 멈춥니다. 시도할수록 기다리는 시간을 늘리면 붐빌 때 서버와 한도를 덜 괴롭힙니다.
← 이전 절2장 · 실습 4 — ChatOpenAI 로 묻고 응답 읽기다음 절 →2장 · 프로젝트 1 — 내 PC 점검표 만들기