GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||
GeezerVison is showing github.com — feat(streaming): stream tool call argument deltas in TemporalStreamingModel by vkalmathscale · Pull Request #355 · scaleapi/scale-agentex-python Conversation
|
| 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.
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.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:
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).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.
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").
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_streaming_model.py::TestStreamingModelFunctionCallArgsStreaming:
ToolRequestContent, one StreamTaskMessageDelta(ToolRequestDelta) per ArgumentsDelta event preserving the delta text, and one final StreamTaskMessageFull(ToolRequestContent) with parsed args.arguments={} in the final Full and logs a WARNING.test_streaming_model.py suite passes (42/42).ruff check clean on both modified files.streaming_mode="off" suppresses tool-arg deltas (only the final persisted message exists on close).cc reviewers familiar with #333's CoalescingBuffer design.
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.
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.arguments={} + WARNING log), complementing the existing 42-test suite.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.
| 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.
There was an error while loading. Please reload this page.
…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).
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
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.
…call-arg-deltas # Conflicts: # src/agentex/lib/core/temporal/plugins/openai_agents/models/temporal_streaming_model.py
Co-authored-by: Claude Opus 4.8 <[email protected]>
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.