diff --git a/dotnet/samples/A2AClientServer/A2AClient/Program.cs b/dotnet/samples/A2AClientServer/A2AClient/Program.cs index 838cbaaef8..b701ea7441 100644 --- a/dotnet/samples/A2AClientServer/A2AClient/Program.cs +++ b/dotnet/samples/A2AClientServer/A2AClient/Program.cs @@ -42,7 +42,7 @@ private static async Task HandleCommandsAsync(CancellationToken cancellationToke // Create the Host agent var hostAgent = new HostClientAgent(loggerFactory); await hostAgent.InitializeAgentAsync(modelId, apiKey, agentUrls!.Split(";")); - AgentThread thread = hostAgent.Agent!.GetNewThread(); + AgentThread thread = await hostAgent.Agent!.GetNewThreadAsync(cancellationToken); try { while (true) diff --git a/dotnet/samples/AGUIClientServer/AGUIClient/Program.cs b/dotnet/samples/AGUIClientServer/AGUIClient/Program.cs index 3079bf1451..0d461f92af 100644 --- a/dotnet/samples/AGUIClientServer/AGUIClient/Program.cs +++ b/dotnet/samples/AGUIClientServer/AGUIClient/Program.cs @@ -88,7 +88,7 @@ private static async Task HandleCommandsAsync(CancellationToken cancellationToke description: "AG-UI Client Agent", tools: [changeBackground, readClientClimateSensors]); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(cancellationToken); List messages = [new(ChatRole.System, "You are a helpful assistant.")]; try { diff --git a/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/FunctionTriggers.cs b/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/FunctionTriggers.cs index a631e7715c..f2a714f5af 100644 --- a/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/FunctionTriggers.cs +++ b/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/FunctionTriggers.cs @@ -19,7 +19,7 @@ public sealed record TextResponse(string Text); public static async Task RunOrchestrationAsync([OrchestrationTrigger] TaskOrchestrationContext context) { DurableAIAgent writer = context.GetAgent("WriterAgent"); - AgentThread writerThread = writer.GetNewThread(); + AgentThread writerThread = await writer.GetNewThreadAsync(); AgentRunResponse initial = await writer.RunAsync( message: "Write a concise inspirational sentence about learning.", diff --git a/dotnet/samples/AzureFunctions/04_AgentOrchestration_Conditionals/FunctionTriggers.cs b/dotnet/samples/AzureFunctions/04_AgentOrchestration_Conditionals/FunctionTriggers.cs index 14a91185f8..059cb12db1 100644 --- a/dotnet/samples/AzureFunctions/04_AgentOrchestration_Conditionals/FunctionTriggers.cs +++ b/dotnet/samples/AzureFunctions/04_AgentOrchestration_Conditionals/FunctionTriggers.cs @@ -21,7 +21,7 @@ public static async Task RunOrchestrationAsync([OrchestrationTrigger] Ta // Get the spam detection agent DurableAIAgent spamDetectionAgent = context.GetAgent("SpamDetectionAgent"); - AgentThread spamThread = spamDetectionAgent.GetNewThread(); + AgentThread spamThread = await spamDetectionAgent.GetNewThreadAsync(); // Step 1: Check if the email is spam AgentRunResponse spamDetectionResponse = await spamDetectionAgent.RunAsync( @@ -43,7 +43,7 @@ public static async Task RunOrchestrationAsync([OrchestrationTrigger] Ta // Generate and send response for legitimate email DurableAIAgent emailAssistantAgent = context.GetAgent("EmailAssistantAgent"); - AgentThread emailThread = emailAssistantAgent.GetNewThread(); + AgentThread emailThread = await emailAssistantAgent.GetNewThreadAsync(); AgentRunResponse emailAssistantResponse = await emailAssistantAgent.RunAsync( message: diff --git a/dotnet/samples/AzureFunctions/05_AgentOrchestration_HITL/FunctionTriggers.cs b/dotnet/samples/AzureFunctions/05_AgentOrchestration_HITL/FunctionTriggers.cs index 001a52c105..056e827487 100644 --- a/dotnet/samples/AzureFunctions/05_AgentOrchestration_HITL/FunctionTriggers.cs +++ b/dotnet/samples/AzureFunctions/05_AgentOrchestration_HITL/FunctionTriggers.cs @@ -24,7 +24,7 @@ public static async Task RunOrchestrationAsync( // Get the writer agent DurableAIAgent writerAgent = context.GetAgent("WriterAgent"); - AgentThread writerThread = writerAgent.GetNewThread(); + AgentThread writerThread = await writerAgent.GetNewThreadAsync(); // Set initial status context.SetCustomStatus($"Starting content generation for topic: {input.Topic}"); diff --git a/dotnet/samples/AzureFunctions/06_LongRunningTools/FunctionTriggers.cs b/dotnet/samples/AzureFunctions/06_LongRunningTools/FunctionTriggers.cs index b5f81276b8..95987203a1 100644 --- a/dotnet/samples/AzureFunctions/06_LongRunningTools/FunctionTriggers.cs +++ b/dotnet/samples/AzureFunctions/06_LongRunningTools/FunctionTriggers.cs @@ -20,7 +20,7 @@ public static async Task RunOrchestrationAsync( // Get the writer agent DurableAIAgent writerAgent = context.GetAgent("Writer"); - AgentThread writerThread = writerAgent.GetNewThread(); + AgentThread writerThread = await writerAgent.GetNewThreadAsync(); // Set initial status context.SetCustomStatus($"Starting content generation for topic: {input.Topic}"); diff --git a/dotnet/samples/AzureFunctions/08_ReliableStreaming/FunctionTriggers.cs b/dotnet/samples/AzureFunctions/08_ReliableStreaming/FunctionTriggers.cs index a6d3e9db55..94905f8156 100644 --- a/dotnet/samples/AzureFunctions/08_ReliableStreaming/FunctionTriggers.cs +++ b/dotnet/samples/AzureFunctions/08_ReliableStreaming/FunctionTriggers.cs @@ -95,7 +95,7 @@ public async Task CreateAsync( AIAgent agentProxy = durableClient.AsDurableAgentProxy(context, "TravelPlanner"); // Create a new agent thread - AgentThread thread = agentProxy.GetNewThread(); + AgentThread thread = await agentProxy.GetNewThreadAsync(cancellationToken); string agentSessionId = thread.GetService().ToString(); this._logger.LogInformation("Creating new agent session: {AgentSessionId}", agentSessionId); diff --git a/dotnet/samples/GettingStarted/A2A/A2AAgent_PollingForTaskCompletion/Program.cs b/dotnet/samples/GettingStarted/A2A/A2AAgent_PollingForTaskCompletion/Program.cs index 7b5934575c..f54f290e82 100644 --- a/dotnet/samples/GettingStarted/A2A/A2AAgent_PollingForTaskCompletion/Program.cs +++ b/dotnet/samples/GettingStarted/A2A/A2AAgent_PollingForTaskCompletion/Program.cs @@ -16,7 +16,7 @@ // 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); diff --git a/dotnet/samples/GettingStarted/AGUI/Step01_GettingStarted/Client/Program.cs b/dotnet/samples/GettingStarted/AGUI/Step01_GettingStarted/Client/Program.cs index d942314806..691ed40495 100644 --- a/dotnet/samples/GettingStarted/AGUI/Step01_GettingStarted/Client/Program.cs +++ b/dotnet/samples/GettingStarted/AGUI/Step01_GettingStarted/Client/Program.cs @@ -20,7 +20,7 @@ name: "agui-client", description: "AG-UI Client Agent"); -AgentThread thread = agent.GetNewThread(); +AgentThread thread = await agent.GetNewThreadAsync(); List messages = [ new(ChatRole.System, "You are a helpful assistant.") diff --git a/dotnet/samples/GettingStarted/AGUI/Step02_BackendTools/Client/Program.cs b/dotnet/samples/GettingStarted/AGUI/Step02_BackendTools/Client/Program.cs index 1919a9565f..4306171979 100644 --- a/dotnet/samples/GettingStarted/AGUI/Step02_BackendTools/Client/Program.cs +++ b/dotnet/samples/GettingStarted/AGUI/Step02_BackendTools/Client/Program.cs @@ -20,7 +20,7 @@ name: "agui-client", description: "AG-UI Client Agent"); -AgentThread thread = agent.GetNewThread(); +AgentThread thread = await agent.GetNewThreadAsync(); List messages = [ new(ChatRole.System, "You are a helpful assistant.") diff --git a/dotnet/samples/GettingStarted/AGUI/Step03_FrontendTools/Client/Program.cs b/dotnet/samples/GettingStarted/AGUI/Step03_FrontendTools/Client/Program.cs index d295ed7116..ec0fa2c74e 100644 --- a/dotnet/samples/GettingStarted/AGUI/Step03_FrontendTools/Client/Program.cs +++ b/dotnet/samples/GettingStarted/AGUI/Step03_FrontendTools/Client/Program.cs @@ -33,7 +33,7 @@ static string GetUserLocation() description: "AG-UI Client Agent", tools: frontendTools); -AgentThread thread = agent.GetNewThread(); +AgentThread thread = await agent.GetNewThreadAsync(); List messages = [ new(ChatRole.System, "You are a helpful assistant.") diff --git a/dotnet/samples/GettingStarted/AGUI/Step05_StateManagement/Client/Program.cs b/dotnet/samples/GettingStarted/AGUI/Step05_StateManagement/Client/Program.cs index 49ffa0587d..87286fbe4c 100644 --- a/dotnet/samples/GettingStarted/AGUI/Step05_StateManagement/Client/Program.cs +++ b/dotnet/samples/GettingStarted/AGUI/Step05_StateManagement/Client/Program.cs @@ -30,7 +30,7 @@ }; StatefulAgent agent = new(baseAgent, jsonOptions, new AgentState()); -AgentThread thread = agent.GetNewThread(); +AgentThread thread = await agent.GetNewThreadAsync(); List messages = [ new(ChatRole.System, "You are a helpful recipe assistant.") diff --git a/dotnet/samples/GettingStarted/AgentOpenTelemetry/Program.cs b/dotnet/samples/GettingStarted/AgentOpenTelemetry/Program.cs index dd5c6f9c7d..abef6ee30f 100644 --- a/dotnet/samples/GettingStarted/AgentOpenTelemetry/Program.cs +++ b/dotnet/samples/GettingStarted/AgentOpenTelemetry/Program.cs @@ -128,7 +128,7 @@ static async Task GetWeatherAsync([Description("The location to get the .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); diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/Program.cs index 31f18ee7ae..e3d37a39d7 100644 --- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/Program.cs +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/Program.cs @@ -31,7 +31,7 @@ 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. diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Program.cs index 2c2b9d1969..b7b175b2cf 100644 --- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Program.cs +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Program.cs @@ -40,7 +40,7 @@ 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. diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs index 6beef64405..51913d3d7d 100644 --- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs @@ -28,16 +28,16 @@ internal sealed class UpperCaseParrotAgent : AIAgent { public override string? Name => "UpperCaseParrotAgent"; - public override AgentThread GetNewThread() - => new CustomAgentThread(); + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) + => new(new CustomAgentThread()); - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) - => new CustomAgentThread(serializedThread, jsonSerializerOptions); + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) + => new(new CustomAgentThread(serializedThread, jsonSerializerOptions)); protected override async Task RunCoreAsync(IEnumerable 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 @@ protected override async Task RunCoreAsync(IEnumerable RunCoreStreamingAsync(IEnumerable 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) { diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIAssistants/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIAssistants/Program.cs index 3079bd103c..eb194badfe 100644 --- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIAssistants/Program.cs +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIAssistants/Program.cs @@ -33,7 +33,7 @@ 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. diff --git a/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/Program.cs b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/Program.cs index a56db8d4a2..ad117fca3d 100644 --- a/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/Program.cs @@ -26,11 +26,11 @@ static string GetWeather([Description("The location to get the weather for.")] s .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); diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/Program.cs b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/Program.cs index a11edafabc..3acd6fbc2b 100644 --- a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/Program.cs @@ -34,7 +34,7 @@ { ChatOptions = new() { Instructions = "You are good at telling jokes." }, Name = "Joker", - AIContextProviderFactory = (ctx) => new ChatHistoryMemoryProvider( + AIContextProviderFactory = (ctx, ct) => new ValueTask(new ChatHistoryMemoryProvider( vectorStore, collectionName: "chathistory", vectorDimensions: 3072, @@ -43,18 +43,18 @@ 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)); diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs index 739c5e3f13..18390718f9 100644 --- a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs @@ -31,16 +31,16 @@ .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(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()!; @@ -56,9 +56,9 @@ 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)); diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs index 5727e8ca3c..1069d0cd44 100644 --- a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs @@ -33,11 +33,11 @@ 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(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 @@ 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 @@ // 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() is UserInfoMemory newThreadMemory) { newThreadMemory.UserInfo = userInfo; diff --git a/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step05_Conversation/Program.cs b/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step05_Conversation/Program.cs index 8aebebdfa0..07a67edae4 100644 --- a/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step05_Conversation/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step05_Conversation/Program.cs @@ -30,7 +30,7 @@ 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"); diff --git a/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step05_Conversation/README.md b/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step05_Conversation/README.md index c279ba2c17..5b999955b2 100644 --- a/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step05_Conversation/README.md +++ b/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step05_Conversation/README.md @@ -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()` diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/Program.cs b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/Program.cs index 9207a08182..3b446b5bec 100644 --- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/Program.cs @@ -62,15 +62,15 @@ .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(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(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)); diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/Program.cs b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/Program.cs index f20e42f01d..83ed7857f6 100644 --- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/Program.cs @@ -71,10 +71,10 @@ .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(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)); diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/Program.cs b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/Program.cs index e9a62e382f..960278b512 100644 --- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/Program.cs @@ -29,10 +29,10 @@ .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(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)); diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/Program.cs b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/Program.cs index 0989394185..e93fd474f6 100644 --- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/Program.cs @@ -43,7 +43,7 @@ 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)); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step02_MultiturnConversation/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step02_MultiturnConversation/Program.cs index e27a5bb36d..b08ef1cb3a 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step02_MultiturnConversation/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step02_MultiturnConversation/Program.cs @@ -17,12 +17,12 @@ .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); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step04_UsingFunctionToolsWithApprovals/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step04_UsingFunctionToolsWithApprovals/Program.cs index be2a4801ae..61ad65a164 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step04_UsingFunctionToolsWithApprovals/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step04_UsingFunctionToolsWithApprovals/Program.cs @@ -30,7 +30,7 @@ static string GetWeather([Description("The location to get the weather for.")] s .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(); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs index 5d3247b69c..462d26e3d7 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs @@ -19,7 +19,7 @@ .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 @@ 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)); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs index 280c84dc0d..32d38b9e29 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs @@ -31,17 +31,15 @@ { ChatOptions = new() { Instructions = "You are good at telling jokes." }, Name = "Joker", - ChatMessageStoreFactory = ctx => - { + ChatMessageStoreFactory = (ctx, ct) => new ValueTask( // 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 @@ // 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)); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Program.cs index d1b75d2fe5..ab0ac64e99 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Program.cs @@ -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); } diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step11_UsingImages/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step11_UsingImages/Program.cs index f534e4edd7..901d6ddbfc 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step11_UsingImages/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step11_UsingImages/Program.cs @@ -22,7 +22,7 @@ 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)) { diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs index 29dc347b4a..124ffa2a63 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs @@ -32,7 +32,7 @@ // 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 @@ 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")] diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Program.cs index a0ca338297..00217024ec 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Program.cs @@ -45,7 +45,7 @@ static string GetDateTime() .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."); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs index decf0de25a..61e7b2b48a 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs @@ -24,10 +24,10 @@ { 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(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)); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/Program.cs index 3e172a95b5..2ac72ef06e 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/Program.cs @@ -19,7 +19,7 @@ // 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 @@ // Reset options and thread for streaming. options = new() { AllowBackgroundResponses = true }; -thread = agent.GetNewThread(); +thread = await agent.GetNewThreadAsync(); AgentRunResponseUpdate? lastReceivedUpdate = null; // Start streaming. diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step18_DeepResearch/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step18_DeepResearch/Program.cs index f6aa825a54..e36612d89b 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step18_DeepResearch/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step18_DeepResearch/Program.cs @@ -39,7 +39,7 @@ try { - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); await foreach (var response in agent.RunStreamingAsync(Task, thread)) { diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step02_MultiturnConversation/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step02_MultiturnConversation/Program.cs index 3cbb0099ea..4b9e2caa73 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step02_MultiturnConversation/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step02_MultiturnConversation/Program.cs @@ -26,12 +26,12 @@ 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); diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step03_UsingFunctionTools/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step03_UsingFunctionTools/Program.cs index 38c5a15d75..fbf7d9b5d9 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step03_UsingFunctionTools/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step03_UsingFunctionTools/Program.cs @@ -37,11 +37,11 @@ static string GetWeather([Description("The location to get the weather for.")] s 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); diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step04_UsingFunctionToolsWithApprovals/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step04_UsingFunctionToolsWithApprovals/Program.cs index 1b51d210cf..9de52a54f1 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step04_UsingFunctionToolsWithApprovals/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step04_UsingFunctionToolsWithApprovals/Program.cs @@ -32,7 +32,7 @@ static string GetWeather([Description("The location to get the weather for.")] s // 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). diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs index d404a814c0..7c1c65c01d 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs @@ -19,7 +19,7 @@ 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 @@ 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)); diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step07_Observability/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step07_Observability/Program.cs index eb011ba064..002d547a14 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step07_Observability/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step07_Observability/Program.cs @@ -38,11 +38,11 @@ .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); diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step08_DependencyInjection/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step08_DependencyInjection/Program.cs index 4bf4843d66..de972b213a 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step08_DependencyInjection/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step08_DependencyInjection/Program.cs @@ -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); } diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step10_UsingImages/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step10_UsingImages/Program.cs index a799fe46fb..6f915c69ba 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step10_UsingImages/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step10_UsingImages/Program.cs @@ -24,7 +24,7 @@ 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)) { diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step11_AsFunctionTool/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step11_AsFunctionTool/Program.cs index 9fb589f5ce..e00b05d9cb 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step11_AsFunctionTool/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step11_AsFunctionTool/Program.cs @@ -39,7 +39,7 @@ static string GetWeather([Description("The location to get the weather for.")] s 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. diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/Program.cs index 0a00e9107c..cd1e9e9ef4 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/Program.cs @@ -49,7 +49,7 @@ static string GetDateTime() .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."); diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step13_Plugins/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step13_Plugins/Program.cs index b55f38b66b..0cd9674770 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step13_Plugins/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step13_Plugins/Program.cs @@ -42,7 +42,7 @@ 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. diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step15_ComputerUse/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step15_ComputerUse/Program.cs index ff4f57924a..9f4002deae 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step15_ComputerUse/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step15_ComputerUse/Program.cs @@ -83,7 +83,7 @@ private static async Task InvokeComputerUseAgentAsync(AIAgent agent) 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."), diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs b/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs index 123d666f09..9a42c1c467 100644 --- a/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs +++ b/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs @@ -42,7 +42,7 @@ }); // 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 @@ }); // 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(); diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/ResponseAgent_Hosted_MCP/Program.cs b/dotnet/samples/GettingStarted/ModelContextProtocol/ResponseAgent_Hosted_MCP/Program.cs index 13ee28d6a1..3b90adb1c6 100644 --- a/dotnet/samples/GettingStarted/ModelContextProtocol/ResponseAgent_Hosted_MCP/Program.cs +++ b/dotnet/samples/GettingStarted/ModelContextProtocol/ResponseAgent_Hosted_MCP/Program.cs @@ -37,7 +37,7 @@ 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 @@ 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(); diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs b/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs index 91f58f460e..594f447e8c 100644 --- a/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs @@ -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; /// /// Initializes a new instance of the class. @@ -128,7 +128,6 @@ public SloganWriterExecutor(string id, IChatClient chatClient) : base(id) }; this._agent = new ChatClientAgent(chatClient, agentOptions); - this._thread = this._agent.GetNewThread(); } protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) => @@ -137,6 +136,8 @@ protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) => public async ValueTask 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(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 { private readonly AIAgent _agent; - private readonly AgentThread _thread; + private AgentThread? _thread; public int MinimumRating { get; init; } = 8; @@ -204,11 +205,12 @@ public FeedbackExecutor(string id, IChatClient chatClient) : base(id) }; 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} diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/WorkflowAsAnAgent/Program.cs b/dotnet/samples/GettingStarted/Workflows/Agents/WorkflowAsAnAgent/Program.cs index 6aa65d56b5..18b19c5885 100644 --- a/dotnet/samples/GettingStarted/Workflows/Agents/WorkflowAsAnAgent/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Agents/WorkflowAsAnAgent/Program.cs @@ -37,7 +37,7 @@ private static async Task Main() // 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) diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/HostedWorkflow/Program.cs b/dotnet/samples/GettingStarted/Workflows/Declarative/HostedWorkflow/Program.cs index ff45cbc0c2..c3cd0219d3 100644 --- a/dotnet/samples/GettingStarted/Workflows/Declarative/HostedWorkflow/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Declarative/HostedWorkflow/Program.cs @@ -47,7 +47,7 @@ public static async Task Main(string[] args) AIAgent agent = aiProjectClient.GetAIAgent(agentVersion); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ProjectConversation conversation = await aiProjectClient diff --git a/dotnet/samples/GettingStarted/Workflows/Observability/WorkflowAsAnAgent/Program.cs b/dotnet/samples/GettingStarted/Workflows/Observability/WorkflowAsAnAgent/Program.cs index 17d7d03b3f..e2790d5196 100644 --- a/dotnet/samples/GettingStarted/Workflows/Observability/WorkflowAsAnAgent/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Observability/WorkflowAsAnAgent/Program.cs @@ -90,7 +90,7 @@ private static async Task Main() { 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) diff --git a/dotnet/samples/M365Agent/AFAgentApplication.cs b/dotnet/samples/M365Agent/AFAgentApplication.cs index 04aabf96a8..8afff15167 100644 --- a/dotnet/samples/M365Agent/AFAgentApplication.cs +++ b/dotnet/samples/M365Agent/AFAgentApplication.cs @@ -43,8 +43,8 @@ private async Task MessageActivityAsync(ITurnContext turnContext, ITurnState tur // Deserialize the conversation history into an AgentThread, or create a new one if none exists. AgentThread agentThread = threadElementStart.ValueKind is not JsonValueKind.Undefined and not JsonValueKind.Null - ? this._agent.DeserializeThread(threadElementStart, JsonUtilities.DefaultOptions) - : this._agent.GetNewThread(); + ? await this._agent.DeserializeThreadAsync(threadElementStart, JsonUtilities.DefaultOptions, cancellationToken) + : await this._agent.GetNewThreadAsync(cancellationToken); ChatMessage chatMessage = HandleUserInput(turnContext); diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs b/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs index 96a8856dea..a31c3110dc 100644 --- a/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs @@ -52,27 +52,27 @@ public A2AAgent(A2AClient a2aClient, string? id = null, string? name = null, str } /// - public sealed override AgentThread GetNewThread() - => new A2AAgentThread(); + public sealed override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) + => new(new A2AAgentThread()); /// /// Get a new instance using an existing context id, to continue that conversation. /// /// The context id to continue. - /// A new instance. - public AgentThread GetNewThread(string contextId) - => new A2AAgentThread() { ContextId = contextId }; + /// A value task representing the asynchronous operation. The task result contains a new instance. + public ValueTask GetNewThreadAsync(string contextId) + => new(new A2AAgentThread() { ContextId = contextId }); /// - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) - => new A2AAgentThread(serializedThread, jsonSerializerOptions); + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) + => new(new A2AAgentThread(serializedThread, jsonSerializerOptions)); /// protected override async Task RunCoreAsync(IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { _ = Throw.IfNull(messages); - A2AAgentThread typedThread = this.GetA2AThread(thread, options); + A2AAgentThread typedThread = await this.GetA2AThreadAsync(thread, options, cancellationToken).ConfigureAwait(false); this._logger.LogA2AAgentInvokingAgent(nameof(RunAsync), this.Id, this.Name); @@ -139,7 +139,7 @@ protected override async IAsyncEnumerable RunCoreStreami { _ = Throw.IfNull(messages); - A2AAgentThread typedThread = this.GetA2AThread(thread, options); + A2AAgentThread typedThread = await this.GetA2AThreadAsync(thread, options, cancellationToken).ConfigureAwait(false); this._logger.LogA2AAgentInvokingAgent(nameof(RunStreamingAsync), this.Id, this.Name); @@ -211,7 +211,7 @@ protected override async IAsyncEnumerable RunCoreStreami /// public override string? Description => this._description; - private A2AAgentThread GetA2AThread(AgentThread? thread, AgentRunOptions? options) + private async ValueTask GetA2AThreadAsync(AgentThread? thread, AgentRunOptions? options, CancellationToken cancellationToken) { // Aligning with other agent implementations that support background responses, where // a thread is required for background responses to prevent inconsistent experience @@ -221,7 +221,7 @@ private A2AAgentThread GetA2AThread(AgentThread? thread, AgentRunOptions? option throw new InvalidOperationException("A thread must be provided when AllowBackgroundResponses is enabled."); } - thread ??= this.GetNewThread(); + thread ??= await this.GetNewThreadAsync(cancellationToken).ConfigureAwait(false); if (thread is not A2AAgentThread typedThread) { diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs index afed5d1518..71135ad6e6 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs @@ -105,7 +105,8 @@ public abstract class AIAgent /// /// Creates a new conversation thread that is compatible with this agent. /// - /// A new instance ready for use with this agent. + /// The to monitor for cancellation requests. The default is . + /// A value task that represents the asynchronous operation. The task result contains a new instance ready for use with this agent. /// /// /// This method creates a fresh conversation thread that can be used to maintain state @@ -118,14 +119,15 @@ public abstract class AIAgent /// may be deferred until first use to optimize performance. /// /// - public abstract AgentThread GetNewThread(); + public abstract ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default); /// /// Deserializes an agent thread from its JSON serialized representation. /// /// A containing the serialized thread state. /// Optional settings to customize the deserialization process. - /// A restored instance with the state from . + /// The to monitor for cancellation requests. The default is . + /// A value task that represents the asynchronous operation. The task result contains a restored instance with the state from . /// The is not in the expected format. /// The serialized data is invalid or cannot be deserialized. /// @@ -133,7 +135,7 @@ public abstract class AIAgent /// allowing conversations to resume across application restarts or be migrated between /// different agent instances. /// - public abstract AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null); + public abstract ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default); /// /// Run the agent with no message assuming that all required instructions are already provided to the agent or on the thread. diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentThread.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentThread.cs index 0a3301d05f..318307ec43 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentThread.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentThread.cs @@ -26,8 +26,8 @@ namespace Microsoft.Agents.AI; /// Chat history reduction, e.g. where messages needs to be summarized or truncated to reduce the size. /// /// An is always constructed by an so that the -/// can attach any necessary behaviors to the . See the -/// and methods for more information. +/// can attach any necessary behaviors to the . See the +/// and methods for more information. /// /// /// Because of these behaviors, an may not be reusable across different agents, since each agent @@ -37,13 +37,13 @@ namespace Microsoft.Agents.AI; /// To support conversations that may need to survive application restarts or separate service requests, an can be serialized /// and deserialized, so that it can be saved in a persistent store. /// The provides the method to serialize the thread to a -/// and the method +/// and the method /// can be used to deserialize the thread. /// /// /// -/// -/// +/// +/// public abstract class AgentThread { /// diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/DelegatingAIAgent.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/DelegatingAIAgent.cs index e7bf58f39f..8f6a36a670 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/DelegatingAIAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/DelegatingAIAgent.cs @@ -74,11 +74,11 @@ protected DelegatingAIAgent(AIAgent innerAgent) } /// - public override AgentThread GetNewThread() => this.InnerAgent.GetNewThread(); + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) => this.InnerAgent.GetNewThreadAsync(cancellationToken); /// - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) - => this.InnerAgent.DeserializeThread(serializedThread, jsonSerializerOptions); + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) + => this.InnerAgent.DeserializeThreadAsync(serializedThread, jsonSerializerOptions, cancellationToken); /// protected override Task RunCoreAsync( diff --git a/dotnet/src/Microsoft.Agents.AI.CopilotStudio/CopilotStudioAgent.cs b/dotnet/src/Microsoft.Agents.AI.CopilotStudio/CopilotStudioAgent.cs index 203bab21ed..391af7849e 100644 --- a/dotnet/src/Microsoft.Agents.AI.CopilotStudio/CopilotStudioAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.CopilotStudio/CopilotStudioAgent.cs @@ -42,20 +42,20 @@ public CopilotStudioAgent(CopilotClient client, ILoggerFactory? loggerFactory = } /// - public sealed override AgentThread GetNewThread() - => new CopilotStudioAgentThread(); + public sealed override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) + => new(new CopilotStudioAgentThread()); /// /// Get a new instance using an existing conversation id, to continue that conversation. /// /// The conversation id to continue. /// A new instance. - public AgentThread GetNewThread(string conversationId) - => new CopilotStudioAgentThread() { ConversationId = conversationId }; + public ValueTask GetNewThreadAsync(string conversationId) + => new(new CopilotStudioAgentThread() { ConversationId = conversationId }); /// - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) - => new CopilotStudioAgentThread(serializedThread, jsonSerializerOptions); + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) + => new(new CopilotStudioAgentThread(serializedThread, jsonSerializerOptions)); /// protected override async Task RunCoreAsync( @@ -68,7 +68,7 @@ protected override async Task RunCoreAsync( // Ensure that we have a valid thread to work with. // If the thread ID is null, we need to start a new conversation and set the thread ID accordingly. - thread ??= this.GetNewThread(); + thread ??= await this.GetNewThreadAsync(cancellationToken).ConfigureAwait(false); if (thread is not CopilotStudioAgentThread typedThread) { throw new InvalidOperationException("The provided thread is not compatible with the agent. Only threads created by the agent can be used."); @@ -106,7 +106,8 @@ protected override async IAsyncEnumerable RunCoreStreami // Ensure that we have a valid thread to work with. // If the thread ID is null, we need to start a new conversation and set the thread ID accordingly. - thread ??= this.GetNewThread(); + + thread ??= await this.GetNewThreadAsync(cancellationToken).ConfigureAwait(false); if (thread is not CopilotStudioAgentThread typedThread) { throw new InvalidOperationException("The provided thread is not compatible with the agent. Only threads created by the agent can be used."); diff --git a/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosDBChatExtensions.cs b/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosDBChatExtensions.cs index 4e3b66fd54..45c0d09536 100644 --- a/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosDBChatExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosDBChatExtensions.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; using Azure.Identity; using Microsoft.Azure.Cosmos; @@ -35,7 +36,7 @@ public static ChatClientAgentOptions WithCosmosDBMessageStore( throw new ArgumentNullException(nameof(options)); } - options.ChatMessageStoreFactory = context => new CosmosChatMessageStore(connectionString, databaseId, containerId); + options.ChatMessageStoreFactory = (context, ct) => new ValueTask(new CosmosChatMessageStore(connectionString, databaseId, containerId)); return options; } @@ -62,7 +63,7 @@ public static ChatClientAgentOptions WithCosmosDBMessageStoreUsingManagedIdentit throw new ArgumentNullException(nameof(options)); } - options.ChatMessageStoreFactory = context => new CosmosChatMessageStore(accountEndpoint, new DefaultAzureCredential(), databaseId, containerId); + options.ChatMessageStoreFactory = (context, ct) => new ValueTask(new CosmosChatMessageStore(accountEndpoint, new DefaultAzureCredential(), databaseId, containerId)); return options; } @@ -89,7 +90,7 @@ public static ChatClientAgentOptions WithCosmosDBMessageStore( throw new ArgumentNullException(nameof(options)); } - options.ChatMessageStoreFactory = context => new CosmosChatMessageStore(cosmosClient, databaseId, containerId); + options.ChatMessageStoreFactory = (context, ct) => new ValueTask(new CosmosChatMessageStore(cosmosClient, databaseId, containerId)); return options; } } diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/AgentEntity.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/AgentEntity.cs index ec4ba3acf6..3c23e46fd3 100644 --- a/dotnet/src/Microsoft.Agents.AI.DurableTask/AgentEntity.cs +++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/AgentEntity.cs @@ -67,7 +67,7 @@ public async Task Run(RunRequest request) // Start the agent response stream IAsyncEnumerable responseStream = agentWrapper.RunStreamingAsync( this.State.Data.ConversationHistory.SelectMany(e => e.Messages).Select(m => m.ToChatMessage()), - agentWrapper.GetNewThread(), + await agentWrapper.GetNewThreadAsync(cancellationToken).ConfigureAwait(false), options: null, this._cancellationToken); diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgent.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgent.cs index d841a80ddd..5cc8ce451d 100644 --- a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgent.cs @@ -32,11 +32,12 @@ internal DurableAIAgent(TaskOrchestrationContext context, string agentName) /// /// Creates a new agent thread for this agent using a random session ID. /// - /// A new agent thread. - public override AgentThread GetNewThread() + /// The cancellation token. + /// A value task that represents the asynchronous operation. The task result contains a new agent thread. + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) { AgentSessionId sessionId = this._context.NewAgentSessionId(this._agentName); - return new DurableAgentThread(sessionId); + return ValueTask.FromResult(new DurableAgentThread(sessionId)); } /// @@ -44,12 +45,13 @@ public override AgentThread GetNewThread() /// /// The serialized thread data. /// Optional JSON serializer options. - /// The deserialized agent thread. - public override AgentThread DeserializeThread( + /// The cancellation token. + /// A value task that represents the asynchronous operation. The task result contains the deserialized agent thread. + public override ValueTask DeserializeThreadAsync( JsonElement serializedThread, - JsonSerializerOptions? jsonSerializerOptions = null) + JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { - return DurableAgentThread.Deserialize(serializedThread, jsonSerializerOptions); + return ValueTask.FromResult(DurableAgentThread.Deserialize(serializedThread, jsonSerializerOptions)); } /// @@ -74,12 +76,12 @@ protected override async Task RunCoreAsync( throw new NotSupportedException("Cancellation is not supported for durable agents."); } - thread ??= this.GetNewThread(); + thread ??= await this.GetNewThreadAsync(cancellationToken).ConfigureAwait(false); if (thread is not DurableAgentThread durableThread) { throw new ArgumentException( "The provided thread is not valid for a durable agent. " + - "Create a new thread using GetNewThread or provide a thread previously created by this agent.", + "Create a new thread using GetNewThreadAsync or provide a thread previously created by this agent.", paramName: nameof(thread)); } diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgentProxy.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgentProxy.cs index ecff2d5c90..ca631a32c8 100644 --- a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgentProxy.cs +++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgentProxy.cs @@ -11,16 +11,16 @@ internal class DurableAIAgentProxy(string name, IDurableAgentClient agentClient) public override string? Name { get; } = name; - public override AgentThread DeserializeThread( + public override ValueTask DeserializeThreadAsync( JsonElement serializedThread, - JsonSerializerOptions? jsonSerializerOptions = null) + JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { - return DurableAgentThread.Deserialize(serializedThread, jsonSerializerOptions); + return ValueTask.FromResult(DurableAgentThread.Deserialize(serializedThread, jsonSerializerOptions)); } - public override AgentThread GetNewThread() + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) { - return new DurableAgentThread(AgentSessionId.WithRandomKey(this.Name!)); + return ValueTask.FromResult(new DurableAgentThread(AgentSessionId.WithRandomKey(this.Name!))); } protected override async Task RunCoreAsync( @@ -29,7 +29,7 @@ protected override async Task RunCoreAsync( AgentRunOptions? options = null, CancellationToken cancellationToken = default) { - thread ??= this.GetNewThread(); + thread ??= await this.GetNewThreadAsync(cancellationToken).ConfigureAwait(false); if (thread is not DurableAgentThread durableThread) { throw new ArgumentException( diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/README.md b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/README.md index 4e819e5985..ff0913ff2c 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/README.md +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/README.md @@ -74,7 +74,7 @@ public static async Task SpamDetectionOrchestration( // Get the spam detection agent DurableAIAgent spamDetectionAgent = context.GetAgent("SpamDetectionAgent"); - AgentThread spamThread = spamDetectionAgent.GetNewThread(); + AgentThread spamThread = await spamDetectionAgent.GetNewThreadAsync(); // Step 1: Check if the email is spam AgentRunResponse spamDetectionResponse = await spamDetectionAgent.RunAsync( @@ -97,7 +97,7 @@ public static async Task SpamDetectionOrchestration( { // Generate and send response for legitimate email DurableAIAgent emailAssistantAgent = context.GetAgent("EmailAssistantAgent"); - AgentThread emailThread = emailAssistantAgent.GetNewThread(); + AgentThread emailThread = await emailAssistantAgent.GetNewThreadAsync(); AgentRunResponse emailAssistantResponse = await emailAssistantAgent.RunAsync( message: diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting/Local/InMemoryAgentThreadStore.cs b/dotnet/src/Microsoft.Agents.AI.Hosting/Local/InMemoryAgentThreadStore.cs index 74bbe279fb..febbf4c06a 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting/Local/InMemoryAgentThreadStore.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting/Local/InMemoryAgentThreadStore.cs @@ -38,15 +38,15 @@ public override ValueTask SaveThreadAsync(AIAgent agent, string conversationId, } /// - public override ValueTask GetThreadAsync(AIAgent agent, string conversationId, CancellationToken cancellationToken = default) + public override async ValueTask GetThreadAsync(AIAgent agent, string conversationId, CancellationToken cancellationToken = default) { var key = GetKey(conversationId, agent.Id); JsonElement? threadContent = this._threads.TryGetValue(key, out var existingThread) ? existingThread : null; return threadContent switch { - null => new ValueTask(agent.GetNewThread()), - _ => new ValueTask(agent.DeserializeThread(threadContent.Value)), + null => await agent.GetNewThreadAsync(cancellationToken).ConfigureAwait(false), + _ => await agent.DeserializeThreadAsync(threadContent.Value, cancellationToken: cancellationToken).ConfigureAwait(false), }; } diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting/NoopAgentThreadStore.cs b/dotnet/src/Microsoft.Agents.AI.Hosting/NoopAgentThreadStore.cs index c94489d0b0..02c78178a0 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting/NoopAgentThreadStore.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting/NoopAgentThreadStore.cs @@ -20,6 +20,6 @@ public override ValueTask SaveThreadAsync(AIAgent agent, string conversationId, /// public override ValueTask GetThreadAsync(AIAgent agent, string conversationId, CancellationToken cancellationToken = default) { - return new ValueTask(agent.GetNewThread()); + return agent.GetNewThreadAsync(cancellationToken); } } diff --git a/dotnet/src/Microsoft.Agents.AI.Purview/PurviewAgent.cs b/dotnet/src/Microsoft.Agents.AI.Purview/PurviewAgent.cs index 6907fe8889..bd6c42c564 100644 --- a/dotnet/src/Microsoft.Agents.AI.Purview/PurviewAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.Purview/PurviewAgent.cs @@ -30,15 +30,15 @@ public PurviewAgent(AIAgent innerAgent, PurviewWrapper purviewWrapper) } /// - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { - return this._innerAgent.DeserializeThread(serializedThread, jsonSerializerOptions); + return this._innerAgent.DeserializeThreadAsync(serializedThread, jsonSerializerOptions, cancellationToken); } /// - public override AgentThread GetNewThread() + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) { - return this._innerAgent.GetNewThread(); + return this._innerAgent.GetNewThreadAsync(cancellationToken); } /// diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs index 0a887013a3..87474b9c08 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs @@ -20,8 +20,8 @@ public AIAgentHostExecutor(AIAgent agent, bool emitEvents = false) : base(id: ag this._emitEvents = emitEvents; } - private AgentThread EnsureThread(IWorkflowContext context) => - this._thread ??= this._agent.GetNewThread(); + private async Task EnsureThreadAsync(IWorkflowContext context, CancellationToken cancellationToken) => + this._thread ??= await this._agent.GetNewThreadAsync(cancellationToken).ConfigureAwait(false); private const string ThreadStateKey = nameof(_thread); protected internal override async ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken cancellationToken = default) @@ -43,7 +43,7 @@ protected internal override async ValueTask OnCheckpointRestoredAsync(IWorkflowC JsonElement? threadValue = await context.ReadStateAsync(ThreadStateKey, cancellationToken: cancellationToken).ConfigureAwait(false); if (threadValue.HasValue) { - this._thread = this._agent.DeserializeThread(threadValue.Value); + this._thread = await this._agent.DeserializeThreadAsync(threadValue.Value, cancellationToken: cancellationToken).ConfigureAwait(false); } await base.OnCheckpointRestoredAsync(context, cancellationToken).ConfigureAwait(false); @@ -54,7 +54,10 @@ protected override async ValueTask TakeTurnAsync(List messages, IWo if (emitEvents ?? this._emitEvents) { // Run the agent in streaming mode only when agent run update events are to be emitted. - IAsyncEnumerable agentStream = this._agent.RunStreamingAsync(messages, this.EnsureThread(context), cancellationToken: cancellationToken); + IAsyncEnumerable agentStream = this._agent.RunStreamingAsync( + messages, + await this.EnsureThreadAsync(context, cancellationToken).ConfigureAwait(false), + cancellationToken: cancellationToken); List updates = []; @@ -74,7 +77,10 @@ protected override async ValueTask TakeTurnAsync(List messages, IWo else { // Otherwise, run the agent in non-streaming mode. - AgentRunResponse response = await this._agent.RunAsync(messages, this.EnsureThread(context), cancellationToken: cancellationToken).ConfigureAwait(false); + AgentRunResponse response = await this._agent.RunAsync( + messages, + await this.EnsureThreadAsync(context, cancellationToken).ConfigureAwait(false), + cancellationToken: cancellationToken).ConfigureAwait(false); await context.SendMessageAsync(response.Messages, cancellationToken: cancellationToken).ConfigureAwait(false); } } diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostAgent.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostAgent.cs index 4e5ee86070..37867cfd51 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostAgent.cs @@ -63,14 +63,15 @@ private async ValueTask ValidateWorkflowAsync() protocol.ThrowIfNotChatProtocol(); } - public override AgentThread GetNewThread() => new WorkflowThread(this._workflow, this.GenerateNewId(), this._executionEnvironment, this._checkpointManager, this._includeExceptionDetails); + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) + => new(new WorkflowThread(this._workflow, this.GenerateNewId(), this._executionEnvironment, this._checkpointManager, this._includeExceptionDetails)); - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) - => new WorkflowThread(this._workflow, serializedThread, this._executionEnvironment, this._checkpointManager, this._includeExceptionDetails, jsonSerializerOptions); + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) + => new(new WorkflowThread(this._workflow, serializedThread, this._executionEnvironment, this._checkpointManager, this._includeExceptionDetails, jsonSerializerOptions)); - private ValueTask UpdateThreadAsync(IEnumerable messages, AgentThread? thread = null, CancellationToken cancellationToken = default) + private async ValueTask UpdateThreadAsync(IEnumerable messages, AgentThread? thread = null, CancellationToken cancellationToken = default) { - thread ??= this.GetNewThread(); + thread ??= await this.GetNewThreadAsync(cancellationToken).ConfigureAwait(false); if (thread is not WorkflowThread workflowThread) { @@ -80,7 +81,7 @@ private ValueTask UpdateThreadAsync(IEnumerable mes // For workflow threads, messages are added directly via the internal AddMessages method // The MessageStore methods are used for agent invocation scenarios workflowThread.MessageStore.AddMessages(messages); - return new ValueTask(workflowThread); + return workflowThread; } protected override async diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs index 0fa6473de0..0c02932b0a 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs @@ -283,7 +283,7 @@ protected override async IAsyncEnumerable RunCoreStreami // We can derive the type of supported thread from whether we have a conversation id, // so let's update it and set the conversation id for the service thread case. - this.UpdateThreadWithTypeAndConversationId(safeThread, chatResponse.ConversationId); + await this.UpdateThreadWithTypeAndConversationIdAsync(safeThread, chatResponse.ConversationId, cancellationToken).ConfigureAwait(false); // To avoid inconsistent state we only notify the thread of the input messages if no error occurs after the initial request. await NotifyMessageStoreOfNewMessagesAsync(safeThread, GetInputMessages(inputMessages, continuationToken), chatMessageStoreMessages, aiContextProviderMessages, chatResponse.Messages, cancellationToken).ConfigureAwait(false); @@ -302,19 +302,30 @@ protected override async IAsyncEnumerable RunCoreStreami : this.ChatClient.GetService(serviceType, serviceKey)); /// - public override AgentThread GetNewThread() - => new ChatClientAgentThread + public override async ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) + { + ChatMessageStore? messageStore = this._agentOptions?.ChatMessageStoreFactory is not null + ? await this._agentOptions.ChatMessageStoreFactory.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }, cancellationToken).ConfigureAwait(false) + : null; + + AIContextProvider? contextProvider = this._agentOptions?.AIContextProviderFactory is not null + ? await this._agentOptions.AIContextProviderFactory.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }, cancellationToken).ConfigureAwait(false) + : null; + + return new ChatClientAgentThread { - MessageStore = this._agentOptions?.ChatMessageStoreFactory?.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }), - AIContextProvider = this._agentOptions?.AIContextProviderFactory?.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }) + MessageStore = messageStore, + AIContextProvider = contextProvider }; + } /// /// Creates a new agent thread instance using an existing conversation identifier to continue that conversation. /// /// The identifier of an existing conversation to continue. + /// The to monitor for cancellation requests. /// - /// A new instance configured to work with the specified conversation. + /// A value task representing the asynchronous operation. The task result contains a new instance configured to work with the specified conversation. /// /// /// @@ -326,19 +337,26 @@ public override AgentThread GetNewThread() /// instances that support server-side conversation storage through their underlying . /// /// - public AgentThread GetNewThread(string conversationId) - => new ChatClientAgentThread() + public async ValueTask GetNewThreadAsync(string conversationId, CancellationToken cancellationToken = default) + { + AIContextProvider? contextProvider = this._agentOptions?.AIContextProviderFactory is not null + ? await this._agentOptions.AIContextProviderFactory.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }, cancellationToken).ConfigureAwait(false) + : null; + + return new ChatClientAgentThread() { ConversationId = conversationId, - AIContextProvider = this._agentOptions?.AIContextProviderFactory?.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }) + AIContextProvider = contextProvider }; + } /// /// Creates a new agent thread instance using an existing to continue a conversation. /// /// The instance to use for managing the conversation's message history. + /// The to monitor for cancellation requests. /// - /// A new instance configured to work with the provided . + /// A value task representing the asynchronous operation. The task result contains a new instance configured to work with the provided . /// /// /// @@ -347,36 +365,43 @@ public AgentThread GetNewThread(string conversationId) /// with a may not be compatible with these services. /// /// - /// Where a service requires server-side conversation storage, use . + /// Where a service requires server-side conversation storage, use . /// /// /// If the agent detects, during the first run, that the underlying AI service requires server-side conversation storage, /// the thread will throw an exception to indicate that it cannot continue using the provided . /// /// - public AgentThread GetNewThread(ChatMessageStore chatMessageStore) - => new ChatClientAgentThread() + public async ValueTask GetNewThreadAsync(ChatMessageStore chatMessageStore, CancellationToken cancellationToken = default) + { + AIContextProvider? contextProvider = this._agentOptions?.AIContextProviderFactory is not null + ? await this._agentOptions.AIContextProviderFactory.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }, cancellationToken).ConfigureAwait(false) + : null; + + return new ChatClientAgentThread() { MessageStore = Throw.IfNull(chatMessageStore), - AIContextProvider = this._agentOptions?.AIContextProviderFactory?.Invoke(new() { SerializedState = default, JsonSerializerOptions = null }) + AIContextProvider = contextProvider }; + } /// - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) + public override async ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { - Func? chatMessageStoreFactory = this._agentOptions?.ChatMessageStoreFactory is null ? + Func>? chatMessageStoreFactory = this._agentOptions?.ChatMessageStoreFactory is null ? null : - (jse, jso) => this._agentOptions.ChatMessageStoreFactory.Invoke(new() { SerializedState = jse, JsonSerializerOptions = jso }); + (jse, jso, ct) => this._agentOptions.ChatMessageStoreFactory.Invoke(new() { SerializedState = jse, JsonSerializerOptions = jso }, ct); - Func? aiContextProviderFactory = this._agentOptions?.AIContextProviderFactory is null ? + Func>? aiContextProviderFactory = this._agentOptions?.AIContextProviderFactory is null ? null : - (jse, jso) => this._agentOptions.AIContextProviderFactory.Invoke(new() { SerializedState = jse, JsonSerializerOptions = jso }); + (jse, jso, ct) => this._agentOptions.AIContextProviderFactory.Invoke(new() { SerializedState = jse, JsonSerializerOptions = jso }, ct); - return new ChatClientAgentThread( + return await ChatClientAgentThread.DeserializeAsync( serializedThread, jsonSerializerOptions, chatMessageStoreFactory, - aiContextProviderFactory); + aiContextProviderFactory, + cancellationToken).ConfigureAwait(false); } #region Private @@ -426,7 +451,7 @@ private async Task RunCoreAsync /// which will be used to store chat messages for this agent. /// - public Func? ChatMessageStoreFactory { get; set; } + public Func>? ChatMessageStoreFactory { get; set; } /// /// Gets or sets a factory function to create an instance of /// which will be used to create a context provider for each new thread, and can then /// provide additional context for each agent run. /// - public Func? AIContextProviderFactory { get; set; } + public Func>? AIContextProviderFactory { get; set; } /// /// Gets or sets a value indicating whether to use the provided instance as is, diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentThread.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentThread.cs index f4cf4aa033..06326d1ed2 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentThread.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentThread.cs @@ -3,6 +3,8 @@ using System; using System.Diagnostics; using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI; @@ -22,48 +24,6 @@ internal ChatClientAgentThread() { } - /// - /// Initializes a new instance of the class from previously serialized state. - /// - /// A representing the serialized state of the thread. - /// Optional settings for customizing the JSON deserialization process. - /// - /// An optional factory function to create a custom from its serialized state. - /// If not provided, the default in-memory message store will be used. - /// - /// - /// An optional factory function to create a custom from its serialized state. - /// If not provided, no context provider will be configured. - /// - internal ChatClientAgentThread( - JsonElement serializedThreadState, - JsonSerializerOptions? jsonSerializerOptions = null, - Func? chatMessageStoreFactory = null, - Func? aiContextProviderFactory = null) - { - if (serializedThreadState.ValueKind != JsonValueKind.Object) - { - throw new ArgumentException("The serialized thread state must be a JSON object.", nameof(serializedThreadState)); - } - - var state = serializedThreadState.Deserialize( - AgentJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ThreadState))) as ThreadState; - - this.AIContextProvider = aiContextProviderFactory?.Invoke(state?.AIContextProviderState ?? default, jsonSerializerOptions); - - if (state?.ConversationId is string threadId) - { - this.ConversationId = threadId; - - // Since we have an ID, we should not have a chat message store and we can return here. - return; - } - - this._messageStore = - chatMessageStoreFactory?.Invoke(state?.StoreState ?? default, jsonSerializerOptions) ?? - new InMemoryChatMessageStore(state?.StoreState ?? default, jsonSerializerOptions); // default to an in-memory store - } - /// /// Gets or sets the ID of the underlying service thread to support cases where the chat history is stored by the agent service. /// @@ -152,6 +112,58 @@ internal set /// public AIContextProvider? AIContextProvider { get; internal set; } + /// + /// Creates a new instance of the class from previously serialized state. + /// + /// A representing the serialized state of the thread. + /// Optional settings for customizing the JSON deserialization process. + /// + /// An optional factory function to create a custom from its serialized state. + /// If not provided, the default in-memory message store will be used. + /// + /// + /// An optional factory function to create a custom from its serialized state. + /// If not provided, no context provider will be configured. + /// + /// The to monitor for cancellation requests. + /// A task representing the asynchronous operation. The task result contains the deserialized . + internal static async Task DeserializeAsync( + JsonElement serializedThreadState, + JsonSerializerOptions? jsonSerializerOptions = null, + Func>? chatMessageStoreFactory = null, + Func>? aiContextProviderFactory = null, + CancellationToken cancellationToken = default) + { + if (serializedThreadState.ValueKind != JsonValueKind.Object) + { + throw new ArgumentException("The serialized thread state must be a JSON object.", nameof(serializedThreadState)); + } + + var state = serializedThreadState.Deserialize( + AgentJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ThreadState))) as ThreadState; + + var thread = new ChatClientAgentThread(); + + thread.AIContextProvider = aiContextProviderFactory is not null + ? await aiContextProviderFactory.Invoke(state?.AIContextProviderState ?? default, jsonSerializerOptions, cancellationToken).ConfigureAwait(false) + : null; + + if (state?.ConversationId is string threadId) + { + thread.ConversationId = threadId; + + // Since we have an ID, we should not have a chat message store and we can return here. + return thread; + } + + thread._messageStore = + chatMessageStoreFactory is not null + ? await chatMessageStoreFactory.Invoke(state?.StoreState ?? default, jsonSerializerOptions, cancellationToken).ConfigureAwait(false) + : new InMemoryChatMessageStore(state?.StoreState ?? default, jsonSerializerOptions); // default to an in-memory store + + return thread; + } + /// public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) { diff --git a/dotnet/tests/AgentConformance.IntegrationTests/ChatClientAgentRunStreamingTests.cs b/dotnet/tests/AgentConformance.IntegrationTests/ChatClientAgentRunStreamingTests.cs index 834de0ea4e..2d8d6787aa 100644 --- a/dotnet/tests/AgentConformance.IntegrationTests/ChatClientAgentRunStreamingTests.cs +++ b/dotnet/tests/AgentConformance.IntegrationTests/ChatClientAgentRunStreamingTests.cs @@ -22,7 +22,7 @@ public virtual async Task RunWithInstructionsAndNoMessageReturnsExpectedResultAs { // Arrange var agent = await this.Fixture.CreateChatClientAgentAsync(instructions: "Always respond with 'Computer says no', even if there was no user input."); - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); await using var agentCleanup = new AgentCleanup(agent, this.Fixture); await using var threadCleanup = new ThreadCleanup(thread, this.Fixture); @@ -53,7 +53,7 @@ public virtual async Task RunWithFunctionsInvokesFunctionsAndReturnsExpectedResu AIFunctionFactory.Create(MenuPlugin.GetSpecials), AIFunctionFactory.Create(MenuPlugin.GetItemPrice) ]); - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); foreach (var questionAndAnswer in questionsAndAnswers) { diff --git a/dotnet/tests/AgentConformance.IntegrationTests/ChatClientAgentRunTests.cs b/dotnet/tests/AgentConformance.IntegrationTests/ChatClientAgentRunTests.cs index ab85bf5ba0..80fd7106ac 100644 --- a/dotnet/tests/AgentConformance.IntegrationTests/ChatClientAgentRunTests.cs +++ b/dotnet/tests/AgentConformance.IntegrationTests/ChatClientAgentRunTests.cs @@ -21,7 +21,7 @@ public virtual async Task RunWithInstructionsAndNoMessageReturnsExpectedResultAs { // Arrange var agent = await this.Fixture.CreateChatClientAgentAsync(instructions: "ALWAYS RESPOND WITH 'Computer says no', even if there was no user input."); - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); await using var agentCleanup = new AgentCleanup(agent, this.Fixture); await using var threadCleanup = new ThreadCleanup(thread, this.Fixture); @@ -53,7 +53,7 @@ public virtual async Task RunWithFunctionsInvokesFunctionsAndReturnsExpectedResu AIFunctionFactory.Create(MenuPlugin.GetSpecials), AIFunctionFactory.Create(MenuPlugin.GetItemPrice) ]); - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); foreach (var questionAndAnswer in questionsAndAnswers) { diff --git a/dotnet/tests/AgentConformance.IntegrationTests/RunStreamingTests.cs b/dotnet/tests/AgentConformance.IntegrationTests/RunStreamingTests.cs index a2da3e0d6e..d5c85b1866 100644 --- a/dotnet/tests/AgentConformance.IntegrationTests/RunStreamingTests.cs +++ b/dotnet/tests/AgentConformance.IntegrationTests/RunStreamingTests.cs @@ -24,7 +24,7 @@ public virtual async Task RunWithNoMessageDoesNotFailAsync() { // Arrange var agent = this.Fixture.Agent; - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); await using var cleanup = new ThreadCleanup(thread, this.Fixture); // Act @@ -36,7 +36,7 @@ public virtual async Task RunWithStringReturnsExpectedResultAsync() { // Arrange var agent = this.Fixture.Agent; - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); await using var cleanup = new ThreadCleanup(thread, this.Fixture); // Act @@ -52,7 +52,7 @@ public virtual async Task RunWithChatMessageReturnsExpectedResultAsync() { // Arrange var agent = this.Fixture.Agent; - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); await using var cleanup = new ThreadCleanup(thread, this.Fixture); // Act @@ -68,7 +68,7 @@ public virtual async Task RunWithChatMessagesReturnsExpectedResultAsync() { // Arrange var agent = this.Fixture.Agent; - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); await using var cleanup = new ThreadCleanup(thread, this.Fixture); // Act @@ -92,7 +92,7 @@ public virtual async Task ThreadMaintainsHistoryAsync() const string Q1 = "What is the capital of France."; const string Q2 = "And Austria?"; var agent = this.Fixture.Agent; - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); await using var cleanup = new ThreadCleanup(thread, this.Fixture); // Act diff --git a/dotnet/tests/AgentConformance.IntegrationTests/RunTests.cs b/dotnet/tests/AgentConformance.IntegrationTests/RunTests.cs index 58f8b67d1d..be98bbd2bf 100644 --- a/dotnet/tests/AgentConformance.IntegrationTests/RunTests.cs +++ b/dotnet/tests/AgentConformance.IntegrationTests/RunTests.cs @@ -24,7 +24,7 @@ public virtual async Task RunWithNoMessageDoesNotFailAsync() { // Arrange var agent = this.Fixture.Agent; - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); await using var cleanup = new ThreadCleanup(thread, this.Fixture); // Act @@ -39,7 +39,7 @@ public virtual async Task RunWithStringReturnsExpectedResultAsync() { // Arrange var agent = this.Fixture.Agent; - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); await using var cleanup = new ThreadCleanup(thread, this.Fixture); // Act @@ -57,7 +57,7 @@ public virtual async Task RunWithChatMessageReturnsExpectedResultAsync() { // Arrange var agent = this.Fixture.Agent; - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); await using var cleanup = new ThreadCleanup(thread, this.Fixture); // Act @@ -74,7 +74,7 @@ public virtual async Task RunWithChatMessagesReturnsExpectedResultAsync() { // Arrange var agent = this.Fixture.Agent; - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); await using var cleanup = new ThreadCleanup(thread, this.Fixture); // Act @@ -99,7 +99,7 @@ public virtual async Task ThreadMaintainsHistoryAsync() const string Q1 = "What is the capital of France."; const string Q2 = "And Austria?"; var agent = this.Fixture.Agent; - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); await using var cleanup = new ThreadCleanup(thread, this.Fixture); // Act diff --git a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AAgentTests.cs index 236ae7b332..6364e87012 100644 --- a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AAgentTests.cs @@ -148,7 +148,7 @@ public async Task RunAsync_WithNewThread_UpdatesThreadConversationIdAsync() new(ChatRole.User, "Test message") }; - var thread = this._agent.GetNewThread(); + var thread = await this._agent.GetNewThreadAsync(); // Act await this._agent.RunAsync(inputMessages, thread); @@ -168,7 +168,7 @@ public async Task RunAsync_WithExistingThread_SetConversationIdToMessageAsync() new(ChatRole.User, "Test message") }; - var thread = this._agent.GetNewThread(); + var thread = await this._agent.GetNewThreadAsync(); var a2aThread = (A2AAgentThread)thread; a2aThread.ContextId = "existing-context-id"; @@ -201,7 +201,7 @@ public async Task RunAsync_WithThreadHavingDifferentContextId_ThrowsInvalidOpera ContextId = "different-context" }; - var thread = this._agent.GetNewThread(); + var thread = await this._agent.GetNewThreadAsync(); var a2aThread = (A2AAgentThread)thread; a2aThread.ContextId = "existing-context-id"; @@ -272,7 +272,7 @@ public async Task RunStreamingAsync_WithThread_UpdatesThreadConversationIdAsync( ContextId = "new-stream-context" }; - var thread = this._agent.GetNewThread(); + var thread = await this._agent.GetNewThreadAsync(); // Act await foreach (var _ in this._agent.RunStreamingAsync(inputMessages, thread)) @@ -296,7 +296,7 @@ public async Task RunStreamingAsync_WithExistingThread_SetConversationIdToMessag this._handler.StreamingResponseToReturn = new AgentMessage(); - var thread = this._agent.GetNewThread(); + var thread = await this._agent.GetNewThreadAsync(); var a2aThread = (A2AAgentThread)thread; a2aThread.ContextId = "existing-context-id"; @@ -316,7 +316,7 @@ public async Task RunStreamingAsync_WithExistingThread_SetConversationIdToMessag public async Task RunStreamingAsync_WithThreadHavingDifferentContextId_ThrowsInvalidOperationExceptionAsync() { // Arrange - var thread = this._agent.GetNewThread(); + var thread = await this._agent.GetNewThreadAsync(); var a2aThread = (A2AAgentThread)thread; a2aThread.ContextId = "existing-context-id"; @@ -440,7 +440,7 @@ public async Task RunAsync_WithTaskInThreadAndMessage_AddTaskAsReferencesToMessa Parts = [new TextPart { Text = "Response to task" }] }; - var thread = (A2AAgentThread)this._agent.GetNewThread(); + var thread = (A2AAgentThread)await this._agent.GetNewThreadAsync(); thread.TaskId = "task-123"; var inputMessage = new ChatMessage(ChatRole.User, "Please make the background transparent"); @@ -466,7 +466,7 @@ public async Task RunAsync_WithAgentTask_UpdatesThreadTaskIdAsync() Status = new() { State = TaskState.Submitted } }; - var thread = this._agent.GetNewThread(); + var thread = await this._agent.GetNewThreadAsync(); // Act await this._agent.RunAsync("Start a task", thread); @@ -492,7 +492,7 @@ public async Task RunAsync_WithAgentTaskResponse_ReturnsTaskResponseCorrectlyAsy } }; - var thread = this._agent.GetNewThread(); + var thread = await this._agent.GetNewThreadAsync(); // Act var result = await this._agent.RunAsync("Start a long-running task", thread); @@ -586,7 +586,7 @@ public async Task RunStreamingAsync_WithTaskInThreadAndMessage_AddTaskAsReferenc Parts = [new TextPart { Text = "Response to task" }] }; - var thread = (A2AAgentThread)this._agent.GetNewThread(); + var thread = (A2AAgentThread)await this._agent.GetNewThreadAsync(); thread.TaskId = "task-123"; // Act @@ -613,7 +613,7 @@ public async Task RunStreamingAsync_WithAgentTask_UpdatesThreadTaskIdAsync() Status = new() { State = TaskState.Submitted } }; - var thread = this._agent.GetNewThread(); + var thread = await this._agent.GetNewThreadAsync(); // Act await foreach (var _ in this._agent.RunStreamingAsync("Start a task", thread)) @@ -686,7 +686,7 @@ public async Task RunStreamingAsync_WithAgentTask_YieldsResponseUpdateAsync() ] }; - var thread = this._agent.GetNewThread(); + var thread = await this._agent.GetNewThreadAsync(); // Act var updates = new List(); @@ -725,7 +725,7 @@ public async Task RunStreamingAsync_WithTaskStatusUpdateEvent_YieldsResponseUpda Status = new() { State = TaskState.Working } }; - var thread = this._agent.GetNewThread(); + var thread = await this._agent.GetNewThreadAsync(); // Act var updates = new List(); @@ -768,7 +768,7 @@ public async Task RunStreamingAsync_WithTaskArtifactUpdateEvent_YieldsResponseUp } }; - var thread = this._agent.GetNewThread(); + var thread = await this._agent.GetNewThreadAsync(); // Act var updates = new List(); diff --git a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatClientTests.cs b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatClientTests.cs index c61a7e289d..cc000ca73a 100644 --- a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatClientTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatClientTests.cs @@ -228,7 +228,7 @@ public async Task RunStreamingAsync_ReturnsStreamingUpdates_AfterCompletionAsync var chatClient = new AGUIChatClient(httpClient, "http://localhost/agent", null, AGUIJsonSerializerContext.Default.Options); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "agent1", description: "Test agent", tools: []); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); List messages = [new ChatMessage(ChatRole.User, "Hello")]; // Act @@ -244,17 +244,17 @@ public async Task RunStreamingAsync_ReturnsStreamingUpdates_AfterCompletionAsync } [Fact] - public void DeserializeThread_WithValidState_ReturnsChatClientAgentThread() + public async Task DeserializeThread_WithValidState_ReturnsChatClientAgentThreadAsync() { // Arrange using var httpClient = new HttpClient(); var chatClient = new AGUIChatClient(httpClient, "http://localhost/agent", null, AGUIJsonSerializerContext.Default.Options); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "agent1", description: "Test agent", tools: []); - AgentThread originalThread = agent.GetNewThread(); + AgentThread originalThread = await agent.GetNewThreadAsync(); JsonElement serialized = originalThread.Serialize(); // Act - AgentThread deserialized = agent.DeserializeThread(serialized); + AgentThread deserialized = await agent.DeserializeThreadAsync(serialized); // Assert Assert.NotNull(deserialized); @@ -487,7 +487,7 @@ public async Task RunStreamingAsync_UpdatesThreadWithToolMessages_AfterCompletio var chatClient = new AGUIChatClient(httpClient, "http://localhost/agent", null, AGUIJsonSerializerContext.Default.Options); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "agent1", description: "Test agent", tools: [testTool]); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); List messages = [new ChatMessage(ChatRole.User, "Test")]; // Act diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs index a1c8cb32bf..514de74dab 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs @@ -378,10 +378,10 @@ public MockAgent(string? id = null) protected override string? IdCore { get; } - public override AgentThread GetNewThread() + public override async ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) + public override async ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); protected override Task RunCoreAsync( diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/DelegatingAIAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/DelegatingAIAgentTests.cs index 2a6cc7bb81..be9522fc0b 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/DelegatingAIAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/DelegatingAIAgentTests.cs @@ -35,7 +35,7 @@ public DelegatingAIAgentTests() this._innerAgentMock.Protected().SetupGet("IdCore").Returns("test-agent-id"); this._innerAgentMock.Setup(x => x.Name).Returns("Test Agent"); this._innerAgentMock.Setup(x => x.Description).Returns("Test Description"); - this._innerAgentMock.Setup(x => x.GetNewThread()).Returns(this._testThread); + this._innerAgentMock.Setup(x => x.GetNewThreadAsync()).ReturnsAsync(this._testThread); this._innerAgentMock .Protected() @@ -132,17 +132,17 @@ public void Description_DelegatesToInnerAgent() #region Method Delegation Tests /// - /// Verify that GetNewThread delegates to inner agent. + /// Verify that GetNewThreadAsync delegates to inner agent. /// [Fact] - public void GetNewThread_DelegatesToInnerAgent() + public async Task GetNewThreadAsync_DelegatesToInnerAgentAsync() { // Act - var thread = this._delegatingAgent.GetNewThread(); + var thread = await this._delegatingAgent.GetNewThreadAsync(); // Assert Assert.Same(this._testThread, thread); - this._innerAgentMock.Verify(x => x.GetNewThread(), Times.Once); + this._innerAgentMock.Verify(x => x.GetNewThreadAsync(), Times.Once); } /// diff --git a/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientTests.cs b/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientTests.cs index eee9f520b6..0c93c72172 100644 --- a/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientTests.cs @@ -53,7 +53,7 @@ public async Task ChatClient_UsesDefaultConversationIdAsync() }); // Act - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); await agent.RunAsync("Hello", thread); Assert.True(requestTriggered); @@ -102,7 +102,7 @@ public async Task ChatClient_UsesPerRequestConversationId_WhenNoDefaultConversat }); // Act - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); await agent.RunAsync("Hello", thread, options: new ChatClientAgentRunOptions() { ChatOptions = new() { ConversationId = "conv_12345" } }); Assert.True(requestTriggered); @@ -151,7 +151,7 @@ public async Task ChatClient_UsesPerRequestConversationId_EvenWhenDefaultConvers }); // Act - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); await agent.RunAsync("Hello", thread, options: new ChatClientAgentRunOptions() { ChatOptions = new() { ConversationId = "conv_12345" } }); Assert.True(requestTriggered); @@ -200,7 +200,7 @@ public async Task ChatClient_UsesPreviousResponseId_WhenConversationIsNotPrefixe }); // Act - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); await agent.RunAsync("Hello", thread, options: new ChatClientAgentRunOptions() { ChatOptions = new() { ConversationId = "resp_0888a" } }); Assert.True(requestTriggered); diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AggregatorPromptAgentFactoryTests.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AggregatorPromptAgentFactoryTests.cs index 09ee72504a..2e10935686 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AggregatorPromptAgentFactoryTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AggregatorPromptAgentFactoryTests.cs @@ -66,12 +66,12 @@ public TestAgentFactory(AIAgent? agentToReturn = null) private sealed class TestAgent : AIAgent { - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public override AgentThread GetNewThread() + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) { throw new NotImplementedException(); } diff --git a/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/AgentEntityTests.cs b/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/AgentEntityTests.cs index b615bf1cd6..afd5d0a573 100644 --- a/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/AgentEntityTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/AgentEntityTests.cs @@ -51,7 +51,7 @@ public async Task EntityNamePrefixAsync() // A proxy agent is needed to call the hosted test agent AIAgent simpleAgentProxy = simpleAgent.AsDurableAgentProxy(testHelper.Services); - AgentThread thread = simpleAgentProxy.GetNewThread(); + AgentThread thread = await simpleAgentProxy.GetNewThreadAsync(this.TestTimeoutToken); DurableTaskClient client = testHelper.GetClient(); @@ -98,7 +98,7 @@ public async Task RunAgentMethodNamesAllWorkAsync(string runAgentMethodName) // A proxy agent is needed to call the hosted test agent AIAgent simpleAgentProxy = simpleAgent.AsDurableAgentProxy(testHelper.Services); - AgentThread thread = simpleAgentProxy.GetNewThread(); + AgentThread thread = await simpleAgentProxy.GetNewThreadAsync(this.TestTimeoutToken); DurableTaskClient client = testHelper.GetClient(); @@ -184,7 +184,7 @@ private sealed class TestOrchestrator : TaskOrchestrator public override async Task RunAsync(TaskOrchestrationContext context, string input) { DurableAIAgent writer = context.GetAgent("TestAgent"); - AgentThread writerThread = writer.GetNewThread(); + AgentThread writerThread = await writer.GetNewThreadAsync(); await writer.RunAsync( message: context.GetInput()!, diff --git a/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/ExternalClientTests.cs b/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/ExternalClientTests.cs index c43b86e330..9076b6f6e7 100644 --- a/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/ExternalClientTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/ExternalClientTests.cs @@ -51,7 +51,7 @@ public async Task SimplePromptAsync() AIAgent simpleAgentProxy = simpleAgent.AsDurableAgentProxy(testHelper.Services); // Act: send a prompt to the agent and wait for a response - AgentThread thread = simpleAgentProxy.GetNewThread(); + AgentThread thread = await simpleAgentProxy.GetNewThreadAsync(this.TestTimeoutToken); await simpleAgentProxy.RunAsync( message: "Hello!", thread, @@ -156,7 +156,7 @@ async Task RunWorkflowAsync(TaskOrchestrationContext context, string nam { // 1. Get agent and create a session DurableAIAgent agent = context.GetAgent("SimpleAgent"); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(this.TestTimeoutToken); // 2. Call an agent and tell it my name await agent.RunAsync($"My name is {name}.", thread); @@ -194,7 +194,7 @@ async Task RunWorkflowAsync(TaskOrchestrationContext context, string nam AIAgent workflowManagerAgentProxy = testHelper.Services.GetDurableAgentProxy("WorkflowAgent"); // Act: send a prompt to the agent - AgentThread thread = workflowManagerAgentProxy.GetNewThread(); + AgentThread thread = await workflowManagerAgentProxy.GetNewThreadAsync(this.TestTimeoutToken); await workflowManagerAgentProxy.RunAsync( message: "Start a greeting workflow for \"John Doe\".", thread, diff --git a/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/TimeToLiveTests.cs b/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/TimeToLiveTests.cs index 25d40a1c5a..f9a1e44404 100644 --- a/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/TimeToLiveTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/TimeToLiveTests.cs @@ -55,7 +55,7 @@ public async Task EntityExpiresAfterTTLAsync() }); AIAgent agentProxy = simpleAgent.AsDurableAgentProxy(testHelper.Services); - AgentThread thread = agentProxy.GetNewThread(); + AgentThread thread = await agentProxy.GetNewThreadAsync(this.TestTimeoutToken); DurableTaskClient client = testHelper.GetClient(); AgentSessionId sessionId = thread.GetService(); @@ -120,7 +120,7 @@ public async Task EntityTTLResetsOnInteractionAsync() }); AIAgent agentProxy = simpleAgent.AsDurableAgentProxy(testHelper.Services); - AgentThread thread = agentProxy.GetNewThread(); + AgentThread thread = await agentProxy.GetNewThreadAsync(this.TestTimeoutToken); DurableTaskClient client = testHelper.GetClient(); AgentSessionId sessionId = thread.GetService(); diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/AIAgentExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/AIAgentExtensionsTests.cs index 0d5b895974..10e9bea395 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/AIAgentExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/AIAgentExtensionsTests.cs @@ -175,7 +175,7 @@ private static Mock CreateAgentMock(Action optionsCal { Mock agentMock = new() { CallBase = true }; agentMock.SetupGet(x => x.Name).Returns("TestAgent"); - agentMock.Setup(x => x.GetNewThread()).Returns(new TestAgentThread()); + agentMock.Setup(x => x.GetNewThreadAsync()).ReturnsAsync(new TestAgentThread()); agentMock .Protected() .Setup>("RunCoreAsync", @@ -194,7 +194,7 @@ private static Mock CreateAgentMockWithResponse(AgentRunResponse respon { Mock agentMock = new() { CallBase = true }; agentMock.SetupGet(x => x.Name).Returns("TestAgent"); - agentMock.Setup(x => x.GetNewThread()).Returns(new TestAgentThread()); + agentMock.Setup(x => x.GetNewThreadAsync()).ReturnsAsync(new TestAgentThread()); agentMock .Protected() .Setup>("RunCoreAsync", diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs index dfabaca64e..26b578d305 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs @@ -31,7 +31,7 @@ public async Task ClientReceivesStreamedAssistantMessageAsync() await this.SetupTestServerAsync(); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Sample assistant", tools: []); - ChatClientAgentThread thread = (ChatClientAgentThread)agent.GetNewThread(); + ChatClientAgentThread thread = (ChatClientAgentThread)await agent.GetNewThreadAsync(); ChatMessage userMessage = new(ChatRole.User, "hello"); List updates = []; @@ -62,7 +62,7 @@ public async Task ClientReceivesRunLifecycleEventsAsync() await this.SetupTestServerAsync(); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Sample assistant", tools: []); - ChatClientAgentThread thread = (ChatClientAgentThread)agent.GetNewThread(); + ChatClientAgentThread thread = (ChatClientAgentThread)await agent.GetNewThreadAsync(); ChatMessage userMessage = new(ChatRole.User, "test"); List updates = []; @@ -106,7 +106,7 @@ public async Task RunAsyncAggregatesStreamingUpdatesAsync() await this.SetupTestServerAsync(); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Sample assistant", tools: []); - ChatClientAgentThread thread = (ChatClientAgentThread)agent.GetNewThread(); + ChatClientAgentThread thread = (ChatClientAgentThread)await agent.GetNewThreadAsync(); ChatMessage userMessage = new(ChatRole.User, "hello"); // Act @@ -125,7 +125,7 @@ public async Task MultiTurnConversationPreservesAllMessagesInThreadAsync() await this.SetupTestServerAsync(); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Sample assistant", tools: []); - ChatClientAgentThread chatClientThread = (ChatClientAgentThread)agent.GetNewThread(); + ChatClientAgentThread chatClientThread = (ChatClientAgentThread)await agent.GetNewThreadAsync(); ChatMessage firstUserMessage = new(ChatRole.User, "First question"); // Act - First turn @@ -169,7 +169,7 @@ public async Task AgentSendsMultipleMessagesInOneTurnAsync() await this.SetupTestServerAsync(useMultiMessageAgent: true); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Sample assistant", tools: []); - ChatClientAgentThread chatClientThread = (ChatClientAgentThread)agent.GetNewThread(); + ChatClientAgentThread chatClientThread = (ChatClientAgentThread)await agent.GetNewThreadAsync(); ChatMessage userMessage = new(ChatRole.User, "Tell me a story"); List updates = []; @@ -201,7 +201,7 @@ public async Task UserSendsMultipleMessagesAtOnceAsync() await this.SetupTestServerAsync(); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Sample assistant", tools: []); - ChatClientAgentThread chatClientThread = (ChatClientAgentThread)agent.GetNewThread(); + ChatClientAgentThread chatClientThread = (ChatClientAgentThread)await agent.GetNewThreadAsync(); // Multiple user messages sent in one turn ChatMessage[] userMessages = @@ -280,15 +280,11 @@ internal sealed class FakeChatClientAgent : AIAgent public override string? Description => "A fake agent for testing"; - public override AgentThread GetNewThread() - { - return new FakeInMemoryAgentThread(); - } + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) => + new(new FakeInMemoryAgentThread()); - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) - { - return new FakeInMemoryAgentThread(serializedThread, jsonSerializerOptions); - } + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => + new(new FakeInMemoryAgentThread(serializedThread, jsonSerializerOptions)); protected override async Task RunCoreAsync( IEnumerable messages, @@ -348,15 +344,11 @@ internal sealed class FakeMultiMessageAgent : AIAgent public override string? Description => "A fake agent that sends multiple messages for testing"; - public override AgentThread GetNewThread() - { - return new FakeInMemoryAgentThread(); - } + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) => + new(new FakeInMemoryAgentThread()); - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) - { - return new FakeInMemoryAgentThread(serializedThread, jsonSerializerOptions); - } + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => + new(new FakeInMemoryAgentThread(serializedThread, jsonSerializerOptions)); protected override async Task RunCoreAsync( IEnumerable messages, diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs index 1777ff456a..5bd6c8b2c5 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs @@ -334,12 +334,11 @@ protected override async IAsyncEnumerable RunCoreStreami await Task.CompletedTask; } - public override AgentThread GetNewThread() => new FakeInMemoryAgentThread(); + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) => + new(new FakeInMemoryAgentThread()); - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) - { - return new FakeInMemoryAgentThread(serializedThread, jsonSerializerOptions); - } + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => + new(new FakeInMemoryAgentThread(serializedThread, jsonSerializerOptions)); private sealed class FakeInMemoryAgentThread : InMemoryAgentThread { diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs index df51d1cbc4..2bcc264d74 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs @@ -34,7 +34,7 @@ public async Task StateSnapshot_IsReturnedAsDataContent_WithCorrectMediaTypeAsyn await this.SetupTestServerAsync(fakeAgent); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Sample assistant", tools: []); - ChatClientAgentThread thread = (ChatClientAgentThread)agent.GetNewThread(); + ChatClientAgentThread thread = (ChatClientAgentThread)await agent.GetNewThreadAsync(); string stateJson = JsonSerializer.Serialize(initialState); byte[] stateBytes = System.Text.Encoding.UTF8.GetBytes(stateJson); @@ -77,7 +77,7 @@ public async Task StateSnapshot_HasCorrectAdditionalPropertiesAsync() await this.SetupTestServerAsync(fakeAgent); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Sample assistant", tools: []); - ChatClientAgentThread thread = (ChatClientAgentThread)agent.GetNewThread(); + ChatClientAgentThread thread = (ChatClientAgentThread)await agent.GetNewThreadAsync(); string stateJson = JsonSerializer.Serialize(initialState); byte[] stateBytes = System.Text.Encoding.UTF8.GetBytes(stateJson); @@ -119,7 +119,7 @@ public async Task ComplexState_WithNestedObjectsAndArrays_RoundTripsCorrectlyAsy await this.SetupTestServerAsync(fakeAgent); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Sample assistant", tools: []); - ChatClientAgentThread thread = (ChatClientAgentThread)agent.GetNewThread(); + ChatClientAgentThread thread = (ChatClientAgentThread)await agent.GetNewThreadAsync(); string stateJson = JsonSerializer.Serialize(complexState); byte[] stateBytes = System.Text.Encoding.UTF8.GetBytes(stateJson); @@ -159,7 +159,7 @@ public async Task StateSnapshot_CanBeUsedInSubsequentRequest_ForStateRoundTripAs await this.SetupTestServerAsync(fakeAgent); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Sample assistant", tools: []); - ChatClientAgentThread thread = (ChatClientAgentThread)agent.GetNewThread(); + ChatClientAgentThread thread = (ChatClientAgentThread)await agent.GetNewThreadAsync(); string stateJson = JsonSerializer.Serialize(initialState); byte[] stateBytes = System.Text.Encoding.UTF8.GetBytes(stateJson); @@ -210,7 +210,7 @@ public async Task WithoutState_AgentBehavesNormally_NoStateSnapshotReturnedAsync await this.SetupTestServerAsync(fakeAgent); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Sample assistant", tools: []); - ChatClientAgentThread thread = (ChatClientAgentThread)agent.GetNewThread(); + ChatClientAgentThread thread = (ChatClientAgentThread)await agent.GetNewThreadAsync(); ChatMessage userMessage = new(ChatRole.User, "hello"); @@ -243,7 +243,7 @@ public async Task EmptyState_DoesNotTriggerStateHandlingAsync() await this.SetupTestServerAsync(fakeAgent); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Sample assistant", tools: []); - ChatClientAgentThread thread = (ChatClientAgentThread)agent.GetNewThread(); + ChatClientAgentThread thread = (ChatClientAgentThread)await agent.GetNewThreadAsync(); string stateJson = JsonSerializer.Serialize(emptyState); byte[] stateBytes = System.Text.Encoding.UTF8.GetBytes(stateJson); @@ -280,7 +280,7 @@ public async Task NonStreamingRunAsync_WithState_ReturnsStateInResponseAsync() await this.SetupTestServerAsync(fakeAgent); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Sample assistant", tools: []); - ChatClientAgentThread thread = (ChatClientAgentThread)agent.GetNewThread(); + ChatClientAgentThread thread = (ChatClientAgentThread)await agent.GetNewThreadAsync(); string stateJson = JsonSerializer.Serialize(initialState); byte[] stateBytes = System.Text.Encoding.UTF8.GetBytes(stateJson); @@ -417,12 +417,11 @@ stateObj is JsonElement state && await Task.CompletedTask; } - public override AgentThread GetNewThread() => new FakeInMemoryAgentThread(); + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) => + new(new FakeInMemoryAgentThread()); - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) - { - return new FakeInMemoryAgentThread(serializedThread, jsonSerializerOptions); - } + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => + new(new FakeInMemoryAgentThread(serializedThread, jsonSerializerOptions)); private sealed class FakeInMemoryAgentThread : InMemoryAgentThread { diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ToolCallingTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ToolCallingTests.cs index 178ed20d73..4e51fadf94 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ToolCallingTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ToolCallingTests.cs @@ -45,7 +45,7 @@ public async Task ServerTriggersSingleFunctionCallAsync() await this.SetupTestServerAsync(serverTools: [serverTool]); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Test assistant", tools: []); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ChatMessage userMessage = new(ChatRole.User, "Call the server function"); List updates = []; @@ -93,7 +93,7 @@ public async Task ServerTriggersMultipleFunctionCallsAsync() await this.SetupTestServerAsync(serverTools: [getWeatherTool, getTimeTool]); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Test assistant", tools: []); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ChatMessage userMessage = new(ChatRole.User, "What's the weather and time?"); List updates = []; @@ -134,7 +134,7 @@ public async Task ClientTriggersSingleFunctionCallAsync() await this.SetupTestServerAsync(); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Test assistant", tools: [clientTool]); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ChatMessage userMessage = new(ChatRole.User, "Call the client function"); List updates = []; @@ -182,7 +182,7 @@ public async Task ClientTriggersMultipleFunctionCallsAsync() await this.SetupTestServerAsync(); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Test assistant", tools: [calculateTool, formatTool]); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ChatMessage userMessage = new(ChatRole.User, "Calculate 5 + 3 and format 'hello'"); List updates = []; @@ -233,7 +233,7 @@ public async Task ServerAndClientTriggerFunctionCallsSimultaneouslyAsync() await this.SetupTestServerAsync(serverTools: [serverTool]); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Test assistant", tools: [clientTool]); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ChatMessage userMessage = new(ChatRole.User, "Get both server and client data"); List updates = []; @@ -298,7 +298,7 @@ public async Task FunctionCallsPreserveCallIdAndNameAsync() await this.SetupTestServerAsync(serverTools: [testTool]); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Test assistant", tools: []); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ChatMessage userMessage = new(ChatRole.User, "Call the test function"); List updates = []; @@ -342,7 +342,7 @@ public async Task ParallelFunctionCallsFromServerAreHandledCorrectlyAsync() await this.SetupTestServerAsync(serverTools: [func1, func2], triggerParallelCalls: true); var chatClient = new AGUIChatClient(this._client!, "", null); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Test assistant", tools: []); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ChatMessage userMessage = new(ChatRole.User, "Call both functions in parallel"); List updates = []; @@ -428,7 +428,7 @@ public async Task ServerToolCallWithCustomArgumentsAsync() await this.SetupTestServerAsync(serverTools: [serverTool], jsonSerializerOptions: ServerJsonContext.Default.Options); var chatClient = new AGUIChatClient(this._client!, "", null, ServerJsonContext.Default.Options); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Test assistant", tools: []); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ChatMessage userMessage = new(ChatRole.User, "Get server forecast for Seattle for 5 days"); List updates = []; @@ -474,7 +474,7 @@ public async Task ClientToolCallWithCustomArgumentsAsync() await this.SetupTestServerAsync(); var chatClient = new AGUIChatClient(this._client!, "", null, ClientJsonContext.Default.Options); AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "assistant", description: "Test assistant", tools: [clientTool]); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ChatMessage userMessage = new(ChatRole.User, "Get client forecast for Portland with hourly data"); List updates = []; diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIEndpointRouteBuilderExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIEndpointRouteBuilderExtensionsTests.cs index 402451b061..e84876e719 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIEndpointRouteBuilderExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIEndpointRouteBuilderExtensionsTests.cs @@ -425,10 +425,11 @@ private sealed class MultiResponseAgent : AIAgent public override string? Description => "Agent that produces multiple text chunks"; - public override AgentThread GetNewThread() => new TestInMemoryAgentThread(); + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) => + new(new TestInMemoryAgentThread()); - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) => - new TestInMemoryAgentThread(serializedThread, jsonSerializerOptions); + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => + new(new TestInMemoryAgentThread(serializedThread, jsonSerializerOptions)); protected override Task RunCoreAsync(IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { @@ -514,10 +515,11 @@ private sealed class TestAgent : AIAgent public override string? Description => "Test agent"; - public override AgentThread GetNewThread() => new TestInMemoryAgentThread(); + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) => + new(new TestInMemoryAgentThread()); - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) => - new TestInMemoryAgentThread(serializedThread, jsonSerializerOptions); + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => + new(new TestInMemoryAgentThread(serializedThread, jsonSerializerOptions)); protected override Task RunCoreAsync(IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/TestAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/TestAgent.cs index e6824a2dd4..ed6f4aa132 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/TestAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/TestAgent.cs @@ -11,11 +11,11 @@ internal sealed class TestAgent(string name, string description) : AIAgent public override string? Description => description; - public override AgentThread GetNewThread() => new DummyAgentThread(); + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) => new(new DummyAgentThread()); - public override AgentThread DeserializeThread( + public override ValueTask DeserializeThreadAsync( JsonElement serializedThread, - JsonSerializerOptions? jsonSerializerOptions = null) => new DummyAgentThread(); + JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new DummyAgentThread()); protected override Task RunCoreAsync( IEnumerable messages, diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentExtensionsTests.cs index d039c95652..33d08351b9 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentExtensionsTests.cs @@ -324,10 +324,10 @@ public TestAgent(string? name, string? description, Exception exceptionToThrow) this._exceptionToThrow = exceptionToThrow; } - public override AgentThread GetNewThread() + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); public override string? Name { get; } diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentOptionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentOptionsTests.cs index 58cf5f718f..896a4ceba5 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentOptionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentOptionsTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.AI; using Moq; @@ -115,12 +117,11 @@ public void Clone_CreatesDeepCopyWithSameValues() const string Description = "Test description"; var tools = new List { AIFunctionFactory.Create(() => "test") }; - static ChatMessageStore ChatMessageStoreFactory( - ChatClientAgentOptions.ChatMessageStoreFactoryContext ctx) => new Mock().Object; + static ValueTask ChatMessageStoreFactoryAsync( + ChatClientAgentOptions.ChatMessageStoreFactoryContext ctx, CancellationToken ct) => new(new Mock().Object); - static AIContextProvider AIContextProviderFactory( - ChatClientAgentOptions.AIContextProviderFactoryContext ctx) => - new Mock().Object; + static ValueTask AIContextProviderFactoryAsync( + ChatClientAgentOptions.AIContextProviderFactoryContext ctx, CancellationToken ct) => new(new Mock().Object); var original = new ChatClientAgentOptions() { @@ -128,8 +129,8 @@ static AIContextProvider AIContextProviderFactory( Description = Description, ChatOptions = new() { Tools = tools }, Id = "test-id", - ChatMessageStoreFactory = ChatMessageStoreFactory, - AIContextProviderFactory = AIContextProviderFactory + ChatMessageStoreFactory = ChatMessageStoreFactoryAsync, + AIContextProviderFactory = AIContextProviderFactoryAsync }; // Act diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs index 29d3d3afee..18d20e3e76 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs @@ -241,8 +241,8 @@ public async Task RunAsyncRetrievesMessagesFromThreadWhenThreadStoresMessagesThr ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } }); - // Create a thread using the agent's GetNewThread method - var thread = agent.GetNewThread(); + // Create a thread using the agent's GetNewThreadAsync method + var thread = await agent.GetNewThreadAsync(); // Act await agent.RunAsync([new(ChatRole.User, "new message")], thread: thread); @@ -438,8 +438,8 @@ public async Task RunAsyncUsesChatMessageStoreWhenNoConversationIdReturnedByChat It.IsAny>(), It.IsAny(), It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")])); - Mock> mockFactory = new(); - mockFactory.Setup(f => f(It.IsAny())).Returns(new InMemoryChatMessageStore()); + Mock>> mockFactory = new(); + mockFactory.Setup(f => f(It.IsAny(), It.IsAny())).ReturnsAsync(new InMemoryChatMessageStore()); ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" }, @@ -447,7 +447,7 @@ public async Task RunAsyncUsesChatMessageStoreWhenNoConversationIdReturnedByChat }); // Act - ChatClientAgentThread? thread = agent.GetNewThread() as ChatClientAgentThread; + ChatClientAgentThread? thread = await agent.GetNewThreadAsync() as ChatClientAgentThread; await agent.RunAsync([new(ChatRole.User, "test")], thread); // Assert @@ -455,7 +455,7 @@ public async Task RunAsyncUsesChatMessageStoreWhenNoConversationIdReturnedByChat Assert.Equal(2, messageStore.Count); Assert.Equal("test", messageStore[0].Text); Assert.Equal("response", messageStore[1].Text); - mockFactory.Verify(f => f(It.IsAny()), Times.Once); + mockFactory.Verify(f => f(It.IsAny(), It.IsAny()), Times.Once); } /// @@ -477,7 +477,7 @@ public async Task RunAsyncUsesDefaultInMemoryChatMessageStoreWhenNoConversationI }); // Act - ChatClientAgentThread? thread = agent.GetNewThread() as ChatClientAgentThread; + ChatClientAgentThread? thread = await agent.GetNewThreadAsync() as ChatClientAgentThread; await agent.RunAsync([new(ChatRole.User, "test")], thread); // Assert @@ -509,8 +509,8 @@ public async Task RunAsyncUsesChatMessageStoreFactoryWhenProvidedAndNoConversati It.IsAny(), It.IsAny())).Returns(new ValueTask()); - Mock> mockFactory = new(); - mockFactory.Setup(f => f(It.IsAny())).Returns(mockChatMessageStore.Object); + Mock>> mockFactory = new(); + mockFactory.Setup(f => f(It.IsAny(), It.IsAny())).ReturnsAsync(mockChatMessageStore.Object); ChatClientAgent agent = new(mockService.Object, options: new() { @@ -519,7 +519,7 @@ public async Task RunAsyncUsesChatMessageStoreFactoryWhenProvidedAndNoConversati }); // Act - ChatClientAgentThread? thread = agent.GetNewThread() as ChatClientAgentThread; + ChatClientAgentThread? thread = await agent.GetNewThreadAsync() as ChatClientAgentThread; await agent.RunAsync([new(ChatRole.User, "test")], thread); // Assert @@ -538,7 +538,7 @@ public async Task RunAsyncUsesChatMessageStoreFactoryWhenProvidedAndNoConversati It.Is(x => x.RequestMessages.Count() == 1 && x.ChatMessageStoreMessages.Count() == 1 && x.ResponseMessages!.Count() == 1), It.IsAny()), Times.Once); - mockFactory.Verify(f => f(It.IsAny()), Times.Once); + mockFactory.Verify(f => f(It.IsAny(), It.IsAny()), Times.Once); } /// @@ -557,8 +557,8 @@ public async Task RunAsyncNotifiesChatMessageStoreOnFailureAsync() Mock mockChatMessageStore = new(); - Mock> mockFactory = new(); - mockFactory.Setup(f => f(It.IsAny())).Returns(mockChatMessageStore.Object); + Mock>> mockFactory = new(); + mockFactory.Setup(f => f(It.IsAny(), It.IsAny())).ReturnsAsync(mockChatMessageStore.Object); ChatClientAgent agent = new(mockService.Object, options: new() { @@ -567,7 +567,7 @@ public async Task RunAsyncNotifiesChatMessageStoreOnFailureAsync() }); // Act - ChatClientAgentThread? thread = agent.GetNewThread() as ChatClientAgentThread; + ChatClientAgentThread? thread = await agent.GetNewThreadAsync() as ChatClientAgentThread; await Assert.ThrowsAsync(() => agent.RunAsync([new(ChatRole.User, "test")], thread)); // Assert @@ -576,7 +576,7 @@ public async Task RunAsyncNotifiesChatMessageStoreOnFailureAsync() It.Is(x => x.RequestMessages.Count() == 1 && x.ResponseMessages == null && x.InvokeException!.Message == "Test Error"), It.IsAny()), Times.Once); - mockFactory.Verify(f => f(It.IsAny()), Times.Once); + mockFactory.Verify(f => f(It.IsAny(), It.IsAny()), Times.Once); } /// @@ -592,8 +592,8 @@ public async Task RunAsyncThrowsWhenChatMessageStoreFactoryProvidedAndConversati It.IsAny>(), It.IsAny(), It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]) { ConversationId = "ConvId" }); - Mock> mockFactory = new(); - mockFactory.Setup(f => f(It.IsAny())).Returns(new InMemoryChatMessageStore()); + Mock>> mockFactory = new(); + mockFactory.Setup(f => f(It.IsAny(), It.IsAny())).ReturnsAsync(new InMemoryChatMessageStore()); ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" }, @@ -601,7 +601,7 @@ public async Task RunAsyncThrowsWhenChatMessageStoreFactoryProvidedAndConversati }); // Act & Assert - ChatClientAgentThread? thread = agent.GetNewThread() as ChatClientAgentThread; + ChatClientAgentThread? thread = await agent.GetNewThreadAsync() as ChatClientAgentThread; var exception = await Assert.ThrowsAsync(() => agent.RunAsync([new(ChatRole.User, "test")], thread)); Assert.Equal("Only the ConversationId or MessageStore may be set, but not both and switching from one to another is not supported.", exception.Message); } @@ -649,10 +649,10 @@ public async Task RunAsyncInvokesAIContextProviderAndUsesResultAsync() .Setup(p => p.InvokedAsync(It.IsAny(), It.IsAny())) .Returns(new ValueTask()); - ChatClientAgent agent = new(mockService.Object, options: new() { AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] } }); + ChatClientAgent agent = new(mockService.Object, options: new() { AIContextProviderFactory = (_, _) => new(mockProvider.Object), ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] } }); // Act - var thread = agent.GetNewThread() as ChatClientAgentThread; + var thread = await agent.GetNewThreadAsync() as ChatClientAgentThread; await agent.RunAsync(requestMessages, thread); // Assert @@ -711,7 +711,7 @@ public async Task RunAsyncInvokesAIContextProviderWhenGetResponseFailsAsync() .Setup(p => p.InvokedAsync(It.IsAny(), It.IsAny())) .Returns(new ValueTask()); - ChatClientAgent agent = new(mockService.Object, options: new() { AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] } }); + ChatClientAgent agent = new(mockService.Object, options: new() { AIContextProviderFactory = (_, _) => new(mockProvider.Object), ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] } }); // Act await Assert.ThrowsAsync(() => agent.RunAsync(requestMessages)); @@ -757,7 +757,7 @@ public async Task RunAsyncInvokesAIContextProviderAndSucceedsWithEmptyAIContextA .Setup(p => p.InvokingAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new AIContext()); - ChatClientAgent agent = new(mockService.Object, options: new() { AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] } }); + ChatClientAgent agent = new(mockService.Object, options: new() { AIContextProviderFactory = (_, _) => new(mockProvider.Object), ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] } }); // Act await agent.RunAsync([new(ChatRole.User, "user message")]); @@ -2010,8 +2010,8 @@ public async Task RunStreamingAsyncUsesChatMessageStoreWhenNoConversationIdRetur It.IsAny>(), It.IsAny(), It.IsAny())).Returns(ToAsyncEnumerableAsync(returnUpdates)); - Mock> mockFactory = new(); - mockFactory.Setup(f => f(It.IsAny())).Returns(new InMemoryChatMessageStore()); + Mock>> mockFactory = new(); + mockFactory.Setup(f => f(It.IsAny(), It.IsAny())).ReturnsAsync(new InMemoryChatMessageStore()); ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" }, @@ -2019,7 +2019,7 @@ public async Task RunStreamingAsyncUsesChatMessageStoreWhenNoConversationIdRetur }); // Act - ChatClientAgentThread? thread = agent.GetNewThread() as ChatClientAgentThread; + ChatClientAgentThread? thread = await agent.GetNewThreadAsync() as ChatClientAgentThread; await agent.RunStreamingAsync([new(ChatRole.User, "test")], thread).ToListAsync(); // Assert @@ -2027,7 +2027,7 @@ public async Task RunStreamingAsyncUsesChatMessageStoreWhenNoConversationIdRetur Assert.Equal(2, messageStore.Count); Assert.Equal("test", messageStore[0].Text); Assert.Equal("what?", messageStore[1].Text); - mockFactory.Verify(f => f(It.IsAny()), Times.Once); + mockFactory.Verify(f => f(It.IsAny(), It.IsAny()), Times.Once); } /// @@ -2048,8 +2048,8 @@ public async Task RunStreamingAsyncThrowsWhenChatMessageStoreFactoryProvidedAndC It.IsAny>(), It.IsAny(), It.IsAny())).Returns(ToAsyncEnumerableAsync(returnUpdates)); - Mock> mockFactory = new(); - mockFactory.Setup(f => f(It.IsAny())).Returns(new InMemoryChatMessageStore()); + Mock>> mockFactory = new(); + mockFactory.Setup(f => f(It.IsAny(), It.IsAny())).ReturnsAsync(new InMemoryChatMessageStore()); ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" }, @@ -2057,7 +2057,7 @@ public async Task RunStreamingAsyncThrowsWhenChatMessageStoreFactoryProvidedAndC }); // Act & Assert - ChatClientAgentThread? thread = agent.GetNewThread() as ChatClientAgentThread; + ChatClientAgentThread? thread = await agent.GetNewThreadAsync() as ChatClientAgentThread; var exception = await Assert.ThrowsAsync(async () => await agent.RunStreamingAsync([new(ChatRole.User, "test")], thread).ToListAsync()); Assert.Equal("Only the ConversationId or MessageStore may be set, but not both and switching from one to another is not supported.", exception.Message); } @@ -2105,10 +2105,16 @@ public async Task RunStreamingAsyncInvokesAIContextProviderAndUsesResultAsync() .Setup(p => p.InvokedAsync(It.IsAny(), It.IsAny())) .Returns(new ValueTask()); - ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] }, AIContextProviderFactory = _ => mockProvider.Object }); + ChatClientAgent agent = new( + mockService.Object, + options: new() + { + ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] }, + AIContextProviderFactory = (_, _) => new(mockProvider.Object) + }); // Act - var thread = agent.GetNewThread() as ChatClientAgentThread; + var thread = await agent.GetNewThreadAsync() as ChatClientAgentThread; var updates = agent.RunStreamingAsync(requestMessages, thread); _ = await updates.ToAgentRunResponseAsync(); @@ -2168,7 +2174,13 @@ public async Task RunStreamingAsyncInvokesAIContextProviderWhenGetResponseFailsA .Setup(p => p.InvokedAsync(It.IsAny(), It.IsAny())) .Returns(new ValueTask()); - ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] }, AIContextProviderFactory = _ => mockProvider.Object }); + ChatClientAgent agent = new( + mockService.Object, + options: new() + { + ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] }, + AIContextProviderFactory = (_, _) => new(mockProvider.Object) + }); // Act await Assert.ThrowsAsync(async () => diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentThreadTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentThreadTests.cs index 48caef1b3d..57af3b6449 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentThreadTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentThreadTests.cs @@ -93,7 +93,7 @@ public void SetChatMessageStoreThrowsWhenConversationIdIsSet() #region Deserialize Tests [Fact] - public async Task VerifyDeserializeConstructorWithMessagesAsync() + public async Task VerifyDeserializeWithMessagesAsync() { // Arrange var json = JsonSerializer.Deserialize(""" @@ -103,7 +103,7 @@ public async Task VerifyDeserializeConstructorWithMessagesAsync() """, TestJsonSerializerContext.Default.JsonElement); // Act. - var thread = new ChatClientAgentThread(json); + var thread = await ChatClientAgentThread.DeserializeAsync(json); // Assert Assert.Null(thread.ConversationId); @@ -115,7 +115,7 @@ public async Task VerifyDeserializeConstructorWithMessagesAsync() } [Fact] - public async Task VerifyDeserializeConstructorWithIdAsync() + public async Task VerifyDeserializeWithIdAsync() { // Arrange var json = JsonSerializer.Deserialize(""" @@ -125,7 +125,7 @@ public async Task VerifyDeserializeConstructorWithIdAsync() """, TestJsonSerializerContext.Default.JsonElement); // Act - var thread = new ChatClientAgentThread(json); + var thread = await ChatClientAgentThread.DeserializeAsync(json); // Assert Assert.Equal("TestConvId", thread.ConversationId); @@ -133,7 +133,7 @@ public async Task VerifyDeserializeConstructorWithIdAsync() } [Fact] - public async Task VerifyDeserializeConstructorWithAIContextProviderAsync() + public async Task VerifyDeserializeWithAIContextProviderAsync() { // Arrange var json = JsonSerializer.Deserialize(""" @@ -145,7 +145,7 @@ public async Task VerifyDeserializeConstructorWithAIContextProviderAsync() Mock mockProvider = new(); // Act - var thread = new ChatClientAgentThread(json, aiContextProviderFactory: (_, _) => mockProvider.Object); + var thread = await ChatClientAgentThread.DeserializeAsync(json, aiContextProviderFactory: (_, _, _) => new(mockProvider.Object)); // Assert Assert.Null(thread.MessageStore); @@ -153,14 +153,14 @@ public async Task VerifyDeserializeConstructorWithAIContextProviderAsync() } [Fact] - public async Task DeserializeContructorWithInvalidJsonThrowsAsync() + public async Task DeserializeWithInvalidJsonThrowsAsync() { // Arrange var invalidJson = JsonSerializer.Deserialize("[42]", TestJsonSerializerContext.Default.JsonElement); var thread = new ChatClientAgentThread(); // Act & Assert - Assert.Throws(() => new ChatClientAgentThread(invalidJson)); + await Assert.ThrowsAsync(() => ChatClientAgentThread.DeserializeAsync(invalidJson)); } #endregion Deserialize Tests diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_DeserializeThreadTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_DeserializeThreadTests.cs index 04eabf36af..98e5b0ed1a 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_DeserializeThreadTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_DeserializeThreadTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; +using System.Threading.Tasks; using Microsoft.Extensions.AI; using Moq; @@ -12,7 +13,7 @@ namespace Microsoft.Agents.AI.UnitTests.ChatClient; public class ChatClientAgent_DeserializeThreadTests { [Fact] - public void DeserializeThread_UsesAIContextProviderFactory_IfProvided() + public async Task DeserializeThread_UsesAIContextProviderFactory_IfProvidedAsync() { // Arrange var mockChatClient = new Mock(); @@ -21,10 +22,10 @@ public void DeserializeThread_UsesAIContextProviderFactory_IfProvided() var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { ChatOptions = new() { Instructions = "Test instructions" }, - AIContextProviderFactory = _ => + AIContextProviderFactory = (_, _) => { factoryCalled = true; - return mockContextProvider.Object; + return new ValueTask(mockContextProvider.Object); } }); @@ -35,7 +36,7 @@ public void DeserializeThread_UsesAIContextProviderFactory_IfProvided() """, TestJsonSerializerContext.Default.JsonElement); // Act - var thread = agent.DeserializeThread(json); + var thread = await agent.DeserializeThreadAsync(json); // Assert Assert.True(factoryCalled, "AIContextProviderFactory was not called."); @@ -45,7 +46,7 @@ public void DeserializeThread_UsesAIContextProviderFactory_IfProvided() } [Fact] - public void DeserializeThread_UsesChatMessageStoreFactory_IfProvided() + public async Task DeserializeThread_UsesChatMessageStoreFactory_IfProvidedAsync() { // Arrange var mockChatClient = new Mock(); @@ -54,10 +55,10 @@ public void DeserializeThread_UsesChatMessageStoreFactory_IfProvided() var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { ChatOptions = new() { Instructions = "Test instructions" }, - ChatMessageStoreFactory = _ => + ChatMessageStoreFactory = (_, _) => { factoryCalled = true; - return mockMessageStore.Object; + return new ValueTask(mockMessageStore.Object); } }); @@ -68,7 +69,7 @@ public void DeserializeThread_UsesChatMessageStoreFactory_IfProvided() """, TestJsonSerializerContext.Default.JsonElement); // Act - var thread = agent.DeserializeThread(json); + var thread = await agent.DeserializeThreadAsync(json); // Assert Assert.True(factoryCalled, "ChatMessageStoreFactory was not called."); diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_GetNewThreadTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_GetNewThreadTests.cs index 628d738e72..e6cc7e90e9 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_GetNewThreadTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_GetNewThreadTests.cs @@ -1,17 +1,18 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Threading.Tasks; using Microsoft.Extensions.AI; using Moq; namespace Microsoft.Agents.AI.UnitTests.ChatClient; /// -/// Contains unit tests for the ChatClientAgent.GetNewThread methods. +/// Contains unit tests for the ChatClientAgent.GetNewThreadAsync methods. /// public class ChatClientAgent_GetNewThreadTests { [Fact] - public void GetNewThread_UsesAIContextProviderFactory_IfProvided() + public async Task GetNewThread_UsesAIContextProviderFactory_IfProvidedAsync() { // Arrange var mockChatClient = new Mock(); @@ -20,15 +21,15 @@ public void GetNewThread_UsesAIContextProviderFactory_IfProvided() var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { ChatOptions = new() { Instructions = "Test instructions" }, - AIContextProviderFactory = _ => + AIContextProviderFactory = (_, _) => { factoryCalled = true; - return mockContextProvider.Object; + return new ValueTask(mockContextProvider.Object); } }); // Act - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); // Assert Assert.True(factoryCalled, "AIContextProviderFactory was not called."); @@ -38,7 +39,7 @@ public void GetNewThread_UsesAIContextProviderFactory_IfProvided() } [Fact] - public void GetNewThread_UsesChatMessageStoreFactory_IfProvided() + public async Task GetNewThread_UsesChatMessageStoreFactory_IfProvidedAsync() { // Arrange var mockChatClient = new Mock(); @@ -47,15 +48,15 @@ public void GetNewThread_UsesChatMessageStoreFactory_IfProvided() var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { ChatOptions = new() { Instructions = "Test instructions" }, - ChatMessageStoreFactory = _ => + ChatMessageStoreFactory = (_, _) => { factoryCalled = true; - return mockMessageStore.Object; + return new ValueTask(mockMessageStore.Object); } }); // Act - var thread = agent.GetNewThread(); + var thread = await agent.GetNewThreadAsync(); // Assert Assert.True(factoryCalled, "ChatMessageStoreFactory was not called."); @@ -65,7 +66,7 @@ public void GetNewThread_UsesChatMessageStoreFactory_IfProvided() } [Fact] - public void GetNewThread_UsesChatMessageStore_FromTypedOverload() + public async Task GetNewThread_UsesChatMessageStore_FromTypedOverloadAsync() { // Arrange var mockChatClient = new Mock(); @@ -73,7 +74,7 @@ public void GetNewThread_UsesChatMessageStore_FromTypedOverload() var agent = new ChatClientAgent(mockChatClient.Object); // Act - var thread = agent.GetNewThread(mockMessageStore.Object); + var thread = await agent.GetNewThreadAsync(mockMessageStore.Object); // Assert Assert.IsType(thread); @@ -82,7 +83,7 @@ public void GetNewThread_UsesChatMessageStore_FromTypedOverload() } [Fact] - public void GetNewThread_UsesConversationId_FromTypedOverload() + public async Task GetNewThread_UsesConversationId_FromTypedOverloadAsync() { // Arrange var mockChatClient = new Mock(); @@ -90,7 +91,7 @@ public void GetNewThread_UsesConversationId_FromTypedOverload() var agent = new ChatClientAgent(mockChatClient.Object); // Act - var thread = agent.GetNewThread(TestConversationId); + var thread = await agent.GetNewThreadAsync(TestConversationId); // Assert Assert.IsType(thread); diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_RunWithCustomOptionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_RunWithCustomOptionsTests.cs index 85cb4cf0b4..ee84928ba5 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_RunWithCustomOptionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_RunWithCustomOptionsTests.cs @@ -30,7 +30,7 @@ public async Task RunAsync_WithThreadAndOptions_CallsBaseMethodAsync() It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "Response")])); ChatClientAgent agent = new(mockChatClient.Object); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ChatClientAgentRunOptions options = new(); // Act @@ -59,7 +59,7 @@ public async Task RunAsync_WithStringMessageAndOptions_CallsBaseMethodAsync() It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "Response")])); ChatClientAgent agent = new(mockChatClient.Object); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ChatClientAgentRunOptions options = new(); // Act @@ -88,7 +88,7 @@ public async Task RunAsync_WithChatMessageAndOptions_CallsBaseMethodAsync() It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "Response")])); ChatClientAgent agent = new(mockChatClient.Object); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ChatMessage message = new(ChatRole.User, "Test message"); ChatClientAgentRunOptions options = new(); @@ -118,7 +118,7 @@ public async Task RunAsync_WithMessagesCollectionAndOptions_CallsBaseMethodAsync It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "Response")])); ChatClientAgent agent = new(mockChatClient.Object); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); IEnumerable messages = [new(ChatRole.User, "Message 1"), new(ChatRole.User, "Message 2")]; ChatClientAgentRunOptions options = new(); @@ -179,7 +179,7 @@ public async Task RunStreamingAsync_WithThreadAndOptions_CallsBaseMethodAsync() It.IsAny())).Returns(GetAsyncUpdatesAsync()); ChatClientAgent agent = new(mockChatClient.Object); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ChatClientAgentRunOptions options = new(); // Act @@ -211,7 +211,7 @@ public async Task RunStreamingAsync_WithStringMessageAndOptions_CallsBaseMethodA It.IsAny())).Returns(GetAsyncUpdatesAsync()); ChatClientAgent agent = new(mockChatClient.Object); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ChatClientAgentRunOptions options = new(); // Act @@ -243,7 +243,7 @@ public async Task RunStreamingAsync_WithChatMessageAndOptions_CallsBaseMethodAsy It.IsAny())).Returns(GetAsyncUpdatesAsync()); ChatClientAgent agent = new(mockChatClient.Object); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ChatMessage message = new(ChatRole.User, "Test message"); ChatClientAgentRunOptions options = new(); @@ -276,7 +276,7 @@ public async Task RunStreamingAsync_WithMessagesCollectionAndOptions_CallsBaseMe It.IsAny())).Returns(GetAsyncUpdatesAsync()); ChatClientAgent agent = new(mockChatClient.Object); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); IEnumerable messages = [new ChatMessage(ChatRole.User, "Message 1"), new ChatMessage(ChatRole.User, "Message 2")]; ChatClientAgentRunOptions options = new(); @@ -324,7 +324,7 @@ public async Task RunAsyncOfT_WithThreadAndOptions_CallsBaseMethodAsync() It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, """{"id":2, "fullName":"Tigger", "species":"Tiger"}""")])); ChatClientAgent agent = new(mockChatClient.Object); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ChatClientAgentRunOptions options = new(); // Act @@ -354,7 +354,7 @@ public async Task RunAsyncOfT_WithStringMessageAndOptions_CallsBaseMethodAsync() It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, """{"id":2, "fullName":"Tigger", "species":"Tiger"}""")])); ChatClientAgent agent = new(mockChatClient.Object); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ChatClientAgentRunOptions options = new(); // Act @@ -384,7 +384,7 @@ public async Task RunAsyncOfT_WithChatMessageAndOptions_CallsBaseMethodAsync() It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, """{"id":2, "fullName":"Tigger", "species":"Tiger"}""")])); ChatClientAgent agent = new(mockChatClient.Object); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); ChatMessage message = new(ChatRole.User, "Test message"); ChatClientAgentRunOptions options = new(); @@ -415,7 +415,7 @@ public async Task RunAsyncOfT_WithMessagesCollectionAndOptions_CallsBaseMethodAs It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, """{"id":2, "fullName":"Tigger", "species":"Tiger"}""")])); ChatClientAgent agent = new(mockChatClient.Object); - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); IEnumerable messages = [new(ChatRole.User, "Message 1"), new(ChatRole.User, "Message 2")]; ChatClientAgentRunOptions options = new(); diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/TestAIAgent.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/TestAIAgent.cs index 3d2cdff868..afeb81baa5 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/TestAIAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/TestAIAgent.cs @@ -24,11 +24,11 @@ internal sealed class TestAIAgent : AIAgent public override string? Description => this.DescriptionFunc?.Invoke() ?? base.Description; - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) => - this.DeserializeThreadFunc(serializedThread, jsonSerializerOptions); + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => + new(this.DeserializeThreadFunc(serializedThread, jsonSerializerOptions)); - public override AgentThread GetNewThread() => - this.GetNewThreadFunc(); + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) => + new(this.GetNewThreadFunc()); protected override Task RunCoreAsync(IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) => this.RunAsyncFunc(messages, thread, options, cancellationToken); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentWorkflowBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentWorkflowBuilderTests.cs index c45ef8726e..938d1da927 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentWorkflowBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentWorkflowBuilderTests.cs @@ -135,11 +135,11 @@ private class DoubleEchoAgent(string name) : AIAgent { public override string Name => name; - public override AgentThread GetNewThread() - => new DoubleEchoAgentThread(); + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) + => new(new DoubleEchoAgentThread()); - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) - => new DoubleEchoAgentThread(); + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) + => new(new DoubleEchoAgentThread()); protected override Task RunCoreAsync( IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) => diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutionTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutionTests.cs index b3e53da6f8..188622de51 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutionTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutionTests.cs @@ -144,10 +144,10 @@ public SimpleTestAgent(string name) public override string Name { get; } - public override AgentThread GetNewThread() => new SimpleTestAgentThread(); + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) => new(new SimpleTestAgentThread()); - public override AgentThread DeserializeThread(System.Text.Json.JsonElement serializedThread, - System.Text.Json.JsonSerializerOptions? jsonSerializerOptions = null) => new SimpleTestAgentThread(); + public override ValueTask DeserializeThreadAsync(System.Text.Json.JsonElement serializedThread, + System.Text.Json.JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new SimpleTestAgentThread()); protected override Task RunCoreAsync( IEnumerable messages, diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs index 5eb8696221..299c6af903 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs @@ -24,10 +24,10 @@ private sealed class TestExecutor() : Executor("TestExecutor") private sealed class TestAgent : AIAgent { - public override AgentThread GetNewThread() + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); protected override Task RunCoreAsync(IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) => diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/06_GroupChat_Workflow.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/06_GroupChat_Workflow.cs index a351c45b20..342113e658 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/06_GroupChat_Workflow.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/06_GroupChat_Workflow.cs @@ -60,11 +60,11 @@ internal sealed class HelloAgent(string id = nameof(HelloAgent)) : AIAgent protected override string? IdCore => id; public override string? Name => id; - public override AgentThread GetNewThread() - => new HelloAgentThread(); + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) + => new(new HelloAgentThread()); - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) - => new HelloAgentThread(); + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) + => new(new HelloAgentThread()); protected override async Task RunCoreAsync(IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/07_GroupChat_Workflow_HostAsAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/07_GroupChat_Workflow_HostAsAgent.cs index 1739fe7a34..17c37772dd 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/07_GroupChat_Workflow_HostAsAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/07_GroupChat_Workflow_HostAsAgent.cs @@ -19,7 +19,7 @@ public static async ValueTask RunAsync(TextWriter writer, IWorkflowExecutionEnvi for (int i = 0; i < numIterations; i++) { - AgentThread thread = agent.GetNewThread(); + AgentThread thread = await agent.GetNewThreadAsync(); await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(thread).ConfigureAwait(false)) { if (update.RawRepresentation is WorkflowEvent) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/10_Sequential_HostAsAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/10_Sequential_HostAsAgent.cs index fc23d44155..acd392a4d4 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/10_Sequential_HostAsAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/10_Sequential_HostAsAgent.cs @@ -21,7 +21,7 @@ public static async ValueTask RunAsync(TextWriter writer, IWorkflowExecutionEnvi { AIAgent hostAgent = WorkflowInstance.AsAgent("echo-workflow", "EchoW", executionEnvironment: executionEnvironment); - AgentThread thread = hostAgent.GetNewThread(); + AgentThread thread = await hostAgent.GetNewThreadAsync(); foreach (string input in inputs) { AgentRunResponse response; diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/11_Concurrent_HostAsAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/11_Concurrent_HostAsAgent.cs index d47b90223c..09f07267b6 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/11_Concurrent_HostAsAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/11_Concurrent_HostAsAgent.cs @@ -33,7 +33,7 @@ public static async ValueTask RunAsync(TextWriter writer, IWorkflowExecutionEnvi { AIAgent hostAgent = WorkflowInstance.AsAgent("echo-workflow", "EchoW", executionEnvironment: executionEnvironment); - AgentThread thread = hostAgent.GetNewThread(); + AgentThread thread = await hostAgent.GetNewThreadAsync(); foreach (string input in inputs) { AgentRunResponse response; diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/12_HandOff_HostAsAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/12_HandOff_HostAsAgent.cs index c319a0ac32..28e3d7c48c 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/12_HandOff_HostAsAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/12_HandOff_HostAsAgent.cs @@ -69,7 +69,7 @@ public static async ValueTask RunAsync(TextWriter writer, IWorkflowExecutionEnvi { AIAgent hostAgent = WorkflowInstance.AsAgent("echo-workflow", "EchoW", executionEnvironment: executionEnvironment); - AgentThread thread = hostAgent.GetNewThread(); + AgentThread thread = await hostAgent.GetNewThreadAsync(); foreach (string input in inputs) { AgentRunResponse response; diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SpecializedExecutorSmokeTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SpecializedExecutorSmokeTests.cs index ed9af701c6..2bf8ac52d1 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SpecializedExecutorSmokeTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/SpecializedExecutorSmokeTests.cs @@ -51,11 +51,11 @@ static ChatMessage ToMessage(string text) return result; } - public override AgentThread GetNewThread() - => new TestAgentThread(); + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) + => new(new TestAgentThread()); - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) - => new TestAgentThread(); + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) + => new(new TestAgentThread()); public static TestAIAgent FromStrings(params string[] messages) => new(ToChatMessages(messages)); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestEchoAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestEchoAgent.cs index 422d7a16ba..73a14568e5 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestEchoAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestEchoAgent.cs @@ -16,15 +16,13 @@ internal class TestEchoAgent(string? id = null, string? name = null, string? pre protected override string? IdCore => id; public override string? Name => name ?? base.Name; - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) + public override async ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { - return serializedThread.Deserialize(jsonSerializerOptions) ?? this.GetNewThread(); + return serializedThread.Deserialize(jsonSerializerOptions) ?? await this.GetNewThreadAsync(cancellationToken); } - public override AgentThread GetNewThread() - { - return new EchoAgentThread(); - } + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) => + new(new EchoAgentThread()); private static ChatMessage UpdateThread(ChatMessage message, InMemoryAgentThread? thread = null) { diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs index be3c96d9f5..32308d8b56 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs @@ -41,14 +41,14 @@ public Thread(JsonElement serializedThread, JsonSerializerOptions? jsonSerialize { } } - public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null) + public override ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { - return new Thread(serializedThread, jsonSerializerOptions); + return new(new Thread(serializedThread, jsonSerializerOptions)); } - public override AgentThread GetNewThread() + public override ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) { - return new Thread(); + return new(new Thread()); } protected override async Task RunCoreAsync(IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)