.NET: Add Anthropic Agent Package (#2359)

* WIP

* WIP

* Simple call working

* Update Thinking sample

* Non-Streaming Function calling working

* Update Anthropic Impl

* Public Preps

* UT + IT working

* Update documentation + samples

* Update variable

* Revert nuget.config

* Add IT for BetaService implementation

* Remove polyfill + enable IT to run for netstandard 2.0

* Skipping Anthropic IT's for manual execution and avoid pipeline execution

* Fix compilation error

* Address error in UT

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix warning

* Net 10 update

* Update for NET 10, remove Anthropic.Foundry due to vulnerability

* Final missing adjustments for NET 10

* Address PR comments

* Remove unused code

* Address feedback

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Roger Barreto
2025-11-25 11:53:18 +00:00
committed by GitHub
co-authored by Copilot
parent 112410ecd6
commit 1dde57981e
33 changed files with 1628 additions and 2 deletions
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<InjectSharedIntegrationTestCode>True</InjectSharedIntegrationTestCode>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.Anthropic\Microsoft.Agents.AI.Anthropic.csproj" />
<ProjectReference Include="..\AgentConformance.IntegrationTests\AgentConformance.IntegrationTests.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using AgentConformance.IntegrationTests;
namespace AnthropicChatCompletion.IntegrationTests;
public abstract class SkipAllChatClientRunStreaming(Func<AnthropicChatCompletionFixture> func) : ChatClientAgentRunStreamingTests<AnthropicChatCompletionFixture>(func)
{
[Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
public override Task RunWithFunctionsInvokesFunctionsAndReturnsExpectedResultsAsync()
=> base.RunWithFunctionsInvokesFunctionsAndReturnsExpectedResultsAsync();
[Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync()
=> base.RunWithInstructionsAndNoMessageReturnsExpectedResultAsync();
}
public class AnthropicBetaChatCompletionChatClientAgentReasoningRunStreamingTests() : SkipAllChatClientRunStreaming(() => new(useReasoningChatModel: true, useBeta: true));
public class AnthropicBetaChatCompletionChatClientAgentRunStreamingTests() : SkipAllChatClientRunStreaming(() => new(useReasoningChatModel: false, useBeta: true));
public class AnthropicChatCompletionChatClientAgentRunStreamingTests() : SkipAllChatClientRunStreaming(() => new(useReasoningChatModel: false, useBeta: false));
public class AnthropicChatCompletionChatClientAgentReasoningRunStreamingTests() : SkipAllChatClientRunStreaming(() => new(useReasoningChatModel: true, useBeta: false));
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using AgentConformance.IntegrationTests;
namespace AnthropicChatCompletion.IntegrationTests;
public abstract class SkipAllChatClientAgentRun(Func<AnthropicChatCompletionFixture> func) : ChatClientAgentRunTests<AnthropicChatCompletionFixture>(func)
{
[Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
public override Task RunWithFunctionsInvokesFunctionsAndReturnsExpectedResultsAsync()
=> base.RunWithFunctionsInvokesFunctionsAndReturnsExpectedResultsAsync();
[Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync()
=> base.RunWithInstructionsAndNoMessageReturnsExpectedResultAsync();
}
public class AnthropicBetaChatCompletionChatClientAgentRunTests()
: SkipAllChatClientAgentRun(() => new(useReasoningChatModel: false, useBeta: true));
public class AnthropicBetaChatCompletionChatClientAgentReasoningRunTests()
: SkipAllChatClientAgentRun(() => new(useReasoningChatModel: true, useBeta: true));
public class AnthropicChatCompletionChatClientAgentRunTests()
: SkipAllChatClientAgentRun(() => new(useReasoningChatModel: false, useBeta: false));
public class AnthropicChatCompletionChatClientAgentReasoningRunTests()
: SkipAllChatClientAgentRun(() => new(useReasoningChatModel: true, useBeta: false));
@@ -0,0 +1,105 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AgentConformance.IntegrationTests;
using AgentConformance.IntegrationTests.Support;
using Anthropic;
using Anthropic.Models.Beta.Messages;
using Anthropic.Models.Messages;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Shared.IntegrationTests;
namespace AnthropicChatCompletion.IntegrationTests;
public class AnthropicChatCompletionFixture : IChatClientAgentFixture
{
// All tests for Anthropic are intended to be ran locally as the CI pipeline for Anthropic is not setup.
internal const string SkipReason = "Integrations tests for local execution only";
private static readonly AnthropicConfiguration s_config = TestConfiguration.LoadSection<AnthropicConfiguration>();
private readonly bool _useReasoningModel;
private readonly bool _useBeta;
private ChatClientAgent _agent = null!;
public AnthropicChatCompletionFixture(bool useReasoningChatModel, bool useBeta)
{
this._useReasoningModel = useReasoningChatModel;
this._useBeta = useBeta;
}
public AIAgent Agent => this._agent;
public IChatClient ChatClient => this._agent.ChatClient;
public async Task<List<ChatMessage>> GetChatHistoryAsync(AgentThread thread)
{
var typedThread = (ChatClientAgentThread)thread;
return typedThread.MessageStore is null ? [] : (await typedThread.MessageStore.GetMessagesAsync()).ToList();
}
public Task<ChatClientAgent> CreateChatClientAgentAsync(
string name = "HelpfulAssistant",
string instructions = "You are a helpful assistant.",
IList<AITool>? aiTools = null)
{
var anthropicClient = new AnthropicClient() { APIKey = s_config.ApiKey };
IChatClient? chatClient = this._useBeta
? anthropicClient
.Beta
.AsIChatClient()
.AsBuilder()
.ConfigureOptions(options
=> options.RawRepresentationFactory = _
=> new Anthropic.Models.Beta.Messages.MessageCreateParams()
{
Model = options.ModelId ?? (this._useReasoningModel ? s_config.ChatReasoningModelId : s_config.ChatModelId),
MaxTokens = options.MaxOutputTokens ?? 4096,
Messages = [],
Thinking = this._useReasoningModel
? new BetaThinkingConfigParam(new BetaThinkingConfigEnabled(2048))
: new BetaThinkingConfigParam(new BetaThinkingConfigDisabled())
}).Build()
: anthropicClient
.AsIChatClient()
.AsBuilder()
.ConfigureOptions(options
=> options.RawRepresentationFactory = _
=> new Anthropic.Models.Messages.MessageCreateParams()
{
Model = options.ModelId ?? (this._useReasoningModel ? s_config.ChatReasoningModelId : s_config.ChatModelId),
MaxTokens = options.MaxOutputTokens ?? 4096,
Messages = [],
Thinking = this._useReasoningModel
? new ThinkingConfigParam(new ThinkingConfigEnabled(2048))
: new ThinkingConfigParam(new ThinkingConfigDisabled())
}).Build();
return Task.FromResult(new ChatClientAgent(chatClient, options: new()
{
Name = name,
Instructions = instructions,
ChatOptions = new() { Tools = aiTools }
}));
}
public Task DeleteAgentAsync(ChatClientAgent agent) =>
// Chat Completion does not require/support deleting agents, so this is a no-op.
Task.CompletedTask;
public Task DeleteThreadAsync(AgentThread thread) =>
// Chat Completion does not require/support deleting threads, so this is a no-op.
Task.CompletedTask;
public async Task InitializeAsync() =>
this._agent = await this.CreateChatClientAgentAsync();
public Task DisposeAsync() =>
Task.CompletedTask;
}
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using AgentConformance.IntegrationTests;
namespace AnthropicChatCompletion.IntegrationTests;
public abstract class SkipAllRunStreaming(Func<AnthropicChatCompletionFixture> func) : RunStreamingTests<AnthropicChatCompletionFixture>(func)
{
[Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
public override Task RunWithChatMessageReturnsExpectedResultAsync() => base.RunWithChatMessageReturnsExpectedResultAsync();
[Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
public override Task RunWithNoMessageDoesNotFailAsync() => base.RunWithNoMessageDoesNotFailAsync();
[Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
public override Task RunWithChatMessagesReturnsExpectedResultAsync() => base.RunWithChatMessagesReturnsExpectedResultAsync();
[Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
public override Task RunWithStringReturnsExpectedResultAsync() => base.RunWithStringReturnsExpectedResultAsync();
[Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
public override Task ThreadMaintainsHistoryAsync() => base.ThreadMaintainsHistoryAsync();
}
public class AnthropicBetaChatCompletionRunStreamingTests()
: SkipAllRunStreaming(() => new(useReasoningChatModel: false, useBeta: true));
public class AnthropicBetaChatCompletionReasoningRunStreamingTests()
: SkipAllRunStreaming(() => new(useReasoningChatModel: true, useBeta: true));
public class AnthropicChatCompletionRunStreamingTests()
: SkipAllRunStreaming(() => new(useReasoningChatModel: false, useBeta: false));
public class AnthropicChatCompletionReasoningRunStreamingTests()
: SkipAllRunStreaming(() => new(useReasoningChatModel: true, useBeta: false));
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using AgentConformance.IntegrationTests;
namespace AnthropicChatCompletion.IntegrationTests;
public abstract class SkipAllRun(Func<AnthropicChatCompletionFixture> func) : RunTests<AnthropicChatCompletionFixture>(func)
{
[Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
public override Task RunWithChatMessageReturnsExpectedResultAsync() => base.RunWithChatMessageReturnsExpectedResultAsync();
[Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
public override Task RunWithNoMessageDoesNotFailAsync() => base.RunWithNoMessageDoesNotFailAsync();
[Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
public override Task RunWithChatMessagesReturnsExpectedResultAsync() => base.RunWithChatMessagesReturnsExpectedResultAsync();
[Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
public override Task RunWithStringReturnsExpectedResultAsync() => base.RunWithStringReturnsExpectedResultAsync();
[Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
public override Task ThreadMaintainsHistoryAsync() => base.ThreadMaintainsHistoryAsync();
}
public class AnthropicBetaChatCompletionRunTests()
: SkipAllRun(() => new(useReasoningChatModel: false, useBeta: true));
public class AnthropicBetaChatCompletionReasoningRunTests()
: SkipAllRun(() => new(useReasoningChatModel: true, useBeta: true));
public class AnthropicChatCompletionRunTests()
: SkipAllRun(() => new(useReasoningChatModel: false, useBeta: false));
public class AnthropicChatCompletionReasoningRunTests()
: SkipAllRun(() => new(useReasoningChatModel: true, useBeta: false));
@@ -0,0 +1,290 @@
// Copyright (c) Microsoft. All rights reserved.
#pragma warning disable IDE0052 // Remove unread private members
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Core;
using Anthropic.Services;
using Microsoft.Extensions.AI;
using Moq;
using IBetaMessageService = Anthropic.Services.Beta.IMessageService;
using IMessageService = Anthropic.Services.IMessageService;
namespace Microsoft.Agents.AI.Anthropic.UnitTests.Extensions;
/// <summary>
/// Unit tests for the AnthropicClientExtensions class.
/// </summary>
public sealed class AnthropicBetaServiceExtensionsTests
{
/// <summary>
/// Verify that CreateAIAgent with clientFactory parameter correctly applies the factory.
/// </summary>
[Fact]
public void CreateAIAgent_WithClientFactory_AppliesFactoryCorrectly()
{
// Arrange
var chatClient = new TestAnthropicChatClient();
var testChatClient = new TestChatClient(chatClient.Beta.AsIChatClient());
// Act
var agent = chatClient.Beta.CreateAIAgent(
model: "test-model",
instructions: "Test instructions",
name: "Test Agent",
description: "Test description",
clientFactory: (innerClient) => testChatClient);
// Assert
Assert.NotNull(agent);
Assert.Equal("Test Agent", agent.Name);
Assert.Equal("Test description", agent.Description);
// Verify that the custom chat client can be retrieved from the agent's service collection
var retrievedTestClient = agent.GetService<TestChatClient>();
Assert.NotNull(retrievedTestClient);
Assert.Same(testChatClient, retrievedTestClient);
}
/// <summary>
/// Verify that CreateAIAgent with clientFactory using AsBuilder pattern works correctly.
/// </summary>
[Fact]
public void CreateAIAgent_WithClientFactoryUsingAsBuilder_AppliesFactoryCorrectly()
{
// Arrange
var chatClient = new TestAnthropicChatClient();
TestChatClient? testChatClient = null;
// Act
var agent = chatClient.Beta.CreateAIAgent(
model: "test-model",
instructions: "Test instructions",
clientFactory: (innerClient) =>
innerClient.AsBuilder().Use((innerClient) => testChatClient = new TestChatClient(innerClient)).Build());
// Assert
Assert.NotNull(agent);
// Verify that the custom chat client can be retrieved from the agent's service collection
var retrievedTestClient = agent.GetService<TestChatClient>();
Assert.NotNull(retrievedTestClient);
Assert.Same(testChatClient, retrievedTestClient);
}
/// <summary>
/// Verify that CreateAIAgent with options and clientFactory parameter correctly applies the factory.
/// </summary>
[Fact]
public void CreateAIAgent_WithOptionsAndClientFactory_AppliesFactoryCorrectly()
{
// Arrange
var chatClient = new TestAnthropicChatClient();
var testChatClient = new TestChatClient(chatClient.Beta.AsIChatClient());
var options = new ChatClientAgentOptions
{
Name = "Test Agent",
Description = "Test description",
Instructions = "Test instructions"
};
// Act
var agent = chatClient.Beta.CreateAIAgent(
options,
clientFactory: (innerClient) => testChatClient);
// Assert
Assert.NotNull(agent);
Assert.Equal("Test Agent", agent.Name);
Assert.Equal("Test description", agent.Description);
// Verify that the custom chat client can be retrieved from the agent's service collection
var retrievedTestClient = agent.GetService<TestChatClient>();
Assert.NotNull(retrievedTestClient);
Assert.Same(testChatClient, retrievedTestClient);
}
/// <summary>
/// Verify that CreateAIAgent without clientFactory works normally.
/// </summary>
[Fact]
public void CreateAIAgent_WithoutClientFactory_WorksNormally()
{
// Arrange
var chatClient = new TestAnthropicChatClient();
// Act
var agent = chatClient.Beta.CreateAIAgent(
model: "test-model",
instructions: "Test instructions",
name: "Test Agent");
// Assert
Assert.NotNull(agent);
Assert.Equal("Test Agent", agent.Name);
// Verify that no TestChatClient is available since no factory was provided
var retrievedTestClient = agent.GetService<TestChatClient>();
Assert.Null(retrievedTestClient);
}
/// <summary>
/// Verify that CreateAIAgent with null clientFactory works normally.
/// </summary>
[Fact]
public void CreateAIAgent_WithNullClientFactory_WorksNormally()
{
// Arrange
var chatClient = new TestAnthropicChatClient();
// Act
var agent = chatClient.Beta.CreateAIAgent(
model: "test-model",
instructions: "Test instructions",
name: "Test Agent",
clientFactory: null);
// Assert
Assert.NotNull(agent);
Assert.Equal("Test Agent", agent.Name);
// Verify that no TestChatClient is available since no factory was provided
var retrievedTestClient = agent.GetService<TestChatClient>();
Assert.Null(retrievedTestClient);
}
/// <summary>
/// Verify that CreateAIAgent throws ArgumentNullException when client is null.
/// </summary>
[Fact]
public void CreateAIAgent_WithNullClient_ThrowsArgumentNullException()
{
// Act & Assert
var exception = Assert.Throws<ArgumentNullException>(() =>
((IBetaService)null!).CreateAIAgent("test-model"));
Assert.Equal("betaService", exception.ParamName);
}
/// <summary>
/// Verify that CreateAIAgent with options throws ArgumentNullException when options is null.
/// </summary>
[Fact]
public void CreateAIAgent_WithNullOptions_ThrowsArgumentNullException()
{
// Arrange
var chatClient = new TestAnthropicChatClient();
// Act & Assert
var exception = Assert.Throws<ArgumentNullException>(() =>
chatClient.Beta.CreateAIAgent((ChatClientAgentOptions)null!));
Assert.Equal("options", exception.ParamName);
}
/// <summary>
/// Test custom chat client that can be used to verify clientFactory functionality.
/// </summary>
private sealed class TestChatClient : IChatClient
{
private readonly IChatClient _innerClient;
public TestChatClient(IChatClient innerClient)
{
this._innerClient = innerClient;
}
public Task<ChatResponse> GetResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
=> this._innerClient.GetResponseAsync(messages, options, cancellationToken);
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
IEnumerable<ChatMessage> messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await foreach (var update in this._innerClient.GetStreamingResponseAsync(messages, options, cancellationToken))
{
yield return update;
}
}
public object? GetService(Type serviceType, object? serviceKey = null)
{
// Return this instance when requested
if (serviceType == typeof(TestChatClient))
{
return this;
}
return this._innerClient.GetService(serviceType, serviceKey);
}
public void Dispose() => this._innerClient.Dispose();
}
/// <summary>
/// Creates a test ChatClient implementation for testing.
/// </summary>
private sealed class TestAnthropicChatClient : IAnthropicClient
{
public TestAnthropicChatClient()
{
this.BetaService = new TestBetaService(this);
}
public HttpClient HttpClient { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
public Uri BaseUrl { get => new("http://localhost"); init => throw new NotImplementedException(); }
public bool ResponseValidation { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
public int? MaxRetries { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
public TimeSpan? Timeout { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
public string? APIKey { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
public string? AuthToken { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
public IMessageService Messages => throw new NotImplementedException();
public IModelService Models => throw new NotImplementedException();
public IBetaService Beta => this.BetaService;
public IBetaService BetaService { get; }
IMessageService IAnthropicClient.Messages => new Mock<IMessageService>().Object;
public Task<HttpResponse> Execute<T>(HttpRequest<T> request, CancellationToken cancellationToken = default) where T : ParamsBase
{
throw new NotImplementedException();
}
public IAnthropicClient WithOptions(Func<ClientOptions, ClientOptions> modifier)
{
throw new NotImplementedException();
}
private sealed class TestBetaService : IBetaService
{
private readonly IAnthropicClient _client;
public TestBetaService(IAnthropicClient client)
{
this._client = client;
}
public global::Anthropic.Services.Beta.IModelService Models => throw new NotImplementedException();
public global::Anthropic.Services.Beta.IFileService Files => throw new NotImplementedException();
public global::Anthropic.Services.Beta.ISkillService Skills => throw new NotImplementedException();
public IBetaMessageService Messages => new Mock<IBetaMessageService>().Object;
public IBetaService WithOptions(Func<ClientOptions, ClientOptions> modifier)
{
throw new NotImplementedException();
}
}
}
}
@@ -0,0 +1,257 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Anthropic;
using Anthropic.Core;
using Anthropic.Services;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI.Anthropic.UnitTests.Extensions;
/// <summary>
/// Unit tests for the AnthropicClientExtensions class.
/// </summary>
public sealed class AnthropicClientExtensionsTests
{
/// <summary>
/// Test custom chat client that can be used to verify clientFactory functionality.
/// </summary>
private sealed class TestChatClient : IChatClient
{
private readonly IChatClient _innerClient;
public TestChatClient(IChatClient innerClient)
{
this._innerClient = innerClient;
}
public Task<ChatResponse> GetResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
=> this._innerClient.GetResponseAsync(messages, options, cancellationToken);
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
IEnumerable<ChatMessage> messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await foreach (var update in this._innerClient.GetStreamingResponseAsync(messages, options, cancellationToken))
{
yield return update;
}
}
public object? GetService(Type serviceType, object? serviceKey = null)
{
// Return this instance when requested
if (serviceType == typeof(TestChatClient))
{
return this;
}
return this._innerClient.GetService(serviceType, serviceKey);
}
public void Dispose() => this._innerClient.Dispose();
}
/// <summary>
/// Creates a test ChatClient implementation for testing.
/// </summary>
private sealed class TestAnthropicChatClient : IAnthropicClient
{
public TestAnthropicChatClient()
{
}
public HttpClient HttpClient { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
public Uri BaseUrl { get => new("http://localhost"); init => throw new NotImplementedException(); }
public bool ResponseValidation { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
public int? MaxRetries { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
public TimeSpan? Timeout { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
public string? APIKey { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
public string? AuthToken { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
public IMessageService Messages => throw new NotImplementedException();
public IModelService Models => throw new NotImplementedException();
public IBetaService Beta => throw new NotImplementedException();
public Task<HttpResponse> Execute<T>(HttpRequest<T> request, CancellationToken cancellationToken = default) where T : ParamsBase
{
throw new NotImplementedException();
}
public IAnthropicClient WithOptions(Func<ClientOptions, ClientOptions> modifier)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Verify that CreateAIAgent with clientFactory parameter correctly applies the factory.
/// </summary>
[Fact]
public void CreateAIAgent_WithClientFactory_AppliesFactoryCorrectly()
{
// Arrange
var chatClient = new TestAnthropicChatClient();
var testChatClient = new TestChatClient(chatClient.AsIChatClient());
// Act
var agent = chatClient.CreateAIAgent(
model: "test-model",
instructions: "Test instructions",
name: "Test Agent",
description: "Test description",
clientFactory: (innerClient) => testChatClient);
// Assert
Assert.NotNull(agent);
Assert.Equal("Test Agent", agent.Name);
Assert.Equal("Test description", agent.Description);
// Verify that the custom chat client can be retrieved from the agent's service collection
var retrievedTestClient = agent.GetService<TestChatClient>();
Assert.NotNull(retrievedTestClient);
Assert.Same(testChatClient, retrievedTestClient);
}
/// <summary>
/// Verify that CreateAIAgent with clientFactory using AsBuilder pattern works correctly.
/// </summary>
[Fact]
public void CreateAIAgent_WithClientFactoryUsingAsBuilder_AppliesFactoryCorrectly()
{
// Arrange
var chatClient = new TestAnthropicChatClient();
TestChatClient? testChatClient = null;
// Act
var agent = chatClient.CreateAIAgent(
model: "test-model",
instructions: "Test instructions",
clientFactory: (innerClient) =>
innerClient.AsBuilder().Use((innerClient) => testChatClient = new TestChatClient(innerClient)).Build());
// Assert
Assert.NotNull(agent);
// Verify that the custom chat client can be retrieved from the agent's service collection
var retrievedTestClient = agent.GetService<TestChatClient>();
Assert.NotNull(retrievedTestClient);
Assert.Same(testChatClient, retrievedTestClient);
}
/// <summary>
/// Verify that CreateAIAgent with options and clientFactory parameter correctly applies the factory.
/// </summary>
[Fact]
public void CreateAIAgent_WithOptionsAndClientFactory_AppliesFactoryCorrectly()
{
// Arrange
var chatClient = new TestAnthropicChatClient();
var testChatClient = new TestChatClient(chatClient.AsIChatClient());
var options = new ChatClientAgentOptions
{
Name = "Test Agent",
Description = "Test description",
Instructions = "Test instructions"
};
// Act
var agent = chatClient.CreateAIAgent(
options,
clientFactory: (innerClient) => testChatClient);
// Assert
Assert.NotNull(agent);
Assert.Equal("Test Agent", agent.Name);
Assert.Equal("Test description", agent.Description);
// Verify that the custom chat client can be retrieved from the agent's service collection
var retrievedTestClient = agent.GetService<TestChatClient>();
Assert.NotNull(retrievedTestClient);
Assert.Same(testChatClient, retrievedTestClient);
}
/// <summary>
/// Verify that CreateAIAgent without clientFactory works normally.
/// </summary>
[Fact]
public void CreateAIAgent_WithoutClientFactory_WorksNormally()
{
// Arrange
var chatClient = new TestAnthropicChatClient();
// Act
var agent = chatClient.CreateAIAgent(
model: "test-model",
instructions: "Test instructions",
name: "Test Agent");
// Assert
Assert.NotNull(agent);
Assert.Equal("Test Agent", agent.Name);
// Verify that no TestChatClient is available since no factory was provided
var retrievedTestClient = agent.GetService<TestChatClient>();
Assert.Null(retrievedTestClient);
}
/// <summary>
/// Verify that CreateAIAgent with null clientFactory works normally.
/// </summary>
[Fact]
public void CreateAIAgent_WithNullClientFactory_WorksNormally()
{
// Arrange
var chatClient = new TestAnthropicChatClient();
// Act
var agent = chatClient.CreateAIAgent(
model: "test-model",
instructions: "Test instructions",
name: "Test Agent",
clientFactory: null);
// Assert
Assert.NotNull(agent);
Assert.Equal("Test Agent", agent.Name);
// Verify that no TestChatClient is available since no factory was provided
var retrievedTestClient = agent.GetService<TestChatClient>();
Assert.Null(retrievedTestClient);
}
/// <summary>
/// Verify that CreateAIAgent throws ArgumentNullException when client is null.
/// </summary>
[Fact]
public void CreateAIAgent_WithNullClient_ThrowsArgumentNullException()
{
// Act & Assert
var exception = Assert.Throws<ArgumentNullException>(() =>
((TestAnthropicChatClient)null!).CreateAIAgent("test-model"));
Assert.Equal("client", exception.ParamName);
}
/// <summary>
/// Verify that CreateAIAgent with options throws ArgumentNullException when options is null.
/// </summary>
[Fact]
public void CreateAIAgent_WithNullOptions_ThrowsArgumentNullException()
{
// Arrange
var chatClient = new TestAnthropicChatClient();
// Act & Assert
var exception = Assert.Throws<ArgumentNullException>(() =>
chatClient.CreateAIAgent((ChatClientAgentOptions)null!));
Assert.Equal("options", exception.ParamName);
}
}
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<InjectIsExternalInitOnLegacy>true</InjectIsExternalInitOnLegacy>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.Anthropic\Microsoft.Agents.AI.Anthropic.csproj" />
</ItemGroup>
</Project>