From 200bde0bfb92d039b553522a9add2b8b45869fac Mon Sep 17 00:00:00 2001 From: worldmozara Date: Wed, 29 Jul 2026 00:03:04 +0800 Subject: [PATCH] core/todo-nag: inject reasoning_content on synthetic assistant messages DeepSeek's reasoning API enforces: if an assistant with tool_calls sits between two user messages, its reasoning_content must be passed back. The synthetic todo nag pair creates an AssistantChatMessage with tool_calls but no reasoning_content, which triggered a 400 error: "The reasoning_content in the thinking mode must be passed back to the API." Fix: 1. _append_synthetic_todo_list now sets reasoning_content="" so NEW pairs carry the field. 2. refresh_synthetic_todo_nags runs _inject_reasoning_content_for_synthetic to fix EXISTING pairs (in DB) even if reasoning_content was UNSET. 3. The earlier _inject_thinking change is reverted in the next commit since the real root cause was the missing reasoning_content on forged tool_call messages, not the thinking parameter itself. --- src/plyngent/agent/todo_nag.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/plyngent/agent/todo_nag.py b/src/plyngent/agent/todo_nag.py index 5c42b8c..98103ab 100644 --- a/src/plyngent/agent/todo_nag.py +++ b/src/plyngent/agent/todo_nag.py @@ -69,6 +69,7 @@ def _append_synthetic_todo_list(messages: list[AnyChatMessage], body: str) -> st messages.append( AssistantChatMessage( content=UNSET, + reasoning_content="", tool_calls=[ AssistantFunctionToolCall( id=call_id, @@ -130,9 +131,39 @@ def refresh_synthetic_todo_nags( continue messages[index] = ToolChatMessage(tool_call_id=msg.tool_call_id, content=body) updated += 1 + + # Synthetic assistant messages (tool calls but no reasoning_content) trip + # DeepSeek's rule: "if the model performed a tool call between two user + # messages, the assistant's reasoning_content must be passed back." + # Inject empty reasoning_content so the API doesn't reject the request. + updated += _inject_reasoning_content_for_synthetic(messages) return updated +def _inject_reasoning_content_for_synthetic( + messages: list[AnyChatMessage], +) -> int: + """Inject ``reasoning_content=\"\"`` into synthetic nag assistants that lack it.""" + injected = 0 + for index, msg in enumerate(messages): + if not isinstance(msg, AssistantChatMessage): + continue + tool_calls = msg.tool_calls + if tool_calls is UNSET or not tool_calls: + continue + if not any( + isinstance(c, AssistantFunctionToolCall) and is_synthetic_todo_nag_call_id(c.id) for c in tool_calls + ): + continue + if msg.reasoning_content is not UNSET: + continue + from msgspec import structs + + messages[index] = structs.replace(msg, reasoning_content="") + injected += 1 + return injected + + def inject_todo_nag( messages: list[AnyChatMessage], body: str,