Skip to content

fix: use stream(values) for reliable agent streaming - #50

Merged
avoidwork merged 12 commits into
mainfrom
fix/web-search-streaming-hang
May 30, 2026
Merged

fix: use stream(values) for reliable agent streaming#50
avoidwork merged 12 commits into
mainfrom
fix/web-search-streaming-hang

Conversation

@avoidwork

@avoidwork avoidwork commented May 30, 2026

Copy link
Copy Markdown
Owner

Description

Replace streamEvents(v3) with stream(streamMode: "values") for reliable ReAct agent streaming. The v3 protocol does not emit method: "tools" events for ReAct agents, and dual-stream approaches start two independent executions that produce mismatched results.

Type of Change

  • Bugfix (non-breaking change which fixes an issue)

Testing

  • All 743 tests pass (react_agent: 22/22, checkpoint: 9/9, full suite: 743/743)
  • Verified stream(v3) yields state snapshots containing messages array with HumanMessage, AIMessage with tool_calls, and AIMessage with text content

Coverage

  • 100% line coverage maintained

Checklist

  • npm run lint passes
  • Tests pass with 100% line coverage
  • No forbidden patterns used
  • Conventional Commit style applied

avoidwork added 12 commits May 30, 2026 12:16
…ithout text

The streaming event loop used two sequential for-await loops: one on
stream.messages (to collect text) and one on stream (for tool events).
When the LLM generated an AIMessage with tool calls but no text content,
stream.messages yielded a ChatModelStream backed by a ReplayBuffer that
blocked indefinitely waiting for text-delta events that never arrived.
This prevented tool events from ever being processed, causing the agent
to silently hang with no response.

Replace the two-loop approach with a single loop over the raw stream that
processes both text events (from ChatModelStream chunk events) and tool
events in one pass. This ensures nothing blocks and all events are
processed correctly.

Add test cases covering the blocking scenario and the new event-chunk
text extraction approach.
…hoing input

- Iterate stream events directly (not stream.messages) to avoid
  ChatModelStream.text ReplayBuffer blocking bug
- Extract text from content-block-delta protocol events
- Fall back to agent.invoke() when no streaming text captured
- Throw 'No response from agent' when nothing captured instead of
  silently echoing user input
- Update all streaming tests to new event format
Tool always returns 'hello_world!' and requires zero permissions so it's
always available to the LLM. Helps diagnose if the problem is:
- Tool registration/availability
- Streaming text capture (ReplayBuffer blocking)
- Tool execution/sandbox layer
LangGraph v3 streamEvents normalizes internal event names before the
protocol event stream reaches consumers. The previous code checked for
internal names ('on_tool_start', 'on_tool_end', etc.) which never
matched the protocol names ('tool-started', 'tool-finished', etc.).

This caused tool_end events to never fire in production, so tool results
never appeared in the TUI chat. Fixed by checking for both protocol and
legacy names. Also updated test mocks to use protocol event names.
…l_end events

Logs every event method and event key flowing through the stream so we
can see exactly what events arrive and in what order. Look for
[stream] lines in the server output.
…ogging

The agent.invoke() fallback was restarting the entire agent run, causing
infinite tool call loops. Now the streaming loop properly returns the
captured text from content-block-delta events, or throws a clear error
if no text was produced.

Also cleaned up debug logging files (mazd_stream.log, mazd_emit.log,
mazd_cb.log) from react.js and hello.js.
Logs every event the TUI callback receives (type, text, toolName, data, error). This will show if tool_end/tool_error callbacks are actually firing and what data they contain.
Replace streamEvents(v3) approach which did not produce tool events
for ReAct agents. Now uses agent.stream(streamMode: 'values') to
yield state snapshots containing messages array. Extract tool calls
from AIMessage content, extract text from message payload.

This provides a single-execution streaming path with no blocking
issues from ReplayBuffer or mismatched tool/text pairs from dual
stream runs.
@avoidwork avoidwork self-assigned this May 30, 2026
@avoidwork avoidwork changed the title fix: prevent web_search tool from hanging agent when LLM calls tool without text fix: use stream(values) for reliable agent streaming May 30, 2026
@avoidwork
avoidwork merged commit a2aa6db into main May 30, 2026
@avoidwork
avoidwork deleted the fix/web-search-streaming-hang branch May 30, 2026 20:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant