Skip to content

[Platform] Add DeferredResult::asPartialJsonStream() for streaming structured output#2115

Draft
wachterjohannes wants to merge 6 commits into
symfony:mainfrom
wachterjohannes:platform-deferred-partial-json-stream
Draft

[Platform] Add DeferredResult::asPartialJsonStream() for streaming structured output#2115
wachterjohannes wants to merge 6 commits into
symfony:mainfrom
wachterjohannes:platform-deferred-partial-json-stream

Conversation

@wachterjohannes

Copy link
Copy Markdown
Contributor
Q A
Bug fix? no
New feature? yes
Docs? yes
Issues -
License MIT

Note

Depends on #2113. Merge after that one or rebase this PR once it lands.

Follow-up to #2113. The PartialJsonParser introduced there is a static utility — useful, but it leaves consumers to wire up the streaming buffer themselves. This PR adds DeferredResult::asPartialJsonStream(), which feeds the running text buffer into the parser and yields each new partial value, so the caller doesn't have to touch Object denormalization or build their own listener.

Behavior

  • Accumulates TextDeltas from asTextStream() into a single buffer.
  • Re-parses the buffer with PartialJsonParser after every delta.
  • Yields the recovered value (array / scalar / object) only when it differs from the previous one — so whitespace-only deltas don't produce duplicate snapshots.
  • Silently swallows buffers that are not recoverable yet (parser sets \$errorMessage).

Usage

\$deferred = \$platform->invoke('gpt-5-mini', \$messages, ['stream' => true]);

foreach (\$deferred->asPartialJsonStream() as \$partial) {
    // render the partial structure (array/object/scalar)
}

The return type is intentionally mixed (matches the parser) — this PR stays at the "raw JSON" layer. Typed object denormalization on top of the partial stream is a separate follow-up.

Notes

  • New example at examples/platform/partial-json-stream.php prompts a model for a structured book object via streaming output and renders each snapshot as it grows.
  • Docs extended under the existing "Parsing Partial JSON from Streams" section.
  • CHANGELOG entry added.
  • composer test and vendor/bin/phpstan analyse on the platform component pass (the existing unmatched-ignore in Schema.php is unrelated and tracked in [Platform] Remove unmatched PHPStan ignore in JsonSchema Schema attribute #2110).

Adds a static, dependency-free utility that recovers the largest valid
structure from an incomplete JSON string. This unblocks rendering
partial objects from streamed structured output before the model
finishes emitting them.

The implementation handles trailing commas, unclosed strings, dangling
colons, partial true/false/null literals, and unclosed object/array
structures. Ported from modelflow-ai's OptimisticJsonParser.

Includes example, documentation under Structured Output, and an entry
in the platform CHANGELOG.
…ructured output

Wires the new PartialJsonParser to the text-delta stream of a DeferredResult
so consumers can render partial JSON snapshots without rebuilding the buffer
themselves. The generator skips deltas that leave the recovered structure
unchanged and silently swallows unrecoverable buffers, so each iteration is
a denser snapshot of the same logical value.

Includes an example that prompts a model for a structured book object via
streaming output and renders snapshots progressively, an entry in the
platform CHANGELOG, and a docs section under the Partial JSON parsing
chapter.
@wachterjohannes wachterjohannes force-pushed the platform-deferred-partial-json-stream branch from 662f286 to 70370de Compare May 27, 2026 07:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant