모든 AI 제공업체의 실패가 같은 것은 아니다: 장애 인식 LLM 폴백 시스템 구축하기
요약
다중 LLM 제공업체 통합 시 발생하는 다양한 오류들을 단순히 '실패'로 취급하는 것은 비효율적입니다. 본 글은 실패 원인을 분류하여, 키 냉각, 제공업체 전환, 캐시 무효화 등 적절한 대응을 결정하는 다중 제공업체 라우팅 시스템 설계 방식을 제시합니다.
핵심 포인트
- 실패를 네 가지 범주(KEY_FAULT, PROVIDER_TRANSIENT 등)로 분류해야 합니다.
- 단순 오류 처리 대신 실패 원인에 따른 맞춤형 조치가 필요합니다.
- HTTP 상태 코드는 신호일 뿐이며, API 동작의 맥락에서 해석되어야 합니다.
다중 제공업체(multi-provider) LLM 통합을 위한 일반적인 접근 방식은 간단합니다. 한 제공업체에 요청을 보내고, 오류가 발생하면 이를 포착한 다음, 사용 가능한 다음 키나 제공업체를 시도하는 것입니다.
이 방법은 일부 실패에는 작동하지만, 모든 오류를 동일하게 취급하는 것은 불필요한 문제를 야기합니다.
유효하지 않은 API 키, 제공업체 전체의 서비스 중단(outage), 사용되지 않는 모델 식별자(deprecated model identifier), 그리고 잘못된 애플리케이션 요청은 같은 실패가 아닙니다. 이들은 서로 다른 원인을 가지고 있으며, 각기 다른 대응이 필요합니다.
예를 들어, 제공업체가 서비스가 일시적으로 이용 불가능하여 HTTP 503을 반환하는 경우, 완벽하게 유효한 API 키를 냉각(cooling down)시키는 것은 근본적인 문제를 해결하지 못합니다. 이는 단지 그 키의 유효성과는 관련 없는 실패 이후에 해당 키의 사용 가능성을 줄일 뿐입니다.
StudyHub에서 저는 제공업체 장애를 분류하여, 키를 냉각할지, 제공업체를 전환할지, 캐시된 모델을 무효화할지, 또는 요청을 중단할지를 결정하는 다중 제공업체 LLM 라우팅 시스템을 구축했습니다.
이 글에서는 그 설계에 대해 탐구합니다.
- 실패 분류가 중요한 이유
폴백 시스템은 두 가지 질문에 답해야 합니다:
- 가장 가능성 높은 실패 원인은 무엇인가?
- 해당 실패에 적절한 조치는 무엇인가?
분류 없이는 라우터가 유효한 자격 증명(credentials)을 낭비하거나, 더 이상 존재하지 않는 모델을 반복적으로 요청하거나, 어떤 제공업체가 받든 상관없이 실패할 요청을 계속 재시도할 수 있습니다.
StudyHub는 커스텀 "ProviderCallError"를 통해 제공업체 장애를 표현하며, 여기에는 상태 코드(status codes), 제공업체 식별자(provider identifiers), 그리고 파싱된 오류 세부 정보(parsed error details)와 같은 진단 정보가 포함됩니다.
이 시스템은 실패를 네 가지 범주로 분류합니다:
- "KEY_FAULT"
- "PROVIDER_TRANSIENT"
- "BAD_MODEL"
- "NON_RETRYABLE"
이러한 분류 체계(taxonomy)가 라우팅 시스템이 어떻게 대응해야 하는지를 결정합니다.
- KEY_FAULT: 영향을 받은 키 냉각하기
"KEY_FAULT" 범주는 자격 증명 또는 계정 상태와 관련된 실패를 나타냅니다.
현재 분류 규칙에 따르면 여기에는 HTTP 상태 코드 401, 402, 403, 429가 포함됩니다.
예시로는 유효하지 않은 자격 증명(invalid credentials), 계정 제한(account restrictions), 결제 문제(billing problems), 그리고 계정 또는 키와 관련된 속도 제한(rate limits) 등이 있습니다.
라우터가 이러한 방식으로 실패를 분류하면, 영향을 받은 키를 냉각시키고 다른 적격한 키나 제공업체를 시도합니다.
이는 현재 요청을 처리할 수 없는 자격 증명을 반복적으로 통해 전송하는 것을 방지하기 위함입니다.
하지만 상태 코드는 보편적인 설명이라기보다는 신호(signal)입니다. 예를 들어, 429 응답은 키별 속도 제한일 수도 있고, 계정 수준의 할당량(quota)일 수도 있으며, 다른 제공업체별 제한일 수도 있습니다.
이러한 이유로 분류는 제공업체의 API 동작과 구현에 사용 가능한 오류 정보의 맥락에서 이해되어야 합니다.
- PROVIDER_TRANSIENT: 키를 패널티 없이 변경하는 제공업체
"PROVIDER_TRANSIENT" 카테고리는 HTTP 500, 502, 503, 504와 같은 실패 및 적격한 네트워크 수준 연결 실패를 처리합니다.
이러한 실패는 일반적으로 유효하지 않은 자격 증명보다는 일시적인 제공업체 또는 네트워크 문제를 나타냅니다.
그러한 실패가 일시적(transient)으로 분류되면, StudyHub는 현재 키를 불필요하게 냉각시키지 않고 다른 제공업체로 전환합니다.
이러한 구분은 외부 장애 발생 시 자격 증명의 가용성을 유지하는 데 도움이 됩니다.
또한 이는 재시도 동작(retry behavior)이 모든 예외를 현재 키가 사용 불가능하다는 증거로 취급하기보다는 실패의 원인에 따라 이루어져야 함을 보여줍니다.
정확한 처리는 여전히 제공업체의 응답 의미론(response semantics)과 라우터의 분류 규칙에 달려 있습니다.
- BAD_MODEL: 자격 증명이 아닌 모델 무효화
제공업체들은 모델 카탈로그를 변경합니다. 모델은 이름이 바뀌거나, 제거되거나, 사용 중단되거나(deprecated), 특정 엔드포인트에서 사용할 수 없게 될 수 있습니다.
따라서 어제 작동했던 모델 식별자도 제공업체와 자격 증명이 여전히 사용 가능하더라도 실패할 수 있습니다.
StudyHub는 구조화된 오류 정보가 요청된 모델이 유효하지 않거나 누락되었거나 폐기되었음을 나타낼 때 특정 HTTP 400 응답을 "BAD_MODEL"로 분류합니다.
클래시파이어는 다음과 같은 필드를 검사합니다:
- "error.type"
- "error.code"
- "error.message"
이후 사용 불가능한 모델의 알려진 지표를 확인합니다.
응답이 분류 규칙과 일치하면, 라우터는 키를 냉각(cooling)하는 대신 영향을 받은 모델을 공유 Redis 디스커버리 캐시에서 제거(evict)합니다.
이는 시스템이 해당 모델이 더 이상 사용 가능하지 않다고 판단한 후에도 다른 애플리케이션 인스턴스가 동일한 캐시된 모델 식별자를 계속 선택하는 것을 방지합니다.
이 구분은 중요합니다: 모델을 무효화(invalidating)하는 것과 자격 증명(credential)을 무효화하는 것은 서로 다른 기본 리소스를 다루기 때문에 다른 작업입니다.
또한 분류는 보수적이어야 합니다. 모든 HTTP 400이 모델이 사라졌다는 것을 의미하지는 않습니다. 잘못 구성된 페이로드나 기타 클라이언트 측 오류도 동일한 상태를 생성할 수 있습니다.
- NON_RETRYABLE: 폴백(Fallback)이 도움이 될 가능성이 낮을 때 빠르게 실패하기
최종 범주인 "NON_RETRYABLE"은 알려진 분류에 일치하지 않는 오류에 대한 보수적인 기본값 역할을 합니다.
그 이유는 일부 장애가 제공업체의 가용성보다는 애플리케이션의 요청 구성에서 발생하기 때문입니다.
만약 애플리케이션이 잘못 구성된 페이로드를 전송한다면, 여러 제공업체를 순환하는 것은 단순히 같은 실수를 반복할 수 있습니다.
이러한 경우, 빠르게 실패함으로써 요청 낭비를 막고 성공하지 못한 폴백 시도 체인 뒤에 원래 오류를 가리는 것을 방지합니다.
이 정책은 클래시파이어가 정확해야 합니다. 생소한 제공업체 오류는 다른 제공업체가 요청을 처리할 수 있었음에도 불구하고 비재시도 가능(non-retryable)으로 잘못 분류될 수 있습니다.
따라서 알려지지 않은 오류는 관찰 가능한 상태로 유지되어야 하며, 새로운 제공업체 응답 패턴이 이해됨에 따라 분류 체계는 진화해야 합니다.
- Python 요청 예외 정규화
실제 구현상의 어려움은 Python의 "requests" 라이브러리가 다양한 예외 유형을 통해 실패를 노출한다는 점입니다.
HTTP 오류는 상태 코드와 구조화된 본문(body)이 포함된 응답을 포함할 수 있습니다. 반면, 연결 실패나 시간 초과는 HTTP 응답 자체가 전혀 없이 발생할 수도 있습니다.
StudyHub는 이러한 실패들을 공통의 표현으로 정규화하기 위해 "_wrap_request_exception"이라는 헬퍼를 사용합니다.
HTTP 오류의 경우, 이 래퍼(wrapper)는 응답 상태 코드를 추출하고, 분류기(classifier)가 구조화된 오류 세부 정보를 검사할 수 있도록 응답 본문을 JSON으로 파싱하려고 시도합니다.
네트워크 실패의 경우, 응답을 받지 못했더라도 래퍼는 예외 정보를 포착합니다. 필요에 따라 오류 메시지를 검사할 수 있는 폴백(fallback) 정규 표현식인 "_NETWORK_MESSAGE_FALLBACK_RE"가 있습니다.
이러한 정보의 정규화 덕분에, 분류 계층은 모든 라우팅 결정이 각 Python 예외 유형의 세부 정보를 이해할 필요 없이 일관되게 실패에 대해 추론할 수 있게 됩니다.
궁극적인 목표는 예외 처리(exception handling)를 라우터가 다음에 무엇을 해야 할지 결정하는 정책(policy)과 분리하는 것입니다.
- 애플리케이션 인스턴스 간의 실패 상태 공유
만약 모든 애플리케이션 인스턴스가 제공업체 상태에 대한 독립적인 뷰를 유지한다면, 실패 분류는 효용성이 떨어집니다.
한 인스턴스가 특정 키가 일시적으로 사용 불가능하다고 식별했다고 가정해 봅시다. 만약 다른 인스턴스가 첫 번째 인스턴스의 상태 변화를 관찰하지 못했기 때문에 즉시 동일한 키를 재시도한다면, 시스템은 불필요한 실패를 발생시킬 수 있습니다.
StudyHub는 쿨다운(cooldown) 및 제공업체 블랙리스트를 위한 전용 키 패밀리를 사용하여 Redis에 런타임 상태를 중앙 집중화합니다. 여기에는 다음과 같은 접두사들이 포함됩니다:
"sh:1:ai:cooldown:"
"sh:1:ai:blacklist:"
이를 통해 여러 인스턴스가 관련 실패 상태를 관찰할 수 있는 공유 메커니즘을 갖게 됩니다.
또한, 시스템은 Redis 기반의 제공업체 상태 추적 기능을 비활성화하고 프로세스 로컬 메모리 상태로 폴백할 수 있는 환경 변수 제어 스위치인 "AI_PROVIDER_REDIS_STATE_ENABLED"도 제공합니다.
이러한 폴백은 Redis가 사용 불가능할 때 일부 작업을 유지할 수 있지만, 조정 모델을 변경합니다. 개별 프로세스가 서로 다른 쿨다운 및 블랙리스트 정보를 보유하게 될 수 있습니다.
다시 말해, 이 폴백은 일관된 공유 상태보다 지속적인 로컬 작동에 중점을 둡니다. 이는 동등한 분산 조정으로 오인해서는 안 됩니다.
- 장애 인식 라우팅 뒤의 트레이드오프(Trade-Offs)
클래스피케이션 시스템은 라우터가 적절하게 대응하는 능력을 향상시키지만, 설계상의 책임도 도입합니다.
클래스피케이션은 진화해야 합니다
제공업체들은 상태 코드와 오류 본문(error bodies)을 반드시 동일한 방식으로 사용하지 않습니다. 한 제공업체에 작동하는 분류기가 다른 제공업체에는 추가 규칙이 필요할 수 있습니다.
폴백이 항상 정답은 아닙니다
두 번째 제공업체가 요청된 모델, 모달리티(modality), 또는 요청 형식을 지원하지 않을 수 있습니다. 라우터는 여전히 제공업체 호환성과 요청 제약 조건을 존중해야 합니다.
공유 상태는 자체적인 의존성을 도입합니다
Redis는 인스턴스가 쿨다운 및 블랙리스트를 조정할 수 있게 하지만, Redis의 가용성과 로컬 상태 폴백의 동작을 명시적으로 고려해야 합니다.
진단(Diagnostics)은 유용하게 유지되어야 합니다
클래스파이어가 실패를 자신감 있게 식별할 수 없을 때, 진단 정보를 보존하고 알려지지 않은 오류를 모니터링하는 것은 조용한 오분류(silent misclassification)를 방지하는 데 도움이 됩니다.
이러한 트레이드오프들은 신뢰할 수 있는 라우팅 계층을 구축하는 일부입니다. 제공업체 폴백은 단순히 API 키 목록이 아닙니다. 이는 다양한 실패 조건에 대응하기 위한 정책입니다.
결론
다중 제공업체 LLM 라우팅은 시스템이 요청이 실패하는 이유를 구별할 때 더 신뢰성을 갖게 됩니다.
StudyHub의 실패 분류학(failure taxonomy)은 자격 증명 관련 문제, 일시적인 제공업체 장애, 유효하지 않은 모델 식별자, 그리고 추가 폴백 시도를 중단해야 하는 오류로 분리합니다.
각 카테고리는 다른 응답을 초래합니다: 관련 키를 쿨다운시키거나, 자격 증명에 페널티를 부과하지 않고 제공업체를 전환하거나, 오래된 모델 항목을 무효화하거나, 또는 빠르게 실패(fail fast)하는 것입니다.
정규화된 Python 예외 및 Redis 기반 공유 상태와 결합하여, 이 설계는 제공업체 장애 처리를 더욱 의도적이고 관찰 가능하게 만듭니다.
더 광범위한 교훈은 복구력(resilience)이 모든 것을 재시도하는 것에서 오는 것이 아니라는 것입니다. 그것은 무엇이 실패했는지 이해하고, 그 실패에 적절한 대응을 선택하며, 시스템이 아직 이해하지 못하는 사례를 조사할 수 있는 충분한 정보를 보존하는 데서 옵니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기