fix(middleware): use HumanMessage in LoopDetectionMiddleware for Anthropic compat (#1300)

LoopDetectionMiddleware injected SystemMessage mid-conversation to warn
about repetitive tool calls. This crashes Anthropic models because
langchain_anthropic's _format_messages() requires system messages to
appear only at the start of the conversation — interleaved system
messages raise 'Received multiple non-consecutive system messages'.

Switch the warning injection from SystemMessage to HumanMessage, which
works with all providers (Anthropic, OpenAI, Google, etc.).

Fixes #1299

Co-authored-by: voidborne-d <voidborne-d@users.noreply.github.com>
This commit is contained in:
d 🔹
2026-03-25 08:00:01 +08:00
committed by GitHub
parent 067b19af00
commit 77b8ef79ca
2 changed files with 10 additions and 5 deletions

View File

@@ -21,7 +21,7 @@ from typing import override
from langchain.agents import AgentState
from langchain.agents.middleware import AgentMiddleware
from langchain_core.messages import SystemMessage
from langchain_core.messages import HumanMessage
from langgraph.runtime import Runtime
logger = logging.getLogger(__name__)
@@ -203,8 +203,13 @@ class LoopDetectionMiddleware(AgentMiddleware[AgentState]):
return {"messages": [stripped_msg]}
if warning:
# Inject a system message warning the model
return {"messages": [SystemMessage(content=warning)]}
# Inject as HumanMessage instead of SystemMessage to avoid
# Anthropic's "multiple non-consecutive system messages" error.
# Anthropic models require system messages only at the start of
# the conversation; injecting one mid-conversation crashes
# langchain_anthropic's _format_messages(). HumanMessage works
# with all providers. See #1299.
return {"messages": [HumanMessage(content=warning)]}
return None

View File

@@ -2,7 +2,7 @@
from unittest.mock import MagicMock
from langchain_core.messages import AIMessage, SystemMessage
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
from deerflow.agents.middlewares.loop_detection_middleware import (
_HARD_STOP_MSG,
@@ -81,7 +81,7 @@ class TestLoopDetection:
assert result is not None
msgs = result["messages"]
assert len(msgs) == 1
assert isinstance(msgs[0], SystemMessage)
assert isinstance(msgs[0], HumanMessage)
assert "LOOP DETECTED" in msgs[0].content
def test_warn_only_injected_once(self):