Skip to content

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

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 13 commits into from
Closed
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", "{D15E598A-DC52-4647-8204-072DCF819513}"
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
{D15E598A-DC52-4647-8204-072DCF819513}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D15E598A-DC52-4647-8204-072DCF819513}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D15E598A-DC52-4647-8204-072DCF819513}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D15E598A-DC52-4647-8204-072DCF819513}.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