Add MessageDelta streaming tool-call status helpers#549
Merged
Conversation
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.
Problem
Streaming consumers (Phoenix LiveView, Sagents) need to track the lifecycle of a tool call as it arrives in pieces across multiple
MessageDeltaevents: first the call is identified, thendisplay_textis filled in, then execution status moves throughexecuting→completed/failed. Today every consumer reimplements the same merge-and-update logic againstdelta.tool_calls, including subtle rules like "never overwrite an existingdisplay_textwithnil" and "don't clear the in-flight UI until every sibling tool call has terminated." Those rules belong on the library side.Solution
Add four small, well-documented helpers to
LangChain.MessageDeltathat codify the merge rules:upsert_tool_call/2— append a newToolCallor merge non-nil fields onto an existing one with the samecall_id.statusis promoted one-way (:incomplete→:complete, never back), andmetadatais shallow-merged with incoming keys winning per-key.set_tool_execution_status/3— drive themetadata["execution_status"]lifecycle for a specificcall_id, delegating toToolCall.set_execution_status/2.set_tool_display_text/3— refinedisplay_textas more is learned ("Reading file"→"Reading \"outline.md\" (lines 60–100)"). Anilincoming value is an explicit no-op so the UI never regresses from showing something to showing nothing.all_tools_terminal?/1— gate UI cleanup on every tool call reaching a terminal status. Returnsfalsefornilor emptytool_callsso empty deltas don't trigger premature clears.All helpers are additive — no existing
MessageDeltabehavior changes — and they toleratenil/emptytool_callsso callers don't need to guard.Changes
lib/message_delta.ex— Addedupsert_tool_call/2,set_tool_execution_status/3,set_tool_display_text/3,all_tools_terminal?/1, plus privatemerge_tool_call_fields/2,promote_status/2,merge_metadata/2, andupdate_matching_tool_call/3helpers. Each public function carries a@specand a doctest covering the typical case.test/message_delta_test.exs— Added fourdescribeblocks (one per helper) covering: append vs. merge onupsert_tool_call,niltool_calls handling, per-field merge precedence, one-way:completestatus promotion, shallow metadata merge,set_*no-ops when no matchingcall_idis present, thenil-display-text no-op rule, and terminal/non-terminal cases forall_tools_terminal?.Testing
MessageDelta.Run locally with: