"AI가 갑자기 멍청해졌어요" — 에러 로그는 0건이었습니다
"AI가 갑자기 멍청해졌어요." 내부 프로젝트 관리 도구에 붙여 둔 AI 채팅에 이런 불만이 들어오기 시작했습니다. 방금 전까지 나누던 이야기를 AI가 처음 듣는 것처럼 되묻는다는 내용이었습니다.
로그부터 뒤졌지만 에러는 한 건도 없었습니다. 타임아웃도, 폴백 실패도, 5xx도 없었습니다. 시스템 지표는 전부 정상인데 사용자 체감만 나빠진, 제일 골치 아픈 종류의 문제였습니다.
원인을 따라가 보니 대화 연속성의 열쇠가 엉뚱한 회사로 배달되고 있었습니다. A사가 발급한 대화 체인 id를 B사에 들이밀고 있었던 겁니다. 그것도 한 곳이 아니라 세 경로에서.
에러 없이 끊기는 대화
이 채팅은 LLM provider를 여러 개 씁니다. 주력은 A사, 장애 대비 폴백은 B사, 이미지 인식(vision)은 B사만 지원합니다. 대화 연속성은 provider가 응답마다 발급하는 interaction id를 다음 턴에 넘겨주는 체인 방식으로 유지합니다.
핵심은 이 interaction id가 provider 종속이라는 점입니다. A사의 id는 A사 서버에 쌓인 대화 기록을 가리키는 열쇠입니다. 이 열쇠를 B사에 넘기면 B사 입장에선 모르는 id인데, 에러를 내는 대신 새 대화로 조용히 처리하는 경우가 많습니다.
그래서 체인 단절은 장애로 잡히지 않습니다. HTTP 200에 문장도 멀쩡한 응답이 오지만, 내용은 맥락을 전부 잃은 대답입니다. 사용자에게는 그게 "AI가 멍청해졌다"로 보입니다.
애초에 provider를 여러 개 쓰게 된 건 취향이 아니라 429 폭탄 때문이었습니다. 그 배경은 앞서 따로 정리해 두었습니다.

체인이 끊기는 세 갈래
웹 요청부터 왕복 루프, 게이트웨이, provider 호출부까지 파이프라인 전체를 정밀 리뷰했습니다. 체인 id가 남의 회사로 넘어가는 경로가 세 곳 나왔습니다.
경로 | 무슨 일이 벌어지나 |
|---|---|
1. fallback 후속 턴 | 텍스트 턴에서 폴백 B사가 응답을 생성했는데, 후속 도구 결과 턴이 주력 A사로 가면서 B사의 id를 들고 감 |
2. 스레드 저장 | 스레드가 체인 소유 provider를 저장하지 않아, 이미지(B사) 대화 뒤 텍스트 턴이 A사로 가면서 B사 id를 들고 감 |
3. 이미지 첨부 | A사 체인으로 진행 중인 스레드에 이미지가 붙으면 vision을 지원하는 B사로 가는데, 이때 A사 id를 그대로 전달 |
셋의 공통 원인은 코드 한 줄의 실수가 아니었습니다. "이 id의 주인이 누구인가"라는 정보가 시스템 어디에도 없었습니다. id는 꼬박꼬박 저장하면서 소유자는 저장하지 않은 겁니다.

id의 주인을 상태로 만들다
여기서 인지가 한 번 바뀌었습니다. 처음엔 경로별 분기 버그 세 개라고 생각했지만, 파보니 대화 체인 id 자체가 provider 종속 상태였습니다. 문자열 하나가 아니라 (id, 소유 provider) 쌍이어야 비로소 온전한 상태입니다.
고친 내용은 세 가지입니다.
후속 턴은 응답을 만든 provider로 고정합니다. 폴백 B사가 응답을 생성했다면 그 턴의 도구 결과 반환도 B사로 갑니다. 기본 provider 복귀는 체인이 끝난 다음의 일입니다.
스레드에 chatProvider 필드를 신설했습니다. 매 턴 체인 소유 provider를 저장하고, 다음 턴은 그 값을 보고 목적지를 정합니다.
provider가 강제로 바뀌는 턴(이미지 첨부 등)은 이전 id를 끌고 가지 않고 새 vision 체인으로 시작합니다. 대신 컨텍스트 단절을 로그에 명시적으로 남깁니다.
세 번째가 특히 중요했습니다. 어쩔 수 없는 단절을 조용히 넘기면 그 비용은 "이유 모를 맥락 상실"이라는 형태로 사용자에게 전가됩니다. 단절 자체는 못 막아도 기록은 남길 수 있습니다.
고정과 복원력의 양립
그런데 provider를 고정하는 순간 새 문제가 생깁니다. A사 장애 때 B사로 넘어가라고 만든 게 폴백인데, "이 체인은 A사 것"이라고 무조건 고정하면 폴백이 영영 못 뜹니다. 고정은 체인을 지키는 대신 복원력을 죽입니다.
결론은 규칙 세분화였습니다. 지정된 provider가 기본 provider와 같으면 fallback을 허용합니다. 평상시 상태이니 복원력이 우선입니다. 반대로 지정이 기본과 다르면 고정합니다. 이미 폴백 체인이 진행 중이라는 뜻이니 체인 보호가 우선입니다.

회귀 테스트 4건이 세 구멍을 각각 고정하고 있습니다. 그리고 리뷰 산출물로 수정 목록만 남기지 않았습니다. 이상 없음을 확인한 지점(업로드 안전성, 에러 경로)과, 고치지 않고 관찰만 하기로 한 항목(안전망 과발동 — 낭비가 재조회 1회 수준)을 구분해 기록했습니다.
이 채팅 시스템 위에서 AI 여러 명에게 회의를 시키는 실험도 돌리고 있는데, 그쪽은 그쪽대로 전혀 다른 종류의 삽질이 있었습니다.
멀티 provider 채팅을 운영한다면
대화 연속성 키(interaction id 등)가 provider 종속인지 먼저 확인한다 — 종속이면 키와 소유 provider를 반드시 쌍으로 저장한다
fallback이 응답을 만든 턴의 후속 처리(도구 결과 반환 등)가 어느 provider로 가는지 추적한다
기능 차이(vision 등)로 provider가 강제 전환되는 경로를 전부 나열하고, 전환 시 새 체인 시작 + 단절 기록으로 처리한다
고정 규칙에는 예외 조건을 명시한다 — 전부 고정하면 복원력이 죽고, 전부 유연하면 체인이 깨진다
정밀 리뷰가 끝나면 수정 목록 외에 이상 없음 확인 목록과 관찰 항목도 함께 남긴다
provider가 하나뿐이라면 이 모든 게 불필요한 복잡도입니다. 체인 없이 매 턴 전체 맥락을 보내는 stateless 방식이라면 문제 자체가 없습니다. 멀티 provider의 진짜 비용은 API 키 두 개가 아니라 이런 상태 관리에서 나옵니다.
보강 과제도 하나 남아 있습니다. 새 체인으로 시작할 때 직전 대화 요약을 첫 턴에 접붙이면 단절의 손실을 더 줄일 수 있습니다. 수정 배포 이후, "AI가 멍청해졌다"는 불만은 들어오지 않고 있습니다.