.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:
Roger Barreto
2025-11-12 21:32:24 +00:00
committed by GitHub
Unverified
parent 0f539d9748
commit 56ced98b27
4 changed files with 505 additions and 42 deletions
@@ -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 = """