diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index 5da4e166d5..95812435d8 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -60,6 +60,7 @@ + diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step20_BackgroundResponsesWithToolsAndPersistence/AgentFunctions.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step20_BackgroundResponsesWithToolsAndPersistence/AgentFunctions.cs new file mode 100644 index 0000000000..ee39b8351f --- /dev/null +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step20_BackgroundResponsesWithToolsAndPersistence/AgentFunctions.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.ComponentModel; +using Microsoft.Extensions.AI; + +internal sealed class AgentFunctions +{ + /// + /// Researches relevant space facts. + /// + /// The specific space-related topic to research (e.g., "galaxy formation", "space travel", "astronaut training"). + /// Research findings about the specified space topic. + [Description("Researches relevant space facts and scientific information for writing a science fiction novel")] + public async Task ResearchSpaceFactsAsync(string topic) + { + Console.WriteLine($"[ResearchSpaceFacts] Researching topic: {topic}"); + + // Simulate a research operation + await Task.Delay(TimeSpan.FromSeconds(10)); + + string result = topic.ToUpperInvariant() switch + { + var t when t.Contains("GALAXY") => "Research findings: Galaxies contain billions of stars. Uncharted galaxies may have unique stellar formations, exotic matter, and unexplored phenomena like dark energy concentrations.", + var t when t.Contains("SPACE") || t.Contains("TRAVEL") => "Research findings: Interstellar travel requires advanced propulsion systems. Challenges include radiation exposure, life support, and navigation through unknown space.", + var t when t.Contains("ASTRONAUT") => "Research findings: Astronauts undergo rigorous training in zero-gravity environments, emergency protocols, spacecraft systems, and team dynamics for long-duration missions.", + _ => $"Research findings: General space exploration facts related to {topic}. Deep space missions require advanced technology, crew resilience, and contingency planning for unknown scenarios." + }; + + Console.WriteLine("[ResearchSpaceFacts] Research complete"); + return result; + } + + /// + /// Generates detailed character profiles for the main characters. + /// + /// Detailed character profiles including background, personality traits, and role in the story. + [Description("Generates character profiles for the main astronaut characters in the novel")] + public async Task> GenerateCharacterProfilesAsync() + { + Console.WriteLine("[GenerateCharacterProfiles] Generating character profiles..."); + + // Simulate a character generation operation + await Task.Delay(TimeSpan.FromSeconds(10)); + + string[] profiles = [ + "Captain Elena Voss: A seasoned mission commander with 15 years of experience. Strong-willed and decisive, she struggles with the weight of responsibility for her crew. Former military pilot turned astronaut.", + "Dr. James Chen: Chief science officer and astrophysicist. Brilliant but socially awkward, he finds solace in data and discovery. His curiosity often pushes the mission into uncharted territory.", + "Lieutenant Maya Torres: Navigation specialist and youngest crew member. Optimistic and tech-savvy, she brings fresh perspective and innovative problem-solving to challenges.", + "Commander Marcus Rivera: Chief engineer with expertise in spacecraft systems. Pragmatic and resourceful, he can fix almost anything with limited resources. Values crew safety above all.", + "Dr. Amara Okafor: Medical officer and psychologist. Empathetic and observant, she helps maintain crew morale and mental health during the long journey. Expert in space medicine." + ]; + + Console.WriteLine($"[GenerateCharacterProfiles] Generated {profiles.Length} character profiles"); + return profiles; + } + + /// + /// Returns the functions as AI tools. + /// + public IEnumerable AsAITools() + { + yield return AIFunctionFactory.Create(this.ResearchSpaceFactsAsync); + yield return AIFunctionFactory.Create(this.GenerateCharacterProfilesAsync); + } +} diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step20_BackgroundResponsesWithToolsAndPersistence/AgentStateStore.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step20_BackgroundResponsesWithToolsAndPersistence/AgentStateStore.cs new file mode 100644 index 0000000000..66f90301ff --- /dev/null +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step20_BackgroundResponsesWithToolsAndPersistence/AgentStateStore.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Text.Json; +using Microsoft.Agents.AI; +using Microsoft.Extensions.AI; + +internal sealed class AgentStateStore +{ + private readonly Dictionary _agentStateStore = new(); + + public void PersistAgentState(AgentThread thread, object? continuationToken) + { + this._agentStateStore["thread"] = thread.Serialize(); + this._agentStateStore["continuationToken"] = JsonSerializer.SerializeToElement(continuationToken, AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ResponseContinuationToken))); + } + + public void RestoreAgentState(AIAgent agent, out AgentThread thread, out object? continuationToken) + { + JsonElement serializedThread = this._agentStateStore["thread"] ?? throw new InvalidOperationException("No serialized thread found in state store."); + JsonElement? serializedToken = this._agentStateStore["continuationToken"]; + + thread = agent.DeserializeThread(serializedThread); + continuationToken = serializedToken?.Deserialize(AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ResponseContinuationToken))); + } +} diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step20_BackgroundResponsesWithToolsAndPersistence/Agent_Step20_BackgroundResponsesWithToolsAndPersistence.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step20_BackgroundResponsesWithToolsAndPersistence/Agent_Step20_BackgroundResponsesWithToolsAndPersistence.csproj new file mode 100644 index 0000000000..4735f4a7a0 --- /dev/null +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step20_BackgroundResponsesWithToolsAndPersistence/Agent_Step20_BackgroundResponsesWithToolsAndPersistence.csproj @@ -0,0 +1,20 @@ + + + + Exe + net9.0 + + enable + enable + + + + + + + + + + + + diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step20_BackgroundResponsesWithToolsAndPersistence/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step20_BackgroundResponsesWithToolsAndPersistence/Program.cs new file mode 100644 index 0000000000..c2d5de9b42 --- /dev/null +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step20_BackgroundResponsesWithToolsAndPersistence/Program.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft. All rights reserved. + +// This sample demonstrates how to use background responses with ChatClientAgent and AzureOpenAI Responses for long-running operations. +// It shows polling for completion using continuation tokens, function calling during background operations, +// and persisting/restoring agent state between polling cycles. + +#pragma warning disable CA1050 // Declare types in namespaces + +using Azure.AI.OpenAI; +using Azure.Identity; +using Microsoft.Agents.AI; +using Microsoft.Extensions.AI; +using OpenAI; + +var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); +var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5"; + +var stateStore = new AgentStateStore(); + +AIAgent agent = new AzureOpenAIClient( + new Uri(endpoint), + new AzureCliCredential()) + .GetOpenAIResponseClient(deploymentName) + .CreateAIAgent( + name: "SpaceNovelWriter", + instructions: "You are a space novel writer. Always research relevant facts and generate character profiles for the main characters before writing novels." + + "Write complete chapters without asking for approval or feedback. Do not ask the user about tone, style, pace, or format preferences - just write the novel based on the request.", + tools: [.. new AgentFunctions().AsAITools()]); + +// Enable background responses (only supported by {Azure}OpenAI Responses at this time). +AgentRunOptions options = new() { AllowBackgroundResponses = true }; + +AgentThread thread = agent.GetNewThread(); + +// 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); + +// Poll for background responses until complete. +while (response.ContinuationToken is not null) +{ + stateStore.PersistAgentState(thread, response.ContinuationToken); + + await Task.Delay(TimeSpan.FromSeconds(10)); + + stateStore.RestoreAgentState(agent, out thread, out object? continuationToken); + + options.ContinuationToken = continuationToken; + response = await agent.RunAsync(thread, options); +} + +Console.WriteLine(response.Text); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step20_BackgroundResponsesWithToolsAndPersistence/README.md b/dotnet/samples/GettingStarted/Agents/Agent_Step20_BackgroundResponsesWithToolsAndPersistence/README.md new file mode 100644 index 0000000000..6472ce81e4 --- /dev/null +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step20_BackgroundResponsesWithToolsAndPersistence/README.md @@ -0,0 +1,25 @@ +# What This Sample Shows + +This sample demonstrates how to use background responses with ChatCompletionAgent and AzureOpenAI Responses for long-running operations. Background responses support: + +- **Polling for completion** - Non-streaming APIs can start a background operation and return a continuation token. Poll with the token until the response completes. +- **Function calling** - Functions can be called during background operations. +- **State persistence** - Thread and continuation token can be persisted and restored between polling cycles. + +> **Note:** Background responses are currently only supported by OpenAI Responses. + +For more information, see the [official documentation](https://learn.microsoft.com/en-us/agent-framework/user-guide/agents/agent-background-responses?pivots=programming-language-csharp). + +# Prerequisites + +Before you begin, ensure you have the following prerequisites: + +- .NET 8.0 SDK or later +- OpenAI api key + +Set the following environment variables: + +```powershell +$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" # Replace with your Azure OpenAI resource endpoint +$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5" # Optional, defaults to gpt-5 +``` diff --git a/dotnet/samples/GettingStarted/Agents/README.md b/dotnet/samples/GettingStarted/Agents/README.md index 2b8f7550c9..bf5c291f9e 100644 --- a/dotnet/samples/GettingStarted/Agents/README.md +++ b/dotnet/samples/GettingStarted/Agents/README.md @@ -45,6 +45,7 @@ Before you begin, ensure you have the following prerequisites: |[Background responses](./Agent_Step17_BackgroundResponses/)|This sample demonstrates how to use background responses for long-running operations with polling and resumption support| |[Adding RAG with text search](./Agent_Step18_TextSearchRag/)|This sample demonstrates how to enrich agent responses with retrieval augmented generation using the text search provider| |[Using Mem0-backed memory](./Agent_Step19_Mem0Provider/)|This sample demonstrates how to use the Mem0Provider to persist and recall memories across conversations| +|[Background responses with tools and persistence](./Agent_Step20_BackgroundResponsesWithToolsAndPersistence/)|This sample demonstrates advanced background response scenarios including function calling during background operations and state persistence| ## Running the samples from the console