mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
* Initial plan * Update code for Microsoft.Extensions.AI.Abstractions 10.4.0 breaking changes - Rename FunctionApprovalRequestContent → ToolApprovalRequestContent - Rename FunctionApprovalResponseContent → ToolApprovalResponseContent - Rename UserInputRequestContent → ToolApprovalRequestContent - Rename UserInputResponseContent → ToolApprovalResponseContent - Update .FunctionCall property → .ToolCall with FunctionCallContent casts where needed - Update .Id property → .RequestId on the renamed types - Rename FunctionApprovalRequestEventGenerator → ToolApprovalRequestEventGenerator - Rename FunctionApprovalResponseEventGenerator → ToolApprovalResponseEventGenerator Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update OpenAI 2.9.1, ME.AI 10.4.0, fix breaking API changes Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Fix remaining ME.AI 10.4.0 breaking changes: MCP approval types, .Output→.Outputs Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Use pattern matching with `when` for ToolApprovalRequestContent/FunctionCallContent Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Update Azure.AI.OpenAI to 2.9.0-beta.1 Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Fix remaining GetResponsesClient(model) build failures for Azure.AI.OpenAI 2.9.0-beta.1 Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Address review feedback: remove redundant type checks in TestRequestAgent.cs and fix error message in AIAgentHostExecutor.cs Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Update Azure.AI.Projects to 2.0.0-beta.2 with namespace migration - Azure.AI.Projects 2.0.0-beta.1 → 2.0.0-beta.2 - Azure.AI.Projects.OpenAI → Azure.AI.Extensions.OpenAI (transitive) - Agent types moved to Azure.AI.Projects.Agents namespace - AgentRecord.Versions.Latest → AgentRecord.GetLatestVersion() - OpenAPIFunctionDefinition → OpenApiFunctionDefinition - BingCustomSearchToolParameters → BingCustomSearchToolOptions - MemorySearchPreviewTool.UpdateDelay → UpdateDelayInSecs - Azure.Identity 1.17.1 → 1.19.0 - Microsoft.Identity.Client.Extensions.Msal 4.78.0 → 4.83.1 Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> * Fix remaining type renames for Azure.AI.Projects 2.0.0-beta.2 - BrowserAutomationToolParameters → BrowserAutomationToolOptions - MemoryUpdateOptions.UpdateDelay stays as UpdateDelay (not renamed) - WaitForMemoriesUpdateAsync parameter order: pollingInterval before options - AIProjectAgentsOperations → AgentsClient Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> * Fix format errors and OpenTelemetry test for ME.AI 10.4.0 - Remove unused 'using Azure.AI.Extensions.OpenAI' and fix import ordering in Agent_With_AzureAIProject/Program.cs - Update OpenTelemetryAgentTests: gen_ai.tool.definitions is now always emitted regardless of EnableSensitiveData per ME.AI 10.4.0 change (dotnet/extensions#7346). Tool definitions are not considered sensitive. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix GetRepoFolder() to work in git worktrees Use 'workflow-samples' directory as repo root marker instead of '.git', which fails in worktrees (.git is a file) and also matches too early when a '.github' folder exists in subdirectories. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix formatting: remove unused usings and fix import ordering dotnet format applied across 59 impacted projects. Primarily removes unnecessary 'using Azure.AI.Projects' where Azure.AI.Projects.Agents provides all needed types, and fixes import ordering per editorconfig. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Disable AzureAIAgentsPersistent integration tests (#4769) Azure.AI.Agents.Persistent 1.2.0-beta.9 references McpServerToolApprovalResponseContent which was removed in ME.AI 10.4.0 (renamed to ToolApprovalResponseContent), causing TypeLoadException at runtime. Mark all 6 test classes with IntegrationDisabled trait until Persistent ships a version targeting ME.AI 10.4.0+. Upstream fix: https://github.com/Azure/azure-sdk-for-net/pull/56929 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add README with compatibility note for AzureAI.Persistent (#4769) Documents that Azure.AI.Agents.Persistent 1.2.0-beta.9 is only compatible with ME.AI ≤10.3.0 and OpenAI ≤2.8.0 due to type renames in ME.AI 10.4.0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix file encoding: restore UTF-8 BOM on Persistent test files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Mark AzureAI.Persistent as IsPackable=false (#4769) Prevent shipping until Azure.AI.Agents.Persistent targets ME.AI 10.4.0+. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Moving IsPackable after import --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
224 lines
10 KiB
C#
224 lines
10 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
// This sample shows multiple middleware layers working together with Azure Foundry Agents:
|
|
// agent run (PII filtering and guardrails),
|
|
// function invocation (logging and result overrides), and human-in-the-loop
|
|
// approval workflows for sensitive function calls.
|
|
|
|
using System.ComponentModel;
|
|
using System.Text.RegularExpressions;
|
|
using Azure.AI.Projects;
|
|
using Azure.Identity;
|
|
using Microsoft.Agents.AI;
|
|
using Microsoft.Extensions.AI;
|
|
|
|
// Get Azure AI Foundry configuration from environment variables
|
|
string endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
|
|
string deploymentName = System.Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o";
|
|
|
|
const string AssistantInstructions = "You are an AI assistant that helps people find information.";
|
|
const string AssistantName = "InformationAssistant";
|
|
|
|
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
|
|
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
|
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
|
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
|
|
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
|
|
|
|
[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.";
|
|
|
|
[Description("The current datetime offset.")]
|
|
static string GetDateTime()
|
|
=> DateTimeOffset.Now.ToString();
|
|
|
|
AITool dateTimeTool = AIFunctionFactory.Create(GetDateTime, name: nameof(GetDateTime));
|
|
AITool getWeatherTool = AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather));
|
|
|
|
// Define the agent you want to create. (Prompt Agent in this case)
|
|
AIAgent originalAgent = await aiProjectClient.CreateAIAgentAsync(
|
|
name: AssistantName,
|
|
model: deploymentName,
|
|
instructions: AssistantInstructions,
|
|
tools: [getWeatherTool, dateTimeTool]);
|
|
|
|
// Adding middleware to the agent level
|
|
AIAgent middlewareEnabledAgent = originalAgent
|
|
.AsBuilder()
|
|
.Use(FunctionCallMiddleware)
|
|
.Use(FunctionCallOverrideWeather)
|
|
.Use(PIIMiddleware, null)
|
|
.Use(GuardrailMiddleware, null)
|
|
.Build();
|
|
|
|
AgentSession session = await middlewareEnabledAgent.CreateSessionAsync();
|
|
|
|
Console.WriteLine("\n\n=== Example 1: Wording Guardrail ===");
|
|
AgentResponse guardRailedResponse = await middlewareEnabledAgent.RunAsync("Tell me something harmful.");
|
|
Console.WriteLine($"Guard railed response: {guardRailedResponse}");
|
|
|
|
Console.WriteLine("\n\n=== Example 2: PII detection ===");
|
|
AgentResponse piiResponse = await middlewareEnabledAgent.RunAsync("My name is John Doe, call me at 123-456-7890 or email me at john@something.com");
|
|
Console.WriteLine($"Pii filtered response: {piiResponse}");
|
|
|
|
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.
|
|
|
|
AgentResponse functionCallResponse = await middlewareEnabledAgent.RunAsync("What's the current time and the weather in Seattle?", session);
|
|
Console.WriteLine($"Function calling response: {functionCallResponse}");
|
|
|
|
// Special per-request middleware agent.
|
|
Console.WriteLine("\n\n=== Example 4: Middleware with human in the loop function approval ===");
|
|
|
|
AIAgent humanInTheLoopAgent = await aiProjectClient.CreateAIAgentAsync(
|
|
name: "HumanInTheLoopAgent",
|
|
model: deploymentName,
|
|
instructions: "You are an Human in the loop testing AI assistant that helps people find information.",
|
|
|
|
// Adding a function with approval required
|
|
tools: [new ApprovalRequiredAIFunction(AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather)))]);
|
|
|
|
// Using the ConsolePromptingApprovalMiddleware for a specific request to handle user approval during function calls.
|
|
AgentResponse response = await humanInTheLoopAgent
|
|
.AsBuilder()
|
|
.Use(ConsolePromptingApprovalMiddleware, null)
|
|
.Build()
|
|
.RunAsync("What's the current time and the weather in Seattle?");
|
|
|
|
Console.WriteLine($"HumanInTheLoopAgent 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)
|
|
{
|
|
Console.WriteLine($"Function Name: {context!.Function.Name} - Middleware 1 Pre-Invoke");
|
|
var result = await next(context, cancellationToken);
|
|
Console.WriteLine($"Function Name: {context!.Function.Name} - Middleware 1 Post-Invoke");
|
|
|
|
return result;
|
|
}
|
|
|
|
// Function invocation middleware that overrides the result of the GetWeather function.
|
|
async ValueTask<object?> FunctionCallOverrideWeather(AIAgent agent, FunctionInvocationContext context, Func<FunctionInvocationContext, CancellationToken, ValueTask<object?>> next, CancellationToken cancellationToken)
|
|
{
|
|
Console.WriteLine($"Function Name: {context!.Function.Name} - Middleware 2 Pre-Invoke");
|
|
|
|
var result = await next(context, cancellationToken);
|
|
|
|
if (context.Function.Name == nameof(GetWeather))
|
|
{
|
|
// Override the result of the GetWeather function
|
|
result = "The weather is sunny with a high of 25°C.";
|
|
}
|
|
Console.WriteLine($"Function Name: {context!.Function.Name} - Middleware 2 Post-Invoke");
|
|
return result;
|
|
}
|
|
|
|
// This middleware redacts PII information from input and output messages.
|
|
async Task<AgentResponse> PIIMiddleware(IEnumerable<ChatMessage> messages, AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
|
{
|
|
// Redact PII information from input messages
|
|
var filteredMessages = FilterMessages(messages);
|
|
Console.WriteLine("Pii Middleware - Filtered Messages Pre-Run");
|
|
|
|
var response = await innerAgent.RunAsync(filteredMessages, session, options, cancellationToken).ConfigureAwait(false);
|
|
|
|
// Redact PII information from output messages
|
|
response.Messages = FilterMessages(response.Messages);
|
|
|
|
Console.WriteLine("Pii Middleware - Filtered Messages Post-Run");
|
|
|
|
return response;
|
|
|
|
static IList<ChatMessage> FilterMessages(IEnumerable<ChatMessage> messages)
|
|
{
|
|
return messages.Select(m => new ChatMessage(m.Role, FilterPii(m.Text))).ToList();
|
|
}
|
|
|
|
static string FilterPii(string content)
|
|
{
|
|
// Regex patterns for PII detection (simplified for demonstration)
|
|
Regex[] piiPatterns = [
|
|
new(@"\b\d{3}-\d{3}-\d{4}\b", RegexOptions.Compiled), // Phone number (e.g., 123-456-7890)
|
|
new(@"\b[\w\.-]+@[\w\.-]+\.\w+\b", RegexOptions.Compiled), // Email address
|
|
new(@"\b[A-Z][a-z]+\s[A-Z][a-z]+\b", RegexOptions.Compiled) // Full name (e.g., John Doe)
|
|
];
|
|
|
|
foreach (var pattern in piiPatterns)
|
|
{
|
|
content = pattern.Replace(content, "[REDACTED: PII]");
|
|
}
|
|
|
|
return content;
|
|
}
|
|
}
|
|
|
|
// This middleware enforces guardrails by redacting certain keywords from input and output messages.
|
|
async Task<AgentResponse> GuardrailMiddleware(IEnumerable<ChatMessage> messages, AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
|
{
|
|
// Redact keywords from input messages
|
|
var filteredMessages = FilterMessages(messages);
|
|
|
|
Console.WriteLine("Guardrail Middleware - Filtered messages Pre-Run");
|
|
|
|
// Proceed with the agent run
|
|
var response = await innerAgent.RunAsync(filteredMessages, session, options, cancellationToken);
|
|
|
|
// Redact keywords from output messages
|
|
response.Messages = FilterMessages(response.Messages);
|
|
|
|
Console.WriteLine("Guardrail Middleware - Filtered messages Post-Run");
|
|
|
|
return response;
|
|
|
|
List<ChatMessage> FilterMessages(IEnumerable<ChatMessage> messages)
|
|
{
|
|
return messages.Select(m => new ChatMessage(m.Role, FilterContent(m.Text))).ToList();
|
|
}
|
|
|
|
static string FilterContent(string content)
|
|
{
|
|
foreach (var keyword in new[] { "harmful", "illegal", "violence" })
|
|
{
|
|
if (content.Contains(keyword, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return "[REDACTED: Forbidden content]";
|
|
}
|
|
}
|
|
|
|
return content;
|
|
}
|
|
}
|
|
|
|
// This middleware handles Human in the loop console interaction for any user approval required during function calling.
|
|
async Task<AgentResponse> ConsolePromptingApprovalMiddleware(IEnumerable<ChatMessage> messages, AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
|
|
{
|
|
AgentResponse response = await innerAgent.RunAsync(messages, session, options, cancellationToken);
|
|
|
|
// For simplicity, we are assuming here that only function approvals are pending.
|
|
List<ToolApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<ToolApprovalRequestContent>().ToList();
|
|
|
|
while (approvalRequests.Count > 0)
|
|
{
|
|
// Ask the user to approve each function call request.
|
|
// Pass the user input responses back to the agent for further processing.
|
|
response.Messages = approvalRequests
|
|
.ConvertAll(functionApprovalRequest =>
|
|
{
|
|
Console.WriteLine($"The agent would like to invoke the following function, please reply Y to approve: Name {((FunctionCallContent)functionApprovalRequest.ToolCall).Name}");
|
|
bool approved = Console.ReadLine()?.Equals("Y", StringComparison.OrdinalIgnoreCase) ?? false;
|
|
return new ChatMessage(ChatRole.User, [functionApprovalRequest.CreateResponse(approved)]);
|
|
});
|
|
|
|
response = await innerAgent.RunAsync(response.Messages, session, options, cancellationToken);
|
|
|
|
approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<ToolApprovalRequestContent>().ToList();
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
// Cleanup by agent name removes the agent version created.
|
|
await aiProjectClient.Agents.DeleteAgentAsync(middlewareEnabledAgent.Name);
|