mateclaw/mateclaw-server/src/main/java/vip/mate/agent/graph
matevip 07eb625d11 fix(agent): collapse SystemMessages at egress to fix LM Studio 400 (#218)
Some OpenAI-compatible providers (LM Studio's built-in server, certain
strict-mode vLLM / SGLang deployments) reject 400 "System message must
be at the beginning" when SystemMessages appear after user / assistant
/ tool messages. The reasoning loop currently emits four SystemMessage
segments — main prompt at index 0, skill catalog inserted at index 1,
progress-ledger snapshot and stale-reminder appended at the end of
nonHistoryPrefix after the runtime-context UserMessage. The latter two
violate the strict shape, so conversations on LM Studio 400 on the
first turn (reported in #218).

Add MessageNormalizer: collects every SystemMessage in the outbound
prompt regardless of position, joins their text with a blank-line
separator, and emits a single SystemMessage at index 0. Non-system
messages keep their relative order, so AssistantMessage(tool_calls) ↔
ToolResponseMessage adjacency is preserved verbatim (required by strict
pair validators).

Wire it into doStreamCall as the first pre-egress step so every node
(reasoning, step-execution, summarizing, plan-generation, limit-exceeded)
inherits the fix without per-node changes, and any future node that
emits multiple SystemMessages stays compliant.

The transformation is semantically equivalent on permissive providers
(OpenAI, DashScope, Ollama, DeepSeek, Kimi, Doubao, GLM) — the merged
token sequence matches what they would have seen across N SystemMessages
— and safe on non-OpenAI protocols (Anthropic, Vertex / Gemini), whose
adapters already extract SystemMessages into a top-level system field
and receive an identical payload.

Kill switch: -Dmateclaw.llm.message-normalizer.enabled=false reverts to
the prior behavior for emergency rollback.

Tests: 11 unit tests on MessageNormalizer cover empty / no-system /
canonical / mid-list / tail / blanks / tool-pair preservation / Prompt
option-reference preservation / kill switch. 1 wiring test pins the
call site in doStreamCall. Full vip.mate.agent.** suite (504 tests)
stays green.

Closes #218.
2026-05-25 17:57:03 +08:00
..
edge Harden goal approval and workspace flows 2026-05-23 22:55:16 +08:00
executor fix(agent): structure-aware truncation to stop mid-JSON cuts inducing hallucination (#187) 2026-05-21 15:13:07 +08:00
lifecycle fix(agent): drop brittle output policing, add evidence-grounded long-task safeguards 2026-05-04 11:55:44 +08:00
node feat(agent): include progress-ledger snapshot in limit-exceeded wrap-up 2026-05-24 23:00:49 +08:00
observation fix(agent): structure-aware truncation to stop mid-JSON cuts inducing hallucination (#187) 2026-05-21 15:13:07 +08:00
plan Harden goal approval and workspace flows 2026-05-23 22:55:16 +08:00
state Harden goal approval and workspace flows 2026-05-23 22:55:16 +08:00
MessageNormalizer.java fix(agent): collapse SystemMessages at egress to fix LM Studio 400 (#218) 2026-05-25 17:57:03 +08:00
NodeStreamingChatHelper.java fix(agent): collapse SystemMessages at egress to fix LM Studio 400 (#218) 2026-05-25 17:57:03 +08:00
StateGraphReActAgent.java feat(goal): graph topology + evaluation node wired into ReAct + Plan-Execute 2026-05-21 14:43:13 +08:00