Files
youlbot/docs/ROADMAP.md
T
shinalok 432cc9565c Add Phase 21 (Telegram Bot) and Phase 22 (REST API) to ROADMAP
- Phase 21: python-telegram-bot 직접 AgentService 연결 (동일 머신)
  - /start, /reset 커맨드, 스트리밍 edit_message_text, Telegram user_id → user_id 매핑
- Phase 22: FastAPI + SSE 스트리밍 REST API (원격 Python 클라이언트)
  - POST /chat, POST /ingest, GET/DELETE /documents, Bearer Token 인증
- 우선순위 재조정: Telegram(1순위) → REST API(2순위) → RAGAS(3순위) → 모델선택(4순위)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 20:01:21 +09:00

18 KiB

율봇 개발 로드맵

현재 구현 상태

영역 현황
LLM Qwen3-8B-4bit (MLX, Apple Silicon)
Agent LangGraph ReAct + Tool Calling + Thinking 모드
RAG Qdrant + BAAI/bge-m3 임베딩 + Semantic Chunking (SemanticChunker) + Reranker (BAAI/bge-reranker-v2-m3)
Tools search_documents, web_search, get_current_date, remember_user_info, recall_user_info (5개)
Feedback Gradio 👍/👎td_feedback DB 저장 + LangSmith create_feedback() 연동
UI CLI + Gradio Web UI + 음성 입력(STT)/출력(TTS)
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로 자동 마이그레이션. AgentServiceuser_id 파라미터 추가, 모든 Repository 호출에 전파. Gradio에 사용자 선택 드롭다운(아록/근혜/도율/하율) 추가 및 사용자별 에이전트 캐시 구현.

버그 4 — 나이 계산 오류 (수정 완료)

LLM이 훈련 데이터 기준 연도로 나이를 계산하는 문제. AgentService.call_model()에서 매 호출 시 시스템 프롬프트 앞에 오늘 날짜: {date.today().isoformat()}를 주입. 프로필에서 생년월일/생년 값을 파싱해 한국 나이(현재연도-출생연도+1)와 만 나이(생일 기준 정확 계산)를 자동 계산해 시스템 프롬프트에 포함.

버그 5 — 사고 과정(thinking) 체크박스 무효 (수정 완료)

ON/OFF와 무관하게 사고 과정이 표시되지 않던 버그.

  • call_model 내부에서 get_stream_writer()로 thinking 토큰을 custom 이벤트로 emit → 답변 앞에 먼저 스트리밍
  • 체크박스 값을 LangGraph configurable → llm_with_tools.bind(enable_thinking=...) 로 모델 레벨까지 전달 (.env ENABLE_THINKING 설정과 독립)
  • stream_response 루프를 stream_mode=["messages", "custom"] 이중 스트림으로 전환
  • self._think_verbose 인스턴스 변수 참조 버그 수정 (_think_verbose 로컬 변수 사용)

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 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에 주입.

turns = conversation_repository.load_turns_after(self._conv_id, None, limit=10)
# → HumanMessage / AIMessage 변환 후 _pending_history에 저장

Phase 12 — 답변 피드백 & 품질 개선

배경: 에이전트가 잘못된 답변을 해도 피드백 루프가 없어 개선이 어려움.

구현 내용:

  • Gradio Chatbot 메시지마다 👍 / 👎 버튼 (chatbot.like() 이벤트)
  • td_feedback 테이블에 user_id, 질문, 답변, 평점 저장 (FeedbackRepository)
  • AgentService에서 응답마다 run_id(UUID)를 LangChain config에 주입 → last_run_id property로 노출
  • run_ids_state(gr.State)로 대화 턴별 run_id 추적
  • LangSmith Client().create_feedback() 연동 (트레이싱 활성화 시 자동 기록)

난이도: 중간 | 임팩트: 중간 (장기 품질 향상)


Phase 13 — RAG 품질 향상 ★★★ (완료)

배경: 고정 크기 청킹 + 벡터 유사도 검색만으로는 관련 없는 청크가 섞일 수 있음.

Semantic Chunker — 완료

