# 3.4 에러 처리 및 응답 구조

OpenAI API를 개발 환경에서 사용하기 시작하면 가장 빠르게 마주하게 되는 주요 과제가 바로 ‘에러 처리’입니다. 예상치 못한 입력, 과다한 요청, 잘못된 인증 키, 혹은 일시적인 서버 이슈 등으로 인해 API 요청이 실패할 수 있습니다. 본 절에서는 OpenAI API 호출 시 발생 가능한 주요 에러 유형과 각 에러의 처리 전략, 그리고 정상적인 응답(Response)의 구조에 대해 상세히 알아보겠습니다.

## 3.4.1 OpenAI API 에러 처리의 기본 개념

OpenAI API는 요청에 대해 HTTP status code와 함께 JSON 형식의 응답을 반환합니다. 에러가 발생한 경우에도 마찬가지로 JSON 응답을 통해 어떤 종류의 문제가 있었는지를 알려줍니다. 이러한 응답 구조는 예측 가능한 에러 처리를 가능하게 해줍니다.

에러 처리를 위한 기본 원칙은 다음과 같습니다:

* 가능한 모든 API 호출에 대해 예외 처리를 구현할 것 (try-except 사용)
* 각 상태 코드(status code)에 맞는 로직을 분기 처리할 것
* 사용자에게 발생 원인을 명확히 알리고 재요청하게 할 수 있는 UX 설계
* 에러 로그를 서버에 기록하고, 필요시 자동 경고 또는 재시도를 구현할 것

## 3.4.2 OpenAI API의 주요 에러 코드 및 원인

아래는 OpenAI API에서 반환하는 주요 HTTP 상태 코드와 그 의미, 그리고 해당 코드에 대응하는 일반적인 문제 및 조치 방법입니다.

| HTTP Status               | 원인            | 설명                         | 대처 방안                  |
| ------------------------- | ------------- | -------------------------- | ---------------------- |
| 400 Bad Request           | 클라이언트 입력 잘못   | 파라미터 값 오류, 너무 짧거나 긴 입력     | 입력 검증 로직 강화, 요청 데이터 확인 |
| 401 Unauthorized          | 인증 실패         | 잘못된 API Key, 키 누락, 비활성화된 키 | API Key 재확인, 환경 변수 재설정 |
| 403 Forbidden             | 접근 권한 없음      | 조직 제한, 요금제 제한 등            | 조직/계정 상태 확인, 관리자에 문의   |
| 404 Not Found             | API 경로 오류     | 잘못된 엔드포인트 또는 리소스 없음        | URL과 모델 이름 점검          |
| 429 Too Many Requests     | Rate limit 초과 | 짧은 시간 내 호출 수 과다            | 재시도 로직 구현, 일정 지연 설정    |
| 500 Internal Server Error | OpenAI 서버 오류  | 내부 시스템 문제로 일시적 오류          | 재시도 로직 및 fallback 처리   |
| 503 Service Unavailable   | 서버 과부하        | 서비스 일시 중단 또는 트래픽 급증        | 백오프(backoff) 전략 도입 필요  |

※ 대부분의 5xx 에러는 일시적인 현상으로 짧은 딜레이 후 재요청으로 해결 가능하며, 자동 재시도 로직이 권장됩니다.

## 3.4.3 에러 응답 형식(JSON 구조)

OpenAI API는 에러 발생 시에도 API 응답은 JSON 형식으로 반환됩니다. 에러 응답의 기본 구조는 다음과 같습니다.

예시:

```json
{
  "error": {
    "message": "You exceeded your current quota, please check your plan and billing details.",
    "type": "insufficient_quota",
    "param": null,
    "code": "quota_exceeded"
  }
}
```

| 필드명     | 설명                                                           |
| ------- | ------------------------------------------------------------ |
| message | 사용자에게 보여줄 수 있는 에러 메세지                                        |
| type    | 에러 유형 (e.g. `invalid_request_error`, `authentication_error`) |
| param   | 문제가 있었던 입력 파라미터 (nullable)                                   |
| code    | 커스텀 에러 코드 (예: quota\_exceeded, rate\_limit\_exceeded)        |

이 에러 메시지는 단순 출력만이 아니라, 코드에서 정확히 어떤 문제가 있었는지를 분기 처리하는 근거로 사용될 수 있습니다.

예를 들어, code가 quota\_exceeded이면 사용자에게 요금제를 업그레이드하라는 문구를 유도할 수 있습니다.

## 3.4.4 에러 처리 예시 코드 (Python)

OpenAI Python SDK에서는 모든 에러를 Exception으로 처리할 수 있도록 설계되어 있습니다. 우리가 자주 처리해야 할 대표적인 예외 클래스는 다음과 같습니다.

