.NET: Update to OpenAI 2.9.1, Azure.AI.OpenAI 2.9.0-beta.1, Microsoft.Extensions.AI 10.4.0, and Azure.AI.Projects 2.0.0-beta.2 (#4613)

* 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>
This commit is contained in:
Copilot
2026-03-20 14:29:29 +00:00
committed by GitHub
Unverified
parent 8edcb282f4
commit 88ea9d08c7
101 changed files with 464 additions and 358 deletions
+17 -18
View File
@@ -19,11 +19,10 @@
<PackageVersion Include="Aspire.Microsoft.Azure.Cosmos" Version="$(AspireAppHostSdkVersion)" />
<PackageVersion Include="CommunityToolkit.Aspire.OllamaSharp" Version="13.0.0" />
<!-- Azure.* -->
<PackageVersion Include="Azure.AI.Projects" Version="2.0.0-beta.1" />
<PackageVersion Include="Azure.AI.Projects.OpenAI" Version="2.0.0-beta.1" />
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.8" />
<PackageVersion Include="Azure.AI.OpenAI" Version="2.8.0-beta.1" />
<PackageVersion Include="Azure.Identity" Version="1.17.1" />
<PackageVersion Include="Azure.AI.Projects" Version="2.0.0-beta.2" />
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.9" />
<PackageVersion Include="Azure.AI.OpenAI" Version="2.9.0-beta.1" />
<PackageVersion Include="Azure.Identity" Version="1.19.0" />
<PackageVersion Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.4.0" />
<!-- Google Gemini -->
<PackageVersion Include="Google.GenAI" Version="0.11.0" />
@@ -40,12 +39,12 @@
<PackageVersion Include="System.CodeDom" Version="10.0.0" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.1" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-rc.2.25502.107" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.3" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.4" />
<PackageVersion Include="System.Linq.AsyncEnumerable" Version="10.0.4" />
<PackageVersion Include="System.Net.Http.Json" Version="10.0.0" />
<PackageVersion Include="System.Net.ServerSentEvents" Version="10.0.3" />
<PackageVersion Include="System.Text.Json" Version="10.0.3" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.3" />
<PackageVersion Include="System.Net.ServerSentEvents" Version="10.0.4" />
<PackageVersion Include="System.Text.Json" Version="10.0.4" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.4" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="System.Net.Security" Version="4.3.2" />
<!-- OpenTelemetry -->
@@ -64,12 +63,12 @@
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.0.0" />
<!-- Microsoft.Extensions.* -->
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.3.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.3.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation" Version="10.3.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation.Quality" Version="10.3.0" />
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.4.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.4.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation" Version="10.4.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation.Quality" Version="10.4.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation.Safety" Version="10.3.0-preview.1.26109.11" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.3.0" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.4.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
@@ -77,11 +76,11 @@
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.VectorData.Abstractions" Version="9.7.0" />
@@ -111,9 +110,9 @@
<PackageVersion Include="Microsoft.ML.OnnxRuntimeGenAI" Version="0.10.0" />
<PackageVersion Include="Microsoft.ML.Tokenizers" Version="2.0.0" />
<PackageVersion Include="OllamaSharp" Version="5.4.8" />
<PackageVersion Include="OpenAI" Version="2.8.0" />
<PackageVersion Include="OpenAI" Version="2.9.1" />
<!-- Identity -->
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.78.0" />
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.83.1" />
<!-- Workflows -->
<PackageVersion Include="Microsoft.Agents.ObjectModel" Version="2026.2.4.1" />
<PackageVersion Include="Microsoft.Agents.ObjectModel.Json" Version="2026.2.4.1" />
@@ -59,14 +59,14 @@ while ((input = Console.ReadLine()) != null && !input.Equals("exit", StringCompa
{
switch (content)
{
case FunctionApprovalRequestContent approvalRequest:
DisplayApprovalRequest(approvalRequest);
case ToolApprovalRequestContent approvalRequest when approvalRequest.ToolCall is FunctionCallContent fcc:
DisplayApprovalRequest(approvalRequest, fcc);
Console.Write($"\nApprove '{approvalRequest.FunctionCall.Name}'? (yes/no): ");
Console.Write($"\nApprove '{fcc.Name}'? (yes/no): ");
string? userInput = Console.ReadLine();
bool approved = userInput?.ToUpperInvariant() is "YES" or "Y";
FunctionApprovalResponseContent approvalResponse = approvalRequest.CreateResponse(approved);
ToolApprovalResponseContent approvalResponse = approvalRequest.CreateResponse(approved);
if (approvalRequest.AdditionalProperties != null)
{
@@ -128,19 +128,19 @@ while ((input = Console.ReadLine()) != null && !input.Equals("exit", StringCompa
}
#pragma warning disable MEAI001
static void DisplayApprovalRequest(FunctionApprovalRequestContent approvalRequest)
static void DisplayApprovalRequest(ToolApprovalRequestContent approvalRequest, FunctionCallContent fcc)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine();
Console.WriteLine("============================================================");
Console.WriteLine("APPROVAL REQUIRED");
Console.WriteLine("============================================================");
Console.WriteLine($"Function: {approvalRequest.FunctionCall.Name}");
Console.WriteLine($"Function: {fcc.Name}");
if (approvalRequest.FunctionCall.Arguments != null)
if (fcc.Arguments != null)
{
Console.WriteLine("Arguments:");
foreach (var arg in approvalRequest.FunctionCall.Arguments)
foreach (var arg in fcc.Arguments)
{
Console.WriteLine($" {arg.Key} = {arg.Value}");
}
@@ -9,7 +9,7 @@ using ServerFunctionApproval;
/// <summary>
/// A delegating agent that handles server function approval requests and responses.
/// Transforms between FunctionApprovalRequestContent/FunctionApprovalResponseContent
/// Transforms between ToolApprovalRequestContent/ToolApprovalResponseContent
/// and the server's request_approval tool call pattern.
/// </summary>
internal sealed class ServerFunctionApprovalClientAgent : DelegatingAIAgent
@@ -50,14 +50,14 @@ internal sealed class ServerFunctionApprovalClientAgent : DelegatingAIAgent
}
#pragma warning disable MEAI001 // Type is for evaluation purposes only
private static FunctionResultContent ConvertApprovalResponseToToolResult(FunctionApprovalResponseContent approvalResponse, JsonSerializerOptions jsonOptions)
private static FunctionResultContent ConvertApprovalResponseToToolResult(ToolApprovalResponseContent approvalResponse, JsonSerializerOptions jsonOptions)
{
return new FunctionResultContent(
callId: approvalResponse.Id,
callId: approvalResponse.RequestId,
result: JsonSerializer.SerializeToElement(
new ApprovalResponse
{
ApprovalId = approvalResponse.Id,
ApprovalId = approvalResponse.RequestId,
Approved = approvalResponse.Approved
},
jsonOptions));
@@ -89,7 +89,7 @@ internal sealed class ServerFunctionApprovalClientAgent : DelegatingAIAgent
{
List<ChatMessage>? result = null;
Dictionary<string, FunctionApprovalRequestContent> approvalRequests = [];
Dictionary<string, ToolApprovalRequestContent> approvalRequests = [];
for (var messageIndex = 0; messageIndex < messages.Count; messageIndex++)
{
var message = messages[messageIndex];
@@ -102,21 +102,21 @@ internal sealed class ServerFunctionApprovalClientAgent : DelegatingAIAgent
var content = message.Contents[contentIndex];
// Handle pending approval requests (transform to tool call)
if (content is FunctionApprovalRequestContent approvalRequest &&
if (content is ToolApprovalRequestContent approvalRequest &&
approvalRequest.AdditionalProperties?.TryGetValue("original_function", out var originalFunction) == true &&
originalFunction is FunctionCallContent original)
{
approvalRequests[approvalRequest.Id] = approvalRequest;
approvalRequests[approvalRequest.RequestId] = approvalRequest;
transformedContents ??= CopyContentsUpToIndex(message.Contents, contentIndex);
transformedContents.Add(original);
}
// Handle pending approval responses (transform to tool result)
else if (content is FunctionApprovalResponseContent approvalResponse &&
approvalRequests.TryGetValue(approvalResponse.Id, out var correspondingRequest))
else if (content is ToolApprovalResponseContent approvalResponse &&
approvalRequests.TryGetValue(approvalResponse.RequestId, out var correspondingRequest))
{
transformedContents ??= CopyContentsUpToIndex(message.Contents, contentIndex);
transformedContents.Add(ConvertApprovalResponseToToolResult(approvalResponse, jsonSerializerOptions));
approvalRequests.Remove(approvalResponse.Id);
approvalRequests.Remove(approvalResponse.RequestId);
correspondingRequest.AdditionalProperties?.Remove("original_function");
}
// Skip historical approval content
@@ -198,8 +198,8 @@ internal sealed class ServerFunctionApprovalClientAgent : DelegatingAIAgent
var functionCallArgs = (Dictionary<string, object?>?)approvalRequest.FunctionArguments?
.Deserialize(jsonSerializerOptions.GetTypeInfo(typeof(Dictionary<string, object?>)));
var approvalRequestContent = new FunctionApprovalRequestContent(
id: approvalRequest.ApprovalId,
var approvalRequestContent = new ToolApprovalRequestContent(
requestId: approvalRequest.ApprovalId,
new FunctionCallContent(
callId: approvalRequest.ApprovalId,
name: approvalRequest.FunctionName,
@@ -9,7 +9,7 @@ using ServerFunctionApproval;
/// <summary>
/// A delegating agent that handles function approval requests on the server side.
/// Transforms between FunctionApprovalRequestContent/FunctionApprovalResponseContent
/// Transforms between ToolApprovalRequestContent/ToolApprovalResponseContent
/// and the request_approval tool call pattern for client communication.
/// </summary>
internal sealed class ServerFunctionApprovalAgent : DelegatingAIAgent
@@ -50,7 +50,7 @@ internal sealed class ServerFunctionApprovalAgent : DelegatingAIAgent
}
#pragma warning disable MEAI001 // Type is for evaluation purposes only
private static FunctionApprovalRequestContent ConvertToolCallToApprovalRequest(FunctionCallContent toolCall, JsonSerializerOptions jsonSerializerOptions)
private static ToolApprovalRequestContent ConvertToolCallToApprovalRequest(FunctionCallContent toolCall, JsonSerializerOptions jsonSerializerOptions)
{
if (toolCall.Name != "request_approval" || toolCall.Arguments == null)
{
@@ -67,15 +67,15 @@ internal sealed class ServerFunctionApprovalAgent : DelegatingAIAgent
throw new InvalidOperationException("Failed to deserialize approval request from tool call");
}
return new FunctionApprovalRequestContent(
id: request.ApprovalId,
return new ToolApprovalRequestContent(
requestId: request.ApprovalId,
new FunctionCallContent(
callId: request.ApprovalId,
name: request.FunctionName,
arguments: request.FunctionArguments));
}
private static FunctionApprovalResponseContent ConvertToolResultToApprovalResponse(FunctionResultContent result, FunctionApprovalRequestContent approval, JsonSerializerOptions jsonSerializerOptions)
private static ToolApprovalResponseContent ConvertToolResultToApprovalResponse(FunctionResultContent result, ToolApprovalRequestContent approval, JsonSerializerOptions jsonSerializerOptions)
{
var approvalResponse = result.Result is JsonElement je ?
(ApprovalResponse?)je.Deserialize(jsonSerializerOptions.GetTypeInfo(typeof(ApprovalResponse))) :
@@ -121,7 +121,7 @@ internal sealed class ServerFunctionApprovalAgent : DelegatingAIAgent
// Track approval ID to original call ID mapping
_ = new Dictionary<string, string>();
#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
Dictionary<string, FunctionApprovalRequestContent> trackedRequestApprovalToolCalls = new(); // Remote approvals
Dictionary<string, ToolApprovalRequestContent> trackedRequestApprovalToolCalls = new(); // Remote approvals
for (int messageIndex = 0; messageIndex < messages.Count; messageIndex++)
{
var message = messages[messageIndex];
@@ -181,11 +181,10 @@ internal sealed class ServerFunctionApprovalAgent : DelegatingAIAgent
{
var content = update.Contents[i];
#pragma warning disable MEAI001 // Type is for evaluation purposes only
if (content is FunctionApprovalRequestContent request)
if (content is ToolApprovalRequestContent request && request.ToolCall is FunctionCallContent functionCall)
{
updatedContents ??= [.. update.Contents];
var functionCall = request.FunctionCall;
var approvalId = request.Id;
var approvalId = request.RequestId;
var approvalData = new ApprovalRequest
{
@@ -3,7 +3,7 @@
// This sample shows how to create and use a AI agents with Azure Foundry Agents as the backend.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
@@ -17,8 +17,8 @@ var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT
AIAgent agent = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
.GetResponsesClient(deploymentName)
.AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
.GetResponsesClient()
.AsAIAgent(model: deploymentName, instructions: "You are good at telling jokes.", name: "Joker");
// Invoke the agent and output the text result.
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
@@ -29,8 +29,8 @@ Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
AIAgent agentStoreFalse = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
.GetResponsesClient(deploymentName)
.AsIChatClientWithStoredOutputDisabled()
.GetResponsesClient()
.AsIChatClientWithStoredOutputDisabled(model: deploymentName)
.AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
// Invoke the agent and output the text result.
@@ -11,8 +11,8 @@ var model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt
AIAgent agent = new OpenAIClient(
apiKey)
.GetResponsesClient(model)
.AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker");
.GetResponsesClient()
.AsAIAgent(model: model, instructions: "You are good at telling jokes.", name: "Joker");
// Invoke the agent and output the text result.
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
@@ -23,7 +23,7 @@ var skillsProvider = new FileAgentSkillsProvider(skillPath: Path.Combine(AppCont
// --- Agent Setup ---
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient(deploymentName)
.GetResponsesClient()
.AsAIAgent(new ChatClientAgentOptions
{
Name = "SkillsAgent",
@@ -32,7 +32,8 @@ AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredent
Instructions = "You are a helpful assistant.",
},
AIContextProviders = [skillsProvider],
});
},
model: deploymentName);
// --- Example 1: Expense policy question (loads FAQ resource) ---
Console.WriteLine("Example 1: Checking expense policy FAQ");
@@ -10,8 +10,8 @@ var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new I
var model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-5";
var client = new OpenAIClient(apiKey)
.GetResponsesClient(model)
.AsIChatClient().AsBuilder()
.GetResponsesClient()
.AsIChatClient(model).AsBuilder()
.ConfigureOptions(o =>
{
o.Reasoning = new()
@@ -20,19 +20,21 @@ public class OpenAIResponseClientAgent : DelegatingAIAgent
/// <param name="instructions">Optional instructions for the agent.</param>
/// <param name="name">Optional name for the agent.</param>
/// <param name="description">Optional description for the agent.</param>
/// <param name="model">Optional default model ID to use for requests. Required when using a plain <see cref="ResponsesClient"/> (not via Azure OpenAI).</param>
/// <param name="loggerFactory">Optional instance of <see cref="ILoggerFactory"/></param>
public OpenAIResponseClientAgent(
ResponsesClient client,
string? instructions = null,
string? name = null,
string? description = null,
string? model = null,
ILoggerFactory? loggerFactory = null) :
this(client, new()
{
Name = name,
Description = description,
ChatOptions = new ChatOptions() { Instructions = instructions },
}, loggerFactory)
}, model, loggerFactory)
{
}
@@ -41,10 +43,11 @@ public class OpenAIResponseClientAgent : DelegatingAIAgent
/// </summary>
/// <param name="client">Instance of <see cref="ResponsesClient"/></param>
/// <param name="options">Options to create the agent.</param>
/// <param name="model">Optional default model ID to use for requests. Required when using a plain <see cref="ResponsesClient"/> (not via Azure OpenAI).</param>
/// <param name="loggerFactory">Optional instance of <see cref="ILoggerFactory"/></param>
public OpenAIResponseClientAgent(
ResponsesClient client, ChatClientAgentOptions options, ILoggerFactory? loggerFactory = null) :
base(new ChatClientAgent((client ?? throw new ArgumentNullException(nameof(client))).AsIChatClient(), options, loggerFactory))
ResponsesClient client, ChatClientAgentOptions options, string? model = null, ILoggerFactory? loggerFactory = null) :
base(new ChatClientAgent((client ?? throw new ArgumentNullException(nameof(client))).AsIChatClient(model), options, loggerFactory))
{
}
@@ -10,10 +10,10 @@ var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new I
var model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL_NAME") ?? "gpt-4o-mini";
// Create a ResponsesClient directly from OpenAIClient
ResponsesClient responseClient = new OpenAIClient(apiKey).GetResponsesClient(model);
ResponsesClient responseClient = new OpenAIClient(apiKey).GetResponsesClient();
// Create an agent directly from the ResponsesClient using OpenAIResponseClientAgent
OpenAIResponseClientAgent agent = new(responseClient, instructions: "You are good at telling jokes.", name: "Joker");
OpenAIResponseClientAgent agent = new(responseClient, instructions: "You are good at telling jokes.", name: "Joker", model: model);
ResponseItem userMessage = ResponseItem.CreateUserMessageItem("Tell me a joke about a pirate.");
@@ -22,7 +22,7 @@ OpenAIClient openAIClient = new(apiKey);
ConversationClient conversationClient = openAIClient.GetConversationClient();
// Create an agent directly from the ResponsesClient using OpenAIResponseClientAgent
ChatClientAgent agent = new(openAIClient.GetResponsesClient(model).AsIChatClient(), instructions: "You are a helpful assistant.", name: "ConversationAgent");
ChatClientAgent agent = new(openAIClient.GetResponsesClient().AsIChatClient(model), instructions: "You are a helpful assistant.", name: "ConversationAgent");
ClientResult createConversationResult = await conversationClient.CreateConversationAsync(BinaryContent.Create(BinaryData.FromString("{}")));
@@ -36,11 +36,11 @@ AIAgent agent = new AzureOpenAIClient(
// For simplicity, we are assuming here that only function approvals are pending.
AgentSession session = await agent.CreateSessionAsync();
AgentResponse response = await agent.RunAsync("What is the weather like in Amsterdam?", session);
List<FunctionApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<FunctionApprovalRequestContent>().ToList();
List<ToolApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<ToolApprovalRequestContent>().ToList();
// For streaming use:
// var updates = await agent.RunStreamingAsync("What is the weather like in Amsterdam?", session).ToListAsync();
// approvalRequests = updates.SelectMany(x => x.Contents).OfType<FunctionApprovalRequestContent>().ToList();
// approvalRequests = updates.SelectMany(x => x.Contents).OfType<ToolApprovalRequestContent>().ToList();
while (approvalRequests.Count > 0)
{
@@ -48,18 +48,18 @@ while (approvalRequests.Count > 0)
List<ChatMessage> userInputResponses = approvalRequests
.ConvertAll(functionApprovalRequest =>
{
Console.WriteLine($"The agent would like to invoke the following function, please reply Y to approve: Name {functionApprovalRequest.FunctionCall.Name}");
Console.WriteLine($"The agent would like to invoke the following function, please reply Y to approve: Name {((FunctionCallContent)functionApprovalRequest.ToolCall).Name}");
return new ChatMessage(ChatRole.User, [functionApprovalRequest.CreateResponse(Console.ReadLine()?.Equals("Y", StringComparison.OrdinalIgnoreCase) ?? false)]);
});
// Pass the user input responses back to the agent for further processing.
response = await agent.RunAsync(userInputResponses, session);
approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<FunctionApprovalRequestContent>().ToList();
approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<ToolApprovalRequestContent>().ToList();
// For streaming use:
// updates = await agent.RunStreamingAsync(userInputResponses, session).ToListAsync();
// approvalRequests = updates.SelectMany(x => x.Contents).OfType<FunctionApprovalRequestContent>().ToList();
// approvalRequests = updates.SelectMany(x => x.Contents).OfType<ToolApprovalRequestContent>().ToList();
}
Console.WriteLine($"\nAgent: {response}");
@@ -25,8 +25,9 @@ var stateStore = new Dictionary<string, JsonElement?>();
AIAgent agent = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
.GetResponsesClient(deploymentName)
.GetResponsesClient()
.AsAIAgent(
model: deploymentName,
name: "SpaceNovelWriter",
instructions: "You are a space novel writer. Always research relevant facts and generate character profiles for the main characters before writing novels." +
"Write complete chapters without asking for approval or feedback. Do not ask the user about tone, style, pace, or format preferences - just write the novel based on the request.",
@@ -246,7 +246,7 @@ async Task<AgentResponse> ConsolePromptingApprovalMiddleware(IEnumerable<ChatMes
AgentResponse response = await innerAgent.RunAsync(messages, session, options, cancellationToken);
// For simplicity, we are assuming here that only function approvals are pending.
List<FunctionApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<FunctionApprovalRequestContent>().ToList();
List<ToolApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<ToolApprovalRequestContent>().ToList();
while (approvalRequests.Count > 0)
{
@@ -255,13 +255,13 @@ async Task<AgentResponse> ConsolePromptingApprovalMiddleware(IEnumerable<ChatMes
response.Messages = approvalRequests
.ConvertAll(functionApprovalRequest =>
{
Console.WriteLine($"The agent would like to invoke the following function, please reply Y to approve: Name {functionApprovalRequest.FunctionCall.Name}");
Console.WriteLine($"The agent would like to invoke the following function, please reply Y to approve: Name {((FunctionCallContent)functionApprovalRequest.ToolCall).Name}");
return new ChatMessage(ChatRole.User, [functionApprovalRequest.CreateResponse(Console.ReadLine()?.Equals("Y", StringComparison.OrdinalIgnoreCase) ?? false)]);
});
response = await innerAgent.RunAsync(response.Messages, session, options, cancellationToken);
approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<FunctionApprovalRequestContent>().ToList();
approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<ToolApprovalRequestContent>().ToList();
}
return response;
@@ -16,8 +16,8 @@ var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT
AIAgent agent = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
.GetResponsesClient(deploymentName)
.AsAIAgent();
.GetResponsesClient()
.AsAIAgent(model: deploymentName);
// Enable background responses (only supported by OpenAI Responses at this time).
AgentRunOptions options = new() { AllowBackgroundResponses = true };
@@ -3,7 +3,7 @@
// This sample shows how to create and use AI agents with Azure Foundry Agents as the backend.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
@@ -3,7 +3,7 @@
// This sample shows how to create and use a simple AI agent with Azure Foundry Agents as the backend.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
@@ -2,8 +2,9 @@
// This sample shows how to create and use a simple AI agent with a multi-turn conversation.
using Azure.AI.Extensions.OpenAI;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
@@ -40,7 +40,7 @@ AgentResponse response = await agent.RunAsync("What is the weather like in Amste
// Check if there are any approval requests.
// For simplicity, we are assuming here that only function approvals are pending.
List<FunctionApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<FunctionApprovalRequestContent>().ToList();
List<ToolApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<ToolApprovalRequestContent>().ToList();
while (approvalRequests.Count > 0)
{
@@ -48,7 +48,7 @@ while (approvalRequests.Count > 0)
List<ChatMessage> userInputMessages = approvalRequests
.ConvertAll(functionApprovalRequest =>
{
Console.WriteLine($"The agent would like to invoke the following function, please reply Y to approve: Name {functionApprovalRequest.FunctionCall.Name}");
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)]);
});
@@ -56,7 +56,7 @@ while (approvalRequests.Count > 0)
// Pass the user input responses back to the agent for further processing.
response = await agent.RunAsync(userInputMessages, session);
approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<FunctionApprovalRequestContent>().ToList();
approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<ToolApprovalRequestContent>().ToList();
}
Console.WriteLine($"\nAgent: {response}");
@@ -197,7 +197,7 @@ async Task<AgentResponse> ConsolePromptingApprovalMiddleware(IEnumerable<ChatMes
AgentResponse response = await innerAgent.RunAsync(messages, session, options, cancellationToken);
// For simplicity, we are assuming here that only function approvals are pending.
List<FunctionApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<FunctionApprovalRequestContent>().ToList();
List<ToolApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<ToolApprovalRequestContent>().ToList();
while (approvalRequests.Count > 0)
{
@@ -206,14 +206,14 @@ async Task<AgentResponse> ConsolePromptingApprovalMiddleware(IEnumerable<ChatMes
response.Messages = approvalRequests
.ConvertAll(functionApprovalRequest =>
{
Console.WriteLine($"The agent would like to invoke the following function, please reply Y to approve: Name {functionApprovalRequest.FunctionCall.Name}");
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<FunctionApprovalRequestContent>().ToList();
approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<ToolApprovalRequestContent>().ToList();
}
return response;
@@ -4,7 +4,7 @@
using System.Text;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
@@ -3,7 +3,7 @@
// This sample shows how to use Computer Use Tool with AI Agents.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
@@ -3,7 +3,7 @@
// This sample shows how to use File Search Tool with AI Agents.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
@@ -3,7 +3,7 @@
// This sample shows how to use OpenAPI Tools with AI Agents.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using OpenAI.Responses;
@@ -72,7 +72,7 @@ const string CountriesOpenApiSpec = """
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
// Create the OpenAPI function definition
var openApiFunction = new OpenAPIFunctionDefinition(
var openApiFunction = new OpenApiFunctionDefinition(
"get_countries",
BinaryData.FromString(CountriesOpenApiSpec),
new OpenAPIAnonymousAuthenticationDetails())
@@ -3,7 +3,7 @@
// This sample shows how to use Bing Custom Search Tool with AI Agents.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using OpenAI.Responses;
@@ -25,7 +25,7 @@ const string AgentInstructions = """
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
// Bing Custom Search tool parameters shared by both options
BingCustomSearchToolParameters bingCustomSearchToolParameters = new([
BingCustomSearchToolOptions bingCustomSearchToolParameters = new([
new BingCustomSearchConfiguration(connectionId, instanceName)
]);
@@ -3,7 +3,7 @@
// This sample shows how to use SharePoint Grounding Tool with AI Agents.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using OpenAI.Responses;
@@ -3,7 +3,7 @@
// This sample shows how to use Microsoft Fabric Tool with AI Agents.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using OpenAI.Responses;
@@ -3,7 +3,7 @@
// This sample shows how to use the Responses API Web Search Tool with AI Agents.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
@@ -13,7 +13,6 @@
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Azure.AI.Projects" />
<PackageReference Include="Azure.AI.Projects.OpenAI" />
</ItemGroup>
<ItemGroup>
@@ -4,8 +4,9 @@
// The Memory Search Tool enables agents to recall information from previous conversations,
// supporting user profile persistence and chat summaries across sessions.
using Azure.AI.Extensions.OpenAI;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using OpenAI.Responses;
@@ -36,7 +37,7 @@ AIProjectClient aiProjectClient = new(new Uri(endpoint), credential);
await EnsureMemoryStoreAsync();
// Create the Memory Search tool configuration
MemorySearchPreviewTool memorySearchTool = new(memoryStoreName, userScope) { UpdateDelay = 0 };
MemorySearchPreviewTool memorySearchTool = new(memoryStoreName, userScope) { UpdateDelayInSecs = 0 };
// Create agent using Option 1 (MEAI) or Option 2 (Native SDK)
AIAgent agent = await CreateAgentWithMEAI();
@@ -128,8 +129,8 @@ async Task EnsureMemoryStoreAsync()
MemoryUpdateResult updateResult = await aiProjectClient.MemoryStores.WaitForMemoriesUpdateAsync(
memoryStoreName: memoryStoreName,
options: memoryOptions,
pollingInterval: 500);
pollingInterval: 500,
options: memoryOptions);
if (updateResult.Status == MemoryStoreUpdateStatus.Failed)
{
@@ -81,7 +81,7 @@ AIAgent agentWithRequiredApproval = await persistentAgentsClient.CreateAIAgentAs
// For simplicity, we are assuming here that only mcp tool approvals are pending.
AgentSession sessionWithRequiredApproval = await agentWithRequiredApproval.CreateSessionAsync();
AgentResponse response = await agentWithRequiredApproval.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", sessionWithRequiredApproval);
List<McpServerToolApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<McpServerToolApprovalRequestContent>().ToList();
List<ToolApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<ToolApprovalRequestContent>().ToList();
while (approvalRequests.Count > 0)
{
@@ -89,11 +89,12 @@ while (approvalRequests.Count > 0)
List<ChatMessage> userInputResponses = approvalRequests
.ConvertAll(approvalRequest =>
{
McpServerToolCallContent mcpToolCall = (McpServerToolCallContent)approvalRequest.ToolCall!;
Console.WriteLine($"""
The agent would like to invoke the following MCP Tool, please reply Y to approve.
ServerName: {approvalRequest.ToolCall.ServerName}
Name: {approvalRequest.ToolCall.ToolName}
Arguments: {string.Join(", ", approvalRequest.ToolCall.Arguments?.Select(x => $"{x.Key}: {x.Value}") ?? [])}
ServerName: {mcpToolCall.ServerName}
Name: {mcpToolCall.Name}
Arguments: {string.Join(", ", mcpToolCall.Arguments?.Select(x => $"{x.Key}: {x.Value}") ?? [])}
""");
return new ChatMessage(ChatRole.User, [approvalRequest.CreateResponse(Console.ReadLine()?.Equals("Y", StringComparison.OrdinalIgnoreCase) ?? false)]);
});
@@ -101,7 +102,7 @@ while (approvalRequests.Count > 0)
// Pass the user input responses back to the agent for further processing.
response = await agentWithRequiredApproval.RunAsync(userInputResponses, sessionWithRequiredApproval);
approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<McpServerToolApprovalRequestContent>().ToList();
approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<ToolApprovalRequestContent>().ToList();
}
Console.WriteLine($"\nAgent: {response}");
@@ -33,8 +33,9 @@ var mcpTool = new HostedMcpServerTool(
AIAgent agent = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
.GetResponsesClient(deploymentName)
.GetResponsesClient()
.AsAIAgent(
model: deploymentName,
instructions: "You answer questions by searching the Microsoft Learn content only.",
name: "MicrosoftLearnAgent",
tools: [mcpTool]);
@@ -60,8 +61,9 @@ var mcpToolWithApproval = new HostedMcpServerTool(
AIAgent agentWithRequiredApproval = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
.GetResponsesClient(deploymentName)
.GetResponsesClient()
.AsAIAgent(
model: deploymentName,
instructions: "You answer questions by searching the Microsoft Learn content only.",
name: "MicrosoftLearnAgentWithApproval",
tools: [mcpToolWithApproval]);
@@ -70,7 +72,7 @@ AIAgent agentWithRequiredApproval = new AzureOpenAIClient(
// For simplicity, we are assuming here that only mcp tool approvals are pending.
AgentSession sessionWithRequiredApproval = await agentWithRequiredApproval.CreateSessionAsync();
AgentResponse response = await agentWithRequiredApproval.RunAsync("Please summarize the Azure AI Agent documentation related to MCP Tool calling?", sessionWithRequiredApproval);
List<McpServerToolApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<McpServerToolApprovalRequestContent>().ToList();
List<ToolApprovalRequestContent> approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<ToolApprovalRequestContent>().ToList();
while (approvalRequests.Count > 0)
{
@@ -78,11 +80,12 @@ while (approvalRequests.Count > 0)
List<ChatMessage> userInputResponses = approvalRequests
.ConvertAll(approvalRequest =>
{
McpServerToolCallContent mcpToolCall = (McpServerToolCallContent)approvalRequest.ToolCall!;
Console.WriteLine($"""
The agent would like to invoke the following MCP Tool, please reply Y to approve.
ServerName: {approvalRequest.ToolCall.ServerName}
Name: {approvalRequest.ToolCall.ToolName}
Arguments: {string.Join(", ", approvalRequest.ToolCall.Arguments?.Select(x => $"{x.Key}: {x.Value}") ?? [])}
ServerName: {mcpToolCall.ServerName}
Name: {mcpToolCall.Name}
Arguments: {string.Join(", ", mcpToolCall.Arguments?.Select(x => $"{x.Key}: {x.Value}") ?? [])}
""");
return new ChatMessage(ChatRole.User, [approvalRequest.CreateResponse(Console.ReadLine()?.Equals("Y", StringComparison.OrdinalIgnoreCase) ?? false)]);
});
@@ -90,7 +93,7 @@ while (approvalRequests.Count > 0)
// Pass the user input responses back to the agent for further processing.
response = await agentWithRequiredApproval.RunAsync(userInputResponses, sessionWithRequiredApproval);
approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<McpServerToolApprovalRequestContent>().ToList();
approvalRequests = response.Messages.SelectMany(m => m.Contents).OfType<ToolApprovalRequestContent>().ToList();
}
Console.WriteLine($"\nAgent: {response}");
@@ -17,7 +17,7 @@
//
// Demonstrate:
// - Using custom GroupChatManager with agents that have approval-required tools.
// - Handling FunctionApprovalRequestContent in group chat scenarios.
// - Handling ToolApprovalRequestContent in group chat scenarios.
// - Multi-round group chat with tool approval interruption and resumption.
using System.ComponentModel;
@@ -101,16 +101,16 @@ public static class Program
{
case RequestInfoEvent e:
{
if (e.Request.TryGetDataAs(out FunctionApprovalRequestContent? approvalRequestContent))
if (e.Request.TryGetDataAs(out ToolApprovalRequestContent? approvalRequestContent))
{
Console.WriteLine();
Console.WriteLine($"[APPROVAL REQUIRED] From agent: {e.Request.PortInfo.PortId}");
Console.WriteLine($" Tool: {approvalRequestContent.FunctionCall.Name}");
Console.WriteLine($" Arguments: {JsonSerializer.Serialize(approvalRequestContent.FunctionCall.Arguments)}");
Console.WriteLine($" Tool: {((FunctionCallContent)approvalRequestContent.ToolCall).Name}");
Console.WriteLine($" Arguments: {JsonSerializer.Serialize(((FunctionCallContent)approvalRequestContent.ToolCall).Arguments)}");
Console.WriteLine();
// Approve the tool call request
Console.WriteLine($"Tool: {approvalRequestContent.FunctionCall.Name} approved");
Console.WriteLine($"Tool: {((FunctionCallContent)approvalRequestContent.ToolCall).Name} approved");
await run.SendResponseAsync(e.Request.CreateResponse(approvalRequestContent.CreateResponse(approved: true)));
}
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using OpenAI.Responses;
@@ -275,7 +275,7 @@ internal sealed class Program
Tools =
{
AgentTool.CreateOpenApiTool(
new OpenAPIFunctionDefinition(
new OpenApiFunctionDefinition(
"weather-forecast",
BinaryData.FromString(File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "wttr.json"))),
new OpenAPIAnonymousAuthenticationDetails()))
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
@@ -3,8 +3,9 @@
// Uncomment this to enable JSON checkpointing to the local file system.
//#define CHECKPOINT_JSON
using Azure.AI.Extensions.OpenAI;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using OpenAI.Responses;
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
@@ -5,7 +5,7 @@
// invoked to perform specific tasks, like searching documentation or executing operations.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Core;
using Azure.Identity;
using Microsoft.Agents.AI.Workflows.Declarative.Mcp;
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Shared.Foundry;
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Shared.Foundry;
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using OpenAI.Responses;
@@ -27,7 +27,7 @@ internal sealed class OpenAIResponsesAgentClient(HttpClient httpClient) : AgentC
Transport = new HttpClientPipelineTransport(httpClient)
};
var openAiClient = new ResponsesClient(model: agentName, credential: new ApiKeyCredential("dummy-key"), options: options).AsIChatClient();
var openAiClient = new ResponsesClient(credential: new ApiKeyCredential("dummy-key"), options: options).AsIChatClient(agentName);
var chatOptions = new ChatOptions()
{
ConversationId = sessionId
@@ -30,8 +30,8 @@ TokenCredential browserCredential = new InteractiveBrowserCredential(
using IChatClient client = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
.GetResponsesClient(deploymentName)
.AsIChatClient()
.GetResponsesClient()
.AsIChatClient(deploymentName)
.AsBuilder()
.WithPurview(browserCredential, new PurviewSettings("Agent Framework Test App"))
.Build();
@@ -37,7 +37,7 @@
<ItemGroup>
<PackageReference Include="Azure.AI.AgentServer.AgentFramework" Version="1.0.0-beta.9" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.8.0-beta.1" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.9.0-beta.1" />
<PackageReference Include="Azure.Identity" Version="1.17.1" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" Version="1.0.0-rc1" />
</ItemGroup>
@@ -36,9 +36,10 @@
<ItemGroup>
<PackageReference Include="Azure.AI.AgentServer.AgentFramework" Version="1.0.0-beta.9" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.8.0-beta.1" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.9.0-beta.1" />
<PackageReference Include="Azure.Identity" Version="1.17.1" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" Version="1.0.0-rc1" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="10.4.0" />
</ItemGroup>
<!-- Add analyzers with compatible versions -->
@@ -31,7 +31,8 @@ AITool mcpTool = new HostedMcpServerTool(serverName: "microsoft_learn", serverAd
AIAgent agent = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
.GetResponsesClient(deploymentName)
.GetResponsesClient()
.AsIChatClient(deploymentName)
.AsAIAgent(
instructions: "You answer questions by searching the Microsoft Learn content only.",
name: "MicrosoftLearnAgent",
@@ -38,7 +38,7 @@
<ItemGroup>
<PackageReference Include="Azure.AI.AgentServer.AgentFramework" Version="1.0.0-beta.9" />
<PackageReference Include="Azure.AI.Projects" Version="1.2.0-beta.5" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.8.0-beta.1" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.9.0-beta.1" />
<PackageReference Include="Azure.Identity" Version="1.17.1" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" Version="1.0.0-rc1" />
</ItemGroup>
@@ -36,7 +36,7 @@
<ItemGroup>
<PackageReference Include="Azure.AI.AgentServer.AgentFramework" Version="1.0.0-beta.9" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.7.0-beta.2" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.9.0-beta.1" />
<PackageReference Include="Azure.Identity" Version="1.17.1" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" Version="1.0.0-rc1" />
</ItemGroup>
@@ -36,7 +36,7 @@
<ItemGroup>
<PackageReference Include="Azure.AI.AgentServer.AgentFramework" Version="1.0.0-beta.9" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.8.0-beta.1" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.9.0-beta.1" />
<PackageReference Include="Azure.Identity" Version="1.17.1" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" Version="1.0.0-rc1" />
</ItemGroup>
@@ -36,7 +36,7 @@
<ItemGroup>
<PackageReference Include="Azure.AI.AgentServer.AgentFramework" Version="1.0.0-beta.9" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.7.0-beta.2" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.9.0-beta.1" />
<PackageReference Include="Azure.Identity" Version="1.17.1" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" Version="1.0.0-rc1" />
</ItemGroup>
@@ -122,7 +122,7 @@ internal sealed class AFAgentApplication : AgentApplication
&& valueElement.GetProperty("requestJson") is JsonElement requestJsonElement
&& requestJsonElement.ValueKind == JsonValueKind.String)
{
var requestContent = JsonSerializer.Deserialize<FunctionApprovalRequestContent>(requestJsonElement.GetString()!, JsonUtilities.DefaultOptions);
var requestContent = JsonSerializer.Deserialize<ToolApprovalRequestContent>(requestJsonElement.GetString()!, JsonUtilities.DefaultOptions);
return new ChatMessage(ChatRole.User, [requestContent!.CreateResponse(approvedJsonElement.ValueKind == JsonValueKind.True)]);
}
@@ -138,7 +138,7 @@ internal sealed class AFAgentApplication : AgentApplication
/// <param name="attachments">The list of <see cref="Attachment"/> to which the adaptive cards will be added.</param>
private static void HandleUserInputRequests(AgentResponse response, ref List<Attachment>? attachments)
{
foreach (FunctionApprovalRequestContent functionApprovalRequest in response.Messages.SelectMany(m => m.Contents).OfType<FunctionApprovalRequestContent>())
foreach (ToolApprovalRequestContent functionApprovalRequest in response.Messages.SelectMany(m => m.Contents).OfType<ToolApprovalRequestContent>())
{
var functionApprovalRequestJson = JsonSerializer.Serialize(functionApprovalRequest, JsonUtilities.DefaultOptions);
@@ -152,7 +152,7 @@ internal sealed class AFAgentApplication : AgentApplication
});
card.Body.Add(new AdaptiveTextBlock
{
Text = $"Function: {functionApprovalRequest.FunctionCall.Name}"
Text = $"Function: {((FunctionCallContent)functionApprovalRequest.ToolCall).Name}"
});
card.Body.Add(new AdaptiveActionSet()
{
@@ -20,6 +20,8 @@
<!-- NuGet Package Settings -->
<Title>Microsoft Agent Framework AzureAI Persistent Agents</Title>
<Description>Provides Microsoft Agent Framework support for Azure AI Persistent Agents.</Description>
<!-- Disabled until Azure.AI.Agents.Persistent targets ME.AI 10.4.0+ (https://github.com/microsoft/agent-framework/issues/4769) -->
<IsPackable>false</IsPackable>
</PropertyGroup>
</Project>
@@ -0,0 +1,19 @@
# Microsoft.Agents.AI.AzureAI.Persistent
Provides integration between the Microsoft Agent Framework and Azure AI Agents Persistent (`Azure.AI.Agents.Persistent`).
## ⚠️ Known Compatibility Limitation
The underlying `Azure.AI.Agents.Persistent` package (currently 1.2.0-beta.9) targets `Microsoft.Extensions.AI.Abstractions` 10.1.x and references types that were renamed in 10.4.0 (e.g., `McpServerToolApprovalResponseContent``ToolApprovalResponseContent`). This causes `TypeLoadException` at runtime when used with ME.AI 10.4.0+.
**Compatible versions:**
| Package | Compatible Version |
|---|---|
| `Azure.AI.Agents.Persistent` | 1.2.0-beta.9 (targets ME.AI 10.1.x) |
| `Microsoft.Extensions.AI.Abstractions` | ≤ 10.3.0 |
| `OpenAI` | ≤ 2.8.0 |
**Resolution:** An updated version of `Azure.AI.Agents.Persistent` targeting ME.AI 10.4.0+ is expected in 1.2.0-beta.10. The upstream fix is tracked in [Azure/azure-sdk-for-net#56929](https://github.com/Azure/azure-sdk-for-net/pull/56929).
**Tracking issue:** [microsoft/agent-framework#4769](https://github.com/microsoft/agent-framework/issues/4769)
@@ -2,8 +2,9 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Azure.AI.Extensions.OpenAI;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Microsoft.Extensions.AI;
using Microsoft.Shared.DiagnosticIds;
using Microsoft.Shared.Diagnostics;
@@ -57,7 +58,7 @@ internal sealed class AzureAIProjectChatClient : DelegatingChatClient
/// The <see cref="IChatClient"/> provided should be decorated with a <see cref="AzureAIProjectChatClient"/> for proper functionality.
/// </remarks>
internal AzureAIProjectChatClient(AIProjectClient aiProjectClient, AgentRecord agentRecord, ChatOptions? chatOptions)
: this(aiProjectClient, Throw.IfNull(agentRecord).Versions.Latest, chatOptions)
: this(aiProjectClient, Throw.IfNull(agentRecord).GetLatestVersion(), chatOptions)
{
this._agentRecord = agentRecord;
}
@@ -9,7 +9,8 @@ using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Extensions.OpenAI;
using Azure.AI.Projects.Agents;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.AzureAI;
using Microsoft.Extensions.AI;
@@ -189,7 +190,7 @@ public static partial class AzureAIProjectChatClientExtensions
ThrowIfInvalidAgentName(options.Name);
AgentRecord agentRecord = await GetAgentRecordByNameAsync(aiProjectClient, options.Name, cancellationToken).ConfigureAwait(false);
var agentVersion = agentRecord.Versions.Latest;
var agentVersion = agentRecord.GetLatestVersion();
var agentOptions = CreateChatClientAgentOptions(agentVersion, options, requireInvocableTools: !options.UseProvidedChatClientAsIs);
@@ -361,7 +362,7 @@ public static partial class AzureAIProjectChatClientExtensions
{
ClientResult protocolResponse = await aiProjectClient.Agents.GetAgentAsync(agentName, cancellationToken.ToRequestOptions(false)).ConfigureAwait(false);
var rawResponse = protocolResponse.GetRawResponse();
AgentRecord? result = ModelReaderWriter.Read<AgentRecord>(rawResponse.Content, s_modelWriterOptionsWire, AzureAIProjectsOpenAIContext.Default);
AgentRecord? result = ModelReaderWriter.Read<AgentRecord>(rawResponse.Content, s_modelWriterOptionsWire, AzureAIProjectsAgentsContext.Default);
return result ?? throw new InvalidOperationException($"Agent with name '{agentName}' not found.");
}
@@ -370,11 +371,11 @@ public static partial class AzureAIProjectChatClientExtensions
/// </summary>
private static async Task<AgentVersion> CreateAgentVersionWithProtocolAsync(AIProjectClient aiProjectClient, string agentName, AgentVersionCreationOptions creationOptions, CancellationToken cancellationToken)
{
BinaryData serializedOptions = ModelReaderWriter.Write(creationOptions, s_modelWriterOptionsWire, AzureAIProjectsContext.Default);
BinaryData serializedOptions = ModelReaderWriter.Write(creationOptions, s_modelWriterOptionsWire, AzureAIProjectsAgentsContext.Default);
BinaryContent content = BinaryContent.Create(serializedOptions);
ClientResult protocolResponse = await aiProjectClient.Agents.CreateAgentVersionAsync(agentName, content, foundryFeatures: null, cancellationToken.ToRequestOptions(false)).ConfigureAwait(false);
var rawResponse = protocolResponse.GetRawResponse();
AgentVersion? result = ModelReaderWriter.Read<AgentVersion>(rawResponse.Content, s_modelWriterOptionsWire, AzureAIProjectsOpenAIContext.Default);
AgentVersion? result = ModelReaderWriter.Read<AgentVersion>(rawResponse.Content, s_modelWriterOptionsWire, AzureAIProjectsAgentsContext.Default);
return result ?? throw new InvalidOperationException($"Failed to create agent version for agent '{agentName}'.");
}
@@ -485,7 +486,7 @@ public static partial class AzureAIProjectChatClientExtensions
=> AsChatClientAgent(
AIProjectClient,
agentRecord,
CreateChatClientAgentOptions(agentRecord.Versions.Latest, new ChatOptions() { Tools = tools }, requireInvocableTools),
CreateChatClientAgentOptions(agentRecord.GetLatestVersion(), new ChatOptions() { Tools = tools }, requireInvocableTools),
clientFactory,
services);
@@ -15,7 +15,6 @@
<ItemGroup>
<PackageReference Include="Azure.AI.Projects" />
<PackageReference Include="Azure.AI.Projects.OpenAI" />
<PackageReference Include="Microsoft.Extensions.AI" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
<PackageReference Include="OpenAI" />
@@ -196,8 +196,8 @@ internal static class AgentResponseUpdateExtensions
TextReasoningContent => new TextReasoningContentEventGenerator(context.IdGenerator, seq, outputIndex),
FunctionCallContent => new FunctionCallEventGenerator(context.IdGenerator, seq, outputIndex, context.JsonSerializerOptions),
FunctionResultContent => new FunctionResultEventGenerator(context.IdGenerator, seq, outputIndex),
FunctionApprovalRequestContent => new FunctionApprovalRequestEventGenerator(context.IdGenerator, seq, outputIndex, context.JsonSerializerOptions),
FunctionApprovalResponseContent => new FunctionApprovalResponseEventGenerator(context.IdGenerator, seq, outputIndex),
ToolApprovalRequestContent => new ToolApprovalRequestEventGenerator(context.IdGenerator, seq, outputIndex, context.JsonSerializerOptions),
ToolApprovalResponseContent => new ToolApprovalResponseEventGenerator(context.IdGenerator, seq, outputIndex),
ErrorContent => new ErrorContentEventGenerator(context.IdGenerator, seq, outputIndex),
UriContent uriContent when uriContent.HasTopLevelMediaType("image") => new ImageContentEventGenerator(context.IdGenerator, seq, outputIndex),
DataContent dataContent when dataContent.HasTopLevelMediaType("image") => new ImageContentEventGenerator(context.IdGenerator, seq, outputIndex),
@@ -12,33 +12,37 @@ namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses.Streaming;
/// A generator for streaming events from function approval request content.
/// This is a non-standard DevUI extension for human-in-the-loop scenarios.
/// </summary>
internal sealed class FunctionApprovalRequestEventGenerator(
internal sealed class ToolApprovalRequestEventGenerator(
IdGenerator idGenerator,
SequenceNumber seq,
int outputIndex,
JsonSerializerOptions jsonSerializerOptions) : StreamingEventGenerator
{
public override bool IsSupported(AIContent content) => content is FunctionApprovalRequestContent;
public override bool IsSupported(AIContent content) => content is ToolApprovalRequestContent;
public override IEnumerable<StreamingResponseEvent> ProcessContent(AIContent content)
{
if (content is not FunctionApprovalRequestContent approvalRequest)
if (content is not ToolApprovalRequestContent approvalRequest)
{
throw new InvalidOperationException("FunctionApprovalRequestEventGenerator only supports FunctionApprovalRequestContent.");
throw new InvalidOperationException("ToolApprovalRequestEventGenerator only supports ToolApprovalRequestContent.");
}
if (approvalRequest.ToolCall is not FunctionCallContent functionCall)
{
yield break;
}
yield return new StreamingFunctionApprovalRequested
{
SequenceNumber = seq.Increment(),
OutputIndex = outputIndex,
RequestId = approvalRequest.Id,
RequestId = approvalRequest.RequestId,
ItemId = idGenerator.GenerateMessageId(),
FunctionCall = new FunctionCallInfo
{
Id = approvalRequest.FunctionCall.CallId,
Name = approvalRequest.FunctionCall.Name,
Id = functionCall.CallId,
Name = functionCall.Name,
Arguments = JsonSerializer.SerializeToElement(
approvalRequest.FunctionCall.Arguments,
functionCall.Arguments,
jsonSerializerOptions.GetTypeInfo(typeof(IDictionary<string, object>)))
}
};
@@ -11,25 +11,25 @@ namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses.Streaming;
/// A generator for streaming events from function approval response content.
/// This is a non-standard DevUI extension for human-in-the-loop scenarios.
/// </summary>
internal sealed class FunctionApprovalResponseEventGenerator(
internal sealed class ToolApprovalResponseEventGenerator(
IdGenerator idGenerator,
SequenceNumber seq,
int outputIndex) : StreamingEventGenerator
{
public override bool IsSupported(AIContent content) => content is FunctionApprovalResponseContent;
public override bool IsSupported(AIContent content) => content is ToolApprovalResponseContent;
public override IEnumerable<StreamingResponseEvent> ProcessContent(AIContent content)
{
if (content is not FunctionApprovalResponseContent approvalResponse)
if (content is not ToolApprovalResponseContent approvalResponse)
{
throw new InvalidOperationException("FunctionApprovalResponseEventGenerator only supports FunctionApprovalResponseContent.");
throw new InvalidOperationException("ToolApprovalResponseEventGenerator only supports ToolApprovalResponseContent.");
}
yield return new StreamingFunctionApprovalResponded
{
SequenceNumber = seq.Increment(),
OutputIndex = outputIndex,
RequestId = approvalResponse.Id,
RequestId = approvalResponse.RequestId,
Approved = approvalResponse.Approved,
ItemId = idGenerator.GenerateMessageId()
};
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
#pragma warning disable OPENAI001 // Experimental OpenAI features
using System.ClientModel;
using OpenAI.Chat;
@@ -26,6 +26,7 @@ public static class OpenAIResponseClientExtensions
/// Creates an AI agent from an <see cref="ResponsesClient"/> using the OpenAI Response API.
/// </summary>
/// <param name="client">The <see cref="ResponsesClient" /> to use for the agent.</param>
/// <param name="model">Optional default model ID to use for requests. Required when using a plain <see cref="ResponsesClient"/> (not via Azure OpenAI).</param>
/// <param name="instructions">Optional system instructions that define the agent's behavior and personality.</param>
/// <param name="name">Optional name for the agent for identification purposes.</param>
/// <param name="description">Optional description of the agent's capabilities and purpose.</param>
@@ -37,6 +38,7 @@ public static class OpenAIResponseClientExtensions
/// <exception cref="ArgumentNullException">Thrown when <paramref name="client"/> is <see langword="null"/>.</exception>
public static ChatClientAgent AsAIAgent(
this ResponsesClient client,
string? model = null,
string? instructions = null,
string? name = null,
string? description = null,
@@ -58,6 +60,7 @@ public static class OpenAIResponseClientExtensions
Tools = tools,
}
},
model,
clientFactory,
loggerFactory,
services);
@@ -68,6 +71,7 @@ public static class OpenAIResponseClientExtensions
/// </summary>
/// <param name="client">The <see cref="ResponsesClient" /> to use for the agent.</param>
/// <param name="options">Full set of options to configure the agent.</param>
/// <param name="model">Optional default model ID to use for requests. Required when using a plain <see cref="ResponsesClient"/> (not via Azure OpenAI).</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="loggerFactory">Optional logger factory for enabling logging within the agent.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
@@ -76,6 +80,7 @@ public static class OpenAIResponseClientExtensions
public static ChatClientAgent AsAIAgent(
this ResponsesClient client,
ChatClientAgentOptions options,
string? model = null,
Func<IChatClient, IChatClient>? clientFactory = null,
ILoggerFactory? loggerFactory = null,
IServiceProvider? services = null)
@@ -83,7 +88,7 @@ public static class OpenAIResponseClientExtensions
Throw.IfNull(client);
Throw.IfNull(options);
var chatClient = client.AsIChatClient();
var chatClient = client.AsIChatClient(model);
if (clientFactory is not null)
{
@@ -100,6 +105,7 @@ public static class OpenAIResponseClientExtensions
/// This corresponds to setting the "store" property in the JSON representation to false.
/// </remarks>
/// <param name="responseClient">The client.</param>
/// <param name="model">Optional default model ID to use for requests. Required when using a plain <see cref="ResponsesClient"/> (not via Azure OpenAI).</param>
/// <param name="includeReasoningEncryptedContent">
/// Includes an encrypted version of reasoning tokens in reasoning item outputs.
/// This enables reasoning items to be used in multi-turn conversations when using the Responses API statelessly
@@ -109,10 +115,10 @@ public static class OpenAIResponseClientExtensions
/// <returns>An <see cref="IChatClient"/> that can be used to converse via the <see cref="ResponsesClient"/> that does not store responses for later retrieval.</returns>
/// <exception cref="ArgumentNullException"><paramref name="responseClient"/> is <see langword="null"/>.</exception>
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
public static IChatClient AsIChatClientWithStoredOutputDisabled(this ResponsesClient responseClient, bool includeReasoningEncryptedContent = true)
public static IChatClient AsIChatClientWithStoredOutputDisabled(this ResponsesClient responseClient, string? model = null, bool includeReasoningEncryptedContent = true)
{
return Throw.IfNull(responseClient)
.AsIChatClient()
.AsIChatClient(model)
.AsBuilder()
.ConfigureOptions(x => x.RawRepresentationFactory = _ => includeReasoningEncryptedContent
? new CreateResponseOptions() { StoredOutputEnabled = false, IncludedProperties = { IncludedResponseProperty.ReasoningEncryptedContent } }
@@ -10,8 +10,9 @@ using System.Runtime.CompilerServices;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using Azure.AI.Extensions.OpenAI;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Azure.Core;
using Microsoft.Extensions.AI;
using OpenAI.Responses;
@@ -149,7 +150,7 @@ public sealed class AzureAgentProvider(Uri projectEndpoint, TokenCredential proj
agentName,
cancellationToken).ConfigureAwait(false);
targetAgent = agentRecord.Versions.Latest;
targetAgent = agentRecord.GetLatestVersion();
}
else
{
@@ -185,8 +185,8 @@ public sealed class DefaultMcpToolHandler : IMcpToolHandler, IAsyncDisposable
private static void PopulateResultContent(McpServerToolResultContent resultContent, CallToolResult result)
{
// Ensure Output list is initialized
resultContent.Output ??= [];
// Ensure Outputs list is initialized
resultContent.Outputs ??= [];
if (result.IsError == true)
{
@@ -203,7 +203,7 @@ public sealed class DefaultMcpToolHandler : IMcpToolHandler, IAsyncDisposable
}
}
resultContent.Output.Add(new TextContent($"Error: {errorText ?? "Unknown error from MCP Server call"}"));
resultContent.Outputs.Add(new TextContent($"Error: {errorText ?? "Unknown error from MCP Server call"}"));
return;
}
@@ -218,7 +218,7 @@ public sealed class DefaultMcpToolHandler : IMcpToolHandler, IAsyncDisposable
AIContent content = ConvertContentBlock(block);
if (content is not null)
{
resultContent.Output.Add(content);
resultContent.Outputs.Add(content);
}
}
}
@@ -150,7 +150,7 @@ internal sealed class InvokeAzureAgentExecutor(InvokeAzureAgent model, ResponseA
foreach (ChatMessage responseMessage in agentResponse.Messages)
{
if (responseMessage.Contents.Any(content => content is UserInputRequestContent))
if (responseMessage.Contents.Any(content => content is ToolApprovalRequestContent))
{
yield return responseMessage;
continue;
@@ -68,7 +68,7 @@ internal sealed class InvokeFunctionToolExecutor(
// If approval is required, add user input request content
if (requireApproval)
{
requestMessage.Contents.Add(new FunctionApprovalRequestContent(this.Id, functionCall));
requestMessage.Contents.Add(new ToolApprovalRequestContent(this.Id, functionCall));
}
AgentResponse agentResponse = new([requestMessage]);
@@ -85,7 +85,7 @@ internal sealed class InvokeMcpToolExecutor(
toolCall.AdditionalProperties.Add(headers);
}
McpServerToolApprovalRequestContent approvalRequest = new(this.Id, toolCall);
ToolApprovalRequestContent approvalRequest = new(this.Id, toolCall);
ChatMessage requestMessage = new(ChatRole.Assistant, [approvalRequest]);
AgentResponse agentResponse = new([requestMessage]);
@@ -127,11 +127,10 @@ internal sealed class InvokeMcpToolExecutor(
ExternalInputResponse response,
CancellationToken cancellationToken)
{
// Check for approval response
McpServerToolApprovalResponseContent? approvalResponse = response.Messages
ToolApprovalResponseContent? approvalResponse = response.Messages
.SelectMany(m => m.Contents)
.OfType<McpServerToolApprovalResponseContent>()
.FirstOrDefault(r => r.Id == this.Id);
.OfType<ToolApprovalResponseContent>()
.FirstOrDefault(r => r.RequestId == this.Id);
if (approvalResponse?.Approved != true)
{
@@ -174,7 +173,7 @@ internal sealed class InvokeMcpToolExecutor(
string? conversationId = this.GetConversationId();
await this.AssignResultAsync(context, resultContent).ConfigureAwait(false);
ChatMessage resultMessage = new(ChatRole.Tool, resultContent.Output);
ChatMessage resultMessage = new(ChatRole.Tool, resultContent.Outputs);
// Store messages if output path is configured
if (this.Model.Output?.Messages is not null)
@@ -192,20 +191,20 @@ internal sealed class InvokeMcpToolExecutor(
// Add messages to conversation if conversationId is provided
if (conversationId is not null)
{
ChatMessage assistantMessage = new(ChatRole.Assistant, resultContent.Output);
ChatMessage assistantMessage = new(ChatRole.Assistant, resultContent.Outputs);
await agentProvider.CreateMessageAsync(conversationId, assistantMessage, cancellationToken).ConfigureAwait(false);
}
}
private async ValueTask AssignResultAsync(IWorkflowContext context, McpServerToolResultContent toolResult)
{
if (this.Model.Output?.Result is null || toolResult.Output is null || toolResult.Output.Count == 0)
if (this.Model.Output?.Result is null || toolResult.Outputs is null || toolResult.Outputs.Count == 0)
{
return;
}
List<object?> parsedResults = [];
foreach (AIContent resultContent in toolResult.Output)
foreach (AIContent resultContent in toolResult.Outputs)
{
object? resultValue = resultContent switch
{
@@ -21,7 +21,7 @@ public sealed class AIAgentHostOptions
public bool EmitAgentResponseEvents { get; set; }
/// <summary>
/// Gets or sets a value indicating whether <see cref="UserInputRequestContent"/> should be intercepted and sent
/// Gets or sets a value indicating whether <see cref="ToolApprovalRequestContent"/> should be intercepted and sent
/// as a message to the workflow for handling, instead of being raised as a request.
/// </summary>
public bool InterceptUserInputRequests { get; set; }
@@ -19,7 +19,7 @@ internal sealed class AIAgentHostExecutor : ChatProtocolExecutor
private AgentSession? _session;
private bool? _currentTurnEmitEvents;
private AIContentExternalHandler<UserInputRequestContent, UserInputResponseContent>? _userInputHandler;
private AIContentExternalHandler<ToolApprovalRequestContent, ToolApprovalResponseContent>? _userInputHandler;
private AIContentExternalHandler<FunctionCallContent, FunctionResultContent>? _functionCallHandler;
private static readonly ChatProtocolExecutorOptions s_defaultChatProtocolOptions = new()
@@ -38,7 +38,7 @@ internal sealed class AIAgentHostExecutor : ChatProtocolExecutor
private ProtocolBuilder ConfigureUserInputHandling(ProtocolBuilder protocolBuilder)
{
this._userInputHandler = new AIContentExternalHandler<UserInputRequestContent, UserInputResponseContent>(
this._userInputHandler = new AIContentExternalHandler<ToolApprovalRequestContent, ToolApprovalResponseContent>(
ref protocolBuilder,
portId: $"{this.Id}_UserInput",
intercepted: this._options.InterceptUserInputRequests,
@@ -59,13 +59,13 @@ internal sealed class AIAgentHostExecutor : ChatProtocolExecutor
}
private ValueTask HandleUserInputResponseAsync(
UserInputResponseContent response,
ToolApprovalResponseContent response,
IWorkflowContext context,
CancellationToken cancellationToken)
{
if (!this._userInputHandler!.MarkRequestAsHandled(response.Id))
if (!this._userInputHandler!.MarkRequestAsHandled(response.RequestId))
{
throw new InvalidOperationException($"No pending UserInputRequest found with id '{response.Id}'.");
throw new InvalidOperationException($"No pending ToolApprovalRequest found with id '{response.RequestId}'.");
}
List<ChatMessage> implicitTurnMessages = [new ChatMessage(ChatRole.User, [response])];
@@ -164,7 +164,7 @@ internal sealed class AIAgentHostExecutor : ChatProtocolExecutor
private async ValueTask<AgentResponse> InvokeAgentAsync(IEnumerable<ChatMessage> messages, IWorkflowContext context, bool emitEvents, CancellationToken cancellationToken = default)
{
#pragma warning disable MEAI001
Dictionary<string, UserInputRequestContent> userInputRequests = new();
Dictionary<string, ToolApprovalRequestContent> userInputRequests = new();
Dictionary<string, FunctionCallContent> functionCalls = new();
AgentResponse response;
@@ -218,15 +218,15 @@ internal sealed class AIAgentHostExecutor : ChatProtocolExecutor
{
foreach (AIContent content in contents)
{
if (content is UserInputRequestContent userInputRequest)
if (content is ToolApprovalRequestContent userInputRequest)
{
// It is an error to simultaneously have multiple outstanding user input requests with the same ID.
userInputRequests.Add(userInputRequest.Id, userInputRequest);
userInputRequests.Add(userInputRequest.RequestId, userInputRequest);
}
else if (content is UserInputResponseContent userInputResponse)
else if (content is ToolApprovalResponseContent userInputResponse)
{
// If the set of messages somehow already has a corresponding user input response, remove it.
_ = userInputRequests.Remove(userInputResponse.Id);
_ = userInputRequests.Remove(userInputResponse.RequestId);
}
else if (content is FunctionCallContent functionCall)
{
@@ -4,8 +4,9 @@
using System;
using System.Threading.Tasks;
using Azure.AI.Extensions.OpenAI;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
namespace Shared.Foundry;
@@ -306,8 +306,7 @@ internal sealed class WorkflowRunner
requestItem switch
{
FunctionCallContent functionCall when !functionCall.InformationalOnly => await InvokeFunctionAsync(functionCall).ConfigureAwait(false),
FunctionApprovalRequestContent functionApprovalRequest => ApproveFunction(functionApprovalRequest),
McpServerToolApprovalRequestContent mcpApprovalRequest => ApproveMCP(mcpApprovalRequest),
ToolApprovalRequestContent approvalRequest => ApproveToolCall(approvalRequest),
_ => HandleUnknown(requestItem),
};
@@ -325,16 +324,16 @@ internal sealed class WorkflowRunner
return null;
}
ChatMessage ApproveFunction(FunctionApprovalRequestContent functionApprovalRequest)
ChatMessage ApproveToolCall(ToolApprovalRequestContent approvalRequest)
{
Notify($"INPUT - Approving Function: {functionApprovalRequest.FunctionCall.Name}");
return new ChatMessage(ChatRole.User, [functionApprovalRequest.CreateResponse(approved: true)]);
}
ChatMessage ApproveMCP(McpServerToolApprovalRequestContent mcpApprovalRequest)
{
Notify($"INPUT - Approving MCP: {mcpApprovalRequest.ToolCall.ToolName}");
return new ChatMessage(ChatRole.User, [mcpApprovalRequest.CreateResponse(approved: true)]);
string toolName = approvalRequest.ToolCall switch
{
McpServerToolCallContent mcp => mcp.Name,
FunctionCallContent f => f.Name,
_ => approvalRequest.ToolCall!.CallId
};
Notify($"INPUT - Approving: {toolName}");
return new ChatMessage(ChatRole.User, [approvalRequest.CreateResponse(approved: true)]);
}
async Task<ChatMessage> InvokeFunctionAsync(FunctionCallContent functionCall)
@@ -5,7 +5,7 @@ using System.IO;
using System.Threading.Tasks;
using AgentConformance.IntegrationTests.Support;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI.Files;
@@ -56,8 +56,8 @@ public class AIProjectClientCreateTests
var agentRecord = await this._client.Agents.GetAgentAsync(agent.Name);
Assert.NotNull(agentRecord);
Assert.Equal(AgentName, agentRecord.Value.Name);
var definition = Assert.IsType<PromptAgentDefinition>(agentRecord.Value.Versions.Latest.Definition);
Assert.Equal(AgentDescription, agentRecord.Value.Versions.Latest.Description);
var definition = Assert.IsType<PromptAgentDefinition>(agentRecord.Value.GetLatestVersion().Definition);
Assert.Equal(AgentDescription, agentRecord.Value.GetLatestVersion().Description);
Assert.Equal(AgentInstructions, definition.Instructions);
}
finally
@@ -6,8 +6,8 @@ using System.Linq;
using System.Threading.Tasks;
using AgentConformance.IntegrationTests;
using AgentConformance.IntegrationTests.Support;
using Azure.AI.Extensions.OpenAI;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI.Responses;
@@ -4,6 +4,10 @@ using AgentConformance.IntegrationTests;
namespace AzureAIAgentsPersistent.IntegrationTests;
// Disabled: Azure.AI.Agents.Persistent 1.2.0-beta.9 references McpServerToolApprovalResponseContent
// which was removed in ME.AI 10.4.0. Re-enable once Persistent targets ME.AI 10.4.0+ (expected in 1.2.0-beta.10).
// Tracking: https://github.com/microsoft/agent-framework/issues/4769
[Trait("Category", "IntegrationDisabled")]
public class AzureAIAgentsChatClientAgentRunStreamingTests() : ChatClientAgentRunStreamingTests<AzureAIAgentsPersistentFixture>(() => new())
{
}
@@ -4,6 +4,10 @@ using AgentConformance.IntegrationTests;
namespace AzureAIAgentsPersistent.IntegrationTests;
// Disabled: Azure.AI.Agents.Persistent 1.2.0-beta.9 references McpServerToolApprovalResponseContent
// which was removed in ME.AI 10.4.0. Re-enable once Persistent targets ME.AI 10.4.0+ (expected in 1.2.0-beta.10).
// Tracking: https://github.com/microsoft/agent-framework/issues/4769
[Trait("Category", "IntegrationDisabled")]
public class AzureAIAgentsChatClientAgentRunTests() : ChatClientAgentRunTests<AzureAIAgentsPersistentFixture>(() => new())
{
}
@@ -12,6 +12,10 @@ using Shared.IntegrationTests;
namespace AzureAIAgentsPersistent.IntegrationTests;
// Disabled: Azure.AI.Agents.Persistent 1.2.0-beta.9 references McpServerToolApprovalResponseContent
// which was removed in ME.AI 10.4.0. Re-enable once Persistent targets ME.AI 10.4.0+ (expected in 1.2.0-beta.10).
// Tracking: https://github.com/microsoft/agent-framework/issues/4769
[Trait("Category", "IntegrationDisabled")]
public class AzureAIAgentsPersistentCreateTests
{
private const string SkipCodeInterpreterReason = "Azure AI Code Interpreter intermittently fails to execute uploaded files in CI";
@@ -4,6 +4,10 @@ using AgentConformance.IntegrationTests;
namespace AzureAIAgentsPersistent.IntegrationTests;
// Disabled: Azure.AI.Agents.Persistent 1.2.0-beta.9 references McpServerToolApprovalResponseContent
// which was removed in ME.AI 10.4.0. Re-enable once Persistent targets ME.AI 10.4.0+ (expected in 1.2.0-beta.10).
// Tracking: https://github.com/microsoft/agent-framework/issues/4769
[Trait("Category", "IntegrationDisabled")]
public class AzureAIAgentsPersistentRunStreamingTests() : RunStreamingTests<AzureAIAgentsPersistentFixture>(() => new())
{
}
@@ -4,6 +4,10 @@ using AgentConformance.IntegrationTests;
namespace AzureAIAgentsPersistent.IntegrationTests;
// Disabled: Azure.AI.Agents.Persistent 1.2.0-beta.9 references McpServerToolApprovalResponseContent
// which was removed in ME.AI 10.4.0. Re-enable once Persistent targets ME.AI 10.4.0+ (expected in 1.2.0-beta.10).
// Tracking: https://github.com/microsoft/agent-framework/issues/4769
[Trait("Category", "IntegrationDisabled")]
public class AzureAIAgentsPersistentRunTests() : RunTests<AzureAIAgentsPersistentFixture>(() => new())
{
}
@@ -5,6 +5,10 @@ using AgentConformance.IntegrationTests;
namespace AzureAIAgentsPersistent.IntegrationTests;
// Disabled: Azure.AI.Agents.Persistent 1.2.0-beta.9 references McpServerToolApprovalResponseContent
// which was removed in ME.AI 10.4.0. Re-enable once Persistent targets ME.AI 10.4.0+ (expected in 1.2.0-beta.10).
// Tracking: https://github.com/microsoft/agent-framework/issues/4769
[Trait("Category", "IntegrationDisabled")]
public class AzureAIAgentsPersistentStructuredOutputRunTests() : StructuredOutputRunTests<AzureAIAgentsPersistentFixture>(() => new())
{
private const string SkipReason = "Fails intermittently on the build agent/CI";
@@ -12,8 +12,9 @@ using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Azure.AI.Extensions.OpenAI;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Microsoft.Extensions.AI;
using Moq;
using OpenAI.Responses;
@@ -369,7 +370,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
public async Task GetAIAgentAsync_ByName_WithNonExistentAgent_ThrowsInvalidOperationExceptionAsync()
{
// Arrange
var mockAgentOperations = new Mock<AIProjectAgentsOperations>();
var mockAgentOperations = new Mock<AgentsClient>();
mockAgentOperations
.Setup(c => c.GetAgentAsync(It.IsAny<string>(), It.IsAny<RequestOptions>()))
.ReturnsAsync(ClientResult.FromOptionalValue((AgentRecord)null!, new MockPipelineResponse(200, BinaryData.FromString("null"))));
@@ -889,12 +890,12 @@ public sealed class AzureAIProjectChatClientExtensionsTests
// Add tools to the definition
definition.Tools.Add(ResponseTool.CreateFunctionTool("create_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
definition.Tools.Add((ResponseTool)AgentTool.CreateBingCustomSearchTool(new BingCustomSearchToolParameters([new BingCustomSearchConfiguration("connection-id", "instance-name")])));
definition.Tools.Add((ResponseTool)AgentTool.CreateBrowserAutomationTool(new BrowserAutomationToolParameters(new BrowserAutomationToolConnectionParameters("id"))));
definition.Tools.Add((ResponseTool)AgentTool.CreateBingCustomSearchTool(new BingCustomSearchToolOptions([new BingCustomSearchConfiguration("connection-id", "instance-name")])));
definition.Tools.Add((ResponseTool)AgentTool.CreateBrowserAutomationTool(new BrowserAutomationToolOptions(new BrowserAutomationToolConnectionParameters("id"))));
definition.Tools.Add(AgentTool.CreateA2ATool(new Uri("https://test-uri.microsoft.com")));
definition.Tools.Add((ResponseTool)AgentTool.CreateBingGroundingTool(new BingGroundingSearchToolOptions([new BingGroundingSearchConfiguration("connection-id")])));
definition.Tools.Add((ResponseTool)AgentTool.CreateMicrosoftFabricTool(fabricToolOptions));
definition.Tools.Add((ResponseTool)AgentTool.CreateOpenApiTool(new OpenAPIFunctionDefinition("name", BinaryData.FromString(OpenAPISpec), new OpenAPIAnonymousAuthenticationDetails())));
definition.Tools.Add((ResponseTool)AgentTool.CreateOpenApiTool(new OpenApiFunctionDefinition("name", BinaryData.FromString(OpenAPISpec), new OpenAPIAnonymousAuthenticationDetails())));
definition.Tools.Add((ResponseTool)AgentTool.CreateSharepointTool(sharepointOptions));
definition.Tools.Add((ResponseTool)AgentTool.CreateStructuredOutputsTool(structuredOutputs));
definition.Tools.Add((ResponseTool)AgentTool.CreateAzureAISearchTool(new AzureAISearchToolOptions([new AzureAISearchToolIndex() { IndexName = "name" }])));
@@ -3020,7 +3021,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
{
// Handle backward compatibility with bool parameter
var effectiveVersionMode = useEmptyVersion ? VersionMode.Empty : versionMode;
this.Agents = new FakeAIProjectAgentsOperations(agentName, instructions, description, agentDefinitionResponse, effectiveVersionMode);
this.Agents = new FakeAgentsClient(agentName, instructions, description, agentDefinitionResponse, effectiveVersionMode);
}
public override ClientConnection GetConnection(string connectionId)
@@ -3028,9 +3029,9 @@ public sealed class AzureAIProjectChatClientExtensionsTests
return new ClientConnection("fake-connection-id", "http://localhost", ClientPipeline.Create(), CredentialKind.None);
}
public override AIProjectAgentsOperations Agents { get; }
public override AgentsClient Agents { get; }
private sealed class FakeAIProjectAgentsOperations : AIProjectAgentsOperations
private sealed class FakeAgentsClient : AgentsClient
{
private readonly string? _agentName;
private readonly string? _instructions;
@@ -3038,7 +3039,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
private readonly AgentDefinition? _agentDefinition;
private readonly VersionMode _versionMode;
public FakeAIProjectAgentsOperations(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null, VersionMode versionMode = VersionMode.Normal)
public FakeAgentsClient(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null, VersionMode versionMode = VersionMode.Normal)
{
this._agentName = agentName;
this._instructions = instructions;
@@ -2,7 +2,7 @@
using System.ClientModel.Primitives;
using System.IO;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
namespace Microsoft.Agents.AI.AzureAI.UnitTests;
@@ -20,7 +20,7 @@ public sealed class FunctionApprovalTests : ConformanceTestBase
// Streaming request JSON for OpenAI Responses API
private const string StreamingRequestJson = @"{""model"":""gpt-4o-mini"",""input"":""test"",""stream"":true}";
#region FunctionApprovalRequestContent Tests
#region ToolApprovalRequestContent Tests
[Fact]
public async Task FunctionApprovalRequest_GeneratesCorrectEvent_SuccessAsync()
@@ -34,7 +34,7 @@ public sealed class FunctionApprovalTests : ConformanceTestBase
#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates
FunctionCallContent functionCall = new(FunctionId, FunctionName, arguments);
FunctionApprovalRequestContent approvalRequest = new(RequestId, functionCall);
ToolApprovalRequestContent approvalRequest = new(RequestId, functionCall);
#pragma warning restore MEAI001
HttpClient client = await this.CreateTestServerAsync(AgentName, "You are a test agent.", string.Empty, (msg) =>
@@ -81,7 +81,7 @@ public sealed class FunctionApprovalTests : ConformanceTestBase
#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates
FunctionCallContent functionCall = new(FunctionId, FunctionName, arguments);
FunctionApprovalRequestContent approvalRequest = new(RequestId, functionCall);
ToolApprovalRequestContent approvalRequest = new(RequestId, functionCall);
#pragma warning restore MEAI001
HttpClient client = await this.CreateTestServerAsync(AgentName, "You are a test agent.", string.Empty, (msg) =>
@@ -114,7 +114,7 @@ public sealed class FunctionApprovalTests : ConformanceTestBase
#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates
FunctionCallContent functionCall = new("call-1", "test_function", new Dictionary<string, object?>());
FunctionApprovalRequestContent approvalRequest = new("req-1", functionCall);
ToolApprovalRequestContent approvalRequest = new("req-1", functionCall);
#pragma warning restore MEAI001
HttpClient client = await this.CreateTestServerAsync(AgentName, "You are a test agent.", string.Empty, (msg) =>
@@ -150,7 +150,7 @@ public sealed class FunctionApprovalTests : ConformanceTestBase
#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates
FunctionCallContent functionCall = new("call-1", "test", new Dictionary<string, object?>());
FunctionApprovalRequestContent approvalRequest = new("req-1", functionCall);
ToolApprovalRequestContent approvalRequest = new("req-1", functionCall);
#pragma warning restore MEAI001
HttpClient client = await this.CreateTestServerAsync(AgentName, "You are a test agent.", string.Empty, (msg) =>
@@ -173,7 +173,7 @@ public sealed class FunctionApprovalTests : ConformanceTestBase
#endregion
#region FunctionApprovalResponseContent Tests
#region ToolApprovalResponseContent Tests
[Fact]
public async Task FunctionApprovalResponse_Approved_GeneratesCorrectEvent_SuccessAsync()
@@ -187,7 +187,7 @@ public sealed class FunctionApprovalTests : ConformanceTestBase
#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates
FunctionCallContent functionCall = new(FunctionId, FunctionName, arguments);
FunctionApprovalResponseContent approvalResponse = new(RequestId, approved: true, functionCall);
ToolApprovalResponseContent approvalResponse = new(RequestId, approved: true, functionCall);
#pragma warning restore MEAI001
HttpClient client = await this.CreateTestServerAsync(AgentName, "You are a test agent.", string.Empty, (msg) =>
@@ -221,7 +221,7 @@ public sealed class FunctionApprovalTests : ConformanceTestBase
#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates
FunctionCallContent functionCall = new(FunctionId, FunctionName, new Dictionary<string, object?> { ["path"] = "/important.txt" });
FunctionApprovalResponseContent approvalResponse = new(RequestId, approved: false, functionCall);
ToolApprovalResponseContent approvalResponse = new(RequestId, approved: false, functionCall);
#pragma warning restore MEAI001
HttpClient client = await this.CreateTestServerAsync(AgentName, "You are a test agent.", string.Empty, (msg) =>
@@ -249,7 +249,7 @@ public sealed class FunctionApprovalTests : ConformanceTestBase
#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates
FunctionCallContent functionCall = new("call-1", "test_function", new Dictionary<string, object?>());
FunctionApprovalResponseContent approvalResponse = new("req-1", approved: true, functionCall);
ToolApprovalResponseContent approvalResponse = new("req-1", approved: true, functionCall);
#pragma warning restore MEAI001
HttpClient client = await this.CreateTestServerAsync(AgentName, "You are a test agent.", string.Empty, (msg) =>
@@ -279,7 +279,7 @@ public sealed class FunctionApprovalTests : ConformanceTestBase
#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates
FunctionCallContent functionCall = new("call-mixed-1", "test", new Dictionary<string, object?>());
FunctionApprovalRequestContent approvalRequest = new("req-mixed-1", functionCall);
ToolApprovalRequestContent approvalRequest = new("req-mixed-1", functionCall);
#pragma warning restore MEAI001
HttpClient client = await this.CreateTestServerAsync(AgentName, "You are a test agent.", string.Empty, (msg) =>
@@ -308,10 +308,10 @@ public sealed class FunctionApprovalTests : ConformanceTestBase
#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates
FunctionCallContent functionCall1 = new("call-multi-1", "function1", new Dictionary<string, object?>());
FunctionApprovalRequestContent approvalRequest1 = new("req-multi-1", functionCall1);
ToolApprovalRequestContent approvalRequest1 = new("req-multi-1", functionCall1);
FunctionCallContent functionCall2 = new("call-multi-2", "function2", new Dictionary<string, object?>());
FunctionApprovalRequestContent approvalRequest2 = new("req-multi-2", functionCall2);
ToolApprovalRequestContent approvalRequest2 = new("req-multi-2", functionCall2);
#pragma warning restore MEAI001
HttpClient client = await this.CreateTestServerAsync(AgentName, "You are a test agent.", string.Empty, (msg) =>
@@ -52,7 +52,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Count to 3");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Count to 3");
// Assert
List<StreamingResponseUpdate> updates = [];
@@ -93,7 +93,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
ResponseResult response = await responseClient.CreateResponseAsync("Hello");
ResponseResult response = await responseClient.CreateResponseAsync("test-model", "Hello");
// Assert
Assert.NotNull(response);
@@ -120,7 +120,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Test");
// Assert
List<StreamingResponseUpdate> updates = [];
@@ -166,8 +166,8 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient2 = this.CreateResponseClient(Agent2Name);
// Act
ResponseResult response1 = await responseClient1.CreateResponseAsync("Hello");
ResponseResult response2 = await responseClient2.CreateResponseAsync("Hello");
ResponseResult response1 = await responseClient1.CreateResponseAsync("test-model", "Hello");
ResponseResult response2 = await responseClient2.CreateResponseAsync("test-model", "Hello");
// Assert
string content1 = response1.GetOutputText();
@@ -193,10 +193,10 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act - Non-streaming
ResponseResult nonStreamingResponse = await responseClient.CreateResponseAsync("Test");
ResponseResult nonStreamingResponse = await responseClient.CreateResponseAsync("test-model", "Test");
// Act - Streaming
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Test");
StringBuilder streamingContent = new();
await foreach (StreamingResponseUpdate update in streamingResult)
{
@@ -227,7 +227,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
ResponseResult response = await responseClient.CreateResponseAsync("Test");
ResponseResult response = await responseClient.CreateResponseAsync("test-model", "Test");
// Assert
Assert.Equal(ResponseStatus.Completed, response.Status);
@@ -250,7 +250,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Test");
// Assert
List<StreamingResponseUpdate> updates = [];
@@ -289,7 +289,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Test");
// Assert
List<StreamingResponseUpdate> updates = [];
@@ -319,7 +319,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
ResponseResult response = await responseClient.CreateResponseAsync("Test");
ResponseResult response = await responseClient.CreateResponseAsync("test-model", "Test");
// Assert
Assert.NotNull(response.Id);
@@ -343,7 +343,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Generate long text");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Generate long text");
// Assert
StringBuilder contentBuilder = new();
@@ -374,7 +374,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Test");
// Assert
List<int> outputIndices = [];
@@ -410,7 +410,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Test");
// Assert
StringBuilder contentBuilder = new();
@@ -440,7 +440,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Test");
// Assert
StringBuilder contentBuilder = new();
@@ -470,7 +470,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
ResponseResult response = await responseClient.CreateResponseAsync("Test");
ResponseResult response = await responseClient.CreateResponseAsync("test-model", "Test");
// Assert
string content = response.GetOutputText();
@@ -492,7 +492,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Test");
// Assert
List<string> itemIds = [];
@@ -530,7 +530,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
// Act & Assert - Make 5 sequential requests
for (int i = 0; i < 5; i++)
{
ResponseResult response = await responseClient.CreateResponseAsync($"Request {i}");
ResponseResult response = await responseClient.CreateResponseAsync("test-model", $"Request {i}");
Assert.NotNull(response);
Assert.Equal(ResponseStatus.Completed, response.Status);
Assert.Equal(ExpectedResponse, response.GetOutputText());
@@ -554,7 +554,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
// Act & Assert - Make 3 sequential streaming requests
for (int i = 0; i < 3; i++)
{
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync($"Request {i}");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", $"Request {i}");
StringBuilder contentBuilder = new();
await foreach (StreamingResponseUpdate update in streamingResult)
@@ -587,7 +587,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
List<string> responseIds = [];
for (int i = 0; i < 10; i++)
{
ResponseResult response = await responseClient.CreateResponseAsync($"Request {i}");
ResponseResult response = await responseClient.CreateResponseAsync("test-model", $"Request {i}");
responseIds.Add(response.Id);
}
@@ -611,7 +611,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Test");
// Assert
List<int> sequenceNumbers = [];
@@ -644,7 +644,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
ResponseResult response = await responseClient.CreateResponseAsync("Test");
ResponseResult response = await responseClient.CreateResponseAsync("test-model", "Test");
// Assert
Assert.NotNull(response.Model);
@@ -666,7 +666,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Test");
// Assert
StringBuilder contentBuilder = new();
@@ -696,7 +696,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
ResponseResult response = await responseClient.CreateResponseAsync("Hi");
ResponseResult response = await responseClient.CreateResponseAsync("test-model", "Hi");
// Assert
Assert.NotNull(response);
@@ -719,7 +719,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Test");
// Assert
List<int> contentIndices = [];
@@ -751,7 +751,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
ResponseResult response = await responseClient.CreateResponseAsync("Test");
ResponseResult response = await responseClient.CreateResponseAsync("test-model", "Test");
// Assert
string content = response.GetOutputText();
@@ -774,7 +774,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Test");
// Assert
StringBuilder contentBuilder = new();
@@ -810,7 +810,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
ResponseResult response = await responseClient.CreateResponseAsync("Show me an image");
ResponseResult response = await responseClient.CreateResponseAsync("test-model", "Show me an image");
// Assert
Assert.NotNull(response);
@@ -837,7 +837,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Show me an image");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Show me an image");
// Assert
List<StreamingResponseUpdate> updates = [];
@@ -871,7 +871,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
ResponseResult response = await responseClient.CreateResponseAsync("Generate audio");
ResponseResult response = await responseClient.CreateResponseAsync("test-model", "Generate audio");
// Assert
Assert.NotNull(response);
@@ -899,7 +899,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Generate audio");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Generate audio");
// Assert
List<StreamingResponseUpdate> updates = [];
@@ -933,7 +933,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
ResponseResult response = await responseClient.CreateResponseAsync("What's the weather?");
ResponseResult response = await responseClient.CreateResponseAsync("test-model", "What's the weather?");
// Assert
Assert.NotNull(response);
@@ -960,7 +960,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Calculate 2+2");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Calculate 2+2");
// Assert
List<StreamingResponseUpdate> updates = [];
@@ -991,7 +991,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
ResponseResult response = await responseClient.CreateResponseAsync("Show me various content");
ResponseResult response = await responseClient.CreateResponseAsync("test-model", "Show me various content");
// Assert
Assert.NotNull(response);
@@ -1017,7 +1017,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Show me various content");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Show me various content");
// Assert
List<StreamingResponseUpdate> updates = [];
@@ -1050,7 +1050,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Test");
// Assert
List<StreamingResponseUpdate> updates = [];
@@ -1078,7 +1078,7 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
ResponsesClient responseClient = this.CreateResponseClient(AgentName);
// Act
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("Test");
AsyncCollectionResult<StreamingResponseUpdate> streamingResult = responseClient.CreateResponseStreamingAsync("test-model", "Test");
// Assert
List<StreamingResponseUpdate> updates = [];
@@ -1273,7 +1273,6 @@ public sealed class OpenAIResponsesIntegrationTests : IAsyncDisposable
private ResponsesClient CreateResponseClient(string agentName)
{
return new ResponsesClient(
model: "test-model",
credential: new ApiKeyCredential("test-api-key"),
options: new OpenAIClientOptions
{
@@ -603,7 +603,47 @@ public class OpenTelemetryAgentTests
Assert.False(tags.ContainsKey("gen_ai.input.messages"));
Assert.False(tags.ContainsKey("gen_ai.output.messages"));
Assert.False(tags.ContainsKey("gen_ai.system_instructions"));
Assert.False(tags.ContainsKey("gen_ai.tool.definitions"));
// gen_ai.tool.definitions is always emitted regardless of EnableSensitiveData (ME.AI 10.4.0+)
Assert.Equal(ReplaceWhitespace("""
[
{
"type": "function",
"name": "GetPersonAge",
"description": "Gets the age of a person by name.",
"parameters": {
"type": "object",
"properties": {
"personName": {
"type": "string"
}
},
"required": [
"personName"
]
}
},
{
"type": "web_search"
},
{
"type": "function",
"name": "GetCurrentWeather",
"description": "Gets the current weather for a location.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string"
}
},
"required": [
"location"
]
}
}
]
"""), ReplaceWhitespace(tags["gen_ai.tool.definitions"]));
}
}
@@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Microsoft.Extensions.Configuration;
using Shared.IntegrationTests;
@@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using OpenAI.Responses;
@@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Microsoft.Extensions.Configuration;
using Shared.Foundry;
using Shared.IntegrationTests;
@@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Microsoft.Extensions.Configuration;
using Shared.Foundry;
using Shared.IntegrationTests;
@@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Microsoft.Extensions.Configuration;
using Shared.Foundry;
using Shared.IntegrationTests;
@@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Microsoft.Extensions.Configuration;
using Shared.Foundry;
using Shared.IntegrationTests;
@@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.AI.Projects.Agents;
using Microsoft.Extensions.Configuration;
using Shared.Foundry;
using Shared.IntegrationTests;
@@ -94,7 +94,7 @@ public abstract class WorkflowTest(ITestOutputHelper output) : IntegrationTest(o
while (current is not null)
{
if (Directory.Exists(Path.Combine(current.FullName, ".git")))
if (Directory.Exists(Path.Combine(current.FullName, "workflow-samples")))
{
return current.FullName;
}
@@ -123,9 +123,9 @@ public sealed class InvokeToolWorkflowTest(ITestOutputHelper output) : Integrati
foreach (ChatMessage message in toolRequest.AgentResponse.Messages)
{
// Handle approval requests if present
foreach (FunctionApprovalRequestContent approvalRequest in message.Contents.OfType<FunctionApprovalRequestContent>())
foreach (ToolApprovalRequestContent approvalRequest in message.Contents.OfType<ToolApprovalRequestContent>())
{
this.Output.WriteLine($"APPROVAL REQUEST: {approvalRequest.FunctionCall.Name}");
this.Output.WriteLine($"APPROVAL REQUEST: {((FunctionCallContent)approvalRequest.ToolCall).Name}");
// Auto-approve for testing
results.Add(approvalRequest.CreateResponse(approved: true));
}
@@ -233,12 +233,12 @@ public sealed class InvokeToolWorkflowTest(ITestOutputHelper output) : Integrati
foreach (ChatMessage message in toolRequest.AgentResponse.Messages)
{
// Handle MCP approval requests if present
foreach (McpServerToolApprovalRequestContent approvalRequest in message.Contents.OfType<McpServerToolApprovalRequestContent>())
foreach (ToolApprovalRequestContent approvalRequest in message.Contents.OfType<ToolApprovalRequestContent>())
{
this.Output.WriteLine($"MCP APPROVAL REQUEST: {approvalRequest.Id}");
this.Output.WriteLine($"MCP APPROVAL REQUEST: {approvalRequest.RequestId}");
// Respond based on test configuration
McpServerToolApprovalResponseContent response = approvalRequest.CreateResponse(approved: approveRequest);
ToolApprovalResponseContent response = approvalRequest.CreateResponse(approved: approveRequest);
results.Add(response);
this.Output.WriteLine($"MCP APPROVAL RESPONSE: {(approveRequest ? "Approved" : "Rejected")}");
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using Microsoft.Agents.AI.Workflows.Declarative.Events;
using Microsoft.Extensions.AI;
@@ -33,8 +35,8 @@ public sealed class ExternalInputRequestTest(ITestOutputHelper output) : EventTe
new ChatMessage(
ChatRole.Assistant,
[
new McpServerToolApprovalRequestContent("call1", new McpServerToolCallContent("call1", "testmcp", "server-name")),
new FunctionApprovalRequestContent("call2", new FunctionCallContent("call2", "result1")),
new ToolApprovalRequestContent("call1", new McpServerToolCallContent("call1", "testmcp", "server-name")),
new ToolApprovalRequestContent("call2", new FunctionCallContent("call2", "result1")),
new FunctionCallContent("call3", "myfunc"),
new TextContent("Heya"),
])));
@@ -46,11 +48,14 @@ public sealed class ExternalInputRequestTest(ITestOutputHelper output) : EventTe
ChatMessage messageCopy = Assert.Single(source.AgentResponse.Messages);
Assert.Equal(messageCopy.Contents.Count, copy.AgentResponse.Messages[0].Contents.Count);
McpServerToolApprovalRequestContent mcpRequest = AssertContent<McpServerToolApprovalRequestContent>(messageCopy);
Assert.Equal("call1", mcpRequest.Id);
List<ToolApprovalRequestContent> approvalRequests = messageCopy.Contents.OfType<ToolApprovalRequestContent>().ToList();
Assert.Equal(2, approvalRequests.Count);
FunctionApprovalRequestContent functionRequest = AssertContent<FunctionApprovalRequestContent>(messageCopy);
Assert.Equal("call2", functionRequest.Id);
ToolApprovalRequestContent mcpRequest = approvalRequests[0];
Assert.Equal("call1", mcpRequest.RequestId);
ToolApprovalRequestContent functionRequest = approvalRequests[1];
Assert.Equal("call2", functionRequest.RequestId);
FunctionCallContent functionCall = AssertContent<FunctionCallContent>(messageCopy);
Assert.Equal("call3", functionCall.CallId);
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using Microsoft.Agents.AI.Workflows.Declarative.Events;
using Microsoft.Extensions.AI;
@@ -32,8 +34,8 @@ public sealed class ExternalInputResponseTest(ITestOutputHelper output) : EventT
new(new ChatMessage(
ChatRole.Assistant,
[
new McpServerToolApprovalRequestContent("call1", new McpServerToolCallContent("call1", "testmcp", "server-name")).CreateResponse(approved: true),
new FunctionApprovalRequestContent("call2", new FunctionCallContent("call2", "result1")).CreateResponse(approved: true),
new ToolApprovalRequestContent("call1", new McpServerToolCallContent("call1", "testmcp", "server-name")).CreateResponse(approved: true),
new ToolApprovalRequestContent("call2", new FunctionCallContent("call2", "result1")).CreateResponse(approved: true),
new FunctionResultContent("call3", 33),
new TextContent("Heya"),
]));
@@ -45,11 +47,14 @@ public sealed class ExternalInputResponseTest(ITestOutputHelper output) : EventT
ChatMessage responseMessage = Assert.Single(source.Messages);
Assert.Equal(responseMessage.Contents.Count, copy.Messages[0].Contents.Count);
McpServerToolApprovalResponseContent mcpApproval = AssertContent<McpServerToolApprovalResponseContent>(responseMessage);
Assert.Equal("call1", mcpApproval.Id);
List<ToolApprovalResponseContent> approvalResponses = responseMessage.Contents.OfType<ToolApprovalResponseContent>().ToList();
Assert.Equal(2, approvalResponses.Count);
FunctionApprovalResponseContent functionApproval = AssertContent<FunctionApprovalResponseContent>(responseMessage);
Assert.Equal("call2", functionApproval.Id);
ToolApprovalResponseContent mcpApproval = approvalResponses[0];
Assert.Equal("call1", mcpApproval.RequestId);
ToolApprovalResponseContent functionApproval = approvalResponses[1];
Assert.Equal("call2", functionApproval.RequestId);
FunctionResultContent functionResult = AssertContent<FunctionResultContent>(responseMessage);
Assert.Equal("call3", functionResult.CallId);
@@ -473,8 +473,8 @@ public sealed class InvokeMcpToolExecutorTest(ITestOutputHelper output) : Workfl
// Create approval request then response
McpServerToolCallContent toolCall = new(action.Id, TestToolName, TestServerUrl);
McpServerToolApprovalRequestContent approvalRequest = new(action.Id, toolCall);
McpServerToolApprovalResponseContent approvalResponse = approvalRequest.CreateResponse(approved: true);
ToolApprovalRequestContent approvalRequest = new(action.Id, toolCall);
ToolApprovalResponseContent approvalResponse = approvalRequest.CreateResponse(approved: true);
ExternalInputResponse response = new(new ChatMessage(ChatRole.User, [approvalResponse]));
// Act
@@ -501,8 +501,8 @@ public sealed class InvokeMcpToolExecutorTest(ITestOutputHelper output) : Workfl
// Create approval request then response (rejected)
McpServerToolCallContent toolCall = new(action.Id, TestToolName, TestServerUrl);
McpServerToolApprovalRequestContent approvalRequest = new(action.Id, toolCall);
McpServerToolApprovalResponseContent approvalResponse = approvalRequest.CreateResponse(approved: false);
ToolApprovalRequestContent approvalRequest = new(action.Id, toolCall);
ToolApprovalResponseContent approvalResponse = approvalRequest.CreateResponse(approved: false);
ExternalInputResponse response = new(new ChatMessage(ChatRole.User, [approvalResponse]));
// Act
@@ -552,8 +552,8 @@ public sealed class InvokeMcpToolExecutorTest(ITestOutputHelper output) : Workfl
// Create approval with different ID
McpServerToolCallContent toolCall = new("different_id", TestToolName, TestServerUrl);
McpServerToolApprovalRequestContent approvalRequest = new("different_id", toolCall);
McpServerToolApprovalResponseContent approvalResponse = approvalRequest.CreateResponse(approved: true);
ToolApprovalRequestContent approvalRequest = new("different_id", toolCall);
ToolApprovalResponseContent approvalResponse = approvalRequest.CreateResponse(approved: true);
ExternalInputResponse response = new(new ChatMessage(ChatRole.User, [approvalResponse]));
// Act
@@ -582,8 +582,8 @@ public sealed class InvokeMcpToolExecutorTest(ITestOutputHelper output) : Workfl
// Create approval request then response
McpServerToolCallContent toolCall = new(action.Id, TestToolName, TestServerUrl);
McpServerToolApprovalRequestContent approvalRequest = new(action.Id, toolCall);
McpServerToolApprovalResponseContent approvalResponse = approvalRequest.CreateResponse(approved: true);
ToolApprovalRequestContent approvalRequest = new(action.Id, toolCall);
ToolApprovalResponseContent approvalResponse = approvalRequest.CreateResponse(approved: true);
ExternalInputResponse response = new(new ChatMessage(ChatRole.User, [approvalResponse]));
// Act
@@ -613,8 +613,8 @@ public sealed class InvokeMcpToolExecutorTest(ITestOutputHelper output) : Workfl
// Create approval request then response
McpServerToolCallContent toolCall = new(action.Id, TestToolName, TestServerLabel);
McpServerToolApprovalRequestContent approvalRequest = new(action.Id, toolCall);
McpServerToolApprovalResponseContent approvalResponse = approvalRequest.CreateResponse(approved: true);
ToolApprovalRequestContent approvalRequest = new(action.Id, toolCall);
ToolApprovalResponseContent approvalResponse = approvalRequest.CreateResponse(approved: true);
ExternalInputResponse response = new(new ChatMessage(ChatRole.User, [approvalResponse]));
// Act
@@ -643,8 +643,8 @@ public sealed class InvokeMcpToolExecutorTest(ITestOutputHelper output) : Workfl
// Create approval request then response
McpServerToolCallContent toolCall = new(action.Id, TestToolName, TestServerUrl);
McpServerToolApprovalRequestContent approvalRequest = new(action.Id, toolCall);
McpServerToolApprovalResponseContent approvalResponse = approvalRequest.CreateResponse(approved: true);
ToolApprovalRequestContent approvalRequest = new(action.Id, toolCall);
ToolApprovalResponseContent approvalResponse = approvalRequest.CreateResponse(approved: true);
ExternalInputResponse response = new(new ChatMessage(ChatRole.User, [approvalResponse]));
// Act
@@ -799,31 +799,31 @@ public sealed class InvokeMcpToolExecutorTest(ITestOutputHelper output) : Workfl
if (returnNullOutput)
{
result.Output = null;
result.Outputs = null;
}
else if (returnEmptyOutput)
{
result.Output = [];
result.Outputs = [];
}
else if (returnJsonObject)
{
result.Output = [new TextContent("{\"key\": \"value\", \"number\": 42}")];
result.Outputs = [new TextContent("{\"key\": \"value\", \"number\": 42}")];
}
else if (returnJsonArray)
{
result.Output = [new TextContent("[1, 2, 3, \"four\"]")];
result.Outputs = [new TextContent("[1, 2, 3, \"four\"]")];
}
else if (returnInvalidJson)
{
result.Output = [new TextContent("this is not valid json {")];
result.Outputs = [new TextContent("this is not valid json {")];
}
else if (returnDataContent)
{
result.Output = [new DataContent("data:image/png;base64,iVBORw0KGgo=", "image/png")];
result.Outputs = [new DataContent("data:image/png;base64,iVBORw0KGgo=", "image/png")];
}
else if (returnMultipleContent)
{
result.Output =
result.Outputs =
[
new TextContent("First text"),
new TextContent("{\"nested\": true}"),
@@ -832,7 +832,7 @@ public sealed class InvokeMcpToolExecutorTest(ITestOutputHelper output) : Workfl
}
else
{
result.Output = [new TextContent("Mock MCP tool result")];
result.Outputs = [new TextContent("Mock MCP tool result")];
}
return Task.FromResult(result);
@@ -229,7 +229,7 @@ public class AIAgentHostExecutorTests
responses = ExtractAndValidateRequestContents<FunctionCallContent>();
break;
case TestAgentRequestType.UserInputRequest:
responses = ExtractAndValidateRequestContents<UserInputRequestContent>();
responses = ExtractAndValidateRequestContents<ToolApprovalRequestContent>();
break;
default:
throw new NotSupportedException();
@@ -33,7 +33,7 @@ internal sealed class TestRequestAgent(TestAgentRequestType requestType, int unp
=> new(requestType switch
{
TestAgentRequestType.FunctionCall => new TestRequestAgentSession<FunctionCallContent, FunctionResultContent>(),
TestAgentRequestType.UserInputRequest => new TestRequestAgentSession<UserInputRequestContent, UserInputResponseContent>(),
TestAgentRequestType.UserInputRequest => new TestRequestAgentSession<ToolApprovalRequestContent, ToolApprovalResponseContent>(),
_ => throw new NotSupportedException(),
});
@@ -41,7 +41,7 @@ internal sealed class TestRequestAgent(TestAgentRequestType requestType, int unp
=> new(requestType switch
{
TestAgentRequestType.FunctionCall => new TestRequestAgentSession<FunctionCallContent, FunctionResultContent>(),
TestAgentRequestType.UserInputRequest => new TestRequestAgentSession<UserInputRequestContent, UserInputResponseContent>(),
TestAgentRequestType.UserInputRequest => new TestRequestAgentSession<ToolApprovalRequestContent, ToolApprovalResponseContent>(),
_ => throw new NotSupportedException(),
});
@@ -179,58 +179,43 @@ internal sealed class TestRequestAgent(TestAgentRequestType requestType, int unp
}
}
private sealed class FunctionApprovalStrategy : IRequestResponseStrategy<UserInputRequestContent, UserInputResponseContent>
private sealed class FunctionApprovalStrategy : IRequestResponseStrategy<ToolApprovalRequestContent, ToolApprovalResponseContent>
{
public UserInputResponseContent CreatePairedResponse(UserInputRequestContent request)
public ToolApprovalResponseContent CreatePairedResponse(ToolApprovalRequestContent request)
{
if (request is not FunctionApprovalRequestContent approvalRequest)
{
throw new InvalidOperationException($"Invalid request: Expecting {typeof(FunctionApprovalResponseContent)}, got {request.GetType()}");
}
return new FunctionApprovalResponseContent(approvalRequest.Id, true, approvalRequest.FunctionCall);
return new ToolApprovalResponseContent(request.RequestId, true, request.ToolCall);
}
public IEnumerable<(string, UserInputRequestContent)> CreateRequests(int count)
public IEnumerable<(string, ToolApprovalRequestContent)> CreateRequests(int count)
{
for (int i = 0; i < count; i++)
{
string id = Guid.NewGuid().ToString("N");
UserInputRequestContent request = new FunctionApprovalRequestContent(id, new(id, "TestFunction"));
ToolApprovalRequestContent request = new(id, new FunctionCallContent(id, "TestFunction"));
yield return (id, request);
}
}
public void ProcessResponse(UserInputResponseContent response, TestRequestAgentSession<UserInputRequestContent, UserInputResponseContent> session)
public void ProcessResponse(ToolApprovalResponseContent response, TestRequestAgentSession<ToolApprovalRequestContent, ToolApprovalResponseContent> session)
{
if (session.UnservicedRequests.TryGetValue(response.Id, out UserInputRequestContent? request))
if (session.UnservicedRequests.TryGetValue(response.RequestId, out ToolApprovalRequestContent? request))
{
if (request is not FunctionApprovalRequestContent approvalRequest)
{
throw new InvalidOperationException($"Invalid request: Expecting {typeof(FunctionApprovalResponseContent)}, got {request.GetType()}");
}
if (response is not FunctionApprovalResponseContent approvalResponse)
{
throw new InvalidOperationException($"Invalid response: Expecting {typeof(FunctionApprovalResponseContent)}, got {response.GetType()}");
}
approvalResponse.Approved.Should().BeTrue();
approvalResponse.FunctionCall.As<FunctionCallContent>().Should().Be(approvalRequest.FunctionCall);
session.ServicedRequests.Add(response.Id);
session.UnservicedRequests.Remove(response.Id);
response.Approved.Should().BeTrue();
((FunctionCallContent)response.ToolCall).Should().Be((FunctionCallContent)request.ToolCall);
session.ServicedRequests.Add(response.RequestId);
session.UnservicedRequests.Remove(response.RequestId);
}
else if (session.ServicedRequests.Contains(response.Id))
else if (session.ServicedRequests.Contains(response.RequestId))
{
throw new InvalidOperationException($"Seeing duplicate response with id {response.Id}");
throw new InvalidOperationException($"Seeing duplicate response with id {response.RequestId}");
}
else if (session.PairedRequests.Contains(response.Id))
else if (session.PairedRequests.Contains(response.RequestId))
{
throw new InvalidOperationException($"Seeing explicit response to initially paired request with id {response.Id}");
throw new InvalidOperationException($"Seeing explicit response to initially paired request with id {response.RequestId}");
}
else
{
throw new InvalidOperationException($"Seeing response to nonexistent request with id {response.Id}");
throw new InvalidOperationException($"Seeing response to nonexistent request with id {response.RequestId}");
}
}
}
@@ -261,7 +246,7 @@ internal sealed class TestRequestAgent(TestAgentRequestType requestType, int unp
return request switch
{
FunctionCallContent functionCall => functionCall.CallId,
UserInputRequestContent userInputRequest => userInputRequest.Id,
ToolApprovalRequestContent userInputRequest => userInputRequest.RequestId,
_ => throw new NotSupportedException($"Unknown request type {typeof(TRequest)}"),
};
}
@@ -295,12 +280,12 @@ internal sealed class TestRequestAgent(TestAgentRequestType requestType, int unp
return this.ValidateUnpairedRequests((IEnumerable<FunctionCallContent>)requests, new FunctionCallStrategy());
case TestAgentRequestType.UserInputRequest:
if (!typeof(UserInputRequestContent).IsAssignableFrom(typeof(TRequest)))
if (!typeof(ToolApprovalRequestContent).IsAssignableFrom(typeof(TRequest)))
{
throw new ArgumentException($"Invalid request type: Expected {typeof(UserInputRequestContent)}, got {typeof(TRequest)}", nameof(requests));
throw new ArgumentException($"Invalid request type: Expected {typeof(ToolApprovalRequestContent)}, got {typeof(TRequest)}", nameof(requests));
}
return this.ValidateUnpairedRequests((IEnumerable<UserInputRequestContent>)requests, new FunctionApprovalStrategy());
return this.ValidateUnpairedRequests((IEnumerable<ToolApprovalRequestContent>)requests, new FunctionApprovalStrategy());
default:
throw new NotSupportedException($"Unknown AgentRequestType {requestType}");
}
@@ -315,7 +300,7 @@ internal sealed class TestRequestAgent(TestAgentRequestType requestType, int unp
responses = this.ValidateUnpairedRequests(requests.Select(AssertAndExtractRequestContent<FunctionCallContent>)).ToList();
break;
case TestAgentRequestType.UserInputRequest:
responses = this.ValidateUnpairedRequests(requests.Select(AssertAndExtractRequestContent<UserInputRequestContent>)).ToList();
responses = this.ValidateUnpairedRequests(requests.Select(AssertAndExtractRequestContent<ToolApprovalRequestContent>)).ToList();
break;
default:
throw new NotSupportedException($"Unknown AgentRequestType {requestType}");

Some files were not shown because too many files have changed in this diff Show More