Files
microdao-daarion/ROUTER-MULTIMODAL-SUPPORT.md
Apple 3de3c8cb36 feat: Add presence heartbeat for Matrix online status
- 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
2025-11-27 00:19:40 -08:00

13 KiB
Raw Permalink Blame History

🔧 Router - Підтримка мультимодальних запитів

Дата: 2025-11-23
Мета: Додати підтримку images/files у Router payload
Статус: 📝 Інструкції для бекенду


🎯 Що потрібно реалізувати

Поточна структура запиту:

POST http://144.76.224.179:9102/route

{
  "agent": "daarwizz",
  "message": "текст повідомлення",
  "mode": "chat",
  "payload": {
    "context": {
      "system_prompt": "..."
    }
  }
}

Нова структура з мультимодальністю:

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

Додати підтримку мультимодальних моделей:

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:

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. Текстовий запит (існуючий)

curl -X POST http://144.76.224.179:9102/route \
  -H "Content-Type: application/json" \
  -d '{
    "agent": "daarwizz",
    "message": "Привіт!",
    "mode": "chat"
  }'

Очікується: { "data": { "text": "..." } }


2. Запит з зображенням

# Конвертувати зображення в 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. Запит з файлом

# Конвертувати 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

@app.post("/stt")
async def speech_to_text(audio_data: str):
    """Конвертує аудіо в текст"""
    # Використати Whisper або інший STT сервіс
    # TODO: Інтеграція з STT Service на НОДА2
    pass

2. OCR для документів

Endpoint: POST /ocr

@app.post("/ocr")
async def extract_text_from_image(image_data: str):
    """Витягує текст з зображення"""
    # Використати Tesseract або аналогічний
    # TODO: Інтеграція з OCR Service
    pass

3. Веб-пошук

Endpoint: POST /web-search

@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 (вже готово):

  • Enhanced Chat UI
  • MultimodalInput компонент
  • Image upload
  • File upload
  • Voice recording (Web Audio API)
  • Switch toggle для Enhanced режиму
  • Base64 encoding для images
  • Відправка в payload.context.images
  • Відправка в payload.context.files

🚀 Деплой

1. SSH до NODE1

ssh root@144.76.224.179

2. Backup існуючої конфігурації

cd /opt/microdao-daarion/router
cp router-config-final.yml router-config-final.yml.backup
cp main.py main.py.backup  # якщо є

3. Оновити код

# Завантажити новий код з Git або вручну відредагувати
nano router-config-final.yml
nano main.py

4. Перезапустити Router

docker restart dagi-router
# або
docker-compose restart router

5. Перевірити логи

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