Kaynağa Gözat

feat(voiceprint): auto-register on first speech, hide speaker name from LLM output

- Auto-register new speakers without explicit command
- Speaker context injected as system message (not user message)
- LLM instructed to personalize but NEVER mention speaker name
wenhongquan 3 hafta önce
ebeveyn
işleme
d5d1c71fb5
1 değiştirilmiş dosya ile 23 ekleme ve 27 silme
  1. 23 27
      asr_agent/worker.py

+ 23 - 27
asr_agent/worker.py

@@ -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})