// Copyright (c) Microsoft. All rights reserved. using System; using System.ClientModel; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Azure.AI.Extensions.OpenAI; using Azure.AI.Projects; using Microsoft.Extensions.AI; using Microsoft.Extensions.Logging; using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI.Foundry; /// /// Provides an that uses Microsoft Foundry for AI agent capabilities. /// /// /// /// connects to a pre-configured server-side agent in Microsoft Foundry, /// wrapping it as an for use with Agent Framework. Unlike the direct /// AIProjectClient.AsAIAgent(model, instructions) approach (which creates a local agent /// backed by the Responses API without any server-side agent definition), /// works with agents that are managed and versioned in the Foundry service. /// /// /// This class provides convenient access to Foundry-specific features such as server-side /// conversation management via . /// /// /// Instances can be created directly via public constructors or through /// AsAIAgent extension methods on . /// /// [Experimental(DiagnosticIds.Experiments.AIOpenAIResponses)] public sealed class FoundryAgent : DelegatingAIAgent { private readonly AIProjectClient _aiProjectClient; /// /// Initializes a new instance of the class using the direct Responses API path. /// /// The Microsoft Foundry project endpoint. /// The authentication credential. /// The model deployment name. /// The instructions that guide the agent's behavior. /// Optional configuration options for the . /// Optional name for the agent. /// Optional description for the agent. /// Optional tools to use when interacting with the agent. /// Provides a way to customize the creation of the underlying . /// Optional logger factory for creating loggers used by the agent. /// Optional service provider for resolving dependencies required by AI functions. public FoundryAgent( Uri projectEndpoint, AuthenticationTokenProvider credential, string model, string instructions, AIProjectClientOptions? clientOptions = null, string? name = null, string? description = null, IList? tools = null, Func? clientFactory = null, ILoggerFactory? loggerFactory = null, IServiceProvider? services = null) : base(CreateInnerAgent( CreateProjectClient(projectEndpoint, credential, clientOptions), model, instructions, name, description, tools, clientFactory, loggerFactory, services, out var aiProjectClient)) { this._aiProjectClient = aiProjectClient; } /// /// Initializes a new instance of the class from an agent-specific endpoint. /// /// The agent-specific endpoint URI (must contain the agent name in the path). /// The authentication credential. /// Optional configuration options for the . /// Optional tools to use when interacting with the agent. /// Provides a way to customize the creation of the underlying . /// Optional service provider for resolving dependencies required by AI functions. public FoundryAgent( Uri agentEndpoint, AuthenticationTokenProvider credential, AIProjectClientOptions? clientOptions = null, IList? tools = null, Func? clientFactory = null, IServiceProvider? services = null) : base(CreateInnerAgentFromEndpoint( CreateProjectClient(agentEndpoint, credential, clientOptions), agentEndpoint, tools, clientFactory, services, out var aiProjectClient)) { this._aiProjectClient = aiProjectClient; } /// /// Internal constructor used by AsAIAgent extension methods that already have an and a configured . /// internal FoundryAgent(AIProjectClient aiProjectClient, ChatClientAgent innerAgent) : base(WireClientHeaders(Throw.IfNull(innerAgent))) { this._aiProjectClient = Throw.IfNull(aiProjectClient); } #region Convenience methods /// /// Creates a new agent session instance using an existing conversation identifier to continue that conversation. /// /// The identifier of an existing conversation to continue. /// The to monitor for cancellation requests. /// /// A value task representing the asynchronous operation. The task result contains a new instance configured to work with the specified conversation. /// /// /// /// This method creates an that relies on server-side chat history storage, where the chat history /// is maintained by the underlying AI service rather than by a local . /// /// /// Agent sessions created with this method will only work with /// instances that support server-side conversation storage through their underlying . /// /// public ValueTask CreateSessionAsync(string conversationId, CancellationToken cancellationToken = default) => this.GetInnerChatClientAgent().CreateSessionAsync(conversationId, cancellationToken); /// /// Creates a server-side conversation session that appears in the Foundry Project UI. /// /// A token to monitor for cancellation requests. /// A linked to the newly created server-side conversation. public async Task CreateConversationSessionAsync(CancellationToken cancellationToken = default) { var conversationsClient = this._aiProjectClient .GetProjectOpenAIClient() .GetProjectConversationsClient(); var conversation = (await conversationsClient.CreateProjectConversationAsync(options: null, cancellationToken).ConfigureAwait(false)).Value; return (ChatClientAgentSession)await this.GetInnerChatClientAgent().CreateSessionAsync(conversation.Id, cancellationToken).ConfigureAwait(false); } /// Walks the delegating chain to find the inner . private ChatClientAgent GetInnerChatClientAgent() => this.GetService() ?? throw new InvalidOperationException("FoundryAgent inner chain does not contain a ChatClientAgent."); #endregion /// public override object? GetService(Type serviceType, object? serviceKey = null) { if (serviceKey is null && serviceType == typeof(AIProjectClient)) { return this._aiProjectClient; } return base.GetService(serviceType, serviceKey); } #region Private helpers private static AIAgent CreateInnerAgent( AIProjectClient aiProjectClient, string model, string instructions, string? name, string? description, IList? tools, Func? clientFactory, ILoggerFactory? loggerFactory, IServiceProvider? services, out AIProjectClient outClient) { Throw.IfNullOrWhitespace(model); Throw.IfNullOrWhitespace(instructions); outClient = aiProjectClient; ChatClientAgentOptions options = new() { Name = name, Description = description, ChatOptions = new ChatOptions { ModelId = model, Instructions = instructions, Tools = tools, }, }; return CreateResponsesChatClientAgent(aiProjectClient, options, clientFactory, loggerFactory, services); } private static AIAgent CreateResponsesChatClientAgent( AIProjectClient aiProjectClient, ChatClientAgentOptions agentOptions, Func? clientFactory, ILoggerFactory? loggerFactory, IServiceProvider? services) { Throw.IfNull(aiProjectClient); Throw.IfNull(agentOptions); Throw.IfNull(agentOptions.ChatOptions); Throw.IfNullOrWhitespace(agentOptions.ChatOptions.ModelId); IChatClient chatClient = new AzureAIProjectResponsesChatClient(aiProjectClient, agentOptions.ChatOptions.ModelId); if (clientFactory is not null) { chatClient = clientFactory(chatClient); } return WireClientHeaders(new ChatClientAgent(chatClient, agentOptions, loggerFactory, services)); } /// /// Registers on the agent's underlying chat client (if it /// exposes ) and wraps the agent in a /// so per-call x-client-* headers stamped via /// reach /// the wire. Idempotent: if the chain already contains a , /// the original instance is returned unchanged. /// private static AIAgent WireClientHeaders(ChatClientAgent innerAgent) { if (innerAgent.GetService() is not null) { return innerAgent; } if (innerAgent.ChatClient.GetService() is { } policies) { OpenAIRequestPoliciesReflection.AddPolicyIfMissing( policies, ClientHeadersPolicy.Instance, System.ClientModel.Primitives.PipelinePosition.PerCall); } return new ClientHeadersAgent(innerAgent); } private static AIAgent CreateInnerAgentFromEndpoint( AIProjectClient aiProjectClient, Uri agentEndpoint, IList? tools, Func? clientFactory, IServiceProvider? services, out AIProjectClient outClient) { outClient = aiProjectClient; AgentReference agentReference = agentEndpoint.Segments[^1].TrimEnd('/'); ChatClientAgentOptions agentOptions = new() { Name = agentReference.Name, ChatOptions = new() { Tools = tools }, }; IChatClient chatClient = new AzureAIProjectChatClient(aiProjectClient, agentReference, defaultModelId: null, agentOptions.ChatOptions); if (clientFactory is not null) { chatClient = clientFactory(chatClient); } return WireClientHeaders(new ChatClientAgent(chatClient, agentOptions, services: services)); } private static AIProjectClient CreateProjectClient(Uri endpoint, AuthenticationTokenProvider credential, AIProjectClientOptions? clientOptions = null) { Throw.IfNull(endpoint); Throw.IfNull(credential); clientOptions ??= new AIProjectClientOptions(); clientOptions.AddPolicy(RequestOptionsExtensions.UserAgentPolicy, System.ClientModel.Primitives.PipelinePosition.PerCall); return new AIProjectClient(endpoint, credential, clientOptions); } #endregion }