.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
Unverified
parent 112410ecd6
commit 1dde57981e
33 changed files with 1628 additions and 2 deletions
+3 -1
View File
@@ -11,6 +11,8 @@
</PropertyGroup>
<ItemGroup>
<!-- Aspire.* -->
<PackageVersion Include="Anthropic" Version="10.2.1" />
<PackageVersion Include="Anthropic.Foundry" Version="0.0.2" />
<PackageVersion Include="Aspire.Azure.AI.OpenAI" Version="13.0.0-preview.1.25560.3" />
<PackageVersion Include="Aspire.Hosting.AppHost" Version="$(AspireAppHostSdkVersion)" />
<PackageVersion Include="Aspire.Hosting.Azure.CognitiveServices" Version="$(AspireAppHostSdkVersion)" />
@@ -169,4 +171,4 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
</Project>
+11
View File
@@ -45,6 +45,7 @@
<Folder Name="/Samples/GettingStarted/AgentProviders/">
<File Path="samples/GettingStarted/AgentProviders/README.md" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_A2A/Agent_With_A2A.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_Anthropic/Agent_With_Anthropic.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/Agent_With_AzureAIAgentsPersistent.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Agent_With_AzureAIProject.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureFoundryModel/Agent_With_AzureFoundryModel.csproj" />
@@ -82,6 +83,12 @@
<File Path="samples/GettingStarted/DevUI/README.md" />
<Project Path="samples/GettingStarted/DevUI/DevUI_Step01_BasicUsage/DevUI_Step01_BasicUsage.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AgentWithAnthropic/">
<File Path="samples/GettingStarted/AgentWithAnthropic/README.md" />
<Project Path="samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step01_Running/Agent_Anthropic_Step01_Running.csproj" />
<Project Path="samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step02_Reasoning/Agent_Anthropic_Step02_Reasoning.csproj" />
<Project Path="samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/Agent_Anthropic_Step03_UsingFunctionTools.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AgentWithMemory/">
<File Path="samples/GettingStarted/AgentWithMemory/README.md" />
<Project Path="samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/AgentWithMemory_Step01_ChatHistoryMemory.csproj" />
@@ -312,6 +319,7 @@
<File Path="src/Shared/Demos/SampleEnvironment.cs" />
</Folder>
<Folder Name="/Solution Items/src/Shared/IntegrationTests/">
<File Path="src/Shared/IntegrationTests/AnthropicConfiguration.cs" />
<File Path="src/Shared/IntegrationTests/AzureAIConfiguration.cs" />
<File Path="src/Shared/IntegrationTests/Mem0Configuration.cs" />
<File Path="src/Shared/IntegrationTests/OpenAIConfiguration.cs" />
@@ -336,6 +344,7 @@
<Project Path="src/Microsoft.Agents.AI.A2A/Microsoft.Agents.AI.A2A.csproj" />
<Project Path="src/Microsoft.Agents.AI.Abstractions/Microsoft.Agents.AI.Abstractions.csproj" />
<Project Path="src/Microsoft.Agents.AI.AGUI/Microsoft.Agents.AI.AGUI.csproj" />
<Project Path="src/Microsoft.Agents.AI.Anthropic/Microsoft.Agents.AI.Anthropic.csproj" />
<Project Path="src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj" />
<Project Path="src/Microsoft.Agents.AI.AzureAI/Microsoft.Agents.AI.AzureAI.csproj" />
<Project Path="src/Microsoft.Agents.AI.CopilotStudio/Microsoft.Agents.AI.CopilotStudio.csproj" />
@@ -358,6 +367,7 @@
<Folder Name="/Tests/" />
<Folder Name="/Tests/IntegrationTests/">
<Project Path="tests/AgentConformance.IntegrationTests/AgentConformance.IntegrationTests.csproj" />
<Project Path="tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletion.IntegrationTests.csproj" />
<Project Path="tests/AzureAI.IntegrationTests/AzureAI.IntegrationTests.csproj" />
<Project Path="tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistent.IntegrationTests.csproj" />
<Project Path="tests/CopilotStudio.IntegrationTests/CopilotStudio.IntegrationTests.csproj" />
@@ -374,6 +384,7 @@
<Project Path="tests/Microsoft.Agents.AI.A2A.UnitTests/Microsoft.Agents.AI.A2A.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Abstractions.UnitTests/Microsoft.Agents.AI.Abstractions.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.AGUI.UnitTests/Microsoft.Agents.AI.AGUI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Anthropic.UnitTests/Microsoft.Agents.AI.Anthropic.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.AzureAI.UnitTests/Microsoft.Agents.AI.AzureAI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.DevUI.UnitTests/Microsoft.Agents.AI.DevUI.UnitTests.csproj" />
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);IDE0059</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Anthropic\Microsoft.Agents.AI.Anthropic.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,101 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows how to create and use an AI agent with Anthropic as the backend.
using System.ClientModel;
using System.Net.Http.Headers;
using Anthropic;
using Anthropic.Core;
using Azure.Core;
using Azure.Identity;
using Microsoft.Agents.AI;
using Sample;
var deploymentName = Environment.GetEnvironmentVariable("ANTHROPIC_DEPLOYMENT_NAME") ?? "claude-haiku-4-5";
// The resource is the subdomain name / first name coming before '.services.ai.azure.com' in the endpoint Uri
// ie: https://(resource name).services.ai.azure.com/anthropic/v1/chat/completions
var resource = Environment.GetEnvironmentVariable("ANTHROPIC_RESOURCE");
var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY");
const string JokerInstructions = "You are good at telling jokes.";
const string JokerName = "JokerAgent";
AnthropicClient? client = (resource is null)
? new AnthropicClient() { APIKey = apiKey ?? throw new InvalidOperationException("ANTHROPIC_API_KEY is required when no ANTHROPIC_RESOURCE is provided") } // If no resource is provided, use Anthropic public API
: (apiKey is not null)
? new AnthropicFoundryClient(resource, new ApiKeyCredential(apiKey)) // If an apiKey is provided, use Foundry with ApiKey authentication
: new AnthropicFoundryClient(resource, new AzureCliCredential()); // Otherwise, use Foundry with Azure Client authentication
AIAgent agent = client.CreateAIAgent(model: deploymentName, instructions: JokerInstructions, name: JokerName);
// Invoke the agent and output the text result.
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
namespace Sample
{
/// <summary>
/// Provides methods for invoking the Azure hosted Anthropic api.
/// </summary>
public class AnthropicFoundryClient : AnthropicClient
{
private readonly TokenCredential _tokenCredential;
private readonly string _resourceName;
/// <summary>
/// Creates a new instance of the <see cref="AnthropicFoundryClient"/>.
/// </summary>
/// <param name="resourceName">The service resource subdomain name to use in the anthropic azure endpoint</param>
/// <param name="tokenCredential">The credential provider. Use any specialization of <see cref="TokenCredential"/> to get your access token in supported environments.</param>
/// <param name="options">Set of <see cref="Anthropic.Core.ClientOptions"/> client option configurations</param>
/// <exception cref="ArgumentNullException">Resource is null</exception>
/// <exception cref="ArgumentNullException">TokenCredential is null</exception>
/// <remarks>
/// Any <see cref="Anthropic.Core.ClientOptions"/> APIKey or Bearer token provided will be ignored in favor of the <see cref="TokenCredential"/> provided in the constructor
/// </remarks>
public AnthropicFoundryClient(string resourceName, TokenCredential tokenCredential, Anthropic.Core.ClientOptions? options = null) : base(options ?? new())
{
this._resourceName = resourceName ?? throw new ArgumentNullException(nameof(resourceName));
this._tokenCredential = tokenCredential ?? throw new ArgumentNullException(nameof(tokenCredential));
this.BaseUrl = new Uri($"https://{this._resourceName}.services.ai.azure.com/anthropic", UriKind.Absolute);
}
/// <summary>
/// Creates a new instance of the <see cref="AnthropicFoundryClient"/>.
/// </summary>
/// <param name="resourceName">The service resource subdomain name to use in the anthropic azure endpoint</param>
/// <param name="apiKeyCredential">The api key.</param>
/// <param name="options">Set of <see cref="Anthropic.Core.ClientOptions"/> client option configurations</param>
/// <exception cref="ArgumentNullException">Resource is null</exception>
/// <exception cref="ArgumentNullException">Api key is null</exception>
/// <remarks>
/// Any <see cref="Anthropic.Core.ClientOptions"/> APIKey or Bearer token provided will be ignored in favor of the <see cref="ApiKeyCredential"/> provided in the constructor
/// </remarks>
public AnthropicFoundryClient(string resourceName, ApiKeyCredential apiKeyCredential, Anthropic.Core.ClientOptions? options = null) :
this(resourceName, apiKeyCredential is null
? throw new ArgumentNullException(nameof(apiKeyCredential))
: DelegatedTokenCredential.Create((_, _) =>
{
apiKeyCredential.Deconstruct(out string dangerousCredential);
return new AccessToken(dangerousCredential, DateTimeOffset.MaxValue);
}),
options)
{ }
public override IAnthropicClient WithOptions(Func<Anthropic.Core.ClientOptions, Anthropic.Core.ClientOptions> modifier)
=> this;
protected override ValueTask BeforeSend<T>(
HttpRequest<T> request,
HttpRequestMessage requestMessage,
CancellationToken cancellationToken
)
{
var accessToken = this._tokenCredential.GetToken(new TokenRequestContext(scopes: ["https://ai.azure.com/.default"]), cancellationToken);
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken.Token);
return default;
}
}
}
@@ -0,0 +1,53 @@
# Creating an AIAgent with Anthropic
This sample demonstrates how to create an AIAgent using Anthropic Claude models as the underlying inference service.
The sample supports three deployment scenarios:
1. **Anthropic Public API** - Direct connection to Anthropic's public API
2. **Azure Foundry with API Key** - Anthropic models deployed through Azure Foundry using API key authentication
3. **Azure Foundry with Azure CLI** - Anthropic models deployed through Azure Foundry using Azure CLI credentials
## Prerequisites
Before you begin, ensure you have the following prerequisites:
- .NET 8.0 SDK or later
### For Anthropic Public API
- Anthropic API key
Set the following environment variables:
```powershell
$env:ANTHROPIC_API_KEY="your-anthropic-api-key" # Replace with your Anthropic API key
$env:ANTHROPIC_DEPLOYMENT_NAME="claude-haiku-4-5" # Optional, defaults to claude-haiku-4-5
```
### For Azure Foundry with API Key
- Azure Foundry service endpoint and deployment configured
- Anthropic API key
Set the following environment variables:
```powershell
$env:ANTHROPIC_RESOURCE="your-foundry-resource-name" # Replace with your Azure Foundry resource name (subdomain before .services.ai.azure.com)
$env:ANTHROPIC_API_KEY="your-anthropic-api-key" # Replace with your Anthropic API key
$env:ANTHROPIC_DEPLOYMENT_NAME="claude-haiku-4-5" # Optional, defaults to claude-haiku-4-5
```
### For Azure Foundry with Azure CLI
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
Set the following environment variables:
```powershell
$env:ANTHROPIC_RESOURCE="your-foundry-resource-name" # Replace with your Azure Foundry resource name (subdomain before .services.ai.azure.com)
$env:ANTHROPIC_DEPLOYMENT_NAME="claude-haiku-4-5" # Optional, defaults to claude-haiku-4-5
```
**Note**: When using Azure Foundry with Azure CLI, make sure you're logged in with `az login` and have access to the Azure Foundry resource. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
@@ -3,6 +3,7 @@
// This sample shows how to create and use a simple AI agent with OpenAI Chat Completion as the backend.
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;
var apiKey = Environment.GetEnvironmentVariable("OPENAI_APIKEY") ?? throw new InvalidOperationException("OPENAI_APIKEY is not set.");
@@ -15,6 +15,7 @@ See the README.md for each sample for the prerequisites for that sample.
|Sample|Description|
|---|---|
|[Creating an AIAgent with A2A](./Agent_With_A2A/)|This sample demonstrates how to create AIAgent for an existing A2A agent.|
|[Creating an AIAgent with Anthropic](./Agent_With_Anthropic/)|This sample demonstrates how to create an AIAgent using Anthropic Claude models as the underlying inference service|
|[Creating an AIAgent with Foundry Agents using Azure.AI.Agents.Persistent](./Agent_With_AzureAIAgentsPersistent/)|This sample demonstrates how to create a Foundry Persistent agent and expose it as an AIAgent using the Azure.AI.Agents.Persistent SDK|
|[Creating an AIAgent with Foundry Agents using Azure.AI.Project](./Agent_With_AzureAIProject/)|This sample demonstrates how to create an Foundry Project agent and expose it as an AIAgent using the Azure.AI.Project SDK|
|[Creating an AIAgent with AzureFoundry Model](./Agent_With_AzureFoundryModel/)|This sample demonstrates how to use any model deployed to Azure Foundry to create an AIAgent|
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Anthropic\Microsoft.Agents.AI.Anthropic.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,24 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows how to create and use a simple AI agent with Anthropic as the backend.
using Anthropic;
using Anthropic.Core;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY") ?? throw new InvalidOperationException("ANTHROPIC_API_KEY is not set.");
var model = Environment.GetEnvironmentVariable("ANTHROPIC_MODEL") ?? "claude-haiku-4-5";
AIAgent agent = new AnthropicClient(new ClientOptions { APIKey = apiKey })
.CreateAIAgent(model: model, instructions: "You are good at telling jokes.", name: "Joker");
// Invoke the agent and output the text result.
var response = await agent.RunAsync("Tell me a joke about a pirate.");
Console.WriteLine(response);
// Invoke the agent with streaming support.
await foreach (var update in agent.RunStreamingAsync("Tell me a joke about a pirate."))
{
Console.WriteLine(update);
}
@@ -0,0 +1,43 @@
# Running a simple agent with Anthropic
This sample demonstrates how to create and run a basic agent with Anthropic Claude models.
## What this sample demonstrates
- Creating an AI agent with Anthropic Claude
- Running a simple agent with instructions
- Managing agent lifecycle
## Prerequisites
Before you begin, ensure you have the following prerequisites:
- .NET 8.0 SDK or later
- Anthropic API key configured
**Note**: This sample uses Anthropic Claude models. For more information, see [Anthropic documentation](https://docs.anthropic.com/).
Set the following environment variables:
```powershell
$env:ANTHROPIC_API_KEY="your-anthropic-api-key" # Replace with your Anthropic API key
$env:ANTHROPIC_MODEL="your-anthropic-model" # Replace with your Anthropic model
```
## Run the sample
Navigate to the AgentWithAnthropic sample directory and run:
```powershell
cd dotnet\samples\GettingStarted\AgentWithAnthropic
dotnet run --project .\Agent_Anthropic_Step01_Running
```
## Expected behavior
The sample will:
1. Create an agent with Anthropic Claude
2. Run the agent with a simple prompt
3. Display the agent's response
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Anthropic\Microsoft.Agents.AI.Anthropic.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,59 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample shows how to create and use an AI agent with reasoning capabilities.
using Anthropic;
using Anthropic.Core;
using Anthropic.Models.Messages;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY") ?? throw new InvalidOperationException("ANTHROPIC_API_KEY is not set.");
var model = Environment.GetEnvironmentVariable("ANTHROPIC_MODEL") ?? "claude-haiku-4-5";
var maxTokens = 4096;
var thinkingTokens = 2048;
var agent = new AnthropicClient(new ClientOptions { APIKey = apiKey })
.CreateAIAgent(
model: model,
clientFactory: (chatClient) => chatClient
.AsBuilder()
.ConfigureOptions(
options => options.RawRepresentationFactory = (_) => new MessageCreateParams()
{
Model = options.ModelId ?? model,
MaxTokens = options.MaxOutputTokens ?? maxTokens,
Messages = [],
Thinking = new ThinkingConfigParam(new ThinkingConfigEnabled(budgetTokens: thinkingTokens))
})
.Build());
Console.WriteLine("1. Non-streaming:");
var response = await agent.RunAsync("Solve this problem step by step: If a train travels 60 miles per hour and needs to cover 180 miles, how long will the journey take? Show your reasoning.");
Console.WriteLine("#### Start Thinking ####");
Console.WriteLine($"\e[92m{string.Join("\n", response.Messages.SelectMany(m => m.Contents.OfType<TextReasoningContent>().Select(c => c.Text)))}\e[0m");
Console.WriteLine("#### End Thinking ####");
Console.WriteLine("\n#### Final Answer ####");
Console.WriteLine(response.Text);
Console.WriteLine("Token usage:");
Console.WriteLine($"Input: {response.Usage?.InputTokenCount}, Output: {response.Usage?.OutputTokenCount}, {string.Join(", ", response.Usage?.AdditionalCounts ?? [])}");
Console.WriteLine();
Console.WriteLine("2. Streaming");
await foreach (var update in agent.RunStreamingAsync("Explain the theory of relativity in simple terms."))
{
foreach (var item in update.Contents)
{
if (item is TextReasoningContent reasoningContent)
{
Console.WriteLine($"\e[92m{reasoningContent.Text}\e[0m");
}
else if (item is TextContent textContent)
{
Console.WriteLine(textContent.Text);
}
}
}
@@ -0,0 +1,46 @@
# Using reasoning with Anthropic agents
This sample demonstrates how to use extended thinking/reasoning capabilities with Anthropic Claude agents.
## What this sample demonstrates
- Creating an AI agent with Anthropic Claude extended thinking
- Using reasoning capabilities for complex problem solving
- Extracting thinking and response content from agent output
- Managing agent lifecycle
## Prerequisites
Before you begin, ensure you have the following prerequisites:
- .NET 8.0 SDK or later
- Anthropic API key configured
- Access to Anthropic Claude models with extended thinking support
**Note**: This sample uses Anthropic Claude models with extended thinking. For more information, see [Anthropic documentation](https://docs.anthropic.com/).
Set the following environment variables:
```powershell
$env:ANTHROPIC_API_KEY="your-anthropic-api-key" # Replace with your Anthropic API key
$env:ANTHROPIC_MODEL="your-anthropic-model" # Replace with your Anthropic model
```
## Run the sample
Navigate to the AgentWithAnthropic sample directory and run:
```powershell
cd dotnet\samples\GettingStarted\AgentWithAnthropic
dotnet run --project .\Agent_Anthropic_Step02_Reasoning
```
## Expected behavior
The sample will:
1. Create an agent with Anthropic Claude extended thinking enabled
2. Run the agent with a complex reasoning prompt
3. Display the agent's thinking process
4. Display the agent's final response
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Anthropic\Microsoft.Agents.AI.Anthropic.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates how to use an agent with function tools.
// It shows both non-streaming and streaming agent interactions using weather-related tools.
using System.ComponentModel;
using Anthropic;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY") ?? throw new InvalidOperationException("ANTHROPIC_API_KEY is not set.");
var model = Environment.GetEnvironmentVariable("ANTHROPIC_MODEL") ?? "claude-haiku-4-5";
[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
=> $"The weather in {location} is cloudy with a high of 15°C.";
const string AssistantInstructions = "You are a helpful assistant that can get weather information.";
const string AssistantName = "WeatherAssistant";
// Define the agent with function tools.
AITool tool = AIFunctionFactory.Create(GetWeather);
// Get anthropic client to create agents.
AIAgent agent = new AnthropicClient { APIKey = apiKey }
.CreateAIAgent(model: model, instructions: AssistantInstructions, name: AssistantName, tools: [tool]);
// Non-streaming agent interaction with function tools.
AgentThread thread = agent.GetNewThread();
Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?", thread));
// Streaming agent interaction with function tools.
thread = agent.GetNewThread();
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync("What is the weather like in Amsterdam?", thread))
{
Console.WriteLine(update);
}
@@ -0,0 +1,47 @@
# Using Function Tools with Anthropic agents
This sample demonstrates how to use function tools with Anthropic Claude agents, allowing agents to call custom functions to retrieve information.
## What this sample demonstrates
- Creating function tools using AIFunctionFactory
- Passing function tools to an Anthropic Claude agent
- Running agents with function tools (text output)
- Running agents with function tools (streaming output)
- Managing agent lifecycle
## Prerequisites
Before you begin, ensure you have the following prerequisites:
- .NET 8.0 SDK or later
- Anthropic API key configured
**Note**: This sample uses Anthropic Claude models. For more information, see [Anthropic documentation](https://docs.anthropic.com/).
Set the following environment variables:
```powershell
$env:ANTHROPIC_API_KEY="your-anthropic-api-key" # Replace with your Anthropic API key
$env:ANTHROPIC_MODEL="your-anthropic-model" # Replace with your Anthropic model
```
## Run the sample
Navigate to the AgentWithAnthropic sample directory and run:
```powershell
cd dotnet\samples\GettingStarted\AgentWithAnthropic
dotnet run --project .\Agent_Anthropic_Step03_UsingFunctionTools
```
## Expected behavior
The sample will:
1. Create an agent named "WeatherAssistant" with a GetWeather function tool
2. Run the agent with a text prompt asking about weather
3. The agent will invoke the GetWeather function tool to retrieve weather information
4. Run the agent again with streaming to display the response as it's generated
5. Clean up resources by deleting the agent
@@ -0,0 +1,72 @@
# Getting started with agents using Anthropic
The getting started with agents using Anthropic samples demonstrate the fundamental concepts and functionalities
of single agents using Anthropic as the AI provider.
These samples use Anthropic Claude models as the AI provider and use ChatCompletion as the type of service.
For other samples that demonstrate how to create and configure each type of agent that come with the agent framework,
see the [How to create an agent for each provider](../AgentProviders/README.md) samples.
## Getting started with agents using Anthropic prerequisites
Before you begin, ensure you have the following prerequisites:
- .NET 8.0 SDK or later
- Anthropic API key configured
- User has access to Anthropic Claude models
**Note**: These samples use Anthropic Claude models. For more information, see [Anthropic documentation](https://docs.anthropic.com/).
## Using Anthropic with Azure Foundry
To use Anthropic with Azure Foundry, you can check the sample [AgentProviders/Agent_With_Anthropic](../AgentProviders/Agent_With_Anthropic/README.md) for more details.
## Samples
|Sample|Description|
|---|---|
|[Running a simple agent](./Agent_Anthropic_Step01_Running/)|This sample demonstrates how to create and run a basic agent with Anthropic Claude|
|[Using reasoning with an agent](./Agent_Anthropic_Step02_Reasoning/)|This sample demonstrates how to use extended thinking/reasoning capabilities with Anthropic Claude agents|
|[Using function tools with an agent](./Agent_Anthropic_Step03_UsingFunctionTools/)|This sample demonstrates how to use function tools with an Anthropic Claude agent|
## Running the samples from the console
To run the samples, navigate to the desired sample directory, e.g.
```powershell
cd Agent_Anthropic_Step01_Running
```
Set the following environment variables:
```powershell
$env:ANTHROPIC_API_KEY="your-anthropic-api-key" # Replace with your Anthropic API key
```
If the variables are not set, you will be prompted for the values when running the samples.
Execute the following command to build the sample:
```powershell
dotnet build
```
Execute the following command to run the sample:
```powershell
dotnet run --no-build
```
Or just build and run in one step:
```powershell
dotnet run
```
## Running the samples from Visual Studio
Open the solution in Visual Studio and set the desired sample project as the startup project. Then, run the project using the built-in debugger or by pressing `F5`.
You will be prompted for any required environment variables if they are not already set.
+1
View File
@@ -15,5 +15,6 @@ of the agent framework.
|[A2A](./A2A/README.md)|Getting started with A2A (Agent-to-Agent) specific features|
|[Agent Open Telemetry](./AgentOpenTelemetry/README.md)|Getting started with OpenTelemetry for agents|
|[Agent With OpenAI exchange types](./AgentWithOpenAI/README.md)|Using OpenAI exchange types with agents|
|[Agent With Anthropic](./AgentWithAnthropic/README.md)|Getting started with agents using Anthropic Claude|
|[Workflow](./Workflows/README.md)|Getting started with Workflow|
|[Model Context Protocol](./ModelContextProtocol/README.md)|Getting started with Model Context Protocol|
@@ -0,0 +1,97 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using Microsoft.Shared.Diagnostics;
namespace Anthropic.Services;
/// <summary>
/// Provides extension methods for the <see cref="IBetaService"/> class.
/// </summary>
public static class AnthropicBetaServiceExtensions
{
/// <summary>
/// Specifies the default maximum number of tokens allowed for processing operations.
/// </summary>
public static int DefaultMaxTokens { get; set; } = 4096;
/// <summary>
/// Creates a new AI agent using the specified model and options.
/// </summary>
/// <param name="betaService">The Anthropic beta service.</param>
/// <param name="model">The model to use for chat completions.</param>
/// <param name="instructions">The instructions for the AI agent.</param>
/// <param name="name">The name of the AI agent.</param>
/// <param name="description">The description of the AI agent.</param>
/// <param name="tools">The tools available to the AI agent.</param>
/// <param name="defaultMaxTokens">The default maximum tokens for chat completions. Defaults to <see cref="DefaultMaxTokens"/> if not provided.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="loggerFactory">Optional logger factory for enabling logging within the agent.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <returns>The created <see cref="ChatClientAgent"/> AI agent.</returns>
public static ChatClientAgent CreateAIAgent(
this IBetaService betaService,
string model,
string? instructions = null,
string? name = null,
string? description = null,
IList<AITool>? tools = null,
int? defaultMaxTokens = null,
Func<IChatClient, IChatClient>? clientFactory = null,
ILoggerFactory? loggerFactory = null,
IServiceProvider? services = null)
{
var options = new ChatClientAgentOptions
{
Instructions = instructions,
Name = name,
Description = description,
};
if (tools is { Count: > 0 })
{
options.ChatOptions = new ChatOptions { Tools = tools };
}
var chatClient = betaService.AsIChatClient(model, defaultMaxTokens ?? DefaultMaxTokens);
if (clientFactory is not null)
{
chatClient = clientFactory(chatClient);
}
return new ChatClientAgent(chatClient, options, loggerFactory, services);
}
/// <summary>
/// Creates an AI agent from an <see cref="IBetaService"/> using the Anthropic Chat Completion API.
/// </summary>
/// <param name="betaService">The Anthropic <see cref="IBetaService"/> to use for the agent.</param>
/// <param name="options">Full set of options to configure the agent.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="loggerFactory">Optional logger factory for enabling logging within the agent.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <returns>An <see cref="ChatClientAgent"/> instance backed by the Anthropic Chat Completion service.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="betaService"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
public static ChatClientAgent CreateAIAgent(
this IBetaService betaService,
ChatClientAgentOptions options,
Func<IChatClient, IChatClient>? clientFactory = null,
ILoggerFactory? loggerFactory = null,
IServiceProvider? services = null)
{
Throw.IfNull(betaService);
Throw.IfNull(options);
var chatClient = betaService.AsIChatClient();
if (clientFactory is not null)
{
chatClient = clientFactory(chatClient);
}
return new ChatClientAgent(chatClient, options, loggerFactory, services);
}
}
@@ -0,0 +1,97 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using Microsoft.Shared.Diagnostics;
namespace Anthropic;
/// <summary>
/// Provides extension methods for the <see cref="IAnthropicClient"/> class.
/// </summary>
public static class AnthropicClientExtensions
{
/// <summary>
/// Specifies the default maximum number of tokens allowed for processing operations.
/// </summary>
public static int DefaultMaxTokens { get; set; } = 4096;
/// <summary>
/// Creates a new AI agent using the specified model and options.
/// </summary>
/// <param name="client">An Anthropic <see cref="IAnthropicClient"/> to use with the agent..</param>
/// <param name="model">The model to use for chat completions.</param>
/// <param name="instructions">The instructions for the AI agent.</param>
/// <param name="name">The name of the AI agent.</param>
/// <param name="description">The description of the AI agent.</param>
/// <param name="tools">The tools available to the AI agent.</param>
/// <param name="defaultMaxTokens">The default maximum tokens for chat completions. Defaults to <see cref="DefaultMaxTokens"/> if not provided.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="loggerFactory">Optional logger factory for enabling logging within the agent.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <returns>The created <see cref="ChatClientAgent"/> AI agent.</returns>
public static ChatClientAgent CreateAIAgent(
this IAnthropicClient client,
string model,
string? instructions = null,
string? name = null,
string? description = null,
IList<AITool>? tools = null,
int? defaultMaxTokens = null,
Func<IChatClient, IChatClient>? clientFactory = null,
ILoggerFactory? loggerFactory = null,
IServiceProvider? services = null)
{
var options = new ChatClientAgentOptions
{
Instructions = instructions,
Name = name,
Description = description,
};
if (tools is { Count: > 0 })
{
options.ChatOptions = new ChatOptions { Tools = tools };
}
var chatClient = client.AsIChatClient(model, defaultMaxTokens ?? DefaultMaxTokens);
if (clientFactory is not null)
{
chatClient = clientFactory(chatClient);
}
return new ChatClientAgent(chatClient, options, loggerFactory, services);
}
/// <summary>
/// Creates an AI agent from an <see cref="IAnthropicClient"/> using the Anthropic Chat Completion API.
/// </summary>
/// <param name="client">An Anthropic <see cref="IAnthropicClient"/> to use with the agent..</param>
/// <param name="options">Full set of options to configure the agent.</param>
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
/// <param name="loggerFactory">Optional logger factory for enabling logging within the agent.</param>
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
/// <returns>An <see cref="ChatClientAgent"/> instance backed by the Anthropic Chat Completion service.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="client"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
public static ChatClientAgent CreateAIAgent(
this IAnthropicClient client,
ChatClientAgentOptions options,
Func<IChatClient, IChatClient>? clientFactory = null,
ILoggerFactory? loggerFactory = null,
IServiceProvider? services = null)
{
Throw.IfNull(client);
Throw.IfNull(options);
var chatClient = client.AsIChatClient();
if (clientFactory is not null)
{
chatClient = clientFactory(chatClient);
}
return new ChatClientAgent(chatClient, options, loggerFactory, services);
}
}
@@ -0,0 +1,13 @@
// Copyright (c) Microsoft. All rights reserved.
#pragma warning disable CA1812
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Agents.AI.Anthropic;
[JsonSerializable(typeof(JsonElement))]
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(Dictionary<string, object?>))]
internal sealed partial class AnthropicClientJsonContext : JsonSerializerContext;
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<VersionSuffix>preview</VersionSuffix>
<ImplicitUsings>enable</ImplicitUsings>
<InjectSharedThrow>true</InjectSharedThrow>
</PropertyGroup>
<Import Project="$(RepoRoot)/dotnet/nuget/nuget-package.props" />
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.AI" />
<PackageReference Include="Anthropic" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>
<PropertyGroup>
<!-- NuGet Package Settings -->
<Title>Microsoft Agent Framework Anthropic Agents</Title>
<Description>Provides Microsoft Agent Framework support for Anthropic Agents.</Description>
</PropertyGroup>
</Project>
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Shared.IntegrationTests;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
#pragma warning disable CA1812 // Internal class that is apparently never instantiated.
internal sealed class AnthropicConfiguration
{
public string? ServiceId { get; set; }
public string ChatModelId { get; set; }
public string ChatReasoningModelId { get; set; }
public string ApiKey { get; set; }
}
@@ -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>