mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.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.
This commit is contained in:
committed by
GitHub
Unverified
parent
6b22b6bbc7
commit
bbea3c00f8
@@ -21,7 +21,14 @@
|
||||
<Folder Name="/Samples/GettingStartedSteps/">
|
||||
<File Path="samples/GettingStartedSteps/README.md" />
|
||||
<Project Path="samples/GettingStartedSteps/Step01_ChatClientAgent_Running/Step01_ChatClientAgent_Running.csproj" />
|
||||
<Project Path="samples/GettingStartedSteps/Step02_ChatClientAgent_Multiturn/Step02_ChatClientAgent_Multiturn.csproj" />
|
||||
<Project Path="samples/GettingStartedSteps/Step02_ChatClientAgent_MultiturnConversation/Step02_ChatClientAgent_MultiturnConversation.csproj" />
|
||||
<Project Path="samples/GettingStartedSteps/Step03_ChatClientAgent_UsingFunctionTools/Step03_ChatClientAgent_UsingFunctionTools.csproj" />
|
||||
<Project Path="samples/GettingStartedSteps/Step04_ChatClientAgent_UsingFunctionToolsWithApprovals/Step04_ChatClientAgent_UsingFunctionToolsWithApprovals.csproj" />
|
||||
<Project Path="samples/GettingStartedSteps/Step05_ChatClientAgent_StructuredOutput/Step05_ChatClientAgent_StructuredOutput.csproj" />
|
||||
<Project Path="samples/GettingStartedSteps/Step06_ChatClientAgent_PersistedConversations/Step06_ChatClientAgent_PersistedConversations.csproj" />
|
||||
<Project Path="samples/GettingStartedSteps/Step07_ChatClientAgent_3rdPartyThreadStorage/Step07_ChatClientAgent_3rdPartyThreadStorage.csproj" />
|
||||
<Project Path="samples/GettingStartedSteps/Step08_ChatClientAgent_Telemetry/Step08_ChatClientAgent_Telemetry.csproj" />
|
||||
<Project Path="samples/GettingStartedSteps/Step09_ChatClientAgent_DependencyInjection/Step09_ChatClientAgent_DependencyInjection.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/">
|
||||
<File Path=".editorconfig" />
|
||||
@@ -128,9 +135,9 @@
|
||||
<Project Path="src/Microsoft.Extensions.AI.Agents.Abstractions/Microsoft.Extensions.AI.Agents.Abstractions.csproj" />
|
||||
<Project Path="src/Microsoft.Extensions.AI.Agents.AzureAI/Microsoft.Extensions.AI.Agents.AzureAI.csproj" />
|
||||
<Project Path="src/Microsoft.Extensions.AI.Agents.CopilotStudio/Microsoft.Extensions.AI.Agents.CopilotStudio.csproj" />
|
||||
<Project Path="src/Microsoft.Extensions.AI.Agents.Hosting/Microsoft.Extensions.AI.Agents.Hosting.csproj" />
|
||||
<Project Path="src/Microsoft.Extensions.AI.Agents.Hosting.A2A/Microsoft.Extensions.AI.Agents.Hosting.A2A.csproj" />
|
||||
<Project Path="src/Microsoft.Extensions.AI.Agents.Hosting.A2A.AspNetCore/Microsoft.Extensions.AI.Agents.Hosting.A2A.AspNetCore.csproj" />
|
||||
<Project Path="src/Microsoft.Extensions.AI.Agents.Hosting.A2A/Microsoft.Extensions.AI.Agents.Hosting.A2A.csproj" />
|
||||
<Project Path="src/Microsoft.Extensions.AI.Agents.Hosting/Microsoft.Extensions.AI.Agents.Hosting.csproj" />
|
||||
<Project Path="src/Microsoft.Extensions.AI.Agents.OpenAI/Microsoft.Extensions.AI.Agents.OpenAI.csproj" />
|
||||
<Project Path="src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/Microsoft.Extensions.AI.Agents.Runtime.Abstractions.csproj" />
|
||||
<Project Path="src/Microsoft.Extensions.AI.Agents.Runtime.Storage.CosmosDB/Microsoft.Extensions.AI.Agents.Runtime.Storage.CosmosDB.csproj" />
|
||||
@@ -152,9 +159,9 @@
|
||||
</Folder>
|
||||
<Folder Name="/Tests/UnitTests/">
|
||||
<Project Path="tests/Microsoft.Agents.Orchestration.UnitTests/Microsoft.Agents.Orchestration.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Extensions.AI.Agents.Hosting.A2A.Tests/Microsoft.Extensions.AI.Agents.Hosting.A2A.Tests.csproj" Id="2a1c544d-237d-4436-8732-ba0c447ac06b" />
|
||||
<Project Path="tests/Microsoft.Agents.Workflows.UnitTests/Microsoft.Agents.Workflows.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Extensions.AI.Agents.Abstractions.UnitTests/Microsoft.Extensions.AI.Agents.Abstractions.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Extensions.AI.Agents.Hosting.A2A.Tests/Microsoft.Extensions.AI.Agents.Hosting.A2A.Tests.csproj" Id="2a1c544d-237d-4436-8732-ba0c447ac06b" />
|
||||
<Project Path="tests/Microsoft.Extensions.AI.Agents.Hosting.UnitTests/Microsoft.Extensions.AI.Agents.Hosting.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Extensions.AI.Agents.Runtime.Abstractions.UnitTests/Microsoft.Extensions.AI.Agents.Runtime.Abstractions.UnitTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Extensions.AI.Agents.UnitTests/Microsoft.Extensions.AI.Agents.UnitTests.csproj" />
|
||||
|
||||
@@ -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)]);
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
|
||||
namespace Steps;
|
||||
|
||||
/// <summary>
|
||||
/// Provides test methods to demonstrate the usage of chat agents with different interaction models.
|
||||
/// </summary>
|
||||
/// <remarks>This class contains examples of using <see cref="ChatClientAgent"/> 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.
|
||||
/// </remarks>
|
||||
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.";
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[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."));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrate the usage of <see cref="ChatClientAgent"/> where each invocation is
|
||||
/// a unique interaction with no conversation history between them.
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrate the usage of <see cref="ChatClientAgent"/> where a conversation history is maintained.
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrate the usage of <see cref="ChatClientAgent"/> in streaming mode,
|
||||
/// where a conversation is maintained by the <see cref="AgentThread"/>.
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
|
||||
namespace Steps;
|
||||
|
||||
/// <summary>
|
||||
/// This sample demonstrates how to use a <see cref="ChatClientAgent"/> with function tools.
|
||||
/// It includes examples of both streaming and non-streaming agent interactions.
|
||||
/// </summary>
|
||||
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<MenuItem> GetSpecials()
|
||||
{
|
||||
return s_menuItems.Where(i => i.IsSpecial);
|
||||
}
|
||||
|
||||
[Description("Get the price of a menu item.")]
|
||||
public float? GetItemPrice([Description("The name of the menu item.")] string menuItem)
|
||||
{
|
||||
return s_menuItems.FirstOrDefault(i => i.Name.Equals(menuItem, StringComparison.OrdinalIgnoreCase))?.Price;
|
||||
}
|
||||
|
||||
private static readonly MenuItem[] s_menuItems = [
|
||||
new() { Category = "Soup", Name = "Clam Chowder", Price = 4.95f, IsSpecial = true },
|
||||
new() { Category = "Soup", Name = "Tomato Soup", Price = 4.95f, IsSpecial = false },
|
||||
new() { Category = "Salad", Name = "Cobb Salad", Price = 9.99f },
|
||||
new() { Category = "Salad", Name = "House Salad", Price = 4.95f },
|
||||
new() { Category = "Drink", Name = "Chai Tea", Price = 2.95f, IsSpecial = true },
|
||||
new() { Category = "Drink", Name = "Soda", Price = 1.95f },
|
||||
];
|
||||
|
||||
public sealed class MenuItem
|
||||
{
|
||||
public string Category { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public float Price { get; set; }
|
||||
public bool IsSpecial { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Steps;
|
||||
|
||||
public sealed class Step04_ChatClientAgent_DependencyInjection(ITestOutputHelper output) : AgentSample(output)
|
||||
{
|
||||
private const string JokerName = "Joker";
|
||||
private const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
[Theory]
|
||||
[InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
|
||||
[InlineData(ChatClientProviders.AzureOpenAI)]
|
||||
[InlineData(ChatClientProviders.OpenAIAssistant)]
|
||||
[InlineData(ChatClientProviders.OpenAIChatCompletion)]
|
||||
[InlineData(ChatClientProviders.OpenAIResponses)]
|
||||
public async Task RunningWithServiceCollection(ChatClientProviders provider)
|
||||
{
|
||||
// Adding multiple chat clients to the service collection.
|
||||
var services = new ServiceCollection();
|
||||
|
||||
var agentOptions = new ChatClientAgentOptions(JokerInstructions, JokerName);
|
||||
|
||||
services.AddLogging();
|
||||
|
||||
// Create the server-side agent Id when applicable (depending on the provider).
|
||||
agentOptions.Id = await base.AgentCreateAsync(provider, agentOptions);
|
||||
|
||||
services.AddSingleton(agentOptions);
|
||||
|
||||
services.AddChatClient((sp) => base.GetChatClient(provider, sp.GetRequiredService<ChatClientAgentOptions>()));
|
||||
|
||||
services.AddSingleton<AIAgent>((sp)
|
||||
=> new ChatClientAgent(
|
||||
chatClient: sp.GetRequiredService<IChatClient>(),
|
||||
options: sp.GetRequiredService<ChatClientAgentOptions>(),
|
||||
loggerFactory: sp.GetRequiredService<ILoggerFactory>()));
|
||||
|
||||
// Build the service provider.
|
||||
await using var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// Get the agent from the service provider.
|
||||
var agent = serviceProvider.GetRequiredService<AIAgent>();
|
||||
|
||||
// Create the chat history thread to capture the agent interaction.
|
||||
var thread = agent.GetNewThread();
|
||||
|
||||
Console.WriteLine($"Using chat client for provider: {provider}");
|
||||
|
||||
// Respond to user input, invoking functions where appropriate.
|
||||
await RunAgentAsync("Tell me a joke about a pirate.");
|
||||
await RunAgentAsync("Now add some emojis to the joke.");
|
||||
|
||||
async Task RunAgentAsync(string input)
|
||||
{
|
||||
this.WriteUserMessage(input);
|
||||
var response = await agent.RunAsync(input, thread);
|
||||
this.WriteResponseOutput(response);
|
||||
}
|
||||
|
||||
// Clean up the agent and thread after use when applicable (depending on the provider).
|
||||
await base.AgentCleanUpAsync(provider, agent, thread);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using OpenTelemetry;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
namespace Steps;
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how to use telemetry with <see cref="ChatClientAgent"/> using OpenTelemetry.
|
||||
/// </summary>
|
||||
public sealed class Step05_ChatClientAgent_Telemetry(ITestOutputHelper output) : AgentSample(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Demonstrates OpenTelemetry tracing with Agent Framework.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
|
||||
[InlineData(ChatClientProviders.AzureOpenAI)]
|
||||
[InlineData(ChatClientProviders.OpenAIAssistant)]
|
||||
[InlineData(ChatClientProviders.OpenAIChatCompletion)]
|
||||
[InlineData(ChatClientProviders.OpenAIResponses)]
|
||||
public async Task RunWithTelemetry(ChatClientProviders provider)
|
||||
{
|
||||
// Enable telemetry
|
||||
AppContext.SetSwitch("Microsoft.Extensions.AI.Agents.EnableTelemetry", true);
|
||||
|
||||
// Create TracerProvider with console exporter
|
||||
string sourceName = Guid.NewGuid().ToString();
|
||||
|
||||
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
|
||||
.AddSource(sourceName)
|
||||
.AddConsoleExporter()
|
||||
.Build();
|
||||
|
||||
// Define agent options
|
||||
var agentOptions = new ChatClientAgentOptions(name: "TelemetryAgent", instructions: "You are a helpful assistant.");
|
||||
|
||||
// Create the server-side agent Id when applicable (depending on the provider).
|
||||
agentOptions.Id = await base.AgentCreateAsync(provider, agentOptions);
|
||||
|
||||
using var chatClient = base.GetChatClient(provider, agentOptions);
|
||||
var baseAgent = new ChatClientAgent(chatClient, agentOptions);
|
||||
|
||||
// Wrap the agent with OpenTelemetry instrumentation
|
||||
using var agent = baseAgent.WithOpenTelemetry(sourceName: sourceName);
|
||||
var thread = agent.GetNewThread();
|
||||
|
||||
// Run agent interactions
|
||||
await agent.RunAsync("What is artificial intelligence?", thread);
|
||||
await agent.RunAsync("How does machine learning work?", thread);
|
||||
|
||||
// Clean up
|
||||
await base.AgentCleanUpAsync(provider, baseAgent, thread);
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
|
||||
namespace Steps;
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how to use structured outputs with <see cref="ChatClientAgent"/>.
|
||||
/// </summary>
|
||||
public sealed class Step06_ChatClientAgent_StructuredOutputs(ITestOutputHelper output) : AgentSample(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Demonstrates processing structured outputs using JSON schemas to extract information about a person.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
|
||||
[InlineData(ChatClientProviders.AzureOpenAI)]
|
||||
[InlineData(ChatClientProviders.OpenAIAssistant)]
|
||||
[InlineData(ChatClientProviders.OpenAIChatCompletion)]
|
||||
[InlineData(ChatClientProviders.OpenAIResponses)]
|
||||
public async Task RunWithCustomSchema(ChatClientProviders provider)
|
||||
{
|
||||
var agentOptions = new ChatClientAgentOptions(name: "HelpfulAssistant", instructions: "You are a helpful assistant.")
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
ResponseFormat = ChatResponseFormatJson.ForJsonSchema(
|
||||
schema: AIJsonUtilities.CreateJsonSchema(typeof(PersonInfo)),
|
||||
schemaName: "PersonInfo",
|
||||
schemaDescription: "Information about a person including their name, age, and occupation"
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Create the server-side agent Id when applicable (depending on the provider).
|
||||
agentOptions.Id = await base.AgentCreateAsync(provider, agentOptions);
|
||||
|
||||
using var chatClient = base.GetChatClient(provider, agentOptions);
|
||||
|
||||
ChatClientAgent agent = new(chatClient, agentOptions);
|
||||
|
||||
var thread = agent.GetNewThread();
|
||||
|
||||
const string Prompt = "Please provide information about John Smith, who is a 35-year-old software engineer.";
|
||||
|
||||
var updates = agent.RunStreamingAsync(Prompt, thread);
|
||||
var agentResponse = await updates.ToAgentRunResponseAsync();
|
||||
|
||||
var personInfo = agentResponse.Deserialize<PersonInfo>(JsonSerializerOptions.Web);
|
||||
|
||||
Console.WriteLine("Assistant Output:");
|
||||
Console.WriteLine($"Name: {personInfo.Name}");
|
||||
Console.WriteLine($"Age: {personInfo.Age}");
|
||||
Console.WriteLine($"Occupation: {personInfo.Occupation}");
|
||||
|
||||
// Clean up the server-side agent after use when applicable (depending on the provider).
|
||||
await base.AgentCleanUpAsync(provider, agent, thread);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents information about a person, including their name, age, and occupation, matched to the JSON schema used in the agent.
|
||||
/// </summary>
|
||||
public class PersonInfo
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
[JsonPropertyName("age")]
|
||||
public int? Age { get; set; }
|
||||
|
||||
[JsonPropertyName("occupation")]
|
||||
public string? Occupation { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
|
||||
namespace Steps;
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how to suspend and resume a thread with the <see cref="ChatClientAgent"/>.
|
||||
/// </summary>
|
||||
public sealed class Step08_ChatClientAgent_SuspendResumeThread(ITestOutputHelper output) : AgentSample(output)
|
||||
{
|
||||
private const string JokerName = "Joker";
|
||||
private const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrate the usage of <see cref="ChatClientAgent"/> where a thread is suspended.
|
||||
/// The thread is serialized and can be stored to a database, file, or any other storage mechanism,
|
||||
/// and then deserialized later to resume the conversation with the agent.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
|
||||
[InlineData(ChatClientProviders.AzureOpenAI)]
|
||||
[InlineData(ChatClientProviders.OpenAIAssistant)]
|
||||
[InlineData(ChatClientProviders.OpenAIResponses_InMemoryMessageThread)]
|
||||
[InlineData(ChatClientProviders.OpenAIResponses_ConversationIdThread)]
|
||||
public async Task SuspendResumeThread(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
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
|
||||
|
||||
// Serialize the thread state, so it can be stored for later use.
|
||||
JsonElement serializedThread = await thread.SerializeAsync();
|
||||
|
||||
// The thread can now be saved to a database, file, or any other storage mechanism
|
||||
// and loaded again later.
|
||||
|
||||
// Deserialize the thread state after loading from storage.
|
||||
AgentThread resumedThread = await agent.DeserializeThreadAsync(serializedThread);
|
||||
|
||||
Console.WriteLine(await agent.RunAsync("Now tell the same joke in the voice of a pirate, and add some emojis to the joke.", resumedThread));
|
||||
|
||||
// Clean up the server-side agent and thread after use when applicable (depending on the provider).
|
||||
await base.AgentCleanUpAsync(provider, agent, thread);
|
||||
}
|
||||
}
|
||||
-205
@@ -1,205 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
|
||||
namespace Steps;
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how to indicate that certain function calls require user approval before they can be executed and how to then approve or reject those function calls.
|
||||
/// </summary>
|
||||
public sealed class Step10_ChatClientAgent_UsingFunctionToolsWithApprovals(ITestOutputHelper output) : AgentSample(output)
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(ChatClientProviders.AzureOpenAI)]
|
||||
[InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
|
||||
[InlineData(ChatClientProviders.OpenAIAssistant)]
|
||||
[InlineData(ChatClientProviders.OpenAIChatCompletion)]
|
||||
[InlineData(ChatClientProviders.OpenAIResponses)]
|
||||
public async Task ApprovalsWithTools(ChatClientProviders provider)
|
||||
{
|
||||
// Creating a MenuTools instance to be used by the agent.
|
||||
var menuTools = new MenuTools();
|
||||
|
||||
// Define the options for the chat client agent.
|
||||
// We mark GetMenu and GetSpecial as requiring approval before they can be invoked, while GetItemPrice can be invoked without user approval.
|
||||
// IMPORTANT: A limitation of the approvals flow when using ChatClientAgent is that if more than one function needs to be executed in one run,
|
||||
// and any one of them requires approval, approval will be sought for all function calls produced during that run.
|
||||
var agentOptions = new ChatClientAgentOptions(
|
||||
name: "Host",
|
||||
instructions: "Answer questions about the menu",
|
||||
tools: [
|
||||
new ApprovalRequiredAIFunction(AIFunctionFactory.Create(menuTools.GetMenu)),
|
||||
new ApprovalRequiredAIFunction(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("What is the special soup and its price?");
|
||||
await RunAgentAsync("What is the special drink?");
|
||||
|
||||
async Task RunAgentAsync(string input)
|
||||
{
|
||||
this.WriteUserMessage(input);
|
||||
var response = await agent.RunAsync(input, thread);
|
||||
|
||||
// Loop until all user input requests are handled.
|
||||
var userInputRequests = response.UserInputRequests.ToList();
|
||||
while (userInputRequests.Count > 0)
|
||||
{
|
||||
// Approve GetSpecials function calls, reject all others.
|
||||
List<ChatMessage> nextIterationMessages = userInputRequests?.Select((request) => request switch
|
||||
{
|
||||
FunctionApprovalRequestContent functionApprovalRequest when functionApprovalRequest.FunctionCall.Name == "GetSpecials" =>
|
||||
new ChatMessage(ChatRole.User, [functionApprovalRequest.CreateResponse(approved: true)]),
|
||||
|
||||
FunctionApprovalRequestContent functionApprovalRequest =>
|
||||
new ChatMessage(ChatRole.User, [functionApprovalRequest.CreateResponse(approved: false)]),
|
||||
|
||||
_ => throw new NotSupportedException($"Unsupported user input request type: {request.GetType().Name}")
|
||||
})?.ToList() ?? [];
|
||||
|
||||
// Write out what the decision was for each function approval request.
|
||||
nextIterationMessages.ForEach(x => Console.WriteLine($"Approval for the {(x.Contents[0] as FunctionApprovalResponseContent)?.FunctionCall.Name} function call is set to {(x.Contents[0] as FunctionApprovalResponseContent)?.Approved}."));
|
||||
|
||||
// Pass the user input responses back to the agent for further processing.
|
||||
response = await agent.RunAsync(nextIterationMessages, thread);
|
||||
|
||||
userInputRequests = response.UserInputRequests.ToList();
|
||||
}
|
||||
|
||||
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 ApprovalsWithToolsStreaming(ChatClientProviders provider)
|
||||
{
|
||||
// Creating a MenuTools instance to be used by the agent.
|
||||
var menuTools = new MenuTools();
|
||||
|
||||
// Creating a MenuTools instance to be used by the agent.
|
||||
// We mark GetMenu and GetSpecial as requiring approval before they can be invoked, while GetItemPrice can be invoked without user approval.
|
||||
// IMPORTANT: A limitation of the approvals flow when using ChatClientAgent is that if more than one function needs to be executed in one run,
|
||||
// and any one of them requires approval, approval will be sought for all function calls produced during that run.
|
||||
var agentOptions = new ChatClientAgentOptions(
|
||||
name: "Host",
|
||||
instructions: "Answer questions about the menu",
|
||||
tools: [
|
||||
new ApprovalRequiredAIFunction(AIFunctionFactory.Create(menuTools.GetMenu)),
|
||||
new ApprovalRequiredAIFunction(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("What is the special soup and its price?");
|
||||
await RunAgentAsync("What is the special drink?");
|
||||
|
||||
async Task RunAgentAsync(string input)
|
||||
{
|
||||
this.WriteUserMessage(input);
|
||||
var updates = await agent.RunStreamingAsync(input, thread).ToListAsync();
|
||||
|
||||
// Loop until all user input requests are handled.
|
||||
var userInputRequests = updates.SelectMany(x => x.UserInputRequests).ToList();
|
||||
while (userInputRequests.Count > 0)
|
||||
{
|
||||
// Approve GetSpecials function calls, reject all others.
|
||||
List<ChatMessage> nextIterationMessages = userInputRequests?.Select((request) => request switch
|
||||
{
|
||||
FunctionApprovalRequestContent functionApprovalRequest when functionApprovalRequest.FunctionCall.Name == "GetSpecials" =>
|
||||
new ChatMessage(ChatRole.User, [functionApprovalRequest.CreateResponse(approved: true)]),
|
||||
|
||||
FunctionApprovalRequestContent functionApprovalRequest =>
|
||||
new ChatMessage(ChatRole.User, [functionApprovalRequest.CreateResponse(approved: false)]),
|
||||
|
||||
_ => throw new NotSupportedException($"Unsupported request type: {request.GetType().Name}")
|
||||
})?.ToList() ?? [];
|
||||
|
||||
// Write out what the decision was for each function approval request.
|
||||
nextIterationMessages.ForEach(x => Console.WriteLine($"Approval for the {(x.Contents[0] as FunctionApprovalResponseContent)?.FunctionCall.Name} function call is set to {(x.Contents[0] as FunctionApprovalResponseContent)?.Approved}."));
|
||||
|
||||
// Pass the user input responses back to the agent for further processing.
|
||||
updates = await agent.RunStreamingAsync(nextIterationMessages, thread).ToListAsync();
|
||||
|
||||
userInputRequests = updates.SelectMany(x => x.UserInputRequests).ToList();
|
||||
}
|
||||
|
||||
this.WriteResponseOutput(updates.ToAgentRunResponse());
|
||||
}
|
||||
|
||||
// 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<MenuItem> GetSpecials()
|
||||
{
|
||||
return s_menuItems.Where(i => i.IsSpecial);
|
||||
}
|
||||
|
||||
[Description("Get the price of a menu item.")]
|
||||
public float? GetItemPrice([Description("The name of the menu item.")] string menuItem)
|
||||
{
|
||||
return s_menuItems.FirstOrDefault(i => i.Name.Equals(menuItem, StringComparison.OrdinalIgnoreCase))?.Price;
|
||||
}
|
||||
|
||||
private static readonly MenuItem[] s_menuItems = [
|
||||
new() { Category = "Soup", Name = "Clam Chowder", Price = 4.95f, IsSpecial = true },
|
||||
new() { Category = "Soup", Name = "Tomato Soup", Price = 4.95f, IsSpecial = false },
|
||||
new() { Category = "Salad", Name = "Cobb Salad", Price = 9.99f },
|
||||
new() { Category = "Salad", Name = "House Salad", Price = 4.95f },
|
||||
new() { Category = "Drink", Name = "Chai Tea", Price = 2.95f, IsSpecial = true },
|
||||
new() { Category = "Drink", Name = "Soda", Price = 1.95f },
|
||||
];
|
||||
|
||||
public sealed class MenuItem
|
||||
{
|
||||
public string Category { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public float Price { get; set; }
|
||||
public bool IsSpecial { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,14 @@ Before you begin, ensure you have the following prerequisites:
|
||||
|Sample|Description|
|
||||
|---|---|
|
||||
|[Running a simple agent](./Step01_ChatClientAgent_Running/)|This sample demonstrates how to create and run a basic agent with instructions|
|
||||
|[Multi-turn conversation with a simple agent](./Step02_ChatClientAgent_MultiTurn/)|This sample demonstrates how to implement a multi-turn conversation with a simple agent|
|
||||
|[Multi-turn conversation with a simple agent](./Step02_ChatClientAgent_MultiturnConversation/)|This sample demonstrates how to implement a multi-turn conversation with a simple agent|
|
||||
|[Using function tools with a simple agent](./Step03_ChatClientAgent_UsingFunctionTools/)|This sample demonstrates how to use function tools with a simple agent|
|
||||
|[Using function tools with approvals](./Step04_ChatClientAgent_UsingFunctionToolsWithApprovals/)|This sample demonstrates how to use function tools where approvals require human in the loop approvals before execution|
|
||||
|[Structured output with a simple agent](./Step05_ChatClientAgent_StructuredOutput/)|This sample demonstrates how to use structured output with a simple agent|
|
||||
|[Persisted conversations with a simple agent](./Step06_ChatClientAgent_PersistedConversations/)|This sample demonstrates how to persist conversations and reload them later. This is useful for cases where an agent is hosted in a stateless service|
|
||||
|[3rd party thread storage with a simple agent](./Step07_ChatClientAgent_3rdPartyThreadStorage/)|This sample demonstrates how to store conversation history in a 3rd party storage solution|
|
||||
|[Telemetry with a simple agent](./Step08_ChatClientAgent_Telemetry/)|This sample demonstrates how to add telemetry to a simple agent|
|
||||
|[Dependency injection with a simple agent](./Step09_ChatClientAgent_DependencyInjection/)|This sample demonstrates how to add and resolve an agent with a dependency injection container|
|
||||
|
||||
## Running the samples from the console
|
||||
|
||||
|
||||
@@ -8,25 +8,23 @@ using Azure.Identity;
|
||||
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";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(azureOpenAIEndpoint),
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(azureOpenAIDeploymentName)
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(JokerInstructions, JokerName);
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine("--- Run the agent ---\n");
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
// Invoke the agent with streaming support.
|
||||
Console.WriteLine("\n--- Run the agent with streaming ---\n");
|
||||
await foreach (var update in agent.RunStreamingAsync("Tell me a joke about a pirate."))
|
||||
{
|
||||
Console.Write(update);
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
|
||||
+4
-6
@@ -8,26 +8,24 @@ using Azure.Identity;
|
||||
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";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(azureOpenAIEndpoint),
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(azureOpenAIDeploymentName)
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(JokerInstructions, JokerName);
|
||||
|
||||
// Invoke the agent with a multi-turn conversation, where the context is preserved in the thread object.
|
||||
Console.WriteLine("\n--- Run with a thread (context preserved) ---\n");
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
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.
|
||||
Console.WriteLine("\n--- Run with a thread and streaming (context preserved) ---\n");
|
||||
thread = agent.GetNewThread();
|
||||
await foreach (var update in agent.RunStreamingAsync("Tell me a joke about a pirate.", thread))
|
||||
{
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample demonstrates how to use a ChatClientAgent with function tools.
|
||||
// It shows both non-streaming and streaming agent interactions using menu-related tools.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
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-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.";
|
||||
|
||||
// Create the chat client and agent, and provide the function tool to the agent.
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(instructions: "You are a helpful assistant", tools: [AIFunctionFactory.Create(GetWeather)]);
|
||||
|
||||
// Non-streaming agent interaction with function tools.
|
||||
Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?"));
|
||||
|
||||
// Streaming agent interaction with function tools.
|
||||
await foreach (var update in agent.RunStreamingAsync("What is the weather like in Amsterdam?"))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Extensions.AI.Agents.OpenAI\Microsoft.Extensions.AI.Agents.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Extensions.AI.Agents\Microsoft.Extensions.AI.Agents.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample demonstrates how to use a ChatClientAgent with function tools that require a human in the loop for approvals.
|
||||
// It shows both non-streaming and streaming agent interactions using menu-related tools.
|
||||
// If the agent is hosted in a service, with a remote user, combine this sample with the Persisted Conversations sample to persist the chat history
|
||||
// while the agent is waiting for user input.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
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-4o-mini";
|
||||
|
||||
// Create a sample function tool that the agent can use.
|
||||
[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.";
|
||||
|
||||
// Create the chat client and agent.
|
||||
// Note that we are wrapping the function tool with ApprovalRequiredAIFunction to require user approval before invoking it.
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.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();
|
||||
var response = await agent.RunAsync("What is the weather like in Amsterdam?", thread);
|
||||
var userInputRequests = response.UserInputRequests.ToList();
|
||||
|
||||
// For streaming use:
|
||||
// var updates = await agent.RunStreamingAsync("What is the weather like in Amsterdam?", thread).ToListAsync();
|
||||
// userInputRequests = updates.SelectMany(x => x.UserInputRequests).ToList();
|
||||
|
||||
while (userInputRequests.Count > 0)
|
||||
{
|
||||
// Ask the user to approve each function call request.
|
||||
// For simplicity, we are assuming here that only function approval requests are being made.
|
||||
var userInputResponses = userInputRequests
|
||||
.OfType<FunctionApprovalRequestContent>()
|
||||
.Select(functionApprovalRequest =>
|
||||
{
|
||||
Console.WriteLine($"The agent would like to invoke the following function, please reply Y to approve: Name {functionApprovalRequest.FunctionCall.Name}");
|
||||
return new ChatMessage(ChatRole.User, [functionApprovalRequest.CreateResponse(Console.ReadLine()?.Equals("Y", StringComparison.OrdinalIgnoreCase) ?? false)]);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
// Pass the user input responses back to the agent for further processing.
|
||||
response = await agent.RunAsync(userInputResponses, thread);
|
||||
|
||||
userInputRequests = response.UserInputRequests.ToList();
|
||||
|
||||
// For streaming use:
|
||||
// updates = await agent.RunStreamingAsync(userInputResponses, thread).ToListAsync();
|
||||
// userInputRequests = updates.SelectMany(x => x.UserInputRequests).ToList();
|
||||
}
|
||||
|
||||
Console.WriteLine($"\nAgent: {response}");
|
||||
|
||||
// For streaming use:
|
||||
// Console.WriteLine($"\nAgent: {updates.ToAgentRunResponse()}");
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
<PackageReference Include="System.Linq.Async" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Extensions.AI.Agents.OpenAI\Microsoft.Extensions.AI.Agents.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Extensions.AI.Agents\Microsoft.Extensions.AI.Agents.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to create and use a simple AI agent with Azure OpenAI as the backend, to produce structured output using JSON schema from a class.
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using OpenAI;
|
||||
using SampleApp;
|
||||
|
||||
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";
|
||||
|
||||
// Create the agent options, specifying the response format to use a JSON schema based on the PersonInfo class.
|
||||
ChatClientAgentOptions agentOptions = new(name: "HelpfulAssistant", instructions: "You are a helpful assistant.")
|
||||
{
|
||||
ChatOptions = new()
|
||||
{
|
||||
ResponseFormat = ChatResponseFormatJson.ForJsonSchema(
|
||||
schema: AIJsonUtilities.CreateJsonSchema(typeof(PersonInfo)),
|
||||
schemaName: "PersonInfo",
|
||||
schemaDescription: "Information about a person including their name, age, and occupation")
|
||||
}
|
||||
};
|
||||
|
||||
// Create the agent using Azure OpenAI.
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(agentOptions);
|
||||
|
||||
// Invoke the agent with some unstructured input, to extract the structured information from.
|
||||
var response = await agent.RunAsync("Please provide information about John Smith, who is a 35-year-old software engineer.");
|
||||
|
||||
// Deserialize the response into the PersonInfo class.
|
||||
var personInfo = response.Deserialize<PersonInfo>(JsonSerializerOptions.Web);
|
||||
|
||||
Console.WriteLine("Assistant Output:");
|
||||
Console.WriteLine($"Name: {personInfo.Name}");
|
||||
Console.WriteLine($"Age: {personInfo.Age}");
|
||||
Console.WriteLine($"Occupation: {personInfo.Occupation}");
|
||||
|
||||
// Invoke the agent with some unstructured input while streaming, to extract the structured information from.
|
||||
var updates = agent.RunStreamingAsync("Please provide information about John Smith, who is a 35-year-old software engineer.");
|
||||
|
||||
// Assemble all the parts of the streamed output, since we can only deserialize once we have the full json,
|
||||
// then deserialize the response into the PersonInfo class.
|
||||
personInfo = (await updates.ToAgentRunResponseAsync()).Deserialize<PersonInfo>(JsonSerializerOptions.Web);
|
||||
|
||||
Console.WriteLine("Assistant Output:");
|
||||
Console.WriteLine($"Name: {personInfo.Name}");
|
||||
Console.WriteLine($"Age: {personInfo.Age}");
|
||||
Console.WriteLine($"Occupation: {personInfo.Occupation}");
|
||||
|
||||
namespace SampleApp
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents information about a person, including their name, age, and occupation, matched to the JSON schema used in the agent.
|
||||
/// </summary>
|
||||
public class PersonInfo
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
[JsonPropertyName("age")]
|
||||
public int? Age { get; set; }
|
||||
|
||||
[JsonPropertyName("occupation")]
|
||||
public string? Occupation { get; set; }
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Extensions.AI.Agents.OpenAI\Microsoft.Extensions.AI.Agents.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Extensions.AI.Agents\Microsoft.Extensions.AI.Agents.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to create and use a simple AI agent with a conversation that can be persisted to disk.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
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-4o-mini";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
// Create the agent
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(JokerInstructions, JokerName);
|
||||
|
||||
// Start a new thread for the agent conversation.
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
|
||||
// Run the agent with a new thread.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
|
||||
|
||||
// Serialize the thread state to a JsonElement, so it can be stored for later use.
|
||||
JsonElement serializedThread = await thread.SerializeAsync();
|
||||
|
||||
// Save the serialized thread to a temporary file (for demonstration purposes).
|
||||
string tempFilePath = Path.GetTempFileName();
|
||||
await File.WriteAllTextAsync(tempFilePath, JsonSerializer.Serialize(serializedThread));
|
||||
|
||||
// Load the serialized thread from the temporary file (for demonstration purposes).
|
||||
JsonElement reloadedSerializedThread = JsonSerializer.Deserialize<JsonElement>(await File.ReadAllTextAsync(tempFilePath));
|
||||
|
||||
// Deserialize the thread state after loading from storage.
|
||||
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));
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Extensions.AI.Agents.OpenAI\Microsoft.Extensions.AI.Agents.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Extensions.AI.Agents\Microsoft.Extensions.AI.Agents.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+65
-62
@@ -1,79 +1,82 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
#pragma warning disable CA1869 // Cache and reuse 'JsonSerializerOptions' instances
|
||||
|
||||
// This sample shows how to create and use a simple AI agent with a conversation that can be persisted to disk.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using Microsoft.SemanticKernel.Connectors.InMemory;
|
||||
using OpenAI;
|
||||
using SampleApp;
|
||||
|
||||
namespace Steps;
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates how to store the chat history of a thread in a 3rd party store when using <see cref="ChatClientAgent"/>.
|
||||
/// </summary>
|
||||
public sealed class Step09_ChatClientAgent_3rdPartyThreadStorage(ITestOutputHelper output) : AgentSample(output)
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
// Create a vector store to store the chat messages in.
|
||||
// Replace this with a vector store implementation of your choice if you want to persist the chat history to disk.
|
||||
VectorStore vectorStore = new InMemoryVectorStore();
|
||||
|
||||
// Create the agent
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Name = JokerName,
|
||||
Instructions = JokerInstructions,
|
||||
ChatMessageStoreFactory = () =>
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
|
||||
// Start a new thread for the agent conversation.
|
||||
AgentThread thread = agent.GetNewThread();
|
||||
|
||||
// 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));
|
||||
|
||||
// Serialize the thread state, so it can be stored for later use.
|
||||
// Since the chat history is stored in the vector store, the serialized thread
|
||||
// only contains the guid that the messages are stored under in the vector store.
|
||||
JsonElement serializedThread = await thread.SerializeAsync();
|
||||
|
||||
Console.WriteLine("\n--- Serialized thread ---\n");
|
||||
Console.WriteLine(JsonSerializer.Serialize(serializedThread, new JsonSerializerOptions { WriteIndented = true }));
|
||||
|
||||
// The serialized thread can now be saved to a database, file, or any other storage mechanism
|
||||
// and loaded again later.
|
||||
|
||||
// Deserialize the thread state after loading from storage.
|
||||
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));
|
||||
|
||||
namespace SampleApp
|
||||
{
|
||||
private const string JokerName = "Joker";
|
||||
private const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrate storage of the chat history of a thread in a 3rd party store when using <see cref="ChatClientAgent"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this is only supported for services that do not already store the chat history in their own service.
|
||||
/// </remarks>
|
||||
[Theory]
|
||||
[InlineData(ChatClientProviders.AzureOpenAI)]
|
||||
[InlineData(ChatClientProviders.OpenAIResponses_InMemoryMessageThread)]
|
||||
public async Task ThirdPartyStorageThread(ChatClientProviders provider)
|
||||
{
|
||||
VectorStore vectorStore = new InMemoryVectorStore();
|
||||
|
||||
// Define the options for the chat client agent.
|
||||
var agentOptions = new ChatClientAgentOptions
|
||||
{
|
||||
Name = JokerName,
|
||||
Instructions = JokerInstructions,
|
||||
ChatMessageStoreFactory = () =>
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
|
||||
// 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
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", thread));
|
||||
|
||||
// Serialize the thread state, so it can be stored for later use.
|
||||
// Since the chat history is stored in the vector store, the serialized there
|
||||
// only contains the guid that the messages are stored under in the vector store.
|
||||
JsonElement serializedThread = await thread.SerializeAsync();
|
||||
|
||||
// The serialized thread can now be saved to a database, file, or any other storage mechanism
|
||||
// and loaded again later.
|
||||
|
||||
// Deserialize the thread state after loading from storage.
|
||||
AgentThread resumedThread = await agent.DeserializeThreadAsync(serializedThread);
|
||||
|
||||
Console.WriteLine(await agent.RunAsync("Now tell the same joke in the voice of a pirate, and add some emojis to the joke.", resumedThread));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A sample implementation of <see cref="IChatMessageStore"/> that stores chat messages in a vector store.
|
||||
/// </summary>
|
||||
/// <param name="vectorStore">The vector store to store the messages in.</param>
|
||||
private sealed class VectorChatMessageStore(VectorStore vectorStore) : IChatMessageStore
|
||||
internal sealed class VectorChatMessageStore(VectorStore vectorStore) : IChatMessageStore
|
||||
{
|
||||
private string? _threadId;
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Connectors.InMemory" />
|
||||
<PackageReference Include="System.Linq.Async" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Extensions.AI.Agents.OpenAI\Microsoft.Extensions.AI.Agents.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Extensions.AI.Agents\Microsoft.Extensions.AI.Agents.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// This sample shows how to create and use a simple AI agent with Azure OpenAI as the backend that logs telemetry using OpenTelemetry.
|
||||
|
||||
using System;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using OpenAI;
|
||||
using OpenTelemetry;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
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";
|
||||
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
|
||||
// Enable telemetry
|
||||
AppContext.SetSwitch("Microsoft.Extensions.AI.Agents.EnableTelemetry", true);
|
||||
|
||||
// Create TracerProvider with console exporter
|
||||
// This will output the telemetry data to the console.
|
||||
string sourceName = Guid.NewGuid().ToString();
|
||||
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
|
||||
.AddSource(sourceName)
|
||||
.AddConsoleExporter()
|
||||
.Build();
|
||||
|
||||
// Create the agent, and enable OpenTelemetry instrumentation.
|
||||
AIAgent agent = new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.CreateAIAgent(JokerInstructions, JokerName)
|
||||
.WithOpenTelemetry(sourceName: sourceName);
|
||||
|
||||
// Invoke the agent and output the text result.
|
||||
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
|
||||
|
||||
// Invoke the agent with streaming support.
|
||||
await foreach (var update in agent.RunStreamingAsync("Tell me a joke about a pirate."))
|
||||
{
|
||||
Console.WriteLine(update);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
<PackageReference Include="OpenTelemetry" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.Console" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Extensions.AI.Agents.OpenAI\Microsoft.Extensions.AI.Agents.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Extensions.AI.Agents\Microsoft.Extensions.AI.Agents.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
#pragma warning disable CA1812
|
||||
|
||||
// This sample shows how to use dependency injection to register an AIAgent and use it from a hosted service with a user input chat loop.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Azure.AI.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
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";
|
||||
|
||||
// Create a host builder that we will register services with and then run.
|
||||
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
|
||||
|
||||
// Add agent options to the service collection.
|
||||
const string JokerName = "Joker";
|
||||
const string JokerInstructions = "You are good at telling jokes.";
|
||||
builder.Services.AddSingleton(new ChatClientAgentOptions(JokerInstructions, JokerName));
|
||||
|
||||
// Add a chat client to the service collection.
|
||||
builder.Services.AddKeyedChatClient("AzureOpenAI", (sp) => new AzureOpenAIClient(
|
||||
new Uri(endpoint),
|
||||
new AzureCliCredential())
|
||||
.GetChatClient(deploymentName)
|
||||
.AsIChatClient());
|
||||
|
||||
// Add the AI agent to the service collection.
|
||||
builder.Services.AddSingleton<AIAgent>((sp) => new ChatClientAgent(
|
||||
chatClient: sp.GetRequiredKeyedService<IChatClient>("AzureOpenAI"),
|
||||
options: sp.GetRequiredService<ChatClientAgentOptions>()));
|
||||
|
||||
// Add a sample service that will use the agent to respond to user input.
|
||||
builder.Services.AddHostedService<SampleApp.SampleService>();
|
||||
|
||||
// Create a cancellation token and source to pass to the sample service that can
|
||||
// be used to signal shutdown of the application.
|
||||
CancellationTokenSource appShutdownCancellationTokenSource = new();
|
||||
CancellationToken appShutdownCancellationToken = appShutdownCancellationTokenSource.Token;
|
||||
builder.Services.AddKeyedSingleton("AppShutdown", appShutdownCancellationTokenSource);
|
||||
|
||||
// Build and run the host.
|
||||
using IHost host = builder.Build();
|
||||
await host.RunAsync(appShutdownCancellationToken).ConfigureAwait(false);
|
||||
|
||||
namespace SampleApp
|
||||
{
|
||||
/// <summary>
|
||||
/// A sample service that uses an AI agent to respond to user input.
|
||||
/// </summary>
|
||||
internal sealed class SampleService(AIAgent agent, [FromKeyedServices("AppShutdown")] CancellationTokenSource appShutdownCancellationTokenSource) : IHostedService
|
||||
{
|
||||
private AgentThread? _thread;
|
||||
|
||||
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.RunAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task RunAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// Delay a little to allow the service to finish starting.
|
||||
await Task.Delay(100, cancellationToken);
|
||||
|
||||
while (cancellationToken.IsCancellationRequested is false)
|
||||
{
|
||||
Console.WriteLine("\nAgent: Ask me to tell you a joke about a specific topic. To exit just press Ctrl+C or enter without any input.\n");
|
||||
Console.Write("> ");
|
||||
var input = Console.ReadLine();
|
||||
|
||||
// If the user enters no input, signal the application to shut down.
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
appShutdownCancellationTokenSource.Cancel();
|
||||
break;
|
||||
}
|
||||
|
||||
// Stream the output to the console as it is generated.
|
||||
await foreach (var update in agent.RunStreamingAsync(input, this._thread!, cancellationToken: cancellationToken))
|
||||
{
|
||||
Console.Write(update);
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Extensions.AI.Agents.OpenAI\Microsoft.Extensions.AI.Agents.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Microsoft.Extensions.AI.Agents\Microsoft.Extensions.AI.Agents.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -35,7 +35,7 @@ internal static class SampleEnvironment
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.Write(key);
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.Write("'> ");
|
||||
Console.Write("'. Just press enter to accept the default. > ");
|
||||
Console.ForegroundColor = color;
|
||||
value = Console.ReadLine();
|
||||
value = string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||
|
||||
Reference in New Issue
Block a user