mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
afd2739e38
* .NET: Surface x-ms-served-model header as ChatResponse.ModelId for Foundry agents Mirrors Python PR #5910. Adds an internal SCM PipelinePolicy that reads the x-ms-served-model HTTP response header on Azure OpenAI Responses calls and writes it into an AsyncLocal box. A DelegatingChatClient sits between OpenTelemetry and the MEAI OpenAIResponsesChatClient and overwrites ChatResponse.ModelId with the served snapshot so OTel spans report the actual model rather than the deployment alias. Wired through all AsAIAgent paths in Microsoft.Agents.AI.Foundry. * .NET: Fix line endings and BOM on ResponsesAgentServedModelTests * .NET: Address Copilot review on Foundry served-model PR - Restore previous ServedModelScope in finally to avoid AsyncLocal leak into caller execution context. - Make served-model integration test assertion robust to deployment names that already match the snapshot pattern. - Broaden UnitTests csproj comment to cover all conditional removals (net8.0+ requirement). * .NET: Split ServedModelTests into per-SUT files with regions Split the combined ServedModelTests.cs into one test class per SUT: - ServedModelScopeTests.cs (AsyncLocal carrier) - ServedModelPolicyTests.cs (SCM pipeline policy) - ServedModelChatClientTests.cs (delegating client, with regions for Non-streaming / Streaming / End-to-end) Shared helpers and fake clients moved into ServedModelTestHelpers.cs. Csproj net8.0+ exclusion list updated accordingly. * .NET: Consolidate served-model logic into FoundryChatClient Move x-ms-served-model header capture from the standalone ServedModelChatClient decorator directly into FoundryChatClient, eliminating a separate wrapper that had to be applied at every Foundry entry point via WireServedModel(). - Register ServedModelPolicy in FoundryChatClient constructors (alongside the existing AgentFrameworkUserAgentPolicy registration) - Add StrongBox push/read logic to FoundryChatClient.GetResponseAsync and GetStreamingResponseAsync - Delete ServedModelChatClient.cs and its unit tests - Remove WireServedModel() from FoundryAgent and AIProjectClientExtensions - Update ServedModelPolicy/Scope XML docs to reference FoundryChatClient - Simplify ServedModelTestHelpers to use FoundryChatClient directly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
86 lines
3.1 KiB
C#
86 lines
3.1 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using AgentConformance.IntegrationTests.Support;
|
|
using Azure.AI.Projects;
|
|
using Microsoft.Agents.AI;
|
|
using Microsoft.Extensions.AI;
|
|
using Shared.IntegrationTests;
|
|
|
|
namespace Foundry.IntegrationTests;
|
|
|
|
/// <summary>
|
|
/// Integration tests validating that the <c>x-ms-served-model</c> response header
|
|
/// returned by the Azure OpenAI Responses API is surfaced on <see cref="ChatResponse.ModelId"/>.
|
|
/// </summary>
|
|
public class ResponsesAgentServedModelTests
|
|
{
|
|
// Matches a dated served-model snapshot, e.g. "gpt-5-nano-2025-08-07".
|
|
private static readonly Regex s_snapshotRegex = new(@"-\d{4}-\d{2}-\d{2}$", RegexOptions.Compiled);
|
|
|
|
private static Uri Endpoint => new(TestConfiguration.GetRequiredValue(TestSettings.AzureAIProjectEndpoint));
|
|
|
|
private static string DeploymentName => TestConfiguration.GetRequiredValue(TestSettings.AzureAIModelDeploymentName);
|
|
|
|
private readonly AIProjectClient _client = new(Endpoint, TestAzureCliCredentials.CreateAzureCliCredential());
|
|
|
|
[Fact]
|
|
public async Task GetResponseAsync_ReturnsServedModelSnapshotOnModelIdAsync()
|
|
{
|
|
// Arrange
|
|
ChatClientAgent agent = this._client.AsAIAgent(
|
|
model: DeploymentName,
|
|
instructions: "You are a helpful assistant. Reply with a single short word.",
|
|
name: "ServedModelTest");
|
|
|
|
IChatClient chatClient = agent.ChatClient;
|
|
|
|
// Act
|
|
ChatResponse response = await chatClient.GetResponseAsync(
|
|
[new ChatMessage(ChatRole.User, "Say hi.")],
|
|
new ChatOptions { ModelId = DeploymentName });
|
|
|
|
// Assert
|
|
AssertServedModel(response.ModelId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RunAsync_AgentResponseRawRepresentationCarriesServedModelAsync()
|
|
{
|
|
// Arrange
|
|
ChatClientAgent agent = this._client.AsAIAgent(
|
|
model: DeploymentName,
|
|
instructions: "You are a helpful assistant. Reply with a single short word.",
|
|
name: "ServedModelTestRun");
|
|
|
|
// Act
|
|
AgentResponse agentResponse = await agent.RunAsync("Say hi.");
|
|
|
|
// Assert
|
|
ChatResponse? chatResponse = agentResponse.RawRepresentation as ChatResponse;
|
|
Assert.NotNull(chatResponse);
|
|
AssertServedModel(chatResponse!.ModelId);
|
|
}
|
|
|
|
private static void AssertServedModel(string? modelId)
|
|
{
|
|
Assert.False(string.IsNullOrWhiteSpace(modelId), "ChatResponse.ModelId must be populated.");
|
|
|
|
// Primary invariant: the served-model value must look like a dated snapshot
|
|
// (e.g. "gpt-5-nano-2025-08-07"). This is what the x-ms-served-model header carries.
|
|
// Only when the configured deployment name itself already matches the snapshot pattern
|
|
// do we fall back to permitting equality with the deployment alias.
|
|
bool aliasIsSnapshot = s_snapshotRegex.IsMatch(DeploymentName);
|
|
|
|
if (aliasIsSnapshot)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Assert.Matches(s_snapshotRegex, modelId!);
|
|
Assert.NotEqual(DeploymentName, modelId);
|
|
}
|
|
}
|