fix(exploration): flatten dict reply payloads instead of storing str(dict)
E2E on t480 showed assistant bubbles rendering {'content': '...'} because
tutu replies carry msgBody as a parsed object and the router stored
str(reply.content). Coerce to the inner text before persisting.
This commit is contained in:
parent
958cefc380
commit
9abf572949
@ -10,6 +10,7 @@ with 409 plus a readable reason, so the rejection itself is feedback to the
|
||||
resident agent.
|
||||
"""
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
@ -61,6 +62,20 @@ class CloseSessionRequest(BaseModel):
|
||||
experience: dict[str, Any]
|
||||
|
||||
|
||||
def _coerce_reply_text(content: Any) -> str:
|
||||
"""Flatten a reply payload to text; tutu returns msgBody as a parsed object, and str(dict) would leak a Python repr into the view."""
|
||||
if isinstance(content, str):
|
||||
return content
|
||||
if isinstance(content, dict):
|
||||
for key in ("content", "text", "message"):
|
||||
value = content.get(key)
|
||||
if isinstance(value, str) and value:
|
||||
return value
|
||||
if content is None:
|
||||
return ""
|
||||
return json.dumps(content, ensure_ascii=False)
|
||||
|
||||
|
||||
def _check_creation_guardrails(
|
||||
campaign: Campaign,
|
||||
triggered_by: ExplorationTrigger,
|
||||
@ -283,7 +298,7 @@ async def send_session_message(
|
||||
|
||||
received_at = utc_now()
|
||||
latency_ms = int((received_at - sent_at).total_seconds() * 1000)
|
||||
reply_text = str(reply.content)
|
||||
reply_text = _coerce_reply_text(reply.content)
|
||||
message_repo.save_message(
|
||||
ExplorationMessage(
|
||||
session_id=session_obj.id,
|
||||
|
||||
@ -175,6 +175,37 @@ async def test_full_lifecycle_create_message_close(seeded_db, mock_channel, clie
|
||||
assert closed["closed_at"] is not None
|
||||
|
||||
|
||||
async def test_dict_reply_content_is_flattened_to_text(seeded_db, monkeypatch, client):
|
||||
"""通道回复 content 为对象(如 tutu msgBody)时应提取文本而非存 str(dict)。"""
|
||||
from agenteval.channels.base import Reply
|
||||
from tests.unit.mock_channel import MockChannel
|
||||
|
||||
class _DictReplyChannel(MockChannel):
|
||||
async def poll_reply(self, question_msg_id, timeout=30.0, poll_interval=1.0):
|
||||
return Reply(
|
||||
question_msg_id=question_msg_id,
|
||||
content={"content": "您好,我是客服"},
|
||||
raw_message={},
|
||||
)
|
||||
|
||||
_stub_channel_factory(monkeypatch, _DictReplyChannel(reply_text="unused"))
|
||||
|
||||
resp = await _create_session(client)
|
||||
assert resp.status_code == 200, resp.text
|
||||
session_id = resp.json()["id"]
|
||||
|
||||
resp = await client.post(
|
||||
f"/api/exploration/sessions/{session_id}/messages",
|
||||
json={"content": "你好"},
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
assert resp.json()["reply"] == "您好,我是客服"
|
||||
|
||||
messages_resp = await client.get(f"/api/exploration/sessions/{session_id}/messages")
|
||||
assistant = [m for m in messages_resp.json()["messages"] if m["role"] == "assistant"]
|
||||
assert assistant[0]["content"] == "您好,我是客服"
|
||||
|
||||
|
||||
async def test_create_requires_existing_running_campaign(seeded_db, client):
|
||||
resp = await _create_session(client, campaign_id="nope")
|
||||
assert resp.status_code == 404
|
||||
|
||||
Loading…
Reference in New Issue
Block a user