| 예외 클래스                           | 설명              |
| -------------------------------- | --------------- |
| openai.error.AuthenticationError | 인증 실패           |
| openai.error.RateLimitError      | 과도 호출 (429)     |
| openai.error.APIConnectionError  | 네트워크 문제         |
| openai.error.APIError            | 서버 측 에러 (500 등) |
| openai.error.InvalidRequestError | 잘못된 파라미터 입력     |

다음은 이를 바탕으로 한 예시 코드입니다:

```python
import openai
from openai.error import OpenAIError, RateLimitError, AuthenticationError

def ask_gpt(prompt):
    try:
        response = openai.ChatCompletion.create(
            model="gpt-4",
            messages=[{"role": "user", "content": prompt}]
        )
        return response['choices'][0]['message']['content']
    
    except AuthenticationError:
        print("API 인증에 실패했습니다. API 키를 확인해주세요.")
    
    except RateLimitError:
        print("요청이 너무 많습니다. 잠시 후 다시 시도해주세요.")
    
    except OpenAIError as e:
        print(f"OpenAI API 에러 발생: {e}")
    
    except Exception as e:
        print(f"알 수 없는 오류 발생: {e}")
```

이와 같은 구조를 적용하면 예외 상황에서도 클라이언트가 멈추지 않고 ‘사용자 친화적인’ 피드백을 제공할 수 있습니다.

## 3.4.5 재시도(Backoff) 및 안정성 전략

일시적인 에러(특히 429, 500, 503)는 자동 재시도 로직을 통해 사용성 향상이 가능합니다. 다음은 지수적 백오프(Exponential Backoff)를 활용한 간단한 예시입니다:

```python
import time
import openai
from openai.error import RateLimitError

def call_with_retry(prompt, max_retries=5):
    for i in range(max_retries):
        try:
            return openai.ChatCompletion.create(
                model="gpt-4",
                messages=[{"role": "user", "content": prompt}]
            )
        except RateLimitError:
            wait_time = 2 ** i
            print(f"RateLimitError 발생 - {wait_time}초 후 재시도합니다...")
            time.sleep(wait_time)
    raise Exception("재시도 초과 - 나중에 다시 시도해주세요.")
```

이러한 전략은 특히 트래픽이 많은 SaaS 환경에서 필수적인 안정성 보강 장치입니다.

## 3.4.6 정상 응답(Response) 구조 이해하기

정상적인 API 호출 응답 역시 JSON 형식으로 반환되며, 주요 구조는 다음과 같습니다:

예시 (Chat Completion 응답):

```json
{
  "id": "chatcmpl-1234",
  "object": "chat.completion",
  "created": 1681318582,
  "model": "gpt-4-0613",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "안녕하세요! 무엇을 도와드릴까요?"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 10,
    "completion_tokens": 20,
    "total_tokens": 30
  }
}
```

| 필드      | 설명                         |
| ------- | -------------------------- |
| id      | 응답 ID (디버깅 및 트래킹 가능)       |
| model   | 사용된 모델 이름                  |
| choices | 생성된 응답 배열 (보통 1개)          |
| usage   | 토큰 사용량 분석용 정보 (요금 계산에 사용됨) |

응답 데이터는 사용자의 질문에 대한 답(choices\[0].message.content) 외에도, 과금 및 사용량 파악에 필요한 토큰 수(usage.prompt\_tokens 등)를 제공합니다.

이는 다음 장(Chapter 14: 요금 최적화와 비용 관리)와 밀접한 영역으로, 적절한 파싱 및 기록이 중요합니다.

## 요약

* 에러 처리는 선택이 아닌 필수입니다. 모든 API 호출에 예외 처리를 넣으세요.
* 사용자에게 의미 있는 에러 메시지를 제공하도록 하세요.
* 429, 500번대 에러는 주기적으로 발생 가능하므로 지수적 재시도(Exponential Backoff) 전략이 필수입니다.
* 정상 응답에서 토큰 사용량 파악은 요금 최적화 전략의 출발점입니다.
* 전체 API 응답을 로깅하고, 장애가 발생한 시점의 요청/응답 데이터를 저장하여 디버깅 정보를 확보하세요.

이 절에서는 API 호출의 안정성을 위한 핵심 기초 요소인 오류 처리와 응답 구조 해석법을 배웠습니다. 이후 챕터들에서 복잡한 비즈니스 로직과 다양한 기능 연동이 이어지겠지만, 이 절에서 다룬 기본기가 API 활용의 모든 기반이 된다는 점을 잊지 마시기 바랍니다.
