Repository navigation
fix(ai): 타임아웃 설정이 실제 호출 상한이 아니었고 문서 분석 재시도가 연속이었다 - #274
Merged
Merged
Conversation
OpenAI SDK 클라이언트의 max_retries 기본값이 2 라 LangChain 호출 1회가 HTTP 3회다. 코드가 그 값을 지정한 적이 없어 이 곱셈이 보이지 않았고, llm_*_timeout_sec 는 호출 상한이 아니라 실제 상한이 그 (1+n)배였다. ai_request_logs 는 LangChain 호출 1건에 한 행이라 latency_ms 가 숨은 시도의 합이다 — 운영 analyze.document 실패 4건이 타임아웃 60초 설정에서 전부 92.1~92.6초로 찍힌 이유다. settings.py 주석이 "타임아웃 30초 + 재시도 26초 = 56초" 로 관측한 것도 같은 현상이었는데 원인이 특정되지 않은 채 남아 있었다. 12개 ChatOpenAI 전부에 max_retries 를 명시한다(llm_max_retries, 기본 2 — 현 동작 유지). 게이트웨이 429 를 흡수하는 값이라 0 으로 내리지 않았지만 보이지 않는 채로 두지 않는다. 문서 분석만 0 이다 — 바깥 with_retry 와 곱해지면 한 메시지가 HTTP 를 6번 치고 로그 한 행이 여러 시도의 합이 된다. 그 with_retry 의 기본 대기는 1초(+지터 1초)라 사실상 연속 시도였다. 게이트웨이가 구간적으로 멎으면 두 시도가 같은 구간에 들어가 함께 죽는다: resume_id=18 이 연속 2시도(각 92초)로 실패해 4일간 FAILED 로 남았고, 재분석도 또 2시도 실패, 3분 뒤 세 번째가 37초에 성공했다. 입력 문제가 아니라 상류 간헐 정지다(Deepgram 배치 호출과 같은 성질). 시도를 3회로 늘리고 간격을 15초→30초로 벌린다. 소비자 prefetch 가 10 이라 한 문서가 오래 붙잡혀도 다른 분석을 막지 않는다.
llm_api_key 를 주지 않아 빈 문자열이 되면 ChatOpenAI 생성 자체가
OpenAIError("Missing credentials") 로 터진다. 로컬에는 ai/.env 가 있어
pydantic-settings 가 키를 채워 주므로 통과하고, .env 가 없는 CI 에서만
13건이 깨졌다. 픽스처가 키를 직접 준다.
검증은 .env 를 치우고 돌렸다 — 로컬에서 CI 와 같은 조건으로 463건 통과.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
운영
ai_request_logs를 훑다가analyze.document가 5건 중 4건 실패인 것을 발견해 따라간 결과입니다.1. 타임아웃 설정이 호출 상한이 아니었다
llm_pro_timeout_sec는 60초인데 실패가 전부 92.1~92.6초로 좁게 뭉쳐 있습니다. 운영 컨테이너에서 직접 확인했습니다:OpenAI SDK 클라이언트의
max_retries기본값이 2 라 LangChain 호출 1회가 HTTP 3회입니다. 코드가 이 값을 지정한 적이 없어(grep max_retries→ 임베딩 경로만) 곱셈이 보이지 않았습니다. 실제 상한은 설정값의 (1+n)배입니다.ai_request_logs는 LangChain 호출 1건에 한 행이므로latency_ms는 숨은 시도의 합입니다.settings.py주석이 이미 "타임아웃 30초 + 재시도 26초 = 56초" 로 같은 현상을 관측했지만 원인이 특정되지 않은 채 남아 있었습니다.→ 12개
ChatOpenAI전부에max_retries=settings.llm_max_retries를 명시합니다. 기본값 2 로 현 동작을 유지했습니다 — 게이트웨이 429 를 흡수하는 값이라(임베딩 경로에서 429 폭풍이 문서화되어 있음) 0 으로 내리면 재시도 없는 경로(질문 풀·꼬리질문·패널)가 더 약해집니다. 보이게·튜닝 가능하게 만드는 것이 이 변경의 목적입니다.문서 분석만
max_retries=0입니다 — 바깥에with_retry가 있어 두 층이 곱해지면 한 메시지가 HTTP 를 6번 치고 로그 한 행이 여러 시도의 합이 됩니다.2. 문서 분석 재시도가 사실상 연속이었다
with_retry의 대기 기본값은wait_exponential_jitter(initial=1)— 1초(+지터 1초) 입니다. 게이트웨이가 구간적으로 멎으면 두 시도가 같은 구간에 들어가 함께 죽습니다. 운영에서 4/4 가 그랬습니다.같은 이력서(
resume_id=18)의 실제 경로:입력 문제가 아니라 상류 간헐 정지입니다(Deepgram 배치 호출과 같은 성질 —
ai/CLAUDE.md §8). 그런 정지에는 간격을 둔 재시도가 약입니다.→
document_analysis_max_attempts(3) +document_analysis_retry_initial_sec(15)로 간격을 15초→30초로 벌립니다. 소비자prefetch가 10 이라 한 문서가 오래 붙잡혀도 다른 분석을 막지 않습니다(확인:ai_queue_prefetch: int = 10).테스트
ai/tests/test_llm_retry_budget.py(14건):ChatOpenAI의 클라이언트max_retries가 2 임을 고정 — 이 테스트가 지키는 것은 "기본값에 맡기지 않는다" 입니다.llm_max_retries반영 확인. 한 곳만 빠지면 그 경로만 조용히 3배로 돌아갑니다.max_retries == 0, 시도 3회, 간격 ≥10초.uv run pytest463건 통과 ·flake8·black --check통과.환경변수
docker-compose.yml에는 이름만 추가했습니다(값 없음) —test_compose_defaults.py가 지키는 규칙이고, PR #246 이 compose 기본값에 가려 무효가 된 사고의 재발 방지입니다..env.example·docs/environment.md동기화.기본값이 현 동작과 같으므로 운영
.env변경 없이 배포 가능합니다.🤖 Generated with Claude Code