// Copyright (c) Microsoft. All rights reserved. using System.Runtime.CompilerServices; using Azure.AI.Projects; using Azure.AI.Projects.OpenAI; using Microsoft.Extensions.AI; using Microsoft.Shared.Diagnostics; using OpenAI.Responses; #pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. namespace Microsoft.Agents.AI.AzureAI; /// /// Provides a chat client implementation that integrates with Azure AI Agents, enabling chat interactions using /// Azure-specific agent capabilities. /// internal sealed class AzureAIProjectChatClient : DelegatingChatClient { private readonly ChatClientMetadata? _metadata; private readonly AIProjectClient _agentClient; private readonly AgentVersion? _agentVersion; private readonly AgentRecord? _agentRecord; private readonly ChatOptions? _chatOptions; private readonly AgentReference _agentReference; /// /// Initializes a new instance of the class. /// /// An instance of to interact with Azure AI Agents services. /// An instance of representing the specific agent to use. /// The default model to use for the agent, if applicable. /// An instance of representing the options on how the agent was predefined. /// /// The provided should be decorated with a for proper functionality. /// internal AzureAIProjectChatClient(AIProjectClient aiProjectClient, AgentReference agentReference, string? defaultModelId, ChatOptions? chatOptions) : base(Throw.IfNull(aiProjectClient) .GetProjectOpenAIClient() .GetProjectResponsesClientForAgent(agentReference) .AsIChatClient()) { this._agentClient = aiProjectClient; this._agentReference = Throw.IfNull(agentReference); this._metadata = new ChatClientMetadata("azure.ai.agents", defaultModelId: defaultModelId); this._chatOptions = chatOptions; } /// /// Initializes a new instance of the class. /// /// An instance of to interact with Azure AI Agents services. /// An instance of representing the specific agent to use. /// An instance of representing the options on how the agent was predefined. /// /// The provided should be decorated with a for proper functionality. /// internal AzureAIProjectChatClient(AIProjectClient aiProjectClient, AgentRecord agentRecord, ChatOptions? chatOptions) : this(aiProjectClient, Throw.IfNull(agentRecord).Versions.Latest, chatOptions) { this._agentRecord = agentRecord; } internal AzureAIProjectChatClient(AIProjectClient aiProjectClient, AgentVersion agentVersion, ChatOptions? chatOptions) : this( aiProjectClient, new AgentReference(Throw.IfNull(agentVersion).Name, agentVersion.Version), (agentVersion.Definition as PromptAgentDefinition)?.Model, chatOptions) { this._agentVersion = agentVersion; } /// public override object? GetService(Type serviceType, object? serviceKey = null) { return (serviceKey is null && serviceType == typeof(ChatClientMetadata)) ? this._metadata : (serviceKey is null && serviceType == typeof(AIProjectClient)) ? this._agentClient : (serviceKey is null && serviceType == typeof(AgentVersion)) ? this._agentVersion : (serviceKey is null && serviceType == typeof(AgentRecord)) ? this._agentRecord : (serviceKey is null && serviceType == typeof(AgentReference)) ? this._agentReference : base.GetService(serviceType, serviceKey); } /// public override async Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default) { var agentOptions = this.GetAgentEnabledChatOptions(options); return await base.GetResponseAsync(messages, agentOptions, cancellationToken).ConfigureAwait(false); } /// public override async IAsyncEnumerable GetStreamingResponseAsync(IEnumerable messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { var agentOptions = this.GetAgentEnabledChatOptions(options); await foreach (var chunk in base.GetStreamingResponseAsync(messages, agentOptions, cancellationToken).ConfigureAwait(false)) { yield return chunk; } } private ChatOptions GetAgentEnabledChatOptions(ChatOptions? options) { // Start with a clone of the base chat options defined for the agent, if any. ChatOptions agentEnabledChatOptions = this._chatOptions?.Clone() ?? new(); // Ignore per-request all options that can't be overridden. agentEnabledChatOptions.Instructions = null; agentEnabledChatOptions.Tools = null; agentEnabledChatOptions.Temperature = null; agentEnabledChatOptions.TopP = null; agentEnabledChatOptions.PresencePenalty = null; agentEnabledChatOptions.ResponseFormat = null; // Use the conversation from the request, or the one defined at the client level. agentEnabledChatOptions.ConversationId = options?.ConversationId ?? this._chatOptions?.ConversationId; // Preserve the original RawRepresentationFactory var originalFactory = options?.RawRepresentationFactory; agentEnabledChatOptions.RawRepresentationFactory = (client) => { if (originalFactory?.Invoke(this) is not CreateResponseOptions responseCreationOptions) { responseCreationOptions = new CreateResponseOptions(); } responseCreationOptions.Agent = this._agentReference; #pragma warning disable SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. responseCreationOptions.Patch.Remove("$.model"u8); #pragma warning restore SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. return responseCreationOptions; }; return agentEnabledChatOptions; } }