- matrix-gateway: POST /internal/matrix/presence/online endpoint - usePresenceHeartbeat hook with activity tracking - Auto away after 5 min inactivity - Offline on page close/visibility change - Integrated in MatrixChatRoom component
487 lines
13 KiB
Markdown
487 lines
13 KiB
Markdown
# 🔧 Router - Підтримка мультимодальних запитів
|
||
|
||
**Дата:** 2025-11-23
|
||
**Мета:** Додати підтримку images/files у Router payload
|
||
**Статус:** 📝 Інструкції для бекенду
|
||
|
||
---
|
||
|
||
## 🎯 Що потрібно реалізувати
|
||
|
||
### Поточна структура запиту:
|
||
|
||
```json
|
||
POST http://144.76.224.179:9102/route
|
||
|
||
{
|
||
"agent": "daarwizz",
|
||
"message": "текст повідомлення",
|
||
"mode": "chat",
|
||
"payload": {
|
||
"context": {
|
||
"system_prompt": "..."
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Нова структура з мультимодальністю:
|
||
|
||
```json
|
||
POST http://144.76.224.179:9102/route
|
||
|
||
{
|
||
"agent": "daarwizz",
|
||
"message": "Проаналізуй це зображення",
|
||
"mode": "chat",
|
||
"payload": {
|
||
"context": {
|
||
"system_prompt": "...",
|
||
"images": [
|
||
"data:image/png;base64,iVBORw0KGgoAAAANS..."
|
||
],
|
||
"files": [
|
||
{
|
||
"name": "document.pdf",
|
||
"type": "application/pdf",
|
||
"data": "data:application/pdf;base64,JVBERi0xLj..."
|
||
}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 Зміни в Router (NODE1)
|
||
|
||
### 1. Оновити `router-config-final.yml`
|
||
|
||
**Файл:** `/opt/microdao-daarion/router/router-config-final.yml`
|
||
|
||
**Додати підтримку мультимодальних моделей:**
|
||
|
||
```yaml
|
||
llm_profiles:
|
||
# Існуючі профілі...
|
||
|
||
# Мультимодальні профілі
|
||
- name: sofia_vision
|
||
provider: xai
|
||
model: grok-4.1
|
||
settings:
|
||
temperature: 0.7
|
||
max_tokens: 4096
|
||
supports_vision: true # Нове поле
|
||
description: "Sofia - Vision + Code"
|
||
|
||
- name: spectra_multimodal
|
||
provider: ollama
|
||
model: qwen3-vl:latest
|
||
settings:
|
||
temperature: 0.7
|
||
max_tokens: 2048
|
||
supports_vision: true # Нове поле
|
||
description: "Spectra - Vision + Language"
|
||
```
|
||
|
||
---
|
||
|
||
### 2. Оновити Python Router код
|
||
|
||
**Файл:** `/opt/microdao-daarion/router/main.py` (або аналогічний)
|
||
|
||
**Додати обробку images/files:**
|
||
|
||
```python
|
||
from fastapi import FastAPI, HTTPException
|
||
from pydantic import BaseModel
|
||
from typing import List, Optional, Dict, Any
|
||
import base64
|
||
import io
|
||
from PIL import Image
|
||
|
||
app = FastAPI()
|
||
|
||
class ContextPayload(BaseModel):
|
||
system_prompt: Optional[str] = None
|
||
images: Optional[List[str]] = None # base64 encoded images
|
||
files: Optional[List[Dict[str, str]]] = None # file metadata + base64 data
|
||
|
||
class RouteRequest(BaseModel):
|
||
agent: str
|
||
message: str
|
||
mode: str = "chat"
|
||
payload: Optional[Dict[str, Any]] = None
|
||
|
||
def process_images(images: List[str]) -> List[Image.Image]:
|
||
"""Конвертує base64 зображення в PIL Image об'єкти"""
|
||
processed = []
|
||
for img_data in images:
|
||
# Видалити data:image/...;base64, префікс
|
||
if ',' in img_data:
|
||
img_data = img_data.split(',')[1]
|
||
|
||
# Декодувати base64
|
||
img_bytes = base64.b64decode(img_data)
|
||
img = Image.open(io.BytesIO(img_bytes))
|
||
processed.append(img)
|
||
|
||
return processed
|
||
|
||
def process_files(files: List[Dict[str, str]]) -> List[Dict[str, Any]]:
|
||
"""Обробляє файли (PDF, TXT, тощо)"""
|
||
processed = []
|
||
for file_data in files:
|
||
name = file_data.get('name')
|
||
file_type = file_data.get('type')
|
||
data = file_data.get('data')
|
||
|
||
# Видалити data:...;base64, префікс
|
||
if ',' in data:
|
||
data = data.split(',')[1]
|
||
|
||
# Декодувати base64
|
||
file_bytes = base64.b64decode(data)
|
||
|
||
processed.append({
|
||
'name': name,
|
||
'type': file_type,
|
||
'content': file_bytes,
|
||
'size': len(file_bytes)
|
||
})
|
||
|
||
return processed
|
||
|
||
@app.post("/route")
|
||
async def route_message(request: RouteRequest):
|
||
try:
|
||
# Отримати payload
|
||
payload = request.payload or {}
|
||
context = payload.get('context', {})
|
||
|
||
# Обробити зображення (якщо є)
|
||
images = None
|
||
if context.get('images'):
|
||
images = process_images(context['images'])
|
||
print(f"✅ Processed {len(images)} images")
|
||
|
||
# Обробити файли (якщо є)
|
||
files = None
|
||
if context.get('files'):
|
||
files = process_files(context['files'])
|
||
print(f"✅ Processed {len(files)} files")
|
||
|
||
# Визначити агента
|
||
agent_id = request.agent
|
||
|
||
# Перевірити чи агент підтримує vision
|
||
agent_supports_vision = agent_id in ['sofia', 'spectra', 'daarwizz']
|
||
|
||
if images and not agent_supports_vision:
|
||
return {
|
||
"error": f"Агент {agent_id} не підтримує обробку зображень",
|
||
"suggestion": "Спробуйте sofia або spectra для vision tasks"
|
||
}
|
||
|
||
# Підготувати запит до LLM
|
||
llm_request = {
|
||
"model": get_model_for_agent(agent_id),
|
||
"messages": [
|
||
{
|
||
"role": "system",
|
||
"content": context.get('system_prompt', get_default_prompt(agent_id))
|
||
},
|
||
{
|
||
"role": "user",
|
||
"content": request.message
|
||
}
|
||
]
|
||
}
|
||
|
||
# Додати зображення до запиту (для vision моделей)
|
||
if images and agent_supports_vision:
|
||
# Для Ollama qwen3-vl або grok-4.1
|
||
llm_request['images'] = [img_to_base64(img) for img in images]
|
||
|
||
# Додати файли як контекст
|
||
if files:
|
||
files_context = "\n\n".join([
|
||
f"Файл: {f['name']} ({f['size']} bytes)"
|
||
for f in files
|
||
])
|
||
llm_request['messages'][-1]['content'] += f"\n\nПрикріплені файли:\n{files_context}"
|
||
|
||
# Викликати LLM
|
||
response = await call_llm(llm_request)
|
||
|
||
return {
|
||
"data": {
|
||
"text": response['text'],
|
||
"model": response['model'],
|
||
"tokens": response.get('tokens', 0)
|
||
},
|
||
"metadata": {
|
||
"agent": agent_id,
|
||
"has_images": bool(images),
|
||
"has_files": bool(files)
|
||
}
|
||
}
|
||
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=str(e))
|
||
|
||
def img_to_base64(img: Image.Image) -> str:
|
||
"""Конвертує PIL Image в base64"""
|
||
buffered = io.BytesIO()
|
||
img.save(buffered, format="PNG")
|
||
return base64.b64encode(buffered.getvalue()).decode()
|
||
|
||
def get_model_for_agent(agent_id: str) -> str:
|
||
"""Мапінг агентів до моделей"""
|
||
models = {
|
||
'sofia': 'grok-4.1',
|
||
'spectra': 'qwen3-vl:latest',
|
||
'solarius': 'deepseek-r1:70b',
|
||
'daarwizz': 'qwen3-8b',
|
||
}
|
||
return models.get(agent_id, 'qwen3-8b')
|
||
|
||
def get_default_prompt(agent_id: str) -> str:
|
||
"""System prompts для агентів"""
|
||
# ... (існуючі промпти)
|
||
pass
|
||
|
||
async def call_llm(request: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""Викликає LLM (Ollama або xAI)"""
|
||
# ... (існуюча логіка)
|
||
pass
|
||
```
|
||
|
||
---
|
||
|
||
## 🧪 Тестування
|
||
|
||
### 1. Текстовий запит (існуючий)
|
||
|
||
```bash
|
||
curl -X POST http://144.76.224.179:9102/route \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"agent": "daarwizz",
|
||
"message": "Привіт!",
|
||
"mode": "chat"
|
||
}'
|
||
```
|
||
|
||
**Очікується:** `{ "data": { "text": "..." } }`
|
||
|
||
---
|
||
|
||
### 2. Запит з зображенням
|
||
|
||
```bash
|
||
# Конвертувати зображення в base64
|
||
BASE64_IMAGE=$(base64 -w 0 image.png)
|
||
|
||
curl -X POST http://144.76.224.179:9102/route \
|
||
-H "Content-Type: application/json" \
|
||
-d "{
|
||
\"agent\": \"sofia\",
|
||
\"message\": \"Що на цьому зображенні?\",
|
||
\"mode\": \"chat\",
|
||
\"payload\": {
|
||
\"context\": {
|
||
\"images\": [\"data:image/png;base64,$BASE64_IMAGE\"]
|
||
}
|
||
}
|
||
}"
|
||
```
|
||
|
||
**Очікується:** `{ "data": { "text": "На зображенні..." }, "metadata": { "has_images": true } }`
|
||
|
||
---
|
||
|
||
### 3. Запит з файлом
|
||
|
||
```bash
|
||
# Конвертувати PDF в base64
|
||
BASE64_PDF=$(base64 -w 0 document.pdf)
|
||
|
||
curl -X POST http://144.76.224.179:9102/route \
|
||
-H "Content-Type: application/json" \
|
||
-d "{
|
||
\"agent\": \"solarius\",
|
||
\"message\": \"Проаналізуй цей документ\",
|
||
\"mode\": \"chat\",
|
||
\"payload\": {
|
||
\"context\": {
|
||
\"files\": [
|
||
{
|
||
\"name\": \"document.pdf\",
|
||
\"type\": \"application/pdf\",
|
||
\"data\": \"data:application/pdf;base64,$BASE64_PDF\"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
}"
|
||
```
|
||
|
||
**Очікується:** `{ "data": { "text": "Документ містить..." }, "metadata": { "has_files": true } }`
|
||
|
||
---
|
||
|
||
## 📊 Підтримка моделей
|
||
|
||
### Vision-моделі (підтримують зображення):
|
||
|
||
| Агент | Модель | Provider | Vision |
|
||
|-------|--------|----------|--------|
|
||
| Sofia | grok-4.1 | xAI | ✅ |
|
||
| Spectra | qwen3-vl:latest | Ollama | ✅ |
|
||
| Daarwizz | qwen3-8b | Ollama | ❌ → можна додати |
|
||
|
||
### Text-моделі (не підтримують зображення):
|
||
|
||
| Агент | Модель | Provider | Vision |
|
||
|-------|--------|----------|--------|
|
||
| Solarius | deepseek-r1:70b | Ollama | ❌ |
|
||
| Monitor | mistral-nemo:12b | Ollama | ❌ |
|
||
|
||
---
|
||
|
||
## 🔧 Додаткові покращення
|
||
|
||
### 1. STT (Speech-to-Text) для голосових повідомлень
|
||
|
||
**Endpoint:** `POST /stt`
|
||
|
||
```python
|
||
@app.post("/stt")
|
||
async def speech_to_text(audio_data: str):
|
||
"""Конвертує аудіо в текст"""
|
||
# Використати Whisper або інший STT сервіс
|
||
# TODO: Інтеграція з STT Service на НОДА2
|
||
pass
|
||
```
|
||
|
||
---
|
||
|
||
### 2. OCR для документів
|
||
|
||
**Endpoint:** `POST /ocr`
|
||
|
||
```python
|
||
@app.post("/ocr")
|
||
async def extract_text_from_image(image_data: str):
|
||
"""Витягує текст з зображення"""
|
||
# Використати Tesseract або аналогічний
|
||
# TODO: Інтеграція з OCR Service
|
||
pass
|
||
```
|
||
|
||
---
|
||
|
||
### 3. Веб-пошук
|
||
|
||
**Endpoint:** `POST /web-search`
|
||
|
||
```python
|
||
@app.post("/web-search")
|
||
async def web_search(query: str):
|
||
"""Виконує веб-пошук"""
|
||
# Використати Google Search API або аналогічний
|
||
# TODO: Інтеграція з Web Search Service
|
||
pass
|
||
```
|
||
|
||
---
|
||
|
||
## ✅ Чекліст реалізації
|
||
|
||
### Backend (Router на NODE1):
|
||
- [ ] Оновити `router-config-final.yml` з `supports_vision: true`
|
||
- [ ] Додати `process_images()` функцію
|
||
- [ ] Додати `process_files()` функцію
|
||
- [ ] Оновити `/route` endpoint для обробки images/files
|
||
- [ ] Додати маппінг агентів до vision-моделей
|
||
- [ ] Протестувати з Sofia (grok-4.1)
|
||
- [ ] Протестувати з Spectra (qwen3-vl:latest)
|
||
- [ ] Додати error handling для non-vision агентів
|
||
- [ ] Додати логування розміру images/files
|
||
- [ ] Оновити документацію API
|
||
|
||
### Frontend (вже готово):
|
||
- [x] Enhanced Chat UI
|
||
- [x] MultimodalInput компонент
|
||
- [x] Image upload
|
||
- [x] File upload
|
||
- [x] Voice recording (Web Audio API)
|
||
- [x] Switch toggle для Enhanced режиму
|
||
- [x] Base64 encoding для images
|
||
- [x] Відправка в payload.context.images
|
||
- [x] Відправка в payload.context.files
|
||
|
||
---
|
||
|
||
## 🚀 Деплой
|
||
|
||
### 1. SSH до NODE1
|
||
|
||
```bash
|
||
ssh root@144.76.224.179
|
||
```
|
||
|
||
### 2. Backup існуючої конфігурації
|
||
|
||
```bash
|
||
cd /opt/microdao-daarion/router
|
||
cp router-config-final.yml router-config-final.yml.backup
|
||
cp main.py main.py.backup # якщо є
|
||
```
|
||
|
||
### 3. Оновити код
|
||
|
||
```bash
|
||
# Завантажити новий код з Git або вручну відредагувати
|
||
nano router-config-final.yml
|
||
nano main.py
|
||
```
|
||
|
||
### 4. Перезапустити Router
|
||
|
||
```bash
|
||
docker restart dagi-router
|
||
# або
|
||
docker-compose restart router
|
||
```
|
||
|
||
### 5. Перевірити логи
|
||
|
||
```bash
|
||
docker logs -f dagi-router
|
||
```
|
||
|
||
**Шукати:**
|
||
```
|
||
✅ Processed 1 images
|
||
✅ Vision model grok-4.1 selected for sofia
|
||
```
|
||
|
||
---
|
||
|
||
## 📄 Документація
|
||
|
||
**Файли для оновлення:**
|
||
- `INFRASTRUCTURE.md` - додати info про мультимодальність
|
||
- `router-config-final.yml` - оновити конфігурацію
|
||
- `README.md` (Router) - додати приклади з images/files
|
||
|
||
---
|
||
|
||
**СТАТУС:** 📝 Інструкції готові для бекенд команди
|
||
**Наступний крок:** Реалізувати на NODE1 Router
|
||
|