GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — feat(streaming): stream tool call argument deltas in TemporalStreamingModel by vkalmathscale · Pull Request #355 · scaleapi/scale-agentex-python

Conversation

@vkalmathscale

@vkalmathscale vkalmathscale commented May 12, 2026 •
edited by greptile-apps Bot
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Summary

TemporalStreamingModel already streams text deltas and reasoning summary deltas to Redis via StreamingTaskMessageContext, but ResponseFunctionCallArgumentsDeltaEvent was being silently buffered into function_calls_in_progress[...]['arguments'] with no per-delta publish. Consumers only saw the completed tool call surface later (after the activity returned, via downstream hooks if any).

For write-heavy tools — write_file, apply_patch, anything that puts a 2–20KB string into a single argument — the model spends multiple seconds generating the argument body, and the UI sees nothing until the entire activity finishes. The result is a frozen UI followed by an abrupt jump when the activity returns.

This PR threads tool-call argument deltas through the same streaming machinery used for text and reasoning, riding on the CoalescingBuffer + StreamingMode infrastructure added in #333. The buffer's merge helpers already key on tool_call_id for ToolRequestDelta, so coalescing, mode dispatch, and opt-out are inherited from existing infra.

Design

TemporalStreamingModel now opens a streaming_task_message_context per function call (keyed off the call's output_index), with initial_content=ToolRequestContent(...) and the model's configured streaming_mode. Three event handlers participate:

Event Behavior added
ResponseOutputItemAddedEvent (type=function_call) Open the per-call streaming context and stash it on function_calls_in_progress[output_index]['context'].
ResponseFunctionCallArgumentsDeltaEvent Emit StreamTaskMessageDelta(delta=ToolRequestDelta(arguments_delta=..., tool_call_id=..., name=...)) into the per-call context. The coalescing buffer merges consecutive deltas with the same tool_call_id.
ResponseOutputItemDoneEvent (type=function_call) Parse the accumulated args (with a graceful empty-dict fallback on JSONDecodeError), emit a final StreamTaskMessageFull(content=ToolRequestContent(...)), and close the context.

End-of-loop cleanup defensively closes any function-call contexts that didn't see a Done event (truncated stream or mid-stream exception).

ModelResponse output is unchanged: output_items still receives the same complete ResponseFunctionToolCall. Activity determinism is unaffected — streaming is a side effect.

What this does NOT change

  • Text and reasoning streaming paths are untouched.
  • StreamingMode is already the on/off knob. No new flag. streaming_mode="off" suppresses tool-arg deltas the same way it suppresses text deltas. "per_token" publishes immediately; "coalesced" (default) batches at 50ms / 128 chars.
  • TemporalStreamingHooks.on_tool_start is unchanged. It still fires after the activity returns and still emits a ToolRequestContent Full message via the stream_lifecycle_content activity. See Caveats.

Caveats

  1. Overlap with TemporalStreamingHooks.on_tool_start. Users who pass TemporalStreamingHooks to Runner.run will now see two persisted task_messages per tool call: one created by the model (delta stream + final Full) and one created by the hook (Full only). Both land on the same Redis topic task:{task_id} with different parent_task_message.ids, so a default UI will render two cards for the same logical tool call.

    This needs a follow-up to decide which path owns the canonical ToolRequest emission. Options for review discussion:

    • Silence on_tool_start's Full emit when the model is also emitting (auto-detect via a workflow-instance flag, mirroring how _task_id / _trace_id are threaded today).
    • Remove on_tool_start's Full emit entirely in a follow-up major bump (the model becomes the single source of truth for ToolRequest events).

    Until that follow-up, users who want streamed tool args without duplicate emits should subclass TemporalStreamingHooks and override on_tool_start to a no-op.

  2. Coalescing windows still apply. With the default 50ms / 128-char window, tool args render in ~50ms-granularity chunks rather than per-token. This is the same tradeoff already made for text streaming in perf(streaming): coalesce per-token publishes to Redis (50ms / 128-char window) #333, and the right default for write-heavy tools (UX value is "watch the artifact appear", not "see each token").

  3. Malformed argument JSON. If the model produces invalid JSON for the args (truncated stream, hallucinated structure), the path logs a WARNING and emits arguments={} in the final ToolRequestContent. The raw delta stream is preserved on the consumer side regardless — only the structured final view falls back.

Test plan

  • Two new unit tests in test_streaming_model.py::TestStreamingModelFunctionCallArgsStreaming:
    • Happy path: well-formed args produce one streaming context opened with ToolRequestContent, one StreamTaskMessageDelta(ToolRequestDelta) per ArgumentsDelta event preserving the delta text, and one final StreamTaskMessageFull(ToolRequestContent) with parsed args.
    • Malformed args: emits arguments={} in the final Full and logs a WARNING.
  • Full test_streaming_model.py suite passes (42/42).
  • ruff check clean on both modified files.
  • Manual smoke: deploy to a dev environment with an agent that calls a write-heavy tool, confirm UI sees tool args streaming in coalesced batches.
  • Manual smoke: streaming_mode="off" suppresses tool-arg deltas (only the final persisted message exists on close).

cc reviewers familiar with #333's CoalescingBuffer design.

Greptile Summary

This PR threads tool-call argument deltas through the same CoalescingBuffer-backed streaming machinery already used for text and reasoning output. Each function call now opens its own streaming_task_message_context on ResponseOutputItemAddedEvent, emits a ToolRequestDelta per ResponseFunctionCallArgumentsDeltaEvent, and closes with a ToolRequestContent Full on ResponseOutputItemDoneEvent, eliminating the multi-second UI freeze caused by write-heavy tools buffering their entire argument body.

  • Streaming lifecycle: context opened per call → delta emit per chunk → Full + close on Done; orphan cleanup defensively closes any context that didn't receive a Done event (truncated stream / mid-stream exception).
  • Fault tolerance: JSONDecodeError on the final arg parse falls back to arguments={} and logs only the byte count, keeping sensitive content out of production logs; all stream_update / close calls are individually guarded so a streaming failure never breaks the main activity path.
  • Tests: two new async tests cover the happy path (delta-per-chunk fidelity, parsed Final Full) and the malformed-JSON fallback (arguments={} + WARNING log), complementing the existing 42-test suite.

Confidence Score: 5/5

Safe to merge. The new streaming path is additive and fully isolated from ModelResponse construction; activity determinism is unaffected.

The two previously-flagged issues (double-close and raw-args logging) are both resolved: finally: call_data['context'] = None guarantees the orphan cleanup loop skips already-closed contexts, and the warning log now records only raw_args_bytes=len(raw_args) rather than a slice of the raw payload. All stream_update and close calls are individually guarded so a streaming failure cannot break the Temporal activity.

No files require special attention.

Important Files Changed

Filename Overview
src/agentex/lib/core/temporal/plugins/openai_agents/models/temporal_streaming_model.py Adds per-function-call streaming contexts that emit ToolRequestDelta updates as argument bytes arrive, then a final ToolRequestContent Full on OutputItemDone; previously-flagged double-close is fixed via finally: call_data['context'] = None; raw-args logging is replaced with byte-count-only metadata.
src/agentex/lib/core/temporal/plugins/openai_agents/tests/test_streaming_model.py Adds TestStreamingModelFunctionCallArgsStreaming with two well-scoped unit tests: happy-path (correct delta count/content + parsed Final Full) and malformed-JSON fallback (arguments={} + WARNING logged); helper correctly installs a real TaskMessage to satisfy pydantic validation on StreamTaskMessageDelta.

Reviews (5): Last reviewed commit: "Merge branch 'next' into vkalmath/stream..." | Re-trigger Greptile

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

greptile-apps[bot] reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
@vkalmathscale
feat(streaming): stream tool call argument deltas in TemporalStreamin…
8a0994b
…gModel

Wire ResponseFunctionCallArgumentsDeltaEvent into the streaming layer
introduced in #333, so write-heavy tools (write_file, apply_patch) no
longer freeze the UI for the duration of argument generation.

The model now opens a per-function-call streaming context with a
ToolRequestContent placeholder, emits ToolRequestDelta updates for each
argument delta, and finalizes with a StreamTaskMessageFull containing
the parsed arguments on ResponseOutputItemDoneEvent. Coalescing and mode
dispatch are inherited from the existing streaming infrastructure -- no
new flags or surface area.

ModelResponse output is unchanged; activity determinism is unaffected.
End-of-loop cleanup defensively closes any function-call contexts that
didn't see a Done event (truncated stream or mid-stream exception).

Adds two tests covering the happy path (well-formed JSON args -> deltas
+ parsed Full) and the malformed-args fallback (invalid JSON -> empty
dict + WARNING log).
greptile-apps[bot]
greptile-apps Bot reviewed May 12, 2026
View reviewed changes
Comment thread src/agentex/lib/core/temporal/plugins/openai_agents/models/temporal_streaming_model.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/agentex/lib/core/temporal/plugins/openai_agents/models/temporal_streaming_model.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@vkalmathscale
fix(streaming): drop raw tool args from WARNING log
18ea7af
Logging raw_args[:200] could leak partial file contents, PII, or
secrets from write_file / apply_patch arguments into production log
pipelines. Switch to logging only bounded metadata (tool name + raw
arg byte count).

The existing malformed-args test still passes since it asserts on the
"Failed to parse tool call arguments" prefix, which is preserved.
danielmillerp
danielmillerp approved these changes May 18, 2026
View reviewed changes
stainless-app Bot and others added 2 commits May 18, 2026 22:40
@stainless-app
feat(api): add schedule, checkpoints, and deployment endpoints
53b5c36
@declan-scale
fix: resolve lint and test failures from new endpoints (#360)
bdf129c
@vkalmathscale
vkalmathscale changed the base branch from main to next May 19, 2026 17:33
@vkalmathscale
Merge remote-tracking branch 'origin/next' into vkalmath/stream-tool-…
ab56ef2
…call-arg-deltas

# Conflicts:
#	src/agentex/lib/core/temporal/plugins/openai_agents/models/temporal_streaming_model.py
@stainless-app
stainless-app Bot force-pushed the next branch from 781dfe1 to 84dbf72 Compare May 21, 2026 18:46
@danielmillerp danielmillerp mentioned this pull request May 29, 2026
feat(openai-agents): stream tool-call argument deltas to the UI #378
Closed
NiteshDhanpal and others added 3 commits June 17, 2026 21:10
@NiteshDhanpal @claude
feat(compat): runtime SDK↔backend version guard at ACP startup (#408)
433c999
Co-authored-by: Claude Opus 4.8 <[email protected]>
@stainless-app
codegen metadata
7e5be61
@vkalmathscale
Merge remote-tracking branch 'origin/next' into vkalmath/stream-tool-…
6bd1af3
…call-arg-deltas
@stainless-app
stainless-app Bot force-pushed the next branch from 3439f6e to 4d6f4cf Compare June 22, 2026 19:43
@vkalmathscale
Merge branch 'next' into vkalmath/stream-tool-call-arg-deltas
c714fb1
@vkalmathscale
vkalmathscale requested a review from danielmillerp June 22, 2026 19:59
danielmillerp
danielmillerp approved these changes Jun 22, 2026
View reviewed changes
@vkalmathscale
vkalmathscale merged commit c8de1d4 into next Jun 22, 2026
38 of 39 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@vkalmathscale
vkalmathscale deleted the vkalmath/stream-tool-call-arg-deltas branch June 22, 2026 20:02
@stainless-app stainless-app Bot mentioned this pull request Jun 22, 2026
chore: release main #424
Merged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@greptile-apps greptile-apps[bot] greptile-apps[bot] left review comments
@danielmillerp danielmillerp danielmillerp approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

4 participants

@vkalmathscale @danielmillerp @declan-scale @NiteshDhanpal
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.
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.