스트리밍 실패 복구(failover)가 채팅 API가 동일한 프롬프트를 두 번 답변하게 만든 방법 — 첫 토큰 전 모델 전환 또는 아예 안 하기
요약
채팅 API에서 실패 복구(failover) 로직이 스트리밍 중 오류가 발생했을 때 프롬프트를 두 번 답변하게 만드는 문제를 분석했습니다. 이 문제는 첫 토큰 이후 모델을 전환하는 것이 사용자 경험에 부정적임을 보여줍니다.
핵심 포인트
- 실패 복구는 오직 전송된 토큰 0개 상태에서만 허용해야 합니다.
- 첫 토큰 이후의 모델 교체는 컨텍스트 불일치로 인해 사용자에게 혼란을 줄 수 있습니다.
- 재시도 루프 작성 시 스트림 상태를 면밀히 분류하는 것이 중요합니다.
클라이언트의 기록은 마치 언어 모델이 말실수를 한 것처럼 보였습니다. 캐싱에 대한 세 개의 잘린 문장 다음에, 약간 다른 목소리로 다시 답변된 완전히 동일한 질문이 하나의 연속적인 메시지로 이어 붙여져 있었습니다.
원인은 제 채팅 라우터의 실패 복구(failover) 로직에 있었습니다. 저는 50개 이상의 모델을 하나로 통합한 OpenAI-호환 /v1/chat/completions 엔드포인트를 운영하고(https://x402.freeq.one/tools/llm_chat.html), 에코 티어(eco tier)는 각 요청에 대해 가장 저렴하고 정상적인 제공업체를 선택합니다. 어느 날 밤, 상위 스트림 중 하나가 429 오류를 반환하기 시작했는데 — 하지만 연결을 수락하고 약 40개의 토큰을 스트리밍한 후에만 발생했습니다. 제 라우터는 429를 재시도 가능한(retryable) 것으로 간주하여, 프롬프트를 목록의 다음 제공업체로 다시 전송하고 새로운 스트림을 이전 스트림에 추가했습니다. 클라이언트의 SDK는 이 두 부분을 기쁘게 하나의 메시지로 붙였습니다.
해결책은 제가 이제 스트림 상태 머신(stream state machine)에서 강제하는 규칙입니다: 실패 복구는 오직 전송된 토큰 0개 상태에서만 합법적입니다. 연결 거부, 핸드셰이크 시의 401 또는 404, 콘텐츠 변화가 발생하기 전의 429 — 이들은 재라우팅하기에 안전하며 클라이언트는 전혀 알아차리지 못합니다. 첫 번째 콘텐츠 변화 이후에는 조용한 모델 교체가 단순한 실패보다 더 나쁩니다: 이미 하나의 목소리, 하나의 컨텍스트 창, 하나의 기능 세트에 전념했기 때문에, 잘린 첫 번째 절반에 두 번째 모델을 이어 붙이면 마치 하나의 키보드를 공유하는 두 개의 유령이 작성한 텍스트처럼 읽힙니다.
따라서 첫 토큰 이후에는 옵션이 다음과 같이 좁혀집니다: finish_reason을 설정하고 메타데이터에 오류 페이로드를 포함하여 스트림을 종료하거나, 명시적인 오류 이벤트를 방출하는 것입니다. 절대 추가(append)해서는 안 됩니다. 또한 요청당 플러시된 바이트 카운터도 추가하여 로그가 실패가 어떤 상태에서 발생했는지 증명할 수 있게 했습니다 —
변경 이후: 이중 답변 보고가 0건입니다. 여러 공급자 앞에 OpenAI 호환 shim을 실행하는 경우, 재시도 루프를 작성하기 전에 스트림 상태에 따라 상위(upstream) 오류를 분류하십시오. 대부분의 프록시 버그는 모델 선택에 있는 것이 아니라, 필요하다고 생각하지 않았던 상태 간 전환에 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기