Skip to content

fix(examples-chat): handler hooks on_llm_new_token (not on_chat_model_stream)#264

Merged
blove merged 1 commit into
mainfrom
claude/genui-streaming-handler-method-fix
May 12, 2026
Merged

fix(examples-chat): handler hooks on_llm_new_token (not on_chat_model_stream)#264
blove merged 1 commit into
mainfrom
claude/genui-streaming-handler-method-fix

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 12, 2026

Summary

PR #262 wired the streaming callback handler to a method that doesn't exist. AsyncCallbackHandler has no on_chat_model_stream — the canonical streaming-token callback (fired by ChatOpenAI(streaming=True)) is on_llm_new_token. The handler was attached but never invoked, so adispatch_custom_event never fired.

Smoking gun from live verification: 622 KB of stream traffic / 412 SSE chunks during a representative GenUI turn, zero a2ui-partial events on the wire. Frontend bridge sat dormant; the surface still rendered correctly via PR #255's wrapped-AIMessage classifier fallback, but the per-component fallback transition (the whole point of this work) was invisible.

Fix

  • Rename the override to on_llm_new_token with the canonical signature (token: str, *, chunk, run_id, parent_run_id, tags, **kwargs).
  • Read tool_call_chunks from chunk.message (the AIMessageChunk wrapped inside ChatGenerationChunk).
  • Gracefully handle chunk=None (legacy LLM path).

Tests

  • All existing 5 handler tests updated to wrap AIMessageChunk in ChatGenerationChunk + call the new method.
  • New 6th test: chunk=None is a no-op (defends against legacy LLM emitters).
  • Streaming smoke test updated similarly.
  • 7/7 passing locally.

Test plan

  • pytest tests/test_a2ui_partial_handler.py tests/test_streaming_smoke.py — 7/7 green
  • Live smoke at /embed post-merge: confirm a2ui-partial events appear on the SSE stream and the frontend bridge populates liveSurfaceStore mid-stream
  • CI green

…at_model_stream)

PR #262 originally hooked into on_chat_model_stream, but that method
does not exist on AsyncCallbackHandler — the canonical streaming-token
callback (fired by ChatOpenAI when streaming=True) is on_llm_new_token,
which delivers each token plus an optional ChatGenerationChunk whose
message field carries the AIMessageChunk with tool_call_chunks.

Result of the bug: the handler was wired but never invoked at runtime,
so adispatch_custom_event never fired and the frontend bridge stayed
dormant. Live smoke at /embed confirmed: zero a2ui-partial events on
the wire across a 622KB / 412-chunk stream.

This hotfix:
- Renames the override to on_llm_new_token with the canonical signature
  (token: str, *, chunk, run_id, parent_run_id, tags, **kwargs).
- Reads tool_call_chunks from chunk.message (ChatGenerationChunk wraps
  the AIMessageChunk in its message field).
- Gracefully handles chunk=None (legacy LLM path with no chunk object).
- Updates tests to wrap AIMessageChunk in ChatGenerationChunk and call
  the new method name. Adds a 6th test asserting chunk=None is a no-op.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cacheplane Ready Ready Preview, Comment May 12, 2026 10:38pm

Request Review

@blove blove merged commit 6ad09af into main May 12, 2026
14 checks passed
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