Files
youlbot/docs/ROADMAP.md
T
shinalok 68f741af72 Phase 17: Multimodal image understanding via analyze_image tool
Dual-model approach (C): Qwen3-8B handles conversation, Qwen2.5-VL-7B
analyzes images on demand via analyze_image LangChain tool.

- services/model/mlx_vision_model.py: MlxVisionModel (mlx-vlm wrapper, lazy load)
- services/agent/tools.py: make_vision_tool(vision_model, image_path)
- agent_service.py: stream_response(image_path=None), dynamic tool binding
  via config["image_path"] — thread-safe per-request rebinding
- container.py: vision_model Singleton provider
- config.py: vision_enabled, vision_model_id, vision_max_tokens
- api.py: image_base64 in ChatRequest, decode to temp file, cleanup after stream

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 13:52:10 +09:00

26 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 비동기 토큰 스트리밍 + 타입별 이벤트 분리 (__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 멀티모달 완료