diff --git a/tier2/gen-worker/service/bootstrap.py b/tier2/gen-worker/service/bootstrap.py index 3ec6b804..b0c7fbfe 100644 --- a/tier2/gen-worker/service/bootstrap.py +++ b/tier2/gen-worker/service/bootstrap.py @@ -48,6 +48,17 @@ WRITER_KICK_TEXT = ( ) +def _user_input(text: str) -> dict[str, Any]: + """组装 POST /chat/ 的 input(一条 user Msg)。 + + ChatRequest.input 是 agentscope.message.Msg;Msg.content 必须是【内容块列表】而非裸字符串 + (2026-06-24 活服务 smoke 实证:发裸字符串 content 返 422「Input should be a valid list」—— + Python 端 UserMsg(content=str) 的字符串自动包块只发生在构造器内,经 REST 传 JSON 不走那条, + 故须显式发 [TextBlock])。TextBlock JSON 形状 = {type:"text", text:...}(源码 message/_block.py:11)。 + """ + return {"name": "user", "role": "user", "content": [{"type": "text", "text": text}]} + + def _resolve_m3_credential_payload(model_name: str = "MiniMax-M3") -> dict[str, Any]: """组装注册 M3 凭据(POST /credential/)的请求体——base_url/key 经 worker.client 解析,绝不硬编码。 @@ -163,7 +174,7 @@ async def start_new_game( chat_body = { "agent_id": agent_id, "session_id": session_id, - "input": {"name": "user", "role": "user", "content": WRITER_KICK_TEXT}, + "input": _user_input(WRITER_KICK_TEXT), } chat = await _post(http, base_url, "/chat/", chat_body, user_header=user_id) @@ -197,7 +208,7 @@ async def resume_session( chat_body = { "agent_id": agent_id, "session_id": session_id, - "input": {"name": "user", "role": "user", "content": text}, + "input": _user_input(text), } async with httpx.AsyncClient() as http: chat = await _post(http, base_url, "/chat/", chat_body, user_header=user_id) @@ -259,7 +270,7 @@ async def iterate_existing_project( chat_body = { "agent_id": agent_id, "session_id": session_id, - "input": {"name": "user", "role": "user", "content": text}, + "input": _user_input(text), } chat = await _post(http, base_url, "/chat/", chat_body, user_header=user_id)