From 4445aa7510b98d053cdcbabfd3028d684faeb67a Mon Sep 17 00:00:00 2001 From: korexil <289361616+korexil@users.noreply.github.com> Date: Tue, 13 Oct 2026 17:44:13 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20FunASR=20=E8=AF=86=E5=88=AB=E7=BB=93?= =?UTF-8?q?=E6=9E=9C=E4=BB=A5=20JSON=20=E5=8E=9F=E6=A0=B7=E8=BF=9B?= =?UTF-8?q?=E5=85=A5=E5=AF=B9=E8=AF=9D=E5=8E=86=E5=8F=B2=20(#3411)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FunASR 返回 {"content","language","emotion"} 时,startToChat 只在含 speaker 字段时才解包,否则整段 JSON 字符串会作为用户消息交给 LLM 并写入对话历史。 现在没有 speaker 时只取 content。 --- .../core/handle/receiveAudioHandle.py | 4 + .../tests/core/handle/__init__.py | 0 .../handle/test_start_to_chat_asr_payload.py | 73 +++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 main/xiaozhi-server/tests/core/handle/__init__.py create mode 100644 main/xiaozhi-server/tests/core/handle/test_start_to_chat_asr_payload.py diff --git a/main/xiaozhi-server/core/handle/receiveAudioHandle.py b/main/xiaozhi-server/core/handle/receiveAudioHandle.py index 504669b85..8aae3af26 100644 --- a/main/xiaozhi-server/core/handle/receiveAudioHandle.py +++ b/main/xiaozhi-server/core/handle/receiveAudioHandle.py @@ -61,6 +61,10 @@ async def startToChat(conn: "ConnectionHandler", text): actual_text = text else: actual_text = actual_content + elif "content" in data: + # FunASR 等返回 {"content","language","emotion"}:只把识别正文交给 LLM, + # 否则整段 JSON 会进入对话历史 + actual_text = str(data["content"]) except (json.JSONDecodeError, KeyError): # 如果解析失败,继续使用原始文本 pass diff --git a/main/xiaozhi-server/tests/core/handle/__init__.py b/main/xiaozhi-server/tests/core/handle/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/main/xiaozhi-server/tests/core/handle/test_start_to_chat_asr_payload.py b/main/xiaozhi-server/tests/core/handle/test_start_to_chat_asr_payload.py new file mode 100644 index 000000000..dccc305a4 --- /dev/null +++ b/main/xiaozhi-server/tests/core/handle/test_start_to_chat_asr_payload.py @@ -0,0 +1,73 @@ +"""startToChat must hand the recognized text, not the ASR JSON, to the LLM.""" +import json +import logging +from types import SimpleNamespace + +import pytest + +import core.handle.receiveAudioHandle as receive + + +class _Executor: + def __init__(self): + self.calls = [] + + def submit(self, fn, *args): + self.calls.append(args) + + +def _conn(): + logger = logging.getLogger("test") + return SimpleNamespace( + logger=SimpleNamespace(bind=lambda **_: logger), + introduced_speakers=set(), + current_speaker=None, + need_bind=False, + max_output_size=0, + client_is_speaking=False, + client_listen_mode="auto", + client_abort=True, + executor=_Executor(), + chat=lambda text: None, + ) + + +@pytest.fixture(autouse=True) +def _no_side_effects(monkeypatch): + async def not_handled(conn, text): + return False + + async def ignore(*args, **kwargs): + return None + + monkeypatch.setattr(receive, "handle_user_intent", not_handled) + monkeypatch.setattr(receive, "send_stt_message", ignore) + + +async def test_funasr_payload_without_speaker_passes_only_content(): + conn = _conn() + payload = json.dumps({"content": "今日は寒いね。", "language": "ja", "emotion": "😶"}, ensure_ascii=False) + + await receive.startToChat(conn, payload) + + assert conn.executor.calls == [("今日は寒いね。",)] + assert conn.current_speaker is None + + +async def test_first_speaker_payload_still_keeps_json_once(): + conn = _conn() + payload = json.dumps({"speaker": "Alice", "content": "hello"}, ensure_ascii=False) + + await receive.startToChat(conn, payload) + await receive.startToChat(conn, payload) + + assert conn.executor.calls == [(payload,), ("hello",)] + assert conn.current_speaker == "Alice" + + +async def test_plain_text_is_unchanged(): + conn = _conn() + + await receive.startToChat(conn, "你好") + + assert conn.executor.calls == [("你好",)] From a01f0aa29b4df64880388a67fa4abfa9b05d7e84 Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Tue, 13 Oct 2026 16:54:42 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=8E=BB=E9=99=A4=E6=B2=A1=E5=BF=85?= =?UTF-8?q?=E8=A6=81=E7=9A=84=E6=B5=8B=E8=AF=95=EF=BC=8C=E4=B8=8A=E6=B8=B8?= =?UTF-8?q?=E4=B8=BAstr=E4=B8=8D=E9=9C=80=E8=A6=81=E9=98=B2=E5=BE=A1?= =?UTF-8?q?=E6=80=A7=E5=A2=9E=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/handle/receiveAudioHandle.py | 2 +- .../tests/core/handle/__init__.py | 0 .../handle/test_start_to_chat_asr_payload.py | 73 ------------------- 3 files changed, 1 insertion(+), 74 deletions(-) delete mode 100644 main/xiaozhi-server/tests/core/handle/__init__.py delete mode 100644 main/xiaozhi-server/tests/core/handle/test_start_to_chat_asr_payload.py diff --git a/main/xiaozhi-server/core/handle/receiveAudioHandle.py b/main/xiaozhi-server/core/handle/receiveAudioHandle.py index 8aae3af26..2e81a78b2 100644 --- a/main/xiaozhi-server/core/handle/receiveAudioHandle.py +++ b/main/xiaozhi-server/core/handle/receiveAudioHandle.py @@ -64,7 +64,7 @@ async def startToChat(conn: "ConnectionHandler", text): elif "content" in data: # FunASR 等返回 {"content","language","emotion"}:只把识别正文交给 LLM, # 否则整段 JSON 会进入对话历史 - actual_text = str(data["content"]) + actual_text = data["content"] except (json.JSONDecodeError, KeyError): # 如果解析失败,继续使用原始文本 pass diff --git a/main/xiaozhi-server/tests/core/handle/__init__.py b/main/xiaozhi-server/tests/core/handle/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/main/xiaozhi-server/tests/core/handle/test_start_to_chat_asr_payload.py b/main/xiaozhi-server/tests/core/handle/test_start_to_chat_asr_payload.py deleted file mode 100644 index dccc305a4..000000000 --- a/main/xiaozhi-server/tests/core/handle/test_start_to_chat_asr_payload.py +++ /dev/null @@ -1,73 +0,0 @@ -"""startToChat must hand the recognized text, not the ASR JSON, to the LLM.""" -import json -import logging -from types import SimpleNamespace - -import pytest - -import core.handle.receiveAudioHandle as receive - - -class _Executor: - def __init__(self): - self.calls = [] - - def submit(self, fn, *args): - self.calls.append(args) - - -def _conn(): - logger = logging.getLogger("test") - return SimpleNamespace( - logger=SimpleNamespace(bind=lambda **_: logger), - introduced_speakers=set(), - current_speaker=None, - need_bind=False, - max_output_size=0, - client_is_speaking=False, - client_listen_mode="auto", - client_abort=True, - executor=_Executor(), - chat=lambda text: None, - ) - - -@pytest.fixture(autouse=True) -def _no_side_effects(monkeypatch): - async def not_handled(conn, text): - return False - - async def ignore(*args, **kwargs): - return None - - monkeypatch.setattr(receive, "handle_user_intent", not_handled) - monkeypatch.setattr(receive, "send_stt_message", ignore) - - -async def test_funasr_payload_without_speaker_passes_only_content(): - conn = _conn() - payload = json.dumps({"content": "今日は寒いね。", "language": "ja", "emotion": "😶"}, ensure_ascii=False) - - await receive.startToChat(conn, payload) - - assert conn.executor.calls == [("今日は寒いね。",)] - assert conn.current_speaker is None - - -async def test_first_speaker_payload_still_keeps_json_once(): - conn = _conn() - payload = json.dumps({"speaker": "Alice", "content": "hello"}, ensure_ascii=False) - - await receive.startToChat(conn, payload) - await receive.startToChat(conn, payload) - - assert conn.executor.calls == [(payload,), ("hello",)] - assert conn.current_speaker == "Alice" - - -async def test_plain_text_is_unchanged(): - conn = _conn() - - await receive.startToChat(conn, "你好") - - assert conn.executor.calls == [("你好",)]