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:
chengyongru
2026-04-17 16:20:53 +08:00
committed by Xubin Ren
co-authored by darlingbud
parent e9d727c3a5
commit 44b526c4ee
4 changed files with 189 additions and 5 deletions
+9
View File
@@ -932,6 +932,15 @@ class AgentRunner:
if message.get("role") == "user":
kept = kept[i:]
break
else:
# No user message in the kept window — walk backwards through
# non_system to find the nearest user message and keep it plus
# everything after it. Providers like GLM reject requests
# where the first non-system message is not ``user`` (error 1214).
for idx in range(len(non_system) - 1, -1, -1):
if non_system[idx].get("role") == "user":
kept = non_system[idx:]
break
start = find_legal_message_start(kept)
if start:
kept = kept[start:]
+11
View File
@@ -409,6 +409,17 @@ class LLMProvider(ABC):
recovered["role"] = "user"
merged.append(recovered)
# Safety net: ensure the first non-system message is not a bare
# ``assistant`` message. Providers like GLM reject system→assistant
# with error 1214. This can happen when upstream truncation (e.g.
# _snip_history) drops the only user message. Insert a synthetic
# user message to keep the sequence valid.
for i, msg in enumerate(merged):
if msg.get("role") != "system":
if msg.get("role") == "assistant" and not msg.get("tool_calls"):
merged.insert(i, {"role": "user", "content": "(conversation continued)"})
break
return merged
@staticmethod