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 logging
import os
import httpx import httpx
from fastapi import APIRouter, File, Form, HTTPException, UploadFile from fastapi import APIRouter, File, Form, HTTPException, UploadFile
from fastapi.responses import Response, StreamingResponse from fastapi.responses import Response, StreamingResponse
from pydantic import BaseModel 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__) log = logging.getLogger(__name__)
router = APIRouter(prefix="/api") 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 _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 = [] messages = []
if body.system: if body.system:
messages.append({"role": "system", "content": body.system}) messages.append({"role": "system", "content": body.system})
user_text = body.text
if body.image: if body.image:
# Bildschirm-Sicht: User-Message als multimodal (Text + Bild). Das Agent-Modell (fast/Qwen3.6 # Bildschirm-Sicht: erst das Vision-Modell beschreiben lassen, dann die Beschreibung als TEXT-
# mit mmproj) bzw. ein Vision-Modell verarbeitet das Bild via OpenAI-image_url. # Kontext an Hermes (Lucy antwortet mit vollem Hirn/Gedächtnis, sieht aber via besserem VL-Modell).
messages.append({"role": "user", "content": [ desc = await _describe_image(body.image, body.text)
{"type": "text", "text": body.text}, if desc:
{"type": "image_url", "image_url": {"url": body.image}}, 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})
else:
messages.append({"role": "user", "content": body.text})
payload = {"model": body.model or HERMES_API_MODEL, "messages": messages, "stream": True} payload = {"model": body.model or HERMES_API_MODEL, "messages": messages, "stream": True}
headers = { headers = {
"Authorization": f"Bearer {HERMES_API_KEY}", "Authorization": f"Bearer {HERMES_API_KEY}",