Skip to content

fix(langchain): reset streamEvents text between model invocations#15254

Merged
lgrammel merged 5 commits into
vercel:mainfrom
eyueldk:fix/terminate-langchain-messages
Jun 3, 2026
Merged

fix(langchain): reset streamEvents text between model invocations#15254
lgrammel merged 5 commits into
vercel:mainfrom
eyueldk:fix/terminate-langchain-messages

Conversation

@eyueldk

@eyueldk eyueldk commented May 13, 2026

Copy link
Copy Markdown
Contributor

Background

When toUIMessageStream consumes LangGraph streamEvents (v2), processStreamEventsEvent tracks streaming text with textStarted / textMessageId (and reasoning with reasoningStarted / reasoningMessageId). Those flags were only cleared at the end of the whole stream, not when a new chat model run started.

If a graph performs multiple LLM calls in one run (e.g. tool use then model again), on_chat_model_start can fire again while textStarted is still true. Subsequent text-delta chunks then reuse the first text part’s id, so later prose appends to that first block. Tool events still append new tool parts after it, so the assistant UIMessage’s parts no longer reflect chronological order in the UI.

Summary

  • On on_chat_model_start, if the previous model turn left streams open, emit reasoning-end and text-end with the correct ids, then reset reasoningStarted / reasoningMessageId and textStarted / textMessageId.
  • After that, set messageId from event.run_id or event.data.run_id as before.

This matches the intent of ending open streams when a new model invocation begins, so the next text-start / reasoning-start pair applies to the new turn.

Manual Verification

  • Use a LangGraph agent with streamEvents (v2) that invokes the chat model more than once in the same run (e.g. tools then model again), with text on both segments.
  • Pipe the iterator through toUIMessageStream and consume the UI message stream.
  • Confirm the resulting assistant message has multiple text parts in order (one per model segment) and that tool parts appear in the order events occurred, not all text merged into the first text part.

Checklist

  • All commits are signed (PRs with unsigned commits cannot be merged)
  • Tests have been added / updated (for bug fixes / features)
  • Documentation has been added / updated (for bug fixes / features)
  • A patch changeset for relevant packages has been added (for bug fixes / features - run pnpm changeset in the project root)
  • I have reviewed this pull request (self-review)

Future Work

Optional: add an automated test that simulates two on_chat_model_start / on_chat_model_stream sequences and asserts the emitted chunk sequence includes text-end before the second segment’s text-start.

…end and text-end when the previous model turn left those streams open. Previously textStarted stayed true across LLM invocations, so later text-delta chunks appended to the first text part while new tool parts were appended afterward, breaking chronological order in the UI message.
Comment thread packages/langchain/src/adapter.ts Outdated

switch (event.event) {
case 'on_chat_model_start': {
case "on_chat_model_start": {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would revert the format here

@lgrammel

Copy link
Copy Markdown
Collaborator

Please add a patch changeset.

@lgrammel lgrammel closed this May 26, 2026
@lgrammel lgrammel reopened this May 26, 2026
@lgrammel lgrammel closed this Jun 3, 2026
@lgrammel lgrammel reopened this Jun 3, 2026
@lgrammel lgrammel closed this Jun 3, 2026
@lgrammel lgrammel reopened this Jun 3, 2026
@lgrammel lgrammel merged commit 3711955 into vercel:main Jun 3, 2026
47 of 48 checks passed
@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

🚀 Published in:

Package Version
ai 7.0.0-canary.162 github npm
@ai-sdk/angular 3.0.0-canary.162 github npm
@ai-sdk/bytedance 2.0.0-canary.47 github npm
@ai-sdk/gateway 4.0.0-canary.96 github npm
@ai-sdk/google-vertex 5.0.0-canary.101 github npm
@ai-sdk/langchain 3.0.0-canary.162 github npm
@ai-sdk/llamaindex 3.0.0-canary.162 github npm
@ai-sdk/mcp 2.0.0-canary.57 github npm
@ai-sdk/otel 1.0.0-canary.108 github npm
@ai-sdk/react 4.0.0-canary.165 github npm
@ai-sdk/rsc 3.0.0-canary.163 github npm
@ai-sdk/svelte 5.0.0-canary.162 github npm
@ai-sdk/vue 4.0.0-canary.162 github npm
@ai-sdk/workflow 1.0.0-canary.79 github npm

gr2m pushed a commit that referenced this pull request Jun 5, 2026
…5254)

## Background

When `toUIMessageStream` consumes LangGraph **`streamEvents`** (v2),
`processStreamEventsEvent` tracks streaming text with `textStarted` /
`textMessageId` (and reasoning with `reasoningStarted` /
`reasoningMessageId`). Those flags were only cleared at the end of the
whole stream, not when a **new** chat model run started.

If a graph performs multiple LLM calls in one run (e.g. tool use then
model again), **`on_chat_model_start`** can fire again while
`textStarted` is still `true`. Subsequent **`text-delta`** chunks then
reuse the first text part’s id, so later prose appends to that first
block. Tool events still append **new** tool parts after it, so the
assistant `UIMessage`’s `parts` no longer reflect chronological order in
the UI.

## Summary

- On **`on_chat_model_start`**, if the previous model turn left streams
open, emit **`reasoning-end`** and **`text-end`** with the correct ids,
then reset `reasoningStarted` / `reasoningMessageId` and `textStarted` /
`textMessageId`.
- After that, set **`messageId`** from `event.run_id` or
`event.data.run_id` as before.

This matches the intent of ending open streams when a new model
invocation begins, so the next **`text-start`** / **`reasoning-start`**
pair applies to the new turn.

## Manual Verification

- Use a LangGraph agent with **`streamEvents`** (v2) that **invokes the
chat model more than once** in the same run (e.g. tools then model
again), with text on both segments.
- Pipe the iterator through **`toUIMessageStream`** and consume the UI
message stream.
- Confirm the resulting assistant message has **multiple text parts in
order** (one per model segment) and that **tool parts** appear in the
order events occurred, not all text merged into the first text part.

## Checklist

- [ ] All commits are signed (PRs with unsigned commits cannot be
merged)
- [ ] Tests have been added / updated (for bug fixes / features)
- [ ] Documentation has been added / updated (for bug fixes / features)
- [ ] A _patch_ changeset for relevant packages has been added (for bug
fixes / features - run `pnpm changeset` in the project root)
- [x] I have reviewed this pull request (self-review)

## Future Work

Optional: add an automated test that simulates two `on_chat_model_start`
/ `on_chat_model_stream` sequences and asserts the emitted chunk
sequence includes **`text-end`** before the second segment’s
**`text-start`**.

---------

Co-authored-by: Lars Grammel <lars.grammel@gmail.com>
gr2m added a commit that referenced this pull request Jul 6, 2026
…ons (#16789)

Backport of #15254 to `release-v6.0` (clean cherry-pick).
`on_chat_model_start` did not reset `textStarted`/`reasoningStarted`, so
multi-invocation streamEvents runs appended later text to the first text
part.

Part of the v6.0 backport tracking issue #16767.

Co-authored-by: eyueldk <Eyueldk@gmail.com>
Co-authored-by: Lars Grammel <lars.grammel@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants