Files
youlbot/docs/ROADMAP.md
T
shinalok a05d2f474e IDEA-8: GraphRAG — NetworkX 기반 지식 그래프
- td_knowledge_graph 테이블 (user_id, subject, relation, object 트리플)
- GraphService: MultiDiGraph 인메모리 캐시 + MySQL 영속화
- add_relation / query_entity LangChain 도구
- call_model에 그래프 요약 자동 주입 (시스템 프롬프트)
- GRAPH_ENABLED=true 환경변수로 활성화
- requirements.txt에 networkx>=3.0 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 10:08:39 +09:00

33 KiB

율봇 개발 로드맵

현재 구현 상태

영역 현황
LLM Qwen3-8B-4bit (MLX, Apple Silicon)
Agent LangGraph ReAct + Tool Calling + Thinking 모드
Scheduler asyncio task 기반 알림 스케줄러 — D-7/D-1/D-0 Telegram push (SchedulerService)
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, set_reminder, list_reminders (7개)
Feedback Gradio 👍/👎td_feedback DB 저장 + LangSmith create_feedback() 연동
UI CLI + Gradio Web UI + 음성 입력(STT)/출력(TTS)
Memory LangGraph MemorySaver (세션 내) + MySQL 대화 저장 + 장기 사용자 프로필
Tracing LangSmith 트레이싱
Streaming 비동기 토큰 스트리밍 + 타입별 이벤트 분리 (__meta / __thinking / __status)
사고 과정 UI 스트리밍 중 현재 줄 실시간 표시 → 완료 후 접기/펼치기 (<details>)
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)와 만 나이(생일 기준 정확 계산)를 자동 계산해 시스템 프롬프트에 포함.

버그 6 — TTS가 진행 메시지까지 읽는 문제 (수정 완료)

stream_response()[LangGraph → agent: ...], 문서 검색 중... 등 진행 메시지와 실제 답변을 동일한 plain string으로 yield해 TTS가 전부 읽던 문제.

  • stream_response() yield 타입 분리: 답변 → plain str, 진행/thinking/출처 → {"__meta": str} dict
  • thinking 토큰은 별도 {"__thinking": str} key 사용
  • call_model 시작 직후 writer({"__start": True}) emit → {"__status": label} 변환으로 LLM 추론 전 즉각 피드백
  • api.py: json.dumps(token) 이 dict/str 모두 처리하므로 변경 없음
  • WebUI respond(): tts_text 누적 변수 분리, __meta·__thinking 토큰 제외 후 TTS 전달
  • Telegram bot.py: __meta·__thinking 토큰 skip

버그 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는 브라우저에서만 사용 가능. 텔레그램으로 이동 중에도 율봇과 대화하고 싶음.

구현 방식: youlbot REST API(Phase 22) 호출 — youlbot-telegram/ 별도 프로젝트로 분리.

youlbot-telegram/
├── bot.py        ← Application (python-telegram-bot >= 20.0, async)
│   ├── /start, /reset CommandHandler
│   └── MessageHandler → api_client.chat() → edit_message_text() (타이핑 효과)
├── api_client.py ← httpx 기반 REST API 클라이언트 (chat/reset)
├── .env          ← TELEGRAM_BOT_TOKEN, YOULBOT_API_URL, 유저 ID 매핑
└── requirements.txt

구현 내용:

  • python-telegram-bot>=20.0 (asyncio 기반)
  • youlbot-telegram/bot.py — 새 진입점 (python bot.py로 실행)
  • /start — 환영 메시지 + 매핑된 youlbot 사용자 이름 표시
  • /resetapi_client.reset(user_id) 호출로 대화 이력 초기화
  • 일반 메시지 → api_client.chat() SSE 스트리밍 → 0.6초 간격 실시간 편집
  • Telegram numeric ID → youlbot user_id .env 매핑 (USER_아록_TELEGRAM_ID 등)
  • 미등록 사용자에게 Telegram ID 안내 메시지 표시

실행 방법:

cd youlbot-telegram
python bot.py

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


Phase 22 — REST API (FastAPI) ★★☆

배경: 다른 Python 스크립트나 원격 서버에서 율봇을 호출하려면 HTTP API가 필요하다.
Telegram Bot을 별도 프로젝트로 분리해 이 API를 호출하는 구조로 사용 가능.

구현 내용:

  • api.py — FastAPI 앱, uvicorn api:app --host 0.0.0.0 --port 8000으로 실행
  • SSE(text/event-stream) 스트리밍: 각 라인 data: <JSON 토큰>\n\n, 종료 data: [DONE]\n\n
  • Bearer Token 인증 (.env API_TOKEN 설정; 빈 값이면 개발 모드 무인증)
  • user_id 파라미터로 멀티유저 지원 (기존 DB·메모리 구조 그대로 재사용)
엔드포인트 설명
GET /health 헬스체크
POST /chat SSE 스트리밍 대화 (message, user_id, show_thinking)
POST /reset 대화 이력 초기화 (user_id)
POST /ingest PDF/TXT 파일 업로드 → 벡터DB 수집
GET /documents 등록 문서 목록
DELETE /documents/{source} 문서 삭제

클라이언트 예시 (별도 Telegram 봇 프로젝트):

import httpx, json

API_URL = "http://192.168.10.x:8000"
HEADERS = {"Authorization": "Bearer YOUR_TOKEN"}

async def ask_youlbot(message: str, user_id: str) -> str:
    full = ""
    async with httpx.AsyncClient(timeout=120) as client:
        async with client.stream("POST", f"{API_URL}/chat",
                                 json={"message": message, "user_id": user_id},
                                 headers=HEADERS) as r:
            async for line in r.aiter_lines():
                if line.startswith("data: ") and line != "data: [DONE]":
                    full += json.loads(line[6:])
    return full

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


Phase 23 — WebUI 분리 (youlbot-webui 별도 프로젝트) ★★☆

배경: 현재 app.py(Gradio)는 container.py를 직접 import해 서비스를 사용한다. REST API(Phase 22)를 완성했으므로, WebUI를 독립 프로젝트로 분리해 API만 호출하도록 변경한다. 분리 후 youlbot은 순수 백엔드(API 서버)로만 동작하며, Telegram Bot과 WebUI가 모두 같은 API를 공유한다.

구현 내용:

① youlbot/api.py 보완

  • POST /feedback 엔드포인트 추가 (FeedbackRepository 노출 + LangSmith 연동)
  • /chat SSE 마지막 이벤트에 run_id 포함 → 피드백 연결 가능
    data: {"__done": true, "run_id": "uuid"}
    

② 신규 프로젝트 youlbot-webui/

youlbot-webui/
├── app.py           ← Gradio UI (REST API 호출 방식으로 재작성)
├── api_client.py    ← httpx 기반 API 클라이언트 (chat/reset/ingest/documents/feedback)
├── .env             ← YOULBOT_API_URL, YOULBOT_API_TOKEN
├── .env.example
└── requirements.txt ← gradio, httpx, python-dotenv, openai-whisper
기존 app.py (container 직접 사용) 변경 후 (API 클라이언트)
container.ingestion_service() api_client.ingest(path)
agent.stream_response() api_client.chat(msg, user_id)
retriever.list_documents() api_client.list_documents()
feedback_repo.save_feedback() api_client.save_feedback(...)
STT (Whisper) 변경 없음 — WebUI 로컬 실행 유지
TTS (macOS say) 변경 없음 — WebUI 로컬 실행 유지

실행 방법:

# 백엔드
cd youlbot && uvicorn api:app --host 0.0.0.0 --port 8000

# WebUI (별도 터미널, 별도 프로젝트)
cd youlbot-webui && python app.py

기존 youlbot/app.py는 레거시 직접 실행 옵션으로 보존.

난이도: 중간 | 임팩트: 높음 (백엔드/프론트엔드 완전 분리, 다중 클라이언트 지원)


Phase 24 — 사고 과정 UI 분리 & 실시간 피드백 ★★☆

배경: 사고 과정(thinking)·진행 로그가 답변과 섞여 출력되고, 10초 동안 아무 피드백 없이 대기하는 UX 문제.

구현 내용:

① 스트리밍 토큰 타입 분리 (youlbot/services/agent/agent_service.py)

  • 답변: yield str (기존 그대로)
  • 진행 메시지([LangGraph → ...], 문서 검색 중... 등): yield {"__meta": str}
  • 사고 과정 내용: yield {"__thinking": str}
  • LLM 추론 시작 즉시: writer({"__start": True})yield {"__status": label} 으로 변환

② 사고 과정 전용 박스 (youlbot-webui/app.py)

단계 표시 방식 비고
전송 즉시 🤔 질문을 분석하고 있습니다... 단순 div, LLM 추론 전 즉각 표시
스트리밍 중 🤔 분석 중... + 현재 줄 plain <div>, 새 줄 도착 시 이전 줄 교체
진행 로그 🤔 분석 중... + 로그 메시지 __meta 토큰 전체를 표시
완료 💭 분석 완료 ▶ <details> 로 전환, 클릭 시 전체 내용 펼침
  • 스트리밍 중 <details> 미사용 → 내용 업데이트 시 닫힘 현상 없음
  • TTS는 순수 답변 토큰만 읽음 (__meta·__thinking 제외)
  • 챗봇에는 답변만 표시 (진행 메시지 숨김)
  • show_thinking 체크박스 기본값 ON으로 변경

③ 멀티클라이언트 대응

  • youlbot-telegram/bot.py: __meta·__thinking 토큰 skip → 순수 답변만 스트리밍
  • asyncio.get_event_loop().run_until_complete()asyncio.run() 전체 교체 (AnyIO 워커 스레드 호환)

난이도: 중간 | 임팩트: 높음 (UX 대폭 개선)


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) ★☆☆

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

구현 내용:

eval/
├── dataset.jsonl      ← 평가용 Q&A 쌍 (질문·정답 — 필요 시 수정)
├── run_ragas.py       ← 평가 실행 스크립트
├── requirements.txt   ← ragas==0.2.9, datasets, langchain-google-vertexai
└── results/           ← report_YYYYMMDD_HHMMSS.{csv,json} 저장

평가 지표:

지표 설명
faithfulness 답변이 검색 컨텍스트에 충실한가 (환각 탐지)
answer_relevancy 답변이 질문에 얼마나 관련 있는가
context_recall 컨텍스트가 정답에 필요한 정보를 포함하는가
context_precision 검색된 컨텍스트 중 실제 유용한 비율

평가 LLM 우선순위: OpenAI GPT-4o-mini > Anthropic Claude Haiku > 로컬 Qwen3

실행 방법:

# API 서버 실행 후
python eval/run_ragas.py
python eval/run_ragas.py --dataset eval/dataset.jsonl --api http://localhost:8000

호환성 처리: ragas 0.2가 langchain-community 0.4+에서 ChatVertexAI 임포트 실패하는 문제를 런타임 shim으로 우회.

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


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 — 멀티모달 이미지 이해 ★☆☆

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

구현 방식: Dual-model C방식 — analyze_image 도구

모델 역할
Qwen3-8B-4bit 대화·추론 (항상 로드)
Qwen2.5-VL-7B-Instruct-4bit 이미지 분석 (lazy load)
  • services/model/mlx_vision_model.py — MlxVisionModel (mlx-vlm 래퍼, lazy load)
  • services/agent/tools.pymake_vision_tool(vision_model, image_path) 추가
  • agent_service.pystream_response(image_path=None), config 경유 vision tool 동적 주입
  • api.pyimage_base64 필드 추가, temp 파일 저장 후 응답 완료 시 삭제
  • youlbot-webuiimage_input 컴포넌트 추가, ChatService.chat(image_path=) 연결
  • .envVISION_ENABLED=true, VISION_MODEL_ID 설정

실행 방법: API 서버 재시작 후 WebUI 이미지 첨부 버튼으로 사진 전송

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


추천 진행 순서

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

우선순위 매트릭스

