feat(loop-detection): defer warning injection#2752
feat(loop-detection): defer warning injection#2752ggnnggez wants to merge 5 commits intobytedance:mainfrom
Conversation
|
I’m still pretty new to this part of the stack, so I appreciate how clearly the behavior is explained here. It helped clarify why injecting the warning earlier caused issues with the tool‑call pairing rules. One thing I’m curious about: could multiple queued warnings accumulate in edge cases, or does the drain logic ensure only one warning is ever surfaced per cycle? I’m trying to understand how this behaves under repeated loop conditions. |
|
@M-Raquel Under the normal agent loop, accumulation should still be uncommon. A warning is enqueued in The important edge case is when a run stops before that next model call, for example interruption or failure during tool execution. The previous version could leave a thread-scoped warning behind and drain it into a later invocation for the same thread. The latest update scopes If multiple warnings are pending inside the same run, they are deduplicated and merged into a single |
The warn branch in LoopDetectionMiddleware injected a HumanMessage into state from after_model. The tools node had not yet produced ToolMessage responses to the previous AIMessage(tool_calls=...), so the new HumanMessage landed *between* the assistant's tool_calls and their responses. OpenAI/Moonshot reject the next request with "tool_call_ids did not have response messages" because their validators require tool_calls to be followed immediately by tool messages. Detection now runs in after_model as before, but only enqueues the warning into a per-thread list. Injection happens in wrap_model_call, where every prior ToolMessage is already present in request.messages. The warning is appended at the end as HumanMessage(name="loop_warning") — pairing intact, AIMessage semantics untouched, no SystemMessage issues for Anthropic. Closes bytedance#2029, addresses bytedance#2255 bytedance#2293 bytedance#2304 bytedance#2511. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
04bcacd to
549c1a6
Compare
Fixes #2733.
keeping this PR as draft until the 2.0-m1 patch is released.
Summary:
HumanMessage(name="loop_warning")on the next model request.thread_id,run_id); before_agent clears stale warnings from older runs, and after_agent clears undelivered warnings for the current run.HumanMessage(name="loop_warning").Tests:
uv run pytest tests/test_loop_detection_middleware.pyuv run pytest tests/test_channels.py tests/test_loop_detection_middleware.py