Phase 25: RAG sources in collapsible box + Korean thinking enforcement

- agent_service: yield {"__sources": [...]} token instead of __meta for sources
- agent_service: inject Korean-only rule at top of system message before date
- config.py: strengthen Korean thinking instruction in system prompt
- ROADMAP: add Phase 25 entry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sal
2026-06-01 16:14:59 +09:00
parent efc5fe6961
commit 589946ab36
3 changed files with 30 additions and 6 deletions
+1 -1
View File
@@ -59,7 +59,7 @@ class Config(BaseSettings):
whisper_model_size: str = "small" whisper_model_size: str = "small"
tts_voice: str = "Yuna" # macOS say 명령어 한국어 음성 tts_voice: str = "Yuna" # macOS say 명령어 한국어 음성
system_prompt: str = """모든 응답과 내부 사고 과정을 반드시 한국어로 작성하세요. system_prompt: str = """모든 사고 과정(thinking)과 답변은 반드시 한국어로 작성하세요. 영어 사용 절대 금지.
당신의 이름은 '율봇'입니다. 친절하고 따뜻한 한국어 상담 도우미입니다. 당신의 이름은 '율봇'입니다. 친절하고 따뜻한 한국어 상담 도우미입니다.
육아와 금융 두 분야를 전문으로 합니다. 육아와 금융 두 분야를 전문으로 합니다.
+17
View File
@@ -393,6 +393,22 @@ cd youlbot-webui && python app.py
--- ---
## ✅ Phase 25 — RAG 출처 전용 접기/펼치기 박스
**배경**: RAG 검색 출처가 사고 과정(thinking)과 같은 `__meta` 토큰으로 섞여 "💭 분석 완료" 박스 안에 표시되던 문제.
**구현 내용**:
- `agent_service.py`: 출처를 `{"__meta": "..."}` 개별 토큰 대신 `{"__sources": [{filename, page}, ...]}` 단일 토큰으로 yield
- `youlbot-webui/app.py`:
- `_sources_html()` 헬퍼 추가 — `<details>` 기반 접기/펼치기
- chatbot 바로 아래 `source_box = gr.HTML()` 컴포넌트 추가
- `respond()`에서 `__sources` 토큰 처리 → 답변 완료 후 "📄 출처 (N개)" 박스 표시
- `youlbot-telegram/bot.py`: `__sources` 토큰 skip 처리 추가
**난이도**: 하 | **임팩트**: 중간 (UX 개선 — 출처와 사고 과정 분리)
---
## Phase 20 — RAG 품질 자동 평가 (RAGAS) ★☆☆ ## Phase 20 — RAG 품질 자동 평가 (RAGAS) ★☆☆
**배경**: 청킹 전략·검색 파라미터·Reranker 변경 시 답변 품질이 실제로 나아졌는지 수치로 확인할 방법이 없다. **배경**: 청킹 전략·검색 파라미터·Reranker 변경 시 답변 품질이 실제로 나아졌는지 수치로 확인할 방법이 없다.
@@ -484,6 +500,7 @@ Phase 20 RAGAS 평가 → Phase 15 (모델선택) → Phase 16 (Docke
| Phase 22 REST API | ✅ 완료 | — | — | — | | Phase 22 REST API | ✅ 완료 | — | — | — |
| Phase 23 WebUI 분리 | ✅ 완료 | — | — | — | | Phase 23 WebUI 분리 | ✅ 완료 | — | — | — |
| Phase 24 사고 과정 UI 분리 | ✅ 완료 | — | — | — | | Phase 24 사고 과정 UI 분리 | ✅ 완료 | — | — | — |
| Phase 25 RAG 출처 전용 박스 | ✅ 완료 | — | — | — |
| Phase 20 RAGAS 평가 | 🔲 신규 | 중간 | 중간 | 1순위 | | Phase 20 RAGAS 평가 | 🔲 신규 | 중간 | 중간 | 1순위 |
| Phase 15 모델 선택 | 🔲 미완 | 중간 | 중간 | 4순위 | | Phase 15 모델 선택 | 🔲 미완 | 중간 | 중간 | 4순위 |
| Phase 16 Docker | 🔲 미완 | 높음 | 중간 | 5순위 | | Phase 16 Docker | 🔲 미완 | 높음 | 중간 | 5순위 |
+12 -5
View File
@@ -79,7 +79,12 @@ class AgentService:
async def call_model(state: MessagesState, config: RunnableConfig) -> dict: async def call_model(state: MessagesState, config: RunnableConfig) -> dict:
from datetime import date from datetime import date
system_content = f"오늘 날짜: {date.today().isoformat()}\n\n" + self._system_prompt system_content = (
"【언어 규칙】모든 사고 과정(thinking)과 답변을 반드시 한국어로 작성하세요. "
"영어 사용 금지. Think in Korean only.\n\n"
f"오늘 날짜: {date.today().isoformat()}\n\n"
+ self._system_prompt
)
if self._profile_repo: if self._profile_repo:
profile = self._profile_repo.get_all(self._user_id) profile = self._profile_repo.get_all(self._user_id)
if profile: if profile:
@@ -367,11 +372,13 @@ class AgentService:
print(f"[Agent] 대화 저장 실패: {e}") print(f"[Agent] 대화 저장 실패: {e}")
if self._rag_show_sources and self._source_buffer: if self._rag_show_sources and self._source_buffer:
yield {"__meta": "\n\n[참고 문서]\n"} sources = []
for src in self._source_buffer: for src in self._source_buffer:
filename = os.path.basename(src["source"]) entry = {"filename": os.path.basename(src["source"])}
page = f" {src['page']}페이지" if "page" in src else "" if "page" in src:
yield {"__meta": f"- {filename}{page}\n"} entry["page"] = src["page"]
sources.append(entry)
yield {"__sources": sources}
def reset(self) -> None: def reset(self) -> None:
"""새 thread_id로 대화 히스토리를 초기화한다.""" """새 thread_id로 대화 히스토리를 초기화한다."""