fix(core): strip null id/name from tool-call-chunk deltas in compat bridge#36962
Open
Christian Bromann (christian-bromann) wants to merge 2 commits intonh/middleware-transformerfrom
Open
fix(core): strip null id/name from tool-call-chunk deltas in compat bridge#36962Christian Bromann (christian-bromann) wants to merge 2 commits intonh/middleware-transformerfrom
Christian Bromann (christian-bromann) wants to merge 2 commits intonh/middleware-transformerfrom
Conversation
…ridge
Many provider integrations (notably Anthropic's `input_json_delta`
path) attach the tool-call `id` and `name` only to the first
`tool_use` chunk; subsequent per-chunk slices carry `id=None,
name=None` and just the fresh `args` segment. The compat bridge
forwarded those `None` values verbatim, producing wire payloads like
`{"type": "tool_call_chunk", "id": null, "name": null, "args": "..."}`.
Consumers that fold deltas via a naive `{...target, ...delta}` spread
(e.g. the langgraph-js SDK's `MessageAssembler.applyContentDelta`)
interpret those as "identifier reset to null" and lose the id/name
captured from `content-block-start`. Downstream extractors then drop
the chunk until the final `content-block-finish` arrives — visible to
end users as tool-call cards appearing all-at-once at the end of a
turn instead of streaming in incrementally (the Deep Agent example
rendering four subagents in a single flicker rather than one after
another).
Introduce `_to_protocol_delta_block` and route every
`content-block-delta` emission (sync / async chunk streams and the
`message_to_events` replay path) through it. For `tool_call_chunk`
and `server_tool_call_chunk` shapes, drop `id` / `name` keys when
they would serialize to `null`. This matches the wire shape produced
by langgraph-js's `toProtocolDeltaBlock`, where identifiers are only
surfaced when they carry a real value.
Merging this PR will not alter performance
Comparing Footnotes
|
- Rename content-block imports to new protocol names (TextContentBlock, ReasoningContentBlock, InvalidToolCall, ToolCall, ToolCallChunk, ServerToolCall, ServerToolCallChunk). - Drop FinishReason and _normalize_finish_reason: the protocol removed ``reason`` from ``MessageFinishData`` in langchain-ai/protocol@2ef8585. Provider-level ``finish_reason`` / ``stop_reason`` now pass through verbatim on ``MessageFinishData.metadata`` for downstream consumers. - Simplify ``_build_message_finish`` and ``_finish_all_blocks``: the tool_use re-classification previously driven by the finish reason is obsolete now that the wire field is gone. - Drop the ``_finish_reason`` accumulator from chat_model_stream: the same data is surfaced via ``response_metadata`` through the passed- through finish metadata. Made-with: Cursor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Many provider integrations (notably Anthropic's
input_json_deltapath) attach the tool-callidandnameonly to the firsttool_usechunk; subsequent per-chunk slices carryid=None, name=Noneand just the freshargssegment. The compat bridge forwarded thoseNonevalues verbatim, producing wire payloads like{"type": "tool_call_chunk", "id": null, "name": null, "args": "..."}.Consumers that fold deltas via a naive
{...target, ...delta}spread (e.g. the langgraph-js SDK'sMessageAssembler.applyContentDelta) interpret those as "identifier reset to null" and lose the id/name captured fromcontent-block-start. Downstream extractors then drop the chunk until the finalcontent-block-finisharrives — visible to end users as tool-call cards appearing all-at-once at the end of a turn instead of streaming in incrementally (the Deep Agent example rendering four subagents in a single flicker rather than one after another).Introduce
_to_protocol_delta_blockand route everycontent-block-deltaemission (sync / async chunk streams and themessage_to_eventsreplay path) through it. Fortool_call_chunkandserver_tool_call_chunkshapes, dropid/namekeys when they would serialize tonull. This matches the wire shape produced by langgraph-js'stoProtocolDeltaBlock, where identifiers are only surfaced when they carry a real value.