mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
2b66ca03b2
* Initial plan * Fix issue #3195: Handle empty Version and ID in Azure AI agent responses This fix addresses the issue where hosted MCP agents (like AgentWithHostedMCP) fail with "ID cannot be null or empty (Parameter 'id')" error when deployed to Azure AI Foundry. Changes: - Add CreateAgentReference helper method in AzureAIProjectChatClient that defaults empty version to "latest" - Update CreateChatClientAgentOptions to generate a fallback ID from name and version when AgentVersion.Id is null or empty - Add GetAgentVersionResponseJsonWithEmptyVersion and GetAgentResponseJsonWithEmptyVersion test data methods - Add unit tests for empty version handling scenarios Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> * Address code review feedback: improve documentation and test comments Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> * Address PR review: Use IsNullOrWhiteSpace and add whitespace unit tests --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
3217 lines
118 KiB
C#
3217 lines
118 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.ClientModel;
|
|
using System.ClientModel.Primitives;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Azure.AI.Projects;
|
|
using Azure.AI.Projects.OpenAI;
|
|
using Microsoft.Extensions.AI;
|
|
using Moq;
|
|
using OpenAI.Responses;
|
|
|
|
namespace Microsoft.Agents.AI.AzureAI.UnitTests;
|
|
|
|
/// <summary>
|
|
/// Unit tests for the <see cref="AzureAIProjectChatClientExtensions"/> class.
|
|
/// </summary>
|
|
public sealed class AzureAIProjectChatClientExtensionsTests
|
|
{
|
|
#region AsAIAgent(AIProjectClient, AgentRecord) Tests
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentNullException when AIProjectClient is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentRecord_WithNullClient_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
AIProjectClient? client = null;
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentNullException>(() =>
|
|
client!.AsAIAgent(agentRecord));
|
|
|
|
Assert.Equal("aiProjectClient", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentNullException when agentRecord is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentRecord_WithNullAgentRecord_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentNullException>(() =>
|
|
mockClient.Object.AsAIAgent((AgentRecord)null!));
|
|
|
|
Assert.Equal("agentRecord", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentRecord creates a valid agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentRecord_CreatesValidAgent()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentRecord);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("agent_abc123", agent.Name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentRecord and clientFactory applies the factory.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentRecord_WithClientFactory_AppliesFactoryCorrectly()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
TestChatClient? testChatClient = null;
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(
|
|
agentRecord,
|
|
clientFactory: (innerClient) => testChatClient = new TestChatClient(innerClient));
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
var retrievedTestClient = agent.GetService<TestChatClient>();
|
|
Assert.NotNull(retrievedTestClient);
|
|
Assert.Same(testChatClient, retrievedTestClient);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AsAIAgent(AIProjectClient, AgentVersion) Tests
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentNullException when AIProjectClient is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentVersion_WithNullClient_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
AIProjectClient? client = null;
|
|
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentNullException>(() =>
|
|
client!.AsAIAgent(agentVersion));
|
|
|
|
Assert.Equal("aiProjectClient", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentNullException when agentVersion is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentVersion_WithNullAgentVersion_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentNullException>(() =>
|
|
mockClient.Object.AsAIAgent((AgentVersion)null!));
|
|
|
|
Assert.Equal("agentVersion", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentVersion creates a valid agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentVersion_CreatesValidAgent()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentVersion);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("agent_abc123", agent.Name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentVersion and clientFactory applies the factory.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentVersion_WithClientFactory_AppliesFactoryCorrectly()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
|
TestChatClient? testChatClient = null;
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(
|
|
agentVersion,
|
|
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 AsAIAgent with requireInvocableTools=true enforces invocable tools.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentVersion_WithRequireInvocableToolsTrue_EnforcesInvocableTools()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "test", "test_function", "A test function")
|
|
};
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentVersion, tools: tools);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with requireInvocableTools=false allows declarative functions.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentVersion_WithRequireInvocableToolsFalse_AllowsDeclarativeFunctions()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
|
|
|
// Act - should not throw even without tools when requireInvocableTools is false
|
|
var agent = client.AsAIAgent(agentVersion);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetAIAgentAsync(AIProjectClient, ChatClientAgentOptions) Tests
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentNullException when client is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithOptions_WithNullClient_ThrowsArgumentNullExceptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient? client = null;
|
|
var options = new ChatClientAgentOptions { Name = "test-agent" };
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
|
client!.GetAIAgentAsync(options));
|
|
|
|
Assert.Equal("aiProjectClient", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentNullException when options is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithOptions_WithNullOptions_ThrowsArgumentNullExceptionAsync()
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
|
mockClient.Object.GetAIAgentAsync((ChatClientAgentOptions)null!));
|
|
|
|
Assert.Equal("options", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with ChatClientAgentOptions creates a valid agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithOptions_CreatesValidAgentAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent");
|
|
var options = new ChatClientAgentOptions { Name = "test-agent" };
|
|
|
|
// Act
|
|
var agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("test-agent", agent.Name);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AsAIAgent(AIProjectClient, string) Tests
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentNullException when AIProjectClient is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_ByName_WithNullClient_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
AIProjectClient? client = null;
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentNullException>(() =>
|
|
client!.AsAIAgent("test-agent"));
|
|
|
|
Assert.Equal("aiProjectClient", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentNullException when name is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_ByName_WithNullName_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentNullException>(() =>
|
|
mockClient.Object.AsAIAgent((string)null!));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentException when name is empty.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_ByName_WithEmptyName_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentException>(() =>
|
|
mockClient.Object.AsAIAgent(string.Empty));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetAIAgentAsync(AIProjectClient, string) Tests
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync throws ArgumentNullException when AIProjectClient is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_ByName_WithNullClient_ThrowsArgumentNullExceptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient? client = null;
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
|
client!.GetAIAgentAsync("test-agent"));
|
|
|
|
Assert.Equal("aiProjectClient", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync throws ArgumentNullException when name is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_ByName_WithNullName_ThrowsArgumentNullExceptionAsync()
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
|
mockClient.Object.GetAIAgentAsync(name: null!));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync throws InvalidOperationException when agent is not found.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_ByName_WithNonExistentAgent_ThrowsInvalidOperationExceptionAsync()
|
|
{
|
|
// Arrange
|
|
var mockAgentOperations = new Mock<AIProjectAgentsOperations>();
|
|
mockAgentOperations
|
|
.Setup(c => c.GetAgentAsync(It.IsAny<string>(), It.IsAny<RequestOptions>()))
|
|
.ReturnsAsync(ClientResult.FromOptionalValue((AgentRecord)null!, new MockPipelineResponse(200, BinaryData.FromString("null"))));
|
|
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
mockClient.SetupGet(c => c.Agents).Returns(mockAgentOperations.Object);
|
|
mockClient.Setup(x => x.GetConnection(It.IsAny<string>())).Returns(new ClientConnection("fake-connection-id", "http://localhost", ClientPipeline.Create(), CredentialKind.None));
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
|
mockClient.Object.GetAIAgentAsync("non-existent-agent"));
|
|
|
|
Assert.Contains("not found", exception.Message);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AsAIAgent(AIProjectClient, AgentRecord) with tools Tests
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with additional tools when the definition has no tools does not throw and results in an agent with no tools.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentRecordAndAdditionalTools_WhenDefinitionHasNoTools_ShouldNotThrow()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "test", "test_function", "A test function")
|
|
};
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentRecord, tools: tools);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
var chatClient = agent.GetService<IChatClient>();
|
|
Assert.NotNull(chatClient);
|
|
var agentVersion = chatClient.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
var definition = Assert.IsType<PromptAgentDefinition>(agentVersion.Definition);
|
|
Assert.Empty(definition.Tools);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with null tools works correctly.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentRecordAndNullTools_WorksCorrectly()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentRecord, tools: null);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("agent_abc123", agent.Name);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetAIAgentAsync(AIProjectClient, string) with tools Tests
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with tools parameter creates an agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithNameAndTools_CreatesAgentAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "test", "test_function", "A test function")
|
|
};
|
|
|
|
// Act
|
|
var agent = await client.GetAIAgentAsync("test-agent", tools: tools);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with model and options creates a valid agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithModelAndOptions_CreatesValidAgentAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", instructions: "Test instructions");
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new() { Instructions = "Test instructions" }
|
|
};
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("test-agent", agent.Name);
|
|
Assert.Equal("Test instructions", agent.Instructions);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with model and options and clientFactory applies the factory.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithModelAndOptions_WithClientFactory_AppliesFactoryCorrectlyAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", instructions: "Test instructions");
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new() { Instructions = "Test instructions" }
|
|
};
|
|
TestChatClient? testChatClient = null;
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync(
|
|
"test-model",
|
|
options,
|
|
clientFactory: (innerClient) => testChatClient = new TestChatClient(innerClient));
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
var retrievedTestClient = agent.GetService<TestChatClient>();
|
|
Assert.NotNull(retrievedTestClient);
|
|
Assert.Same(testChatClient, retrievedTestClient);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CreateAIAgentAsync(AIProjectClient, string, AgentDefinition) Tests
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync throws ArgumentNullException when AIProjectClient is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithAgentDefinition_WithNullClient_ThrowsArgumentNullExceptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient? client = null;
|
|
var definition = new PromptAgentDefinition("test-model");
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
|
client!.CreateAIAgentAsync("agent-name", options));
|
|
|
|
Assert.Equal("aiProjectClient", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync throws ArgumentNullException when creationOptions is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithAgentDefinition_WithNullDefinition_ThrowsArgumentNullExceptionAsync()
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
|
mockClient.Object.CreateAIAgentAsync(name: "agent-name", null!));
|
|
|
|
Assert.Equal("creationOptions", exception.ParamName);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Tool Validation Tests
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgent creates an agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithDefinition_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgent without tools parameter creates an agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithoutToolsParameter_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
|
|
var definitionResponse = GeneratePromptDefinitionResponse(definition, null);
|
|
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgent without tools in definition creates an agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithoutToolsInDefinition_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", agentDefinitionResponse: definition);
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgent uses tools from the definition when no separate tools parameter is provided.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithDefinitionTools_UsesDefinitionToolsAsync()
|
|
{
|
|
// Arrange
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
|
|
// Add a function tool to the definition
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("required_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
|
|
// Create a response definition with the same tool
|
|
var definitionResponse = GeneratePromptDefinitionResponse(definition, definition.Tools.Select(t => t.AsAITool()).ToList());
|
|
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
var agentVersion = agent.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
if (agentVersion.Definition is PromptAgentDefinition promptDef)
|
|
{
|
|
Assert.NotEmpty(promptDef.Tools);
|
|
Assert.Single(promptDef.Tools);
|
|
Assert.Equal("required_tool", (promptDef.Tools.First() as FunctionTool)?.FunctionName);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgent creates an agent successfully when definition has a mix of custom and hosted tools.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithMixedToolsInDefinition_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("create_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
definition.Tools.Add(new HostedWebSearchTool().GetService<ResponseTool>() ?? new HostedWebSearchTool().AsOpenAIResponseTool());
|
|
definition.Tools.Add(new HostedFileSearchTool().GetService<ResponseTool>() ?? new HostedFileSearchTool().AsOpenAIResponseTool());
|
|
|
|
// Simulate agent definition response with the tools
|
|
var definitionResponse = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
|
|
foreach (var tool in definition.Tools)
|
|
{
|
|
definitionResponse.Tools.Add(tool);
|
|
}
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definitionResponse);
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
var agentVersion = agent.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
if (agentVersion.Definition is PromptAgentDefinition promptDef)
|
|
{
|
|
Assert.NotEmpty(promptDef.Tools);
|
|
Assert.Equal(3, promptDef.Tools.Count);
|
|
}
|
|
}
|
|
|
|
/// <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(TestDataUtil.GetAgentVersionResponseJson(), Encoding.UTF8, "application/json") };
|
|
});
|
|
|
|
#pragma warning disable CA5399
|
|
using var httpClient = new HttpClient(httpHandler);
|
|
#pragma warning restore CA5399
|
|
|
|
var client = new AIProjectClient(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 when providing AITools with AsAIAgent, any additional tool that doesn't match the tools in agent definition are ignored.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_AdditionalAITools_WhenNotInTheDefinitionAreIgnored()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var agentVersion = this.CreateTestAgentVersion();
|
|
|
|
// Manually add tools to the definition to simulate inline tools
|
|
if (agentVersion.Definition is PromptAgentDefinition promptDef)
|
|
{
|
|
promptDef.Tools.Add(ResponseTool.CreateFunctionTool("inline_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
}
|
|
|
|
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");
|
|
|
|
// Act & Assert
|
|
var agent = client.AsAIAgent(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
|
|
|
|
#region Inline Tools vs Parameter Tools Tests
|
|
|
|
/// <summary>
|
|
/// Verify that tools passed as parameters are accepted by AsAIAgent.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithParameterTools_AcceptsTools()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "tool1", "param_tool_1", "First parameter tool"),
|
|
AIFunctionFactory.Create(() => "tool2", "param_tool_2", "Second parameter tool")
|
|
};
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentRecord, tools: tools);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
var chatClient = agent.GetService<IChatClient>();
|
|
Assert.NotNull(chatClient);
|
|
var agentVersion = chatClient.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgent with string parameters and tools creates an agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithStringParamsAndTools_CreatesAgentAsync()
|
|
{
|
|
// Arrange
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "weather", "string_param_tool", "Tool from string params")
|
|
};
|
|
|
|
var definitionResponse = GeneratePromptDefinitionResponse(new PromptAgentDefinition("test-model") { Instructions = "Test instructions" }, tools);
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync(
|
|
"test-agent",
|
|
"test-model",
|
|
"Test instructions",
|
|
tools: tools);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
var agentVersion = agent.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
if (agentVersion.Definition is PromptAgentDefinition promptDef)
|
|
{
|
|
Assert.NotEmpty(promptDef.Tools);
|
|
Assert.Single(promptDef.Tools);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with tools in definition creates an agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithDefinitionTools_CreatesAgentAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("async_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with tools parameter creates an agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithToolsParameter_CreatesAgentAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "async_get_result", "async_get_tool", "An async get tool")
|
|
};
|
|
|
|
// Act
|
|
var agent = await client.GetAIAgentAsync("test-agent", tools: tools);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Declarative Function Handling Tests
|
|
|
|
/// <summary>
|
|
/// Verifies that CreateAIAgent uses tools from definition when they are ResponseTool instances, resulting in successful agent creation.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithResponseToolsInDefinition_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
|
|
|
|
var fabricToolOptions = new FabricDataAgentToolOptions();
|
|
fabricToolOptions.ProjectConnections.Add(new ToolProjectConnection("connection-id"));
|
|
|
|
var sharepointOptions = new SharePointGroundingToolOptions();
|
|
sharepointOptions.ProjectConnections.Add(new ToolProjectConnection("connection-id"));
|
|
|
|
var structuredOutputs = new StructuredOutputDefinition("name", "description", BinaryData.FromString(AIJsonUtilities.CreateJsonSchema(new { id = "test" }.GetType()).ToString()), false);
|
|
|
|
// Add tools to the definition
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("create_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
definition.Tools.Add((ResponseTool)AgentTool.CreateBingCustomSearchTool(new BingCustomSearchToolParameters([new BingCustomSearchConfiguration("connection-id", "instance-name")])));
|
|
definition.Tools.Add((ResponseTool)AgentTool.CreateBrowserAutomationTool(new BrowserAutomationToolParameters(new BrowserAutomationToolConnectionParameters("id"))));
|
|
definition.Tools.Add(AgentTool.CreateA2ATool(new Uri("https://test-uri.microsoft.com")));
|
|
definition.Tools.Add((ResponseTool)AgentTool.CreateBingGroundingTool(new BingGroundingSearchToolOptions([new BingGroundingSearchConfiguration("connection-id")])));
|
|
definition.Tools.Add((ResponseTool)AgentTool.CreateMicrosoftFabricTool(fabricToolOptions));
|
|
definition.Tools.Add((ResponseTool)AgentTool.CreateOpenApiTool(new OpenAPIFunctionDefinition("name", BinaryData.FromString(OpenAPISpec), new OpenAPIAnonymousAuthenticationDetails())));
|
|
definition.Tools.Add((ResponseTool)AgentTool.CreateSharepointTool(sharepointOptions));
|
|
definition.Tools.Add((ResponseTool)AgentTool.CreateStructuredOutputsTool(structuredOutputs));
|
|
definition.Tools.Add((ResponseTool)AgentTool.CreateAzureAISearchTool(new AzureAISearchToolOptions([new AzureAISearchToolIndex() { IndexName = "name" }])));
|
|
|
|
// Generate agent definition response with the tools
|
|
var definitionResponse = GeneratePromptDefinitionResponse(definition, definition.Tools.Select(t => t.AsAITool()).ToList());
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definitionResponse);
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
var agentVersion = agent.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
if (agentVersion.Definition is PromptAgentDefinition promptDef)
|
|
{
|
|
Assert.NotEmpty(promptDef.Tools);
|
|
Assert.Equal(10, promptDef.Tools.Count);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync accepts FunctionTools from definition.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithFunctionToolsInDefinition_AcceptsDeclarativeFunctionAsync()
|
|
{
|
|
// Arrange
|
|
var functionTool = ResponseTool.CreateFunctionTool(
|
|
functionName: "get_user_name",
|
|
functionParameters: BinaryData.FromString("{}"),
|
|
strictModeEnabled: false,
|
|
functionDescription: "Gets the user's name, as used for friendly address."
|
|
);
|
|
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
definition.Tools.Add(functionTool);
|
|
|
|
// Generate response with the declarative function
|
|
var definitionResponse = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
definitionResponse.Tools.Add(functionTool);
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync accepts declarative functions from definition.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithDeclarativeFunctionFromDefinition_AcceptsDeclarativeFunctionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
|
|
// Create a declarative function (not invocable) using AIFunctionFactory.CreateDeclaration
|
|
using var doc = JsonDocument.Parse("{}");
|
|
var declarativeFunction = AIFunctionFactory.CreateDeclaration("test_function", "A test function", doc.RootElement);
|
|
|
|
// Add to definition
|
|
definition.Tools.Add(declarativeFunction.AsOpenAIResponseTool() ?? throw new InvalidOperationException());
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync accepts declarative functions from definition.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithDeclarativeFunctionInDefinition_AcceptsDeclarativeFunctionAsync()
|
|
{
|
|
// Arrange
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
|
|
// Create a declarative function (not invocable) using AIFunctionFactory.CreateDeclaration
|
|
using var doc = JsonDocument.Parse("{}");
|
|
var declarativeFunction = AIFunctionFactory.CreateDeclaration("test_function", "A test function", doc.RootElement);
|
|
|
|
// Add to definition
|
|
definition.Tools.Add(declarativeFunction.AsOpenAIResponseTool() ?? throw new InvalidOperationException());
|
|
|
|
// Generate response with the declarative function
|
|
var definitionResponse = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
definitionResponse.Tools.Add(declarativeFunction.AsOpenAIResponseTool() ?? throw new InvalidOperationException());
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Options Generation Validation Tests
|
|
|
|
/// <summary>
|
|
/// Verify that ChatClientAgentOptions are generated correctly without tools.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_GeneratesCorrectChatClientAgentOptionsAsync()
|
|
{
|
|
// Arrange
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
|
|
|
|
var definitionResponse = GeneratePromptDefinitionResponse(definition, null);
|
|
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
var agentVersion = agent.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
Assert.Equal("test-agent", agentVersion.Name);
|
|
Assert.Equal("Test instructions", (agentVersion.Definition as PromptAgentDefinition)?.Instructions);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with options preserves custom properties from input options.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithOptions_PreservesCustomPropertiesAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", instructions: "Custom instructions", description: "Custom description");
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
Description = "Custom description",
|
|
ChatOptions = new ChatOptions { Instructions = "Custom instructions" }
|
|
};
|
|
|
|
// Act
|
|
var agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("test-agent", agent.Name);
|
|
Assert.Equal("Custom instructions", agent.Instructions);
|
|
Assert.Equal("Custom description", agent.Description);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with options and tools generates correct ChatClientAgentOptions.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithOptionsAndTools_GeneratesCorrectOptionsAsync()
|
|
{
|
|
// Arrange
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "result", "option_tool", "A tool from options")
|
|
};
|
|
|
|
var definitionResponse = GeneratePromptDefinitionResponse(
|
|
new PromptAgentDefinition("test-model") { Instructions = "Test" },
|
|
tools);
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
|
|
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions { Instructions = "Test", Tools = tools }
|
|
};
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
var agentVersion = agent.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
if (agentVersion.Definition is PromptAgentDefinition promptDef)
|
|
{
|
|
Assert.NotEmpty(promptDef.Tools);
|
|
Assert.Single(promptDef.Tools);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AgentName Validation Tests
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentException when agent name is invalid.
|
|
/// </summary>
|
|
[Theory]
|
|
[MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
|
|
public void AsAIAgent_ByName_WithInvalidAgentName_ThrowsArgumentException(string invalidName)
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentException>(() =>
|
|
mockClient.Object.AsAIAgent(invalidName));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync throws ArgumentException when agent name is invalid.
|
|
/// </summary>
|
|
[Theory]
|
|
[MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
|
|
public async Task GetAIAgentAsync_ByName_WithInvalidAgentName_ThrowsArgumentExceptionAsync(string invalidName)
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
mockClient.Object.GetAIAgentAsync(invalidName));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentException when agent name is invalid.
|
|
/// </summary>
|
|
[Theory]
|
|
[MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
|
|
public async Task GetAIAgentAsync_WithOptions_WithInvalidAgentName_ThrowsArgumentExceptionAsync(string invalidName)
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions { Name = invalidName };
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
client.GetAIAgentAsync(options));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync throws ArgumentException when agent name is invalid.
|
|
/// </summary>
|
|
[Theory]
|
|
[MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
|
|
public async Task CreateAIAgentAsync_WithBasicParams_WithInvalidAgentName_ThrowsArgumentExceptionAsync(string invalidName)
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
mockClient.Object.CreateAIAgentAsync(invalidName, "model", "instructions"));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with AgentVersionCreationOptions throws ArgumentException when agent name is invalid.
|
|
/// </summary>
|
|
[Theory]
|
|
[MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
|
|
public async Task CreateAIAgentAsync_WithAgentDefinition_WithInvalidAgentName_ThrowsArgumentExceptionAsync(string invalidName)
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
var definition = new PromptAgentDefinition("test-model");
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
mockClient.Object.CreateAIAgentAsync(invalidName, options));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with ChatClientAgentOptions throws ArgumentException when agent name is invalid.
|
|
/// </summary>
|
|
[Theory]
|
|
[MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
|
|
public async Task CreateAIAgentAsync_WithOptions_WithInvalidAgentName_ThrowsArgumentExceptionAsync(string invalidName)
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions { Name = invalidName };
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
client.CreateAIAgentAsync("test-model", options));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentReference throws ArgumentException when agent name is invalid.
|
|
/// </summary>
|
|
[Theory]
|
|
[MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
|
|
public void AsAIAgent_WithAgentReference_WithInvalidAgentName_ThrowsArgumentException(string invalidName)
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
var agentReference = new AgentReference(invalidName, "1");
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentException>(() =>
|
|
mockClient.Object.AsAIAgent(agentReference));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AzureAIChatClient Behavior Tests
|
|
|
|
/// <summary>
|
|
/// Verify that the underlying chat client created by extension methods can be wrapped with clientFactory.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithClientFactory_WrapsUnderlyingChatClient()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
int factoryCallCount = 0;
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(
|
|
agentRecord,
|
|
clientFactory: (innerClient) =>
|
|
{
|
|
factoryCallCount++;
|
|
return new TestChatClient(innerClient);
|
|
});
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal(1, factoryCallCount);
|
|
var wrappedClient = agent.GetService<TestChatClient>();
|
|
Assert.NotNull(wrappedClient);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that clientFactory is called with the correct underlying chat client.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithClientFactory_ReceivesCorrectUnderlyingClientAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
IChatClient? receivedClient = null;
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync(
|
|
"test-agent",
|
|
options,
|
|
clientFactory: (innerClient) =>
|
|
{
|
|
receivedClient = innerClient;
|
|
return new TestChatClient(innerClient);
|
|
});
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.NotNull(receivedClient);
|
|
var wrappedClient = agent.GetService<TestChatClient>();
|
|
Assert.NotNull(wrappedClient);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that multiple clientFactory calls create independent wrapped clients.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_MultipleCallsWithClientFactory_CreatesIndependentClients()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
|
|
// Act
|
|
var agent1 = client.AsAIAgent(
|
|
agentRecord,
|
|
clientFactory: (innerClient) => new TestChatClient(innerClient));
|
|
|
|
var agent2 = client.AsAIAgent(
|
|
agentRecord,
|
|
clientFactory: (innerClient) => new TestChatClient(innerClient));
|
|
|
|
// Assert
|
|
Assert.NotNull(agent1);
|
|
Assert.NotNull(agent2);
|
|
var client1 = agent1.GetService<TestChatClient>();
|
|
var client2 = agent2.GetService<TestChatClient>();
|
|
Assert.NotNull(client1);
|
|
Assert.NotNull(client2);
|
|
Assert.NotSame(client1, client2);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that agent created with clientFactory maintains agent properties.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithClientFactory_PreservesAgentPropertiesAsync()
|
|
{
|
|
// Arrange
|
|
const string AgentName = "test-agent";
|
|
const string Model = "test-model";
|
|
const string Instructions = "Test instructions";
|
|
AIProjectClient client = this.CreateTestAgentClient(AgentName, Instructions);
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync(
|
|
AgentName,
|
|
Model,
|
|
Instructions,
|
|
clientFactory: (innerClient) => new TestChatClient(innerClient));
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal(AgentName, agent.Name);
|
|
Assert.Equal(Instructions, agent.Instructions);
|
|
var wrappedClient = agent.GetService<TestChatClient>();
|
|
Assert.NotNull(wrappedClient);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that agent created with clientFactory is created successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithClientFactory_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
|
|
var agentDefinitionResponse = GeneratePromptDefinitionResponse(definition, null);
|
|
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", agentDefinitionResponse: agentDefinitionResponse);
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync(
|
|
"test-agent",
|
|
options,
|
|
clientFactory: (innerClient) => new TestChatClient(innerClient));
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
var wrappedClient = agent.GetService<TestChatClient>();
|
|
Assert.NotNull(wrappedClient);
|
|
var agentVersion = agent.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region User-Agent Header Tests
|
|
|
|
/// <summary>
|
|
/// Verifies that the user-agent header is added to both synchronous and asynchronous requests made by agent creation methods.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_UserAgentHeaderAddedToRequestsAsync()
|
|
{
|
|
using var httpHandler = new HttpHandlerAssert(request =>
|
|
{
|
|
Assert.Equal("POST", request.Method.Method);
|
|
Assert.Contains("MEAI", request.Headers.UserAgent.ToString());
|
|
|
|
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(TestDataUtil.GetAgentResponseJson(), Encoding.UTF8, "application/json") };
|
|
});
|
|
|
|
#pragma warning disable CA5399
|
|
using var httpClient = new HttpClient(httpHandler);
|
|
#pragma warning restore CA5399
|
|
|
|
// Arrange
|
|
var aiProjectClient = new AIProjectClient(new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new() { Transport = new HttpClientPipelineTransport(httpClient) });
|
|
|
|
var agentOptions = new ChatClientAgentOptions { Name = "test-agent" };
|
|
|
|
// Act
|
|
var agent = await aiProjectClient.CreateAIAgentAsync("test", agentOptions);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that the user-agent header is added to asynchronous GetAIAgentAsync requests.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgent_UserAgentHeaderAddedToRequestsAsync()
|
|
{
|
|
using var httpHandler = new HttpHandlerAssert(request =>
|
|
{
|
|
Assert.Equal("GET", request.Method.Method);
|
|
Assert.Contains("MEAI", request.Headers.UserAgent.ToString());
|
|
|
|
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(TestDataUtil.GetAgentResponseJson(), Encoding.UTF8, "application/json") };
|
|
});
|
|
|
|
#pragma warning disable CA5399
|
|
using var httpClient = new HttpClient(httpHandler);
|
|
#pragma warning restore CA5399
|
|
|
|
// Arrange
|
|
var aiProjectClient = new AIProjectClient(new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new() { Transport = new HttpClientPipelineTransport(httpClient) });
|
|
|
|
// Act
|
|
var agent = await aiProjectClient.GetAIAgentAsync("test");
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetAIAgent(AIProjectClient, AgentReference) Tests
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentNullException when AIProjectClient is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentReference_WithNullClient_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
AIProjectClient? client = null;
|
|
var agentReference = new AgentReference("test-name", "1");
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentNullException>(() =>
|
|
client!.AsAIAgent(agentReference));
|
|
|
|
Assert.Equal("aiProjectClient", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentNullException when agentReference is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentReference_WithNullAgentReference_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentNullException>(() =>
|
|
mockClient.Object.AsAIAgent((AgentReference)null!));
|
|
|
|
Assert.Equal("agentReference", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentReference creates a valid agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentReference_CreatesValidAgent()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var agentReference = new AgentReference("test-name", "1");
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentReference);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("test-name", agent.Name);
|
|
Assert.Equal("test-name:1", agent.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentReference and clientFactory applies the factory.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentReference_WithClientFactory_AppliesFactoryCorrectly()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var agentReference = new AgentReference("test-name", "1");
|
|
TestChatClient? testChatClient = null;
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(
|
|
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 AsAIAgent with AgentReference sets the agent ID correctly.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentReference_SetsAgentIdCorrectly()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var agentReference = new AgentReference("test-name", "2");
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentReference);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("test-name:2", agent.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentReference and tools includes the tools in ChatOptions.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentReference_WithTools_IncludesToolsInChatOptions()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var agentReference = new AgentReference("test-name", "1");
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "test", "test_function", "A test function")
|
|
};
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(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
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(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
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var agentReference = new AgentReference("test-name", "1");
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(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
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(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
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var agentReference = new AgentReference("test-name", "1");
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(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
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(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
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var definition = new PromptAgentDefinition("gpt-4-turbo")
|
|
{
|
|
Instructions = "Test instructions"
|
|
};
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord(definition);
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(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
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(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
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var agentReference = new AgentReference("test-agent", "1.0");
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(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
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(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
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(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
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var agentReference = new AgentReference("versioned-agent", "3.5");
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentReference);
|
|
var retrievedReference = agent.GetService<AgentReference>();
|
|
|
|
// Assert
|
|
Assert.NotNull(retrievedReference);
|
|
Assert.Equal("versioned-agent", retrievedReference.Name);
|
|
Assert.Equal("3.5", retrievedReference.Version);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetAIAgentAsync - Empty Name Tests
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentException when name is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithOptions_WithNullName_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions { Name = null };
|
|
|
|
// Act & Assert
|
|
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
client.GetAIAgentAsync(options));
|
|
|
|
Assert.Equal("options", exception.ParamName);
|
|
Assert.Contains("Agent name must be provided", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentException when name is empty.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithOptions_WithEmptyName_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions { Name = string.Empty };
|
|
|
|
// Act & Assert
|
|
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
client.GetAIAgentAsync(options));
|
|
|
|
Assert.Equal("options", exception.ParamName);
|
|
Assert.Contains("Agent name must be provided", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentException when name is whitespace.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithOptions_WithWhitespaceName_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions { Name = " " };
|
|
|
|
// Act & Assert
|
|
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
client.GetAIAgentAsync(options));
|
|
|
|
Assert.Equal("options", exception.ParamName);
|
|
Assert.Contains("Agent name must be provided", exception.Message);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CreateAIAgentAsync - Empty Name Tests
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with model and options throws ArgumentException when name is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithModelAndOptions_WithNullName_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = null,
|
|
ChatOptions = new ChatOptions { Instructions = "Test" }
|
|
};
|
|
|
|
// Act & Assert
|
|
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
client.CreateAIAgentAsync("test-model", options));
|
|
|
|
Assert.Equal("options", exception.ParamName);
|
|
Assert.Contains("Agent name must be provided", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with model and options throws ArgumentException when name is empty.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithModelAndOptions_WithEmptyName_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = string.Empty,
|
|
ChatOptions = new ChatOptions { Instructions = "Test" }
|
|
};
|
|
|
|
// Act & Assert
|
|
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
client.CreateAIAgentAsync("test-model", options));
|
|
|
|
Assert.Equal("options", exception.ParamName);
|
|
Assert.Contains("Agent name must be provided", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with model and options throws ArgumentException when name is whitespace.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithModelAndOptions_WithWhitespaceName_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = " ",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" }
|
|
};
|
|
|
|
// Act & Assert
|
|
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
client.CreateAIAgentAsync("test-model", options));
|
|
|
|
Assert.Equal("options", exception.ParamName);
|
|
Assert.Contains("Agent name must be provided", exception.Message);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CreateAIAgentAsync - Response Format Tests
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with ChatResponseFormatText response format creates agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithTextResponseFormat_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
ResponseFormat = ChatResponseFormat.Text
|
|
}
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with ChatResponseFormatJson response format without schema creates agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithJsonResponseFormatWithoutSchema_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
ResponseFormat = ChatResponseFormat.Json
|
|
}
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with ChatResponseFormatJson with schema creates agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithJsonResponseFormatWithSchema_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
JsonElement schemaElement = AIJsonUtilities.CreateJsonSchema(typeof(TestSchema));
|
|
var jsonFormat = ChatResponseFormat.ForJsonSchema(schemaElement, "test_schema", "A test schema");
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
ResponseFormat = jsonFormat
|
|
}
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with ChatResponseFormatJson with schema and strict mode creates agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithJsonResponseFormatWithSchemaAndStrictMode_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
JsonElement schemaElement = AIJsonUtilities.CreateJsonSchema(typeof(TestSchema));
|
|
var jsonFormat = ChatResponseFormat.ForJsonSchema(schemaElement, "test_schema", "A test schema");
|
|
var additionalProps = new AdditionalPropertiesDictionary
|
|
{
|
|
["strictJsonSchema"] = true
|
|
};
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
ResponseFormat = jsonFormat,
|
|
AdditionalProperties = additionalProps
|
|
}
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with ChatResponseFormatJson with schema and strict mode false creates agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithJsonResponseFormatWithSchemaAndStrictModeFalse_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
JsonElement schemaElement = AIJsonUtilities.CreateJsonSchema(typeof(TestSchema));
|
|
var jsonFormat = ChatResponseFormat.ForJsonSchema(schemaElement, "test_schema", "A test schema");
|
|
var additionalProps = new AdditionalPropertiesDictionary
|
|
{
|
|
["strictJsonSchema"] = false
|
|
};
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
ResponseFormat = jsonFormat,
|
|
AdditionalProperties = additionalProps
|
|
}
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CreateAIAgentAsync - RawRepresentationFactory Tests
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with RawRepresentationFactory that returns CreateResponseOptions creates agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithRawRepresentationFactory_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
RawRepresentationFactory = _ => new CreateResponseOptions()
|
|
}
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with RawRepresentationFactory that returns null does not fail.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithRawRepresentationFactoryReturningNull_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
RawRepresentationFactory = _ => null
|
|
}
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with RawRepresentationFactory that returns non-CreateResponseOptions does not fail.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithRawRepresentationFactoryReturningNonCreateResponseOptions_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
RawRepresentationFactory = _ => new object()
|
|
}
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CreateAIAgentAsync - Description Tests
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with description sets description on the agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithDescription_SetsDescriptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient(description: "Test description");
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
Description = "Test description",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" }
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("Test description", agent.Description);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync without description still creates agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithoutDescription_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" }
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CreateChatClientAgentOptions - Missing Tools Tests
|
|
|
|
/// <summary>
|
|
/// Verify that when invocable tools are required but not provided, an exception is thrown.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithToolsRequiredButNotProvided_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("required_function", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" }
|
|
};
|
|
|
|
// Act & Assert
|
|
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
client.GetAIAgentAsync(options));
|
|
|
|
Assert.Contains("in-process tools must be provided", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when specific invocable tools are required but wrong ones are provided, InvalidOperationException is thrown.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithWrongToolsProvided_ThrowsInvalidOperationExceptionAsync()
|
|
{
|
|
// Arrange
|
|
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("required_function", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "test", "wrong_function", "Wrong function")
|
|
};
|
|
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
Tools = tools
|
|
}
|
|
};
|
|
|
|
// Act & Assert
|
|
InvalidOperationException exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
|
client.GetAIAgentAsync(options));
|
|
|
|
Assert.Contains("required_function", exception.Message);
|
|
Assert.Contains("were not provided", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when tools are provided that match the definition, agent is created successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithMatchingToolsProvided_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("required_function", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "test", "required_function", "Required function")
|
|
};
|
|
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
Tools = tools
|
|
}
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CreateChatClientAgentOptions - Options Preservation Tests
|
|
|
|
/// <summary>
|
|
/// Verify that CreateChatClientAgentOptions preserves AIContextProviderFactory.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithAIContextProviderFactory_PreservesFactoryAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
bool factoryInvoked = false;
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" },
|
|
AIContextProviderFactory = (_, _) =>
|
|
{
|
|
factoryInvoked = true;
|
|
return new ValueTask<AIContextProvider>(new TestAIContextProvider());
|
|
}
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
// Verify the factory was captured (though not necessarily invoked yet)
|
|
Assert.False(factoryInvoked); // Factory is not invoked during creation
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateChatClientAgentOptions preserves ChatHistoryProviderFactory.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithChatHistoryProviderFactory_PreservesFactoryAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" },
|
|
ChatHistoryProviderFactory = (_, _) => new ValueTask<ChatHistoryProvider>(new TestChatHistoryProvider())
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateChatClientAgentOptions preserves UseProvidedChatClientAsIs.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithUseProvidedChatClientAsIs_PreservesSettingAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" },
|
|
UseProvidedChatClientAsIs = true
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Empty Version and ID Handling Tests
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync handles an agent with empty version by using "latest" as fallback.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithEmptyVersion_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClientWithEmptyVersion();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" }
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
// Verify the agent ID is generated from server-returned name ("agent_abc123") and "latest"
|
|
Assert.Equal("agent_abc123:latest", agent.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentRecord handles empty version by using "latest" as fallback.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentRecordEmptyVersion_CreatesAgentWithGeneratedId()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClientWithEmptyVersion();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecordWithEmptyVersion();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentRecord);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
// Verify the agent ID is generated from agent record name ("agent_abc123") and "latest"
|
|
Assert.Equal("agent_abc123:latest", agent.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentVersion handles empty version by using "latest" as fallback.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentVersionEmptyVersion_CreatesAgentWithGeneratedId()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClientWithEmptyVersion();
|
|
AgentVersion agentVersion = this.CreateTestAgentVersionWithEmptyVersion();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentVersion);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
// Verify the agent ID is generated from agent version name ("agent_abc123") and "latest"
|
|
Assert.Equal("agent_abc123:latest", agent.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync handles an agent with whitespace-only version by using "latest" as fallback.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithWhitespaceVersion_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClientWithWhitespaceVersion();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" }
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
// Verify the agent ID is generated from server-returned name ("agent_abc123") and "latest"
|
|
Assert.Equal("agent_abc123:latest", agent.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentRecord handles whitespace-only version by using "latest" as fallback.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentRecordWhitespaceVersion_CreatesAgentWithGeneratedId()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClientWithWhitespaceVersion();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecordWithWhitespaceVersion();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentRecord);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
// Verify the agent ID is generated from agent record name ("agent_abc123") and "latest"
|
|
Assert.Equal("agent_abc123:latest", agent.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentVersion handles whitespace-only version by using "latest" as fallback.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentVersionWhitespaceVersion_CreatesAgentWithGeneratedId()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClientWithWhitespaceVersion();
|
|
AgentVersion agentVersion = this.CreateTestAgentVersionWithWhitespaceVersion();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentVersion);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
// Verify the agent ID is generated from agent version name ("agent_abc123") and "latest"
|
|
Assert.Equal("agent_abc123:latest", agent.Id);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ApplyToolsToAgentDefinition Tests
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with non-PromptAgentDefinition and tools throws ArgumentException.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithNonPromptAgentDefinitionAndTools_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "test", "test_function", "A test function")
|
|
};
|
|
|
|
using HttpHandlerAssert httpHandler = new(_ => new HttpResponseMessage(HttpStatusCode.OK)
|
|
{
|
|
Content = new StringContent(TestDataUtil.GetAgentVersionResponseJson(), Encoding.UTF8, "application/json")
|
|
});
|
|
|
|
#pragma warning disable CA5399
|
|
using HttpClient httpClient = new(httpHandler);
|
|
#pragma warning restore CA5399
|
|
|
|
AIProjectClient client = new(new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new() { Transport = new HttpClientPipelineTransport(httpClient) });
|
|
|
|
// Create a mock AgentDefinition that is not PromptAgentDefinition
|
|
// Since we can't easily create a non-PromptAgentDefinition in the public API, we test this path via the CreateAIAgentAsync that builds a PromptAgentDefinition
|
|
// The ApplyToolsToAgentDefinition is only called when tools.Count > 0, and we provide tools
|
|
// But PromptAgentDefinition is always created by CreateAIAgentAsync(name, model, instructions, tools)
|
|
// So this path is hard to hit without mocking. Let's test the declarative function rejection instead.
|
|
var declarativeFunction = AIFunctionFactory.CreateDeclaration("test_function", "A test function", JsonDocument.Parse("{}").RootElement);
|
|
|
|
// Act & Assert
|
|
InvalidOperationException exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
|
client.CreateAIAgentAsync(
|
|
name: "test-agent",
|
|
model: "test-model",
|
|
instructions: "Test",
|
|
tools: [declarativeFunction]));
|
|
|
|
Assert.Contains("invokable AIFunctions", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with AIFunctionDeclaration tools throws InvalidOperationException.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithAIFunctionDeclarationTool_ThrowsInvalidOperationExceptionAsync()
|
|
{
|
|
// Arrange
|
|
using var doc = JsonDocument.Parse("{}");
|
|
var declarativeFunction = AIFunctionFactory.CreateDeclaration("test_function", "A test function", doc.RootElement);
|
|
|
|
using HttpHandlerAssert httpHandler = new(_ => new HttpResponseMessage(HttpStatusCode.OK)
|
|
{
|
|
Content = new StringContent(TestDataUtil.GetAgentVersionResponseJson(), Encoding.UTF8, "application/json")
|
|
});
|
|
|
|
#pragma warning disable CA5399
|
|
using HttpClient httpClient = new(httpHandler);
|
|
#pragma warning restore CA5399
|
|
|
|
AIProjectClient client = new(new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new() { Transport = new HttpClientPipelineTransport(httpClient) });
|
|
|
|
// Act & Assert
|
|
InvalidOperationException exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
|
client.CreateAIAgentAsync(
|
|
name: "test-agent",
|
|
model: "test-model",
|
|
instructions: "Test",
|
|
tools: [declarativeFunction]));
|
|
|
|
Assert.Contains("invokable AIFunctions", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with ResponseTool converted via AsAITool works.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithResponseToolAsAITool_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
ResponseTool responseTool = ResponseTool.CreateFunctionTool("response_tool", BinaryData.FromString("{}"), strictModeEnabled: false);
|
|
AITool convertedTool = responseTool.AsAITool();
|
|
|
|
// Create a definition with the function tool already in it
|
|
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
|
definition.Tools.Add(responseTool);
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
|
|
|
|
// Matching invokable tool must be provided
|
|
var invokableTool = AIFunctionFactory.Create(() => "test", "response_tool", "Invokable version of the tool");
|
|
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
Tools = [invokableTool]
|
|
}
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with hosted tool types works correctly.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithHostedToolTypes_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var webSearchTool = new HostedWebSearchTool();
|
|
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
Tools = [webSearchTool]
|
|
}
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when the server returns tools but matching tools are provided, the agent is created.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithServerDefinedToolsAndMatchingProvidedTools_CreatesAgentAsync()
|
|
{
|
|
// Arrange
|
|
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
|
// Add multiple function tools
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("tool_one", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("tool_two", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
|
|
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "one", "tool_one", "Tool one"),
|
|
AIFunctionFactory.Create(() => "two", "tool_two", "Tool two")
|
|
};
|
|
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
Tools = tools
|
|
}
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when the server returns mixed tools (function and hosted), the agent handles them correctly.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithMixedServerTools_MatchesFunctionToolsOnlyAsync()
|
|
{
|
|
// Arrange
|
|
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
|
// Add a function tool
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("function_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
// Add a hosted tool
|
|
definition.Tools.Add(new HostedWebSearchTool().GetService<ResponseTool>() ?? new HostedWebSearchTool().AsOpenAIResponseTool());
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
|
|
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "result", "function_tool", "The function tool")
|
|
};
|
|
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
Tools = tools
|
|
}
|
|
};
|
|
|
|
// Act
|
|
ChatClientAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when partial tools are provided (some missing), InvalidOperationException is thrown listing missing tools.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithPartialToolsProvided_ThrowsInvalidOperationWithMissingToolNamesAsync()
|
|
{
|
|
// Arrange
|
|
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("provided_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("missing_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
|
|
|
|
var tools = new List<AITool>
|
|
{
|
|
// Only providing one of two required tools
|
|
AIFunctionFactory.Create(() => "result", "provided_tool", "The provided tool")
|
|
};
|
|
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
Tools = tools
|
|
}
|
|
};
|
|
|
|
// Act & Assert
|
|
InvalidOperationException exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
|
client.GetAIAgentAsync(options));
|
|
|
|
Assert.Contains("missing_tool", exception.Message);
|
|
Assert.DoesNotContain("provided_tool", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when AsAIAgent is called without requireInvocableTools, hosted tools are correctly added.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithServerHostedTools_AddsToolsToAgentOptions()
|
|
{
|
|
// Arrange
|
|
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
|
definition.Tools.Add(new HostedWebSearchTool().GetService<ResponseTool>() ?? new HostedWebSearchTool().AsOpenAIResponseTool());
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentVersion agentVersion = ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(TestDataUtil.GetAgentVersionResponseJson(agentDefinition: definition)))!;
|
|
|
|
// Act - no tools provided, but requireInvocableTools is false when no tools param is passed
|
|
ChatClientAgent agent = client.AsAIAgent(agentVersion);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<ChatClientAgent>(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
|
|
/// <summary>
|
|
/// Creates a test AIProjectClient with fake behavior.
|
|
/// </summary>
|
|
private FakeAgentClient CreateTestAgentClient(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null)
|
|
{
|
|
return new FakeAgentClient(agentName, instructions, description, agentDefinitionResponse);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a test AgentRecord for testing.
|
|
/// </summary>
|
|
private AgentRecord CreateTestAgentRecord(AgentDefinition? agentDefinition = null)
|
|
{
|
|
return ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(TestDataUtil.GetAgentResponseJson(agentDefinition: agentDefinition)))!;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a test AIProjectClient with empty version fields for testing hosted MCP agents.
|
|
/// </summary>
|
|
private FakeAgentClient CreateTestAgentClientWithEmptyVersion(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null)
|
|
{
|
|
return new FakeAgentClient(agentName, instructions, description, agentDefinitionResponse, useEmptyVersion: true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a test AgentRecord with empty version for testing hosted MCP agents.
|
|
/// </summary>
|
|
private AgentRecord CreateTestAgentRecordWithEmptyVersion(AgentDefinition? agentDefinition = null)
|
|
{
|
|
return ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(TestDataUtil.GetAgentResponseJsonWithEmptyVersion(agentDefinition: agentDefinition)))!;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a test AgentVersion with empty version for testing hosted MCP agents.
|
|
/// </summary>
|
|
private AgentVersion CreateTestAgentVersionWithEmptyVersion()
|
|
{
|
|
return ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(TestDataUtil.GetAgentVersionResponseJsonWithEmptyVersion()))!;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a test AIProjectClient with whitespace-only version fields for testing hosted MCP agents.
|
|
/// </summary>
|
|
private FakeAgentClient CreateTestAgentClientWithWhitespaceVersion(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null)
|
|
{
|
|
return new FakeAgentClient(agentName, instructions, description, agentDefinitionResponse, versionMode: VersionMode.Whitespace);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a test AgentRecord with whitespace-only version for testing hosted MCP agents.
|
|
/// </summary>
|
|
private AgentRecord CreateTestAgentRecordWithWhitespaceVersion(AgentDefinition? agentDefinition = null)
|
|
{
|
|
return ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(TestDataUtil.GetAgentResponseJsonWithWhitespaceVersion(agentDefinition: agentDefinition)))!;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a test AgentVersion with whitespace-only version for testing hosted MCP agents.
|
|
/// </summary>
|
|
private AgentVersion CreateTestAgentVersionWithWhitespaceVersion()
|
|
{
|
|
return ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(TestDataUtil.GetAgentVersionResponseJsonWithWhitespaceVersion()))!;
|
|
}
|
|
|
|
private const string OpenAPISpec = """
|
|
{
|
|
"openapi": "3.0.3",
|
|
"info": { "title": "Tiny Test API", "version": "1.0.0" },
|
|
"paths": {
|
|
"/ping": {
|
|
"get": {
|
|
"summary": "Health check",
|
|
"operationId": "getPing",
|
|
"responses": {
|
|
"200": {
|
|
"description": "OK",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": { "message": { "type": "string" } },
|
|
"required": ["message"]
|
|
},
|
|
"example": { "message": "pong" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
/// <summary>
|
|
/// Creates a test AgentVersion for testing.
|
|
/// </summary>
|
|
private AgentVersion CreateTestAgentVersion()
|
|
{
|
|
return ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(TestDataUtil.GetAgentVersionResponseJson()))!;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies the version mode for test data generation.
|
|
/// </summary>
|
|
private enum VersionMode
|
|
{
|
|
Normal,
|
|
Empty,
|
|
Whitespace
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fake AIProjectClient for testing.
|
|
/// </summary>
|
|
private sealed class FakeAgentClient : AIProjectClient
|
|
{
|
|
public FakeAgentClient(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null, bool useEmptyVersion = false, VersionMode versionMode = VersionMode.Normal)
|
|
{
|
|
// Handle backward compatibility with bool parameter
|
|
var effectiveVersionMode = useEmptyVersion ? VersionMode.Empty : versionMode;
|
|
this.Agents = new FakeAIProjectAgentsOperations(agentName, instructions, description, agentDefinitionResponse, effectiveVersionMode);
|
|
}
|
|
|
|
public override ClientConnection GetConnection(string connectionId)
|
|
{
|
|
return new ClientConnection("fake-connection-id", "http://localhost", ClientPipeline.Create(), CredentialKind.None);
|
|
}
|
|
|
|
public override AIProjectAgentsOperations Agents { get; }
|
|
|
|
private sealed class FakeAIProjectAgentsOperations : AIProjectAgentsOperations
|
|
{
|
|
private readonly string? _agentName;
|
|
private readonly string? _instructions;
|
|
private readonly string? _description;
|
|
private readonly AgentDefinition? _agentDefinition;
|
|
private readonly VersionMode _versionMode;
|
|
|
|
public FakeAIProjectAgentsOperations(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null, VersionMode versionMode = VersionMode.Normal)
|
|
{
|
|
this._agentName = agentName;
|
|
this._instructions = instructions;
|
|
this._description = description;
|
|
this._agentDefinition = agentDefinitionResponse;
|
|
this._versionMode = versionMode;
|
|
}
|
|
|
|
private string GetAgentResponseJson()
|
|
{
|
|
return this._versionMode switch
|
|
{
|
|
VersionMode.Empty => TestDataUtil.GetAgentResponseJsonWithEmptyVersion(this._agentName, this._agentDefinition, this._instructions, this._description),
|
|
VersionMode.Whitespace => TestDataUtil.GetAgentResponseJsonWithWhitespaceVersion(this._agentName, this._agentDefinition, this._instructions, this._description),
|
|
_ => TestDataUtil.GetAgentResponseJson(this._agentName, this._agentDefinition, this._instructions, this._description)
|
|
};
|
|
}
|
|
|
|
private string GetAgentVersionResponseJson()
|
|
{
|
|
return this._versionMode switch
|
|
{
|
|
VersionMode.Empty => TestDataUtil.GetAgentVersionResponseJsonWithEmptyVersion(this._agentName, this._agentDefinition, this._instructions, this._description),
|
|
VersionMode.Whitespace => TestDataUtil.GetAgentVersionResponseJsonWithWhitespaceVersion(this._agentName, this._agentDefinition, this._instructions, this._description),
|
|
_ => TestDataUtil.GetAgentVersionResponseJson(this._agentName, this._agentDefinition, this._instructions, this._description)
|
|
};
|
|
}
|
|
|
|
public override ClientResult GetAgent(string agentName, RequestOptions options)
|
|
{
|
|
var responseJson = this.GetAgentResponseJson();
|
|
return ClientResult.FromValue(ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200, BinaryData.FromString(responseJson)));
|
|
}
|
|
|
|
public override ClientResult<AgentRecord> GetAgent(string agentName, CancellationToken cancellationToken = default)
|
|
{
|
|
var responseJson = this.GetAgentResponseJson();
|
|
return ClientResult.FromValue(ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200));
|
|
}
|
|
|
|
public override Task<ClientResult> GetAgentAsync(string agentName, RequestOptions options)
|
|
{
|
|
var responseJson = this.GetAgentResponseJson();
|
|
return Task.FromResult<ClientResult>(ClientResult.FromValue(ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200, BinaryData.FromString(responseJson))));
|
|
}
|
|
|
|
public override Task<ClientResult<AgentRecord>> GetAgentAsync(string agentName, CancellationToken cancellationToken = default)
|
|
{
|
|
var responseJson = this.GetAgentResponseJson();
|
|
return Task.FromResult(ClientResult.FromValue(ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200)));
|
|
}
|
|
|
|
public override ClientResult CreateAgentVersion(string agentName, BinaryContent content, RequestOptions? options = null)
|
|
{
|
|
var responseJson = this.GetAgentVersionResponseJson();
|
|
return ClientResult.FromValue(ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200, BinaryData.FromString(responseJson)));
|
|
}
|
|
|
|
public override ClientResult<AgentVersion> CreateAgentVersion(string agentName, AgentVersionCreationOptions? options = null, CancellationToken cancellationToken = default)
|
|
{
|
|
var responseJson = this.GetAgentVersionResponseJson();
|
|
return ClientResult.FromValue(ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200));
|
|
}
|
|
|
|
public override Task<ClientResult> CreateAgentVersionAsync(string agentName, BinaryContent content, RequestOptions? options = null)
|
|
{
|
|
var responseJson = this.GetAgentVersionResponseJson();
|
|
return Task.FromResult<ClientResult>(ClientResult.FromValue(ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200, BinaryData.FromString(responseJson))));
|
|
}
|
|
|
|
public override Task<ClientResult<AgentVersion>> CreateAgentVersionAsync(string agentName, AgentVersionCreationOptions? options = null, CancellationToken cancellationToken = default)
|
|
{
|
|
var responseJson = this.GetAgentVersionResponseJson();
|
|
return Task.FromResult(ClientResult.FromValue(ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200)));
|
|
}
|
|
}
|
|
}
|
|
|
|
private static PromptAgentDefinition GeneratePromptDefinitionResponse(PromptAgentDefinition inputDefinition, List<AITool>? tools)
|
|
{
|
|
var definitionResponse = new PromptAgentDefinition(inputDefinition.Model) { Instructions = inputDefinition.Instructions };
|
|
if (tools is not null)
|
|
{
|
|
foreach (var tool in tools)
|
|
{
|
|
definitionResponse.Tools.Add(tool.GetService<ResponseTool>() ?? tool.AsOpenAIResponseTool());
|
|
}
|
|
}
|
|
|
|
return definitionResponse;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test custom chat client that can be used to verify clientFactory functionality.
|
|
/// </summary>
|
|
private sealed class TestChatClient : DelegatingChatClient
|
|
{
|
|
public TestChatClient(IChatClient innerClient) : base(innerClient)
|
|
{
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mock pipeline response for testing ClientResult wrapping.
|
|
/// </summary>
|
|
private sealed class MockPipelineResponse : PipelineResponse
|
|
{
|
|
private readonly int _status;
|
|
private readonly MockPipelineResponseHeaders _headers;
|
|
|
|
public MockPipelineResponse(int status, BinaryData? content = null)
|
|
{
|
|
this._status = status;
|
|
this.Content = content ?? BinaryData.Empty;
|
|
this._headers = new MockPipelineResponseHeaders();
|
|
}
|
|
|
|
public override int Status => this._status;
|
|
|
|
public override string ReasonPhrase => "OK";
|
|
|
|
public override Stream? ContentStream
|
|
{
|
|
get => null;
|
|
set { }
|
|
}
|
|
|
|
public override BinaryData Content { get; }
|
|
|
|
protected override PipelineResponseHeaders HeadersCore => this._headers;
|
|
|
|
public override BinaryData BufferContent(CancellationToken cancellationToken = default) =>
|
|
throw new NotSupportedException("Buffering content is not supported for mock responses.");
|
|
|
|
public override ValueTask<BinaryData> BufferContentAsync(CancellationToken cancellationToken = default) =>
|
|
throw new NotSupportedException("Buffering content asynchronously is not supported for mock responses.");
|
|
|
|
public override void Dispose()
|
|
{
|
|
}
|
|
|
|
private sealed class MockPipelineResponseHeaders : PipelineResponseHeaders
|
|
{
|
|
private readonly Dictionary<string, string> _headers = new(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
{ "Content-Type", "application/json" },
|
|
{ "x-ms-request-id", "test-request-id" }
|
|
};
|
|
|
|
public override bool TryGetValue(string name, out string? value)
|
|
{
|
|
return this._headers.TryGetValue(name, out value);
|
|
}
|
|
|
|
public override bool TryGetValues(string name, out IEnumerable<string>? values)
|
|
{
|
|
if (this._headers.TryGetValue(name, out var value))
|
|
{
|
|
values = [value];
|
|
return true;
|
|
}
|
|
|
|
values = null;
|
|
return false;
|
|
}
|
|
|
|
public override IEnumerator<KeyValuePair<string, string>> GetEnumerator()
|
|
{
|
|
return this._headers.GetEnumerator();
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test schema for JSON response format tests.
|
|
/// </summary>
|
|
#pragma warning disable CA1812 // Avoid uninstantiated internal classes - used via reflection by AIJsonUtilities
|
|
private sealed class TestSchema
|
|
{
|
|
public string? Name { get; set; }
|
|
public int Value { get; set; }
|
|
}
|
|
#pragma warning restore CA1812
|
|
|
|
/// <summary>
|
|
/// Test AIContextProvider for options preservation tests.
|
|
/// </summary>
|
|
private sealed class TestAIContextProvider : AIContextProvider
|
|
{
|
|
public override ValueTask<AIContext> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
|
{
|
|
return new ValueTask<AIContext>(new AIContext());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test ChatHistoryProvider for options preservation tests.
|
|
/// </summary>
|
|
private sealed class TestChatHistoryProvider : ChatHistoryProvider
|
|
{
|
|
public override ValueTask<IEnumerable<ChatMessage>> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
|
{
|
|
return new ValueTask<IEnumerable<ChatMessage>>(Array.Empty<ChatMessage>());
|
|
}
|
|
|
|
public override ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
|
{
|
|
return default;
|
|
}
|
|
|
|
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
|
|
{
|
|
return default;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides test data for invalid agent name validation tests.
|
|
/// </summary>
|
|
internal static class InvalidAgentNameTestData
|
|
{
|
|
/// <summary>
|
|
/// Gets a collection of invalid agent names for theory-based testing.
|
|
/// </summary>
|
|
/// <returns>Collection of invalid agent name test cases.</returns>
|
|
public static IEnumerable<object[]> GetInvalidAgentNames()
|
|
{
|
|
yield return new object[] { "-agent" };
|
|
yield return new object[] { "agent-" };
|
|
yield return new object[] { "agent_name" };
|
|
yield return new object[] { "agent name" };
|
|
yield return new object[] { "agent@name" };
|
|
yield return new object[] { "agent#name" };
|
|
yield return new object[] { "agent$name" };
|
|
yield return new object[] { "agent%name" };
|
|
yield return new object[] { "agent&name" };
|
|
yield return new object[] { "agent*name" };
|
|
yield return new object[] { "agent.name" };
|
|
yield return new object[] { "agent/name" };
|
|
yield return new object[] { "agent\\name" };
|
|
yield return new object[] { "agent:name" };
|
|
yield return new object[] { "agent;name" };
|
|
yield return new object[] { "agent,name" };
|
|
yield return new object[] { "agent<name" };
|
|
yield return new object[] { "agent>name" };
|
|
yield return new object[] { "agent?name" };
|
|
yield return new object[] { "agent!name" };
|
|
yield return new object[] { "agent~name" };
|
|
yield return new object[] { "agent`name" };
|
|
yield return new object[] { "agent^name" };
|
|
yield return new object[] { "agent|name" };
|
|
yield return new object[] { "agent[name" };
|
|
yield return new object[] { "agent]name" };
|
|
yield return new object[] { "agent{name" };
|
|
yield return new object[] { "agent}name" };
|
|
yield return new object[] { "agent(name" };
|
|
yield return new object[] { "agent)name" };
|
|
yield return new object[] { "agent+name" };
|
|
yield return new object[] { "agent=name" };
|
|
yield return new object[] { "a" + new string('b', 63) };
|
|
}
|
|
}
|