mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Update Extensions to be less restrictive for GetAIAgents (#2091)
* Update behavior / restrictiveness when retrieving agents * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Address format * Address copilot feedback --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
a746cedf59
commit
a257db3aea
@@ -136,7 +136,7 @@ public static class AgentClientExtensions
|
||||
/// </summary>
|
||||
/// <param name="agentClient">The client used to interact with Azure AI Agents. Cannot be <see langword="null"/>.</param>
|
||||
/// <param name="agentVersion">The agent version to be converted. Cannot be <see langword="null"/>.</param>
|
||||
/// <param name="tools">The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.</param>
|
||||
/// <param name="tools">In-process invocable tools to be provided. If no tools are provided manual handling will be necessary to invoke in-process tools.</param>
|
||||
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
||||
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
|
||||
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
||||
@@ -156,7 +156,7 @@ public static class AgentClientExtensions
|
||||
Throw.IfNull(agentClient);
|
||||
Throw.IfNull(agentVersion);
|
||||
|
||||
ValidateUsingToolsParameter(agentVersion, tools);
|
||||
var allowDeclarativeMode = tools is not { Count: > 0 };
|
||||
|
||||
return CreateChatClientAgent(
|
||||
agentClient,
|
||||
@@ -164,7 +164,7 @@ public static class AgentClientExtensions
|
||||
tools,
|
||||
clientFactory,
|
||||
openAIClientOptions,
|
||||
requireInvocableTools: true,
|
||||
!allowDeclarativeMode,
|
||||
services);
|
||||
}
|
||||
|
||||
@@ -293,7 +293,6 @@ public static class AgentClientExtensions
|
||||
new AgentVersionCreationOptions(new PromptAgentDefinition(model) { Instructions = instructions }) { Description = description },
|
||||
clientFactory,
|
||||
openAIClientOptions,
|
||||
requireInvocableTools: true,
|
||||
services,
|
||||
cancellationToken);
|
||||
}
|
||||
@@ -339,7 +338,6 @@ public static class AgentClientExtensions
|
||||
new AgentVersionCreationOptions(new PromptAgentDefinition(model) { Instructions = instructions }) { Description = description },
|
||||
clientFactory,
|
||||
openAIClientOptions,
|
||||
requireInvocableTools: true,
|
||||
services,
|
||||
cancellationToken);
|
||||
}
|
||||
@@ -381,7 +379,7 @@ public static class AgentClientExtensions
|
||||
Instructions = options.Instructions,
|
||||
};
|
||||
|
||||
ApplyToolsToAgentDefinition(agentDefinition, options.ChatOptions?.Tools, RequireInvocableTools);
|
||||
ApplyToolsToAgentDefinition(agentDefinition, options.ChatOptions?.Tools);
|
||||
|
||||
AgentVersionCreationOptions? creationOptions = new(agentDefinition);
|
||||
if (!string.IsNullOrWhiteSpace(options.Description))
|
||||
@@ -440,7 +438,7 @@ public static class AgentClientExtensions
|
||||
Instructions = options.Instructions,
|
||||
};
|
||||
|
||||
ApplyToolsToAgentDefinition(agentDefinition, options.ChatOptions?.Tools, RequireInvocableTools);
|
||||
ApplyToolsToAgentDefinition(agentDefinition, options.ChatOptions?.Tools);
|
||||
|
||||
AgentVersionCreationOptions? creationOptions = new(agentDefinition);
|
||||
if (!string.IsNullOrWhiteSpace(options.Description))
|
||||
@@ -489,16 +487,13 @@ public static class AgentClientExtensions
|
||||
Throw.IfNullOrWhitespace(name);
|
||||
Throw.IfNull(creationOptions);
|
||||
|
||||
var tools = (creationOptions.Definition as PromptAgentDefinition)?.Tools.Select(t => t.AsAITool()).ToList();
|
||||
|
||||
return CreateAIAgent(
|
||||
agentClient,
|
||||
name,
|
||||
tools,
|
||||
tools: null,
|
||||
creationOptions,
|
||||
clientFactory,
|
||||
openAIClientOptions,
|
||||
requireInvocableTools: false,
|
||||
services: null,
|
||||
cancellationToken);
|
||||
}
|
||||
@@ -531,16 +526,13 @@ public static class AgentClientExtensions
|
||||
Throw.IfNull(agentClient);
|
||||
Throw.IfNull(creationOptions);
|
||||
|
||||
var tools = (creationOptions.Definition as PromptAgentDefinition)?.Tools.Select(t => t.AsAITool()).ToList();
|
||||
|
||||
return CreateAIAgentAsync(
|
||||
agentClient,
|
||||
name,
|
||||
tools,
|
||||
tools: null,
|
||||
creationOptions,
|
||||
clientFactory,
|
||||
openAIClientOptions,
|
||||
requireInvocableTools: false,
|
||||
services: null,
|
||||
cancellationToken);
|
||||
}
|
||||
@@ -594,7 +586,6 @@ public static class AgentClientExtensions
|
||||
AgentVersionCreationOptions creationOptions,
|
||||
Func<IChatClient, IChatClient>? clientFactory,
|
||||
OpenAIClientOptions? openAIClientOptions,
|
||||
bool requireInvocableTools,
|
||||
IServiceProvider? services,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -602,9 +593,12 @@ public static class AgentClientExtensions
|
||||
Throw.IfNullOrWhitespace(name);
|
||||
Throw.IfNull(creationOptions);
|
||||
|
||||
tools ??= (creationOptions.Definition as PromptAgentDefinition)?.Tools.Select(t => t.AsAITool()).ToList();
|
||||
var allowDeclarativeMode = tools is not { Count: > 0 };
|
||||
|
||||
ApplyToolsToAgentDefinition(creationOptions.Definition, tools, requireInvocableTools);
|
||||
if (!allowDeclarativeMode)
|
||||
{
|
||||
ApplyToolsToAgentDefinition(creationOptions.Definition, tools);
|
||||
}
|
||||
|
||||
AgentVersion agentVersion = CreateAgentVersionWithProtocol(agentClient, name, creationOptions, cancellationToken);
|
||||
|
||||
@@ -614,7 +608,7 @@ public static class AgentClientExtensions
|
||||
tools,
|
||||
clientFactory,
|
||||
openAIClientOptions,
|
||||
requireInvocableTools,
|
||||
!allowDeclarativeMode,
|
||||
services);
|
||||
}
|
||||
|
||||
@@ -625,7 +619,6 @@ public static class AgentClientExtensions
|
||||
AgentVersionCreationOptions creationOptions,
|
||||
Func<IChatClient, IChatClient>? clientFactory,
|
||||
OpenAIClientOptions? openAIClientOptions,
|
||||
bool requireInvocableTools,
|
||||
IServiceProvider? services,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -633,9 +626,12 @@ public static class AgentClientExtensions
|
||||
Throw.IfNull(agentClient);
|
||||
Throw.IfNull(creationOptions);
|
||||
|
||||
tools ??= (creationOptions.Definition as PromptAgentDefinition)?.Tools.Select(t => t.AsAITool()).ToList();
|
||||
var allowDeclarativeMode = tools is not { Count: > 0 };
|
||||
|
||||
ApplyToolsToAgentDefinition(creationOptions.Definition, tools, requireInvocableTools);
|
||||
if (!allowDeclarativeMode)
|
||||
{
|
||||
ApplyToolsToAgentDefinition(creationOptions.Definition, tools);
|
||||
}
|
||||
|
||||
AgentVersion agentVersion = await CreateAgentVersionWithProtocolAsync(agentClient, name, creationOptions, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -645,7 +641,7 @@ public static class AgentClientExtensions
|
||||
tools,
|
||||
clientFactory,
|
||||
openAIClientOptions,
|
||||
requireInvocableTools,
|
||||
!allowDeclarativeMode,
|
||||
services);
|
||||
}
|
||||
|
||||
@@ -719,14 +715,11 @@ public static class AgentClientExtensions
|
||||
// Check function tools
|
||||
foreach (ResponseTool responseTool in definitionTools)
|
||||
{
|
||||
if (responseTool is FunctionTool functionTool)
|
||||
if (requireInvocableTools && responseTool is FunctionTool functionTool)
|
||||
{
|
||||
// Check if a tool with the same type and name exists in the provided tools.
|
||||
var matchingTool = chatOptions?.Tools?.FirstOrDefault(t =>
|
||||
requireInvocableTools
|
||||
? t is AIFunction tf && functionTool.FunctionName == tf.Name // When invocable tools are required, match only AIFunction.
|
||||
: (t is AIFunctionDeclaration tfd && functionTool.FunctionName == tfd.Name) ? true // When not required, match AIFunctionDeclaration OR
|
||||
: (t.GetService<FunctionTool>() is FunctionTool ft && functionTool.FunctionName == ft.FunctionName)); // Match a FunctionTool converted AsAITool.
|
||||
// When invocable tools are required, match only AIFunction.
|
||||
var matchingTool = chatOptions?.Tools?.FirstOrDefault(t => t is AIFunction tf && functionTool.FunctionName == tf.Name);
|
||||
|
||||
if (matchingTool is null)
|
||||
{
|
||||
@@ -742,7 +735,7 @@ public static class AgentClientExtensions
|
||||
(agentTools ??= []).Add(responseTool.AsAITool());
|
||||
}
|
||||
|
||||
if (missingTools is { Count: > 0 })
|
||||
if (requireInvocableTools && missingTools is { Count: > 0 })
|
||||
{
|
||||
throw new InvalidOperationException($"The following prompt agent definition required tools were not provided: {string.Join(", ", missingTools)}");
|
||||
}
|
||||
@@ -796,35 +789,14 @@ public static class AgentClientExtensions
|
||||
return agentOptions;
|
||||
}
|
||||
|
||||
/// <summary>For already created agent versions, retrieve the definition and validate the tools parameter.</summary>
|
||||
/// <exception cref="ArgumentException"><see cref="PromptAgentDefinition.Tools"/> cannot be used. The <paramref name="tools"/> parameter should be used instead.</exception>
|
||||
/// <remarks>
|
||||
/// Because <see cref="PromptAgentDefinition.Tools"/> doesn't support in-proc tools (only declarative/definitions),
|
||||
/// the <paramref name="tools"/> parameter needs to be the single source of truth for tools, and must be provided when using tools.
|
||||
/// </remarks>
|
||||
private static void ValidateUsingToolsParameter(AgentVersion agentVersion, IList<AITool>? tools)
|
||||
{
|
||||
if (agentVersion.Definition is PromptAgentDefinition { Tools.Count: > 0 } && tools is null or { Count: 0 })
|
||||
{
|
||||
throw new ArgumentException("When retrieving prompt agents with tools the tools parameter needs to be provided with the necessary tools.", nameof(tools));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified AI tools to a prompt agent definition, ensuring that all tools are compatible and, if required, invocable.
|
||||
/// Adds the specified AI tools to a prompt agent definition, while also ensuring that all invocable tools are provided.
|
||||
/// </summary>
|
||||
/// <remarks>This method ensures that only compatible and properly constructed tools are added to the agent definition.
|
||||
/// When <paramref name="requireInvocableTools"/> is <see langword="true"/>, all tools must be
|
||||
/// invocable AIFunctions, which can be created using AIFunctionFactory.Create. Tools are converted to ResponseTool
|
||||
/// instances before being added.</remarks>
|
||||
/// <param name="agentDefinition">The agent definition to which the tools will be applied. Must be a PromptAgentDefinition to support tools.</param>
|
||||
/// <param name="tools">A list of AI tools to add to the agent definition. If null or empty, no tools are added.</param>
|
||||
/// <param name="requireInvocableTools">Indicates whether all provided tools must be invocable AI functions. If set to <see langword="true"/>, only
|
||||
/// invocable AIFunctions are accepted.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="agentDefinition"/> is not a <see cref="PromptAgentDefinition"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">Thrown if <paramref name="requireInvocableTools"/> is <see langword="true"/> and a tool is an
|
||||
/// <see cref="AIFunctionDeclaration"/> that is not invocable, or if a tool cannot be converted to a <see cref="ResponseTool"/>.</exception>
|
||||
private static void ApplyToolsToAgentDefinition(AgentDefinition agentDefinition, IList<AITool>? tools, bool requireInvocableTools)
|
||||
/// <exception cref="ArgumentException">Thrown if tools were provided but <paramref name="agentDefinition"/> is not a <see cref="PromptAgentDefinition"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">When providing functions, they need to be invokable AIFunctions.</exception>
|
||||
private static void ApplyToolsToAgentDefinition(AgentDefinition agentDefinition, IList<AITool>? tools)
|
||||
{
|
||||
if (tools is { Count: > 0 })
|
||||
{
|
||||
@@ -833,10 +805,14 @@ public static class AgentClientExtensions
|
||||
throw new ArgumentException("Only prompt agent definitions support tools.", nameof(agentDefinition));
|
||||
}
|
||||
|
||||
// When tools are provided, those should represent the complete set of tools for the agent definition.
|
||||
// This is particularly important for existing agents so no duplication happens for what was already defined.
|
||||
promptAgentDefinition.Tools.Clear();
|
||||
|
||||
foreach (var tool in tools)
|
||||
{
|
||||
// Ensure that any AIFunctions provided are In-Proc, not just the declarations.
|
||||
if (requireInvocableTools && tool is not AIFunction && (
|
||||
if (tool is not AIFunction && (
|
||||
tool.GetService<FunctionTool>() is not null // Declarative FunctionTool converted as AsAITool()
|
||||
|| tool is AIFunctionDeclaration)) // AIFunctionDeclaration type
|
||||
{
|
||||
|
||||
@@ -80,8 +80,8 @@ public sealed class AzureAgentProvider(Uri projectEndpoint, TokenCredential proj
|
||||
IEnumerable<ChatMessage>? messages,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
AgentVersion agentDefinition = await this.QueryAgentAsync(agentId, agentVersion, cancellationToken).ConfigureAwait(false);
|
||||
AIAgent agent = await this.GetAgentAsync(agentDefinition, cancellationToken).ConfigureAwait(false);
|
||||
AgentVersion agentVersionResult = await this.QueryAgentAsync(agentId, agentVersion, cancellationToken).ConfigureAwait(false);
|
||||
AIAgent agent = await this.GetAgentAsync(agentVersionResult, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
ChatOptions chatOptions =
|
||||
new()
|
||||
@@ -99,7 +99,7 @@ public sealed class AzureAgentProvider(Uri projectEndpoint, TokenCredential proj
|
||||
|
||||
await foreach (AgentRunResponseUpdate update in agentResponse.ConfigureAwait(false))
|
||||
{
|
||||
update.AuthorName = agentDefinition.Name;
|
||||
update.AuthorName = agentVersionResult.Name;
|
||||
yield return update;
|
||||
}
|
||||
}
|
||||
@@ -146,16 +146,7 @@ public sealed class AzureAgentProvider(Uri projectEndpoint, TokenCredential proj
|
||||
|
||||
AgentClient client = this.GetAgentClient();
|
||||
|
||||
IList<AITool>? tools = null;
|
||||
if (agentVersion.Definition is PromptAgentDefinition promptAgent)
|
||||
{
|
||||
tools =
|
||||
promptAgent.Tools
|
||||
.Select(tool => tool.AsAITool())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
agent = client.GetAIAgent(agentVersion, tools, clientFactory: null, openAIClientOptions: null, services: null, cancellationToken);
|
||||
agent = client.GetAIAgent(agentVersion, tools: null, clientFactory: null, openAIClientOptions: null, services: null, cancellationToken);
|
||||
|
||||
FunctionInvokingChatClient? functionInvokingClient = agent.GetService<FunctionInvokingChatClient>();
|
||||
if (functionInvokingClient is not null)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
launchSettings.json
|
||||
+141
-13
@@ -504,10 +504,10 @@ public sealed class AgentClientExtensionsTests
|
||||
#region GetAIAgent(AgentClient, AgentRecord) with tools Tests
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetAIAgent with tools parameter passes tools to the agent.
|
||||
/// Verify that GetAIAgent with additional tools when the definition has no tools does not throw and results in an agent with no tools.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetAIAgent_WithAgentRecordAndTools_PassesToolsToAgent()
|
||||
public void GetAIAgent_WithAgentRecordAndAdditionalTools_WhenDefinitionHasNoTools_ShouldNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
AgentClient client = this.CreateTestAgentClient();
|
||||
@@ -527,6 +527,8 @@ public sealed class AgentClientExtensionsTests
|
||||
Assert.NotNull(chatClient);
|
||||
var agentVersion = chatClient.GetService<AgentVersion>();
|
||||
Assert.NotNull(agentVersion);
|
||||
var definition = Assert.IsType<PromptAgentDefinition>(agentVersion.Definition);
|
||||
Assert.Empty(definition.Tools);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -974,6 +976,88 @@ public sealed class AgentClientExtensionsTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that CreateAIAgentAsync when AI Tools are provided, uses them for the definition via http request.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task CreateAIAgentAsync_WithNameAndAITools_SendsToolDefinitionViaHttpAsync()
|
||||
{
|
||||
// Arrange
|
||||
using var httpHandler = new HttpHandlerAssert(async (request) =>
|
||||
{
|
||||
if (request.Content is not null)
|
||||
{
|
||||
var requestBody = await request.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
Assert.Contains("required_tool", requestBody);
|
||||
}
|
||||
|
||||
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(AgentVersionTestJsonObject, Encoding.UTF8, "application/json") };
|
||||
});
|
||||
|
||||
#pragma warning disable CA5399
|
||||
using var httpClient = new HttpClient(httpHandler);
|
||||
#pragma warning restore CA5399
|
||||
|
||||
var client = new AgentClient(new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new() { Transport = new HttpClientPipelineTransport(httpClient) });
|
||||
|
||||
// Act
|
||||
var agent = await client.CreateAIAgentAsync(
|
||||
name: "test-agent",
|
||||
model: "test-model",
|
||||
instructions: "Test",
|
||||
tools: [AIFunctionFactory.Create(() => true, "required_tool")]);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(agent);
|
||||
Assert.IsType<ChatClientAgent>(agent);
|
||||
var agentVersion = agent.GetService<AgentVersion>();
|
||||
Assert.NotNull(agentVersion);
|
||||
Assert.IsType<PromptAgentDefinition>(agentVersion.Definition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that CreateAIAgent when AI Tools are provided, uses them for the definition via http request.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void CreateAIAgent_WithNameAndAITools_SendsToolDefinitionViaHttp()
|
||||
{
|
||||
// Arrange
|
||||
using var httpHandler = new HttpHandlerAssert((request) =>
|
||||
{
|
||||
if (request.Content is not null)
|
||||
{
|
||||
#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits
|
||||
var requestBody = request.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||
#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits
|
||||
|
||||
Assert.Contains("required_tool", requestBody);
|
||||
}
|
||||
|
||||
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(AgentVersionTestJsonObject, Encoding.UTF8, "application/json") };
|
||||
});
|
||||
|
||||
#pragma warning disable CA5399
|
||||
using var httpClient = new HttpClient(httpHandler);
|
||||
#pragma warning restore CA5399
|
||||
|
||||
var client = new AgentClient(new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new() { Transport = new HttpClientPipelineTransport(httpClient) });
|
||||
|
||||
// Act
|
||||
var agent = client.CreateAIAgent(
|
||||
name: "test-agent",
|
||||
model: "test-model",
|
||||
instructions: "Test",
|
||||
tools: [AIFunctionFactory.Create(() => true, "required_tool")]);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(agent);
|
||||
Assert.IsType<ChatClientAgent>(agent);
|
||||
var agentVersion = agent.GetService<AgentVersion>();
|
||||
Assert.NotNull(agentVersion);
|
||||
Assert.IsType<PromptAgentDefinition>(agentVersion.Definition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that CreateAIAgent without tools creates an agent successfully.
|
||||
/// </summary>
|
||||
@@ -997,10 +1081,10 @@ public sealed class AgentClientExtensionsTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetAIAgent with inline tools in agent definition throws ArgumentException.
|
||||
/// Verify that when providing AITools with GetAIAgent, any additional tool that doesn't match the tools in agent definition are ignored.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetAIAgent_WithInlineToolsInDefinition_ThrowsArgumentException()
|
||||
public void GetAIAgent_AdditionalAITools_WhenNotInTheDefinitionAreIgnored()
|
||||
{
|
||||
// Arrange
|
||||
AgentClient client = this.CreateTestAgentClient();
|
||||
@@ -1012,11 +1096,20 @@ public sealed class AgentClientExtensionsTests
|
||||
promptDef.Tools.Add(ResponseTool.CreateFunctionTool("inline_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
||||
}
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<ArgumentException>(() =>
|
||||
client.GetAIAgent(agentVersion));
|
||||
var invocableInlineAITool = AIFunctionFactory.Create(() => "test", "inline_tool", "An invocable AIFunction for the inline function");
|
||||
var shouldBeIgnoredTool = AIFunctionFactory.Create(() => "test", "additional_tool", "An additional test function that should be ignored");
|
||||
|
||||
Assert.Contains("tools parameter", exception.Message);
|
||||
// Act & Assert
|
||||
var agent = client.GetAIAgent(agentVersion, tools: [invocableInlineAITool, shouldBeIgnoredTool]);
|
||||
Assert.NotNull(agent);
|
||||
var version = agent.GetService<AgentVersion>();
|
||||
Assert.NotNull(version);
|
||||
var definition = Assert.IsType<PromptAgentDefinition>(version.Definition);
|
||||
Assert.NotEmpty(definition.Tools);
|
||||
Assert.NotNull(GetAgentChatOptions(agent));
|
||||
Assert.NotNull(GetAgentChatOptions(agent)!.Tools);
|
||||
Assert.Single(GetAgentChatOptions(agent)!.Tools!);
|
||||
Assert.Equal("inline_tool", (definition.Tools.First() as FunctionTool)?.FunctionName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -2174,19 +2267,54 @@ public sealed class AgentClientExtensionsTests
|
||||
|
||||
#endregion
|
||||
|
||||
private sealed class HttpHandlerAssert(Func<HttpRequestMessage, HttpResponseMessage> assertion) : HttpClientHandler
|
||||
private sealed class HttpHandlerAssert : HttpClientHandler
|
||||
{
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
private readonly Func<HttpRequestMessage, HttpResponseMessage>? _assertion;
|
||||
private readonly Func<HttpRequestMessage, Task<HttpResponseMessage>>? _assertionAsync;
|
||||
|
||||
public HttpHandlerAssert(Func<HttpRequestMessage, HttpResponseMessage> assertion)
|
||||
{
|
||||
var response = assertion(request);
|
||||
return Task.FromResult(response);
|
||||
this._assertion = assertion;
|
||||
}
|
||||
public HttpHandlerAssert(Func<HttpRequestMessage, Task<HttpResponseMessage>> assertionAsync)
|
||||
{
|
||||
this._assertionAsync = assertionAsync;
|
||||
}
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (this._assertionAsync is not null)
|
||||
{
|
||||
return await this._assertionAsync.Invoke(request);
|
||||
}
|
||||
|
||||
return this._assertion!.Invoke(request);
|
||||
}
|
||||
|
||||
#if NET
|
||||
protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
return assertion(request);
|
||||
return this._assertion!(request);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to access internal ChatOptions property via reflection.
|
||||
/// </summary>
|
||||
private static ChatOptions? GetAgentChatOptions(ChatClientAgent agent)
|
||||
{
|
||||
if (agent is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var chatOptionsProperty = typeof(ChatClientAgent).GetProperty(
|
||||
"ChatOptions",
|
||||
System.Reflection.BindingFlags.Public |
|
||||
System.Reflection.BindingFlags.NonPublic |
|
||||
System.Reflection.BindingFlags.Instance);
|
||||
|
||||
return chatOptionsProperty?.GetValue(agent) as ChatOptions;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user