feat: 大屏后端(FastAPI+MQTT+sim_engine)
- REST :10085 / MQTT 控制 / SSE 回退 / AI+语音 - 数据源规划:接核心服务端 + 算力引擎真实数据
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""对话开场画面识别 —— 用多模态 LLM(qwen3-vl-flash)识别实时画面中的人数、性别等,
|
||||
并把结果作为上下文注入对话,让 AI 了解当前在场观众。
|
||||
|
||||
与 vision_yolo(YOLO 数人脸/姿态)互补:这里用 LLM 做语义级理解(人数、性别构成、场景描述)。
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import ssl
|
||||
import urllib.request
|
||||
|
||||
import certifi
|
||||
|
||||
from .config import settings
|
||||
|
||||
log = logging.getLogger("dpm.vision")
|
||||
|
||||
_SSL_CTX = ssl.create_default_context(cafile=certifi.where())
|
||||
|
||||
_DEFAULT_PROMPT = (
|
||||
"请识别这张实时画面,只输出一个 JSON 对象(不要输出任何其他文字):"
|
||||
'{"people": 画面中人数(int), "males": 其中男性人数(int), "females": 其中女性人数(int), '
|
||||
'"desc": 一句话中文描述画面(含大致人数、性别构成、人物大致状态,如年龄/坐站/是否看屏幕)。'
|
||||
"若画面无人或不确定,则 people=0、males=0、females=0,desc='画面中暂时没有人'。"
|
||||
)
|
||||
|
||||
|
||||
def analyze_scene(jpeg_b64: str, prompt: str = "") -> dict:
|
||||
"""调用多模态 LLM 识别画面中人数/性别,返回 {ok, people, males, females, desc}。"""
|
||||
payload = {
|
||||
"model": settings.VISION_LLM_MODEL,
|
||||
"messages": [{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": prompt or _DEFAULT_PROMPT},
|
||||
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{jpeg_b64}"}},
|
||||
],
|
||||
}],
|
||||
}
|
||||
req = urllib.request.Request(
|
||||
f"{settings.LLM_BASE_URL}/chat/completions",
|
||||
data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {settings.DASHSCOPE_API_KEY}",
|
||||
},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=30, context=_SSL_CTX) as resp:
|
||||
data = json.loads(resp.read().decode("utf-8"))
|
||||
content = data["choices"][0]["message"].get("content") or ""
|
||||
return _parse(content)
|
||||
|
||||
|
||||
def _parse(text: str) -> dict:
|
||||
"""从模型输出中抽取 JSON 对象并规范化为结果 dict。"""
|
||||
text = (text or "").strip()
|
||||
if text.startswith("```"):
|
||||
lines = [l for l in text.splitlines() if not l.strip().startswith("```")]
|
||||
text = "\n".join(lines).strip()
|
||||
obj: dict = {}
|
||||
i, j = text.find("{"), text.rfind("}")
|
||||
if i != -1 and j > i:
|
||||
try:
|
||||
obj = json.loads(text[i:j + 1])
|
||||
except Exception: # noqa: BLE001
|
||||
log.warning("vision llm: 返回 JSON 解析失败: %s", text[:120])
|
||||
people = _as_int(obj.get("people"))
|
||||
males = _as_int(obj.get("males"))
|
||||
females = _as_int(obj.get("females"))
|
||||
return {
|
||||
"ok": True,
|
||||
"people": people,
|
||||
"males": males,
|
||||
"females": females,
|
||||
"desc": str(obj.get("desc") or "").strip(),
|
||||
}
|
||||
|
||||
|
||||
def _as_int(v):
|
||||
try:
|
||||
return int(float(v))
|
||||
except (TypeError, ValueError):
|
||||
return 0
|
||||
Reference in New Issue
Block a user