mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Address PR review: pre-wire AsAIAgent path and dedup TryApplyUserAgent
* FoundryAgent: extract WireClientHeaders helper and call it from the internal (AIProjectClient, ChatClientAgent) constructor used by AzureAIProjectChatClientExtensions.AsAIAgent so those Foundry-built agents also pre-wire the x-client header pipeline. * Foundry.Hosting TryApplyUserAgent: dedup HostedAgentUserAgentPolicy registration per OpenAIRequestPolicies instance via ConditionalWeakTable so per-request resolution does not grow the policy list unboundedly on singleton agents.
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.ClientModel.Primitives;
|
using System.ClientModel.Primitives;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using Azure.AI.AgentServer.Responses;
|
using Azure.AI.AgentServer.Responses;
|
||||||
using Azure.Core;
|
using Azure.Core;
|
||||||
using Azure.Identity;
|
using Azure.Identity;
|
||||||
@@ -230,12 +231,21 @@ public static class FoundryHostingExtensions
|
|||||||
var chatClient = agent.GetService<IChatClient>();
|
var chatClient = agent.GetService<IChatClient>();
|
||||||
if (chatClient?.GetService<OpenAIRequestPolicies>() is { } policies)
|
if (chatClient?.GetService<OpenAIRequestPolicies>() is { } policies)
|
||||||
{
|
{
|
||||||
// The HostedAgentUserAgentPolicy is idempotent on the wire (it skips when the
|
// Hosted agents are typically singletons resolved per request, so AddPolicy must be
|
||||||
// supplement is already present in the User-Agent header), so we can register it
|
// called at most once per OpenAIRequestPolicies instance to avoid unbounded growth of
|
||||||
// unconditionally without dedup. MEAI's lock-free CAS-append on _entries is safe.
|
// the policy list (each entry adds per-request CPU work even though the User-Agent
|
||||||
policies.AddPolicy(HostedAgentUserAgentPolicy.Instance, PipelinePosition.PerCall);
|
// value stays stable). Track which instances we have already wired with a
|
||||||
|
// ConditionalWeakTable keyed on the OpenAIRequestPolicies reference; the table holds
|
||||||
|
// weak references so it does not extend the lifetime of the chat client.
|
||||||
|
if (s_userAgentRegistrations.TryAdd(policies, s_boxedTrue))
|
||||||
|
{
|
||||||
|
policies.AddPolicy(HostedAgentUserAgentPolicy.Instance, PipelinePosition.PerCall);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return agent;
|
return agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly object s_boxedTrue = new();
|
||||||
|
private static readonly ConditionalWeakTable<OpenAIRequestPolicies, object> s_userAgentRegistrations = new();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ public sealed class FoundryAgent : DelegatingAIAgent
|
|||||||
/// Internal constructor used by <c>AsAIAgent</c> extension methods that already have an <see cref="AIProjectClient"/> and a configured <see cref="ChatClientAgent"/>.
|
/// Internal constructor used by <c>AsAIAgent</c> extension methods that already have an <see cref="AIProjectClient"/> and a configured <see cref="ChatClientAgent"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal FoundryAgent(AIProjectClient aiProjectClient, ChatClientAgent innerAgent)
|
internal FoundryAgent(AIProjectClient aiProjectClient, ChatClientAgent innerAgent)
|
||||||
: base(Throw.IfNull(innerAgent))
|
: base(WireClientHeaders(Throw.IfNull(innerAgent)))
|
||||||
{
|
{
|
||||||
this._aiProjectClient = Throw.IfNull(aiProjectClient);
|
this._aiProjectClient = Throw.IfNull(aiProjectClient);
|
||||||
}
|
}
|
||||||
@@ -166,7 +166,7 @@ public sealed class FoundryAgent : DelegatingAIAgent
|
|||||||
|
|
||||||
#region Private helpers
|
#region Private helpers
|
||||||
|
|
||||||
private static ClientHeadersAgent CreateInnerAgent(
|
private static AIAgent CreateInnerAgent(
|
||||||
AIProjectClient aiProjectClient,
|
AIProjectClient aiProjectClient,
|
||||||
string model, string instructions,
|
string model, string instructions,
|
||||||
string? name, string? description,
|
string? name, string? description,
|
||||||
@@ -196,7 +196,7 @@ public sealed class FoundryAgent : DelegatingAIAgent
|
|||||||
return CreateResponsesChatClientAgent(aiProjectClient, options, clientFactory, loggerFactory, services);
|
return CreateResponsesChatClientAgent(aiProjectClient, options, clientFactory, loggerFactory, services);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ClientHeadersAgent CreateResponsesChatClientAgent(
|
private static AIAgent CreateResponsesChatClientAgent(
|
||||||
AIProjectClient aiProjectClient,
|
AIProjectClient aiProjectClient,
|
||||||
ChatClientAgentOptions agentOptions,
|
ChatClientAgentOptions agentOptions,
|
||||||
Func<IChatClient, IChatClient>? clientFactory,
|
Func<IChatClient, IChatClient>? clientFactory,
|
||||||
@@ -215,9 +215,25 @@ public sealed class FoundryAgent : DelegatingAIAgent
|
|||||||
chatClient = clientFactory(chatClient);
|
chatClient = clientFactory(chatClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the ClientHeadersPolicy on the chat client's OpenAIRequestPolicies, if available.
|
return WireClientHeaders(new ChatClientAgent(chatClient, agentOptions, loggerFactory, services));
|
||||||
// Silent no-op when the chat client is not OpenAI-backed.
|
}
|
||||||
if (chatClient.GetService<OpenAIRequestPolicies>() is { } policies)
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers <see cref="ClientHeadersPolicy"/> on the agent's underlying chat client (if it
|
||||||
|
/// exposes <see cref="OpenAIRequestPolicies"/>) and wraps the agent in a
|
||||||
|
/// <see cref="ClientHeadersAgent"/> so per-call <c>x-client-*</c> headers stamped via
|
||||||
|
/// <see cref="ClientHeadersExtensions.WithClientHeader(ChatOptions, string, string)"/> reach
|
||||||
|
/// the wire. Idempotent: if the chain already contains a <see cref="ClientHeadersAgent"/>,
|
||||||
|
/// the original instance is returned unchanged.
|
||||||
|
/// </summary>
|
||||||
|
private static AIAgent WireClientHeaders(ChatClientAgent innerAgent)
|
||||||
|
{
|
||||||
|
if (innerAgent.GetService<ClientHeadersAgent>() is not null)
|
||||||
|
{
|
||||||
|
return innerAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (innerAgent.ChatClient.GetService<OpenAIRequestPolicies>() is { } policies)
|
||||||
{
|
{
|
||||||
OpenAIRequestPoliciesReflection.AddPolicyIfMissing(
|
OpenAIRequestPoliciesReflection.AddPolicyIfMissing(
|
||||||
policies,
|
policies,
|
||||||
@@ -225,11 +241,10 @@ public sealed class FoundryAgent : DelegatingAIAgent
|
|||||||
System.ClientModel.Primitives.PipelinePosition.PerCall);
|
System.ClientModel.Primitives.PipelinePosition.PerCall);
|
||||||
}
|
}
|
||||||
|
|
||||||
var inner = new ChatClientAgent(chatClient, agentOptions, loggerFactory, services);
|
return new ClientHeadersAgent(innerAgent);
|
||||||
return new ClientHeadersAgent(inner);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ClientHeadersAgent CreateInnerAgentFromEndpoint(
|
private static AIAgent CreateInnerAgentFromEndpoint(
|
||||||
AIProjectClient aiProjectClient,
|
AIProjectClient aiProjectClient,
|
||||||
Uri agentEndpoint,
|
Uri agentEndpoint,
|
||||||
IList<AITool>? tools,
|
IList<AITool>? tools,
|
||||||
@@ -254,16 +269,7 @@ public sealed class FoundryAgent : DelegatingAIAgent
|
|||||||
chatClient = clientFactory(chatClient);
|
chatClient = clientFactory(chatClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chatClient.GetService<OpenAIRequestPolicies>() is { } policies)
|
return WireClientHeaders(new ChatClientAgent(chatClient, agentOptions, services: services));
|
||||||
{
|
|
||||||
OpenAIRequestPoliciesReflection.AddPolicyIfMissing(
|
|
||||||
policies,
|
|
||||||
ClientHeadersPolicy.Instance,
|
|
||||||
System.ClientModel.Primitives.PipelinePosition.PerCall);
|
|
||||||
}
|
|
||||||
|
|
||||||
var inner = new ChatClientAgent(chatClient, agentOptions, services: services);
|
|
||||||
return new ClientHeadersAgent(inner);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AIProjectClient CreateProjectClient(Uri endpoint, AuthenticationTokenProvider credential, AIProjectClientOptions? clientOptions = null)
|
private static AIProjectClient CreateProjectClient(Uri endpoint, AuthenticationTokenProvider credential, AIProjectClientOptions? clientOptions = null)
|
||||||
|
|||||||
Reference in New Issue
Block a user