커스텀 _SemanticSplitter를 제거하고 langchain_experimental.SemanticChunker로 교체 (services/rag/ingestion_service.py).
기존에 무시되던 semantic_breakpoint_threshold_type 설정이 이제 실제로 적용된다.

기능 지원 여부
breakpoint_threshold_type percentile / standard_deviation / interquartile / gradient
buffer_size SEMANTIC_BUFFER_SIZE 환경변수로 설정
min_chunk_size (SemanticChunker 기본 지원)
HuggingFaceEmbeddings 재사용 기존 임베딩 모델 그대로 사용

langchain-experimental 패키지 상태:
langchain-experimental v0.4.2는 공식 유지보수 종료가 선언됐지만(#87),
SemanticChunker 자체는 현재 정상 동작하며 후속 패키지(langchain-text-splitters)로 이전 완료 시 migration 예정.

미완 1 — Semantic Chunker 기능 완성 (완료)

기존 Qdrant 저장 문서는 재등록해야 새 청킹 방식이 적용됨.

난이도: 중간 | 임팩트: 중간 (답변 정확도 향상)


Phase 14 — 음성 인터페이스

배경: 육아 중에는 손이 자유롭지 않아 타이핑이 어려움.

구현 내용:

  • openai-whisper (small 모델) — 마이크 녹음 → 한국어 텍스트 변환, 지연 로딩
  • macOS say -v Yuna — 에이전트 응답을 음성으로 읽어줌 (aiff 파일 경유)
  • Gradio "대화" 탭 확장 — 마이크 녹음 + "음성→텍스트 변환" 버튼 + "음성으로 답변 읽기" 체크박스 + TTS 오디오 플레이어
  • LLM/Agent 레이어 변경 없음 — 순수 I/O 어댑터로 구현

config.py 추가: whisper_model_size = "small", tts_voice = "Yuna"

난이도: 중간 | 임팩트: 높음 (핵심 사용 시나리오)


Phase 13-B — Reranker ★★☆

배경: 벡터 유사도 검색은 의미적으로 비슷한 청크를 가져오지만, 질문과 실제로 관련 있는 청크를 정확히 가려내지 못하는 경우가 있다. Reranker는 검색 후 순위를 재조정해 LLM에 전달되는 컨텍스트 품질을 높인다.

구현 내용:

  • services/rag/rerank_service.pyRerankService 클래스 (Cross-Encoder 래퍼)
  • RetrieverService.search(): reranker 활성화 시 rerank_fetch_k(기본 10)개 후보 검색 → rerank → 상위 rag_top_k(기본 3)개 반환
  • tools.py make_retriever_tool: as_retriever()search() 직접 호출로 변경 (reranker 자동 적용)
  • .env RERANKER_ENABLED=true로 활성화, 기본 비활성 (첫 실행 시 모델 다운로드)
설정 기본값 설명
RERANKER_ENABLED false true로 설정 시 활성화
RERANKER_MODEL_ID cross-encoder/mmarco-mMiniLMv2-L12-H384-v1 한국어 포함 다국어 모델 (117MB)
RERANKER_FETCH_K 10 rerank 전 벡터 검색 후보 수

난이도: 중간 | 임팩트: 높음 (관련성 낮은 청크 필터링 → 답변 정확도 향상)


Phase 18 — Hybrid Search (BM25 + Vector) ★★☆

배경: 한국어 질문에서 고유명사·전문용어가 포함된 경우 의미 검색(Dense)만으로는 recall이 떨어진다. BM25 키워드 검색과 결합(Hybrid)하면 보완이 가능하다.

구현 내용:

  • FastEmbedSparse(model_name="Qdrant/bm25") — 언어 무관 BM25 sparse 임베딩 (fastembed 패키지)
  • IngestionService: HYBRID_SEARCH_ENABLED=true 시 dense + sparse 동시 저장 (RetrievalMode.HYBRID)
  • RetrieverService: hybrid 스토어로 검색 → Qdrant 내장 RRF로 결과 통합; sparse vector 미설정 컬렉션은 dense로 자동 폴백
  • _ensure_collection_schema(): hybrid 전환 시 스키마 불일치 컬렉션 자동 재생성 (기존 문서 재수집 필요)
  • .env HYBRID_SEARCH_ENABLED=true로 활성화, 활성화 후 기존 문서 재수집 필요
설정 기본값 설명
HYBRID_SEARCH_ENABLED false true로 설정 시 활성화
SPARSE_MODEL_ID Qdrant/bm25 fastembed sparse 모델 (첫 실행 시 자동 다운로드)

난이도: 중간 | 임팩트: 높음 (키워드 포함 질문 recall 대폭 향상)


Phase 19 — Query Rewriting ★☆☆

배경: 사용자 구어체 질문("아이가 밥을 안 먹어요")은 벡터 검색에 최적화되어 있지 않다. LLM이 검색 전에 질문을 재작성하면 관련 문서 검색 확률이 높아진다.

구현 내용:

  • LangGraph 그래프에 query_rewrite 노드 추가 — agent → query_rewrite → tools 순서
  • search_documents 호출 시에만 작동하는 조건부 라우팅 (route_after_agent): 다른 도구 호출이나 tool 없음 케이스는 그대로 통과
  • 구어체 → 키워드 중심 쿼리로 변환 + 대명사·지시어를 구체적 명칭으로 해소 (이전 대화 2턴 컨텍스트 활용)
  • tools_condition 제거 → 커스텀 route_after_agent 함수로 대체
  • 변환 결과를 custom stream 이벤트로 emit → RAG_VERBOSE=true쿼리 최적화: "원본" → "최적화" 출력
  • .env QUERY_REWRITE_ENABLED=true로 활성화

난이도: 하 | 임팩트: 중간 (구어체 질문 검색 품질 향상)


Phase 21 — Telegram Bot ★★☆

배경: Gradio Web UI는 브라우저에서만 사용 가능. 텔레그램으로 이동 중에도 율봇과 대화하고 싶음.

구현 방식: AgentService를 직접 임포트 — 별도 API 서버 없이 동일 머신에서 실행.

telegram_bot.py
├── Application (python-telegram-bot >= 20.0, async)
├── /start, /reset CommandHandler
├── MessageHandler → agent.stream_response() → message.edit_text() (타이핑 효과)
└── Telegram user_id → youlbot user_id 매핑 (멀티유저 그대로 활용)

구현 내용:

  • python-telegram-bot>=20.0 (asyncio 기반)
  • telegram_bot.py — 새 진입점 (python telegram_bot.py로 실행)
  • /start — 환영 메시지 + 사용법 안내
  • /reset — 대화 이력 초기화 (agent.reset())
  • 일반 메시지 → agent.stream_response() → 500자 단위 실시간 편집 (Telegram edit_message_text)
  • telegram_user_iduser_id로 사용 → 기존 멀티유저·메모리·DB 구조 그대로 재사용
  • .env TELEGRAM_BOT_TOKEN 추가

제약: 동일 머신에서만 실행 가능 (원격 실행은 Phase 22 REST API 필요)

난이도: 중간 | 임팩트: 높음 (모바일·이동 중 접근)


Phase 22 — REST API (FastAPI) ★★☆

배경: 다른 Python 스크립트나 원격 서버에서 율봇을 호출하려면 HTTP API가 필요하다.
Phase 21 Telegram Bot을 원격 서버에서 실행하거나, 다른 앱에 율봇을 임베딩할 때도 활용 가능.

구현 방식: FastAPI + SSE(Server-Sent Events) 스트리밍.

api.py  (uvicorn api:app)
├── POST /chat          — SSE 스트리밍 응답
├── POST /ingest        — 파일 수집
├── GET  /documents     — 등록 문서 목록
└── DELETE /documents/{source}  — 문서 삭제

클라이언트 예시:

import httpx

with httpx.Client() as client:
    with client.stream("POST", "http://localhost:8000/chat",
                       json={"message": "아이 발달 단계가 궁금해요"},
                       headers={"Authorization": "Bearer YOUR_TOKEN"}) as r:
        for line in r.iter_lines():
            if line.startswith("data: "):
                print(line[6:], end="", flush=True)

구현 내용:

  • api.py — FastAPI 앱, uvicorn api:app --host 0.0.0.0 --port 8000으로 실행
  • Bearer Token 인증 (.env API_TOKEN)
  • user_id 헤더/파라미터로 멀티유저 지원
  • SSE(text/event-stream)로 토큰 단위 스트리밍
  • Gradio(app.py)와 동일한 Container 공유 가능

난이도: 중간 | 임팩트: 높음 (확장성·외부 연동)


Phase 20 — RAG 품질 자동 평가 (RAGAS) ★☆☆

배경: 청킹 전략·검색 파라미터·Reranker 변경 시 답변 품질이 실제로 나아졌는지 수치로 확인할 방법이 없다.

구현 방식:

  • ragas 라이브러리로 Faithfulness·Answer Relevancy·Context Recall 자동 측정
  • 테스트 질문-정답 셋을 eval/ 디렉터리에 관리
  • 설정 변경 후 python eval/run_ragas.py로 비교 가능

난이도: 중간 | 임팩트: 중간 (장기 품질 관리 기반)


Phase 15 — 모델 선택 (Claude API / OpenAI 옵션) ★☆☆

배경: 로컬 MLX 모델은 Apple Silicon 전용. 원격 접속 시나리오나 더 높은 품질이 필요할 때 Claude API/OpenAI를 선택할 수 있으면 유연성 확보.

구현 방식: config.pymodel_provider 추가, container.py에서 provider별 chat_model 분기.

model_provider: str = "mlx"  # "mlx" | "claude" | "openai"

난이도: 중간 | 임팩트: 중간


Phase 16 — Docker 컨테이너화 ★☆☆

배경: 현재 로컬 전용. 가족이나 지인도 쓸 수 있도록 서버 배포 가능한 형태로 패키징.

구현 범위:

docker-compose.yml
├── youlbot (Gradio app)
├── qdrant
└── mysql

주의: MLX는 Apple Silicon 전용이라 서버 배포 시 Phase 15(모델 선택)이 선행되어야 함.

난이도: 높음 | 임팩트: 중간


Phase 17 — 멀티모달 이미지 이해 ★☆☆

배경: 이유식 사진 → 재료 분석, 금융 서류 사진 → 내용 해석 등.

제약: Qwen3-14B는 이미지 미지원 → mlx-community/Qwen2.5-VL-7B-Instruct-4bit 교체 필요.

난이도: 높음 | 임팩트: 높음 (장기 과제)


추천 진행 순서

단기 (1~2주)                  중기 (1개월)              장기
────────────────────────     ──────────────────────    ──────────────────
Phase 21 Telegram Bot     →  Phase 22 REST API      →  Phase 16 (Docker)
Phase 20 RAGAS 평가       →  Phase 15 (모델선택)    →  Phase 17 (멀티모달)

우선순위 매트릭스

Phase 상태 난이도 임팩트 추천 순위
버그 1 RAG 중복 완료
버그 2 이력 미연동 완료
버그 3 단일 사용자 완료
버그 4 나이 계산 오류 완료
버그 5 thinking 체크박스 무효 완료
Phase 4 Web UI 완료
Phase 5 장기 사용자 메모리 완료
Phase 6 웹 검색 완료
Phase 7 LangSmith 트레이싱 완료
Phase 9 문서 관리 완료
Phase 10 멀티유저 완료
Phase 11 이력 복원 완료
Phase 12 피드백 완료
Phase 13 Semantic Chunker 완료
Phase 14 음성 인터페이스 완료
Phase 13-B Reranker 완료
Phase 18 Hybrid Search 완료
Phase 19 Query Rewriting 완료
Phase 21 Telegram Bot 🔲 신규 중간 높음 1순위
Phase 22 REST API 🔲 신규 중간 높음 2순위
Phase 20 RAGAS 평가 🔲 신규 중간 중간 3순위
Phase 15 모델 선택 🔲 미완 중간 중간 4순위
Phase 16 Docker 🔲 미완 높음 중간 5순위
Phase 17 멀티모달 🔲 미완 높음 높음 6순위