---
tags: [rb8001, prompt-db, hardcoded, ssot, research]
---

# 프롬프트 DB 폐루프 및 하드코딩 잔존 전수 조사 리서치

## 관련 문서

### 트러블슈팅 (이 리서치가 닫아야 할 문제)
- [프롬프트 DB 응답생성 폐루프 미연결](../troubleshooting/260310_프롬프트DB_응답생성_폐루프_미연결.md)
- [프롬프트 DB 부분 도입 상태와 하드코딩 프롬프트 잔존](../troubleshooting/260311_prompt_db_partial_adoption_and_hardcoded_prompts.md)

### 계획 (구현 방향)
- [프롬프트 동적 관리 시스템 계획](../plans/251225_프롬프트_동적관리_계획.md)
- [자기개선루프 프롬프트DB 23로컬24 통합실행기록](../plans/260304_자기개선루프_프롬프트DB_23로컬24_통합실행기록.md)

### 상위 원칙
- [0_VALUE Coding Principles](https://github.com/happybell80/0_VALUE/blob/main/02_Governance/coding-principles.md) — #4 폴백 절제, #7 설정 일관성

## 리서치 목적

두 트러블슈팅 문서를 완전히 닫기 위해 다음을 확정한다.
1. 하드코딩 프롬프트 전체 인벤토리 (위치·용도·우선순위)
2. 현재 DB 상태와 코드 실행 경로의 gap
3. 폐루프 연결에 필요한 최소 수정 범위
4. 구현 순서 제안

## 1. 현재 구조 (실행 경로)

```
POST /api/message
  → route_message_service()
    → llm_service.process_request()
      → openai_handler.generate() 또는 gemini_handler.generate()
        → _get_system_prompt()  ← 하드코딩 문자열 반환, DB 참조 없음
  → record_message_self_improvement_artifacts()  ← 응답 후 기록만
    → get_active_prompt_version()  ← 이벤트 로깅용으로만 사용
```

**끊어진 지점**: `_get_system_prompt()`가 DB를 보지 않음. 활성 버전 조회는 응답 생성 이후에만 발생.

## 2. 이미 있는 것

| 구성요소 | 상태 | 위치 |
|----------|------|------|
| prompt_templates 테이블 | 구현 완료 | `prompt_management_repository.py` |
| prompt_versions 테이블 | 구현 완료 | `prompt_management_repository.py` |
| prompt_events 테이블 | 구현 완료 | `prompt_management_repository.py` |
| 버전 생성/활성화/조회 API | 구현 완료 | `prompt_db_endpoint.py` |
| run_id 추적 | 구현 완료 | `message_tracking_service.py` |
| `context['system_instruction']` 주입 경로 | 구현 완료 | `openai_handler.py`, `gemini_handler.py` |
| `context['skip_default_prompt']` 플래그 | 구현 완료 | `companyx_grounding_service.py`에서 사용 중 |

## 3. 하드코딩 프롬프트 전수 인벤토리

2026-03-19 기준 `rb8001/app/` 코드 검색 결과.

### P1 — 핵심 대화 경로 (폐루프 필수)

| # | 파일:줄 | template_key 제안 | 용도 |
|---|---------|------------------|------|
| 1 | `llm/openai_handler.py:203` | `system_chat_openai` | 일반 대화 system prompt (OpenAI) |
| 2 | `llm/gemini_handler.py:602` | `system_chat_gemini` | 일반 대화 system prompt (Gemini) |
| 3 | `llm/llm_service.py:262` | `constraints_emotion_stats` | 감정/스탯 constraints 주입 |

이 3곳이 폐루프 연결 시 최우선 대상. DB 활성 버전이 있으면 하드코딩 대신 사용하도록 변경.

### P2 — 운영 스킬 프롬프트

| # | 파일:줄 | template_key 제안 | 용도 |
|---|---------|------------------|------|
| 4 | `llm/intent_parser.py:56` | `intent_parse_classify` | 의도 분류 프롬프트 |
| 5 | `llm/intent_parser.py:91` | `intent_parse_slot` | 슬롯 추출 프롬프트 |
| 6 | `brain/intent/intent_analyzer.py:195` | `intent_analysis_system` | 의도 분석 system prompt |
| 7 | `skills/naverworks_briefing.py:282` | `briefing_insight` | 브리핑 인사이트 생성 |
| 8 | `skills/naverworks_briefing.py:379` | `briefing_format_instruction` | 브리핑 포맷 지시 |
| 9 | `skills/naverworks_briefing.py:386` | `briefing_summary` | 브리핑 요약 생성 |
| 10 | `skills/dm_skill.py:316` | `dm_gmail_summary` | Gmail 요약 프롬프트 |
| 11 | `companyx_grounding_service.py:333` | `companyx_rag_grounding` | Company X RAG 답변 합성 |

### P3 — 보조/분석 프롬프트

| # | 파일:줄 | template_key 제안 | 용도 |
|---|---------|------------------|------|
| 12 | `coldmail_llm_classifier.py:19` | `coldmail_classify` | 콜드메일 분류 |
| 13 | `coldmail_processor.py:138` | `coldmail_extract_system` | 콜드메일 정보 추출 (system) |
| 14 | `coldmail_processor.py:159` | `coldmail_extract_prompt` | 콜드메일 정보 추출 (user) |
| 15 | `ir_deck_analyzer.py:355` | `ir_page_eval_system` | IR 페이지 평가 system |
| 16 | `ir_deck_analyzer.py:729` | `ir_overall_eval_system` | IR 종합 평가 system |
| 17 | `ir_deck_analyzer.py:856` | `ir_qa_system` | IR Q&A system |
| 18 | `ir_analyzer.py:235` | `ir_metric_extract_1` | IR 메트릭 추출 (1) |
| 19 | `ir_analyzer.py:307` | `ir_metric_extract_2` | IR 메트릭 추출 (2) |
| 20 | `workflows/ir_deck_workflow.py:175` | `ir_workflow_qa` | IR 워크플로우 Q&A |
| 21 | `workflows/headlines_workflow.py:144` | `headlines_term_extract` | 뉴스 용어 추출 |
| 22 | `diary/generator.py:112` | `diary_generate` | 일기 생성 |
| 23 | `llm/llm_service.py:314` | `calendar_parse` | 일정 파싱 |
| 24 | `llm/gemini_handler.py:458` | `gemini_task_prompt` | Gemini 전용 task |

**총 24곳** 하드코딩 프롬프트 확인.

## 4. 현재 DB 상태

- `prompt_templates`: `message_chat` 등 자동 생성 템플릿만 존재 (bootstrap 용도)
- `prompt_versions`: `v1` active 상태, 내용은 `{"role": "assistant", "constraints": ["be concise and actionable"]}` (placeholder)
- 실제 운영 프롬프트는 DB에 **0건** 적재됨

## 5. Gap 분석

| 항목 | 기대 상태 | 현재 상태 | Gap |
|------|----------|----------|-----|
| 폐루프 연결 | DB 활성 버전 → 응답 생성 전 주입 | 응답 후 기록만 | 주입 코드 없음 |
| P1 프롬프트 DB화 | 3건 DB 관리 | 0건 | 3건 미적재 |
| P2 프롬프트 DB화 | 8건 DB 관리 | 0건 | 8건 미적재 |
| P3 프롬프트 DB화 | 13건 DB 관리 | 0건 | 13건 미적재 |
| 기존 주입 경로 활용 | `system_instruction` + `skip_default_prompt` | RAG에서만 사용 | 일반 대화 미적용 |

## 6. 폐루프 연결 최소 수정 범위

### 6-1. P1 폐루프 연결 (3건) — 트러블슈팅 #3 종결 조건

**수정 파일 2개:**
1. `llm/openai_handler.py` — `_get_system_prompt()` 내부에서 DB 조회, 없으면 기존 하드코딩 fallback
2. `llm/gemini_handler.py` — 동일 패턴

**또는 상위 계층 1곳 수정:**
- `llm/llm_service.py`의 `process_request()` 진입 시 DB 활성 프롬프트를 읽어 `context['system_instruction']` + `context['skip_default_prompt']`로 전달
- 이미 `companyx_grounding_service.py`에서 같은 패턴 사용 중이므로 검증된 경로

**권장**: 상위 계층 1곳 수정 방식. 이유:
- handler 2개를 각각 수정하는 것보다 진입점 1곳이 변경 범위 작음
- `skip_default_prompt` 패턴이 이미 RAG에서 검증됨
- 향후 P2, P3 확장 시에도 같은 진입점에서 template_key만 분기

### 6-2. P1 프롬프트 DB 적재 — 트러블슈팅 #7 부분 종결

P1 3건의 현재 하드코딩 내용을 DB `prompt_versions`에 v1으로 적재.
- `system_chat_openai` — openai_handler의 `_get_system_prompt()` 내용
- `system_chat_gemini` — gemini_handler의 `_get_system_prompt()` 내용
- `constraints_emotion_stats` — llm_service의 constraints XML 내용

### 6-3. 검증 기준 (계획 문서 Phase 2 기준)

1. DB에서 `system_chat_openai` v2를 활성화 → 다음 `/api/message` 응답에 v2 내용 반영 확인
2. DB 조회 실패 시 기존 하드코딩 fallback 정상 동작 확인
3. `prompt_events.version_id`가 실제 사용된 버전과 일치 확인
4. 기존 RAG (`companyx_grounding_service`) 경로 영향 없음 확인

## 7. 구현 순서 제안

| 단계 | 내용 | 종결하는 문서 |
|------|------|-------------|
| **1단계** | P1 3건 DB 적재 + 폐루프 연결 (`llm_service.py` 1곳 수정) | 트러블슈팅 #3 (폐루프 미연결) |
| **2단계** | P2 8건 DB 적재 + template_key 분기 확장 | 트러블슈팅 #7 (하드코딩 잔존) 부분 종결 |
| **3단계** | P3 13건 DB 적재 | 트러블슈팅 #7 완전 종결 |
| **4단계** | A/B 실험, 자동 승격/롤백 | 계획 문서 Phase 4 |

## 8. 문서 세트 종결 구조

```
문제 정의
  ├─ 260310_프롬프트DB_응답생성_폐루프_미연결.md         ← 1단계로 닫힘
  └─ 260311_prompt_db_partial_adoption_and_hardcoded.md  ← 2~3단계로 닫힘

원인 조사
  └─ 260319_프롬프트DB_폐루프_및_하드코딩_잔존_전수조사_리서치.md  ← 본 문서

해결 방안
  ├─ 251225_프롬프트_동적관리_계획.md                    ← 전체 설계 (Phase 1~4)
  └─ 260304_자기개선루프_프롬프트DB_통합실행기록.md       ← 실행 기록

구현 완료
  └─ (worklog 작성 예정)                                ← 단계별 종결 시 작성
```

## 9. 캐싱 전략

프롬프트는 매 요청마다 변하지 않으므로 메모리 캐싱이 필수.

| 항목 | 값 | 근거 |
|------|---|------|
| 캐시 위치 | `llm_service.py` 내 모듈 레벨 dict | 프로세스 내 공유, 외부 의존 없음 |
| TTL | 60초 | 프롬프트 변경 빈도 낮음, 1분 내 반영이면 운영 충분 |
| 캐시 키 | `template_key` | 1:1 매핑 |
| 무효화 | TTL 만료 시 자동. 즉시 반영 필요 시 컨테이너 재시작 또는 `/api/prompt-db/cache/invalidate` 엔드포인트 추가 (2단계에서 검토) |
| 실패 처리 | 캐시 miss + DB 조회 실패 → 하드코딩 fallback (`safe_prod`) |

## 10. 리스크

| 리스크 | 대응 |
|--------|------|
| DB 조회 지연으로 응답 속도 저하 | 캐싱으로 해소 (§9). 첫 요청만 DB 조회, 이후 60초간 메모리 |
| DB 장애 시 응답 불가 | 하드코딩 fallback 유지 (`safe_prod` 모드) |
| 23에서 rb8001 코드 동시 수정 중 | P1 수정 범위가 `llm_service.py` 1곳으로 좁아 충돌 가능성 낮음, 단 사전 확인 필요 |
| 캐시와 DB 불일치 | 최대 60초 지연 허용. 즉시 반영이 필요한 장애 대응은 컨테이너 재시작으로 해소 |

## 11. 결론

- 폐루프 연결은 `llm_service.py` 1곳 수정으로 가능. 이미 검증된 `system_instruction` + `skip_default_prompt` 패턴 활용.
- 하드코딩 프롬프트 총 24건 확인. P1(3건) → P2(8건) → P3(13건) 순서로 DB 이관.
- 1단계(P1) 완료 시 트러블슈팅 #3 종결 가능. 2~3단계 완료 시 트러블슈팅 #7 종결 가능.
