mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Address PR review: forward pipeline settings; add UTs
- CreateProjectClientOptions also carries RetryPolicy, NetworkTimeout, ClientLoggingOptions, MessageLoggingPolicy (was Transport+UserAgentApplicationId only). - Make CreateProjectClientOptions internal so tests can verify the copy directly. - Add AsAIAgent(Uri) UTs covering tools forwarding to inner ChatOptions and null tools handling. - Add CreateProjectClientOptions UTs covering null caller and full pipeline-settings copy.
This commit is contained in:
@@ -428,18 +428,31 @@ public sealed class FoundryAgent : DelegatingAIAgent
|
||||
return new AIProjectClient(endpoint, credential, clientOptions);
|
||||
}
|
||||
|
||||
private static AIProjectClientOptions? CreateProjectClientOptions(ProjectOpenAIClientOptions? clientOptions)
|
||||
internal static AIProjectClientOptions? CreateProjectClientOptions(ProjectOpenAIClientOptions? clientOptions)
|
||||
{
|
||||
if (clientOptions is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new AIProjectClientOptions
|
||||
// Copy pipeline behavior the caller configured on the per-agent options bag onto the
|
||||
// project-level options bag so the agent endpoint client honors it. UserAgentApplicationId
|
||||
// is project-level (not derived from the agent endpoint), so it must be carried through too.
|
||||
var projectOptions = new AIProjectClientOptions
|
||||
{
|
||||
Transport = clientOptions.Transport,
|
||||
RetryPolicy = clientOptions.RetryPolicy,
|
||||
NetworkTimeout = clientOptions.NetworkTimeout,
|
||||
MessageLoggingPolicy = clientOptions.MessageLoggingPolicy,
|
||||
UserAgentApplicationId = clientOptions.UserAgentApplicationId,
|
||||
};
|
||||
|
||||
if (clientOptions.ClientLoggingOptions is not null)
|
||||
{
|
||||
projectOptions.ClientLoggingOptions = clientOptions.ClientLoggingOptions;
|
||||
}
|
||||
|
||||
return projectOptions;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
+46
@@ -1459,6 +1459,52 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
Assert.Same(testChatClient, retrievedTestClient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that AsAIAgent(Uri agentEndpoint) forwards the supplied tools to the inner
|
||||
/// <see cref="ChatClientAgent"/>'s <see cref="ChatOptions.Tools"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AsAIAgent_WithAgentEndpoint_ForwardsToolsToInnerChatOptions()
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
var tool1 = AIFunctionFactory.Create(() => "result-1", "tool_1", "First test tool.");
|
||||
var tool2 = AIFunctionFactory.Create(() => "result-2", "tool_2", "Second test tool.");
|
||||
List<AITool> tools = [tool1, tool2];
|
||||
|
||||
// Act
|
||||
var agent = client.AsAIAgent(new Uri(TestAgentEndpointUrl), tools: tools);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(agent);
|
||||
ChatOptions? chatOptions = GetAgentChatOptions(agent);
|
||||
Assert.NotNull(chatOptions);
|
||||
Assert.NotNull(chatOptions!.Tools);
|
||||
Assert.Equal(2, chatOptions.Tools!.Count);
|
||||
Assert.Same(tool1, chatOptions.Tools[0]);
|
||||
Assert.Same(tool2, chatOptions.Tools[1]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that AsAIAgent(Uri agentEndpoint) accepts a null tools argument without throwing
|
||||
/// and produces an agent whose inner <see cref="ChatOptions.Tools"/> is null.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AsAIAgent_WithAgentEndpoint_WithNullTools_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
AIProjectClient client = this.CreateTestAgentClient();
|
||||
|
||||
// Act
|
||||
var agent = client.AsAIAgent(new Uri(TestAgentEndpointUrl), tools: null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(agent);
|
||||
ChatOptions? chatOptions = GetAgentChatOptions(agent);
|
||||
Assert.NotNull(chatOptions);
|
||||
Assert.Null(chatOptions!.Tools);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.ClientModel.Primitives;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
@@ -356,7 +357,7 @@ public class FoundryAgentTests
|
||||
bool userAgentFound = false;
|
||||
using HttpHandlerAssert httpHandler = new(request =>
|
||||
{
|
||||
if (request.Headers.TryGetValues("User-Agent", out System.Collections.Generic.IEnumerable<string>? values))
|
||||
if (request.Headers.TryGetValues("User-Agent", out IEnumerable<string>? values))
|
||||
{
|
||||
foreach (string value in values)
|
||||
{
|
||||
@@ -676,6 +677,71 @@ public class FoundryAgentTests
|
||||
Assert.Equal("my-app-id", opts.UserAgentApplicationId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProjectClientOptions_NullCallerOptions_ReturnsNull()
|
||||
{
|
||||
Assert.Null(FoundryAgent.CreateProjectClientOptions(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProjectClientOptions_CarriesPipelineSettingsAndUserAgent()
|
||||
{
|
||||
// Arrange
|
||||
var transport = new FakePipelineTransport();
|
||||
var retryPolicy = new FakeRetryPolicy();
|
||||
var messageLoggingPolicy = new FakeMessageLoggingPolicy();
|
||||
var clientLoggingOptions = new ClientLoggingOptions { EnableLogging = false };
|
||||
var networkTimeout = TimeSpan.FromSeconds(42);
|
||||
|
||||
ProjectOpenAIClientOptions callerOptions = new()
|
||||
{
|
||||
UserAgentApplicationId = "my-app-id",
|
||||
Transport = transport,
|
||||
RetryPolicy = retryPolicy,
|
||||
MessageLoggingPolicy = messageLoggingPolicy,
|
||||
ClientLoggingOptions = clientLoggingOptions,
|
||||
NetworkTimeout = networkTimeout,
|
||||
};
|
||||
|
||||
// Act
|
||||
AIProjectClientOptions? projectOptions = FoundryAgent.CreateProjectClientOptions(callerOptions);
|
||||
|
||||
// Assert: every settable pipeline behavior the caller configured is forwarded
|
||||
// onto the project-level options bag, not silently dropped.
|
||||
Assert.NotNull(projectOptions);
|
||||
Assert.Equal("my-app-id", projectOptions!.UserAgentApplicationId);
|
||||
Assert.Same(transport, projectOptions.Transport);
|
||||
Assert.Same(retryPolicy, projectOptions.RetryPolicy);
|
||||
Assert.Same(messageLoggingPolicy, projectOptions.MessageLoggingPolicy);
|
||||
Assert.Same(clientLoggingOptions, projectOptions.ClientLoggingOptions);
|
||||
Assert.Equal(networkTimeout, projectOptions.NetworkTimeout);
|
||||
}
|
||||
|
||||
private sealed class FakeRetryPolicy : PipelinePolicy
|
||||
{
|
||||
public override void Process(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
|
||||
=> ProcessNext(message, pipeline, currentIndex);
|
||||
|
||||
public override ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
|
||||
=> ProcessNextAsync(message, pipeline, currentIndex);
|
||||
}
|
||||
|
||||
private sealed class FakeMessageLoggingPolicy : PipelinePolicy
|
||||
{
|
||||
public override void Process(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
|
||||
=> ProcessNext(message, pipeline, currentIndex);
|
||||
|
||||
public override ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
|
||||
=> ProcessNextAsync(message, pipeline, currentIndex);
|
||||
}
|
||||
|
||||
private sealed class FakePipelineTransport : PipelineTransport
|
||||
{
|
||||
protected override PipelineMessage CreateMessageCore() => throw new NotSupportedException();
|
||||
protected override void ProcessCore(PipelineMessage message) => throw new NotSupportedException();
|
||||
protected override ValueTask ProcessCoreAsync(PipelineMessage message) => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ParseAgentEndpoint tests
|
||||
@@ -758,13 +824,13 @@ public class FoundryAgentTests
|
||||
private readonly string _value;
|
||||
public HeaderStampPolicy(string name, string value) { this._name = name; this._value = value; }
|
||||
|
||||
public override void Process(PipelineMessage message, System.Collections.Generic.IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
|
||||
public override void Process(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
|
||||
{
|
||||
message.Request.Headers.Set(this._name, this._value);
|
||||
ProcessNext(message, pipeline, currentIndex);
|
||||
}
|
||||
|
||||
public override ValueTask ProcessAsync(PipelineMessage message, System.Collections.Generic.IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
|
||||
public override ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
|
||||
{
|
||||
message.Request.Headers.Set(this._name, this._value);
|
||||
return ProcessNextAsync(message, pipeline, currentIndex);
|
||||
|
||||
Reference in New Issue
Block a user