Skip to content

feat: add additional callbacks to LangChain/LangGraph#12281

Merged
lgrammel merged 2 commits intovercel:mainfrom
dflynn15:df/ai-sdk-langgraph-callbacks
Feb 12, 2026
Merged

feat: add additional callbacks to LangChain/LangGraph#12281
lgrammel merged 2 commits intovercel:mainfrom
dflynn15:df/ai-sdk-langgraph-callbacks

Conversation

@dflynn15
Copy link
Contributor

@dflynn15 dflynn15 commented Feb 5, 2026

Background

When using LangGraph with toUIMessageStream, there's no way to access the final graph state after stream completion. The existing onFinal callback only provides aggregated text, not the full state object containing tool calls, custom fields, or other graph-specific data.

Currently, consumers must wrap the raw LangGraph stream with custom logic before passing to toUIMessageStream:

// Current workaround
const stream = withOnSuccess(rawStream, async finalState => {
  await sendAnalytics(finalState);
});
const uiStream = toUIMessageStream(stream);

Summary

Extends StreamCallbacks with three new callbacks:

Callback When Called Receives
onFinish(state) Successful completion LangGraph state (or undefined for model/streamEvents)
onError(error) Stream error The Error object
onAbort() Stream aborted Nothing
toUIMessageStream<MyGraphState>(graphStream, {
  onFinish: async finalState => {
    if (finalState) {
      await saveConversation(finalState.messages);
    }
  },
  onError: error => logger.error('Stream failed', error),
  onAbort: () => logger.warn('Client disconnected'),
});

Callback lifecycle:

Outcome onFinal onFinish onError onAbort
Success ✓ (text) ✓ (state) - -
Error ✓ (partial) - -
Abort ✓ (partial) - -

Other changes:

  • Added parseLangGraphEvent helper to utils.ts (extracts duplicated logic)
  • Updated README with callbacks documentation

Design rationale:

This aligns with the core SDK's streamText callback pattern:

Core SDK This PR Notes
onFinish onFinish Success only, receives result data
onError onError Error path
onAbort onAbort Abort path
- onFinal Langchain-specific legacy (aggregated text)

The naming overlap between onFinal and onFinish exists because onFinal was added to the langchain package before the core SDK standardized on onFinish. We preserve onFinal for backward compatibility.

Trade-offs:

  • onFinish receives undefined for non-LangGraph streams (consistent lifecycle vs. conditional invocation)
  • Abort detection only catches standard AbortError; edge cases go to onError
  • Type safety is opt-in via generic parameter
  • onFinish currently only receives state, not a rich event object like core SDK

Manual Verification

Verified callback behavior using the next-langchain example with a local LangGraph agent. Confirmed:

  • onFinish receives complete state after successful stream
  • onError fires on thrown errors
  • onAbort fires when request is cancelled via AbortController

Future Work

Consolidate onFinal into onFinish (breaking change):

The ideal API would deprecate onFinal and have onFinish receive a unified result object:

interface StreamCallbacks<TState = unknown> {
  onStart?: () => void;
  onChunk?: (text: string) => void;
  onFinish?: (result: StreamResult<TState>) => void;
}

type StreamResult<TState> =
  | { status: 'success'; text: string; state: TState | undefined }
  | { status: 'error'; text: string; error: Error }
  | { status: 'abort'; text: string };

This would:

  • Eliminate the confusing onFinal vs onFinish naming
  • Always call onFinish (success, error, or abort)
  • Match the pattern users expect from the core SDK

Enrich onFinish event data:

The core SDK's onFinish receives rich metadata (usage, finishReason, steps, etc.). We could expand ours:

onFinish?: (event: {
  text: string;           // accumulated text
  state: TState | undefined;  // LangGraph state
  // Future additions:
  messages?: UIMessage[]; // extracted from state
  duration?: number;      // stream duration in ms
}) => void;

Other improvements:

  • Add onStepStart/onStepFinish callbacks for LangGraph step transitions
  • Expand abort detection to handle more edge cases (e.g., ECONNRESET) if users report issues

@vercel-ai-sdk vercel-ai-sdk bot added the ai/core core functions like generateText, streamText, etc. Provider utils, and provider spec. label Feb 5, 2026
Copy link

@hntrl hntrl left a comment

Choose a reason for hiding this comment

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

lgtm!

@lgrammel lgrammel enabled auto-merge (squash) February 12, 2026 14:21
@lgrammel lgrammel merged commit 2b29f7a into vercel:main Feb 12, 2026
17 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai/core core functions like generateText, streamText, etc. Provider utils, and provider spec.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants