diff --git a/app/api/chat.py b/app/api/chat.py index 490fb16..038b5ee 100644 --- a/app/api/chat.py +++ b/app/api/chat.py @@ -394,7 +394,7 @@ def chat_stream_api( """流式对话(方案 C):与 POST "" 同守卫,逐块推送 LLM 文本。""" agent_type, message = _guard_request(req, request, auth) sid, customer_id = _prepare_turn(req, auth, agent_type) - trace_id = current_trace() or "" + trace_id = current_trace() or new_trace() has_disclaimer = agent_service.needs_disclaimer(agent_type) history = memory_service.get_recent(agent_type, sid) diff --git a/tests/test_chat_stream.py b/tests/test_chat_stream.py index f30f92e..8be2aaf 100644 --- a/tests/test_chat_stream.py +++ b/tests/test_chat_stream.py @@ -157,6 +157,9 @@ def test_stream_contract_and_persist(env, fake_llm): assert first["meta"]["session_id"].startswith("sess-") assert first["meta"]["has_disclaimer"] is True assert first["meta"]["disclaimer"] == agent_service.CHAT_DISCLAIMER + assert first["meta"]["trace_id"] + assert first["meta"]["trace_id"] == first["id"] + assert first["meta"]["trace_id"] == r.headers["X-Trace-Id"] delta_text = "".join( p["choices"][0]["delta"].get("content", "") for p in payloads if "content" in p["choices"][0]["delta"] @@ -178,6 +181,16 @@ def test_stream_contract_and_persist(env, fake_llm): assert hist.status_code == 200 and hist.json()["total"] == 2 +def test_stream_meta_trace_id_when_context_empty(env, fake_llm, monkeypatch): + """无上下文 trace 时须 new_trace(),禁止向客户端下发空 trace_id。""" + monkeypatch.setattr(chat_mod, "current_trace", lambda: "") + r = env["client"].post("/api/chat/stream", json={"message": "hi"}, headers=CUSTOMER) + assert r.status_code == 200 + first = _payloads(r)[0] + assert first["meta"]["trace_id"].startswith("trc-") + assert first["meta"]["trace_id"] == first["id"] + + def test_stream_advisor_no_disclaimer(env, fake_llm): """内部角色(advisor)无免责声明:meta.disclaimer=None,落库 has_disclaimer=0。""" r = env["client"].post("/api/chat/stream", json={"message": "客户情况"}, headers=ADVISOR)