Files
ai-companion/app/llm/local_vllm.py
T
ylhan 35e107c290 助手本体:会话引擎、LLM 路由、记忆、身份、语音适配
从 Obsidian 笔记库剥离为独立仓库。规划文档与 P0 报告留在笔记库,
代码(助手本体、TTS 服务、P0 基准脚本、ESP32 固件)在此。

架构:服务器是助手,本地是客户端
- 助手本体跑在 cadadmin-server,N5095 只做 USB 外设桥与网络桥接
- 起因:实测 N5095 无 AVX2,跑不动可用的语音模型

当前技术栈
- LLM:DeepSeek deepseek-flash(云端,必须关思考)
- TTS:Qwen3-TTS 1.7B(服务器 3090,独立 HTTP 服务)
- STT:Zipformer 中英双语 int8(CPU,中文识别率未测)

实测:端到端 16/16 通过,首音延迟 1545ms
(对比:本地 LLM + vits-zh-ll 是 179ms,但音色不满意——音质与延迟
在此硬件上此消彼长)

设计要点
- MemoryStore 接口按"可能远程"设计(全 async、可超时、可失败)
- user_id / session_id / track_id 三者分开,人脸只作候选不自动切身份
- 记忆查询强制带 owner_user_id,无按 id 单独取的口子
- 自动抽取的记忆是"候选",默认不参与检索
- 异步写记忆绑定创建时的身份,不读"当前用户"
- 引擎与传输解耦,只用文件音频即可端到端测试

模型与音频产物不进版本管理(1.1GB),由 download_models.sh 恢复
2026-09-15 11:05:09 +08:00

47 lines
1.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""本机 Qwen3.8-27B(vLLM,OpenAI 兼容)。
实测数据(P0-本地LLM验证报告.md):
关思考 首 token 367ms / 完成 10 tokens
开思考 首 token 1390ms / 完成 187 tokens,且**回答被 max_tokens 截断**
(实测 200 预算下只吐出"我不"两个字)
开思考不只是慢——思考 token 会占用 max_tokens 预算。
语音场景 max_tokens 本来就要设小,所以这不是"慢一点"的取舍,是"说不出话"。
默认关闭。
约束:只接受一条 system 消息,多条返回 400。已在基类归一化。
视觉:本实例不支持("At most 0 image(s) may be provided"),
模型目录里没有视觉塔。视觉要走 llama.cpp + mmproj 那条线。
"""
from __future__ import annotations
from typing import Sequence
from .base import LLMProvider
class LocalVLLMProvider(LLMProvider):
supports_vision = False
def _build_body(self, messages: list[dict], *, tools: Sequence[dict] | None,
max_tokens: int) -> dict:
body: dict = {
"model": self.cfg.model,
"messages": messages,
"stream": True,
"max_tokens": max_tokens,
"stream_options": {"include_usage": True},
}
# 关闭思考。这是本机部署实测唯一有效的方式
# (服务器上的 ctx_test.sh 也用同一写法)。
# 注意:即使关掉,响应里仍可能出现空的 reasoning 字段,属正常。
body["chat_template_kwargs"] = {"enable_thinking": self.cfg.thinking}
if tools:
body["tools"] = list(tools)
body["tool_choice"] = "auto"
return body
__all__ = ["LocalVLLMProvider"]