|
|
@@ -65,10 +65,12 @@ LLM_TEMPERATURE = 0.7
|
|
|
LLM_TIMEOUT = 25
|
|
|
|
|
|
SYSTEM_PROMPT = (
|
|
|
- "你是友好的中文语音助手,名字叫狄诺尼试验员。"
|
|
|
+ "你是友好的中文语音助手,名字叫小智。"
|
|
|
"回答简洁自然,2-3句即可,用口语化中文。"
|
|
|
"不要使用 Markdown、代码块、表格或特殊符号。"
|
|
|
"不要输出括号注释、不要使用英文缩写。"
|
|
|
+ "如果系统提供了说话人信息,请根据对方的身份调整语气,"
|
|
|
+ "但绝对不要在回复中说出或暗示说话人的名字或代号。"
|
|
|
)
|
|
|
|
|
|
# ── TTS segmentation (xiaozhi-style two-tier) ──
|
|
|
@@ -95,19 +97,21 @@ def _token(room, ident):
|
|
|
# LLM (streaming, <think> filtering)
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
|
|
-async def _llm_stream(prompt: str, hist: list[dict]):
|
|
|
+async def _llm_stream(prompt: str, hist: list[dict], speaker: str | None = None):
|
|
|
"""Stream LLM tokens, dispatching based on LLM_PROVIDER env."""
|
|
|
if LLM_PROVIDER == "mimo":
|
|
|
- async for x in _llm_stream_mimo(prompt, hist):
|
|
|
+ async for x in _llm_stream_mimo(prompt, hist, speaker):
|
|
|
yield x
|
|
|
return
|
|
|
- async for x in _llm_stream_vllm(prompt, hist):
|
|
|
+ async for x in _llm_stream_vllm(prompt, hist, speaker):
|
|
|
yield x
|
|
|
|
|
|
|
|
|
-async def _llm_stream_vllm(prompt: str, hist: list[dict]):
|
|
|
+async def _llm_stream_vllm(prompt: str, hist: list[dict], speaker: str | None = None):
|
|
|
"""vLLM streaming via OpenAI-compatible SSE."""
|
|
|
msgs = [{"role": "system", "content": SYSTEM_PROMPT}]
|
|
|
+ if speaker:
|
|
|
+ msgs.append({"role": "system", "content": f"[内部上下文] 当前说话人: {speaker}。请根据对方身份自然地调整回应风格,但不要在回复中主动提及说话人的名字。"})
|
|
|
msgs.extend(hist)
|
|
|
msgs.append({"role": "user", "content": prompt})
|
|
|
|
|
|
@@ -143,12 +147,14 @@ async def _llm_stream_vllm(prompt: str, hist: list[dict]):
|
|
|
yield "", True
|
|
|
|
|
|
|
|
|
-async def _llm_stream_mimo(prompt: str, hist: list[dict]):
|
|
|
+async def _llm_stream_mimo(prompt: str, hist: list[dict], speaker: str | None = None):
|
|
|
"""Mimo v2.5 LLM streaming via OpenAI-compatible SSE."""
|
|
|
if not MIMO_KEY:
|
|
|
yield "", True
|
|
|
return
|
|
|
msgs = [{"role": "system", "content": SYSTEM_PROMPT}]
|
|
|
+ if speaker:
|
|
|
+ msgs.append({"role": "system", "content": f"[内部上下文] 当前说话人: {speaker}。请根据对方身份自然地调整回应风格,但不要在回复中主动提及说话人的名字。"})
|
|
|
msgs.extend(hist)
|
|
|
msgs.append({"role": "user", "content": prompt})
|
|
|
|
|
|
@@ -623,7 +629,7 @@ class Worker:
|
|
|
logger.info("ASR: %s", txt[:80])
|
|
|
await self._send({"type": "utterance", "text": txt, "seq": 0})
|
|
|
|
|
|
- # ── Voiceprint identification (parallel with LLM prep) ──
|
|
|
+ # ── Voiceprint: identify or auto-register ──
|
|
|
speaker_name = None
|
|
|
if _VP_ENABLED:
|
|
|
store = get_store()
|
|
|
@@ -633,33 +639,23 @@ class Worker:
|
|
|
if speaker_name:
|
|
|
logger.info("VP: identified %s (sim=%.3f)", speaker_name, _sim)
|
|
|
await self._send({"type": "speaker", "name": speaker_name, "confidence": round(_sim, 3)})
|
|
|
-
|
|
|
- # ── Voiceprint registration command ──
|
|
|
- import re
|
|
|
- vp_match = re.match(r"(注册声纹|我是|我叫)\s*(.+)", txt)
|
|
|
- if vp_match and _VP_ENABLED:
|
|
|
- store = get_store()
|
|
|
- if store and store.enabled:
|
|
|
- name = vp_match.group(2).strip()
|
|
|
- user_id = f"user_{hash(name) % 1000000:06d}"
|
|
|
- ok = await loop.run_in_executor(None, store.register, user_id, name, all_audio)
|
|
|
- if ok:
|
|
|
- await self._send({"type": "vp_registered", "name": name, "user_id": user_id})
|
|
|
- speaker_name = name
|
|
|
- logger.info("VP: registered %s", name)
|
|
|
+ else:
|
|
|
+ # Auto-register: first-time speaker
|
|
|
+ auto_id = f"user_{int(time.time() * 1000) % 1000000:06d}"
|
|
|
+ auto_name = f"用户{auto_id[-3:]}"
|
|
|
+ ok = await loop.run_in_executor(None, store.register, auto_id, auto_name, all_audio)
|
|
|
+ if ok:
|
|
|
+ speaker_name = auto_name
|
|
|
+ logger.info("VP: auto-registered %s", auto_name)
|
|
|
+ await self._send({"type": "vp_registered", "name": auto_name, "user_id": auto_id})
|
|
|
|
|
|
logger.info("LLM start")
|
|
|
reply_full = ""
|
|
|
seg = _TTSSegmenter()
|
|
|
tts_segments: list[str] = []
|
|
|
|
|
|
- # ── Build prompt with voiceprint context ──
|
|
|
- prompt = txt
|
|
|
- if speaker_name:
|
|
|
- prompt = f"[说话人: {speaker_name}] {txt}"
|
|
|
-
|
|
|
try:
|
|
|
- async for delta, is_final in _llm_stream(prompt, self.hist):
|
|
|
+ async for delta, is_final in _llm_stream(txt, self.hist, speaker_name):
|
|
|
if delta:
|
|
|
reply_full += delta
|
|
|
await self._send({"type": "reply_partial", "text": reply_full, "seq": 0})
|