Skip to content

Narrower update to StreamingClientResult/SSE#68

Closed
trrwilson wants to merge 3 commits intomainfrom
user/travisw/sse-update
Closed

Narrower update to StreamingClientResult/SSE#68
trrwilson wants to merge 3 commits intomainfrom
user/travisw/sse-update

Conversation

@trrwilson
Copy link
Copy Markdown
Collaborator

No description provided.

/// </summary>
/// <typeparam name="T"> The data type representative of distinct, streamable items. </typeparam>
public class StreamingClientResult<T>
: IDisposable
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should not implement IDisposable - see discussion in Azure/azure-sdk-for-net#43392 and related email thread.

/// <returns> A new instance of <see cref="StreamingClientResult{T}"/>. </returns>
public static StreamingClientResult<T> Create(
PipelineResponse response,
Func<JsonElement, IEnumerable<T>> multiElementJsonDeserializerFunc,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We wouldn't take a Func in a ClientModel API - I found an alternate approach that works when you constrain T to IJsonModel that is illustrated in #49

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah, just saw you added this below.

Copy link
Copy Markdown
Collaborator Author

@trrwilson trrwilson Apr 30, 2024

Choose a reason for hiding this comment

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

Got it; I was considering something similar; having an internal "collection of updates" type (that's the thing actually deserialized per chunk, StreamingChatResult in your PR) and doing the public enumeration from there does indeed get us out of the business of doing weird T : IJsonModel<IEnumerable<T>> things. I will snap to the clearer pattern with the intermediate internal type.

/// The optional cancellation token used to control the enumeration.
/// </param>
/// <returns> A new instance of <see cref="StreamingClientResult{T}"/>. </returns>
public static StreamingClientResult<U> Create<U>(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why wouldn't this be T?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Strangely, can't apply where clauses without making the method specifically generic (Create<T>) and then making the method generic emits an analyzer warning that's effectively "hey, that's a different T, FYI."

finally
{
// Always dispose the stream immediately once enumeration is complete for any reason
contentStream.Dispose();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Disposal should be handled by the enumerator, not the enumerable.

Comment thread .dotnet/src/Custom/Chat/ChatClient.cs Outdated
try
{
response = requestMessage.ExtractResponse();
ClientResult genericResult = ClientResult.FromResponse(response);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do you need this genericResult? It does not seem to be used anywhere.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Without it (and exposure via GetRawResponse()), is there another mechanism by which we'd allow customers to query for response headers, etc.?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

but you are not returning this genericResult to the user, and so they cannot read the headers anyway.

finishReason = null;
continue;
}
finishReason = choiceProperty.Value.GetString() switch
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this is lower pri, but would it be better to stay in UTF8 span world and use a sequence of if statements (like for top level properties)

/// <returns> A new instance of <see cref="StreamingClientResult{T}"/>. </returns>
public static StreamingClientResult<T> Create(
PipelineResponse response,
Func<JsonElement, IEnumerable<T>> multiElementJsonDeserializerFunc,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why do we need this parameter? Why the method below (with the constraint to IJsonModel) is not enoughl? In addition, we would need a Create method overload that returns StreamingClientResult for the protocol method case, i.e. when the caller want to get access to raw bytes of each event data so they can deserialize additional properties themselves.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The complexity arises because StreamingChatUpdate has a many-to-one relationship with the wire payload, since we "splat" the uncommon case of multiple choices on a single SSE into separate update instances.

Anne's suggestion to just add an internal intermediate type that implements the canonical T : IJsonModel<T> pattern should get around the need for this; StreamingChatUpdate will then not be directly serializable. (Open question from that: does that mean we should actually make the StreamingChatUpdateCollection type public, despite it not being directly consumed or produced by operations?)

/// The optional cancellation token used to control the enumeration.
/// </param>
/// <returns> A new instance of <see cref="StreamingClientResult{T}"/>. </returns>
public static StreamingClientResult<U> Create<U>(PipelineResponse response, CancellationToken cancellationToken = default)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Curious: is this used anywhere in the OAI client?

return new(response, GetServerSentEventDeserializationEnumerator<U>);
}

public static StreamingClientResult<TInstanceType> Create<TInstanceType, TJsonDataType>(PipelineResponse response, CancellationToken cancellationToken = default)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Curious: is the TJsonDataType strictly required? I notice that it is an internal collection type in your implementation. Is it only to represent a collection? Is the collection format part of the SSE spec? If so, I am wondering if the factory method could just be Create<T> somehow and the collection could be left as an internal implementation detail.

annelo-msft added a commit to annelo-msft/azure-sdk-for-net that referenced this pull request May 2, 2024
annelo-msft added a commit to Azure/azure-sdk-for-net that referenced this pull request May 14, 2024
…entation (#43840)

* Add async and sync enumerable client results

* Implement IAsyncDisposable

* Remove constrant and disposable; update ClientResult so response can be replaced in a polling paradigm

* rename and upate tests

* initial addition of files from joseharriaga/openai-in-typespec#68

* make it build

* hello world test

* bootstrap more tests

* more internal tests

* adding enumerator tests; haven't figured out the batch piece yet

* Make batch test pass

* remove collection-event functionality and add tests for public type

* reshuffle

* Add mock convenience SSE type to give POC of lazy request sending

* add tests of delayed request

* Add BinaryData factory method

* remove funcs for creating enumerators

* renames

* postpone call to protocol method from convenience APIs

* implement IAsyncDisposable correctly

* initial pass over cancellation token

* Per FDG, throw OperationCanceledException if cancellation token is cancelled.

* remove factory method taking Func<T> and provide example of layering convenience implementation in a way that postpones sending the request

* rename internal types and WIP adding reader tests

* nits

* parameterize terminal event; TBD to provide virtual method on collection type

* WIP: nits

* WIP: added concatenation of data lines per SSE spec

* updates and bug fixes

* add tests and update per SSE spec

* WIP: refactor to reuse field processing across sync and async methods

* make look a little more like the BCL type proposal

* simplify field implementation a bit

* cosmetic reworking of creating an event from a pending event

* Remove factory method from public API; move MockSseClient to Tests.Internal to access internal SSE types

* update API; reimplement mock client implementations without internal BinaryData enumerable

* Add sync client result collection abstraction

* tidy up and add tests

* add default constructor to ClientResult

* more tidy-up

* rename and add refdocs

* comments

* pr fb

* rework last event id and retry per BCL design shift

* add CHANGELOG entry
annelo-msft added a commit to Azure/azure-sdk-for-net that referenced this pull request May 16, 2024
* Add async and sync enumerable client results

* Implement IAsyncDisposable

* Remove constrant and disposable; update ClientResult so response can be replaced in a polling paradigm

* rename and upate tests

* initial addition of files from joseharriaga/openai-in-typespec#68

* make it build

* hello world test

* bootstrap more tests

* more internal tests

* adding enumerator tests; haven't figured out the batch piece yet

* Make batch test pass

* remove collection-event functionality and add tests for public type

* reshuffle

* Add mock convenience SSE type to give POC of lazy request sending

* add tests of delayed request

* Add BinaryData factory method

* remove funcs for creating enumerators

* renames

* postpone call to protocol method from convenience APIs

* implement IAsyncDisposable correctly

* initial pass over cancellation token

* Per FDG, throw OperationCanceledException if cancellation token is cancelled.

* remove factory method taking Func<T> and provide example of layering convenience implementation in a way that postpones sending the request

* rename internal types and WIP adding reader tests

* nits

* parameterize terminal event; TBD to provide virtual method on collection type

* WIP: nits

* WIP: added concatenation of data lines per SSE spec

* updates and bug fixes

* add tests and update per SSE spec

* WIP: refactor to reuse field processing across sync and async methods

* make look a little more like the BCL type proposal

* simplify field implementation a bit

* cosmetic reworking of creating an event from a pending event

* Remove factory method from public API; move MockSseClient to Tests.Internal to access internal SSE types

* update API; reimplement mock client implementations without internal BinaryData enumerable

* Add sync client result collection abstraction

* tidy up and add tests

* add default constructor to ClientResult

* more tidy-up

* rename and add refdocs

* comments

* Add pageables

* add ClientPage<T>

* pageables implement enumerators

* give page a response constructor

* updates post merging main branch

* Add hello world test for pageable collections

* wip: tests

* some API updates

* make page non-abstract

* add test for continuation token

* add tests for async pageable

* remove nonnull constraint from T on pageables

* add comments to public types

* thread through response updates

* export API

* update comments per pr fb

* Expose factory method for creating ResultPage instead of constructor

* update CHANGELOG

* nit
@trrwilson trrwilson closed this May 21, 2024
@trrwilson trrwilson deleted the user/travisw/sse-update branch May 21, 2024 06:38
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.

3 participants