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:
@@ -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}",
|
||||||
|
|||||||
Reference in New Issue
Block a user