Skip to content

perf(models/pi-ai): parse streamed tool-call arguments incrementally - #2740

Merged
mattzcarey merged 1 commit into
mainfrom
perf/pi-ai-streaming-tool-args
Oct 29, 2026
Merged

mattzcarey merged 1 commit into
mainfrom
perf/pi-ai-streaming-tool-args

Conversation

@mattzcarey

@mattzcarey mattzcarey commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

The chat-completions wire in agents/models/pi-ai now parses streamed tool-call arguments with a new PartialArguments class instead of calling pi-ai's parseStreamingJson on the whole text after every delta. Partial arguments still update on every delta. The final arguments still come from parseStreamingJson in closeToolCall, so finished tool calls are unchanged.

Fixes #2734. For every delta, parseStreamingJson runs a JSON.parse that throws, rebuilds the string one character at a time in repairJson, throws again, then runs partial-json. That's quadratic over a long argument, like a file passed to write.

PartialArguments keeps the lexer state (open containers, in-string, pending escape) between deltas. On each delta it closes the open string and containers and calls the native JSON.parse once:

  • If the text ends mid-token (a key, a number, tru), the closed text doesn't parse, and the previous snapshot stands until the next delta.
  • Text that needs pi-ai's repairs (a raw control character or an invalid escape inside a string) goes back to parseStreamingJson for the rest of that call.
  • One difference: a partial string keeps its trailing whitespace, where partial-json trims it.
main this PR
23 KB write argument in 6-char deltas (Node microbenchmark) 673 ms 34 ms
tool-argument parsing, allocated per Pi harness workload 5.0 MB 1.1 MB
everything allocated per Pi harness workload 33.0 MB 30.0 MB

The workload numbers come from replaying recorded Workers AI responses through examples/next/harnesses/pi in wrangler dev, with V8 allocation sampling turned on.

pnpm vitest --project workers src/tests/models/ ran. The new partial-arguments.test.ts feeds JSON documents in several chunk sizes. It checks that each run ends at JSON.parse of the whole text, and that every snapshot that changes matches parseStreamingJson of that prefix. It also covers escapes split across deltas and the repair fallback, plus a wire-level test that streams arguments one character at a time.


Devin Review

@changeset-bot

changeset-bot Bot commented Oct 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d75bbae

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
agents Patch
@cloudflare/agent-think Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

Devin Review

"agents": patch
---

`agents/models/pi-ai` parses streamed chat-completions tool-call arguments incrementally, instead of re-parsing the whole argument text on every delta.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changeset lacks required documentation link

The agents/models/pi-ai changeset omits the required documentation link, leaving the release note without a route to details.

Suggested change
`agents/models/pi-ai` parses streamed chat-completions tool-call arguments incrementally, instead of re-parsing the whole argument text on every delta.
`agents/models/pi-ai` parses streamed chat-completions tool-call arguments incrementally, instead of re-parsing the whole argument text on every delta. See [Models for pi-ai](https://github.com/cloudflare/agents/blob/main/docs/agents/models-pi-ai.md).

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +330 to +335
let parser = this.toolArguments.get(index);
if (parser === undefined) {
parser = new PartialArguments();
this.toolArguments.set(index, parser);
}
block.arguments = parser.push(args);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 Interleaved calls remain unsupported

When a new tool index appears, the assembler closes the previous call. Later fragments for that index are ignored, so interleaved calls remain incomplete.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@agent-think

agent-think Bot commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

✅ agents import sizes: no significant changes (e6ada867 → d75bbaeb, workflow run)

@pkg-pr-new

pkg-pr-new Bot commented Oct 27, 2026

Copy link
Copy Markdown

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@2740

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@2740

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@2740

hono-agents

npm i https://pkg.pr.new/hono-agents@2740

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@2740

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@2740

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@2740

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@2740

commit: d75bbae

@mattzcarey
mattzcarey merged commit 8cf6da1 into main Oct 29, 2026
19 checks passed
@mattzcarey
mattzcarey deleted the perf/pi-ai-streaming-tool-args branch October 29, 2026 12:24
@github-actions github-actions Bot mentioned this pull request Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant