Feat: Bildschirm-Sicht nutzt das dedizierte Vision-Modell (Qwen3-VL-8B)

Statt das Bild an die fast-MoE (schwaechere Vision) zu geben: das VL-Modell
beschreibt den Screenshot, die Beschreibung geht als Text-Kontext an Hermes.
-> bessere Bilderkennung UND Lucy behaelt ihr volles Hirn/Gedaechtnis.
MC_VISION_MODEL (default 'vision') steuerbar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-28 22:26:55 +02:00
parent d1ea505b7a
commit 9c3dd9be82
+34 -9
View File
@@ -10,17 +10,43 @@ LAN-only (kein Token in der 2.0-Phase), wie die übrigen MC2-Endpoints.
"""
import logging
import os
import httpx
from fastapi import APIRouter, File, Form, HTTPException, UploadFile
from fastapi.responses import Response, StreamingResponse
from pydantic import BaseModel
from config import HERMES_API_KEY, HERMES_API_MODEL, HERMES_API_URL, VOICE_SERVICE_URL
from config import HERMES_API_KEY, HERMES_API_MODEL, HERMES_API_URL, LLAMA_SWAP_URL, VOICE_SERVICE_URL
log = logging.getLogger(__name__)
router = APIRouter(prefix="/api")
# Bildschirm-Sicht: das DEDIZIERTE Vision-Modell (Qwen3-VL-8B) beschreibt das Bild; die Beschreibung
# geht als TEXT an Hermes -> Lucy behält ihr volles Hirn/Gedächtnis UND nutzt das bessere VL-Modell
# (statt der schwächeren Vision der fast-MoE). Per Env abschaltbar/umstellbar.
VISION_MODEL = os.environ.get("MC_VISION_MODEL", "vision")
async def _describe_image(image_url: str, hint: str) -> str:
"""Lässt das Vision-Modell den Screenshot knapp beschreiben (Deutsch). Leerer String bei Fehler."""
prompt = ("Beschreibe knapp und präzise auf Deutsch, was auf diesem Screenshot zu sehen ist "
"(App/Fenster, wichtige Inhalte, sichtbarer Text/Code). Frage des Nutzers dazu: " + hint)
try:
async with httpx.AsyncClient(timeout=httpx.Timeout(90.0, connect=5.0)) as client:
r = await client.post(f"{LLAMA_SWAP_URL}/v1/chat/completions", json={
"model": VISION_MODEL, "max_tokens": 450, "stream": False,
"messages": [{"role": "user", "content": [
{"type": "text", "text": prompt},
{"type": "image_url", "image_url": {"url": image_url}},
]}],
})
r.raise_for_status()
return (r.json().get("choices") or [{}])[0].get("message", {}).get("content", "").strip()
except Exception as exc:
log.warning("Vision-Beschreibung fehlgeschlagen: %s", exc)
return ""
_TIMEOUT = httpx.Timeout(120.0, connect=5.0) # Chatterbox-TTS auf CPU darf dauern
@@ -140,15 +166,14 @@ async def voice_chat(body: ChatIn) -> StreamingResponse:
messages = []
if body.system:
messages.append({"role": "system", "content": body.system})
user_text = body.text
if body.image:
# Bildschirm-Sicht: User-Message als multimodal (Text + Bild). Das Agent-Modell (fast/Qwen3.6
# mit mmproj) bzw. ein Vision-Modell verarbeitet das Bild via OpenAI-image_url.
messages.append({"role": "user", "content": [
{"type": "text", "text": body.text},
{"type": "image_url", "image_url": {"url": body.image}},
]})
else:
messages.append({"role": "user", "content": body.text})
# Bildschirm-Sicht: erst das Vision-Modell beschreiben lassen, dann die Beschreibung als TEXT-
# Kontext an Hermes (Lucy antwortet mit vollem Hirn/Gedächtnis, sieht aber via besserem VL-Modell).
desc = await _describe_image(body.image, body.text)
if desc:
user_text = f"[Bildschirm-Sicht — das ist gerade auf dem Schirm zu sehen:\n{desc}\n]\n\n{body.text}"
messages.append({"role": "user", "content": user_text})
payload = {"model": body.model or HERMES_API_MODEL, "messages": messages, "stream": True}
headers = {
"Authorization": f"Bearer {HERMES_API_KEY}",