Skip to content

Update long-running operation APIs to use LRO subclient pattern #111

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

Closed
wants to merge 62 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
f4ab277
Add project reference to SCM; move to new paging types
annelo-msft Jun 25, 2024
38dd09d
wire return values and stub out implementations
annelo-msft Jun 25, 2024
17c4078
update page collection to use new page token contract
annelo-msft Jun 26, 2024
9cf0447
Update tests and samples
annelo-msft Jun 26, 2024
7679061
fix page token serialization logic
annelo-msft Jun 26, 2024
6acd48b
add async page collections
annelo-msft Jun 26, 2024
6327af6
Add rehydration overload
annelo-msft Jun 26, 2024
e0c14eb
Add async rehydration overload
annelo-msft Jun 26, 2024
2d46435
backup WIP on generalized implementation
annelo-msft Jun 26, 2024
bae625a
backup WIP on generalized implementation
annelo-msft Jun 26, 2024
03e17b6
remove Assistant-specific page collection implementations in favor of…
annelo-msft Jun 26, 2024
1f150ea
remove Assistant-specific page collection implementations in favor of…
annelo-msft Jun 26, 2024
6ba8dbd
Backup of generalized protocol paging idea
annelo-msft Jun 26, 2024
7c1f74d
remove Assistant-specific page result enumerator implementations in f…
annelo-msft Jun 26, 2024
bc2004e
take BinaryData instead of ClientToken, which cannot be constructed a…
annelo-msft Jun 26, 2024
6138636
rework rehydration methods to use new ClientToken pattern
annelo-msft Jun 26, 2024
bae34b9
add test for rehydration method
annelo-msft Jun 26, 2024
5283bc5
add a second test for rehydration method
annelo-msft Jun 26, 2024
e0305ea
SCM renames
annelo-msft Jun 26, 2024
64bea2b
updates from mini pr review
annelo-msft Jun 26, 2024
ea8db96
rework RequestOptions
annelo-msft Jun 26, 2024
bcfed80
bug fix
annelo-msft Jun 26, 2024
e365566
GetPageCore
annelo-msft Jun 27, 2024
1ed51af
backup of initial thinking on operation-specific page token idea
annelo-msft Jun 27, 2024
2ad6dc1
extend operation-specific page token idea to protocol methods
annelo-msft Jun 27, 2024
5b236a6
rename ClientToken to ContinuationToken
annelo-msft Jun 27, 2024
444bee0
nits: tidy
annelo-msft Jun 27, 2024
0060d5f
add test to illustrate convenience/protocol interop
annelo-msft Jun 28, 2024
0aca908
updates based on feedback from Krzysztof
annelo-msft Jul 1, 2024
c4a8a63
add protocol implementations of all Assistant-related paged methods
annelo-msft Jul 1, 2024
4a9d6af
add convenience implementations of Assistant-related paged methods
annelo-msft Jul 1, 2024
f91969f
move to CurrentPageToken instead of FirstPageToken
annelo-msft Jul 1, 2024
90eed11
starting idea around client as enumerator; backing up
annelo-msft Jul 1, 2024
313753e
more backup
annelo-msft Jul 1, 2024
a8e17e3
tidy up before taking inventory
annelo-msft Jul 1, 2024
c5261d1
backup prior to attempt to converge around single internal collection…
annelo-msft Jul 1, 2024
9c05fc2
more refining
annelo-msft Jul 1, 2024
209b71d
implement interface explicitly
annelo-msft Jul 1, 2024
b0d5f04
more tidy
annelo-msft Jul 1, 2024
c5b568d
Make it work for async
annelo-msft Jul 1, 2024
e07fb0b
restructure to look more like mini-client
annelo-msft Jul 2, 2024
f3c5e96
simplify
annelo-msft Jul 2, 2024
8b762e2
more nits and simplify
annelo-msft Jul 2, 2024
7cd2e92
nits
annelo-msft Jul 2, 2024
67d70fa
renames and a little rework
annelo-msft Jul 2, 2024
daab108
move page tokens
annelo-msft Jul 2, 2024
818b6bd
Implement remaining Assistants endpoings
annelo-msft Jul 2, 2024
34264b5
Implement vector store paginated endpoints
annelo-msft Jul 2, 2024
74e8b34
follow-ups to convenience and tests for vector stores
annelo-msft Jul 2, 2024
827cf2b
bug fix
annelo-msft Jul 2, 2024
891ccf5
temp bug fix
annelo-msft Jul 2, 2024
b15bd18
Proof of concept of PageEnumerator pattern for MessageCollection
annelo-msft Jul 2, 2024
34175dc
Refactor to PageEnumerator pattern
annelo-msft Jul 2, 2024
4a175d8
bug fix
annelo-msft Jul 2, 2024
8656f12
nit
annelo-msft Jul 2, 2024
4d2bf33
updates from SCM polish work
annelo-msft Jul 3, 2024
73faa95
Implement new LRO pattern on top of page collection implementatino
annelo-msft Jul 5, 2024
fc5d7ed
add missed files
annelo-msft Jul 5, 2024
529b399
add async overloads and rework poller a bit
annelo-msft Jul 5, 2024
a1422d1
fix some bugs
annelo-msft Jul 6, 2024
d338ec8
back up WIP
annelo-msft Jul 6, 2024
80dbcfa
more backup WIP
annelo-msft Jul 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions OpenAI.sln
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenAI", "src\OpenAI.csproj
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenAI.Examples", "examples\OpenAI.Examples.csproj", "{1F1CD1D4-9932-4B73-99D8-C252A67D4B46}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenAI.Tests", "tests\OpenAI.Tests.csproj", "{6F156401-2544-41D7-B204-3148C51C1D09}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenAI.Tests", "tests\OpenAI.Tests.csproj", "{6F156401-2544-41D7-B204-3148C51C1D09}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.ClientModel", "..\azure-sdk-for-net\sdk\core\System.ClientModel\src\System.ClientModel.csproj", "{8269B350-8875-49F9-A9EA-DEF8718FEBE4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -26,11 +28,15 @@ Global
{6F156401-2544-41D7-B204-3148C51C1D09}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6F156401-2544-41D7-B204-3148C51C1D09}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6F156401-2544-41D7-B204-3148C51C1D09}.Release|Any CPU.Build.0 = Release|Any CPU
{8269B350-8875-49F9-A9EA-DEF8718FEBE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8269B350-8875-49F9-A9EA-DEF8718FEBE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8269B350-8875-49F9-A9EA-DEF8718FEBE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8269B350-8875-49F9-A9EA-DEF8718FEBE4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A97F4B90-2591-4689-B1F8-5F21FE6D6CAE}
EndGlobalSection
EndGlobal
EndGlobal
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,11 @@ When you request a chat completion, the default behavior is for the server to ge
The client library offers a convenient approach to working with streaming chat completions. If you wanted to re-write the example from the previous section using streaming, rather than calling the `ChatClient`'s `CompleteChat` method, you would call its `CompleteChatStreaming` method instead:

