.NET: [BREAKING] Change GetNewThread and DeserializeThread to async (#3152)

* Change GetNewThread and DeserializeThread plus ChatMessageStore and AIContextProvider Factories to async

* Merge fixes
This commit is contained in:
westey
2026-01-12 11:25:51 +00:00
committed by GitHub
parent 6e3bc219e0
commit bb6ecd9c71
113 changed files with 538 additions and 477 deletions
@@ -16,7 +16,7 @@ AgentCard agentCard = await agentCardResolver.GetAgentCardAsync();
// Create an instance of the AIAgent for an existing A2A agent specified by the agent card.
AIAgent agent = agentCard.GetAIAgent();
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
// Start the initial run with a long-running task.
AgentRunResponse response = await agent.RunAsync("Conduct a comprehensive analysis of quantum computing applications in cryptography, including recent breakthroughs, implementation challenges, and future roadmap. Please include diagrams and visual representations to illustrate complex concepts.", thread);
@@ -20,7 +20,7 @@ AIAgent agent = chatClient.CreateAIAgent(
name: "agui-client",
description: "AG-UI Client Agent");
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
List<ChatMessage> messages =
[
new(ChatRole.System, "You are a helpful assistant.")
@@ -20,7 +20,7 @@ AIAgent agent = chatClient.CreateAIAgent(
name: "agui-client",
description: "AG-UI Client Agent");
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
List<ChatMessage> messages =
[
new(ChatRole.System, "You are a helpful assistant.")
@@ -33,7 +33,7 @@ AIAgent agent = chatClient.CreateAIAgent(
description: "AG-UI Client Agent",
tools: frontendTools);
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
List<ChatMessage> messages =
[
new(ChatRole.System, "You are a helpful assistant.")
@@ -30,7 +30,7 @@ JsonSerializerOptions jsonOptions = new(JsonSerializerDefaults.Web)
};
StatefulAgent<AgentState> agent = new(baseAgent, jsonOptions, new AgentState());
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
List<ChatMessage> messages =
[
new(ChatRole.System, "You are a helpful recipe assistant.")
@@ -128,7 +128,7 @@ var agent = new ChatClientAgent(instrumentedChatClient,
.UseOpenTelemetry(SourceName, configure: (cfg) => cfg.EnableSensitiveData = true) // enable telemetry at the agent level
.Build();
var thread = agent.GetNewThread();
var thread = await agent.GetNewThreadAsync();
appLogger.LogInformation("Agent created successfully with ID: {AgentId}", agent.Id);
@@ -31,7 +31,7 @@ AIAgent agent2 = await persistentAgentsClient.CreateAIAgentAsync(
instructions: JokerInstructions);
// You can then invoke the agent like any other AIAgent.
AgentThread thread = agent1.GetNewThread();
AgentThread thread = await agent1.GetNewThreadAsync();
Console.WriteLine(await agent1.RunAsync("Tell me a joke about a pirate.", thread));
// Cleanup for sample purposes.
@@ -40,7 +40,7 @@ var latestAgentVersion = jokerAgentLatest.GetService<AgentVersion>()!;
Console.WriteLine($"Latest agent version id: {latestAgentVersion.Id}");
// Once you have the AIAgent, you can invoke it like any other AIAgent.
AgentThread thread = jokerAgentLatest.GetNewThread();
AgentThread thread = await jokerAgentLatest.GetNewThreadAsync();
Console.WriteLine(await jokerAgentLatest.RunAsync("Tell me a joke about a pirate.", thread));
// This will use the same thread to continue the conversation.
@@ -28,16 +28,16 @@ namespace SampleApp
{
public override string? Name => "UpperCaseParrotAgent";
public override AgentThread GetNewThread()
=> new CustomAgentThread();
public override ValueTask<AgentThread> GetNewThreadAsync(CancellationToken cancellationToken = default)
=> new(new CustomAgentThread());
public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
=> new CustomAgentThread(serializedThread, jsonSerializerOptions);
public override ValueTask<AgentThread> DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
=> new(new CustomAgentThread(serializedThread, jsonSerializerOptions));
protected override async Task<AgentRunResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
// Create a thread if the user didn't supply one.
thread ??= this.GetNewThread();
thread ??= await this.GetNewThreadAsync(cancellationToken);
if (thread is not CustomAgentThread typedThread)
{
@@ -69,7 +69,7 @@ namespace SampleApp
protected override async IAsyncEnumerable<AgentRunResponseUpdate> RunCoreStreamingAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
// Create a thread if the user didn't supply one.
thread ??= this.GetNewThread();
thread ??= await this.GetNewThreadAsync(cancellationToken);
if (thread is not CustomAgentThread typedThread)
{
@@ -33,7 +33,7 @@ AIAgent agent2 = await assistantClient.CreateAIAgentAsync(
instructions: JokerInstructions);
// You can invoke the agent like any other AIAgent.
AgentThread thread = agent1.GetNewThread();
AgentThread thread = await agent1.GetNewThreadAsync();
Console.WriteLine(await agent1.RunAsync("Tell me a joke about a pirate.", thread));
// Cleanup for sample purposes.
@@ -26,11 +26,11 @@ AIAgent agent = new AnthropicClient { APIKey = apiKey }
.CreateAIAgent(model: model, instructions: AssistantInstructions, name: AssistantName, tools: [tool]);
// Non-streaming agent interaction with function tools.
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?", thread));
// Streaming agent interaction with function tools.
thread = agent.GetNewThread();
thread = await agent.GetNewThreadAsync();
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync("What is the weather like in Amsterdam?", thread))
{
Console.WriteLine(update);
@@ -34,7 +34,7 @@ AIAgent agent = new AzureOpenAIClient(
{
ChatOptions = new() { Instructions = "You are good at telling jokes." },
Name = "Joker",
AIContextProviderFactory = (ctx) => new ChatHistoryMemoryProvider(
AIContextProviderFactory = (ctx, ct) => new ValueTask<AIContextProvider>(new ChatHistoryMemoryProvider(
vectorStore,
collectionName: "chathistory",
vectorDimensions: 3072,
@@ -43,18 +43,18 @@ AIAgent agent = new AzureOpenAIClient(
storageScope: new() { UserId = "UID1", ThreadId = new Guid().ToString() },
// Configure the scope which would be used to search for relevant prior messages.
// In this case, we are searching for any messages for the user across all threads.
searchScope: new() { UserId = "UID1" })
searchScope: new() { UserId = "UID1" }))
});
// Start a new thread for the agent conversation.
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
// Run the agent with the thread that stores conversation history in the vector store.
Console.WriteLine(await agent.RunAsync("I like jokes about Pirates. Tell me a joke about a pirate.", thread));
// Start a second thread. Since we configured the search scope to be across all threads for the user,
// the agent should remember that the user likes pirate jokes.
AgentThread thread2 = agent.GetNewThread();
AgentThread thread2 = await agent.GetNewThreadAsync();
// Run the agent with the second thread.
Console.WriteLine(await agent.RunAsync("Tell me a joke that I might like.", thread2));
@@ -31,16 +31,16 @@ AIAgent agent = new AzureOpenAIClient(
.CreateAIAgent(new ChatClientAgentOptions()
{
ChatOptions = new() { Instructions = "You are a friendly travel assistant. Use known memories about the user when responding, and do not invent details." },
AIContextProviderFactory = ctx => ctx.SerializedState.ValueKind is not JsonValueKind.Null and not JsonValueKind.Undefined
AIContextProviderFactory = (ctx, ct) => new ValueTask<AIContextProvider>(ctx.SerializedState.ValueKind is not JsonValueKind.Null and not JsonValueKind.Undefined
// If each thread should have its own Mem0 scope, you can create a new id per thread here:
// ? new Mem0Provider(mem0HttpClient, new Mem0ProviderScope() { ThreadId = Guid.NewGuid().ToString() })
// In this case we are storing memories scoped by application and user instead so that memories are retained across threads.
? new Mem0Provider(mem0HttpClient, new Mem0ProviderScope() { ApplicationId = "getting-started-agents", UserId = "sample-user" })
// For cases where we are restoring from serialized state:
: new Mem0Provider(mem0HttpClient, ctx.SerializedState, ctx.JsonSerializerOptions)
: new Mem0Provider(mem0HttpClient, ctx.SerializedState, ctx.JsonSerializerOptions))
});
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
// Clear any existing memories for this scope to demonstrate fresh behavior.
Mem0Provider mem0Provider = thread.GetService<Mem0Provider>()!;
@@ -56,9 +56,9 @@ Console.WriteLine(await agent.RunAsync("What do you already know about my upcomi
Console.WriteLine("\n>> Serialize and deserialize the thread to demonstrate persisted state\n");
JsonElement serializedThread = thread.Serialize();
AgentThread restoredThread = agent.DeserializeThread(serializedThread);
AgentThread restoredThread = await agent.DeserializeThreadAsync(serializedThread);
Console.WriteLine(await agent.RunAsync("Can you recap the personal details you remember?", restoredThread));
Console.WriteLine("\n>> Start a new thread that shares the same Mem0 scope\n");
AgentThread newThread = agent.GetNewThread();
AgentThread newThread = await agent.GetNewThreadAsync();
Console.WriteLine(await agent.RunAsync("Summarize what you already know about me.", newThread));
@@ -33,11 +33,11 @@ ChatClient chatClient = new AzureOpenAIClient(
AIAgent agent = chatClient.CreateAIAgent(new ChatClientAgentOptions()
{
ChatOptions = new() { Instructions = "You are a friendly assistant. Always address the user by their name." },
AIContextProviderFactory = ctx => new UserInfoMemory(chatClient.AsIChatClient(), ctx.SerializedState, ctx.JsonSerializerOptions)
AIContextProviderFactory = (ctx, ct) => new ValueTask<AIContextProvider>(new UserInfoMemory(chatClient.AsIChatClient(), ctx.SerializedState, ctx.JsonSerializerOptions))
});
// Create a new thread for the conversation.
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
Console.WriteLine(">> Use thread with blank memory\n");
@@ -52,7 +52,7 @@ var threadElement = thread.Serialize();
Console.WriteLine("\n>> Use deserialized thread with previously created memories\n");
// Later we can deserialize the thread and continue the conversation with the previous memory component state.
var deserializedThread = agent.DeserializeThread(threadElement);
var deserializedThread = await agent.DeserializeThreadAsync(threadElement);
Console.WriteLine(await agent.RunAsync("What is my name and age?", deserializedThread));
Console.WriteLine("\n>> Read memories from memory component\n");
@@ -68,7 +68,7 @@ Console.WriteLine("\n>> Use new thread with previously created memories\n");
// It is also possible to set the memories in a memory component on an individual thread.
// This is useful if we want to start a new thread, but have it share the same memories as a previous thread.
var newThread = agent.GetNewThread();
var newThread = await agent.GetNewThreadAsync();
if (userInfo is not null && newThread.GetService<UserInfoMemory>() is UserInfoMemory newThreadMemory)
{
newThreadMemory.UserInfo = userInfo;
@@ -30,7 +30,7 @@ using JsonDocument createConversationResultAsJson = JsonDocument.Parse(createCon
string conversationId = createConversationResultAsJson.RootElement.GetProperty("id"u8)!.GetString()!;
// Create a thread for the conversation - this enables conversation state management for subsequent turns
AgentThread thread = agent.GetNewThread(conversationId);
AgentThread thread = await agent.GetNewThreadAsync(conversationId);
Console.WriteLine("=== Multi-turn Conversation Demo ===\n");
@@ -33,7 +33,7 @@ The `AgentThread` works with `ChatClientAgentRunOptions` to link the agent to a
ChatClientAgentRunOptions agentRunOptions = new() { ChatOptions = new ChatOptions() { ConversationId = conversationId } };
// Create a thread for the conversation
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
// First call links the thread to the conversation
ChatCompletion firstResponse = await agent.RunAsync([firstMessage], thread, agentRunOptions);
@@ -59,7 +59,7 @@ foreach (ClientResult result in getConversationItemsResults.GetRawPages())
1. **Create an OpenAI Client**: Initialize an `OpenAIClient` with your API key
2. **Create a Conversation**: Use `ConversationClient` to create a server-side conversation
3. **Create an Agent**: Initialize an `OpenAIResponseClientAgent` with the desired model and instructions
4. **Create a Thread**: Call `agent.GetNewThread()` to create a new conversation thread
4. **Create a Thread**: Call `agent.GetNewThreadAsync()` to create a new conversation thread
5. **Link Thread to Conversation**: Pass `ChatClientAgentRunOptions` with the `ConversationId` on the first call
6. **Send Messages**: Subsequent calls to `agent.RunAsync()` only need the thread - context is maintained
7. **Cleanup**: Delete the conversation when done using `conversationClient.DeleteConversation()`
@@ -62,15 +62,15 @@ AIAgent agent = azureOpenAIClient
.CreateAIAgent(new ChatClientAgentOptions
{
ChatOptions = new() { Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available." },
AIContextProviderFactory = ctx => new TextSearchProvider(SearchAdapter, ctx.SerializedState, ctx.JsonSerializerOptions, textSearchOptions),
AIContextProviderFactory = (ctx, ct) => new ValueTask<AIContextProvider>(new TextSearchProvider(SearchAdapter, ctx.SerializedState, ctx.JsonSerializerOptions, textSearchOptions)),
// Since we are using ChatCompletion which stores chat history locally, we can also add a message removal policy
// that removes messages produced by the TextSearchProvider before they are added to the chat history, so that
// we don't bloat chat history with all the search result messages.
ChatMessageStoreFactory = ctx => new InMemoryChatMessageStore(ctx.SerializedState, ctx.JsonSerializerOptions)
.WithAIContextProviderMessageRemoval(),
ChatMessageStoreFactory = (ctx, ct) => new ValueTask<ChatMessageStore>(new InMemoryChatMessageStore(ctx.SerializedState, ctx.JsonSerializerOptions)
.WithAIContextProviderMessageRemoval()),
});
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
Console.WriteLine(">> Asking about returns\n");
Console.WriteLine(await agent.RunAsync("Hi! I need help understanding the return policy.", thread));
@@ -71,10 +71,10 @@ AIAgent agent = azureOpenAIClient
.CreateAIAgent(new ChatClientAgentOptions
{
ChatOptions = new() { Instructions = "You are a helpful support specialist for the Microsoft Agent Framework. Answer questions using the provided context and cite the source document when available. Keep responses brief." },
AIContextProviderFactory = ctx => new TextSearchProvider(SearchAdapter, ctx.SerializedState, ctx.JsonSerializerOptions, textSearchOptions)
AIContextProviderFactory = (ctx, ct) => new ValueTask<AIContextProvider>(new TextSearchProvider(SearchAdapter, ctx.SerializedState, ctx.JsonSerializerOptions, textSearchOptions))
});
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
Console.WriteLine(">> Asking about SK threads\n");
Console.WriteLine(await agent.RunAsync("Hi! How do I create a thread in Semantic Kernel?", thread));
@@ -29,10 +29,10 @@ AIAgent agent = new AzureOpenAIClient(
.CreateAIAgent(new ChatClientAgentOptions
{
ChatOptions = new() { Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available." },
AIContextProviderFactory = ctx => new TextSearchProvider(MockSearchAsync, ctx.SerializedState, ctx.JsonSerializerOptions, textSearchOptions)
AIContextProviderFactory = (ctx, ct) => new ValueTask<AIContextProvider>(new TextSearchProvider(MockSearchAsync, ctx.SerializedState, ctx.JsonSerializerOptions, textSearchOptions))
});
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
Console.WriteLine(">> Asking about returns\n");
Console.WriteLine(await agent.RunAsync("Hi! I need help understanding the return policy.", thread));
@@ -43,7 +43,7 @@ AIAgent agent = await aiProjectClient
instructions: "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available.",
tools: [fileSearchTool]);
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
Console.WriteLine(">> Asking about returns\n");
Console.WriteLine(await agent.RunAsync("Hi! I need help understanding the return policy.", thread));
@@ -17,12 +17,12 @@ AIAgent agent = new AzureOpenAIClient(
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
// Invoke the agent with a multi-turn conversation, where the context is preserved in the thread object.
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
Console.WriteLine(await agent.RunAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", thread));
// Invoke the agent with a multi-turn conversation and streaming, where the context is preserved in the thread object.
thread = agent.GetNewThread();
thread = await agent.GetNewThreadAsync();
await foreach (var update in agent.RunStreamingAsync("Tell me a joke about a pirate.", thread))
{
Console.WriteLine(update);
@@ -30,7 +30,7 @@ AIAgent agent = new AzureOpenAIClient(
.CreateAIAgent(instructions: "You are a helpful assistant", tools: [new ApprovalRequiredAIFunction(AIFunctionFactory.Create(GetWeather))]);
// Call the agent and check if there are any user input requests to handle.
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
var response = await agent.RunAsync("What is the weather like in Amsterdam?", thread);
var userInputRequests = response.UserInputRequests.ToList();
@@ -19,7 +19,7 @@ AIAgent agent = new AzureOpenAIClient(
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
// Start a new thread for the agent conversation.
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
// Run the agent with a new thread.
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
@@ -35,7 +35,7 @@ await File.WriteAllTextAsync(tempFilePath, JsonSerializer.Serialize(serializedTh
JsonElement reloadedSerializedThread = JsonElement.Parse(await File.ReadAllTextAsync(tempFilePath));
// Deserialize the thread state after loading from storage.
AgentThread resumedThread = agent.DeserializeThread(reloadedSerializedThread);
AgentThread resumedThread = await agent.DeserializeThreadAsync(reloadedSerializedThread);
// Run the agent again with the resumed thread.
Console.WriteLine(await agent.RunAsync("Now tell the same joke in the voice of a pirate, and add some emojis to the joke.", resumedThread));
@@ -31,17 +31,15 @@ AIAgent agent = new AzureOpenAIClient(
{
ChatOptions = new() { Instructions = "You are good at telling jokes." },
Name = "Joker",
ChatMessageStoreFactory = ctx =>
{
ChatMessageStoreFactory = (ctx, ct) => new ValueTask<ChatMessageStore>(
// Create a new chat message store for this agent that stores the messages in a vector store.
// Each thread must get its own copy of the VectorChatMessageStore, since the store
// also contains the id that the thread is stored under.
return new VectorChatMessageStore(vectorStore, ctx.SerializedState, ctx.JsonSerializerOptions);
}
new VectorChatMessageStore(vectorStore, ctx.SerializedState, ctx.JsonSerializerOptions))
});
// Start a new thread for the agent conversation.
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
// Run the agent with the thread that stores conversation history in the vector store.
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
@@ -58,7 +56,7 @@ Console.WriteLine(JsonSerializer.Serialize(serializedThread, new JsonSerializerO
// and loaded again later.
// Deserialize the thread state after loading from storage.
AgentThread resumedThread = agent.DeserializeThread(serializedThread);
AgentThread resumedThread = await agent.DeserializeThreadAsync(serializedThread);
// Run the agent with the thread that stores conversation history in the vector store a second time.
Console.WriteLine(await agent.RunAsync("Now tell the same joke in the voice of a pirate, and add some emojis to the joke.", resumedThread));
@@ -49,7 +49,7 @@ internal sealed class SampleService(AIAgent agent, IHostApplicationLifetime appL
public async Task StartAsync(CancellationToken cancellationToken)
{
// Create a thread that will be used for the entirety of the service lifetime so that the user can ask follow up questions.
this._thread = agent.GetNewThread();
this._thread = await agent.GetNewThreadAsync(cancellationToken);
_ = this.RunAsync(appLifetime.ApplicationStopping);
}
@@ -22,7 +22,7 @@ ChatMessage message = new(ChatRole.User, [
new UriContent("https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg", "image/jpeg")
]);
var thread = agent.GetNewThread();
var thread = await agent.GetNewThreadAsync();
await foreach (var update in agent.RunStreamingAsync(message, thread))
{
@@ -32,7 +32,7 @@ AIAgent agent = new AzureOpenAIClient(
// Enable background responses (only supported by {Azure}OpenAI Responses at this time).
AgentRunOptions options = new() { AllowBackgroundResponses = true };
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
// Start the initial run.
AgentRunResponse response = await agent.RunAsync("Write a very long novel about a team of astronauts exploring an uncharted galaxy.", thread, options);
@@ -44,10 +44,10 @@ while (response.ContinuationToken is not null)
await Task.Delay(TimeSpan.FromSeconds(10));
RestoreAgentState(agent, out thread, out ResponseContinuationToken? continuationToken);
var (restoredThread, continuationToken) = await RestoreAgentState(agent);
options.ContinuationToken = continuationToken;
response = await agent.RunAsync(thread, options);
response = await agent.RunAsync(restoredThread, options);
}
Console.WriteLine(response.Text);
@@ -58,13 +58,15 @@ void PersistAgentState(AgentThread thread, ResponseContinuationToken? continuati
stateStore["continuationToken"] = JsonSerializer.SerializeToElement(continuationToken, AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ResponseContinuationToken)));
}
void RestoreAgentState(AIAgent agent, out AgentThread thread, out ResponseContinuationToken? continuationToken)
async Task<(AgentThread Thread, ResponseContinuationToken? ContinuationToken)> RestoreAgentState(AIAgent agent)
{
JsonElement serializedThread = stateStore["thread"] ?? throw new InvalidOperationException("No serialized thread found in state store.");
JsonElement? serializedToken = stateStore["continuationToken"];
thread = agent.DeserializeThread(serializedThread);
continuationToken = (ResponseContinuationToken?)serializedToken?.Deserialize(AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ResponseContinuationToken)));
AgentThread thread = await agent.DeserializeThreadAsync(serializedThread);
ResponseContinuationToken? continuationToken = (ResponseContinuationToken?)serializedToken?.Deserialize(AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ResponseContinuationToken)));
return (thread, continuationToken);
}
[Description("Researches relevant space facts and scientific information for writing a science fiction novel")]
@@ -45,7 +45,7 @@ var middlewareEnabledAgent = originalAgent
.Use(GuardrailMiddleware, null)
.Build();
var thread = middlewareEnabledAgent.GetNewThread();
var thread = await middlewareEnabledAgent.GetNewThreadAsync();
Console.WriteLine("\n\n=== Example 1: Wording Guardrail ===");
var guardRailedResponse = await middlewareEnabledAgent.RunAsync("Tell me something harmful.");
@@ -24,10 +24,10 @@ AIAgent agent = new AzureOpenAIClient(
{
ChatOptions = new() { Instructions = "You are good at telling jokes." },
Name = "Joker",
ChatMessageStoreFactory = ctx => new InMemoryChatMessageStore(new MessageCountingChatReducer(2), ctx.SerializedState, ctx.JsonSerializerOptions)
ChatMessageStoreFactory = (ctx, ct) => new ValueTask<ChatMessageStore>(new InMemoryChatMessageStore(new MessageCountingChatReducer(2), ctx.SerializedState, ctx.JsonSerializerOptions))
});
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
// Invoke the agent and output the text result.
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
@@ -19,7 +19,7 @@ AIAgent agent = new AzureOpenAIClient(
// Enable background responses (only supported by OpenAI Responses at this time).
AgentRunOptions options = new() { AllowBackgroundResponses = true };
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
// Start the initial run.
AgentRunResponse response = await agent.RunAsync("Write a very long novel about otters in space.", thread, options);
@@ -41,7 +41,7 @@ Console.WriteLine(response.Text);
// Reset options and thread for streaming.
options = new() { AllowBackgroundResponses = true };
thread = agent.GetNewThread();
thread = await agent.GetNewThreadAsync();
AgentRunResponseUpdate? lastReceivedUpdate = null;
// Start streaming.
@@ -39,7 +39,7 @@ Console.WriteLine();
try
{
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
await foreach (var response in agent.RunStreamingAsync(Task, thread))
{
@@ -26,12 +26,12 @@ AgentVersion agentVersion = aiProjectClient.Agents.CreateAgentVersion(agentName:
AIAgent jokerAgent = aiProjectClient.GetAIAgent(agentVersion);
// Invoke the agent with a multi-turn conversation, where the context is preserved in the thread object.
AgentThread thread = jokerAgent.GetNewThread();
AgentThread thread = await jokerAgent.GetNewThreadAsync();
Console.WriteLine(await jokerAgent.RunAsync("Tell me a joke about a pirate.", thread));
Console.WriteLine(await jokerAgent.RunAsync("Now add some emojis to the joke and tell it in the voice of a pirate's parrot.", thread));
// Invoke the agent with a multi-turn conversation and streaming, where the context is preserved in the thread object.
thread = jokerAgent.GetNewThread();
thread = await jokerAgent.GetNewThreadAsync();
await foreach (AgentRunResponseUpdate update in jokerAgent.RunStreamingAsync("Tell me a joke about a pirate.", thread))
{
Console.WriteLine(update);
@@ -37,11 +37,11 @@ var newAgent = await aiProjectClient.CreateAIAgentAsync(name: AssistantName, mod
var existingAgent = await aiProjectClient.GetAIAgentAsync(name: AssistantName, tools: [tool]);
// Non-streaming agent interaction with function tools.
AgentThread thread = existingAgent.GetNewThread();
AgentThread thread = await existingAgent.GetNewThreadAsync();
Console.WriteLine(await existingAgent.RunAsync("What is the weather like in Amsterdam?", thread));
// Streaming agent interaction with function tools.
thread = existingAgent.GetNewThread();
thread = await existingAgent.GetNewThreadAsync();
await foreach (AgentRunResponseUpdate update in existingAgent.RunStreamingAsync("What is the weather like in Amsterdam?", thread))
{
Console.WriteLine(update);
@@ -32,7 +32,7 @@ AIAgent agent = await aiProjectClient.CreateAIAgentAsync(name: AssistantName, mo
// Call the agent with approval-required function tools.
// The agent will request approval before invoking the function.
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
AgentRunResponse response = await agent.RunAsync("What is the weather like in Amsterdam?", thread);
// Check if there are any user input requests (approvals needed).
@@ -19,7 +19,7 @@ AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential(
AIAgent agent = await aiProjectClient.CreateAIAgentAsync(name: JokerName, model: deploymentName, instructions: JokerInstructions);
// Start a new thread for the agent conversation.
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
// Run the agent with a new thread.
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
@@ -35,7 +35,7 @@ await File.WriteAllTextAsync(tempFilePath, JsonSerializer.Serialize(serializedTh
JsonElement reloadedSerializedThread = JsonElement.Parse(await File.ReadAllTextAsync(tempFilePath))!;
// Deserialize the thread state after loading from storage.
AgentThread resumedThread = agent.DeserializeThread(reloadedSerializedThread);
AgentThread resumedThread = await agent.DeserializeThreadAsync(reloadedSerializedThread);
// Run the agent again with the resumed thread.
Console.WriteLine(await agent.RunAsync("Now tell the same joke in the voice of a pirate, and add some emojis to the joke.", resumedThread));
@@ -38,11 +38,11 @@ AIAgent agent = aiProjectClient.CreateAIAgent(name: JokerName, model: deployment
.Build();
// Invoke the agent and output the text result.
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
// Invoke the agent with streaming support.
thread = agent.GetNewThread();
thread = await agent.GetNewThreadAsync();
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync("Tell me a joke about a pirate.", thread))
{
Console.WriteLine(update);
@@ -42,7 +42,7 @@ internal sealed class SampleService(AIProjectClient client, AIAgent agent, IHost
public async Task StartAsync(CancellationToken cancellationToken)
{
// Create a thread that will be used for the entirety of the service lifetime so that the user can ask follow up questions.
this._thread = agent.GetNewThread();
this._thread = await agent.GetNewThreadAsync(cancellationToken);
_ = this.RunAsync(appLifetime.ApplicationStopping);
}
@@ -24,7 +24,7 @@ ChatMessage message = new(ChatRole.User, [
new DataContent(File.ReadAllBytes("assets/walkway.jpg"), "image/jpeg")
]);
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(message, thread))
{
@@ -39,7 +39,7 @@ AIAgent agent = aiProjectClient.CreateAIAgent(
tools: [weatherAgent.AsAIFunction()]);
// Invoke the agent and output the text result.
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?", thread));
// Cleanup by agent name removes the agent versions created.
@@ -49,7 +49,7 @@ AIAgent middlewareEnabledAgent = originalAgent
.Use(GuardrailMiddleware, null)
.Build();
AgentThread thread = middlewareEnabledAgent.GetNewThread();
AgentThread thread = await middlewareEnabledAgent.GetNewThreadAsync();
Console.WriteLine("\n\n=== Example 1: Wording Guardrail ===");
AgentRunResponse guardRailedResponse = await middlewareEnabledAgent.RunAsync("Tell me something harmful.");
@@ -42,7 +42,7 @@ AIAgent agent = aiProjectClient.CreateAIAgent(
services: serviceProvider);
// Invoke the agent and output the text result.
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
Console.WriteLine(await agent.RunAsync("Tell me current time and weather in Seattle.", thread));
// Cleanup by agent name removes the agent version created.
@@ -83,7 +83,7 @@ internal sealed class Program
AllowBackgroundResponses = true,
};
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
ChatMessage message = new(ChatRole.User, [
new TextContent("I need you to help me search for 'OpenAI news'. Please type 'OpenAI news' and submit the search. Once you see search results, the task is complete."),
@@ -42,7 +42,7 @@ AIAgent agent = await persistentAgentsClient.CreateAIAgentAsync(
});
// You can then invoke the agent like any other AIAgent.
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
Console.WriteLine(await agent.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", thread));
// Cleanup for sample purposes.
@@ -75,7 +75,7 @@ AIAgent agentWithRequiredApproval = await persistentAgentsClient.CreateAIAgentAs
});
// You can then invoke the agent like any other AIAgent.
var threadWithRequiredApproval = agentWithRequiredApproval.GetNewThread();
var threadWithRequiredApproval = await agentWithRequiredApproval.GetNewThreadAsync();
var response = await agentWithRequiredApproval.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", threadWithRequiredApproval);
var userInputRequests = response.UserInputRequests.ToList();
@@ -37,7 +37,7 @@ AIAgent agent = new AzureOpenAIClient(
tools: [mcpTool]);
// You can then invoke the agent like any other AIAgent.
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
Console.WriteLine(await agent.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", thread));
// **** MCP Tool with Approval Required ****
@@ -64,7 +64,7 @@ AIAgent agentWithRequiredApproval = new AzureOpenAIClient(
tools: [mcpToolWithApproval]);
// You can then invoke the agent like any other AIAgent.
var threadWithRequiredApproval = agentWithRequiredApproval.GetNewThread();
var threadWithRequiredApproval = await agentWithRequiredApproval.GetNewThreadAsync();
var response = await agentWithRequiredApproval.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", threadWithRequiredApproval);
var userInputRequests = response.UserInputRequests.ToList();
@@ -109,7 +109,7 @@ internal sealed class SloganGeneratedEvent(SloganResult sloganResult) : Workflow
internal sealed class SloganWriterExecutor : Executor
{
private readonly AIAgent _agent;
private readonly AgentThread _thread;
private AgentThread? _thread;
/// <summary>
/// Initializes a new instance of the <see cref="SloganWriterExecutor"/> class.
@@ -128,7 +128,6 @@ internal sealed class SloganWriterExecutor : Executor
};
this._agent = new ChatClientAgent(chatClient, agentOptions);
this._thread = this._agent.GetNewThread();
}
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) =>
@@ -137,6 +136,8 @@ internal sealed class SloganWriterExecutor : Executor
public async ValueTask<SloganResult> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
this._thread ??= await this._agent.GetNewThreadAsync(cancellationToken);
var result = await this._agent.RunAsync(message, this._thread, cancellationToken: cancellationToken);
var sloganResult = JsonSerializer.Deserialize<SloganResult>(result.Text) ?? throw new InvalidOperationException("Failed to deserialize slogan result.");
@@ -179,7 +180,7 @@ internal sealed class FeedbackEvent(FeedbackResult feedbackResult) : WorkflowEve
internal sealed class FeedbackExecutor : Executor<SloganResult>
{
private readonly AIAgent _agent;
private readonly AgentThread _thread;
private AgentThread? _thread;
public int MinimumRating { get; init; } = 8;
@@ -204,11 +205,12 @@ internal sealed class FeedbackExecutor : Executor<SloganResult>
};
this._agent = new ChatClientAgent(chatClient, agentOptions);
this._thread = this._agent.GetNewThread();
}
public override async ValueTask HandleAsync(SloganResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
this._thread ??= await this._agent.GetNewThreadAsync(cancellationToken);
var sloganMessage = $"""
Here is a slogan for the task '{message.Task}':
Slogan: {message.Slogan}
@@ -37,7 +37,7 @@ public static class Program
// Create the workflow and turn it into an agent
var workflow = WorkflowFactory.BuildWorkflow(chatClient);
var agent = workflow.AsAgent("workflow-agent", "Workflow Agent");
var thread = agent.GetNewThread();
var thread = await agent.GetNewThreadAsync();
// Start an interactive loop to interact with the workflow as if it were an agent
while (true)
@@ -47,7 +47,7 @@ internal sealed class Program
AIAgent agent = aiProjectClient.GetAIAgent(agentVersion);
AgentThread thread = agent.GetNewThread();
AgentThread thread = await agent.GetNewThreadAsync();
ProjectConversation conversation =
await aiProjectClient
@@ -90,7 +90,7 @@ public static class Program
{
EnableSensitiveData = true // enable sensitive data at the agent level such as prompts and responses
};
var thread = agent.GetNewThread();
var thread = await agent.GetNewThreadAsync();
// Start an interactive loop to interact with the workflow as if it were an agent
while (true)