Implement Phase 4~14: LangGraph Agent, RAG pipeline, Gradio Web UI, voice interface
- Upgrade LLM to Qwen3-14B-4bit with Thinking mode (MlxChatModel as LangChain BaseChatModel) - Add LangGraph ReAct agent with tool calling loop (search_documents, web_search, get_current_date, remember/recall_user_info) - Add RAG pipeline: BAAI/bge-m3 embeddings + Qdrant vector store + semantic chunking (SemanticSplitter via cosine similarity) - Replace fixed-size RecursiveCharacterTextSplitter with meaning-based SemanticSplitter (numpy only, no extra deps) - Add Gradio Web UI (app.py): chat, document ingestion, document management tabs - Add multi-user support (user_id isolation in DB + per-user agent cache + dropdown selector) - Add conversation history restore from MySQL on agent init (Phase 11) - Add UserProfileRepository for persistent user profile (remember/recall tools) - Add thread-local DB connections to fix pymysql thread-safety with LangGraph ToolNode - Add Phase 14 voice interface: Whisper STT (microphone → text) + macOS TTS (say -v Yuna) - Enforce search_documents-first policy in system prompt and tool descriptions - Update ROADMAP2.md: Phase 14 완료, Phase 13 청킹 부분 완료 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
---
|
||||
template: plan
|
||||
version: 1.4
|
||||
feature: rag-tool-chain
|
||||
date: 2026-04-27
|
||||
author: sal
|
||||
project: youlbot
|
||||
status: Draft
|
||||
---
|
||||
|
||||
# rag-tool-chain Planning Document
|
||||
|
||||
> **Summary**: mlx-lm을 LangChain `BaseChatModel`로 래핑하고, LangGraph 에이전트로 RAG + Tool Calling을 통합한다. 커스텀 구현은 최소화하고 LangChain/LangGraph 생태계를 최대한 활용한다.
|
||||
>
|
||||
> **Project**: youlbot
|
||||
> **Author**: sal
|
||||
> **Date**: 2026-04-27
|
||||
> **Status**: Draft
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
| Perspective | Content |
|
||||
|-------------|---------|
|
||||
| **Problem** | 현재 율봇은 모델 파라미터 지식에만 의존하며, Tool Calling·RAG를 직접 구현하면 유지보수 부담이 큼 |
|
||||
| **Solution** | mlx-lm을 `BaseChatModel`로 1회 래핑 후 LangGraph 에이전트와 LangChain RAG 생태계를 그대로 활용 |
|
||||
| **Function/UX Effect** | 육아·금융 전문 문서 기반 답변, Tool 호출로 동적 정보 처리 가능 |
|
||||
| **Core Value** | 커스텀 코드 최소화 — LangGraph가 Tool Calling 루프·상태 관리를 담당, LangChain이 RAG 파이프라인을 담당 |
|
||||
|
||||
---
|
||||
|
||||
## Context Anchor
|
||||
|
||||
| Key | Value |
|
||||
|-----|-------|
|
||||
| **WHY** | Tool Calling 루프·히스토리 관리·RAG 오케스트레이션을 직접 구현하면 버그 표면적이 넓고 유지보수 비용이 높음 |
|
||||
| **WHO** | 개발자 (sal) — 단독 개발 |
|
||||
| **RISK** | mlx-lm `BaseChatModel` 래퍼가 LangGraph와 완전 호환되는지 검증 필요 |
|
||||
| **SUCCESS** | `create_react_agent(llm, tools)` 수준의 단순한 에이전트 구성으로 RAG·Tool Calling 동작 |
|
||||
| **SCOPE** | Phase 1: mlx-lm BaseChatModel 래퍼 / Phase 2: RAG 파이프라인 / Phase 3: LangGraph 에이전트 통합 |
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
### 1.1 Architecture 결정 (Option B)
|
||||
|
||||
```
|
||||
mlx-lm
|
||||
└─ MlxChatModel(BaseChatModel) ← 1회 구현 (~80줄)
|
||||
└─ LangGraph ReAct Agent ← Tool Calling 루프 내장
|
||||
├─ RAG Tool ← LangChain-Qdrant 검색
|
||||
└─ 기타 Tools
|
||||
```
|
||||
|
||||
**LangGraph가 처리하는 것 (커스텀 불필요):**
|
||||
- Tool Calling 루프 (tool_call → 실행 → 재요청)
|
||||
- 대화 상태 및 히스토리 관리
|
||||
- 조건부 라우팅 (일반 답변 vs Tool 호출)
|
||||
- 최대 반복 횟수 제한
|
||||
|
||||
**LangChain이 처리하는 것 (커스텀 불필요):**
|
||||
- 문서 로딩 (PDF, TXT, MD)
|
||||
- 텍스트 청킹
|
||||
- 임베딩 생성
|
||||
- Qdrant 벡터 스토어 연동
|
||||
|
||||
**직접 구현하는 것 (최소):**
|
||||
- `MlxChatModel(BaseChatModel)` — mlx-lm 래퍼 (~80줄)
|
||||
- Tool 구현체 (비즈니스 로직 함수들)
|
||||
- IoC Container 배선
|
||||
|
||||
### 1.2 Background
|
||||
- 율봇의 도메인: 육아, 금융 — 신뢰성 있는 출처 기반 답변이 중요
|
||||
- Qwen2.5-7B-Instruct는 Tool Calling 네이티브 지원
|
||||
- LangGraph는 LangChain 공식 에이전트 오케스트레이션 프레임워크 (2024년 이후 표준)
|
||||
|
||||
---
|
||||
|
||||
## 2. Scope
|
||||
|
||||
### 2.1 In Scope
|
||||
|
||||
**Phase 1 — MlxChatModel 래퍼**
|
||||
- [ ] `services/model/mlx_chat_model.py` — `BaseChatModel` 서브클래스
|
||||
- `_generate()` — 단일 응답 (tool_call 포함 AIMessage 반환)
|
||||
- `_stream()` — 스트리밍 청크
|
||||
- `bind_tools()` — LangChain 표준 Tool 바인딩
|
||||
|
||||
**Phase 2 — RAG 파이프라인**
|
||||
- [ ] `services/rag/ingestion_service.py` — 문서 로드 → 청크 → 임베딩 → Qdrant 저장
|
||||
- [ ] `services/rag/retriever_service.py` — Qdrant 검색 → LangChain Tool 래핑
|
||||
- [ ] `config.py` 확장 — Qdrant, 임베딩 모델, RAG 설정
|
||||
|
||||
**Phase 3 — LangGraph 에이전트 통합**
|
||||
- [ ] `services/agent/agent_service.py` — LangGraph `create_react_agent` 조립
|
||||
- [ ] `services/agent/tools.py` — Tool 구현체 (@tool 데코레이터)
|
||||
- [ ] `container.py` 업데이트 — 신규 서비스 IoC 등록
|
||||
- [ ] 기존 `ChatService` 보존, `AgentService`로 선택적 전환
|
||||
|
||||
### 2.2 기존 코드 처리
|
||||
|
||||
| 기존 코드 | 처리 방향 |
|
||||
|-----------|-----------|
|
||||
| `AbstractModelService` + `MlxModelService` | 보존 (LangGraph 없는 단순 모드용) |
|
||||
| `ChatService` | 보존 |
|
||||
| `HistoryService` | LangGraph State로 대체 (Phase 3) |
|
||||
| `CompactService` | LangGraph Memory 전략으로 추후 대체 |
|
||||
| `EventBus` / `StreamTokenHandler` | LangGraph Streaming callback으로 대체 (Phase 3) |
|
||||
|
||||
### 2.3 Out of Scope
|
||||
- 웹 API 레이어 (FastAPI 등)
|
||||
- 문서 관리 UI
|
||||
- 외부 API 기반 Tool (날씨, 금융 API 등) — 추후 Phase
|
||||
- LangGraph 퍼시스턴스 (체크포인터, 장기 메모리) — 추후 Phase
|
||||
|
||||
---
|
||||
|
||||
## 3. Requirements
|
||||
|
||||
### 3.1 Functional Requirements
|
||||
|
||||
| ID | Requirement | Priority |
|
||||
|----|-------------|----------|
|
||||
| FR-01 | `MlxChatModel`이 LangChain `BaseChatModel` 인터페이스를 완전히 구현 | High |
|
||||
| FR-02 | `bind_tools()`로 Tool을 바인딩하면 모델이 tool_call을 생성 | High |
|
||||
| FR-03 | 문서(PDF, TXT, MD)를 Qdrant에 수집·저장하는 수집 파이프라인 | High |
|
||||
| FR-04 | LangGraph ReAct 에이전트가 RAG Tool을 자동 호출하여 컨텍스트 확보 | High |
|
||||
| FR-05 | Tool Calling 루프는 LangGraph가 관리 (직접 구현 금지) | High |
|
||||
| FR-06 | 스트리밍 출력은 LangGraph의 `stream()` 인터페이스 활용 | Medium |
|
||||
|
||||
### 3.2 Non-Functional Requirements
|
||||
|
||||
| Category | Criteria |
|
||||
|----------|----------|
|
||||
| 커스텀 코드 최소화 | LangGraph/LangChain이 제공하는 기능은 직접 구현하지 않음 |
|
||||
| 교체 용이성 | `MlxChatModel`을 `ChatOllama` 등으로 교체 시 `AgentService` 코드 변경 없음 |
|
||||
| 성능 | 임베딩 모델 Singleton으로 1회만 로딩 |
|
||||
| 안정성 | Tool 실행 실패 시 LangGraph가 에러를 메시지로 처리, 대화 중단 없음 |
|
||||
|
||||
---
|
||||
|
||||
## 4. Architecture
|
||||
|
||||
### 4.1 디렉터리 구조
|
||||
|
||||
```
|
||||
services/
|
||||
model/
|
||||
base.py # AbstractModelService (기존 유지)
|
||||
mlx_model.py # MlxModelService (기존 유지)
|
||||
mlx_chat_model.py # MlxChatModel : BaseChatModel (신규, Phase 1)
|
||||
rag/
|
||||
__init__.py
|
||||
ingestion_service.py # 문서 로드/청크/임베딩/Qdrant 저장 (Phase 2)
|
||||
retriever_service.py # Qdrant 검색 → LangChain Retriever (Phase 2)
|
||||
agent/
|
||||
__init__.py
|
||||
agent_service.py # LangGraph create_react_agent 조립 (Phase 3)
|
||||
tools.py # @tool 데코레이터 Tool 구현체 (Phase 3)
|
||||
chat/ # 기존 전부 유지
|
||||
db/ # 기존 전부 유지
|
||||
events/ # 기존 전부 유지
|
||||
ui/ # 기존 전부 유지
|
||||
```
|
||||
|
||||
### 4.2 MlxChatModel 인터페이스 (Phase 1 핵심)
|
||||
|
||||
```python
|
||||
class MlxChatModel(BaseChatModel):
|
||||
model_id: str
|
||||
max_tokens: int = 1024
|
||||
|
||||
def _generate(self, messages, stop=None, **kwargs) -> ChatResult:
|
||||
prompt = self._tokenizer.apply_chat_template(messages, ...)
|
||||
text = generate(self._model, self._tokenizer, prompt, ...)
|
||||
return ChatResult(generations=[ChatGeneration(message=AIMessage(content=text))])
|
||||
|
||||
def _stream(self, messages, stop=None, **kwargs) -> Iterator[ChatGenerationChunk]:
|
||||
prompt = self._tokenizer.apply_chat_template(messages, ...)
|
||||
for chunk in stream_generate(...):
|
||||
yield ChatGenerationChunk(message=AIMessageChunk(content=chunk.text))
|
||||
```
|
||||
|
||||
### 4.3 LangGraph 에이전트 흐름 (Phase 3)
|
||||
|
||||
```python
|
||||
# AgentService의 핵심 — 대부분 라이브러리가 처리
|
||||
llm = MlxChatModel(model_id=config.model_id)
|
||||
tools = [rag_search_tool, get_current_date_tool, ...]
|
||||
|
||||
agent = create_react_agent(llm, tools)
|
||||
|
||||
# 실행 — Tool Calling 루프, 히스토리, 에러 처리 모두 LangGraph 담당
|
||||
result = agent.invoke({"messages": [HumanMessage(content=user_input)]})
|
||||
```
|
||||
|
||||
### 4.4 RAG Tool 구조 (Phase 2 + Phase 3)
|
||||
|
||||
```python
|
||||
@tool
|
||||
def search_documents(query: str) -> str:
|
||||
"""육아·금융 관련 문서에서 관련 내용을 검색합니다."""
|
||||
docs = retriever.invoke(query)
|
||||
return format_docs(docs)
|
||||
```
|
||||
|
||||
### 4.5 의존성
|
||||
|
||||
```
|
||||
# 신규 추가
|
||||
langchain-core
|
||||
langchain-community # 문서 로더, HuggingFace 임베딩
|
||||
langchain-text-splitters
|
||||
langchain-qdrant # Qdrant 벡터 스토어
|
||||
langgraph # 에이전트 오케스트레이션
|
||||
sentence-transformers # 로컬 임베딩 (BAAI/bge-m3)
|
||||
qdrant-client
|
||||
```
|
||||
|
||||
### 4.6 Config 확장
|
||||
|
||||
```python
|
||||
# Qdrant
|
||||
qdrant_host: str = "localhost"
|
||||
qdrant_port: int = 6333
|
||||
qdrant_collection: str = "youlbot_docs"
|
||||
|
||||
# Embedding
|
||||
embedding_model_id: str = "BAAI/bge-m3"
|
||||
|
||||
# RAG
|
||||
rag_top_k: int = 3
|
||||
rag_score_threshold: float = 0.5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Success Criteria
|
||||
|
||||
- [ ] `MlxChatModel`이 `llm.invoke([HumanMessage(...)])` 호출로 정상 응답
|
||||
- [ ] `llm.bind_tools(tools).invoke(messages)` 호출 시 tool_call 포함 응답 생성
|
||||
- [ ] PDF/TXT 문서를 수집해 Qdrant에 저장, 쿼리로 관련 청크 검색 가능
|
||||
- [ ] LangGraph 에이전트가 RAG Tool을 자동 호출하고 결과를 반영하여 최종 답변 생성
|
||||
- [ ] `MlxChatModel`을 `ChatOllama`로 교체해도 `AgentService` 코드 변경 없음
|
||||
|
||||
---
|
||||
|
||||
## 6. Risks
|
||||
|
||||
| Risk | Impact | Likelihood | Mitigation |
|
||||
|------|--------|------------|------------|
|
||||
| `MlxChatModel`의 tool_call 파싱이 LangGraph와 불일치 | High | Medium | Phase 1에서 단위 검증 후 Phase 3 진행 |
|
||||
| Qwen2.5-7B의 ReAct 프롬프트 준수 불안정 | Medium | Medium | LangGraph 프롬프트 커스터마이징, few-shot 추가 |
|
||||
| 로컬 임베딩 모델(BGE-M3) 최초 로딩 시간 (~30초) | Medium | High | Singleton 1회 로딩, 진행 안내 메시지 |
|
||||
| Qdrant 미실행 시 에이전트 전체 불가 | High | Medium | RAG Tool 비활성화 config 플래그 |
|
||||
| LangChain/LangGraph 버전 충돌 | Low | Low | 버전 고정, 의존성 테스트 |
|
||||
|
||||
---
|
||||
|
||||
## 7. Architecture Decisions
|
||||
|
||||
| Decision | Selected | Rationale |
|
||||
|----------|----------|-----------|
|
||||
| LLM 통합 방식 | mlx-lm → `BaseChatModel` 래퍼 (Option B) | mlx Apple Silicon 최적화 유지 + LangChain 생태계 전체 활용 |
|
||||
| 에이전트 프레임워크 | LangGraph `create_react_agent` | Tool Calling 루프·상태 관리 직접 구현 불필요, LangChain 공식 표준 |
|
||||
| Tool 정의 방식 | `@tool` 데코레이터 | LangGraph 표준, JSON 스키마 자동 생성 |
|
||||
| 임베딩 모델 | BAAI/bge-m3 (로컬) | 한국어 포함 다국어 지원, 서버 불필요 |
|
||||
| Qdrant 운영 | 로컬 Docker | 개발 단계 외부 의존 최소화 |
|
||||
| 기존 코드 처리 | 보존 (병행 운영) | ChatService(단순 모드) / AgentService(RAG+Tool 모드) 선택적 사용 |
|
||||
@@ -0,0 +1,56 @@
|
||||
# 율봇 개발 로드맵
|
||||
|
||||
## 현재 구현 상태 (Phase 1~7 완료)
|
||||
|
||||
| 영역 | 현황 |
|
||||
|------|------|
|
||||
| LLM | Qwen2.5-7B-Instruct-4bit (MLX, Apple Silicon) |
|
||||
| Agent | LangGraph ReAct + Tool Calling + Thinking 모드 |
|
||||
| RAG | Qdrant + BAAI/bge-m3 임베딩 |
|
||||
| Tools | `search_documents`, `get_current_date`, `web_search`, `remember_user_info`, `recall_user_info` (5개) |
|
||||
| UI | Gradio Web UI (`app.py`) + CLI (`main.py`) |
|
||||
| Memory | LangGraph MemorySaver (세션 내) + MySQL (대화 영구 저장) + `td_user_profile` (장기 사용자 메모리) |
|
||||
| Streaming | 비동기 토큰 스트리밍 + `<think>` 블록 파싱 |
|
||||
| Tracing | LangSmith 트레이싱 설정 완료 (`.env`에서 활성화 가능) |
|
||||
|
||||
---
|
||||
|
||||
## ✅ Phase 4 — Web UI (Gradio)
|
||||
|
||||
- `app.py` — Gradio ChatInterface + `stream_response()` 연결
|
||||
- PDF/TXT 파일 업로드 → 인제스트 버튼
|
||||
- 사고 과정(thinking) 표시 토글
|
||||
- 대화 초기화 버튼
|
||||
|
||||
---
|
||||
|
||||
## ✅ Phase 5 — 장기 사용자 메모리
|
||||
|
||||
- MySQL `td_user_profile` 테이블 + Tool 2개 등록
|
||||
- `remember_user_info(key, value)` — 영구 저장 (아이 생년, 재정 목표 등)
|
||||
- `recall_user_info(key)` — 이전 저장 정보 조회
|
||||
- `UserProfileRepository` (`services/db/user_profile_repository.py`)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Phase 6 — 실시간 웹 검색 Tool
|
||||
|
||||
- `web_search(query)` — DuckDuckGo (무료, API 키 불필요)
|
||||
- 최신 금리, 육아 정책, 뉴스 등 실시간 정보 검색 가능
|
||||
|
||||
---
|
||||
|
||||
## ✅ Phase 7 — LangSmith 트레이싱
|
||||
|
||||
- `.env`에서 `LANGCHAIN_TRACING_V2=true` + `LANGCHAIN_API_KEY` 설정으로 활성화
|
||||
- Tool Call 실패 원인, RAG 청크 내용, 에이전트 루프 흐름 시각화 가능
|
||||
|
||||
---
|
||||
|
||||
## Phase 8 — 멀티모달 이미지 이해 ★☆☆
|
||||
|
||||
**배경**: 이유식 사진 → "이 재료로 만들 수 있는 이유식은?", 금융 서류 사진 → 내용 분석 등 이미지 기반 질문 처리.
|
||||
|
||||
**제약**: Qwen2.5-7B는 이미지 미지원 → `mlx-community/Qwen2.5-VL-7B-Instruct-4bit` 모델 교체 필요.
|
||||
|
||||
**난이도**: 높음 | **임팩트**: 높음 (장기 과제)
|
||||
@@ -0,0 +1,224 @@
|
||||
# 율봇 개발 로드맵 2
|
||||
|
||||
## 현재 구현 상태 (Phase 1~11 + Phase 14 완료, 버그 1~3 수정 완료, 모델 업그레이드)
|
||||
|
||||
| 영역 | 현황 |
|
||||
|------|------|
|
||||
| LLM | Qwen3-14B-4bit (MLX, Apple Silicon) |
|
||||
| Agent | LangGraph ReAct + Tool Calling + Thinking 모드 |
|
||||
| RAG | Qdrant + BAAI/bge-m3 임베딩 |
|
||||
| Tools | `search_documents`, `web_search`, `get_current_date`, `remember_user_info`, `recall_user_info` (5개) |
|
||||
| UI | CLI + Gradio Web UI |
|
||||
| Memory | LangGraph MemorySaver (세션 내) + MySQL 대화 저장 + 장기 사용자 프로필 |
|
||||
| Tracing | LangSmith 트레이싱 |
|
||||
| Streaming | 비동기 토큰 스트리밍 + `<think>` 블록 파싱 |
|
||||
| History Compact | 대화 20턴 초과 시 오래된 절반을 LLM으로 자동 요약 (`CompactService`) |
|
||||
|
||||
---
|
||||
|
||||
## 버그 수정 현황
|
||||
|
||||
### ✅ 버그 1 — RAG 중복 수집 (수정 완료)
|
||||
`IngestionService._delete_by_source()`를 구현해 같은 파일 경로로 저장된 기존 청크를 `ingest()` 시작 시 삭제한다.
|
||||
|
||||
### ✅ 버그 2 — LangGraph MemorySaver와 MySQL 이력 미연동 (수정 완료)
|
||||
`AgentService.__init__`에서 MySQL에 저장된 최근 10턴을 `_pending_history`로 불러온 뒤, 첫 `stream_response()` 호출 시 LangGraph 초기 메시지로 주입한다.
|
||||
|
||||
### ✅ 버그 3 — 단일 사용자 전제 (수정 완료)
|
||||
DB 스키마(`td_conversations.user_id`, `td_user_profile.user_id`)는 `_migrate_schema`로 자동 마이그레이션. `AgentService`에 `user_id` 파라미터 추가, 모든 Repository 호출에 전파. Gradio에 사용자 선택 드롭다운(아록/근혜/도율/하율) 추가 및 사용자별 에이전트 캐시 구현.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Phase 9 — 문서 관리 (완료)
|
||||
|
||||
- `IngestionService._delete_by_source()` — 파일 경로 기반 중복 청크 삭제
|
||||
- `RetrieverService.list_documents()` — Qdrant scroll로 고유 source 목록 반환
|
||||
- `RetrieverService.delete_document(source)` — source 기준 청크 전체 삭제
|
||||
- Gradio "문서 관리" 탭 — 목록 테이블 + 경로 입력 삭제 버튼 + 앱 로드 시 자동 새로고침
|
||||
|
||||
---
|
||||
|
||||
## ✅ Phase 10 — 멀티유저 지원 (완료)
|
||||
|
||||
Bug 3 수정 및 Phase 9 작업과 함께 완전 구현됨.
|
||||
|
||||
- DB 마이그레이션: `mysql_service._migrate_schema()`가 `td_conversations`, `td_user_profile` 양쪽에 `user_id` 컬럼 자동 추가
|
||||
- `ConversationRepository`: `create_conversation(user_id)` / `get_latest_conversation_id(user_id)` — user_id 기반 격리
|
||||
- `AgentService`: `user_id` 파라미터 추가, 모든 프로필·대화 조회에 전파
|
||||
- `make_memory_tools(profile_repo, user_id)`: remember/recall 도구가 올바른 사용자 데이터만 접근
|
||||
- Gradio: 사용자 선택 드롭다운(아록/근혜/도율/하율, 기본값 아록) + `_agent_cache` 사전으로 사용자별 에이전트 분리
|
||||
|
||||
---
|
||||
|
||||
## ✅ Phase 11 — 대화 이력 복원 (수정 완료)
|
||||
|
||||
버그 2와 함께 해결됨.
|
||||
`AgentService` 초기화 시 MySQL에서 최근 10턴을 `_pending_history`에 로드 → 첫 메시지와 함께 LangGraph에 주입.
|
||||
|
||||
```python
|
||||
# agent_service.py 초기화 (구현됨)
|
||||
turns = conversation_repository.load_turns_after(self._conv_id, None, limit=10)
|
||||
# → HumanMessage / AIMessage 변환 후 _pending_history에 저장
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 12 — 답변 피드백 & 품질 개선 ★★☆
|
||||
|
||||
**배경**: 에이전트가 잘못된 답변을 해도 피드백 루프가 없어 개선이 어려움.
|
||||
|
||||
**구현 범위**:
|
||||
- Gradio 채팅 메시지마다 👍 / 👎 버튼
|
||||
- `td_feedback` 테이블에 메시지·평점 저장
|
||||
- LangSmith의 `run_id`와 연결해 피드백을 트레이스에 기록 (`langsmith.Client().create_feedback()`)
|
||||
|
||||
```sql
|
||||
CREATE TABLE td_feedback (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
message TEXT,
|
||||
response TEXT,
|
||||
rating TINYINT, -- 1: 좋음, -1: 나쁨
|
||||
langsmith_run_id VARCHAR(100),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
**난이도**: 중간 | **임팩트**: 중간 (장기 품질 향상)
|
||||
|
||||
---
|
||||
|
||||
## Phase 13 — RAG 품질 향상 (Reranker + 청킹 개선) ★★☆ (부분 완료)
|
||||
|
||||
**배경**: 현재 고정 크기 청킹 + 벡터 유사도 검색만으로는 관련 없는 청크가 섞일 수 있음.
|
||||
|
||||
**✅ Semantic Chunker — 완료**
|
||||
- `_SemanticSplitter` 클래스 직접 구현 (`services/rag/ingestion_service.py`)
|
||||
- `langchain-experimental` 사용 없이 numpy + 기존 BAAI/bge-m3 임베딩으로 구현
|
||||
- 인접 문장 간 코사인 유사도 계산 → 유사도 하위 5% 지점에서 청크 분리
|
||||
- `config.py`에서 `rag_chunk_size` / `rag_chunk_overlap` 제거 → `semantic_breakpoint_threshold_type` 추가
|
||||
|
||||
**🔲 미완 — Reranker**
|
||||
1. **Reranker 추가** — `cross-encoder/ms-marco-MiniLM-L-6-v2`로 검색 결과 재순위
|
||||
2. **top_k 조정** — 검색 후 rerank → 상위 3개만 LLM에 전달
|
||||
|
||||
> 기존 Qdrant 저장 문서는 재등록해야 새 청킹 방식이 적용됨.
|
||||
|
||||
**난이도**: 중간 | **임팩트**: 중간 (답변 정확도 향상)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Phase 14 — 음성 인터페이스 (완료)
|
||||
|
||||
**배경**: 육아 중에는 손이 자유롭지 않아 타이핑이 어려움. 음성으로 질문하고 답변을 들을 수 있으면 핵심 사용 시나리오 커버.
|
||||
|
||||
**구현 내용**:
|
||||
- `openai-whisper` (small 모델) — 마이크 녹음 → 한국어 텍스트 변환, 지연 로딩
|
||||
- macOS `say -v Yuna` — 에이전트 응답을 음성으로 읽어줌 (aiff 파일 경유)
|
||||
- Gradio "대화" 탭 확장 — 마이크 녹음 + "음성→텍스트 변환" 버튼 + "음성으로 답변 읽기" 체크박스 + TTS 오디오 플레이어
|
||||
- LLM/Agent 레이어 변경 없음 — 순수 I/O 어댑터로 구현
|
||||
|
||||
```python
|
||||
# app.py — STT
|
||||
def transcribe_audio(filepath: str) -> str:
|
||||
result = whisper.load_model("small").transcribe(filepath, language="ko")
|
||||
return result["text"].strip()
|
||||
|
||||
# app.py — TTS
|
||||
def tts_speak(text: str, voice: str) -> str | None:
|
||||
subprocess.run(["say", "-v", voice, "-o", tmp.name, text], ...)
|
||||
```
|
||||
|
||||
**config.py 추가**: `whisper_model_size = "small"`, `tts_voice = "Yuna"`
|
||||
|
||||
**난이도**: 중간 | **임팩트**: 높음 (핵심 사용 시나리오)
|
||||
|
||||
---
|
||||
|
||||
## Phase 15 — 예방접종·건강검진 알림 스케줄러 ★★☆
|
||||
|
||||
**배경**: 아이 생년을 기억하고 있으므로, 예방접종 일정(BCG, DTaP 등)을 자동 계산해 알림을 줄 수 있음. 율봇의 차별화 포인트.
|
||||
|
||||
**구현 방식**:
|
||||
- `td_user_profile`에서 아이 생년 조회 → 예방접종 스케줄 계산 Tool
|
||||
- Gradio "건강 일정" 탭: 달력형 일정 표시
|
||||
- APScheduler로 당일 알림 (또는 Gradio 시작 시 오늘 일정 배너)
|
||||
|
||||
```python
|
||||
@tool
|
||||
def get_vaccination_schedule(birth_year: int, birth_month: int) -> str:
|
||||
"""아이 생년월을 기반으로 예방접종 일정을 계산합니다."""
|
||||
```
|
||||
|
||||
**난이도**: 중간 | **임팩트**: 높음 (육아 특화 차별화)
|
||||
|
||||
---
|
||||
|
||||
## Phase 16 — 모델 선택 (Claude API / OpenAI 옵션) ★☆☆
|
||||
|
||||
**배경**: 로컬 MLX 모델은 Apple Silicon 전용. 원격 접속 시나리오나 더 높은 품질이 필요할 때 Claude API/OpenAI를 선택할 수 있으면 유연성 확보.
|
||||
|
||||
**구현 방식**: `config.py`에 `model_provider` 추가, `container.py`에서 provider별 chat_model 분기.
|
||||
|
||||
```python
|
||||
model_provider: str = "mlx" # "mlx" | "claude" | "openai"
|
||||
```
|
||||
|
||||
**난이도**: 중간 | **임팩트**: 중간
|
||||
|
||||
---
|
||||
|
||||
## Phase 17 — Docker 컨테이너화 ★☆☆
|
||||
|
||||
**배경**: 현재 로컬 전용. 가족이나 지인도 쓸 수 있도록 서버 배포 가능한 형태로 패키징.
|
||||
|
||||
**구현 범위**:
|
||||
```
|
||||
docker-compose.yml
|
||||
├── youlbot (Gradio app)
|
||||
├── qdrant
|
||||
└── mysql
|
||||
```
|
||||
|
||||
> 주의: MLX는 Apple Silicon 전용이라 서버 배포 시 Phase 16(모델 선택)이 선행되어야 함.
|
||||
|
||||
**난이도**: 높음 | **임팩트**: 중간
|
||||
|
||||
---
|
||||
|
||||
## Phase 18 — 멀티모달 이미지 이해 ★☆☆
|
||||
|
||||
**배경**: 이유식 사진 → 재료 분석, 금융 서류 사진 → 내용 해석 등.
|
||||
|
||||
**제약**: Qwen3-8B는 이미지 미지원 → `mlx-community/Qwen2.5-VL-7B-Instruct-4bit` 교체 필요.
|
||||
|
||||
**난이도**: 높음 | **임팩트**: 높음 (장기 과제)
|
||||
|
||||
---
|
||||
|
||||
## 추천 진행 순서
|
||||
|
||||
```
|
||||
단기 (1~2주) 중기 (1개월) 장기
|
||||
──────────────── ────────────────── ──────────────
|
||||
Phase 14 (음성) → Phase 13 (RAG품질) → Phase 17 (Docker)
|
||||
Phase 15 (알림) Phase 16 (모델선택) Phase 18 (멀티모달)
|
||||
Phase 12 (피드백)
|
||||
```
|
||||
|
||||
### 우선순위 매트릭스
|
||||
|
||||
| Phase | 상태 | 난이도 | 임팩트 | 추천 순위 |
|
||||
|-------|------|--------|--------|-----------|
|
||||
| 버그 1 RAG 중복 | ✅ 완료 | — | — | — |
|
||||
| 버그 2 이력 미연동 | ✅ 완료 | — | — | — |
|
||||
| 버그 3 단일 사용자 | ✅ 완료 | — | — | — |
|
||||
| Phase 9 문서 관리 | ✅ 완료 | — | — | — |
|
||||
| Phase 10 멀티유저 | ✅ 완료 | — | — | — |
|
||||
| Phase 11 이력 복원 | ✅ 완료 | — | — | — |
|
||||
| Phase 14 음성 인터페이스 | ✅ 완료 | — | — | — |
|
||||
| Phase 15 예방접종 알림 | 🔲 미완 | 중간 | 높음 | ⭐ 2순위 |
|
||||
| Phase 12 피드백 | 🔲 미완 | 중간 | 중간 | 3순위 |
|
||||
| Phase 13 RAG 품질 (청킹 완료, Reranker 미완) | 🔲 진행 중 | 중간 | 중간 | 4순위 |
|
||||
| Phase 16 모델 선택 | 🔲 미완 | 중간 | 중간 | 5순위 |
|
||||
| Phase 17 Docker | 🔲 미완 | 높음 | 중간 | 6순위 |
|
||||
| Phase 18 멀티모달 | 🔲 미완 | 높음 | 높음 | 7순위 |
|
||||
Reference in New Issue
Block a user