mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.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:
co-authored by
Copilot
copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
stephentoub
rogerbarreto
parent
8edcb282f4
commit
88ea9d08c7
+2
@@ -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" />
|
||||
|
||||
+2
-2
@@ -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
-8
@@ -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>)))
|
||||
}
|
||||
};
|
||||
|
||||
+5
-5
@@ -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()
|
||||
};
|
||||
|
||||
+2
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+1
-1
@@ -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]);
|
||||
|
||||
+8
-9
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user