From 32537766fc8fe97dcb41b65e7c028c90782487f7 Mon Sep 17 00:00:00 2001 From: happybell80 Date: Mon, 16 Mar 2026 00:58:40 +0900 Subject: [PATCH] docs: inventory llm model call paths --- ...ace_config_로컬이식_통합리서치.md | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/journey/research/260315_모델SSOT_하드코딩_분산과_workspace_config_로컬이식_통합리서치.md b/journey/research/260315_모델SSOT_하드코딩_분산과_workspace_config_로컬이식_통합리서치.md index a14c0fa..765a39f 100644 --- a/journey/research/260315_모델SSOT_하드코딩_분산과_workspace_config_로컬이식_통합리서치.md +++ b/journey/research/260315_모델SSOT_하드코딩_분산과_workspace_config_로컬이식_통합리서치.md @@ -177,6 +177,114 @@ tags: [research, llm, ssot, workspace-config, hardcoding, robeing] - `provider` + `model`을 명시적으로 받고 factory가 둘을 함께 검증 - 현재 로빙처럼 서비스 코드 곳곳에서 Gemini SDK 직접 호출, OpenAI handler 직접 생성, fallback 배열 직접 선언이 섞여 있는 구조는 외부 사례의 "요청 입력은 유연하게, 내부 선택은 단일화" 패턴과 다릅니다. +## 현재 코드 인벤토리 + +### 1. 기본 모델 원천과 기본 handler 생성 + +- [rb8001/app/core/config.py](../../../../rb8001/app/core/config.py) + - `DEFAULT_LLM_MODEL` 기본값 문자열이 코드에 남아 있습니다. + - 현재 설정 진입점으로는 맞지만, 최종 목표는 `workspace-config/runtime.env` 중심 해석입니다. +- [rb8001/app/services/llm/llm_service.py](../../../../rb8001/app/services/llm/llm_service.py) + - 이미 `settings.DEFAULT_LLM_MODEL`로 기본 handler를 생성합니다. + - 새 추상화 계층을 만들기보다 이 파일을 단일 진입점으로 정리하는 편이 최소 변경입니다. + +### 2. SSOT를 깨는 직접 우회 경로 + +- [rb8001/app/services/llm/gemini_handler.py](../../../../rb8001/app/services/llm/gemini_handler.py) + - `os.getenv("GEMINI_MODEL", model or settings.DEFAULT_LLM_MODEL)` 우회가 있습니다. + - CLI 경로도 `GEMINI_MODEL`을 다시 읽습니다. +- [rb8001/app/router/llm_endpoint.py](../../../../rb8001/app/router/llm_endpoint.py) + - 스트리밍 엔드포인트가 `GeminiHandler(model=request.model)`를 직접 생성합니다. +- [rb8001/app/services/coldmail_llm_classifier.py](../../../../rb8001/app/services/coldmail_llm_classifier.py) + - `google.generativeai`를 직접 import하고 `genai.GenerativeModel(GEMINI_MODEL)`을 직접 생성합니다. +- [rb8001/app/services/ir_analyzer.py](../../../../rb8001/app/services/ir_analyzer.py) + - `fallback_models = ["gemini-2.5-flash", "gemini-2.0-flash", "gemini-2.0-flash-lite"]`가 코드에 하드코딩되어 있습니다. + - Gemini File Search API 키도 함수 내부에서 직접 읽습니다. + +### 3. 이미 상대적으로 정리된 경로 + +- [rb8001/app/services/gemini_file_search_client.py](../../../../rb8001/app/services/gemini_file_search_client.py) + - 모델 인자를 받되 기본값은 `settings.DEFAULT_LLM_MODEL`을 사용합니다. +- [rb8001/app/services/skills/naverworks_briefing.py](../../../../rb8001/app/services/skills/naverworks_briefing.py) + - `settings.DEFAULT_LLM_MODEL`을 사용하는 경로가 이미 있습니다. +- [rb8001/app/services/workflows/headlines_workflow.py](../../../../rb8001/app/services/workflows/headlines_workflow.py) + - 기본 모델은 `settings.DEFAULT_LLM_MODEL`을 쓰지만, `or GeminiHandler(default_model)` 직접 생성이 남아 있습니다. + +### 4. provider 추상화는 이미 일부 존재한다 + +- [rb8001/app/services/llm/openai_handler.py](../../../../rb8001/app/services/llm/openai_handler.py) + - OpenAI handler는 이미 구현돼 있습니다. +- [rb8001/app/services/llm/models.py](../../../../rb8001/app/services/llm/models.py) + - `LLMRequest.model` 필드가 이미 존재합니다. +- 즉 "모델 override를 받을 구조"는 이미 있고, 부족한 것은 handler 선택을 한 곳으로 모으는 일입니다. + +### 5. skill-slack 쪽 하드코딩 호출 + +- [skill-slack/app/services/digest.py](../../../../skill-slack/app/services/digest.py) + - `model: "gemini-2.5-flash"` 하드코딩 +- [skill-slack/app/services/action_extractor.py](../../../../skill-slack/app/services/action_extractor.py) + - `model: "gemini-2.5-flash"` 하드코딩 +- [skill-slack/app/services/summarizer.py](../../../../skill-slack/app/services/summarizer.py) + - `skill_level`에 따라 `gemini-2.5-flash` / `gemini-2.5-pro`를 직접 선택합니다. +- [skill-slack/app/core/config.py](../../../../skill-slack/app/core/config.py) + - 현재 LLM 기본 모델 설정 자체가 없습니다. + +## 최소 변경 원칙 + +### 1. 새 추상화 계층을 더 만들지 않는다 + +- 기존 [rb8001/app/services/llm/llm_service.py](../../../../rb8001/app/services/llm/llm_service.py)를 단일 진입점으로 강화하는 편이 맞습니다. +- 새 `manager`, 새 `registry`, 새 `gateway wrapper`를 추가하는 것은 현재 문제를 닫는 데 필수가 아닙니다. + +### 2. 우선순위는 추가보다 제거다 + +- 1순위 제거 대상은 아래입니다. + - `GeminiHandler(...)` 직접 생성 + - `genai.GenerativeModel(...)` 직접 생성 + - 파일별 fallback 배열 + - `GEMINI_MODEL` 별도 env 우회 +- 즉 핵심은 새 기능 추가가 아니라 기존 우회 경로 삭제입니다. + +### 3. 기존 요청 스키마를 재사용한다 + +- [rb8001/app/services/llm/models.py](../../../../rb8001/app/services/llm/models.py)의 `LLMRequest.model`을 그대로 활용하는 편이 맞습니다. +- API 입력 모델 override를 위해 새 request schema를 추가하지 않아도 됩니다. + +### 4. 과도기 허용 범위를 더 넓히지 않는다 + +- 기본값 변경은 `workspace-config/runtime.env`와 과도기 `rb8001/.env`까지만 허용합니다. +- 요청 단위 override는 API 입력 `model`만 허용합니다. +- 그 외 서비스별 별도 `.env`, 별도 `GEMINI_MODEL`, 파일별 fallback 리스트는 더 늘리지 않습니다. + +## 정확히 바꿔야 할 지점 + +### 1. 바로 정리할 파일 + +- [rb8001/app/services/llm/gemini_handler.py](../../../../rb8001/app/services/llm/gemini_handler.py) + - `GEMINI_MODEL` env 우회 제거 +- [rb8001/app/services/llm/llm_service.py](../../../../rb8001/app/services/llm/llm_service.py) + - 동적 handler 생성과 fallback 정책을 중앙화 +- [rb8001/app/router/llm_endpoint.py](../../../../rb8001/app/router/llm_endpoint.py) + - router의 직접 `GeminiHandler` 생성을 `LLMService` 경유로 변경 +- [rb8001/app/services/ir_analyzer.py](../../../../rb8001/app/services/ir_analyzer.py) + - 파일 내부 fallback 배열 제거 또는 중앙 정책으로 이동 +- [rb8001/app/services/coldmail_llm_classifier.py](../../../../rb8001/app/services/coldmail_llm_classifier.py) + - 직접 Gemini SDK 호출 제거 또는 공용 LLM 경유로 정리 +- [skill-slack/app/services/digest.py](../../../../skill-slack/app/services/digest.py) +- [skill-slack/app/services/action_extractor.py](../../../../skill-slack/app/services/action_extractor.py) +- [skill-slack/app/services/summarizer.py](../../../../skill-slack/app/services/summarizer.py) + - 하드코딩 모델 문자열을 설정값 또는 API 입력값으로 승격 + +### 2. 굳이 지금 추가하지 않아도 되는 것 + +- 새 provider enum 파일 +- 새 config loader 계층 +- 새 fallback registry 파일 +- 새 gateway 패키지 +- 새 request model 타입 분기 + +위 항목들은 현재 문제를 닫는 데 필수가 아닙니다. 먼저 기존 구조를 줄이는 것이 우선입니다. + ## Interpretation ### 1. workspace-config는 값 저장 폴더가 아니라 런타임 계약입니다