```csharp
ResultCollection<StreamingChatCompletionUpdate> updates
CollectionResult<StreamingChatCompletionUpdate> updates
= client.CompleteChatStreaming("Say 'this is a test.'");
```

Notice that the returned value is a `ResultCollection<StreamingChatCompletionUpdate>` instance, which can be enumerated to process the streaming response chunks as they arrive:
Notice that the returned value is a `CollectionResult<StreamingChatCompletionUpdate>` instance, which can be enumerated to process the streaming response chunks as they arrive:

```csharp
Console.WriteLine($"[ASSISTANT]:");
Expand All @@ -132,10 +132,10 @@ foreach (StreamingChatCompletionUpdate update in updates)
}
```

Alternatively, you can do this asynchronously by calling the `CompleteChatStreamingAsync` method to get an `AsyncResultCollection<StreamingChatCompletionUpdate>` and enumerate it using `await foreach`:
Alternatively, you can do this asynchronously by calling the `CompleteChatStreamingAsync` method to get an `AsyncCollectionResult<StreamingChatCompletionUpdate>` and enumerate it using `await foreach`:

```csharp
AsyncResultCollection<StreamingChatCompletionUpdate> updates
AsyncCollectionResult<StreamingChatCompletionUpdate> updates
= client.CompleteChatStreamingAsync("Say 'this is a test.'");

Console.WriteLine($"[ASSISTANT]:");
Expand Down Expand Up @@ -528,7 +528,7 @@ Finally, you can use the `AssistantClient`'s `GetMessages` method to retrieve th
For illustrative purposes, you could print the messages to the console and also save any images produced by the assistant to local storage:

```csharp
PageableCollection<ThreadMessage> messages = assistantClient.GetMessages(threadRun.ThreadId, ListOrder.OldestFirst);
PageCollection<ThreadMessage> messages = assistantClient.GetMessages(threadRun.ThreadId, ListOrder.OldestFirst);

