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>
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
---
|
||||
template: plan
|
||||
version: 1.3
|
||||
feature: phase17-multimodal
|
||||
date: 2026-06-02
|
||||
author: sal
|
||||
project: youlbot
|
||||
status: Draft
|
||||
---
|
||||
|
||||
# phase17-multimodal Planning Document
|
||||
|
||||
> **Summary**: analyze_image 도구 방식으로 이미지 이해 기능을 추가한다.
|
||||
> Qwen3-8B가 대화를 유지하고, 이미지 첨부 시 Qwen2.5-VL-7B를 도구로 호출해 설명을 얻은 뒤 답변한다.
|
||||
>
|
||||
> **Project**: youlbot
|
||||
> **Author**: sal
|
||||
> **Date**: 2026-06-02
|
||||
> **Status**: Draft
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
| Perspective | Content |
|
||||
|-------------|---------|
|
||||
| **Problem** | 이유식 사진·금융 서류 등 이미지를 텍스트로만 처리하는 현재 한계 |
|
||||
| **Solution** | Qwen2.5-VL-7B를 `analyze_image` LangChain 도구로 래핑, Qwen3-8B가 필요 시 자동 호출 |
|
||||
| **Function/UX Effect** | 채팅창에 이미지 첨부 → 자동 분석 → 육아·금융 상담으로 자연스럽게 연결 |
|
||||
| **Core Value** | 텍스트 추론 품질(Qwen3-8B)을 유지하면서 이미지 이해 기능 추가 |
|
||||
|
||||
---
|
||||
|
||||
## Context Anchor
|
||||
|
||||
| Key | Value |
|
||||
|-----|-------|
|
||||
| **WHY** | 손이 자유롭지 않은 육아 상황에서 사진 한 장으로 재료 분석·서류 해석이 가능해야 함 |
|
||||
| **WHO** | 아록(주 사용자) — 이유식 사진, 건강보험 서류, 접종 기록지 등 촬영 후 질문 |
|
||||
| **RISK** | 16GB 메모리에서 두 모델 동시 로드 시 OOM 가능 → Vision 모델 lazy load로 완화 |
|
||||
| **SUCCESS** | 이미지 첨부 → analyze_image 도구 자동 호출 → 설명이 대화 히스토리에 남아 후속 질문 가능 |
|
||||
| **SCOPE** | 이미지 분석 + 채팅 연동. 동영상·실시간 캡처는 제외 |
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
### 1.1 Purpose
|
||||
사진을 첨부하면 `analyze_image` 도구가 Qwen2.5-VL-7B를 호출해 이미지 설명을 생성하고,
|
||||
Qwen3-8B가 그 설명을 컨텍스트로 삼아 육아·금융 상담 답변을 제공한다.
|
||||
|
||||
### 1.2 모델 분담
|
||||
|
||||
| 모델 | 역할 | 메모리 |
|
||||
|------|------|--------|
|
||||
| Qwen3-8B-4bit | 대화·추론·도구 결정 (항상 로드) | ~5GB |
|
||||
| Qwen2.5-VL-7B-Instruct-4bit | 이미지 분석 (lazy load) | ~5GB |
|
||||
| 합계 | — | ~10GB / 16GB 사용 가능 |
|
||||
|
||||
---
|
||||
|
||||
## 2. Scope
|
||||
|
||||
### 2.1 In Scope
|
||||
- `mlx-vlm` 패키지로 Vision 모델 로드 및 추론
|
||||
- `analyze_image(image_path, prompt)` LangChain 도구 구현
|
||||
- AgentService: 요청에 이미지 있을 때 도구 동적 주입
|
||||
- API(`/chat`): 이미지 파일 업로드 지원 (multipart form)
|
||||
- WebUI: 채팅 입력창에 이미지 첨부 버튼 추가
|
||||
- Telegram: 사진 메시지 수신 → 이미지 다운로드 → API 전달
|
||||
|
||||
### 2.2 Out of Scope
|
||||
- 동영상 분석
|
||||
- 이미지 생성(text-to-image)
|
||||
- 실시간 카메라 입력
|
||||
|
||||
---
|
||||
|
||||
## 3. Architecture — C방식 (analyze_image 도구)
|
||||
|
||||
```
|
||||
사용자
|
||||
│ 텍스트 + 이미지(선택)
|
||||
▼
|
||||
API /chat (multipart form)
|
||||
│ image → /tmp/youlbot_img_xxx.jpg 저장
|
||||
│ image_path → AgentService.stream_response(message, image_path=...)
|
||||
▼
|
||||
AgentService
|
||||
│ image_path 있을 때: analyze_image 도구를 tools 목록에 동적 추가
|
||||
│ image_path를 도구 클로저로 바인딩
|
||||
▼
|
||||
LangGraph ReAct
|
||||
│ Qwen3-8B가 이미지 관련 질문 감지 → analyze_image() 자동 호출
|
||||
▼
|
||||
analyze_image 도구
|
||||
│ mlx_vision_model.analyze(image_path, prompt)
|
||||
▼
|
||||
MlxVisionModel (Qwen2.5-VL-7B, lazy load)
|
||||
│ 이미지 설명 텍스트 반환
|
||||
▼
|
||||
LangGraph
|
||||
│ 설명이 ToolMessage로 대화 히스토리에 저장
|
||||
▼
|
||||
Qwen3-8B → 최종 답변 생성
|
||||
```
|
||||
|
||||
**핵심 특성:**
|
||||
- Vision 모델은 처음 analyze_image 호출 시 로드 (이후 캐시)
|
||||
- 이미지 설명이 대화 히스토리에 남아 후속 질문("그 재료로 이유식 만들어줘") 가능
|
||||
- 이미지 없는 메시지는 기존과 완전히 동일하게 동작
|
||||
|
||||
---
|
||||
|
||||
## 4. 변경 파일 목록
|
||||
|
||||
### 신규 생성
|
||||
| 파일 | 설명 |
|
||||
|------|------|
|
||||
| `services/model/mlx_vision_model.py` | MlxVisionModel 클래스 (mlx-vlm 래퍼, lazy load) |
|
||||
|
||||
### 수정
|
||||
| 파일 | 변경 내용 |
|
||||
|------|----------|
|
||||
| `config.py` | `vision_enabled: bool`, `vision_model_id: str` 추가 |
|
||||
| `container.py` | `vision_model` Singleton 프로바이더 추가 |
|
||||
| `services/agent/tools.py` | `make_vision_tool(vision_model, image_path)` 추가 |
|
||||
| `services/agent/agent_service.py` | `stream_response(image_path=None)` 파라미터 추가, 도구 동적 주입 |
|
||||
| `api.py` | `/chat` → multipart form으로 변경, 이미지 temp 저장 |
|
||||
| `youlbot-webui/api_client.py` | `chat(image_path=None)` 파라미터 추가, multipart 전송 |
|
||||
| `youlbot-webui/app.py` | 채팅 입력 영역에 이미지 업로드 컴포넌트 추가 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 주요 구현 세부사항
|
||||
|
||||
### 5.1 MlxVisionModel
|
||||
```python
|
||||
class MlxVisionModel:
|
||||
def __init__(self, model_id: str): ...
|
||||
|
||||
def analyze(self, image_path: str, prompt: str = "이 이미지를 한국어로 자세히 설명해줘.") -> str:
|
||||
# 첫 호출 시 lazy load
|
||||
# mlx_vlm.generate() 호출
|
||||
# 한국어 설명 반환
|
||||
```
|
||||
|
||||
### 5.2 make_vision_tool
|
||||
```python
|
||||
def make_vision_tool(vision_model, image_path: str):
|
||||
@tool
|
||||
def analyze_image(prompt: str = "이 이미지를 설명해줘") -> str:
|
||||
"""현재 첨부된 이미지를 분석한다."""
|
||||
return vision_model.analyze(image_path, prompt)
|
||||
return analyze_image
|
||||
```
|
||||
|
||||
### 5.3 API /chat 변경
|
||||
- JSON Body → `multipart/form-data`
|
||||
- 필드: `message`, `user_id`, `show_thinking`, `image` (optional file)
|
||||
- 이미지를 `/tmp/youlbot_img_{uuid}.{ext}`에 저장 후 agent에 전달
|
||||
- 응답 완료 후 temp 파일 삭제
|
||||
|
||||
### 5.4 WebUI 변경
|
||||
- `gr.Image(type="filepath", ...)` 컴포넌트 채팅 입력 영역에 추가
|
||||
- 이미지 첨부 시 api_client.chat()에 image_path 전달
|
||||
- 전송 후 이미지 초기화
|
||||
|
||||
---
|
||||
|
||||
## 6. 환경 설정
|
||||
|
||||
```env
|
||||
# .env 추가
|
||||
VISION_ENABLED=true
|
||||
VISION_MODEL_ID=mlx-community/Qwen2.5-VL-7B-Instruct-4bit
|
||||
```
|
||||
|
||||
```bash
|
||||
# 패키지 설치
|
||||
pip install mlx-vlm
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 위험 요소 및 대응
|
||||
|
||||
| 위험 | 대응 |
|
||||
|------|------|
|
||||
| 16GB에서 두 모델 동시 OOM | Vision 모델 lazy load + 미사용 시 unload 옵션 제공 |
|
||||
| mlx-vlm API 변경 가능성 | MlxVisionModel로 캡슐화해 교체 용이하게 |
|
||||
| Telegram 이미지 전달 복잡성 | Phase 17-B로 분리, 우선 WebUI만 구현 |
|
||||
| 이미지 temp 파일 누적 | 응답 완료 후 즉시 삭제 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 성공 기준
|
||||
|
||||
- [ ] 이미지 첨부 시 `analyze_image` 도구가 자동 호출되어 설명 생성
|
||||
- [ ] "이 사진에서 뭐가 보여?" 후속 질문이 히스토리 기반으로 동작
|
||||
- [ ] 이미지 없는 일반 질문은 기존과 동일하게 Qwen3-8B로 처리
|
||||
- [ ] 16GB 환경에서 OOM 없이 동작
|
||||
+18
-4
@@ -479,13 +479,27 @@ docker-compose.yml
|
||||
|
||||
---
|
||||
|
||||
## Phase 17 — 멀티모달 이미지 이해 ★☆☆
|
||||
## ✅ Phase 17 — 멀티모달 이미지 이해 ★☆☆
|
||||
|
||||
**배경**: 이유식 사진 → 재료 분석, 금융 서류 사진 → 내용 해석 등.
|
||||
|
||||
**제약**: Qwen3-14B는 이미지 미지원 → `mlx-community/Qwen2.5-VL-7B-Instruct-4bit` 교체 필요.
|
||||
**구현 방식**: 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.py` — `make_vision_tool(vision_model, image_path)` 추가
|
||||
- `agent_service.py` — `stream_response(image_path=None)`, config 경유 vision tool 동적 주입
|
||||
- `api.py` — `image_base64` 필드 추가, temp 파일 저장 후 응답 완료 시 삭제
|
||||
- `youlbot-webui` — `image_input` 컴포넌트 추가, ChatService.chat(image_path=) 연결
|
||||
- `.env` — `VISION_ENABLED=true`, `VISION_MODEL_ID` 설정
|
||||
|
||||
**실행 방법**: API 서버 재시작 후 WebUI 이미지 첨부 버튼으로 사진 전송
|
||||
|
||||
**난이도**: 높음 | **임팩트**: 높음
|
||||
|
||||
---
|
||||
|
||||
@@ -529,4 +543,4 @@ Phase 20 RAGAS 평가 → Phase 15 (모델선택) → Phase 16 (Docke
|
||||
| Phase 20 RAGAS 평가 | ✅ 완료 | — | — | — |
|
||||
| Phase 15 모델 선택 | 🔲 미완 | 중간 | 중간 | 4순위 |
|
||||
| Phase 16 Docker | 🔲 미완 | 높음 | 중간 | 5순위 |
|
||||
| Phase 17 멀티모달 | 🔲 미완 | 높음 | 높음 | 6순위 |
|
||||
| Phase 17 멀티모달 | ✅ 완료 | — | — | — |
|
||||
|
||||
Reference in New Issue
Block a user