mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Feature foundry agent/agent reference extension (Python Parity with Name + Version option) (#2147)
* Add agent reference extensions * Add UT covering AgentReference and ModelId
This commit is contained in:
committed by
GitHub
Unverified
parent
0f539d9748
commit
56ced98b27
@@ -19,6 +19,47 @@ namespace Azure.AI.Agents;
|
||||
/// </summary>
|
||||
public static class AgentClientExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AgentClient"/>.
|
||||
/// </summary>
|
||||
/// <param name="agentClient">The <see cref="AgentClient"/> to create the <see cref="ChatClientAgent"/> with. Cannot be <see langword="null"/>.</param>
|
||||
/// <param name="agentReference">The <see cref="AgentReference"/> representing the name and version of the server side agent to create a <see cref="ChatClientAgent"/> for. 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="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>
|
||||
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations based on the latest version of the named Azure AI Agent.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentClient"/> or <paramref name="agentReference"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The agent with the specified name was not found.</exception>
|
||||
/// <remarks>
|
||||
/// When retrieving an agent by using an <see cref="AgentReference"/>, minimal information will be available about the agent in the instance level, and any logic that relies
|
||||
/// on <see cref="AIAgent.GetService(Type, object?)"/> to retrieve information about the agent like <see cref="AgentVersion" /> will receive <see langword="null"/> as the result.
|
||||
/// </remarks>
|
||||
public static ChatClientAgent GetAIAgent(
|
||||
this AgentClient agentClient,
|
||||
AgentReference agentReference,
|
||||
IList<AITool>? tools = null,
|
||||
Func<IChatClient, IChatClient>? clientFactory = null,
|
||||
OpenAIClientOptions? openAIClientOptions = null,
|
||||
IServiceProvider? services = null)
|
||||
{
|
||||
Throw.IfNull(agentClient);
|
||||
Throw.IfNull(agentReference);
|
||||
|
||||
return CreateChatClientAgent(
|
||||
agentClient,
|
||||
agentReference,
|
||||
new ChatClientAgentOptions()
|
||||
{
|
||||
Id = $"{agentReference.Name}:{agentReference.Version}",
|
||||
Name = agentReference.Name,
|
||||
ChatOptions = new() { Tools = tools },
|
||||
},
|
||||
clientFactory,
|
||||
openAIClientOptions,
|
||||
services);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AgentClient"/>.
|
||||
/// </summary>
|
||||
@@ -33,7 +74,6 @@ public static class AgentClientExtensions
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentClient"/> or <paramref name="name"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="name"/> is empty or whitespace, or when the agent with the specified name was not found.</exception>
|
||||
/// <exception cref="InvalidOperationException">The agent with the specified name was not found.</exception>
|
||||
/// <remarks>When using prompt agent definitions with tools the parameter <paramref name="tools"/> needs to be provided.</remarks>
|
||||
public static ChatClientAgent GetAIAgent(
|
||||
this AgentClient agentClient,
|
||||
string name,
|
||||
@@ -54,8 +94,7 @@ public static class AgentClientExtensions
|
||||
tools,
|
||||
clientFactory,
|
||||
openAIClientOptions,
|
||||
services,
|
||||
cancellationToken);
|
||||
services);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -72,7 +111,6 @@ public static class AgentClientExtensions
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentClient"/> or <paramref name="name"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="name"/> is empty or whitespace, or when the agent with the specified name was not found.</exception>
|
||||
/// <exception cref="InvalidOperationException">The agent with the specified name was not found.</exception>
|
||||
/// <remarks>When using prompt agent definitions with tools the parameter <paramref name="tools"/> needs to be provided.</remarks>
|
||||
public static async Task<ChatClientAgent> GetAIAgentAsync(
|
||||
this AgentClient agentClient,
|
||||
string name,
|
||||
@@ -93,8 +131,7 @@ public static class AgentClientExtensions
|
||||
tools,
|
||||
clientFactory,
|
||||
openAIClientOptions,
|
||||
services,
|
||||
cancellationToken);
|
||||
services);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -106,29 +143,29 @@ public static class AgentClientExtensions
|
||||
/// <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>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations based on the latest version of the Azure AI Agent.</returns>
|
||||
/// <remarks>When using prompt agent definitions with tools the parameter <paramref name="tools"/> needs to be provided.</remarks>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentClient"/> or <paramref name="agentRecord"/> is <see langword="null"/>.</exception>
|
||||
public static ChatClientAgent GetAIAgent(
|
||||
this AgentClient agentClient,
|
||||
AgentRecord agentRecord,
|
||||
IList<AITool>? tools = null,
|
||||
Func<IChatClient, IChatClient>? clientFactory = null,
|
||||
OpenAIClientOptions? openAIClientOptions = null,
|
||||
IServiceProvider? services = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
IServiceProvider? services = null)
|
||||
{
|
||||
Throw.IfNull(agentClient);
|
||||
Throw.IfNull(agentRecord);
|
||||
|
||||
return GetAIAgent(
|
||||
var allowDeclarativeMode = tools is not { Count: > 0 };
|
||||
|
||||
return CreateChatClientAgent(
|
||||
agentClient,
|
||||
agentRecord.Versions.Latest,
|
||||
agentRecord,
|
||||
tools,
|
||||
clientFactory,
|
||||
openAIClientOptions,
|
||||
services,
|
||||
cancellationToken);
|
||||
!allowDeclarativeMode,
|
||||
services);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -140,18 +177,15 @@ public static class AgentClientExtensions
|
||||
/// <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>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations based on the provided version of the Azure AI Agent.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentClient"/> or <paramref name="agentVersion"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>When using prompt agent definitions with tools the parameter <paramref name="tools"/> needs to be provided.</remarks>
|
||||
public static ChatClientAgent GetAIAgent(
|
||||
this AgentClient agentClient,
|
||||
AgentVersion agentVersion,
|
||||
IList<AITool>? tools = null,
|
||||
Func<IChatClient, IChatClient>? clientFactory = null,
|
||||
OpenAIClientOptions? openAIClientOptions = null,
|
||||
IServiceProvider? services = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
IServiceProvider? services = null)
|
||||
{
|
||||
Throw.IfNull(agentClient);
|
||||
Throw.IfNull(agentVersion);
|
||||
@@ -206,7 +240,6 @@ public static class AgentClientExtensions
|
||||
agentOptions,
|
||||
clientFactory,
|
||||
openAIClientOptions,
|
||||
requireInvocableTools: true,
|
||||
services);
|
||||
}
|
||||
|
||||
@@ -248,7 +281,6 @@ public static class AgentClientExtensions
|
||||
agentOptions,
|
||||
clientFactory,
|
||||
openAIClientOptions,
|
||||
requireInvocableTools: true,
|
||||
services);
|
||||
}
|
||||
|
||||
@@ -397,7 +429,6 @@ public static class AgentClientExtensions
|
||||
agentOptions,
|
||||
clientFactory,
|
||||
openAIClientOptions,
|
||||
RequireInvocableTools,
|
||||
services);
|
||||
}
|
||||
|
||||
@@ -456,7 +487,6 @@ public static class AgentClientExtensions
|
||||
agentOptions,
|
||||
clientFactory,
|
||||
openAIClientOptions,
|
||||
RequireInvocableTools,
|
||||
services);
|
||||
}
|
||||
|
||||
@@ -652,7 +682,6 @@ public static class AgentClientExtensions
|
||||
ChatClientAgentOptions agentOptions,
|
||||
Func<IChatClient, IChatClient>? clientFactory,
|
||||
OpenAIClientOptions? openAIClientOptions,
|
||||
bool requireInvocableTools,
|
||||
IServiceProvider? services)
|
||||
{
|
||||
IChatClient chatClient = new AzureAIAgentChatClient(agentClient, agentVersion, agentOptions.ChatOptions, openAIClientOptions);
|
||||
@@ -665,6 +694,44 @@ public static class AgentClientExtensions
|
||||
return new ChatClientAgent(chatClient, agentOptions, services: services);
|
||||
}
|
||||
|
||||
/// <summary>This method creates an <see cref="ChatClientAgent"/> with the specified ChatClientAgentOptions.</summary>
|
||||
private static ChatClientAgent CreateChatClientAgent(
|
||||
AgentClient agentClient,
|
||||
AgentRecord agentRecord,
|
||||
ChatClientAgentOptions agentOptions,
|
||||
Func<IChatClient, IChatClient>? clientFactory,
|
||||
OpenAIClientOptions? openAIClientOptions,
|
||||
IServiceProvider? services)
|
||||
{
|
||||
IChatClient chatClient = new AzureAIAgentChatClient(agentClient, agentRecord, agentOptions.ChatOptions, openAIClientOptions);
|
||||
|
||||
if (clientFactory is not null)
|
||||
{
|
||||
chatClient = clientFactory(chatClient);
|
||||
}
|
||||
|
||||
return new ChatClientAgent(chatClient, agentOptions, services: services);
|
||||
}
|
||||
|
||||
/// <summary>This method creates an <see cref="ChatClientAgent"/> with the specified ChatClientAgentOptions.</summary>
|
||||
private static ChatClientAgent CreateChatClientAgent(
|
||||
AgentClient agentClient,
|
||||
AgentReference agentReference,
|
||||
ChatClientAgentOptions agentOptions,
|
||||
Func<IChatClient, IChatClient>? clientFactory,
|
||||
OpenAIClientOptions? openAIClientOptions,
|
||||
IServiceProvider? services)
|
||||
{
|
||||
IChatClient chatClient = new AzureAIAgentChatClient(agentClient, agentReference, defaultModelId: null, agentOptions.ChatOptions, openAIClientOptions);
|
||||
|
||||
if (clientFactory is not null)
|
||||
{
|
||||
chatClient = clientFactory(chatClient);
|
||||
}
|
||||
|
||||
return new ChatClientAgent(chatClient, agentOptions, services: services);
|
||||
}
|
||||
|
||||
/// <summary>This method creates an <see cref="ChatClientAgent"/> with a auto-generated ChatClientAgentOptions from the specified configuration parameters.</summary>
|
||||
private static ChatClientAgent CreateChatClientAgent(
|
||||
AgentClient AgentClient,
|
||||
@@ -680,7 +747,23 @@ public static class AgentClientExtensions
|
||||
CreateChatClientAgentOptions(agentVersion, new ChatOptions() { Tools = tools }, requireInvocableTools),
|
||||
clientFactory,
|
||||
openAIClientOptions,
|
||||
requireInvocableTools,
|
||||
services);
|
||||
|
||||
/// <summary>This method creates an <see cref="ChatClientAgent"/> with a auto-generated ChatClientAgentOptions from the specified configuration parameters.</summary>
|
||||
private static ChatClientAgent CreateChatClientAgent(
|
||||
AgentClient AgentClient,
|
||||
AgentRecord agentRecord,
|
||||
IList<AITool>? tools,
|
||||
Func<IChatClient, IChatClient>? clientFactory,
|
||||
OpenAIClientOptions? openAIClientOptions,
|
||||
bool requireInvocableTools,
|
||||
IServiceProvider? services)
|
||||
=> CreateChatClientAgent(
|
||||
AgentClient,
|
||||
agentRecord,
|
||||
CreateChatClientAgentOptions(agentRecord.Versions.Latest, new ChatOptions() { Tools = tools }, requireInvocableTools),
|
||||
clientFactory,
|
||||
openAIClientOptions,
|
||||
services);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -21,15 +21,39 @@ internal sealed class AzureAIAgentChatClient : DelegatingChatClient
|
||||
{
|
||||
private readonly ChatClientMetadata? _metadata;
|
||||
private readonly AgentClient _agentClient;
|
||||
private readonly AgentVersion _agentVersion;
|
||||
private readonly AgentVersion? _agentVersion;
|
||||
private readonly AgentRecord? _agentRecord;
|
||||
private readonly ChatOptions? _chatOptions;
|
||||
|
||||
private readonly AgentReference _agentReference;
|
||||
/// <summary>
|
||||
/// The usage of a no-op model is a necessary change to avoid OpenAIClients to throw exceptions when
|
||||
/// used with Azure AI Agents as the model used is now defined at the agent creation time.
|
||||
/// </summary>
|
||||
private const string NoOpModel = "no-op";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AzureAIAgentChatClient"/> class.
|
||||
/// </summary>
|
||||
/// <param name="agentClient">An instance of <see cref="AgentClient"/> to interact with Azure AI Agents services.</param>
|
||||
/// <param name="agentReference">An instance of <see cref="AgentReference"/> representing the specific agent to use.</param>
|
||||
/// <param name="defaultModelId">The default model to use for the agent, if applicable.</param>
|
||||
/// <param name="chatOptions">An instance of <see cref="ChatOptions"/> representing the options on how the agent was predefined.</param>
|
||||
/// <param name="openAIClientOptions">An optional <see cref="OpenAIClientOptions"/> for configuring the underlying OpenAI client.</param>
|
||||
/// <remarks>
|
||||
/// The <see cref="IChatClient"/> provided should be decorated with a <see cref="AzureAIAgentChatClient"/> for proper functionality.
|
||||
/// </remarks>
|
||||
internal AzureAIAgentChatClient(AgentClient agentClient, AgentReference agentReference, string? defaultModelId, ChatOptions? chatOptions, OpenAIClientOptions? openAIClientOptions = null)
|
||||
: base(Throw.IfNull(agentClient)
|
||||
.GetOpenAIClient(openAIClientOptions)
|
||||
.GetOpenAIResponseClient(defaultModelId ?? NoOpModel)
|
||||
.AsIChatClient())
|
||||
{
|
||||
this._agentClient = agentClient;
|
||||
this._agentReference = Throw.IfNull(agentReference);
|
||||
this._metadata = new ChatClientMetadata("azure.ai.agents", defaultModelId: defaultModelId);
|
||||
this._chatOptions = chatOptions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AzureAIAgentChatClient"/> class.
|
||||
/// </summary>
|
||||
@@ -43,18 +67,18 @@ internal sealed class AzureAIAgentChatClient : DelegatingChatClient
|
||||
internal AzureAIAgentChatClient(AgentClient agentClient, AgentRecord agentRecord, ChatOptions? chatOptions, OpenAIClientOptions? openAIClientOptions = null)
|
||||
: this(agentClient, Throw.IfNull(agentRecord).Versions.Latest, chatOptions, openAIClientOptions)
|
||||
{
|
||||
this._agentRecord = agentRecord;
|
||||
}
|
||||
|
||||
internal AzureAIAgentChatClient(AgentClient agentClient, AgentVersion agentVersion, ChatOptions? chatOptions, OpenAIClientOptions? openAIClientOptions = null)
|
||||
: base(agentClient
|
||||
.GetOpenAIClient(openAIClientOptions)
|
||||
.GetOpenAIResponseClient((agentVersion.Definition as PromptAgentDefinition)?.Model ?? NoOpModel)
|
||||
.AsIChatClient())
|
||||
: this(
|
||||
agentClient,
|
||||
new AgentReference(Throw.IfNull(agentVersion).Name) { Version = agentVersion.Version },
|
||||
(agentVersion.Definition as PromptAgentDefinition)?.Model,
|
||||
chatOptions,
|
||||
openAIClientOptions)
|
||||
{
|
||||
this._agentClient = Throw.IfNull(agentClient);
|
||||
this._agentVersion = Throw.IfNull(agentVersion);
|
||||
this._metadata = new ChatClientMetadata("azure.ai.agents");
|
||||
this._chatOptions = chatOptions;
|
||||
this._agentVersion = agentVersion;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -66,6 +90,10 @@ internal sealed class AzureAIAgentChatClient : DelegatingChatClient
|
||||
? 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);
|
||||
}
|
||||
|
||||
@@ -113,7 +141,7 @@ internal sealed class AzureAIAgentChatClient : DelegatingChatClient
|
||||
responseCreationOptions = new ResponseCreationOptions();
|
||||
}
|
||||
|
||||
SetAgentReference(responseCreationOptions, this._agentVersion);
|
||||
this.SetAgentReference(responseCreationOptions);
|
||||
|
||||
return responseCreationOptions;
|
||||
};
|
||||
@@ -130,11 +158,9 @@ internal sealed class AzureAIAgentChatClient : DelegatingChatClient
|
||||
responseCreationOptions.Patch.Set([.. "$."u8, .. Encoding.UTF8.GetBytes(key)], value);
|
||||
}
|
||||
|
||||
private static void SetAgentReference(ResponseCreationOptions responseCreationOptions, AgentVersion agentVersion)
|
||||
private void SetAgentReference(ResponseCreationOptions responseCreationOptions)
|
||||
{
|
||||
var agentReference = new AgentReference(agentVersion.Name) { Version = agentVersion.Version };
|
||||
|
||||
SetAdditionalProperty(responseCreationOptions, "agent", ModelReaderWriter.Write(agentReference, new ModelReaderWriterOptions("W"), AzureAIAgentsContext.Default));
|
||||
SetAdditionalProperty(responseCreationOptions, "agent", ModelReaderWriter.Write(this._agentReference, new ModelReaderWriterOptions("W"), AzureAIAgentsContext.Default));
|
||||
responseCreationOptions.Patch.Remove([.. "$."u8, .. Encoding.UTF8.GetBytes("model")]);
|
||||
}
|
||||
#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.
|
||||
|
||||
@@ -161,7 +161,7 @@ public sealed class AzureAgentProvider(Uri projectEndpoint, TokenCredential proj
|
||||
|
||||
AgentClient client = this.GetAgentClient();
|
||||
|
||||
agent = client.GetAIAgent(agentVersion, tools: null, clientFactory: null, openAIClientOptions: null, services: null, cancellationToken);
|
||||
agent = client.GetAIAgent(agentVersion, tools: null, clientFactory: null, openAIClientOptions: null, services: null);
|
||||
|
||||
FunctionInvokingChatClient? functionInvokingClient = agent.GetService<FunctionInvokingChatClient>();
|
||||
if (functionInvokingClient is not null)
|
||||
|
||||
@@ -1944,6 +1944,360 @@ public sealed class AgentClientExtensionsTests
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetAIAgent(AgentClient, AgentReference) Tests
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetAIAgent throws ArgumentNullException when AgentClient is null.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetAIAgent_WithAgentReference_WithNullClient_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
AgentClient? client = null;
|
||||
var agentReference = new AgentReference("test-name") { Version = "1" };
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<ArgumentNullException>(() =>
|
||||
client!.GetAIAgent(agentReference));
|
||||
|
||||
Assert.Equal("agentClient", exception.ParamName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetAIAgent throws ArgumentNullException when agentReference is null.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetAIAgent_WithAgentReference_WithNullAgentReference_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var mockClient = new Mock<AgentClient>();
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<ArgumentNullException>(() =>
|
||||
mockClient.Object.GetAIAgent((AgentReference)null!));
|
||||
|
||||
Assert.Equal("agentReference", exception.ParamName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetAIAgent with AgentReference creates a valid agent.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetAIAgent_WithAgentReference_CreatesValidAgent()
|
||||
{
|
||||
// Arrange
|
||||
AgentClient client = this.CreateTestAgentClient();
|
||||
var agentReference = new AgentReference("test-name") { Version = "1" };
|
||||
|
||||
// Act
|
||||
var agent = client.GetAIAgent(agentReference);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(agent);
|
||||
Assert.Equal("test-name", agent.Name);
|
||||
Assert.Equal("test-name:1", agent.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetAIAgent with AgentReference and clientFactory applies the factory.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetAIAgent_WithAgentReference_WithClientFactory_AppliesFactoryCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
AgentClient client = this.CreateTestAgentClient();
|
||||
var agentReference = new AgentReference("test-name") { Version = "1" };
|
||||
TestChatClient? testChatClient = null;
|
||||
|
||||
// Act
|
||||
var agent = client.GetAIAgent(
|
||||
agentReference,
|
||||
clientFactory: (innerClient) => testChatClient = new TestChatClient(innerClient));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(agent);
|
||||
var retrievedTestClient = agent.GetService<TestChatClient>();
|
||||
Assert.NotNull(retrievedTestClient);
|
||||
Assert.Same(testChatClient, retrievedTestClient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetAIAgent with AgentReference sets the agent ID correctly.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetAIAgent_WithAgentReference_SetsAgentIdCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
AgentClient client = this.CreateTestAgentClient();
|
||||
var agentReference = new AgentReference("test-name") { Version = "2" };
|
||||
|
||||
// Act
|
||||
var agent = client.GetAIAgent(agentReference);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(agent);
|
||||
Assert.Equal("test-name:2", agent.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetAIAgent with AgentReference and tools includes the tools in ChatOptions.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetAIAgent_WithAgentReference_WithTools_IncludesToolsInChatOptions()
|
||||
{
|
||||
// Arrange
|
||||
AgentClient client = this.CreateTestAgentClient();
|
||||
var agentReference = new AgentReference("test-name") { Version = "1" };
|
||||
var tools = new List<AITool>
|
||||
{
|
||||
AIFunctionFactory.Create(() => "test", "test_function", "A test function")
|
||||
};
|
||||
|
||||
// Act
|
||||
var agent = client.GetAIAgent(agentReference, tools: tools);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(agent);
|
||||
var chatOptions = GetAgentChatOptions(agent);
|
||||
Assert.NotNull(chatOptions);
|
||||
Assert.NotNull(chatOptions.Tools);
|
||||
Assert.Single(chatOptions.Tools);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetService<AgentRecord> Tests
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetService returns AgentRecord for agents created from AgentRecord.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetService_WithAgentRecord_ReturnsAgentRecord()
|
||||
{
|
||||
// Arrange
|
||||
AgentClient client = this.CreateTestAgentClient();
|
||||
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
|
||||
// Act
|
||||
var agent = client.GetAIAgent(agentRecord);
|
||||
var retrievedRecord = agent.GetService<AgentRecord>();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(retrievedRecord);
|
||||
Assert.Equal(agentRecord.Id, retrievedRecord.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetService returns null for AgentRecord when agent is created from AgentReference.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetService_WithAgentReference_ReturnsNullForAgentRecord()
|
||||
{
|
||||
// Arrange
|
||||
AgentClient client = this.CreateTestAgentClient();
|
||||
var agentReference = new AgentReference("test-name") { Version = "1" };
|
||||
|
||||
// Act
|
||||
var agent = client.GetAIAgent(agentReference);
|
||||
var retrievedRecord = agent.GetService<AgentRecord>();
|
||||
|
||||
// Assert
|
||||
Assert.Null(retrievedRecord);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetService<AgentVersion> Tests
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetService returns AgentVersion for agents created from AgentVersion.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetService_WithAgentVersion_ReturnsAgentVersion()
|
||||
{
|
||||
// Arrange
|
||||
AgentClient client = this.CreateTestAgentClient();
|
||||
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
|
||||
// Act
|
||||
var agent = client.GetAIAgent(agentVersion);
|
||||
var retrievedVersion = agent.GetService<AgentVersion>();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(retrievedVersion);
|
||||
Assert.Equal(agentVersion.Id, retrievedVersion.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetService returns null for AgentVersion when agent is created from AgentReference.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetService_WithAgentReference_ReturnsNullForAgentVersion()
|
||||
{
|
||||
// Arrange
|
||||
AgentClient client = this.CreateTestAgentClient();
|
||||
var agentReference = new AgentReference("test-name") { Version = "1" };
|
||||
|
||||
// Act
|
||||
var agent = client.GetAIAgent(agentReference);
|
||||
var retrievedVersion = agent.GetService<AgentVersion>();
|
||||
|
||||
// Assert
|
||||
Assert.Null(retrievedVersion);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ChatClientMetadata Tests
|
||||
|
||||
/// <summary>
|
||||
/// Verify that ChatClientMetadata is properly populated for agents created from AgentRecord.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ChatClientMetadata_WithAgentRecord_IsPopulatedCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
AgentClient client = this.CreateTestAgentClient();
|
||||
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
|
||||
// Act
|
||||
var agent = client.GetAIAgent(agentRecord);
|
||||
var metadata = agent.GetService<ChatClientMetadata>();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(metadata);
|
||||
Assert.NotNull(metadata.DefaultModelId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that ChatClientMetadata.DefaultModelId is set from PromptAgentDefinition model property.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ChatClientMetadata_WithPromptAgentDefinition_SetsDefaultModelIdFromModel()
|
||||
{
|
||||
// Arrange
|
||||
AgentClient client = this.CreateTestAgentClient();
|
||||
var definition = new PromptAgentDefinition("gpt-4-turbo")
|
||||
{
|
||||
Instructions = "Test instructions"
|
||||
};
|
||||
AgentRecord agentRecord = this.CreateTestAgentRecord(definition);
|
||||
|
||||
// Act
|
||||
var agent = client.GetAIAgent(agentRecord);
|
||||
var metadata = agent.GetService<ChatClientMetadata>();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(metadata);
|
||||
// The metadata should contain the model information from the agent definition
|
||||
Assert.NotNull(metadata.DefaultModelId);
|
||||
Assert.Equal("gpt-4-turbo", metadata.DefaultModelId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that ChatClientMetadata is properly populated for agents created from AgentVersion.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ChatClientMetadata_WithAgentVersion_IsPopulatedCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
AgentClient client = this.CreateTestAgentClient();
|
||||
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
|
||||
// Act
|
||||
var agent = client.GetAIAgent(agentVersion);
|
||||
var metadata = agent.GetService<ChatClientMetadata>();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(metadata);
|
||||
Assert.NotNull(metadata.DefaultModelId);
|
||||
Assert.Equal((agentVersion.Definition as PromptAgentDefinition)!.Model, metadata.DefaultModelId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AgentReference Availability Tests
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetService returns AgentReference for agents created from AgentReference.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetService_WithAgentReference_ReturnsAgentReference()
|
||||
{
|
||||
// Arrange
|
||||
AgentClient client = this.CreateTestAgentClient();
|
||||
var agentReference = new AgentReference("test-agent") { Version = "1.0" };
|
||||
|
||||
// Act
|
||||
var agent = client.GetAIAgent(agentReference);
|
||||
var retrievedReference = agent.GetService<AgentReference>();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(retrievedReference);
|
||||
Assert.Equal("test-agent", retrievedReference.Name);
|
||||
Assert.Equal("1.0", retrievedReference.Version);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetService returns null for AgentReference when agent is created from AgentRecord.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetService_WithAgentRecord_ReturnsAlsoAgentReference()
|
||||
{
|
||||
// Arrange
|
||||
AgentClient client = this.CreateTestAgentClient();
|
||||
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
||||
|
||||
// Act
|
||||
var agent = client.GetAIAgent(agentRecord);
|
||||
var retrievedReference = agent.GetService<AgentReference>();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(retrievedReference);
|
||||
Assert.Equal(agentRecord.Name, retrievedReference.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetService returns null for AgentReference when agent is created from AgentVersion.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetService_WithAgentVersion_ReturnsAlsoAgentReference()
|
||||
{
|
||||
// Arrange
|
||||
AgentClient client = this.CreateTestAgentClient();
|
||||
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
||||
|
||||
// Act
|
||||
var agent = client.GetAIAgent(agentVersion);
|
||||
var retrievedReference = agent.GetService<AgentReference>();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(retrievedReference);
|
||||
Assert.Equal(agentVersion.Name, retrievedReference.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetService returns AgentReference with correct version information.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetService_WithAgentReference_ReturnsCorrectVersionInformation()
|
||||
{
|
||||
// Arrange
|
||||
AgentClient client = this.CreateTestAgentClient();
|
||||
var agentReference = new AgentReference("versioned-agent") { Version = "3.5" };
|
||||
|
||||
// Act
|
||||
var agent = client.GetAIAgent(agentReference);
|
||||
var retrievedReference = agent.GetService<AgentReference>();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(retrievedReference);
|
||||
Assert.Equal("versioned-agent", retrievedReference.Name);
|
||||
Assert.Equal("3.5", retrievedReference.Version);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
@@ -1957,9 +2311,9 @@ public sealed class AgentClientExtensionsTests
|
||||
/// <summary>
|
||||
/// Creates a test AgentRecord for testing.
|
||||
/// </summary>
|
||||
private AgentRecord CreateTestAgentRecord()
|
||||
private AgentRecord CreateTestAgentRecord(AgentDefinition? agentDefinition = null)
|
||||
{
|
||||
return ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(TestDataUtil.GetAgentResponseJson()))!;
|
||||
return ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(TestDataUtil.GetAgentResponseJson(agentDefinition: agentDefinition)))!;
|
||||
}
|
||||
|
||||
private const string OpenAPISpec = """
|
||||
|
||||
Reference in New Issue
Block a user