foreach (ThreadMessage message in messages)
{
Expand Down Expand Up @@ -640,10 +640,10 @@ AssistantThread thread = assistantClient.CreateThread(new ThreadCreationOptions(
});
```

With the assistant and thread prepared, use the `CreateRunStreaming` method to get an enumerable `ResultCollection<StreamingUpdate>`. You can then iterate over this collection with `foreach`. For async calling patterns, use `CreateRunStreamingAsync` and iterate over the `AsyncResultCollection<StreamingUpdate>` with `await foreach`, instead. Note that streaming variants also exist for `CreateThreadAndRunStreaming` and `SubmitToolOutputsToRunStreaming`.
With the assistant and thread prepared, use the `CreateRunStreaming` method to get an enumerable `CollectionResult<StreamingUpdate>`. You can then iterate over this collection with `foreach`. For async calling patterns, use `CreateRunStreamingAsync` and iterate over the `AsyncCollectionResult<StreamingUpdate>` with `await foreach`, instead. Note that streaming variants also exist for `CreateThreadAndRunStreaming` and `SubmitToolOutputsToRunStreaming`.

```csharp
ResultCollection<StreamingUpdate> streamingUpdates = assistantClient.CreateRunStreaming(
CollectionResult<StreamingUpdate> streamingUpdates = assistantClient.CreateRunStreaming(
thread,
assistant,
new RunCreationOptions()
Expand Down
17 changes: 6 additions & 11 deletions examples/Assistants/Example01_RetrievalAugmentedGeneration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,18 +88,13 @@ public void Example01_RetrievalAugmentedGeneration()
InitialMessages = { "How well did product 113045 sell in February? Graph its trend over time." }
};

ThreadRun threadRun = assistantClient.CreateThreadAndRun(assistant.Id, threadOptions);

// Check back to see when the run is done
do
{
Thread.Sleep(TimeSpan.FromSeconds(1));
threadRun = assistantClient.GetRun(threadRun.ThreadId, threadRun.Id);
} while (!threadRun.Status.IsTerminal);
// Passing ReturnWhen.Completed means CreateThreadAndRun will return control after the run is complete.
ThreadRunOperation runOperation = assistantClient.CreateThreadAndRun(ReturnWhen.Completed, assistant.Id, threadOptions);

// Finally, we'll print out the full history for the thread that includes the augmented generation
PageableCollection<ThreadMessage> messages
= assistantClient.GetMessages(threadRun.ThreadId, ListOrder.OldestFirst);
PageCollection<ThreadMessage> messagePages
= assistantClient.GetMessages(runOperation.ThreadId, new MessageCollectionOptions() { Order = ListOrder.OldestFirst });
IEnumerable<ThreadMessage> messages = messagePages.GetAllValues();

foreach (ThreadMessage message in messages)
{
Expand Down Expand Up @@ -142,7 +137,7 @@ PageableCollection<ThreadMessage> messages
}

// Optionally, delete any persistent resources you no longer need.
_ = assistantClient.DeleteThread(threadRun.ThreadId);
_ = assistantClient.DeleteThread(runOperation.ThreadId);
_ = assistantClient.DeleteAssistant(assistant);
_ = fileClient.DeleteFile(salesFile);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,12 @@ public async Task Example01_RetrievalAugmentedGenerationAsync()
InitialMessages = { "How well did product 113045 sell in February? Graph its trend over time." }
};

ThreadRun threadRun = await assistantClient.CreateThreadAndRunAsync(assistant.Id, threadOptions);

// Check back to see when the run is done
do
{
Thread.Sleep(TimeSpan.FromSeconds(1));
threadRun = assistantClient.GetRun(threadRun.ThreadId, threadRun.Id);
} while (!threadRun.Status.IsTerminal);
ThreadRunOperation runOperation = await assistantClient.CreateThreadAndRunAsync(ReturnWhen.Completed, assistant.Id, threadOptions);

// Finally, we'll print out the full history for the thread that includes the augmented generation
AsyncPageableCollection<ThreadMessage> messages
= assistantClient.GetMessagesAsync(threadRun.ThreadId, ListOrder.OldestFirst);
AsyncPageCollection<ThreadMessage> messagePages
= assistantClient.GetMessagesAsync(runOperation.ThreadId, new MessageCollectionOptions() { Order = ListOrder.OldestFirst });
IAsyncEnumerable<ThreadMessage> messages = messagePages.GetAllValuesAsync();

await foreach (ThreadMessage message in messages)
{
Expand Down Expand Up @@ -143,7 +137,7 @@ AsyncPageableCollection<ThreadMessage> messages
}

// Optionally, delete any persistent resources you no longer need.
_ = await assistantClient.DeleteThreadAsync(threadRun.ThreadId);
_ = await assistantClient.DeleteThreadAsync(runOperation.ThreadId);
_ = await assistantClient.DeleteAssistantAsync(assistant);
_ = await fileClient.DeleteFileAsync(salesFile);
}
Expand Down
22 changes: 12 additions & 10 deletions examples/Assistants/Example02_FunctionCalling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,22 +85,23 @@ string GetCurrentWeather(string location, string unit = "celsius")
InitialMessages = { "What's the weather like today?" }
};