Phase 상태 난이도 임팩트 추천 순위
버그 1 RAG 중복 완료
버그 2 이력 미연동 완료
버그 3 단일 사용자 완료
버그 4 나이 계산 오류 완료
버그 5 thinking 체크박스 무효 완료
버그 6 TTS 메타 토큰 혼재 완료
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 완료
Phase 22 REST API 완료
Phase 23 WebUI 분리 완료
Phase 24 사고 과정 UI 분리 완료
Phase 25 RAG 출처 전용 박스 완료
Phase 20 RAGAS 평가 완료
Phase 15 모델 선택 🔲 미완 중간 중간 4순위
Phase 16 Docker 🔲 미완 높음 중간 5순위
Phase 17 멀티모달 완료

💡 IDEA — 신규 개선 아이디어

단기 — 빠르게 임팩트 큰 것

IDEA-1. 대화 기반 자동 RAG 업데이트

배경: 현재 문서 업로드만 RAG에 들어간다. 중요 대화 내용 자체를 자동으로 벡터DB에 추가하면 사용할수록 지식이 쌓이는 시스템이 된다.

구현 내용:

  • IngestionService.store_text(text, metadata) — 단일 텍스트 직접 저장 (semantic chunking 없이)
  • AgentService._maybe_index_conversation() — 응답 완료 후 LLM이 유용한 정보 판단 → 요약 → Qdrant 저장 (asyncio background task)
  • source="conversation", user_id, timestamp 메타데이터로 문서 RAG와 구분
  • .env CONV_RAG_ENABLED=true로 활성화 (기본 비활성)

난이도: 하 | 임팩트: 높음 (지식 자동 축적)


IDEA-2. 스마트 알림 & 일정 연동

배경: 예방접종, 약 먹을 시간, 병원 예약 등 날짜 기반 알림이 없다.

구현 내용:

  • td_reminders 테이블 (user_id, remind_date, message, sent_d0/d1/d7)
  • set_reminder(remind_date, message) + list_reminders() 도구 — LangGraph 에이전트 자동 호출
  • SchedulerService — asyncio task 기반 60초 간격 체크 → D-7/D-1/D-0 Telegram push
  • FastAPI lifespan으로 앱 시작/종료 시 스케줄러 자동 관리
  • GET /reminders/{user_id} API 엔드포인트 추가
  • .env 설정: TELEGRAM_BOT_TOKEN, TELEGRAM_USER_MAP={"아록":"123456"}

난이도: 중간 | 임팩트: 매우 높음 (육아 핵심 시나리오)


IDEA-3. 대화 요약 일간 리포트

배경: 하루에 어떤 질문을 했고 어떤 결정을 내렸는지 돌아볼 방법이 없다.

구현 방향:

  • 매일 자정 cron → 당일 대화 요약 생성 (CompactService 재활용)
  • Telegram으로 사용자별 요약 발송
  • /chat API + APScheduler로 구현 가능 (새 인프라 불필요)

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


IDEA-4. 텔레그램 그룹 채팅 지원

배경: 현재 1:1 채팅만 지원한다. 가족 그룹에서 @율봇 멘션으로 함께 사용하고 싶다.

구현 방향:

  • 그룹 메시지에서 @율봇 멘션 감지 → 발신자 Telegram ID로 user_id 매핑
  • 그룹 공용 컨텍스트(user_id="family") 옵션
  • python-telegram-bot 기존 코드에 그룹 핸들러 추가

난이도: 하 | 임팩트: 높음 (가족 공동 사용)


중기 — RAG/에이전트 품질 향상

IDEA-5. Agentic RAG — 자기 교정 검색 (CRAG)

배경: 현재 query_rewrite → search_documents 1회로 끝난다. 검색 결과가 부족하면 재시도나 웹 검색 fallback이 없다.

구현 내용:

  • AgentState(TypedDict)messages + crag_fallback_used 커스텀 상태
  • crag_check LangGraph 노드 — search_documents 결과가 비었으면 동일 쿼리로 web_search AIMessage 자동 주입
  • route_after_crag — fallback AIMessage 있으면 tools 재실행, 없으면 agent로 복귀
  • 그래프: tools → crag_check → route_after_crag → {tools, agent}
  • 무한 루프 방지: crag_fallback_used 플래그로 1회만 fallback
  • .env CRAG_ENABLED=true로 활성화 (기본 비활성)

난이도: 중간 | 임팩트: 높음 (검색 실패 케이스 대폭 감소)


IDEA-6. 영수증/가계부 OCR

배경: analyze_image 도구가 이미 있다. 영수증 사진에서 지출을 자동 기록하면 가계 관리가 가능해진다.

구현 방향:

  • analyze_image → 금액·항목·날짜 추출 → MySQL td_expenses 저장
  • get_monthly_expenses(month) 도구 추가 → "이번 달 식비 얼마야?" 대응
  • 카테고리 자동 분류 (식비/의료비/교육비 등)

난이도: 중간 | 임팩트: 높음 (가계 관리 시나리오)


IDEA-7. RAG 파라미터 자동 튜닝 (Auto-Eval Loop)

배경: RAGAS 평가 인프라는 있는데, 파라미터 변경 효과를 수동으로 비교해야 한다.

구현 내용:

  • eval/auto_tune.py — API 서버 없이 RetrieverService 직접 사용, 파라미터 조합별 context_precision + context_recall 비교
  • 기본 조합 4개: baseline(3/10), top_k_5(5/15), top_k_2(2/6), fetch_k_20(3/20)
  • 평균 점수 기준 최적 조합 추천 + .env 설정값 안내
  • eval/results/tune_YYYYMMDD.json 저장
  • 실행: python eval/auto_tune.py [--dataset eval/dataset.jsonl]

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


장기 — 구조적 확장

IDEA-8. GraphRAG / 지식 그래프

배경: td_user_profile이 flat key-value라 엔티티 간 관계 추론이 불가능하다.

구현 내용:

  • td_knowledge_graph 테이블 — (user_id, subject, relation, object) 트리플 영구 저장
  • GraphService — NetworkX MultiDiGraph 인메모리 캐시 + MySQL 영속화
  • add_relation(subject, relation, obj) 도구 — 관계 저장 예: 도율 -[알레르기]→ 복숭아, 아록 -[자녀]→ 도율
  • query_entity(entity) 도구 — 출발/도착 방향 모든 관계 조회
  • call_model에 저장된 그래프 요약을 시스템 프롬프트에 자동 주입
  • .env GRAPH_ENABLED=true로 활성화 (기본 비활성)

사용 예시:

사용자: "도율이 복숭아 알레르기가 있어"
→ add_relation("도율", "알레르기", "복숭아")

사용자: "도율이 먹으면 안 되는 음식은?"
→ query_entity("도율") → "도율 -[알레르기]→ 복숭아"

난이도: 높음 | 임팩트: 높음 (메모리 추론 능력 대폭 향상)


IDEA-9. PWA / 모바일 Web UI

배경: Gradio는 모바일 UX가 좋지 않다. 네이티브 앱처럼 설치하고 카메라 접근도 원활해야 한다.

구현 방향:

  • youlbot-webui를 Next.js + shadcn/ui PWA로 재작성
  • 홈 화면 설치, 오프라인 캐시, 네이티브 카메라 접근
  • 기존 REST API 그대로 재사용 (백엔드 변경 없음)
  • STT는 Web Speech API로 대체 (브라우저 내장)

난이도: 높음 | 임팩트: 높음 (모바일 UX 대폭 개선)


IDEA 우선순위 매트릭스

IDEA 설명 난이도 임팩트 추천 순위
IDEA-2 스마트 알림 asyncio 스케줄러 + Telegram push 중간 매우 높음
IDEA-4 텔레그램 그룹 채팅 기존 Bot 코드 확장 높음 1순위
IDEA-3 일간 리포트 CompactService 재활용 + SchedulerService 중간 2순위
IDEA-1 대화 기반 RAG asyncio background + Qdrant 저장 높음
IDEA-5 CRAG crag_check LangGraph 노드 중간 높음
IDEA-7 Auto-Eval eval/auto_tune.py 중간 중간
IDEA-6 영수증 OCR analyze_image 재활용 중간 높음 1순위
IDEA-8 GraphRAG NetworkX + MySQL + 2개 도구 높음 높음
IDEA-9 PWA WebUI 프론트엔드 재작성 높음 높음 8순위