This commit is contained in:
Roger Barreto
2025-11-07 21:44:31 +00:00
Unverified
parent f5d6056074
commit 6e04c6bbbe
32 changed files with 120 additions and 518 deletions
+9 -13
View File
@@ -44,24 +44,21 @@
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_OpenAIResponses/Agent_With_OpenAIResponses.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AgentProviders/AzureAIAgents/">
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/Agent_Step01_Basics/Agent_Step01_Basics.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step01_Running/AzureAIAgents_Step01_Running.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step01.1_Basics/AzureAIAgents_Step01.1_Basics.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step01.2_Running/AzureAIAgents_Step01.2_Running.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step02_MultiturnConversation/AzureAIAgents_Step02_MultiturnConversation.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step03.1_UsingFunctionTools/AzureAIAgents_Step03.1_UsingFunctionTools.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step03.2_UsingFunctionTools_FromOpenAPI/AzureAIAgents_Step03.2_UsingFunctionTools_FromOpenAPI.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step04_UsingFunctionToolsWithApprovals/AzureAIAgents_Step04_UsingFunctionToolsWithApprovals.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step05_StructuredOutput/AzureAIAgents_Step05_StructuredOutput.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step06_PersistedConversations/AzureAIAgents_Step06_PersistedConversations.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step07_3rdPartyThreadStorage/AzureAIAgents_Step07_3rdPartyThreadStorage.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step08_Observability/AzureAIAgents_Step08_Observability.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step09_DependencyInjection/AzureAIAgents_Step09_DependencyInjection.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step10_AsMcpTool/AzureAIAgents_Step10_AsMcpTool.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step11_UsingImages/AzureAIAgents_Step11_UsingImages.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step12_AsFunctionTool/AzureAIAgents_Step12_AsFunctionTool.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step13_Memory/AzureAIAgents_Step13_Memory.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step14_Middleware/AzureAIAgents_Step14_Middleware.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step15_Plugins/AzureAIAgents_Step15_Plugins.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step16_ChatReduction/AzureAIAgents_Step16_ChatReduction.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step07_Observability/AzureAIAgents_Step07_Observability.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step08_DependencyInjection/AzureAIAgents_Step08_DependencyInjection.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step09_AsMcpTool/AzureAIAgents_Step09_AsMcpTool.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step10_UsingImages/AzureAIAgents_Step10_UsingImages.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step11_AsFunctionTool/AzureAIAgents_Step11_AsFunctionTool.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step12_Middleware/AzureAIAgents_Step12_Middleware.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgent/AzureAIAgents_Step13_Plugins/AzureAIAgents_Step13_Plugins.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/Agents/">
<File Path="samples/GettingStarted/Agents/README.md" />
@@ -81,7 +78,6 @@
<Project Path="samples/GettingStarted/Agents/Agent_Step13_Memory/Agent_Step13_Memory.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step14_Middleware/Agent_Step14_Middleware.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step15_Plugins/Agent_Step15_Plugins.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Agent_Step16_ChatReduction.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/Agent_Step17_BackgroundResponses.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step18_TextSearchRag/Agent_Step18_TextSearchRag.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step19_Mem0Provider/Agent_Step19_Mem0Provider.csproj" />
@@ -15,6 +15,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -1,24 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);CA1812</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Agents" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.InMemory" />
<PackageReference Include="System.Linq.Async" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>
</Project>
@@ -1,156 +0,0 @@
// 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 a 3rd party storage.
using System.Text.Json;
using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel.Connectors.InMemory;
using SampleApp;
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
const string JokerInstructions = "You are good at telling jokes.";
const string JokerName = "JokerAgent";
// 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();
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
AIAgent agent = await agentsClient.CreateAIAgentAsync(
deploymentName,
new ChatClientAgentOptions
{
Instructions = JokerInstructions,
Name = JokerName,
ChatMessageStoreFactory = ctx =>
{
// Create a new chat message store for this agent that stores the messages in a vector store.
// Each thread must get its own copy of the VectorChatMessageStore, since the store
// also contains the id that the thread is stored under.
return new VectorChatMessageStore(vectorStore, ctx.SerializedState, ctx.JsonSerializerOptions);
}
});
// 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 = thread.Serialize();
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 = agent.DeserializeThread(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));
// We can access the VectorChatMessageStore via the thread's GetService method if we need to read the key under which threads are stored.
var messageStore = resumedThread.GetService<VectorChatMessageStore>()!;
Console.WriteLine($"\nThread is stored in vector store under key: {messageStore.ThreadDbKey}");
// Cleanup by agent name removes the agent version created.
agentsClient.DeleteAgent(agent.Name);
namespace SampleApp
{
/// <summary>
/// A sample implementation of <see cref="ChatMessageStore"/> that stores chat messages in a vector store.
/// </summary>
internal sealed class VectorChatMessageStore : ChatMessageStore
{
private readonly VectorStore _vectorStore;
public VectorChatMessageStore(VectorStore vectorStore, JsonElement serializedStoreState, JsonSerializerOptions? jsonSerializerOptions = null)
{
this._vectorStore = vectorStore ?? throw new ArgumentNullException(nameof(vectorStore));
if (serializedStoreState.ValueKind is JsonValueKind.String)
{
// Here we can deserialize the thread id so that we can access the same messages as before the suspension.
this.ThreadDbKey = serializedStoreState.Deserialize<string>();
}
}
public string? ThreadDbKey { get; private set; }
public override async Task AddMessagesAsync(IEnumerable<ChatMessage> messages, CancellationToken cancellationToken = default)
{
this.ThreadDbKey ??= Guid.NewGuid().ToString("N");
var collection = this._vectorStore.GetCollection<string, ChatHistoryItem>("ChatHistory");
await collection.EnsureCollectionExistsAsync(cancellationToken);
await collection.UpsertAsync(messages.Select(x => new ChatHistoryItem()
{
Key = this.ThreadDbKey + x.MessageId,
Timestamp = DateTimeOffset.UtcNow,
ThreadId = this.ThreadDbKey,
SerializedMessage = JsonSerializer.Serialize(x),
MessageText = x.Text
}), cancellationToken);
}
public override async Task<IEnumerable<ChatMessage>> GetMessagesAsync(CancellationToken cancellationToken = default)
{
var collection = this._vectorStore.GetCollection<string, ChatHistoryItem>("ChatHistory");
await collection.EnsureCollectionExistsAsync(cancellationToken);
var records = await collection
.GetAsync(
x => x.ThreadId == this.ThreadDbKey, 10,
new() { OrderBy = x => x.Descending(y => y.Timestamp) },
cancellationToken)
.ToListAsync(cancellationToken);
var messages = records.ConvertAll(x => JsonSerializer.Deserialize<ChatMessage>(x.SerializedMessage!)!)
;
messages.Reverse();
return messages;
}
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) =>
// We have to serialize the thread id, so that on deserialization we can retrieve the messages using the same thread id.
JsonSerializer.SerializeToElement(this.ThreadDbKey);
/// <summary>
/// The data structure used to store chat history items in the vector store.
/// </summary>
private sealed class ChatHistoryItem
{
[VectorStoreKey]
public string? Key { get; set; }
[VectorStoreData]
public string? ThreadId { get; set; }
[VectorStoreData]
public DateTimeOffset? Timestamp { get; set; }
[VectorStoreData]
public string? SerializedMessage { get; set; }
[VectorStoreData]
public string? MessageText { get; set; }
}
}
}
@@ -32,13 +32,7 @@ using var tracerProvider = tracerProviderBuilder.Build();
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
// Define the agent you want to create. (Prompt Agent in this case)
var agentDefinition = new PromptAgentDefinition(model: deploymentName) { Instructions = JokerInstructions };
// Create a server side agent version with the Azure.AI.Agents SDK client.
var agentVersion = agentsClient.CreateAgentVersion(agentName: JokerName, definition: agentDefinition);
// Retrieve an AIAgent for the created server side agent version.
AIAgent agent = agentsClient.GetAIAgent(agentVersion)
AIAgent agent = agentsClient.CreateAIAgent(name: JokerName, model: deploymentName, instructions: JokerInstructions)
.AsBuilder()
.UseOpenTelemetry(sourceName: sourceName)
.Build();
@@ -20,16 +20,12 @@ const string JokerName = "JokerAgent";
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
// Add the agents client to the service collection.
builder.Services.AddSingleton<AgentsClient>((sp) => new AgentsClient(new Uri(endpoint), new AzureCliCredential()));
builder.Services.AddSingleton((sp) => new AgentsClient(new Uri(endpoint), new AzureCliCredential()));
// Add the AI agent to the service collection.
builder.Services.AddSingleton<AIAgent>((sp) =>
{
var agentsClient = sp.GetRequiredService<AgentsClient>();
var agentDefinition = new PromptAgentDefinition(model: deploymentName) { Instructions = JokerInstructions };
var agentVersion = agentsClient.CreateAgentVersion(agentName: JokerName, definition: agentDefinition);
return agentsClient.GetAIAgent(agentVersion);
});
builder.Services.AddSingleton<AIAgent>((sp)
=> sp.GetRequiredService<AgentsClient>()
.CreateAIAgent(name: JokerName, model: deploymentName, instructions: JokerInstructions));
// Add a sample service that will use the agent to respond to user input.
builder.Services.AddHostedService<SampleService>();
@@ -18,7 +18,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI.Persistent\Microsoft.Agents.AI.AzureAI.Persistent.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>
@@ -2,7 +2,7 @@
// This sample shows how to expose an AI agent as an MCP tool.
using Azure.AI.Agents.Persistent;
using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.DependencyInjection;
@@ -12,17 +12,19 @@ using ModelContextProtocol.Server;
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
var persistentAgentsClient = new PersistentAgentsClient(endpoint, new AzureCliCredential());
const string JokerInstructions = "You are good at telling jokes, and you always start each joke with 'Aye aye, captain!'.";
const string JokerName = "JokerAgent";
const string JokerDescription = "An agent that tells jokes.";
// Create a server side persistent agent
var agentMetadata = await persistentAgentsClient.Administration.CreateAgentAsync(
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
// Define the agent you want to create. (Prompt Agent in this case)
AIAgent agent = agentsClient.CreateAIAgent(
name: JokerName,
model: deploymentName,
instructions: "You are good at telling jokes, and you always start each joke with 'Aye aye, captain!'.",
name: "Joker",
description: "An agent that tells jokes.");
// Retrieve the server side persistent agent as an AIAgent.
AIAgent agent = await persistentAgentsClient.GetAIAgentAsync(agentMetadata.Value.Id);
instructions: JokerInstructions,
creationOptions: new() { Description = JokerDescription });
// Convert the agent to an AIFunction and then to an MCP tool.
// The agent name and description will be used as the mcp tool name and description.
@@ -35,4 +37,6 @@ builder.Services
.WithStdioServerTransport()
.WithTools([tool]);
Console.WriteLine("Starting MCP Tool server. Press Ctrl+C to exit.");
await builder.Build().RunAsync();
@@ -17,13 +17,7 @@ const string VisionName = "VisionAgent";
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
// Define the agent you want to create. (Prompt Agent in this case)
var agentDefinition = new PromptAgentDefinition(model: deploymentName) { Instructions = VisionInstructions };
// Create a server side agent version with the Azure.AI.Agents SDK client.
var agentVersion = agentsClient.CreateAgentVersion(agentName: VisionName, definition: agentDefinition);
// Retrieve an AIAgent for the created server side agent version.
AIAgent agent = agentsClient.GetAIAgent(agentVersion);
AIAgent agent = agentsClient.CreateAIAgent(name: VisionName, model: deploymentName, instructions: VisionInstructions);
ChatMessage message = new(ChatRole.User, [
new TextContent("What do you see in this image?"),
@@ -7,7 +7,6 @@ using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI.Responses;
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
@@ -25,24 +24,19 @@ static string GetWeather([Description("The location to get the weather for.")] s
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
// Create the weather agent with function tools.
var weatherAgentDefinition = new PromptAgentDefinition(model: deploymentName)
{
Instructions = WeatherInstructions
};
var weatherTool = AIFunctionFactory.Create(GetWeather);
weatherAgentDefinition.Tools.Add(weatherTool.GetService<ResponseTool>() ?? weatherTool.AsOpenAIResponseTool()!);
var weatherAgentVersion = agentsClient.CreateAgentVersion(agentName: WeatherName, definition: weatherAgentDefinition);
AIAgent weatherAgent = agentsClient.GetAIAgent(weatherAgentVersion);
AIAgent weatherAgent = agentsClient.CreateAIAgent(
name: WeatherName,
model: deploymentName,
instructions: WeatherInstructions,
tools: [weatherTool]);
// Create the main agent, and provide the weather agent as a function tool.
var mainAgentDefinition = new PromptAgentDefinition(model: deploymentName)
{
Instructions = MainInstructions
};
var agentTool = weatherAgent.AsAIFunction();
mainAgentDefinition.Tools.Add(agentTool.GetService<ResponseTool>() ?? agentTool.AsOpenAIResponseTool()!);
var mainAgentVersion = agentsClient.CreateAgentVersion(agentName: MainName, definition: mainAgentDefinition);
AIAgent agent = agentsClient.GetAIAgent(mainAgentVersion);
AIAgent agent = agentsClient.CreateAIAgent(
name: MainName,
model: deploymentName,
instructions: MainInstructions,
tools: [weatherAgent.AsAIFunction()]);
// Invoke the agent and output the text result.
AgentThread thread = agent.GetNewThread();
@@ -11,7 +11,6 @@ using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI.Responses;
// Get Azure AI Foundry configuration from environment variables
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
@@ -31,19 +30,15 @@ static string GetWeather([Description("The location to get the weather for.")] s
static string GetDateTime()
=> DateTimeOffset.Now.ToString();
// Define the agent with tools
var agentDefinition = new PromptAgentDefinition(model: deploymentName)
{
Instructions = AssistantInstructions
};
var dateTimeTool = AIFunctionFactory.Create(GetDateTime, name: nameof(GetDateTime));
agentDefinition.Tools.Add(dateTimeTool.GetService<ResponseTool>() ?? dateTimeTool.AsOpenAIResponseTool()!);
var getWeatherTool = AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather));
// Create a server side agent version with the Azure.AI.Agents SDK client.
var agentVersion = agentsClient.CreateAgentVersion(agentName: AssistantName, definition: agentDefinition);
// Retrieve an AIAgent for the created server side agent version.
var originalAgent = agentsClient.GetAIAgent(agentVersion);
// Define the agent you want to create. (Prompt Agent in this case)
AIAgent originalAgent = agentsClient.CreateAIAgent(
name: AssistantName,
model: deploymentName,
instructions: AssistantInstructions,
tools: [getWeatherTool, dateTimeTool]);
// Adding middleware to the agent level
var middlewareEnabledAgent = originalAgent
@@ -68,33 +63,28 @@ Console.WriteLine("\n\n=== Example 3: Agent function middleware ===");
// Agent function middleware support is limited to agents that wraps a upstream ChatClientAgent or derived from it.
// Add Per-request tools
var options = new ChatClientAgentRunOptions(new()
{
Tools = [AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather))]
});
var functionCallResponse = await middlewareEnabledAgent.RunAsync("What's the current time and the weather in Seattle?", thread, options);
var functionCallResponse = await middlewareEnabledAgent.RunAsync("What's the current time and the weather in Seattle?", thread);
Console.WriteLine($"Function calling response: {functionCallResponse}");
// Special per-request middleware agent.
Console.WriteLine("\n\n=== Example 4: Per-request middleware with human in the loop function approval ===");
Console.WriteLine("\n\n=== Example 4: Middleware with human in the loop function approval ===");
AIAgent humamInTheLoopAgent = agentsClient.CreateAIAgent(
name: "HumanInTheLoopAgent",
model: deploymentName,
instructions: "You are an Human in the loop testing AI assistant that helps people find information.",
var optionsWithApproval = new ChatClientAgentRunOptions(new()
{
// Adding a function with approval required
Tools = [new ApprovalRequiredAIFunction(AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather)))],
});
tools: [new ApprovalRequiredAIFunction(AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather)))]);
// var response = middlewareAgent // Using per-request middleware pipeline in addition to existing agent-level middleware
var response = await originalAgent // Using per-request middleware pipeline without existing agent-level middleware
// Using the ConsolePromptingApprovalMiddleware for a specific request to handle user approval during function calls.
var response = await humamInTheLoopAgent
.AsBuilder()
.Use(PerRequestFunctionCallingMiddleware)
.Use(ConsolePromptingApprovalMiddleware, null)
.Build()
.RunAsync("What's the current time and the weather in Seattle?", thread, optionsWithApproval);
.RunAsync("What's the current time and the weather in Seattle?");
Console.WriteLine($"Per-request middleware response: {response}");
Console.WriteLine($"HumamInTheLoopAgent agent middleware response: {response}");
// Function invocation middleware that logs before and after function calls.
async ValueTask<object?> FunctionCallMiddleware(AIAgent agent, FunctionInvocationContext context, Func<FunctionInvocationContext, CancellationToken, ValueTask<object?>> next, CancellationToken cancellationToken)
@@ -122,17 +112,6 @@ async ValueTask<object?> FunctionCallOverrideWeather(AIAgent agent, FunctionInvo
return result;
}
// There's no difference per-request middleware, except it's added to the agent and used for a single agent run.
// This middleware logs function names before and after they are invoked.
async ValueTask<object?> PerRequestFunctionCallingMiddleware(AIAgent agent, FunctionInvocationContext context, Func<FunctionInvocationContext, CancellationToken, ValueTask<object?>> next, CancellationToken cancellationToken)
{
Console.WriteLine($"Agent Id: {agent.Id}");
Console.WriteLine($"Function Name: {context!.Function.Name} - Per-Request Pre-Invoke");
var result = await next(context, cancellationToken);
Console.WriteLine($"Function Name: {context!.Function.Name} - Per-Request Post-Invoke");
return result;
}
// This middleware redacts PII information from input and output messages.
async Task<AgentRunResponse> PIIMiddleware(IEnumerable<ChatMessage> messages, AgentThread? thread, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
{
@@ -1,22 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);CA1812</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Agents" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>
</Project>
@@ -1,156 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows how to add a basic custom memory component to an agent.
// The memory component subscribes to all messages added to the conversation and
// extracts the user's name and age if provided.
// The component adds a prompt to ask for this information if it is not already known
// and provides it to the model before each invocation if known.
using System.Text;
using System.Text.Json;
using Azure.AI.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using SampleApp;
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
const string AssistantInstructions = "You are a friendly assistant. Always address the user by their name.";
const string AssistantName = "FriendlyAssistant";
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
// Define the agent you want to create. (Prompt Agent in this case)
var agentDefinition = new PromptAgentDefinition(model: deploymentName) { Instructions = AssistantInstructions };
// Create a server side agent version with the Azure.AI.Agents SDK client.
var agentVersion = agentsClient.CreateAgentVersion(agentName: AssistantName, definition: agentDefinition);
// Retrieve an AIAgent for the created server side agent version.
AIAgent agent = agentsClient.GetAIAgent(agentVersion);
// Create a new thread for the conversation.
AgentThread thread = agent.GetNewThread();
Console.WriteLine(">> Use thread with blank memory\n");
// Invoke the agent and output the text result.
Console.WriteLine(await agent.RunAsync("Hello, what is the square root of 9?", thread));
Console.WriteLine(await agent.RunAsync("My name is Ruaidhrí", thread));
Console.WriteLine(await agent.RunAsync("I am 20 years old", thread));
// We can serialize the thread. The serialized state will include the state of the memory component.
var threadElement = thread.Serialize();
Console.WriteLine("\n>> Use deserialized thread with previously created memories\n");
// Later we can deserialize the thread and continue the conversation with the previous memory component state.
var deserializedThread = agent.DeserializeThread(threadElement);
Console.WriteLine(await agent.RunAsync("What is my name and age?", deserializedThread));
Console.WriteLine("\n>> Read memories from memory component\n");
// It's possible to access the memory component via the thread's GetService method.
var userInfo = deserializedThread.GetService<UserInfoMemory>()?.UserInfo;
// Output the user info that was captured by the memory component.
Console.WriteLine($"MEMORY - User Name: {userInfo?.UserName}");
Console.WriteLine($"MEMORY - User Age: {userInfo?.UserAge}");
Console.WriteLine("\n>> Use new thread with previously created memories\n");
// It is also possible to set the memories in a memory component on an individual thread.
// This is useful if we want to start a new thread, but have it share the same memories as a previous thread.
var newThread = agent.GetNewThread();
if (userInfo is not null && newThread.GetService<UserInfoMemory>() is UserInfoMemory newThreadMemory)
{
newThreadMemory.UserInfo = userInfo;
}
// Invoke the agent and output the text result.
// This time the agent should remember the user's name and use it in the response.
Console.WriteLine(await agent.RunAsync("What is my name and age?", newThread));
// Cleanup by agent name removes the agent version created.
agentsClient.DeleteAgent(agent.Name);
namespace SampleApp
{
/// <summary>
/// Sample memory component that can remember a user's name and age.
/// </summary>
internal sealed class UserInfoMemory : AIContextProvider
{
private readonly IChatClient _chatClient;
public UserInfoMemory(IChatClient chatClient, UserInfo? userInfo = null)
{
this._chatClient = chatClient;
this.UserInfo = userInfo ?? new UserInfo();
}
public UserInfoMemory(IChatClient chatClient, JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null)
{
this._chatClient = chatClient;
this.UserInfo = serializedState.ValueKind == JsonValueKind.Object ?
serializedState.Deserialize<UserInfo>(jsonSerializerOptions)! :
new UserInfo();
}
public UserInfo UserInfo { get; set; }
public override async ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
{
// Try and extract the user name and age from the message if we don't have it already and it's a user message.
if ((this.UserInfo.UserName is null || this.UserInfo.UserAge is null) && context.RequestMessages.Any(x => x.Role == ChatRole.User))
{
var result = await this._chatClient.GetResponseAsync<UserInfo>(
context.RequestMessages,
new ChatOptions()
{
Instructions = "Extract the user's name and age from the message if present. If not present return nulls."
},
cancellationToken: cancellationToken);
this.UserInfo.UserName ??= result.Result.UserName;
this.UserInfo.UserAge ??= result.Result.UserAge;
}
}
public override ValueTask<AIContext> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
{
StringBuilder instructions = new();
// If we don't already know the user's name and age, add instructions to ask for them, otherwise just provide what we have to the context.
instructions
.AppendLine(
this.UserInfo.UserName is null ?
"Ask the user for their name and politely decline to answer any questions until they provide it." :
$"The user's name is {this.UserInfo.UserName}.")
.AppendLine(
this.UserInfo.UserAge is null ?
"Ask the user for their age and politely decline to answer any questions until they provide it." :
$"The user's age is {this.UserInfo.UserAge}.");
return new ValueTask<AIContext>(new AIContext
{
Instructions = instructions.ToString()
});
}
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
{
return JsonSerializer.SerializeToElement(this.UserInfo, jsonSerializerOptions);
}
}
internal sealed class UserInfo
{
public string? UserName { get; set; }
public int? UserAge { get; set; }
}
}
@@ -14,7 +14,6 @@ using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using OpenAI.Responses;
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
@@ -34,20 +33,13 @@ IServiceProvider serviceProvider = services.BuildServiceProvider();
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
// Define the agent with plugin tools
var agentDefinition = new PromptAgentDefinition(model: deploymentName)
{
Instructions = AssistantInstructions
};
foreach (var tool in serviceProvider.GetRequiredService<AgentPlugin>().AsAITools())
{
agentDefinition.Tools.Add(tool.GetService<ResponseTool>() ?? tool.AsOpenAIResponseTool()!);
}
// Create a server side agent version with the Azure.AI.Agents SDK client.
var agentVersion = agentsClient.CreateAgentVersion(agentName: AssistantName, definition: agentDefinition);
// Retrieve an AIAgent for the created server side agent version.
AIAgent agent = agentsClient.GetAIAgent(agentVersion);
// Define the agent you want to create. (Prompt Agent in this case)
AIAgent agent = agentsClient.CreateAIAgent(
name: AssistantName,
model: deploymentName,
instructions: AssistantInstructions,
tools: serviceProvider.GetRequiredService<AgentPlugin>().AsAITools().ToList(),
services: serviceProvider);
// Invoke the agent and output the text result.
AgentThread thread = agent.GetNewThread();
@@ -18,13 +18,7 @@ const string JokerName = "JokerAgent";
var agentsClient = new AgentsClient(new Uri(endpoint), new AzureCliCredential());
// Define the agent you want to create. (Prompt Agent in this case)
var agentDefinition = new PromptAgentDefinition(model: deploymentName) { Instructions = JokerInstructions };
// Create a server side agent version with the Azure.AI.Agents SDK client.
var agentVersion = agentsClient.CreateAgentVersion(agentName: JokerName, definition: agentDefinition);
// Retrieve an AIAgent for the created server side agent version.
AIAgent agent = agentsClient.GetAIAgent(agentVersion);
AIAgent agent = agentsClient.CreateAIAgent(name: JokerName, model: deploymentName, instructions: JokerInstructions);
AgentThread thread = agent.GetNewThread();
@@ -25,6 +25,7 @@ public static class AgentsClientExtensions
/// <param name="tools">The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations based on the latest version of the named Azure AI Agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/> or <paramref name="name"/> is <see langword="null"/>.</exception>
@@ -37,6 +38,7 @@ public static class AgentsClientExtensions
IList<AITool>? tools = null,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
@@ -51,6 +53,7 @@ public static class AgentsClientExtensions
tools,
clientFactory,
openAIClientOptions,
services,
cancellationToken);
}
@@ -62,6 +65,7 @@ public static class AgentsClientExtensions
/// <param name="tools">The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations based on the latest version of the named Azure AI Agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/> or <paramref name="name"/> is <see langword="null"/>.</exception>
@@ -74,6 +78,7 @@ public static class AgentsClientExtensions
IList<AITool>? tools = null,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
@@ -88,6 +93,7 @@ public static class AgentsClientExtensions
tools,
clientFactory,
openAIClientOptions,
services,
cancellationToken);
}
@@ -99,6 +105,7 @@ public static class AgentsClientExtensions
/// <param name="tools">The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations based on the latest version of the Azure AI Agent.</returns>
/// <remarks>When using prompt agent definitions with tools the parameter <paramref name="tools"/> needs to be provided.</remarks>
@@ -108,6 +115,7 @@ public static class AgentsClientExtensions
IList<AITool>? tools = null,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
@@ -119,7 +127,7 @@ public static class AgentsClientExtensions
tools,
clientFactory,
openAIClientOptions,
requireInvocableTools: true,
services,
cancellationToken);
}
@@ -131,10 +139,7 @@ public static class AgentsClientExtensions
/// <param name="tools">The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="requireInvocableTools">
/// This defaults to <see langword="true" /> and indicates whether to enforce the presence of invocable tools when the AIAgent is created with an agent definition that uses them.
/// Setting this to <see langword="false" /> will require manual handling of in-proc tool invocations by the caller.
/// </param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations based on the provided version of the Azure AI Agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/> or <paramref name="agentVersion"/> is <see langword="null"/>.</exception>
@@ -145,7 +150,7 @@ public static class AgentsClientExtensions
IList<AITool>? tools = null,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
bool requireInvocableTools = true,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
@@ -159,7 +164,8 @@ public static class AgentsClientExtensions
tools,
clientFactory,
openAIClientOptions,
requireInvocableTools);
requireInvocableTools: true,
services);
}
/// <summary>
@@ -169,6 +175,7 @@ public static class AgentsClientExtensions
/// <param name="options">The options for creating the agent. Cannot be <see langword="null"/>.</param>
/// <param name="clientFactory">A factory function to customize the creation of the chat client used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to cancel the operation if needed.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the newly created agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
@@ -177,6 +184,7 @@ public static class AgentsClientExtensions
ChatClientAgentOptions options,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
@@ -200,7 +208,8 @@ public static class AgentsClientExtensions
agentOptions,
clientFactory,
openAIClientOptions,
requireInvocableTools: true);
requireInvocableTools: true,
services);
}
/// <summary>
@@ -210,6 +219,7 @@ public static class AgentsClientExtensions
/// <param name="options">The options for creating the agent. Cannot be <see langword="null"/>.</param>
/// <param name="clientFactory">A factory function to customize the creation of the chat client used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to cancel the operation if needed.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the newly created agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
@@ -218,6 +228,7 @@ public static class AgentsClientExtensions
ChatClientAgentOptions options,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
@@ -241,7 +252,8 @@ public static class AgentsClientExtensions
agentOptions,
clientFactory,
openAIClientOptions,
requireInvocableTools: true);
requireInvocableTools: true,
services);
}
/// <summary>
@@ -255,6 +267,7 @@ public static class AgentsClientExtensions
/// <param name="creationOptions">Settings that control the creation of the agent.</param>
/// <param name="clientFactory">A factory function to customize the creation of the chat client used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the newly created agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/>, <paramref name="model"/>, or <paramref name="instructions"/> is <see langword="null"/>.</exception>
@@ -269,6 +282,7 @@ public static class AgentsClientExtensions
AgentVersionCreationOptions? creationOptions = null,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
@@ -285,6 +299,7 @@ public static class AgentsClientExtensions
clientFactory,
openAIClientOptions,
requireInvocableTools: true,
services,
cancellationToken);
}
@@ -299,6 +314,7 @@ public static class AgentsClientExtensions
/// <param name="creationOptions">Settings that control the creation of the agent.</param>
/// <param name="clientFactory">A factory function to customize the creation of the chat client used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the newly created agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/>, <paramref name="model"/>, or <paramref name="instructions"/> is <see langword="null"/>.</exception>
@@ -313,6 +329,7 @@ public static class AgentsClientExtensions
AgentVersionCreationOptions? creationOptions = null,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
@@ -329,6 +346,7 @@ public static class AgentsClientExtensions
clientFactory,
openAIClientOptions,
requireInvocableTools: true,
services,
cancellationToken);
}
@@ -340,6 +358,7 @@ public static class AgentsClientExtensions
/// <param name="options">The options for creating the agent. Cannot be <see langword="null"/>.</param>
/// <param name="clientFactory">A factory function to customize the creation of the chat client used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to cancel the operation if needed.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the newly created agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
@@ -350,6 +369,7 @@ public static class AgentsClientExtensions
ChatClientAgentOptions options,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
@@ -385,7 +405,8 @@ public static class AgentsClientExtensions
agentOptions,
clientFactory,
openAIClientOptions,
RequireInvocableTools);
RequireInvocableTools,
services);
}
/// <summary>
@@ -396,6 +417,7 @@ public static class AgentsClientExtensions
/// <param name="options">The options for creating the agent. Cannot be <see langword="null"/>.</param>
/// <param name="clientFactory">A factory function to customize the creation of the chat client used by the agent.</param>
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to cancel the operation if needed.</param>
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the newly created agent.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentsClient"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
@@ -406,6 +428,7 @@ public static class AgentsClientExtensions
ChatClientAgentOptions options,
Func<IChatClient, IChatClient>? clientFactory = null,
OpenAIClientOptions? openAIClientOptions = null,
IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agentsClient);
@@ -441,7 +464,8 @@ public static class AgentsClientExtensions
agentOptions,
clientFactory,
openAIClientOptions,
RequireInvocableTools);
RequireInvocableTools,
services);
}
/// <summary>
@@ -484,6 +508,7 @@ public static class AgentsClientExtensions
clientFactory,
openAIClientOptions,
requireInvocableTools: false,
services: null,
cancellationToken);
}
@@ -528,6 +553,7 @@ public static class AgentsClientExtensions
clientFactory,
openAIClientOptions,
requireInvocableTools: false,
services: null,
cancellationToken);
}
@@ -542,6 +568,7 @@ public static class AgentsClientExtensions
Func<IChatClient, IChatClient>? clientFactory,
OpenAIClientOptions? openAIClientOptions,
bool requireInvocableTools,
IServiceProvider? services,
CancellationToken cancellationToken)
{
Throw.IfNull(agentsClient);
@@ -560,7 +587,8 @@ public static class AgentsClientExtensions
tools,
clientFactory,
openAIClientOptions,
requireInvocableTools);
requireInvocableTools,
services);
}
private static async Task<ChatClientAgent> CreateAIAgentAsync(
@@ -572,6 +600,7 @@ public static class AgentsClientExtensions
Func<IChatClient, IChatClient>? clientFactory,
OpenAIClientOptions? openAIClientOptions,
bool requireInvocableTools,
IServiceProvider? services,
CancellationToken cancellationToken)
{
Throw.IfNullOrWhitespace(name);
@@ -590,7 +619,8 @@ public static class AgentsClientExtensions
tools,
clientFactory,
openAIClientOptions,
requireInvocableTools);
requireInvocableTools,
services);
}
/// <summary>This method creates an <see cref="ChatClientAgent"/> with the specified ChatClientAgentOptions.</summary>
@@ -600,7 +630,8 @@ public static class AgentsClientExtensions
ChatClientAgentOptions agentOptions,
Func<IChatClient, IChatClient>? clientFactory,
OpenAIClientOptions? openAIClientOptions,
bool requireInvocableTools)
bool requireInvocableTools,
IServiceProvider? services)
{
IChatClient chatClient = new AzureAIAgentChatClient(agentsClient, agentVersion, agentOptions.ChatOptions, openAIClientOptions);
@@ -609,7 +640,7 @@ public static class AgentsClientExtensions
chatClient = clientFactory(chatClient);
}
return new ChatClientAgent(chatClient, agentOptions);
return new ChatClientAgent(chatClient, agentOptions, services: services);
}
/// <summary>This method creates an <see cref="ChatClientAgent"/> with a auto-generated ChatClientAgentOptions from the specified configuration parameters.</summary>
@@ -619,14 +650,16 @@ public static class AgentsClientExtensions
IList<AITool>? tools,
Func<IChatClient, IChatClient>? clientFactory,
OpenAIClientOptions? openAIClientOptions,
bool requireInvocableTools)
bool requireInvocableTools,
IServiceProvider? services)
=> CreateChatClientAgent(
agentsClient,
agentVersion,
CreateChatClientAgentOptions(agentVersion, new ChatOptions() { Tools = tools }, requireInvocableTools),
clientFactory,
openAIClientOptions,
requireInvocableTools);
requireInvocableTools,
services);
/// <summary>
/// This method creates <see cref="ChatClientAgentOptions"/> for the specified <see cref="AgentVersion"/> and the provided tools.
@@ -102,6 +102,7 @@ internal sealed class AzureAIAgentChatClient : DelegatingChatClient
// Ignore per-request all options that can't be overriden.
conversationChatOptions.Instructions = null;
conversationChatOptions.Tools = null;
// Preserve the original RawRepresentationFactory
var originalFactory = chatOptions?.RawRepresentationFactory;
@@ -191,25 +191,7 @@ public sealed class AgentsClientExtensionsTests
};
// Act
var agent = client.GetAIAgent(agentVersion, tools: tools, requireInvocableTools: true);
// Assert
Assert.NotNull(agent);
Assert.IsType<ChatClientAgent>(agent);
}
/// <summary>
/// Verify that GetAIAgent with requireInvocableTools=false allows declarative functions.
/// </summary>
[Fact]
public void GetAIAgent_WithAgentVersion_WithRequireInvocableToolsFalse_AllowsDeclarativeFunctions()
{
// Arrange
AgentsClient client = this.CreateTestAgentsClient();
AgentVersion agentVersion = this.CreateTestAgentVersion();
// Act - should not throw even without tools when requireInvocableTools is false
var agent = client.GetAIAgent(agentVersion, requireInvocableTools: false);
var agent = client.GetAIAgent(agentVersion, tools: tools);
// Assert
Assert.NotNull(agent);