ThreadRun run = client.CreateThreadAndRun(assistant.Id, threadOptions);
ThreadRunOperation runOperation = client.CreateThreadAndRun(ReturnWhen.Started, assistant.Id, threadOptions);
#endregion

#region
// Poll the run until it is no longer queued or in progress.
while (!run.Status.IsTerminal)
while (!runOperation.HasCompleted)
{
Thread.Sleep(TimeSpan.FromSeconds(1));
run = client.GetRun(run.ThreadId, run.Id);

// If the run requires action, resolve them.
if (run.Status == RunStatus.RequiresAction)
if (runOperation.Status == RunStatus.RequiresAction)
{
List<ToolOutput> toolOutputs = [];

foreach (RequiredAction action in run.RequiredActions)
// TODO: Maybe improve API around this?

foreach (RequiredAction action in runOperation.Value.RequiredActions)
{
switch (action.FunctionName)
{
Expand Down Expand Up @@ -142,17 +143,18 @@ string GetCurrentWeather(string location, string unit = "celsius")
}

// Submit the tool outputs to the assistant, which returns the run to the queued state.
run = client.SubmitToolOutputsToRun(run.ThreadId, run.Id, toolOutputs);
runOperation.SubmitToolOutputsToRun(toolOutputs);
}
}
#endregion

#region
// With the run complete, list the messages and display their content
if (run.Status == RunStatus.Completed)
if (runOperation.Status == RunStatus.Completed)
{
PageableCollection<ThreadMessage> messages
= client.GetMessages(run.ThreadId, resultOrder: ListOrder.OldestFirst);
PageCollection<ThreadMessage> messagePages
= client.GetMessages(runOperation.ThreadId, new MessageCollectionOptions() { Order = ListOrder.OldestFirst });
IEnumerable<ThreadMessage> messages = messagePages.GetAllValues();

foreach (ThreadMessage message in messages)
{
Expand Down Expand Up @@ -185,7 +187,7 @@ PageableCollection<ThreadMessage> messages
}
else
{
throw new NotImplementedException(run.Status.ToString());
throw new NotImplementedException(runOperation.Status.ToString());
}
#endregion
}
Expand Down
22 changes: 11 additions & 11 deletions examples/Assistants/Example02_FunctionCallingAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,22 +85,21 @@ string GetCurrentWeather(string location, string unit = "celsius")
InitialMessages = { "What's the weather like today?" }
};

ThreadRun run = await client.CreateThreadAndRunAsync(assistant.Id, threadOptions);
ThreadRunOperation runOperation = await client.CreateThreadAndRunAsync(ReturnWhen.Started, assistant.Id, threadOptions);
#endregion

#region
// Poll the run until it is no longer queued or in progress.
while (!run.Status.IsTerminal)
while (!runOperation.HasCompleted)
{
await Task.Delay(TimeSpan.FromSeconds(1));
run = await client.GetRunAsync(run.ThreadId, run.Id);


// If the run requires action, resolve them.
if (run.Status == RunStatus.RequiresAction)
if (runOperation.Status == RunStatus.RequiresAction)
{
List<ToolOutput> toolOutputs = [];

foreach (RequiredAction action in run.RequiredActions)
foreach (RequiredAction action in runOperation.Value.RequiredActions)
{
switch (action.FunctionName)
{
Expand Down Expand Up @@ -142,17 +141,18 @@ string GetCurrentWeather(string location, string unit = "celsius")
}

// Submit the tool outputs to the assistant, which returns the run to the queued state.
run = await client.SubmitToolOutputsToRunAsync(run.ThreadId, run.Id, toolOutputs);
await runOperation.SubmitToolOutputsToRunAsync(toolOutputs);
}
}
#endregion

#region
// With the run complete, list the messages and display their content
if (run.Status == RunStatus.Completed)
if (runOperation.Status == RunStatus.Completed)
{
AsyncPageableCollection<ThreadMessage> messages
= client.GetMessagesAsync(run.ThreadId, resultOrder: ListOrder.OldestFirst);
AsyncPageCollection<ThreadMessage> messagePages
= client.GetMessagesAsync(runOperation.ThreadId, new MessageCollectionOptions() { Order = ListOrder.OldestFirst });
IAsyncEnumerable<ThreadMessage> messages = messagePages.GetAllValuesAsync();

await foreach (ThreadMessage message in messages)
{
Expand Down Expand Up @@ -185,7 +185,7 @@ AsyncPageableCollection<ThreadMessage> messages
}
else
{
throw new NotImplementedException(run.Status.ToString());
throw new NotImplementedException(runOperation.Status.ToString());
}
#endregion
}
Expand Down
Loading