fix(agent): preserve user message in _snip_history to prevent GLM error 1214
When _snip_history truncates the message history and the only user message
ends up outside the kept window, providers like GLM reject the resulting
system→assistant sequence with error 1214 ("messages 参数非法").
Two-layer fix:
1. _snip_history now walks backwards through non_system messages to recover
the nearest user message when none exists in the kept window.
2. _enforce_role_alternation inserts a synthetic user message
"(conversation continued)" when the first non-system message is a bare
assistant (no tool_calls), serving as a safety net for any edge cases
that slip through.
Co-authored-by: darlingbud <darlingbud@users.noreply.github.com>
This commit is contained in:
committed by
Xubin Ren
co-authored by
darlingbud
parent
e9d727c3a5
commit
44b526c4ee
@@ -195,3 +195,46 @@ class TestEnforceRoleAlternation:
|
||||
assert result[3]["role"] == "user"
|
||||
assert "And 3+3?" in result[3]["content"]
|
||||
assert "(please be quick)" in result[3]["content"]
|
||||
|
||||
def test_leading_assistant_after_system_inserts_synthetic_user(self):
|
||||
"""When the first non-system message is assistant (no tool_calls), a
|
||||
synthetic user message is inserted to prevent GLM error 1214."""
|
||||
msgs = [
|
||||
{"role": "system", "content": "sys"},
|
||||
{"role": "assistant", "content": "previous reply"},
|
||||
{"role": "tool", "tool_call_id": "tc_1", "content": "result"},
|
||||
{"role": "assistant", "content": "after tool"},
|
||||
]
|
||||
result = LLMProvider._enforce_role_alternation(msgs)
|
||||
non_system = [m for m in result if m["role"] != "system"]
|
||||
assert non_system[0]["role"] == "user"
|
||||
assert non_system[0]["content"] == "(conversation continued)"
|
||||
# The original assistant should follow.
|
||||
assert non_system[1]["role"] == "assistant"
|
||||
|
||||
def test_leading_assistant_with_tool_calls_not_patched(self):
|
||||
"""An assistant message with tool_calls at the start is left as-is
|
||||
because tool messages will follow and some providers accept this."""
|
||||
msgs = [
|
||||
{"role": "system", "content": "sys"},
|
||||
{"role": "assistant", "content": None, "tool_calls": [
|
||||
{"id": "tc_1", "type": "function", "function": {"name": "ls", "arguments": "{}"}}
|
||||
]},
|
||||
{"role": "tool", "tool_call_id": "tc_1", "content": "result"},
|
||||
]
|
||||
result = LLMProvider._enforce_role_alternation(msgs)
|
||||
non_system = [m for m in result if m["role"] != "system"]
|
||||
# The assistant has tool_calls so it should NOT be patched.
|
||||
assert non_system[0]["role"] == "assistant"
|
||||
assert non_system[0].get("tool_calls") is not None
|
||||
|
||||
def test_user_after_system_not_patched(self):
|
||||
"""Normal system→user sequence is not modified."""
|
||||
msgs = [
|
||||
{"role": "system", "content": "sys"},
|
||||
{"role": "user", "content": "hello"},
|
||||
{"role": "assistant", "content": "hi"},
|
||||
]
|
||||
result = LLMProvider._enforce_role_alternation(msgs)
|
||||
assert result[1]["role"] == "user"
|
||||
assert result[1]["content"] == "hello"
|
||||
|
||||
Reference in New Issue
Block a user