Skip to content

fix(middleware): handle orphaned tool results in DanglingToolCallMiddleware#1973

Open
octo-patch wants to merge 2 commits intobytedance:mainfrom
octo-patch:fix/issue-1936-dangling-tool-call-orphaned-results
Open

fix(middleware): handle orphaned tool results in DanglingToolCallMiddleware#1973
octo-patch wants to merge 2 commits intobytedance:mainfrom
octo-patch:fix/issue-1936-dangling-tool-call-orphaned-results

Conversation

@octo-patch
Copy link
Copy Markdown
Contributor

Fixes #1936

Problem

DanglingToolCallMiddleware already handles the case where an AIMessage has tool_calls but no corresponding ToolMessage exists (dangling tool call). However, it did not handle a related but distinct problem:

When a tool call cycle completes (AIMessage → ToolMessage(s)) but the user sends a follow-up HumanMessage before the AI has a chance to respond to the tool results, the conversation history becomes:

AI(tool_calls=[A]) → Tool(A) → HumanMessage  ← invalid!

Many LLMs require an AIMessage between ToolMessages and HumanMessages. Without it, every subsequent model invocation fails with errors that cannot be recovered from, causing the session to be permanently broken (as reported in the issue).

Solution

Add a Phase 2 pass to _build_patched_messages that scans the (Phase 1 patched) message list and injects a placeholder AIMessage immediately after any ToolMessage that is directly followed by a HumanMessage.

The two-phase approach ensures correct ordering:

  1. Phase 1 injects synthetic ToolMessages for dangling tool calls (existing behavior)
  2. Phase 2 injects synthetic AIMessages for orphaned tool results (new behavior)

Phase 2 does not inject when a ToolMessage is at the end of the list (the model is about to generate a real response) or when it is already followed by an AIMessage (valid conversation flow).

Testing

  • All 19 existing tests pass (one test expectation updated to reflect the correct 5-message output instead of 4, since Phase 2 now also injects an AIMessage after the Phase 1 synthetic ToolMessage)
  • 5 new tests added in TestOrphanedToolResults:

octo-patch and others added 2 commits April 7, 2026 09:31
…nt context

Reasoning models (e.g. minimax M2.7, DeepSeek-R1) emit <think>...</think>
blocks before their actual output. When such a model is used as the title
model (or as the main agent), the raw thinking content leaked into the thread
title stored in state, so the chat list showed the internal monologue instead
of a meaningful title.

Fixes bytedance#1884

- Add `_strip_think_tags()` helper using a regex to remove all <think>...</think> blocks
- Apply it in `_parse_title()` so the title model response is always clean
- Apply it to the assistant message in `_build_title_prompt()` so thinking
  content from the first AI turn is not fed back to the title model
- Add four new unit tests covering: stripping in parse, think-only response,
  assistant prompt stripping, and end-to-end async flow with think tags
…leware

When a tool call cycle completes (AIMessage → ToolMessage(s)) but the user
sends a follow-up HumanMessage before the AI has a chance to respond to the
tool results, the conversation history becomes malformed:

  AI(tool_calls=[A]) → Tool(A) → HumanMessage

Many LLMs require an AIMessage between ToolMessages and HumanMessages,
so this pattern causes persistent LLM errors that cannot be recovered from.

Fix: add a Phase 2 pass to _build_patched_messages that detects any
ToolMessage directly followed by a HumanMessage and injects a placeholder
AIMessage between them, similar to how Phase 1 handles dangling tool calls.

Closes bytedance#1936
@WillemJiang
Copy link
Copy Markdown
Collaborator

@octo-patch this PR has two unrelated commits. Please keep commits clean so we can apply them at any time.

@WillemJiang WillemJiang added the question Further information is requested label Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

question Further information is requested

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DanglingToolCallMiddleware处理问题不完善导致出错的问题

2 participants