From bbea3c00f8ab7f37245c2e99d4923a3de2def2dd Mon Sep 17 00:00:00 2001
From: westey <164392973+westey-m@users.noreply.github.com>
Date: Thu, 28 Aug 2025 15:14:25 +0100
Subject: [PATCH] .NET: Add more console based getting started samples (#507)
* Add more console based getting started samples
* Simplify function calling and approavls samples and some minor renaming based on PR feedback.
* Cover streaming with comments for aprovals sample.
* Remove extra line break.
* Update getting started samples list in readme.
* Address PR comments
* Address PR comments.
---
dotnet/agent-framework-dotnet.slnx | 15 +-
dotnet/demos/MinimalConsole/Program.cs | 8 +-
.../Steps/Step01_ChatClientAgent_Running.cs | 187 ----------------
...ep02_ChatClientAgent_UsingFunctionTools.cs | 154 -------------
...p04_ChatClientAgent_DependencyInjection.cs | 68 ------
.../Steps/Step05_ChatClientAgent_Telemetry.cs | 56 -----
...tep06_ChatClientAgent_StructuredOutputs.cs | 77 -------
...p08_ChatClientAgent_SuspendResumeThread.cs | 68 ------
...ntAgent_UsingFunctionToolsWithApprovals.cs | 205 ------------------
dotnet/samples/GettingStartedSteps/README.md | 9 +-
.../Step01_ChatClientAgent_Running/Program.cs | 12 +-
.../Program.cs | 10 +-
...tClientAgent_MultiturnConversation.csproj} | 0
.../Program.cs | 35 +++
..._ChatClientAgent_UsingFunctionTools.csproj | 23 ++
.../Program.cs | 68 ++++++
...ent_UsingFunctionToolsWithApprovals.csproj | 24 ++
.../Program.cs | 76 +++++++
...05_ChatClientAgent_StructuredOutput.csproj | 23 ++
.../Program.cs | 46 ++++
...tClientAgent_PersistedConversations.csproj | 23 ++
.../Program.cs} | 127 +++++------
...atClientAgent_3rdPartyThreadStorage.csproj | 25 +++
.../Program.cs | 45 ++++
.../Step08_ChatClientAgent_Telemetry.csproj | 25 +++
.../Program.cs | 99 +++++++++
...ChatClientAgent_DependencyInjection.csproj | 24 ++
dotnet/src/Shared/Demos/SampleEnvironment.cs | 2 +-
28 files changed, 634 insertions(+), 900 deletions(-)
delete mode 100644 dotnet/samples/GettingStarted/Steps/Step01_ChatClientAgent_Running.cs
delete mode 100644 dotnet/samples/GettingStarted/Steps/Step02_ChatClientAgent_UsingFunctionTools.cs
delete mode 100644 dotnet/samples/GettingStarted/Steps/Step04_ChatClientAgent_DependencyInjection.cs
delete mode 100644 dotnet/samples/GettingStarted/Steps/Step05_ChatClientAgent_Telemetry.cs
delete mode 100644 dotnet/samples/GettingStarted/Steps/Step06_ChatClientAgent_StructuredOutputs.cs
delete mode 100644 dotnet/samples/GettingStarted/Steps/Step08_ChatClientAgent_SuspendResumeThread.cs
delete mode 100644 dotnet/samples/GettingStarted/Steps/Step10_ChatClientAgent_UsingFunctionToolsWithApprovals.cs
rename dotnet/samples/GettingStartedSteps/{Step02_ChatClientAgent_Multiturn => Step02_ChatClientAgent_MultiturnConversation}/Program.cs (71%)
rename dotnet/samples/GettingStartedSteps/{Step02_ChatClientAgent_Multiturn/Step02_ChatClientAgent_Multiturn.csproj => Step02_ChatClientAgent_MultiturnConversation/Step02_ChatClientAgent_MultiturnConversation.csproj} (100%)
create mode 100644 dotnet/samples/GettingStartedSteps/Step03_ChatClientAgent_UsingFunctionTools/Program.cs
create mode 100644 dotnet/samples/GettingStartedSteps/Step03_ChatClientAgent_UsingFunctionTools/Step03_ChatClientAgent_UsingFunctionTools.csproj
create mode 100644 dotnet/samples/GettingStartedSteps/Step04_ChatClientAgent_UsingFunctionToolsWithApprovals/Program.cs
create mode 100644 dotnet/samples/GettingStartedSteps/Step04_ChatClientAgent_UsingFunctionToolsWithApprovals/Step04_ChatClientAgent_UsingFunctionToolsWithApprovals.csproj
create mode 100644 dotnet/samples/GettingStartedSteps/Step05_ChatClientAgent_StructuredOutput/Program.cs
create mode 100644 dotnet/samples/GettingStartedSteps/Step05_ChatClientAgent_StructuredOutput/Step05_ChatClientAgent_StructuredOutput.csproj
create mode 100644 dotnet/samples/GettingStartedSteps/Step06_ChatClientAgent_PersistedConversations/Program.cs
create mode 100644 dotnet/samples/GettingStartedSteps/Step06_ChatClientAgent_PersistedConversations/Step06_ChatClientAgent_PersistedConversations.csproj
rename dotnet/samples/{GettingStarted/Steps/Step09_ChatClientAgent_3rdPartyThreadStorage.cs => GettingStartedSteps/Step07_ChatClientAgent_3rdPartyThreadStorage/Program.cs} (54%)
create mode 100644 dotnet/samples/GettingStartedSteps/Step07_ChatClientAgent_3rdPartyThreadStorage/Step07_ChatClientAgent_3rdPartyThreadStorage.csproj
create mode 100644 dotnet/samples/GettingStartedSteps/Step08_ChatClientAgent_Telemetry/Program.cs
create mode 100644 dotnet/samples/GettingStartedSteps/Step08_ChatClientAgent_Telemetry/Step08_ChatClientAgent_Telemetry.csproj
create mode 100644 dotnet/samples/GettingStartedSteps/Step09_ChatClientAgent_DependencyInjection/Program.cs
create mode 100644 dotnet/samples/GettingStartedSteps/Step09_ChatClientAgent_DependencyInjection/Step09_ChatClientAgent_DependencyInjection.csproj
diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 136c1a168d..16abdc9479 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -21,7 +21,14 @@
-
+
+
+
+
+
+
+
+
@@ -128,9 +135,9 @@
-
-
+
+
@@ -152,9 +159,9 @@
-
+
diff --git a/dotnet/demos/MinimalConsole/Program.cs b/dotnet/demos/MinimalConsole/Program.cs
index 0ebd6a3833..3daf37b8be 100644
--- a/dotnet/demos/MinimalConsole/Program.cs
+++ b/dotnet/demos/MinimalConsole/Program.cs
@@ -8,17 +8,17 @@ using Microsoft.Extensions.AI;
using Microsoft.Extensions.AI.Agents;
using OpenAI;
-var azureOpenAIEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
-var azureOpenAIDeploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+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-4o-mini";
[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
=> $"The weather in {location} is cloudy with a high of 15°C.";
AIAgent agent = new AzureOpenAIClient(
- new Uri(azureOpenAIEndpoint),
+ new Uri(endpoint),
new AzureCliCredential())
- .GetChatClient(azureOpenAIDeploymentName)
+ .GetChatClient(deploymentName)
.CreateAIAgent(
instructions: "You are a helpful assistant, you can help the user with weather information.",
tools: [AIFunctionFactory.Create(GetWeather)]);
diff --git a/dotnet/samples/GettingStarted/Steps/Step01_ChatClientAgent_Running.cs b/dotnet/samples/GettingStarted/Steps/Step01_ChatClientAgent_Running.cs
deleted file mode 100644
index b593c24f6a..0000000000
--- a/dotnet/samples/GettingStarted/Steps/Step01_ChatClientAgent_Running.cs
+++ /dev/null
@@ -1,187 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using Microsoft.Extensions.AI;
-using Microsoft.Extensions.AI.Agents;
-
-namespace Steps;
-
-///
-/// Provides test methods to demonstrate the usage of chat agents with different interaction models.
-///
-/// This class contains examples of using to showcase scenarios with and without conversation history.
-/// Each test method demonstrates how to configure and interact with the agents, including handling user input and displaying responses.
-///
-public sealed class Step01_ChatClientAgent_Running(ITestOutputHelper output) : AgentSample(output)
-{
- private const string ParrotName = "Parrot";
- private const string ParrotInstructions = "Repeat the user message in the voice of a pirate and then end with a parrot sound.";
-
- private const string JokerName = "Joker";
- private const string JokerInstructions = "You are good at telling jokes.";
-
- ///
- /// Demonstrate the most basic Agent case, where we do not have a server-side agent
- /// but just an in-memory agent, backed by an inference service,
- /// and we are invoking with text input, and getting back a text response.
- ///
- [Theory]
- [InlineData(ChatClientProviders.AzureOpenAI)]
- [InlineData(ChatClientProviders.OpenAIChatCompletion)]
- [InlineData(ChatClientProviders.OpenAIResponses)]
- public async Task RunBasic(ChatClientProviders provider)
- {
- // Get the chat client to communicate with the inference service backing our agent.
- // Any implementation of Microsoft.Extensions.AI.Agents.IChatClient can be used with the ChatClientAgent.
- // See the Providers folder for examples on how to create chat clients for some sample providers.
- IChatClient chatClient = base.GetChatClient(provider);
-
- // Define the agent
- AIAgent agent = new ChatClientAgent(chatClient, ParrotInstructions);
-
- // Invoke the agent and output the text result.
- Console.WriteLine(await agent.RunAsync("Fortune favors the bold."));
- }
-
- ///
- /// Demonstrate the usage of where each invocation is
- /// a unique interaction with no conversation history between them.
- ///
- [Theory]
- [InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
- [InlineData(ChatClientProviders.AzureOpenAI)]
- [InlineData(ChatClientProviders.OpenAIAssistant)]
- [InlineData(ChatClientProviders.OpenAIChatCompletion)]
- [InlineData(ChatClientProviders.OpenAIResponses)]
- public async Task RunWithoutThread(ChatClientProviders provider)
- {
- // Define the options for the chat client agent.
- var agentOptions = new ChatClientAgentOptions(name: ParrotName, instructions: ParrotInstructions);
-
- // Create the server-side agent Id when applicable (depending on the provider).
- agentOptions.Id = await base.AgentCreateAsync(provider, agentOptions);
-
- // Get the chat client to use for the agent.
- using var chatClient = base.GetChatClient(provider, agentOptions);
-
- // Define the agent
- var agent = new ChatClientAgent(chatClient, agentOptions);
-
- // Respond to user input
- await RunAgentAsync("Fortune favors the bold.");
- await RunAgentAsync("I came, I saw, I conquered.");
- await RunAgentAsync("Practice makes perfect.");
-
- // Local function to invoke agent and display the conversation messages.
- async Task RunAgentAsync(string input)
- {
- this.WriteUserMessage(input);
-
- var response = await agent.RunAsync(input);
- this.WriteResponseOutput(response);
- }
-
- // Clean up the server-side agent after use when applicable (depending on the provider).
- await base.AgentCleanUpAsync(provider, agent);
- }
-
- ///
- /// Demonstrate the usage of where a conversation history is maintained.
- ///
- [Theory]
- [InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
- [InlineData(ChatClientProviders.AzureOpenAI)]
- [InlineData(ChatClientProviders.OpenAIAssistant)]
- [InlineData(ChatClientProviders.OpenAIResponses_InMemoryMessageThread)]
- [InlineData(ChatClientProviders.OpenAIResponses_ConversationIdThread)]
- public async Task RunWithThread(ChatClientProviders provider)
- {
- // Define the options for the chat client agent.
- var agentOptions = new ChatClientAgentOptions
- {
- Name = JokerName,
- Instructions = JokerInstructions,
-
- // Get chat options based on the store type, if needed.
- ChatOptions = base.GetChatOptions(provider),
- };
-
- // Create the server-side agent Id when applicable (depending on the provider).
- agentOptions.Id = await base.AgentCreateAsync(provider, agentOptions);
-
- // Get the chat client to use for the agent.
- using var chatClient = base.GetChatClient(provider, agentOptions);
-
- // Define the agent
- var agent = new ChatClientAgent(chatClient, agentOptions);
-
- // Start a new thread for the agent conversation.
- AgentThread thread = agent.GetNewThread();
-
- // Respond to user input
- await RunAgentAsync("Tell me a joke about a pirate.");
- await RunAgentAsync("Now add some emojis to the joke.");
-
- // Local function to invoke agent and display the conversation messages for the thread.
- async Task RunAgentAsync(string input)
- {
- this.WriteUserMessage(input);
-
- var response = await agent.RunAsync(input, thread);
-
- this.WriteResponseOutput(response);
- }
-
- // Clean up the server-side agent and thread after use when applicable (depending on the provider).
- await base.AgentCleanUpAsync(provider, agent, thread);
- }
-
- ///
- /// Demonstrate the usage of in streaming mode,
- /// where a conversation is maintained by the .
- ///
- [Theory]
- [InlineData(ChatClientProviders.AzureOpenAI)]
- [InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
- [InlineData(ChatClientProviders.OpenAIAssistant)]
- [InlineData(ChatClientProviders.OpenAIResponses_InMemoryMessageThread)]
- [InlineData(ChatClientProviders.OpenAIResponses_ConversationIdThread)]
- public async Task RunStreamingWithThread(ChatClientProviders provider)
- {
- // Define the options for the chat client agent.
- var agentOptions = new ChatClientAgentOptions(name: JokerName, instructions: JokerInstructions)
- {
- // Get chat options based on the store type, if needed.
- ChatOptions = base.GetChatOptions(provider),
- };
-
- // Create the server-side agent Id when applicable (depending on the provider).
- agentOptions.Id = await base.AgentCreateAsync(provider, agentOptions);
-
- // Get the chat client to use for the agent.
- using var chatClient = base.GetChatClient(provider, agentOptions);
-
- // Define the agent
- var agent = new ChatClientAgent(chatClient, agentOptions);
-
- // Start a new thread for the agent conversation.
- AgentThread thread = agent.GetNewThread();
-
- // Respond to user input
- await RunAgentAsync("Tell me a joke about a pirate.");
- await RunAgentAsync("Now add some emojis to the joke.");
-
- // Local function to invoke agent and display the conversation messages.
- async Task RunAgentAsync(string input)
- {
- this.WriteUserMessage(input);
-
- await foreach (var update in agent.RunStreamingAsync(input, thread))
- {
- this.WriteAgentOutput(update);
- }
- }
-
- // Clean up the server-side agent and thread after use when applicable (depending on the provider).
- await base.AgentCleanUpAsync(provider, agent, thread);
- }
-}
diff --git a/dotnet/samples/GettingStarted/Steps/Step02_ChatClientAgent_UsingFunctionTools.cs b/dotnet/samples/GettingStarted/Steps/Step02_ChatClientAgent_UsingFunctionTools.cs
deleted file mode 100644
index be945edfbf..0000000000
--- a/dotnet/samples/GettingStarted/Steps/Step02_ChatClientAgent_UsingFunctionTools.cs
+++ /dev/null
@@ -1,154 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using System.ComponentModel;
-using Microsoft.Extensions.AI;
-using Microsoft.Extensions.AI.Agents;
-
-namespace Steps;
-
-///
-/// This sample demonstrates how to use a with function tools.
-/// It includes examples of both streaming and non-streaming agent interactions.
-///
-public sealed class Step02_ChatClientAgent_UsingFunctionTools(ITestOutputHelper output) : AgentSample(output)
-{
- [Theory]
- [InlineData(ChatClientProviders.AzureOpenAI)]
- [InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
- [InlineData(ChatClientProviders.OpenAIAssistant)]
- [InlineData(ChatClientProviders.OpenAIChatCompletion)]
- [InlineData(ChatClientProviders.OpenAIResponses)]
- public async Task RunningWithTools(ChatClientProviders provider)
- {
- // Creating a MenuTools instance to be used by the agent.
- var menuTools = new MenuTools();
-
- // Define the options for the chat client agent.
- var agentOptions = new ChatClientAgentOptions(
- name: "Host",
- instructions: "Answer questions about the menu",
- tools: [
- AIFunctionFactory.Create(menuTools.GetMenu),
- AIFunctionFactory.Create(menuTools.GetSpecials),
- AIFunctionFactory.Create(menuTools.GetItemPrice)
- ]);
-
- // Create the server-side agent Id when applicable (depending on the provider).
- agentOptions.Id = await base.AgentCreateAsync(provider, agentOptions);
-
- // Get the chat client to use for the agent.
- using var chatClient = base.GetChatClient(provider, agentOptions);
-
- // Define the agent
- var agent = new ChatClientAgent(chatClient, agentOptions);
-
- // Create the chat history thread to capture the agent interaction.
- var thread = agent.GetNewThread();
-
- // Respond to user input, invoking functions where appropriate.
- await RunAgentAsync("Hello");
- await RunAgentAsync("What is the special soup and its price?");
- await RunAgentAsync("What is the special drink and its price?");
- await RunAgentAsync("Thank you");
-
- async Task RunAgentAsync(string input)
- {
- this.WriteUserMessage(input);
- var response = await agent.RunAsync(input, thread);
- this.WriteResponseOutput(response);
- }
-
- // Clean up the server-side agent after use when applicable (depending on the provider).
- await base.AgentCleanUpAsync(provider, agent, thread);
- }
-
- [Theory]
- [InlineData(ChatClientProviders.AzureOpenAI)]
- [InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
- [InlineData(ChatClientProviders.OpenAIAssistant)]
- [InlineData(ChatClientProviders.OpenAIChatCompletion)]
- [InlineData(ChatClientProviders.OpenAIResponses)]
- public async Task StreamingRunWithTools(ChatClientProviders provider)
- {
- // Creating a MenuTools instance to be used by the agent.
- var menuTools = new MenuTools();
-
- // Define the options for the chat client agent.
- var agentOptions = new ChatClientAgentOptions(
- name: "Host",
- instructions: "Answer questions about the menu",
- tools: [
- AIFunctionFactory.Create(menuTools.GetMenu),
- AIFunctionFactory.Create(menuTools.GetSpecials),
- AIFunctionFactory.Create(menuTools.GetItemPrice)
- ]);
-
- // Create the server-side agent Id when applicable (depending on the provider).
- agentOptions.Id = await base.AgentCreateAsync(provider, agentOptions);
-
- // Get the chat client to use for the agent.
- using var chatClient = base.GetChatClient(provider, agentOptions);
-
- // Define the agent
- var agent = new ChatClientAgent(chatClient, agentOptions);
-
- // Create the chat history thread to capture the agent interaction.
- var thread = agent.GetNewThread();
-
- // Respond to user input, invoking functions where appropriate.
- await RunAgentAsync("Hello");
- await RunAgentAsync("What is the special soup and its price?");
- await RunAgentAsync("What is the special drink and its price?");
- await RunAgentAsync("Thank you");
-
- async Task RunAgentAsync(string input)
- {
- this.WriteUserMessage(input);
- await foreach (var update in agent.RunStreamingAsync(input, thread))
- {
- this.WriteAgentOutput(update);
- }
- }
-
- // Clean up the server-side agent after use when applicable (depending on the provider).
- await base.AgentCleanUpAsync(provider, agent, thread);
- }
-
- private sealed class MenuTools
- {
- [Description("Get the full menu items.")]
- public MenuItem[] GetMenu()
- {
- return s_menuItems;
- }
-
- [Description("Get the specials from the menu.")]
- public IEnumerable