mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 23:47:54 +08:00
core/agent: keep a rolled-back turn's reasoning with its calls
A failed turn keeps the calls that already ran as a bare pairing carrier, and clipping the chain-of-thought off that carrier sent the call back to the API with no reasoning at all — thinking mode refuses a call whose reasoning never came back. The carrier keeps the reasoning and drops only the text.
This commit is contained in:
@@ -256,8 +256,11 @@ def _executed_tool_pair(
|
|||||||
"""Assistant call record plus the results of the calls that produced one.
|
"""Assistant call record plus the results of the calls that produced one.
|
||||||
|
|
||||||
Pairs by call id (first result per id), so the history keeps the API's
|
Pairs by call id (first result per id), so the history keeps the API's
|
||||||
one-result-per-call shape even when a model repeats an id. Text and
|
one-result-per-call shape even when a model repeats an id. Text goes with
|
||||||
reasoning go with the rest of the unfinished turn.
|
the rest of the unfinished turn; the reasoning stays, because thinking mode
|
||||||
|
refuses a tool call whose reasoning never came back (see
|
||||||
|
``responses_bridge`` / the DeepSeek chat client, which can only send an
|
||||||
|
empty part once the real chain-of-thought is gone).
|
||||||
"""
|
"""
|
||||||
by_id: dict[str, ToolChatMessage] = {}
|
by_id: dict[str, ToolChatMessage] = {}
|
||||||
for result in results:
|
for result in results:
|
||||||
@@ -273,7 +276,7 @@ def _executed_tool_pair(
|
|||||||
paired.append(by_id[call.id])
|
paired.append(by_id[call.id])
|
||||||
if not executed:
|
if not executed:
|
||||||
return []
|
return []
|
||||||
record = msgspec.structs.replace(assistant, content=UNSET, reasoning_content=UNSET, tool_calls=executed)
|
record = msgspec.structs.replace(assistant, content=UNSET, tool_calls=executed)
|
||||||
return [record, *paired]
|
return [record, *paired]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,11 @@ def _call(call_id: str, name: str = "some_tool") -> AssistantFunctionToolCall:
|
|||||||
def test_rollback_tail_keeps_the_executed_call_of_a_cut_short_batch() -> None:
|
def test_rollback_tail_keeps_the_executed_call_of_a_cut_short_batch() -> None:
|
||||||
messages: list[AnyChatMessage] = [
|
messages: list[AnyChatMessage] = [
|
||||||
UserChatMessage(content="do both"),
|
UserChatMessage(content="do both"),
|
||||||
AssistantChatMessage(content="first, then the other", tool_calls=[_call("call_a"), _call("call_b")]),
|
AssistantChatMessage(
|
||||||
|
content="first, then the other",
|
||||||
|
reasoning_content="step by step",
|
||||||
|
tool_calls=[_call("call_a"), _call("call_b")],
|
||||||
|
),
|
||||||
ToolChatMessage(tool_call_id="call_a", content="a ran"),
|
ToolChatMessage(tool_call_id="call_a", content="a ran"),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -37,9 +41,10 @@ def test_rollback_tail_keeps_the_executed_call_of_a_cut_short_batch() -> None:
|
|||||||
assert len(tail) == 2
|
assert len(tail) == 2
|
||||||
record, result = tail
|
record, result = tail
|
||||||
assert isinstance(record, AssistantChatMessage)
|
assert isinstance(record, AssistantChatMessage)
|
||||||
# The assistant row survives only as the carrier of the pairing.
|
# The assistant row survives as the carrier of the pairing, with the
|
||||||
|
# reasoning thinking mode demands on a call.
|
||||||
assert record.content is UNSET
|
assert record.content is UNSET
|
||||||
assert record.reasoning_content is UNSET
|
assert record.reasoning_content == "step by step"
|
||||||
calls = record.tool_calls
|
calls = record.tool_calls
|
||||||
assert calls is not UNSET
|
assert calls is not UNSET
|
||||||
assert [call.id for call in calls] == ["call_a"]
|
assert [call.id for call in calls] == ["call_a"]
|
||||||
|
|||||||
Reference in New Issue
Block a user