tags: [worklog, llm, ssot, workspace-config, skill-news, skill-slack, rb8001]

# 260316 LLM 모델 SSOT 1차구현 및 로컬검증

## 상위 원칙

- [LLM 모델 SSOT 전환 계획](../plans/260316_llm_model_ssot_transition_계획.md)
- [모델 SSOT 하드코딩 분산과 workspace-config 로컬이식 통합 리서치](../research/260315_모델SSOT_하드코딩_분산과_workspace_config_로컬이식_통합리서치.md)
- [workspace-config 루트기준 SSOT와 하드코딩 분산 문제 오픈](../troubleshooting/260316_workspace_config_루트기준_SSOT와_하드코딩_분산_문제오픈.md)

## 상태

- completed
- server-runtime-verified

## 이번 작업에서 구현한 것

- `rb8001`
  - `LLMRequest.model`을 자유 문자열로 열어 새 모델명 변경 시 코드 수정이 필요 없게 맞췄습니다.
  - `internal_llm_service -> LLMRequest` 경로에 `model` 전달을 실제로 연결했습니다.
  - `/api/llm/*`, `/complete` 엔드포인트가 요청 모델값을 실제 내부 호출로 전달하게 맞췄습니다.
  - `GeminiHandler`의 `GEMINI_MODEL` 재조회와 `ir_analyzer.py`의 모델 fallback 체인을 제거했습니다.
  - `coldmail_llm_classifier.py`, `diary/generator.py`, `headlines_workflow.py`를 공용 LLM 경로 중심으로 정리했습니다.
- `skill-slack`
  - `digest.py`, `action_extractor.py`, `summarizer.py`의 모델 하드코딩을 제거하고 `DEFAULT_LLM_MODEL` 단일 설정을 따르게 맞췄습니다.
- `skill-news`
  - 서비스 코드가 `GEMINI_MODEL` 대신 `DEFAULT_LLM_MODEL`만 읽도록 바꿨습니다.
  - `docker-compose.yml`, `.env.example`, `README.md`를 같은 계약으로 맞췄습니다.
  - `app/core/config.py`를 추가해 서비스 내부 모델 설정 진입점을 한 곳으로 모았습니다.

## 로컬 검증

- `python3 -m compileall`
  - `rb8001` 변경 파일과 `skill-slack`, `skill-news` 앱 전체 기준으로 문법 검증 통과
- `pytest -q skill-news/tests/test_sea_news_filter.py skill-news/tests/test_sea_news_endpoint.py`
  - `5 passed`
  - `1 failed`
  - 실패 이유: 현재 로컬 테스트 환경에 `pytest-asyncio` 플러그인이 없어 async 테스트 1건이 실행되지 못함
- `pytest -q rb8001/tests/test_gemini_streaming.py rb8001/tests/test_headlines_workflow.py`
  - 실행 환경 의존성 부족으로 수집 단계에서 중단
  - 확인된 누락: `pydantic_settings`, `slack_sdk`

## 의미

- 이제 기본 목표였던 "모델 이걸로 바꾸자" 요청에 대해, 코드상 주요 기준은 `DEFAULT_LLM_MODEL`로 수렴했습니다.
- 특히 `skill-slack`과 `skill-news`가 서비스별 독자 모델 키를 따르지 않고 같은 키 이름을 쓰게 된 점이 1차 구현의 핵심입니다.
- 다만 `workspace-config/runtime.env`가 실제 컨테이너 런타임에서 서비스별 `.env`보다 어떤 우선순위로 적용되는지는 서버/실행 환경 확인이 아직 남아 있습니다.

## 남은 확인

- `rb8001` 테스트 환경 의존성(`pydantic_settings`, `slack_sdk`) 정리 후 관련 테스트 재실행

## 24서버 런타임 검증 및 보완

- 2026-03-16 51124 서버에서 `/home/admin/workspace-config/runtime.env`에 `DEFAULT_LLM_MODEL=gemini-2.5-flash-lite`를 추가했습니다.
- `rb8001`, `skill-slack`, `skill-news` 컨테이너 내부에서 `python -c "from app.core.config import settings; print(settings.DEFAULT_LLM_MODEL)"`로 모두 같은 값을 읽는 것을 확인했습니다.
- `rb8001`, `skill-slack`, `skill-news`의 health check가 모두 정상 응답하는 것을 확인했습니다.
- 검증 중 `skill-news/docker-compose.yml`의 `DEFAULT_LLM_MODEL=${DEFAULT_LLM_MODEL}`가 Compose 치환 시점에 빈값으로 덮일 수 있는 잔존 경로가 확인됐습니다.
- 이를 제거해 `skill-news`도 `env_file -> /home/admin/workspace-config/runtime.env` 경로만 따르도록 보정했습니다.
- 보정 후 `docker compose up -d --build --force-recreate` 기준으로 `robeing-skill-news`가 healthy 상태로 복구되는 것을 확인했습니다.

## 닫힘 판단

- `DEFAULT_LLM_MODEL` 공용 원천이 24서버 런타임에서 실제로 `workspace-config/runtime.env`로 수렴했습니다.
- `rb8001`, `skill-slack`, `skill-news`가 같은 키 이름을 같은 방식으로 해석하는 것을 확인했습니다.
- `skill-news`의 Compose 직접 오버라이드 잔존 경로까지 제거했으므로, 본 이슈는 1차 구현이 아니라 현재 기준 닫힘으로 판정합니다.
