[GH-ISSUE #534] feat(streaming): v3 streamEvents causes INVALID_TOOL_RESULTS 400 and leaks tool results as text tokens #270

Closed
opened 2026-06-05 17:21:23 -04:00 by yindo · 4 comments
Owner

Originally created by @anouar-bm on GitHub (May 12, 2026).
Original GitHub issue: https://github.com/langchain-ai/deepagentsjs/issues/534

Originally assigned to: @christian-bromann on GitHub.

Environment

  • deepagents: 1.10.1
  • @langchain/langgraph: latest
  • Model: OpenAI gpt-5.4 / gpt-4o
  • Node.js 20, Next.js 16 App Router

Description

When using streamEvents with version: "v3" (introduced in #458 / b1e1b7b), two bugs occur on every fresh thread:

Bug 1: INVALID_TOOL_RESULTS 400 error

After the first tool call completes and its ToolMessage is stored, the agent throws:

Error: 400 Invalid parameter: messages with role 'tool' must be a response to a preceding message with 'tool_calls'.

This does not happen with version: "v2" on the same thread and same input. The error occurs on a fresh thread (new thread_id), so it is not caused by stale checkpointer state.

Bug 2: run.messages emits tool result content as text tokens

When iterating run.messages concurrently with run.toolCalls, tool result content (e.g. "[]") appears as a text SSE event — as if the model had emitted it as an AI message. This causes the tool result to be displayed to the user as assistant text.

Observed SSE output:

data: {"type":"tool_start","id":"call_xxx","name":"wam_list_plugins","input":{},"agent":"wam-coordinator"}
data: {"type":"tool_end","id":"call_xxx","output":{"lc":1,"type":"constructor","id":["langchain_core","messages","ToolMessage"],...}}
data: {"type":"text","text":"[]"}   ← tool result leaked as text token
data: {"type":"error","text":"400 Invalid parameter: messages with role 'tool'..."}

Note also that tool_end output is still a serialized ToolMessage {lc:1, type:"constructor", kwargs:{content:"..."}} — not the raw tool return value as expected from the v3 typed API.

Reproduction

const run = await agent.streamEvents(
  { messages: [{ role: "user", content: "hello" }] },
  { version: "v3", configurable: { thread_id: crypto.randomUUID() }, recursionLimit: 100 }
);

await Promise.all([
  (async () => {
    for await (const msgStream of run.messages) {
      for await (const event of msgStream) {
        // Receives tool result content as text-delta events
        console.log("msg event", event);
      }
    }
  })(),
  (async () => {
    for await (const toolCall of run.toolCalls) {
      console.log("tool start", toolCall.name, toolCall.input);
      const output = await toolCall.output; // still ToolMessage, not raw value
      console.log("tool end", output);
    }
  })(),
]);
// → throws 400 INVALID_TOOL_RESULTS after first tool completes

Expected behaviour

  • No INVALID_TOOL_RESULTS error on fresh threads
  • run.messages yields only AI text tokens, not tool results
  • toolCall.output resolves to the raw tool return value (string/object), not a serialized ToolMessage

Workaround

Fall back to version: "v2" with manual on_tool_start/on_tool_end event handling.

Originally created by @anouar-bm on GitHub (May 12, 2026). Original GitHub issue: https://github.com/langchain-ai/deepagentsjs/issues/534 Originally assigned to: @christian-bromann on GitHub. ## Environment - `deepagents`: 1.10.1 - `@langchain/langgraph`: latest - Model: OpenAI gpt-5.4 / gpt-4o - Node.js 20, Next.js 16 App Router ## Description When using `streamEvents` with `version: "v3"` (introduced in #458 / `b1e1b7b`), two bugs occur on every fresh thread: ### Bug 1: `INVALID_TOOL_RESULTS` 400 error After the first tool call completes and its `ToolMessage` is stored, the agent throws: ``` Error: 400 Invalid parameter: messages with role 'tool' must be a response to a preceding message with 'tool_calls'. ``` This does **not** happen with `version: "v2"` on the same thread and same input. The error occurs on a **fresh thread** (new `thread_id`), so it is not caused by stale checkpointer state. ### Bug 2: `run.messages` emits tool result content as text tokens When iterating `run.messages` concurrently with `run.toolCalls`, tool result content (e.g. `"[]"`) appears as a `text` SSE event — as if the model had emitted it as an AI message. This causes the tool result to be displayed to the user as assistant text. Observed SSE output: ``` data: {"type":"tool_start","id":"call_xxx","name":"wam_list_plugins","input":{},"agent":"wam-coordinator"} data: {"type":"tool_end","id":"call_xxx","output":{"lc":1,"type":"constructor","id":["langchain_core","messages","ToolMessage"],...}} data: {"type":"text","text":"[]"} ← tool result leaked as text token data: {"type":"error","text":"400 Invalid parameter: messages with role 'tool'..."} ``` Note also that `tool_end` output is still a serialized `ToolMessage` `{lc:1, type:"constructor", kwargs:{content:"..."}}` — not the raw tool return value as expected from the v3 typed API. ## Reproduction ```typescript const run = await agent.streamEvents( { messages: [{ role: "user", content: "hello" }] }, { version: "v3", configurable: { thread_id: crypto.randomUUID() }, recursionLimit: 100 } ); await Promise.all([ (async () => { for await (const msgStream of run.messages) { for await (const event of msgStream) { // Receives tool result content as text-delta events console.log("msg event", event); } } })(), (async () => { for await (const toolCall of run.toolCalls) { console.log("tool start", toolCall.name, toolCall.input); const output = await toolCall.output; // still ToolMessage, not raw value console.log("tool end", output); } })(), ]); // → throws 400 INVALID_TOOL_RESULTS after first tool completes ``` ## Expected behaviour - No `INVALID_TOOL_RESULTS` error on fresh threads - `run.messages` yields only AI text tokens, not tool results - `toolCall.output` resolves to the raw tool return value (string/object), not a serialized `ToolMessage` ## Workaround Fall back to `version: "v2"` with manual `on_tool_start`/`on_tool_end` event handling.
yindo added the bugtriage: high-impact labels 2026-06-05 17:21:23 -04:00
yindo closed this issue 2026-06-05 17:21:23 -04:00
Author
Owner

@christian-bromann commented on GitHub (May 16, 2026):

Thanks for reporting @anouar-bm, I will take a look!

<!-- gh-comment-id:4466531042 --> @christian-bromann commented on GitHub (May 16, 2026): Thanks for reporting @anouar-bm, I will take a look!
Author
Owner

@christian-bromann commented on GitHub (May 16, 2026):

A fix was applied and is expected to be published early next week.

<!-- gh-comment-id:4466669924 --> @christian-bromann commented on GitHub (May 16, 2026): A fix was applied and is expected to be published early next week.
Author
Owner

@anouar-bm commented on GitHub (May 19, 2026):

Thanks

<!-- gh-comment-id:4487925371 --> @anouar-bm commented on GitHub (May 19, 2026): Thanks
Author
Owner

@christian-bromann commented on GitHub (May 19, 2026):

Note: the langchain package is not published yet so the full fix is not completely rolled out.

<!-- gh-comment-id:4488564157 --> @christian-bromann commented on GitHub (May 19, 2026): Note: the `langchain` package is not published yet so the full fix is not completely rolled out.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/deepagentsjs#270