[STK8-05] ML sentiment.py Codex fallback 추가
작업 내용 (설계 의도)
변경 사항
sentiment.py의 _claude_runner는 현재 subprocess.run(["claude", "-p", ...]) 단일 호출만 한다.
Claude 실패(subprocess 예외·빈 출력) 시 codex -q로 동일 프롬프트를 재시도하는
_fallback_runner를 추가한다.
analyze_headlines·analyze_headlines_v2의 기본 runner 파라미터를
_claude_runner → _fallback_runner로 교체한다.
runner 파라미터 인터페이스는 그대로 유지하므로 테스트 코드 변경 없음.
# 기존
def _claude_runner(prompt: str) -> str: ...
# 추가
def _codex_runner(prompt: str) -> str: ...
def _fallback_runner(prompt: str) -> str:
# 1차: Claude
# 실패 시 2차: Codex
# 둘 다 실패 시: "" 반환 (기존 parse 실패 시 중립 처리 활용)실패 감지 조건
subprocess.TimeoutExpired/subprocess.CalledProcessErrorresult.stdout이 빈 문자열- stdout에
context length/token limit/rate limit포함
양쪽 다 실패 시
빈 문자열("") 반환 → 기존 _parse_v2_response / parse_sentiment의 중립 fallback이 작동.
ML 레이어에서 예외를 던지지 않으므로 감성분석 실패가 전체 추천 흐름을 차단하지 않는다.
다이어그램
처리 흐름
sequenceDiagram participant AH as analyze_headlines_v2 participant FR as _fallback_runner participant CL as _claude_runner participant CO as _codex_runner AH->>FR: prompt FR->>CL: prompt alt Claude 성공 (non-empty stdout) CL-->>FR: response FR-->>AH: response else Claude 실패 FR->>CO: prompt alt Codex 성공 CO-->>FR: response FR-->>AH: response else Codex 실패 FR-->>AH: "" (빈 문자열) end end
테스트 케이스
- Claude 성공 (non-empty) → Codex 미호출, Claude 응답 반환
- Claude 빈 응답 → Codex fallback 호출됨
- Claude TimeoutExpired → Codex fallback 호출됨
- Codex 성공 → Codex 응답 반환
- Claude·Codex 둘 다 실패 → 빈 문자열 반환,
parse_sentiment중립 처리 - 기존 테스트에서
runner=mock_runner주입 시 동작 변경 없음