Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reading partial JSON responses as they're streamed #6007

Closed
JamesNK opened this issue Mar 3, 2025 · 3 comments
Closed

Reading partial JSON responses as they're streamed #6007

JamesNK opened this issue Mar 3, 2025 · 3 comments
Labels
area-ai Microsoft.Extensions.AI libraries

Comments

@JamesNK
Copy link
Member

JamesNK commented Mar 3, 2025

I want to combine a JSON schema structured response and streaming. However, that causes the response text to be an incomplete JSON string until the streaming response is finished.

Incomplete JSON is challenging to read with System.Text.Json. All the high-level APIs expect JSON to be complete: JsonDocument.Parse(json), JsonSerializer.Deserialize(json). I don't want to wait until the JSON is complete. The reason I have streaming enabled is to show results to the user as they become available.

I've been thinking about reading partial JSON using Utf8JsonReader: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/use-utf8jsonreader#read-from-a-stream-using-utf8jsonreader

I'd use Utf8JsonReader to read the content into my POCO manually, and then stop when the reader throws an error. That way I at least populate the properties that are available. Utf8JsonReader is a pain to use, but it's not an insurmountable problem.

However, even when doing this I don't think I can get the values of strings that are inprogress. For example, the following JSON that's inprogress:

{ "message": "this is the long message but it's not completed yet

I don't think I would be able to access the message value until it has closing double quotes:

{ "message": "this is the long message but it's not completed yet"

Maybe the solution here is to ask AI to format the output so it splits the message up into an array of strings:

{ "message": ["this is the long message ", "but it's not completed yet

Is that good thing to do? I don't know. I'd need control over the response schema which I do here in this situation, but it might not always be true.

It feels like JSON + streaming is a common problem, with a reasonably hard solution: use Utf8JsonReader. APIs in MEAI to make this easier, and/or samples and guidance would be useful.

@stephentoub stephentoub added the area-ai Microsoft.Extensions.AI libraries label Mar 3, 2025
@stephentoub
Copy link
Member

c: @eiriktsarpalis

@stephentoub stephentoub added this to the Backlog milestone Mar 3, 2025
@eiriktsarpalis
Copy link
Member

Incomplete JSON is challenging to read with System.Text.Json. All the high-level APIs expect JSON to be complete: JsonDocument.Parse(json), JsonSerializer.Deserialize(json). I don't want to wait until the JSON is complete. The reason I have streaming enabled is to show results to the user as they become available.

The only STJ method that really supports streaming is DeserializeAsyncEnumerable, which requires that the payload is top-level JSON values. What would a general-purpose partial/streaming deserialization method look like? I struggle to think of a good way to meaningfully achieve that. If we had something like

public static bool DeserializePartial<T>(ReadOnlySpan<byte> partialJson, ref T? result, DeserializationState? state = null);

How would the consumer of result know what bits of it are new?

I don't think I would be able to access the message value until it has closing double quotes:

It's something we're working at improving for .NET 10 (cf. dotnet/runtime#67337) but this would only work at the reader layer; fundamentally it won't change how values are being returned at the deserialization layer.

{ "message": ["this is the long message ", "but it's not completed yet

Is that good thing to do? I don't know. I'd need control over the response schema which I do here in this situation, but it might not always be true.

How would this work assuming that there are trailing properties after the long message that the deserializer would also need to bind to the POCO that is being returned?

@jeffhandley jeffhandley added the waiting-author-feedback 📭 The author of this issue needs to respond in order for us to continue investigating this issue. label Mar 24, 2025
@JamesNK
Copy link
Member Author

JamesNK commented Mar 26, 2025

I'm not trying to do this anymore.

@JamesNK JamesNK closed this as completed Mar 26, 2025
@dotnet-policy-service dotnet-policy-service bot removed this from the Backlog milestone Mar 26, 2025
@dotnet-policy-service dotnet-policy-service bot removed waiting-author-feedback 📭 The author of this issue needs to respond in order for us to continue investigating this issue. untriaged labels Mar 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-ai Microsoft.Extensions.AI libraries
Projects
None yet
Development

No branches or pull requests

4 participants