# 뉴스 브리핑 LangGraph 워크플로우 전환 **날짜**: 2026-02-02 **작성자**: Claude **관련 파일**: `rb8001/app/services/skills/startup_news_skill.py`, `rb8001/app/services/workflows/headlines_workflow.py`, `rb8001/tests/test_headlines_workflow.py` **원칙 참조** (구현 전 필수 확인): - `311_backend_coding_principles.md`: LangGraph 워크플로우 (섹션 5), 계층 분리 - `312_writing-principles.md`: 핵심만 간결, 파일명:줄번호 - `315_테스트_원칙.md`: 테스트는 TDD로 진행 (Red → Green → Refactor) --- ## 1. 현재 문제 - **원칙 위반**: 뉴스 브리핑은 수집→필터→번역→포맷→전송 다단계 처리인데, 일반 함수로 구현되어 있음. 311 원칙 "복잡한 다단계 처리는 LangGraph 적극 활용" 위반. - **추적성 부족**: 각 단계별 실패 지점, 중간 상태, 실행 시간 등이 로그에 체계적으로 남지 않음. - **복구 불가**: 중간 단계 실패 시 처음부터 재실행해야 함 (체크포인트 없음). --- ## 2. LangGraph 전환 설계 ### 2.1 상태 모델 (HeadlinesState) | 필드 | 타입 | 설명 | |------|------|------| | channel_id | str | Slack 채널 ID | | naver_items | List[Dict] | 깡프로 헤드라인(json) | | naver_slack_text | str | 깡프로 슬랙 포맷 텍스트 | | sea_text | str | 동남아 섹션 텍스트 (없으면 "") | | terms | List[str] | 추출된 용어 (없으면 []) | | formatted_text | str | 최종 Slack 텍스트 | | message_ts | Optional[str] | 전송된 메시지 ts | | errors | List[str] | 각 단계 에러 | | extract_terms | bool | HEADLINES_EXTRACT_TERMS 결과 | ### 2.2 노드 구성 (입출력/에러 규칙 고정) 1. **fetch_naver_node**: `SkillCommands.fetch_naver_headlines(fmt="json")` → `naver_items` (실패 시 `errors` 추가, `naver_items=[]`) 2. **fetch_sea_node**: `SkillCommands.fetch_sea_headlines(fmt="json")` → `sea_text` (실패 시 `errors` 추가, `sea_text=""`) 3. **extract_terms_node**: `HEADLINES_EXTRACT_TERMS=true` & `naver_items` 존재 시 `GeminiHandler.extract_keywords()` → `terms` (실패 시 `errors` 추가, `terms=[]`) 4. **format_node**: `fetch_naver_headlines(fmt="slack")`로 `naver_slack_text` 확보 후 - `_insert_sea_section_into_headlines_text()`로 `sea_text` 삽입 - 기존 용어 삽입 규칙(명언 앞 삽입) 유지 → `formatted_text` 5. **send_node**: `WebClient(...).chat_postMessage()` → `message_ts` (실패 시 `errors` 추가) ### 2.3 워크플로우 인터페이스 - `create_workflow(checkpointer=None) -> CompiledGraph` - `async run_headlines_workflow(channel_id: str, date: Optional[str] = None) -> Dict` - 반환: `{"success": bool, "message_ts": Optional[str], "errors": List[str]}` ### 2.4 라우팅 ``` START → fetch_naver → fetch_sea → extract_terms → format → send → END ``` - `fetch_sea` 실패 시에도 계속 진행 (graceful degradation) - `extract_terms` 환경변수 false면 skip (terms 유지) - 각 노드 실패는 `errors` 리스트에 기록, 워크플로우 중단 금지 ### 2.5 체크포인터 - **AsyncSqliteSaver** 사용 (경량 워크플로우, 서버 재시작 시 재개 불필요) - DB 경로: `settings.LANGGRAPH_STATE_DIR` + `headlines_graph.sqlite` - thread_id: `f"headlines:{channel_id}:{date}"` (date=Asia/Seoul `YYYY-MM-DD`) ### 2.6 로깅 기준 - 노드 시작/완료 시 항목 수/문자 수 로그 - 오류는 `errors` 누적 + logger.warning/error --- ## 3. Phase별 작업 ### Phase 1: 워크플로우 파일 생성 - **파일**: `rb8001/app/services/workflows/headlines_workflow.py` (신규) - **내용**: HeadlinesState, 5개 노드, StateGraph 정의, `create_workflow()`/`run_headlines_workflow()` 제공 ### Phase 2: startup_news_skill.py 리팩토링 - **기존**: `run_headlines_job()` 함수 → **변경**: LangGraph 워크플로우 호출 - **로그**: 각 노드별 실행 로그 + 최종 message_ts 로그 - **시그니처 유지**: `run_headlines_job(channel_id)` 유지 (스케줄러 변경 최소화) ### Phase 3: 테스트 (TDD 진행, 315_테스트_원칙.md) - **파일**: `rb8001/tests/test_headlines_workflow.py` (신규) - **시나리오**: - 정상 플로우 (깡프로 + 동남아 + 용어) - 동남아 실패 시 graceful degradation - 용어 추출 skip - Slack 실패 시 errors 기록 - message_ts 반환 확인 (성공 케이스) ### Phase 4: 배포 및 검증 - **배포**: `git push origin main` (rb8001) → Gitea Actions 자동 배포 - **검증**: 내일 아침 09:10 스케줄 실행 시 로그에 노드별 실행 + message_ts 확인 --- ## 4. 남은 작업 - [ ] `headlines_workflow.py` 생성 (LangGraph StateGraph) - [ ] `startup_news_skill.py` 리팩토링 (워크플로우 호출) - [ ] `test_headlines_workflow.py` 작성 (TDD 진행) - [ ] 배포 및 스케줄 실행 검증 --- ## 참고 - `book/300_architecture/311_backend_coding_principles.md` - `book/300_architecture/312_writing-principles.md` - `book/300_architecture/315_테스트_원칙.md`