Merge pull request #1 from StevanFreeborn/stevanfreeborn/docs/prepare-documentation-for-release

docs: prepare README
This commit is contained in:
Stevan Freeborn
2024-07-06 11:59:23 -05:00
committed by GitHub
47 changed files with 1861 additions and 550 deletions
+6 -2
View File
@@ -1,6 +1,10 @@
{ {
"dotnet.defaultSolution": "AnthropicClient.sln", "dotnet.defaultSolution": "AnthropicClient.sln",
"cSpell.words": [ "cSpell.words": [
"Browsable" "Browsable",
] "nameof",
"Szalay",
"typeof"
],
"dotnet.unitTests.runSettingsPath": "./tests/AnthropicClient.Tests/.runsettings"
} }
+600 -3
View File
@@ -1,8 +1,605 @@
# AnthropicClient # AnthropicClient
This library for the Anthropic API is meant to simplify development in C# for Anthropic users.
> [!NOTE]
> This is an unofficial SDK for the Anthropic API. It was not built in consultation with Anthropic or any member of their organization.
This SDK was developed independently using existing libraries and the Anthropic API documentation as the starting point with the intention of making development of integrations done in C# with Anthropic quicker and more convenient.
> [!NOTE] > [!NOTE]
> This client library is heavily inspired by the [Anthropic.SDK](https://github.com/tghamm/Anthropic.SDK) library. I chose to create a new library because I wanted to handle streaming and tool calling differently as well as have control over the client library as I plan to use it to build a connector for [SemanticKernel](https://github.com/microsoft/semantic-kernel). > This client library is heavily inspired by the [Anthropic.SDK](https://github.com/tghamm/Anthropic.SDK) library. I chose to create a new library because I wanted to handle streaming and tool calling differently as well as have control over the client library as I plan to use it to build a connector for [SemanticKernel](https://github.com/microsoft/semantic-kernel). However if you are looking for a client library the Anthropic.SDK is a great place to start.
## ⚠️ Under Construction ⚠️ ## 📝 Issues
This is a client library for the Anthropic API. It is a work in progress and is not yet ready for use. If you encounter any issues while using this library please open an issue [here](https://github.com/StevanFreeborn/anthropic-client/issues).
## 📜 License
This library is licensed under the [MIT License](https://choosealicense.com/licenses/mit/) and is free to use and modify.
## 📝 Contributing
If you would like to contribute to this library please open a pull request [here](https://github.com/StevanFreeborn/anthropic-client/pulls).
## 🛠️ Dependencies
### [Microsoft.Bcl.AsyncInterfaces](https://www.nuget.org/packages/Microsoft.Bcl.AsyncInterfaces/)
![Microsoft.Bcl.AsyncInterfaces NuGet Version](https://img.shields.io/nuget/v/Microsoft.Bcl.AsyncInterfaces)
Used to support async interfaces when streaming messages
### [System.Text.Json](https://www.nuget.org/packages/System.Text.Json/)
![NuGet Version](https://img.shields.io/nuget/v/System.Text.Json)
Used for JSON serialization and deserialization
## 💾 Installation
Install the package from [NuGet](https://www.nuget.org) using the following command:
```bash
dotnet add package AnthropicClient
```
## 🔑 API Key
In order to use the Anthropic API you will need an API key. You can get one by signing up at [Anthropic](https://www.anthropic.com/api). Please keep your API key secure and do not share it with others. Be mindful of where you store your API key and do not commit it to a public repository.
## 👨🏻‍💻 Start Coding
### `AnthropicApiClient`
The most common way to use the SDK is to create an `AnthropicApiClient` instance and call its methods. Its constructor requires two parameters:
- `apiKey` - your Anthropic API key
- `httpClient` - an `HttpClient` instance. You can configure and customize the `HttpClient` instance as needed. This library however will perform the necessary configuration to work with the Anthropic API. Such as setting the base address and adding the proper headers.
> [!NOTE]
> This library does not manage the lifecycle of the `HttpClient` instance. You should create and manage the lifecycle of the `HttpClient` instance in your application.
It is best practice to read the API key from a secure location such as a configuration file or environment variable. For example using the `appsettings.json` file:
```json
{
"AnthropicApiKey": "YOUR_API"
}
```
Example constructing an `AnthropicApiClient` instance:
```csharp
using AnthropicClient;
using Microsoft.Extensions.Configuration;
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var apiKey = configuration["AnthropicApiKey"];
var client = new AnthropicApiClient(apiKey, new HttpClient());
```
### `IAnthropicApiClient`
The library does expose an interface `IAnthropicApiClient` that can be used for dependency injection and testing. The interface is implemented by the `AnthropicApiClient` class.
### Full API Documentation
This library was developed to make using the Anthropic API easier within a .NET application. If you are looking for the full API documentation you can find it at [Anthropic API Documentation](https://docs.anthropic.com/).
## Usage
The primary use case for working with the Anthropic API is to create a message in response to a request that includes one or more other messages. The created message can then be received either as a complete response or a stream of events. This can be used to create a conversation between the caller and Anthropic's AI models and/or to use Anthropic's AI models to perform a task.
> [!NOTE]
> The following examples assume that you have already created an instance of the `AnthropicApiClient` class named `client`. You can also find these snippets in the [examples](./tests/AnthropicClient.Tests/Examples/) directory.
### Create a message
The `AnthropicApiClient` exposes a single method named `CreateMessageAsync` that can be used to create a message. The method requires a `MessageRequest` or a `StreamMessageRequest` instance as a parameter. The `MessageRequest` class is used to create a message whose response is not streamed and the `StreamMessageRequest` class is used to create a message whose response is streamed. The `MessageRequest` instance's properties can be set to configure how the message is created.
#### Non-Streaming
```csharp
using AnthropicClient;
using AnthropicClient.Models;
var response = await client.CreateMessageAsync(new MessageRequest(
AnthropicModels.Claude3Haiku,
[
new(
MessageRole.User,
[new TextContent("Please write a haiku about the ocean.")]
)
]
));
if (response.IsSuccess is false)
{
Console.WriteLine($"Failed to create message");
Console.WriteLine($"Error Type: {0}", response.Error.Error.Type);
Console.WriteLine($"Error Message: {0}", response.Error.Error.Message);
return;
}
foreach (var content in response.Value.Content)
{
switch (content)
{
case TextContent textContent:
Console.WriteLine(textContent.Text);
break;
}
}
```
#### Streaming
Anthropic uses Server-Sent Events (SSE) to stream messages. The possible events and the format of those events are documented in the [Anthropic API Documentation](https://docs.anthropic.com/en/api/messages-streaming). This library provides a way to consume them after they have been deserialized into strongly-typed C# objects that are returned in an `IAsyncEnumerable` collection.
This allows you to consume the events as they are received and process them in the way that best fits your use case. The following example demonstrates how to consume the streamed events and build up the complete text response from the model.
```csharp
using AnthropicClient;
using AnthropicClient.Models;
var events = client.CreateMessageAsync(new StreamMessageRequest(
AnthropicModels.Claude3Haiku,
[
new(
MessageRole.User,
[new TextContent("Please write a haiku about the ocean.")]
)
]
));
var msgBuilder = new StringBuilder();
await foreach (var e in events)
{
switch (e.Data)
{
case var data when data is ContentDeltaEventData contentData:
switch (contentData.Delta)
{
case var delta when delta is TextDelta textDelta:
msgBuilder.Append(textDelta.Text);
break;
}
break;
}
}
Console.WriteLine(msgBuilder.ToString());
```
##### Message Complete Event
This library also provides a custom `message_complete` event that is yielded when all the message's events have been received. This event is not part of Anthropic's SSE events but is provided to allow for easier consumption of the entire message response if desired and make it easier to implement built-in tool calling.
```csharp
using AnthropicClient;
using AnthropicClient.Models;
var events = client.CreateMessageAsync(new StreamMessageRequest(
AnthropicModels.Claude3Haiku,
[
new(
MessageRole.User,
[new TextContent("Please write a haiku about the ocean.")]
)
]
));
MessageResponse? response = null;
await foreach (var e in events)
{
switch (e.Data)
{
case var data when data is MessageCompleteEventData msgData:
response = msgData.Message;
break;
}
}
var textContent = response?.Content
.OfType<TextContent>()
.Aggregate(new StringBuilder(), (sb, c) => sb.Append(c.Text))
.ToString();
Console.WriteLine(textContent);
```
### Tool Use
Anthropic's models support the use of tools to perform tasks. This allows the models to interact with external client-side tools that can perform actions the models cannot do natively. This gives you the ability to further extend the model's abilities with your own custom tools. This feature is covered in depth in [Anthropic's API Documentation](https://docs.anthropic.com/en/docs/build-with-claude/tool-use). This library aims to make using tools convenient by allowing you to create, provide, and call tools from within your application by leveraging the reflection capabilities of C#.
> [!NOTE]
> All tools are user provided. The models do no not have access to any built-in server-side tools.
#### Create a tool
You can create a tool in 4 different ways and then provide that tool when creating a message.
1. Create a tool from a class
1. Create a tool from a static method
1. Create a tool from an instance method
1. Create a tool from a delegate
##### Create a tool from a class
When creating a tool from a class the class must implement the `ITool` interface.
```csharp
using AnthropicClient.Models;
class GetWeatherTool : ITool
{
public string Name => "Get Weather";
public string Description => "Get the weather for a location in the specified units";
public MethodInfo Function => typeof(GetWeatherTool).GetMethod(nameof(GetWeather))!;
public static string GetWeather(string location, string units)
{
return $"The weather in {location} is 72 degrees {units}";
}
}
var getWeatherTool = Tool.CreateFromClass<GetWeatherTool>();
```
##### Create a tool from a static method
When creating a tool from a static method the method must be public and static.
```csharp
using AnthropicClient.Models;
class GetWeatherTool
{
public static string GetWeather(string location)
{
return $"The weather in {location} is 72 degrees Fahrenheit";
}
}
var getWeatherTool = Tool.CreateFromStaticMethod(
"Get Weather",
"Get the weather for a location in the specified units",
typeof(GetWeatherTool),
nameof(GetWeatherTool.GetWeather)
);
```
##### Create a tool from an instance method
When creating a tool from an instance method the method must be public and non-static.
```csharp
using AnthropicClient.Models;
class GetWeatherTool
{
public string GetWeather(string location)
{
return $"The weather in {location} is 72 degrees Fahrenheit";
}
}
var toolInstance = new GetWeatherTool();
var getWeatherTool = Tool.CreateFromInstanceMethod(
"Get Weather",
"Get the weather for a location in the specified units",
toolInstance,
nameof(toolInstance.GetWeather)
);
```
##### Create a tool from a delegate
When creating a tool from a delegate the delegate must be a `Func<TResult>`, `Func<T, TResult>`, or `Func<T1, T2, TResult>`. If you need to create a tool from a delegate that takes more than 2 parameters you should create a complex type and pass that as the parameter.
```csharp
using AnthropicClient.Models;
var tool = (string location, string units) => $"The weather in {location} is 72 degrees {units}";
var getWeatherTool = Tool.CreateFromFunction(
"Get Weather",
"Get the weather for a location in the specified units",
tool
);
```
##### Function Parameter Attribute
When you create a tool from one of the methods above and send it to Anthropic in your request a JSON representation of the tool is provided in the message. This JSON representation includes the name, description, and input schema of the tool. This information is used by Anthropic's models to discern if and when it should use a tool.
This library provides a `FunctionParameterAttribute` that can be used to provide additional information about the parameters of the tool. This information is used to provide a more detailed input schema for the tool.
```csharp
using AnthropicClient.Models;
var tool = (
[FunctionParameter(description: "The location of the weather being got", name: "Location", required: true)]
string location,
string units
) => $"The weather in {location} is 72 degrees {units}";
var getWeatherTool = Tool.CreateFromFunction(
"Get Weather",
"Get the weather for a location in the specified units",
tool
);
```
##### Function Property Attribute
This library also provides a `FunctionPropertyAttribute` that can be used to provide additional information about the members of complex types used as parameters in the tool. This information is used to provide a more detailed input schema for the tool.
```csharp
using AnthropicClient.Models;
class GetWeatherInput
{
[FunctionProperty(
description: "The location of the weather being got",
required: true
)]
public string Location { get; } = string.Empty;
[FunctionProperty(
description: "The units to get the weather in",
required: false,
defaultValue: "Fahrenheit",
possibleValues: ["Fahrenheit", "Celsius"]
)]
public string Units { get; } = "Fahrenheit";
}
var tool = (GetWeatherInput input) => $"The weather in {input.Location} is 72 degrees {input.Units}";
var getWeatherTool = Tool.CreateFromFunction(
"Get Weather",
"Get the weather for a location in the specified units",
tool
);
```
#### Call a tool
It is important to remember that while Anthropic's models do support tool use they don't actually have access to any built-in server-side tools. All tools are user provided. This means that while Anthropic's models can respond to a request to create a message with a request to use a tool that is all it is - a request. It is still up to the client to handle the tool request by calling the tool with the input provided by the model and then providing the result of that call back to the model.
This library aims to make this process convenient by allowing you to simply provide the tools you want Anthropic's models to consider for use when creating a message, receive the response, check if the response contains a tool call, and if it does invoke the tool to get the result.
> [!NOTE]
> Anthropic's API expects requests to contain messages that alternate between the user and the assistant. In addition if you receive a tool use from the model the API expects you to respond with a message that contains the result of the tool call. The tool use content will always be from the assistant while the tool result will always be from the user.
```csharp
using AnthropicClient;
using AnthropicClient.Models;
class GetWeatherTool : ITool
{
public string Name => "Get Weather";
public string Description => "Get the weather for a location in the specified units";
public MethodInfo Function => typeof(GetWeatherTool).GetMethod(nameof(GetWeather))!;
public static string GetWeather(string location, string units)
{
return $"The weather in {location} is 72 degrees {units}";
}
}
List<Message> messages = [
new(
MessageRole.User,
[new TextContent("What is the weather in New York?")]
)
];
List<Tool> tools = [Tool.CreateFromClass<GetWeatherTool>()];
var response = await client.CreateMessageAsync(new MessageRequest(
AnthropicModels.Claude3Haiku,
messages,
tools: tools
));
if (response.IsSuccess is false)
{
Console.WriteLine($"Failed to create message");
Console.WriteLine($"Error Type: {0}", response.Error.Error.Type);
Console.WriteLine($"Error Message: {0}", response.Error.Error.Message);
return;
}
foreach (var content in response.Value.Content)
{
messages.Add(new(MessageRole.Assistant, [content]));
switch (content)
{
case TextContent textContent:
Console.WriteLine(textContent.Text);
break;
case ToolUseContent toolUseContent:
Console.WriteLine(toolUseContent.Name);
break;
}
}
if (response.Value.ToolCall is not null)
{
var toolCallResult = await response.Value.ToolCall.InvokeAsync<string>();
string toolResultContent;
if (toolCallResult.IsSuccess && toolCallResult.Value is not null)
{
Console.WriteLine(toolCallResult.Value);
toolResultContent = toolCallResult.Value;
}
else
{
Console.WriteLine(toolCallResult.Error.Message);
toolResultContent = toolCallResult.Error.Message;
}
messages.Add(
new(
MessageRole.User,
[
new ToolResultContent(
response.Value.ToolCall.ToolUse.Id,
toolResultContent
)
]
)
);
}
var finalResponse = await client.CreateMessageAsync(new MessageRequest(
AnthropicModels.Claude3Haiku,
messages,
tools: tools
));
if (finalResponse.IsSuccess is false)
{
Console.WriteLine($"Failed to create message");
Console.WriteLine($"Error Type: {0}", finalResponse.Error.Error.Type);
Console.WriteLine($"Error Message: {0}", finalResponse.Error.Error.Message);
return;
}
foreach (var content in finalResponse.Value.Content)
{
switch (content)
{
case TextContent textContent:
Console.WriteLine(textContent.Text);
break;
}
}
```
If an exception is thrown while invoking the tool the `InvokeAsync` method will return a `ToolCallResult` with the exception contained in the `Error` property.
> [!NOTE]
> The `InvokeAsync` method does accept a generic type parameter that can be used to specify the type of the `Value` property of the `ToolCallResult`. If it is not specified it will be an `object`.
#### Call a tool in streamed message
Tool calling is also supported when streaming the message response. The following example demonstrates how you can handle a tool call in a streamed message response.
```csharp
using AnthropicClient;
using AnthropicClient.Models;
var tool = (string location, string units) => $"The weather in {location} is 72 degrees {units}";
var messages = [
new(
MessageRole.User,
[new TextContent("What is the weather in New York?")]
)
];
var tools = [Tool.CreateFromFunction(
"Get Weather",
"Get the weather for a location in the specified units",
tool
)];
var events = client.CreateMessageAsync(new StreamMessageRequest(
AnthropicModels.Claude3Haiku,
messages,
tools: tools
));
MessageResponse? response = null;
await foreach (var e in events)
{
switch (e.Data)
{
case var data when data is MessageCompleteEventData msgData:
response = msgData.Message;
break;
}
}
if (response is null)
{
Console.WriteLine("Failed to get message response");
return;
}
foreach (var content in response.Content)
{
messages.Add(new(MessageRole.Assistant, [content]));
}
if (response?.ToolCall is not null)
{
var toolCallResult = await response.ToolCall.InvokeAsync<string>();
string toolResultContent;
if (toolCallResult.IsSuccess && toolCallResult.Value is not null)
{
toolResultContent = toolCallResult.Value;
}
else
{
toolResultContent = toolCallResult.Error.Message;
}
messages.Add(
new(
MessageRole.User,
[
new ToolResultContent(
response.ToolCall.ToolUse.Id,
toolResultContent
)
]
)
);
}
var finalResponse = await client.CreateMessageAsync(new MessageRequest(
AnthropicModels.Claude3Haiku,
messages,
tools: tools
));
if (finalResponse.IsSuccess is false)
{
Console.WriteLine($"Failed to create message");
Console.WriteLine($"Error Type: {0}", finalResponse.Error.Error.Type);
Console.WriteLine($"Error Message: {0}", finalResponse.Error.Error.Message);
return;
}
foreach (var content in finalResponse.Value.Content)
{
switch (content)
{
case TextContent textContent:
Console.WriteLine(textContent.Text);
break;
}
}
```
If you do find that you need more control over how exactly provided tools are called and how the result of those tools are returned you can avoid using the `InvokeAsync` method and instead use the `Tool` and `ToolUse` properties of the `ToolCall` instance to implement your own solution.
+39 -39
View File
@@ -14,18 +14,18 @@ namespace AnthropicClient;
public interface IAnthropicApiClient public interface IAnthropicApiClient
{ {
/// <summary> /// <summary>
/// Creates a chat message asynchronously. /// Creates a message asynchronously.
/// </summary> /// </summary>
/// <param name="request">The chat message request to create.</param> /// <param name="request">The message request to create.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the chat response as an <see cref="AnthropicResult{T}"/>.</returns> /// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/>.</returns>
Task<AnthropicResult<ChatResponse>> CreateChatMessageAsync(ChatMessageRequest request); Task<AnthropicResult<MessageResponse>> CreateMessageAsync(MessageRequest request);
/// <summary> /// <summary>
/// Creates a chat message asynchronously and streams the response. /// Creates a message asynchronously and streams the response.
/// </summary> /// </summary>
/// <param name="request">The chat message request to create.</param> /// <param name="request">The message request to create.</param>
/// <returns>An asynchronous enumerable that yields the chat response line by line.</returns> /// <returns>An asynchronous enumerable that yields the response event by event.</returns>
IAsyncEnumerable<AnthropicEvent> CreateChatMessageAsync(StreamChatMessageRequest request); IAsyncEnumerable<AnthropicEvent> CreateMessageAsync(StreamMessageRequest request);
} }
/// <inheritdoc cref="IAnthropicApiClient"/> /// <inheritdoc cref="IAnthropicApiClient"/>
@@ -69,7 +69,7 @@ public class AnthropicApiClient : IAnthropicApiClient
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task<AnthropicResult<ChatResponse>> CreateChatMessageAsync(ChatMessageRequest request) public async Task<AnthropicResult<MessageResponse>> CreateMessageAsync(MessageRequest request)
{ {
var response = await SendRequestAsync(request); var response = await SendRequestAsync(request);
var anthropicHeaders = new AnthropicHeaders(response.Headers); var anthropicHeaders = new AnthropicHeaders(response.Headers);
@@ -78,21 +78,21 @@ public class AnthropicApiClient : IAnthropicApiClient
if (response.IsSuccessStatusCode is false) if (response.IsSuccessStatusCode is false)
{ {
var error = Deserialize<AnthropicError>(responseContent) ?? new AnthropicError(); var error = Deserialize<AnthropicError>(responseContent) ?? new AnthropicError();
return AnthropicResult<ChatResponse>.Failure(error, anthropicHeaders); return AnthropicResult<MessageResponse>.Failure(error, anthropicHeaders);
} }
var chatResponse = Deserialize<ChatResponse>(responseContent) ?? new ChatResponse(); var msgResponse = Deserialize<MessageResponse>(responseContent) ?? new MessageResponse();
if (request.Tools is not null && request.Tools.Count > 0) if (request.Tools is not null && request.Tools.Count > 0)
{ {
chatResponse.ToolCall = GetToolCall(chatResponse, request.Tools); msgResponse.ToolCall = GetToolCall(msgResponse, request.Tools);
} }
return AnthropicResult<ChatResponse>.Success(chatResponse, anthropicHeaders); return AnthropicResult<MessageResponse>.Success(msgResponse, anthropicHeaders);
} }
/// <inheritdoc /> /// <inheritdoc />
public async IAsyncEnumerable<AnthropicEvent> CreateChatMessageAsync(StreamChatMessageRequest request) public async IAsyncEnumerable<AnthropicEvent> CreateMessageAsync(StreamMessageRequest request)
{ {
var response = await SendRequestAsync(request); var response = await SendRequestAsync(request);
var anthropicHeaders = new AnthropicHeaders(response.Headers); var anthropicHeaders = new AnthropicHeaders(response.Headers);
@@ -100,7 +100,7 @@ public class AnthropicApiClient : IAnthropicApiClient
using var responseContent = await response.Content.ReadAsStreamAsync(); using var responseContent = await response.Content.ReadAsStreamAsync();
using var streamReader = new StreamReader(responseContent); using var streamReader = new StreamReader(responseContent);
ChatResponse? chatResponse = null; MessageResponse? msgResponse = null;
Content? content = null; Content? content = null;
var toolInputJsonStringBuilder = new StringBuilder(); var toolInputJsonStringBuilder = new StringBuilder();
var currentEvent = new AnthropicEvent(); var currentEvent = new AnthropicEvent();
@@ -111,14 +111,14 @@ public class AnthropicApiClient : IAnthropicApiClient
// I know...this is not pretty, but here is why... // I know...this is not pretty, but here is why...
// as events are being yielded I want to also // as events are being yielded I want to also
// build up the complete chat response // build up the complete response
// so I can yield it as a special event to make tool // so I can yield it as a special event to make tool
// calling easier to handle // calling easier to handle
// initialize chat response on message start // initialize response on message start
if (currentEvent.Type is EventType.MessageStart && currentEvent.Data is MessageStartEventData msgStartData) if (currentEvent.Type is EventType.MessageStart && currentEvent.Data is MessageStartEventData msgStartData)
{ {
chatResponse = msgStartData.Message; msgResponse = msgStartData.Message;
} }
// initialize content block on content block start // initialize content block on content block start
@@ -144,14 +144,14 @@ public class AnthropicApiClient : IAnthropicApiClient
} }
// finalize content block on content block stop // finalize content block on content block stop
// and add it to the chat response // and add it to the response
if (currentEvent.Type is EventType.ContentBlockStop) if (currentEvent.Type is EventType.ContentBlockStop)
{ {
if (content is not null && chatResponse is not null) if (content is not null && msgResponse is not null)
{ {
if (content is TextContent textContent) if (content is TextContent textContent)
{ {
chatResponse.Content.Add(textContent); msgResponse.Content.Add(textContent);
} }
if (content is ToolUseContent toolUseContent) if (content is ToolUseContent toolUseContent)
@@ -164,51 +164,51 @@ public class AnthropicApiClient : IAnthropicApiClient
Input = input!, Input = input!,
}; };
chatResponse.Content.Add(newToolUseContent); msgResponse.Content.Add(newToolUseContent);
} }
content = null; content = null;
} }
} }
// update chat response with message delta data // update response with message delta data
if ( if (
currentEvent.Type is EventType.MessageDelta && currentEvent.Type is EventType.MessageDelta &&
currentEvent.Data is MessageDeltaEventData msgDeltaData && currentEvent.Data is MessageDeltaEventData msgDeltaData &&
chatResponse is not null msgResponse is not null
) )
{ {
var existingUsage = chatResponse.Usage; var existingUsage = msgResponse.Usage;
var newUsage = new ChatUsage() var newUsage = new Usage()
{ {
InputTokens = existingUsage.InputTokens + msgDeltaData.Usage.InputTokens, InputTokens = existingUsage.InputTokens + msgDeltaData.Usage.InputTokens,
OutputTokens = existingUsage.OutputTokens + msgDeltaData.Usage.OutputTokens, OutputTokens = existingUsage.OutputTokens + msgDeltaData.Usage.OutputTokens,
}; };
chatResponse = new ChatResponse() msgResponse = new MessageResponse()
{ {
Id = chatResponse.Id, Id = msgResponse.Id,
Model = chatResponse.Model, Model = msgResponse.Model,
Role = chatResponse.Role, Role = msgResponse.Role,
StopReason = msgDeltaData.Delta.StopReason, StopReason = msgDeltaData.Delta.StopReason,
StopSequence = msgDeltaData.Delta.StopSequence, StopSequence = msgDeltaData.Delta.StopSequence,
Type = chatResponse.Type, Type = msgResponse.Type,
Usage = newUsage, Usage = newUsage,
Content = chatResponse.Content, Content = msgResponse.Content,
}; };
if (request.Tools is not null && request.Tools.Count > 0) if (request.Tools is not null && request.Tools.Count > 0)
{ {
chatResponse.ToolCall = GetToolCall(chatResponse, request.Tools); msgResponse.ToolCall = GetToolCall(msgResponse, request.Tools);
} }
} }
// yield chat response on message stop // yield response on message stop
if (currentEvent.Type is EventType.MessageStop && chatResponse is not null) if (currentEvent.Type is EventType.MessageStop && msgResponse is not null)
{ {
var eventData = new MessageCompleteEventData(chatResponse, anthropicHeaders); var eventData = new MessageCompleteEventData(msgResponse, anthropicHeaders);
yield return new AnthropicEvent(EventType.MessageComplete, eventData); yield return new AnthropicEvent(EventType.MessageComplete, eventData);
chatResponse = null; msgResponse = null;
} }
if (line is null) if (line is null)
@@ -245,7 +245,7 @@ public class AnthropicApiClient : IAnthropicApiClient
} while (true); } while (true);
} }
private ToolCall? GetToolCall(ChatResponse response, List<Tool> tools) private ToolCall? GetToolCall(MessageResponse response, List<Tool> tools)
{ {
var toolUse = response.Content.OfType<ToolUseContent>().FirstOrDefault(); var toolUse = response.Content.OfType<ToolUseContent>().FirstOrDefault();
@@ -264,7 +264,7 @@ public class AnthropicApiClient : IAnthropicApiClient
return new ToolCall(tool, toolUse); return new ToolCall(tool, toolUse);
} }
private async Task<HttpResponseMessage> SendRequestAsync(MessageRequest request) private async Task<HttpResponseMessage> SendRequestAsync(BaseMessageRequest request)
{ {
var requestContent = new StringContent(Serialize(request), Encoding.UTF8, JsonContentType); var requestContent = new StringContent(Serialize(request), Encoding.UTF8, JsonContentType);
return await _httpClient.PostAsync(MessagesEndpoint, requestContent); return await _httpClient.PostAsync(MessagesEndpoint, requestContent);
+1 -1
View File
@@ -15,7 +15,7 @@ public class AnthropicError
/// <summary> /// <summary>
/// The error object. /// The error object.
/// </summary> /// </summary>
public Error? Error { get; init; } = null; public Error Error { get; init; } = new ApiError();
[JsonConstructor] [JsonConstructor]
internal AnthropicError() internal AnthropicError()
+35 -2
View File
@@ -6,21 +6,54 @@ using AnthropicClient.Models;
/// <typeparam name="T">The type of the result value.</typeparam> /// <typeparam name="T">The type of the result value.</typeparam>
public class AnthropicResult<T> public class AnthropicResult<T>
{ {
private T _value = default!;
/// <summary> /// <summary>
/// The value of the result. /// The value of the result.
/// </summary> /// </summary>
public T Value { get; } /// <exception cref="InvalidOperationException">Thrown when the result is not successful.</exception>
public T Value
{
get
{
return IsSuccess ? _value : throw new InvalidOperationException("The result is not successful. Check the error property for more information.");
}
private set
{
_value = value;
}
}
private AnthropicError _error = default!;
/// <summary> /// <summary>
/// The error of the result. /// The error of the result.
/// </summary> /// </summary>
public AnthropicError Error { get; } /// <exception cref="InvalidOperationException">Thrown when the result is successful.</exception>
public AnthropicError Error
{
get
{
return IsSuccess ? throw new InvalidOperationException("The result is successful. Check the value property for more information.") : _error;
}
private set
{
_error = value;
}
}
/// <summary> /// <summary>
/// Indicates whether the operation was successful. /// Indicates whether the operation was successful.
/// </summary> /// </summary>
public bool IsSuccess { get; } public bool IsSuccess { get; }
/// <summary>
/// Indicates whether the operation failed.
/// </summary>
public bool IsFailure => !IsSuccess;
/// <summary> /// <summary>
/// The request ID of the operation. /// The request ID of the operation.
/// </summary> /// </summary>
@@ -0,0 +1,147 @@
using System.Text.Json.Serialization;
using AnthropicClient.Utils;
namespace AnthropicClient.Models;
/// <summary>
/// Represents a message request.
/// </summary>
public abstract class BaseMessageRequest
{
/// <summary>
/// Gets the model ID to use for the request.
/// </summary>
public string Model { get; init; } = string.Empty;
/// <summary>
/// Gets the system ID to use for the request.
/// </summary>
public string? System { get; init; } = null;
/// <summary>
/// Gets the messages to send to the model.
/// </summary>
public List<Message> Messages { get; init; } = [];
/// <summary>
/// Gets the maximum number of tokens to generate.
/// </summary>
[JsonPropertyName("max_tokens")]
public int MaxTokens { get; init; } = 1024;
/// <summary>
/// Gets the metadata to include with the request.
/// </summary>
public Dictionary<string, object>? Metadata { get; init; } = null;
/// <summary>
/// Gets the prompt stop sequences.
/// </summary>
[JsonPropertyName("stop_sequences")]
public List<string> StopSequences { get; init; } = [];
/// <summary>
/// Gets the temperature to use for the request.
/// </summary>
public decimal Temperature { get; init; } = 0.0m;
/// <summary>
/// Gets the top-K value to use for the request.
/// </summary>
public int? TopK { get; init; } = null;
/// <summary>
/// Gets the top-P value to use for the request.
/// </summary>
public decimal? TopP { get; init; } = null;
/// <summary>
/// Gets the tool choice mode to use for the request.
/// </summary>
[JsonPropertyName("tool_choice")]
public ToolChoice? ToolChoice { get; init; } = null;
/// <summary>
/// Gets the tools to use for the request.
/// </summary>
public List<Tool>? Tools { get; init; } = null;
/// <summary>
/// Gets a value indicating whether the message should be streamed.
/// </summary>
public bool Stream { get; init; }
[JsonConstructor]
internal BaseMessageRequest() { }
/// <summary>
/// Initializes a new instance of the <see cref="BaseMessageRequest"/> class.
/// </summary>
/// <param name="model">The model ID to use for the request.</param>
/// <param name="messages">The messages to send to the model.</param>
/// <param name="maxTokens">The maximum number of tokens to generate.</param>
/// <param name="system">The system ID to use for the request.</param>
/// <param name="metadata">The metadata to include with the request.</param>
/// <param name="temperature">The temperature to use for the request.</param>
/// <param name="topK">The top-K value to use for the request.</param>
/// <param name="topP">The top-P value to use for the request.</param>
/// <param name="toolChoice">The tool choice mode to use for the request.</param>
/// <param name="tools">The tools to use for the request.</param>
/// <param name="stream">A value indicating whether the message should be streamed.</param>
/// <exception cref="ArgumentException">Thrown when the model ID is invalid.</exception>
/// <exception cref="ArgumentNullException">Thrown when the model or messages is null.</exception>
/// <exception cref="ArgumentException">Thrown when the messages contain no messages.</exception>
/// <exception cref="ArgumentException">Thrown when the max tokens is less than one.</exception>
/// <exception cref="ArgumentException">Thrown when the temperature is less than zero or greater than one.</exception>
/// <returns>A new instance of the <see cref="BaseMessageRequest"/> class.</returns>
protected BaseMessageRequest(
string model,
List<Message> messages,
int maxTokens = 1024,
string? system = null,
Dictionary<string, object>? metadata = null,
decimal temperature = 0.0m,
int? topK = null,
decimal? topP = null,
ToolChoice? toolChoice = null,
List<Tool>? tools = null,
bool stream = false
)
{
ArgumentValidator.ThrowIfNull(model, nameof(model));
ArgumentValidator.ThrowIfNull(messages, nameof(messages));
if (AnthropicModels.IsValidModel(model) is false)
{
throw new ArgumentException($"Invalid model ID: {model}");
}
if (messages.Count < 1)
{
throw new ArgumentException("Messages must contain at least one message");
}
if (maxTokens < 1)
{
throw new ArgumentException($"Invalid max tokens: {maxTokens}");
}
if (temperature < 0.0m || temperature > 1.0m)
{
throw new ArgumentException($"Invalid temperature: {temperature}");
}
Model = model;
Messages = messages;
MaxTokens = maxTokens;
System = system;
Metadata = metadata;
Temperature = temperature;
TopK = topK;
TopP = topP;
ToolChoice = toolChoice;
Tools = tools;
Stream = stream;
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ using System.Text.Json.Serialization;
namespace AnthropicClient.Models; namespace AnthropicClient.Models;
/// <summary> /// <summary>
/// Represents part of the content of a chat message. /// Represents part of the content of a message.
/// </summary> /// </summary>
public abstract class Content public abstract class Content
{ {
+1 -1
View File
@@ -5,7 +5,7 @@ using AnthropicClient.Utils;
namespace AnthropicClient.Models; namespace AnthropicClient.Models;
/// <summary> /// <summary>
/// Represents image content that is part of a chat message. /// Represents image content that is part of a message.
/// </summary> /// </summary>
public class ImageContent : Content public class ImageContent : Content
{ {
@@ -5,9 +5,9 @@ using AnthropicClient.Utils;
namespace AnthropicClient.Models; namespace AnthropicClient.Models;
/// <summary> /// <summary>
/// Represents a chat message. /// Represents a message.
/// </summary> /// </summary>
public class ChatMessage public class Message
{ {
/// <summary> /// <summary>
/// Gets the role of the message. /// Gets the role of the message.
@@ -20,19 +20,19 @@ public class ChatMessage
public List<Content> Content { get; init; } = []; public List<Content> Content { get; init; } = [];
[JsonConstructor] [JsonConstructor]
internal ChatMessage() internal Message()
{ {
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ChatMessage"/> class. /// Initializes a new instance of the <see cref="Message"/> class.
/// </summary> /// </summary>
/// <param name="role">The role of the message.</param> /// <param name="role">The role of the message.</param>
/// <param name="content">The contents of the message.</param> /// <param name="content">The contents of the message.</param>
/// <exception cref="ArgumentException">Thrown when the role is invalid.</exception> /// <exception cref="ArgumentException">Thrown when the role is invalid.</exception>
/// <exception cref="ArgumentNullException">Thrown when the role or content is null.</exception> /// <exception cref="ArgumentNullException">Thrown when the role or content is null.</exception>
/// <returns>A new instance of the <see cref="ChatMessage"/> class.</returns> /// <returns>A new instance of the <see cref="Message"/> class.</returns>
public ChatMessage(string role, List<Content> content) public Message(string role, List<Content> content)
{ {
ArgumentValidator.ThrowIfNull(role, nameof(role)); ArgumentValidator.ThrowIfNull(role, nameof(role));
ArgumentValidator.ThrowIfNull(content, nameof(content)); ArgumentValidator.ThrowIfNull(content, nameof(content));
@@ -11,17 +11,17 @@ public class MessageCompleteEventData : EventData
public AnthropicHeaders Headers { get; init; } public AnthropicHeaders Headers { get; init; }
/// <summary> /// <summary>
/// Gets the chat response message. /// Gets the response message.
/// </summary> /// </summary>
public ChatResponse Message { get; init; } public MessageResponse Message { get; init; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="MessageCompleteEventData"/> class. /// Initializes a new instance of the <see cref="MessageCompleteEventData"/> class.
/// </summary> /// </summary>
/// <param name="message">The chat response message.</param> /// <param name="message">The response message.</param>
/// <param name="headers">The anthropic headers.</param> /// <param name="headers">The anthropic headers.</param>
/// <returns>A new instance of the <see cref="MessageCompleteEventData"/> class.</returns> /// <returns>A new instance of the <see cref="MessageCompleteEventData"/> class.</returns>
public MessageCompleteEventData(ChatResponse message, AnthropicHeaders headers) : base(EventType.MessageComplete) public MessageCompleteEventData(MessageResponse message, AnthropicHeaders headers) : base(EventType.MessageComplete)
{ {
Message = message; Message = message;
Headers = headers; Headers = headers;
@@ -13,9 +13,9 @@ public class MessageDeltaEventData : EventData
public MessageDelta Delta { get; init; } = new(); public MessageDelta Delta { get; init; } = new();
/// <summary> /// <summary>
/// Gets the chat usage. /// Gets the usage.
/// </summary> /// </summary>
public ChatUsage Usage { get; init; } = new(); public Usage Usage { get; init; } = new();
[JsonConstructor] [JsonConstructor]
internal MessageDeltaEventData() : base(EventType.MessageDelta) internal MessageDeltaEventData() : base(EventType.MessageDelta)
@@ -26,9 +26,9 @@ public class MessageDeltaEventData : EventData
/// Initializes a new instance of the <see cref="MessageDeltaEventData"/> class. /// Initializes a new instance of the <see cref="MessageDeltaEventData"/> class.
/// </summary> /// </summary>
/// <param name="delta">The message delta.</param> /// <param name="delta">The message delta.</param>
/// <param name="usage">The chat usage.</param> /// <param name="usage">The usage.</param>
/// <returns>A new instance of the <see cref="MessageDeltaEventData"/> class.</returns> /// <returns>A new instance of the <see cref="MessageDeltaEventData"/> class.</returns>
public MessageDeltaEventData(MessageDelta delta, ChatUsage usage) : base(EventType.MessageDelta) public MessageDeltaEventData(MessageDelta delta, Usage usage) : base(EventType.MessageDelta)
{ {
Delta = delta; Delta = delta;
Usage = usage; Usage = usage;
+17 -106
View File
@@ -1,79 +1,14 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using AnthropicClient.Utils;
namespace AnthropicClient.Models; namespace AnthropicClient.Models;
/// <summary> /// <summary>
/// Represents a message request. /// Represents a message request.
/// </summary> /// </summary>
public abstract class MessageRequest public class MessageRequest : BaseMessageRequest
{ {
/// <summary>
/// Gets the model ID to use for the request.
/// </summary>
public string Model { get; init; } = string.Empty;
/// <summary>
/// Gets the system ID to use for the request.
/// </summary>
public string? System { get; init; } = null;
/// <summary>
/// Gets the messages to send to the model.
/// </summary>
public List<ChatMessage> Messages { get; init; } = [];
/// <summary>
/// Gets the maximum number of tokens to generate.
/// </summary>
[JsonPropertyName("max_tokens")]
public int MaxTokens { get; init; } = 1024;
/// <summary>
/// Gets the metadata to include with the request.
/// </summary>
public Dictionary<string, object>? Metadata { get; init; } = null;
/// <summary>
/// Gets the prompt stop sequences.
/// </summary>
[JsonPropertyName("stop_sequences")]
public List<string> StopSequences { get; init; } = [];
/// <summary>
/// Gets the temperature to use for the request.
/// </summary>
public decimal Temperature { get; init; } = 0.0m;
/// <summary>
/// Gets the top-K value to use for the request.
/// </summary>
public int? TopK { get; init; } = null;
/// <summary>
/// Gets the top-P value to use for the request.
/// </summary>
public decimal? TopP { get; init; } = null;
/// <summary>
/// Gets the tool choice mode to use for the request.
/// </summary>
[JsonPropertyName("tool_choice")]
public ToolChoice? ToolChoice { get; init; } = null;
/// <summary>
/// Gets the tools to use for the request.
/// </summary>
public List<Tool>? Tools { get; init; } = null;
/// <summary>
/// Gets a value indicating whether the message should be streamed.
/// </summary>
public bool Stream { get; init; }
[JsonConstructor] [JsonConstructor]
internal MessageRequest() { } internal MessageRequest() : base() { }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="MessageRequest"/> class. /// Initializes a new instance of the <see cref="MessageRequest"/> class.
@@ -88,16 +23,15 @@ public abstract class MessageRequest
/// <param name="topP">The top-P value to use for the request.</param> /// <param name="topP">The top-P value to use for the request.</param>
/// <param name="toolChoice">The tool choice mode to use for the request.</param> /// <param name="toolChoice">The tool choice mode to use for the request.</param>
/// <param name="tools">The tools to use for the request.</param> /// <param name="tools">The tools to use for the request.</param>
/// <param name="stream">A value indicating whether the message should be streamed.</param>
/// <exception cref="ArgumentException">Thrown when the model ID is invalid.</exception> /// <exception cref="ArgumentException">Thrown when the model ID is invalid.</exception>
/// <exception cref="ArgumentNullException">Thrown when the model or messages is null.</exception> /// <exception cref="ArgumentNullException">Thrown when the model or messages is null.</exception>
/// <exception cref="ArgumentException">Thrown when the messages contain no messages.</exception> /// <exception cref="ArgumentException">Thrown when the messages contain no messages.</exception>
/// <exception cref="ArgumentException">Thrown when the max tokens is less than one.</exception> /// <exception cref="ArgumentException">Thrown when the max tokens is less than one.</exception>
/// <exception cref="ArgumentException">Thrown when the temperature is less than zero or greater than one.</exception> /// <exception cref="ArgumentException">Thrown when the temperature is less than zero or greater than one.</exception>
/// <returns>A new instance of the <see cref="MessageRequest"/> class.</returns> /// <returns>A new instance of the <see cref="MessageRequest"/> class.</returns>
protected MessageRequest( public MessageRequest(
string model, string model,
List<ChatMessage> messages, List<Message> messages,
int maxTokens = 1024, int maxTokens = 1024,
string? system = null, string? system = null,
Dictionary<string, object>? metadata = null, Dictionary<string, object>? metadata = null,
@@ -105,43 +39,20 @@ public abstract class MessageRequest
int? topK = null, int? topK = null,
decimal? topP = null, decimal? topP = null,
ToolChoice? toolChoice = null, ToolChoice? toolChoice = null,
List<Tool>? tools = null, List<Tool>? tools = null
bool stream = false ) : base(
model,
messages,
maxTokens,
system,
metadata,
temperature,
topK,
topP,
toolChoice,
tools,
false
) )
{ {
ArgumentValidator.ThrowIfNull(model, nameof(model));
ArgumentValidator.ThrowIfNull(messages, nameof(messages));
if (AnthropicModels.IsValidModel(model) is false)
{
throw new ArgumentException($"Invalid model ID: {model}");
}
if (messages.Count < 1)
{
throw new ArgumentException("Messages must contain at least one message");
}
if (maxTokens < 1)
{
throw new ArgumentException($"Invalid max tokens: {maxTokens}");
}
if (temperature < 0.0m || temperature > 1.0m)
{
throw new ArgumentException($"Invalid temperature: {temperature}");
}
Model = model;
Messages = messages;
MaxTokens = maxTokens;
System = system;
Metadata = metadata;
Temperature = temperature;
TopK = topK;
TopP = topP;
ToolChoice = toolChoice;
Tools = tools;
Stream = stream;
} }
} }
@@ -3,54 +3,54 @@ using System.Text.Json.Serialization;
namespace AnthropicClient.Models; namespace AnthropicClient.Models;
/// <summary> /// <summary>
/// Represents a chat response. /// Represents a response.
/// </summary> /// </summary>
public class ChatResponse public class MessageResponse
{ {
/// <summary> /// <summary>
/// Gets the ID of the chat response. /// Gets the ID of the response.
/// </summary> /// </summary>
public string Id { get; init; } = string.Empty; public string Id { get; init; } = string.Empty;
/// <summary> /// <summary>
/// Gets the model used for the chat response. /// Gets the model used for the response.
/// </summary> /// </summary>
public string Model { get; init; } = string.Empty; public string Model { get; init; } = string.Empty;
/// <summary> /// <summary>
/// Gets the role of the chat response. /// Gets the role of the response.
/// </summary> /// </summary>
public string Role { get; init; } = string.Empty; public string Role { get; init; } = string.Empty;
/// <summary> /// <summary>
/// Gets the stop reason of the chat response. /// Gets the stop reason of the response.
/// </summary> /// </summary>
[JsonPropertyName("stop_reason")] [JsonPropertyName("stop_reason")]
public string? StopReason { get; init; } public string? StopReason { get; init; }
/// <summary> /// <summary>
/// Gets the stop sequence of the chat response. /// Gets the stop sequence of the response.
/// </summary> /// </summary>
[JsonPropertyName("stop_sequence")] [JsonPropertyName("stop_sequence")]
public string? StopSequence { get; init; } public string? StopSequence { get; init; }
/// <summary> /// <summary>
/// Gets the type of the chat response. /// Gets the type of the response.
/// </summary> /// </summary>
public string Type { get; init; } = string.Empty; public string Type { get; init; } = string.Empty;
/// <summary> /// <summary>
/// Gets the usage of the chat response. /// Gets the usage of the response.
/// </summary> /// </summary>
public ChatUsage Usage { get; init; } = new(); public Usage Usage { get; init; } = new();
/// <summary> /// <summary>
/// Gets the contents of the chat response. /// Gets the contents of the response.
/// </summary> /// </summary>
public List<Content> Content { get; init; } = []; public List<Content> Content { get; init; } = [];
/// <summary> /// <summary>
/// Gets the tool call of the chat response. If the chat response does not contain a tool call, this property is null. /// Gets the tool call of the response. If the response does not contain a tool call, this property is null.
/// </summary> /// </summary>
[JsonIgnore] [JsonIgnore]
public ToolCall? ToolCall { get; set; } = null; public ToolCall? ToolCall { get; set; } = null;
@@ -10,7 +10,7 @@ public class MessageStartEventData : EventData
/// <summary> /// <summary>
/// Gets the message. /// Gets the message.
/// </summary> /// </summary>
public ChatResponse Message { get; init; } = new(); public MessageResponse Message { get; init; } = new();
[JsonConstructor] [JsonConstructor]
internal MessageStartEventData() : base(EventType.MessageStart) internal MessageStartEventData() : base(EventType.MessageStart)
@@ -22,7 +22,7 @@ public class MessageStartEventData : EventData
/// </summary> /// </summary>
/// <param name="message">The message.</param> /// <param name="message">The message.</param>
/// <returns>A new instance of the <see cref="MessageStartEventData"/> class.</returns> /// <returns>A new instance of the <see cref="MessageStartEventData"/> class.</returns>
public MessageStartEventData(ChatResponse message) : base(EventType.MessageStart) public MessageStartEventData(MessageResponse message) : base(EventType.MessageStart)
{ {
Message = message; Message = message;
} }
@@ -1,58 +0,0 @@
using System.Text.Json.Serialization;
namespace AnthropicClient.Models;
/// <summary>
/// Represents a chat message request.
/// </summary>
public class StreamChatMessageRequest : MessageRequest
{
[JsonConstructor]
internal StreamChatMessageRequest() : base() { }
/// <summary>
/// Initializes a new instance of the <see cref="StreamChatMessageRequest"/> class.
/// </summary>
/// <param name="model">The model ID to use for the request.</param>
/// <param name="messages">The messages to send to the model.</param>
/// <param name="maxTokens">The maximum number of tokens to generate.</param>
/// <param name="system">The system ID to use for the request.</param>
/// <param name="metadata">The metadata to include with the request.</param>
/// <param name="temperature">The temperature to use for the request.</param>
/// <param name="topK">The top-K value to use for the request.</param>
/// <param name="topP">The top-P value to use for the request.</param>
/// <param name="toolChoice">The tool choice mode to use for the request.</param>
/// <param name="tools">The tools to use for the request.</param>
/// <exception cref="ArgumentException">Thrown when the model ID is invalid.</exception>
/// <exception cref="ArgumentNullException">Thrown when the model or messages is null.</exception>
/// <exception cref="ArgumentException">Thrown when the messages contain no messages.</exception>
/// <exception cref="ArgumentException">Thrown when the max tokens is less than one.</exception>
/// <exception cref="ArgumentException">Thrown when the temperature is less than zero or greater than one.</exception>
/// <returns>A new instance of the <see cref="StreamChatMessageRequest"/> class.</returns>
public StreamChatMessageRequest(
string model,
List<ChatMessage> messages,
int maxTokens = 1024,
string? system = null,
Dictionary<string, object>? metadata = null,
decimal temperature = 0.0m,
int? topK = null,
decimal? topP = null,
ToolChoice? toolChoice = null,
List<Tool>? tools = null
) : base(
model,
messages,
maxTokens,
system,
metadata,
temperature,
topK,
topP,
toolChoice,
tools,
true
)
{
}
}
@@ -3,15 +3,15 @@ using System.Text.Json.Serialization;
namespace AnthropicClient.Models; namespace AnthropicClient.Models;
/// <summary> /// <summary>
/// Represents a chat message request. /// Represents a message request.
/// </summary> /// </summary>
public class ChatMessageRequest : MessageRequest public class StreamMessageRequest : BaseMessageRequest
{ {
[JsonConstructor] [JsonConstructor]
internal ChatMessageRequest() : base() { } internal StreamMessageRequest() : base() { }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ChatMessageRequest"/> class. /// Initializes a new instance of the <see cref="StreamMessageRequest"/> class.
/// </summary> /// </summary>
/// <param name="model">The model ID to use for the request.</param> /// <param name="model">The model ID to use for the request.</param>
/// <param name="messages">The messages to send to the model.</param> /// <param name="messages">The messages to send to the model.</param>
@@ -28,10 +28,10 @@ public class ChatMessageRequest : MessageRequest
/// <exception cref="ArgumentException">Thrown when the messages contain no messages.</exception> /// <exception cref="ArgumentException">Thrown when the messages contain no messages.</exception>
/// <exception cref="ArgumentException">Thrown when the max tokens is less than one.</exception> /// <exception cref="ArgumentException">Thrown when the max tokens is less than one.</exception>
/// <exception cref="ArgumentException">Thrown when the temperature is less than zero or greater than one.</exception> /// <exception cref="ArgumentException">Thrown when the temperature is less than zero or greater than one.</exception>
/// <returns>A new instance of the <see cref="ChatMessageRequest"/> class.</returns> /// <returns>A new instance of the <see cref="StreamMessageRequest"/> class.</returns>
public ChatMessageRequest( public StreamMessageRequest(
string model, string model,
List<ChatMessage> messages, List<Message> messages,
int maxTokens = 1024, int maxTokens = 1024,
string? system = null, string? system = null,
Dictionary<string, object>? metadata = null, Dictionary<string, object>? metadata = null,
@@ -51,7 +51,7 @@ public class ChatMessageRequest : MessageRequest
topP, topP,
toolChoice, toolChoice,
tools, tools,
false true
) )
{ {
} }
+1 -1
View File
@@ -5,7 +5,7 @@ using AnthropicClient.Utils;
namespace AnthropicClient.Models; namespace AnthropicClient.Models;
/// <summary> /// <summary>
/// Represents text content that is part of a chat message. /// Represents text content that is part of a message.
/// </summary> /// </summary>
public class TextContent : Content public class TextContent : Content
{ {
+2 -2
View File
@@ -29,7 +29,7 @@ public interface ITool
} }
/// <summary> /// <summary>
/// Represents a tool that can be used in the chat. /// Represents a tool that can be used.
/// </summary> /// </summary>
public class Tool public class Tool
{ {
@@ -107,7 +107,7 @@ public class Tool
ArgumentValidator.ThrowIfNullOrWhitespace(tool.Description, nameof(tool.Description)); ArgumentValidator.ThrowIfNullOrWhitespace(tool.Description, nameof(tool.Description));
ArgumentValidator.ThrowIfNull(tool.Function, nameof(tool.Function)); ArgumentValidator.ThrowIfNull(tool.Function, nameof(tool.Function));
return new Tool(tool.Name, tool.Description, new AnthropicFunction(tool.Function)); return new Tool(tool.Name, tool.Description, new AnthropicFunction(tool.Function, tool));
} }
/// <summary> /// <summary>
-1
View File
@@ -1,6 +1,5 @@
using System.Reflection; using System.Reflection;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks;
using AnthropicClient.Json; using AnthropicClient.Json;
+35 -2
View File
@@ -5,21 +5,54 @@ namespace AnthropicClient.Models;
/// </summary> /// </summary>
public class ToolCallResult<T> public class ToolCallResult<T>
{ {
private T? _value = default!;
/// <summary> /// <summary>
/// The value of the tool call result. Can be null if the call failed, the call was successful but the return type is void or Task, or the call was successful but the return value is null /// The value of the tool call result. Can be null if the call failed, the call was successful but the return type is void or Task, or the call was successful but the return value is null
/// </summary> /// </summary>
public T? Value { get; } /// <exception cref="InvalidOperationException">Thrown when the result is not successful.</exception>
public T? Value
{
get
{
return IsSuccess ? _value : throw new InvalidOperationException("The result is not successful. Check the error property for more information.");
}
private set
{
_value = value;
}
}
private Exception _error = default!;
/// <summary> /// <summary>
/// The error of the tool call result. /// The error of the tool call result.
/// </summary> /// </summary>
public Exception Error { get; } /// <exception cref="InvalidOperationException">Thrown when the result is successful.</exception>
public Exception Error
{
get
{
return IsSuccess ? throw new InvalidOperationException("The result is successful. Check the value property for more information.") : _error;
}
private set
{
_error = value;
}
}
/// <summary> /// <summary>
/// Indicates whether the tool call was successful. /// Indicates whether the tool call was successful.
/// </summary> /// </summary>
public bool IsSuccess { get; } public bool IsSuccess { get; }
/// <summary>
/// Indicates whether the tool call failed.
/// </summary>
public bool IsFailure => !IsSuccess;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ToolCallResult{T}"/> class. /// Initializes a new instance of the <see cref="ToolCallResult{T}"/> class.
/// </summary> /// </summary>
@@ -5,7 +5,7 @@ using AnthropicClient.Utils;
namespace AnthropicClient.Models; namespace AnthropicClient.Models;
/// <summary> /// <summary>
/// Represents tool result content that is part of a chat message. /// Represents tool result content that is part of a message.
/// </summary> /// </summary>
public class ToolResultContent : Content public class ToolResultContent : Content
{ {
@@ -3,9 +3,9 @@ using System.Text.Json.Serialization;
namespace AnthropicClient.Models; namespace AnthropicClient.Models;
/// <summary> /// <summary>
/// Represents the usage of a chat response. /// Represents the usage of a response.
/// </summary> /// </summary>
public class ChatUsage public class Usage
{ {
/// <summary> /// <summary>
/// Gets the number of input tokens used. /// Gets the number of input tokens used.
+11
View File
@@ -0,0 +1,11 @@
<RunSettings>
<LoggerRunSettings>
<Loggers>
<Logger friendlyName="console" enabled="True">
<Configuration>
<Verbosity>quiet</Verbosity>
</Configuration>
</Logger>
</Loggers>
</LoggerRunSettings>
</RunSettings>
@@ -1,4 +1,4 @@
namespace AnthropicClient.Tests.Integration; namespace AnthropicClient.Tests.Data;
public class ErrorTestData : IEnumerable<object[]> public class ErrorTestData : IEnumerable<object[]>
{ {
@@ -1,4 +1,4 @@
namespace AnthropicClient.Tests.Integration; namespace AnthropicClient.Tests.Data;
public class EventTestData : IEnumerable<object[]> public class EventTestData : IEnumerable<object[]>
{ {
@@ -21,7 +21,7 @@ public class EventTestData : IEnumerable<object[]>
Role = MessageRole.Assistant, Role = MessageRole.Assistant,
Model = AnthropicModels.Claude3Haiku, Model = AnthropicModels.Claude3Haiku,
StopSequence = null, StopSequence = null,
Usage = new ChatUsage { InputTokens = 472, OutputTokens = 91 }, Usage = new Usage { InputTokens = 472, OutputTokens = 91 },
StopReason = "tool_use", StopReason = "tool_use",
Content = [ Content = [
new TextContent("Okay, let's check the weather for San Francisco, CA:"), new TextContent("Okay, let's check the weather for San Francisco, CA:"),
@@ -54,14 +54,14 @@ public class EventTestData : IEnumerable<object[]>
Type = EventType.MessageStart, Type = EventType.MessageStart,
Data = new MessageStartEventData() Data = new MessageStartEventData()
{ {
Message = new ChatResponse() Message = new MessageResponse()
{ {
Id = "msg_014p7gG3wDgGV9EUtLvnow3U", Id = "msg_014p7gG3wDgGV9EUtLvnow3U",
Type = "message", Type = "message",
Role = "assistant", Role = "assistant",
Model = "claude-3-haiku-20240307", Model = "claude-3-haiku-20240307",
StopSequence = null, StopSequence = null,
Usage = new ChatUsage() Usage = new Usage()
{ {
InputTokens = 472, InputTokens = 472,
OutputTokens = 2, OutputTokens = 2,
@@ -640,7 +640,7 @@ public class EventTestData : IEnumerable<object[]>
StopReason = "tool_use", StopReason = "tool_use",
StopSequence = null, StopSequence = null,
}, },
Usage = new ChatUsage() Usage = new Usage()
{ {
OutputTokens = 89, OutputTokens = 89,
}, },
@@ -1,6 +1,4 @@
using System.Text.Json.Nodes; namespace AnthropicClient.Tests.Data;
namespace AnthropicClient.Tests.Unit.Utils;
public class JsonSchemaGeneratorTestData : IEnumerable<object[]> public class JsonSchemaGeneratorTestData : IEnumerable<object[]>
{ {
@@ -340,7 +338,7 @@ public class JsonSchemaGeneratorTestData : IEnumerable<object[]>
} }
}; };
// datetime parameter type // date time parameter type
yield return new object[] yield return new object[]
{ {
Tool.CreateFromFunction(TestToolName,TestToolDescription,(DateTime date) => date), Tool.CreateFromFunction(TestToolName,TestToolDescription,(DateTime date) => date),
@@ -363,7 +361,7 @@ public class JsonSchemaGeneratorTestData : IEnumerable<object[]>
} }
}; };
// datetimeoffset parameter type // date time offset parameter type
yield return new object[] yield return new object[]
{ {
Tool.CreateFromFunction(TestToolName,TestToolDescription,(DateTimeOffset date) => date), Tool.CreateFromFunction(TestToolName,TestToolDescription,(DateTimeOffset date) => date),
@@ -3,29 +3,29 @@ namespace AnthropicClient.Tests.EndToEnd;
public class ClientTests(ConfigurationFixture configFixture) : EndToEndTest(configFixture) public class ClientTests(ConfigurationFixture configFixture) : EndToEndTest(configFixture)
{ {
[Fact] [Fact]
public async Task CreateChatMessage_WhenCalled_ItShouldReturnChatResponse() public async Task CreateMessageAsync_WhenCalled_ItShouldReturnResponse()
{ {
var request = new ChatMessageRequest( var request = new MessageRequest(
model: AnthropicModels.Claude3Haiku, model: AnthropicModels.Claude3Haiku,
messages: [new(MessageRole.User, [new TextContent("Hello!")])] messages: [new(MessageRole.User, [new TextContent("Hello!")])]
); );
var result = await _client.CreateChatMessageAsync(request); var result = await _client.CreateMessageAsync(request);
result.IsSuccess.Should().BeTrue(); result.IsSuccess.Should().BeTrue();
result.Value.Should().BeOfType<ChatResponse>(); result.Value.Should().BeOfType<MessageResponse>();
result.Value.Content.Should().NotBeNullOrEmpty(); result.Value.Content.Should().NotBeNullOrEmpty();
} }
[Fact] [Fact]
public async Task CreateChatMessage_WhenCalledWithStreamRequest_ItShouldReturnEvents() public async Task CreateMessageAsync_WhenCalledWithStreamRequest_ItShouldReturnEvents()
{ {
var request = new StreamChatMessageRequest( var request = new StreamMessageRequest(
model: AnthropicModels.Claude3Haiku, model: AnthropicModels.Claude3Haiku,
messages: [new(MessageRole.User, [new TextContent("Hello!")])] messages: [new(MessageRole.User, [new TextContent("Hello!")])]
); );
var response = _client.CreateChatMessageAsync(request); var response = _client.CreateMessageAsync(request);
var events = new List<AnthropicEvent>(); var events = new List<AnthropicEvent>();
@@ -38,14 +38,14 @@ public class ClientTests(ConfigurationFixture configFixture) : EndToEndTest(conf
} }
[Fact] [Fact]
public async Task CreateChatMessage_WhenCalledWithStreamRequest_ItShouldYieldAMessageCompleteEvent() public async Task CreateMessageAsync_WhenCalledWithStreamRequest_ItShouldYieldAMessageCompleteEvent()
{ {
var request = new StreamChatMessageRequest( var request = new StreamMessageRequest(
model: AnthropicModels.Claude3Haiku, model: AnthropicModels.Claude3Haiku,
messages: [new(MessageRole.User, [new TextContent("Hello!")])] messages: [new(MessageRole.User, [new TextContent("Hello!")])]
); );
var response = _client.CreateChatMessageAsync(request); var response = _client.CreateMessageAsync(request);
await foreach (var e in response) await foreach (var e in response)
{ {
@@ -1,11 +0,0 @@
namespace AnthropicClient.Tests.EndToEnd;
public class HttpClientFixture
{
public HttpClient HttpClient { get; }
public HttpClientFixture()
{
HttpClient = new HttpClient();
}
}
@@ -0,0 +1,14 @@
namespace AnthropicClient.Tests.Examples;
public class ExampleAttribute : FactAttribute
{
public ExampleAttribute()
{
var skipExamples = Environment.GetEnvironmentVariable("SKIP_EXAMPLES");
if (skipExamples == "true")
{
Skip = "Example";
}
}
}
@@ -0,0 +1,608 @@
#pragma warning disable xUnit1004
using System.Reflection;
using Xunit.Abstractions;
namespace AnthropicClient.Tests.Examples;
public class Examples(ConfigurationFixture config, ITestOutputHelper console) : IClassFixture<ConfigurationFixture>
{
private readonly ITestOutputHelper _console = console;
private readonly AnthropicApiClient _client = new(config.AnthropicApiKey, new());
[Example]
public async Task CreateMessage()
{
var response = await _client.CreateMessageAsync(new MessageRequest(
AnthropicModels.Claude3Haiku,
[
new(
MessageRole.User,
[new TextContent("Please write a haiku about the ocean.")]
)
]
));
if (response.IsSuccess is false)
{
_console.WriteLine($"Failed to create message");
_console.WriteLine($"Error Type: {0}", response.Error.Error.Type);
_console.WriteLine($"Error Message: {0}", response.Error.Error.Message);
return;
}
foreach (var content in response.Value.Content)
{
switch (content)
{
case TextContent textContent:
_console.WriteLine(textContent.Text);
break;
}
}
}
[Example]
public async Task CreateAndStreamMessage()
{
var events = _client.CreateMessageAsync(new StreamMessageRequest(
AnthropicModels.Claude3Haiku,
[
new(
MessageRole.User,
[new TextContent("Please write a haiku about the ocean.")]
)
]
));
var msgBuilder = new StringBuilder();
await foreach (var e in events)
{
switch (e.Data)
{
case var data when data is ContentDeltaEventData contentData:
switch (contentData.Delta)
{
case var delta when delta is TextDelta textDelta:
msgBuilder.Append(textDelta.Text);
break;
}
break;
}
}
_console.WriteLine(msgBuilder.ToString());
}
[Example]
public async Task CreateStreamMessageAndGetCompleteMessageResponse()
{
var events = _client.CreateMessageAsync(new StreamMessageRequest(
AnthropicModels.Claude3Haiku,
[
new(
MessageRole.User,
[new TextContent("Please write a haiku about the ocean.")]
)
]
));
MessageResponse? response = null;
await foreach (var e in events)
{
switch (e.Data)
{
case var data when data is MessageCompleteEventData msgData:
response = msgData.Message;
break;
}
}
var textContent = response?.Content
.OfType<TextContent>()
.Aggregate(new StringBuilder(), (sb, c) => sb.Append(c.Text))
.ToString();
_console.WriteLine(textContent);
}
[Example]
public async Task CreateAToolFromAClass()
{
var getWeatherTool = Tool.CreateFromClass<GetWeatherTool>();
var response = await _client.CreateMessageAsync(new MessageRequest(
AnthropicModels.Claude3Haiku,
[
new(
MessageRole.User,
[new TextContent("What is the weather in New York?")]
)
],
tools: [getWeatherTool]
));
if (response.IsSuccess is false)
{
_console.WriteLine($"Failed to create message");
_console.WriteLine($"Error Type: {0}", response.Error.Error.Type);
_console.WriteLine($"Error Message: {0}", response.Error.Error.Message);
return;
}
foreach (var content in response.Value.Content)
{
switch (content)
{
case TextContent textContent:
_console.WriteLine(textContent.Text);
break;
case ToolUseContent toolUseContent:
_console.WriteLine(toolUseContent.Name);
break;
}
}
}
[Example]
public async Task CreateAToolFromAStaticMethod()
{
var getWeatherTool = Tool.CreateFromStaticMethod(
"Get Weather",
"Get the weather for a location in the specified units",
typeof(GetWeatherTool),
nameof(GetWeatherTool.GetWeatherStatically)
);
var response = await _client.CreateMessageAsync(new MessageRequest(
AnthropicModels.Claude3Haiku,
[
new(
MessageRole.User,
[new TextContent("What is the weather in New York?")]
)
],
tools: [getWeatherTool]
));
if (response.IsSuccess is false)
{
_console.WriteLine($"Failed to create message");
_console.WriteLine($"Error Type: {0}", response.Error.Error.Type);
_console.WriteLine($"Error Message: {0}", response.Error.Error.Message);
return;
}
foreach (var content in response.Value.Content)
{
switch (content)
{
case TextContent textContent:
_console.WriteLine(textContent.Text);
break;
case ToolUseContent toolUseContent:
_console.WriteLine(toolUseContent.Name);
break;
}
}
}
[Example]
public async Task CreateAToolFromInstanceMethod()
{
var toolInstance = new GetWeatherTool();
var getWeatherTool = Tool.CreateFromInstanceMethod(
"Get Weather",
"Get the weather for a location in the specified units",
toolInstance,
nameof(toolInstance.GetWeather)
);
var response = await _client.CreateMessageAsync(new MessageRequest(
AnthropicModels.Claude3Haiku,
[
new(
MessageRole.User,
[new TextContent("What is the weather in New York?")]
)
],
tools: [getWeatherTool]
));
if (response.IsSuccess is false)
{
_console.WriteLine($"Failed to create message");
_console.WriteLine($"Error Type: {0}", response.Error.Error.Type);
_console.WriteLine($"Error Message: {0}", response.Error.Error.Message);
return;
}
foreach (var content in response.Value.Content)
{
switch (content)
{
case TextContent textContent:
_console.WriteLine(textContent.Text);
break;
case ToolUseContent toolUseContent:
_console.WriteLine(toolUseContent.Name);
break;
}
}
}
[Example]
public async Task CreateAToolFromDelegate()
{
var tool = (string location, string units) => $"The weather in {location} is 72 degrees {units}";
var getWeatherTool = Tool.CreateFromFunction(
"Get Weather",
"Get the weather for a location in the specified units",
tool
);
var response = await _client.CreateMessageAsync(new MessageRequest(
AnthropicModels.Claude3Haiku,
[
new(
MessageRole.User,
[new TextContent("What is the weather in New York?")]
)
],
tools: [getWeatherTool]
));
if (response.IsSuccess is false)
{
_console.WriteLine($"Failed to create message");
_console.WriteLine($"Error Type: {0}", response.Error.Error.Type);
_console.WriteLine($"Error Message: {0}", response.Error.Error.Message);
return;
}
foreach (var content in response.Value.Content)
{
switch (content)
{
case TextContent textContent:
_console.WriteLine(textContent.Text);
break;
case ToolUseContent toolUseContent:
_console.WriteLine(toolUseContent.Name);
break;
}
}
}
[Example]
public async Task CreateAToolFromDelegateWithParameterAttributes()
{
var tool = (
[FunctionParameter(description: "The location of the weather being got", name: "Location", required: true)]
string location,
string units
) => $"The weather in {location} is 72 degrees {units}";
var getWeatherTool = Tool.CreateFromFunction(
"Get Weather",
"Get the weather for a location in the specified units",
tool
);
var response = await _client.CreateMessageAsync(new MessageRequest(
AnthropicModels.Claude3Haiku,
[
new(
MessageRole.User,
[new TextContent("What is the weather in New York?")]
)
],
tools: [getWeatherTool]
));
if (response.IsSuccess is false)
{
_console.WriteLine($"Failed to create message");
_console.WriteLine($"Error Type: {0}", response.Error.Error.Type);
_console.WriteLine($"Error Message: {0}", response.Error.Error.Message);
return;
}
foreach (var content in response.Value.Content)
{
switch (content)
{
case TextContent textContent:
_console.WriteLine(textContent.Text);
break;
case ToolUseContent toolUseContent:
_console.WriteLine(toolUseContent.Name);
break;
}
}
}
[Example]
public async Task CreateAToolFromDelegateWithInputClass()
{
var tool = (GetWeatherInput input) => $"The weather in {input.Location} is 72 degrees {input.Units}";
var getWeatherTool = Tool.CreateFromFunction(
"Get Weather",
"Get the weather for a location in the specified units",
tool
);
var response = await _client.CreateMessageAsync(new MessageRequest(
AnthropicModels.Claude3Haiku,
[
new(
MessageRole.User,
[new TextContent("What is the weather in New York?")]
)
],
tools: [getWeatherTool]
));
if (response.IsSuccess is false)
{
_console.WriteLine($"Failed to create message");
_console.WriteLine($"Error Type: {0}", response.Error.Error.Type);
_console.WriteLine($"Error Message: {0}", response.Error.Error.Message);
return;
}
foreach (var content in response.Value.Content)
{
switch (content)
{
case TextContent textContent:
_console.WriteLine(textContent.Text);
break;
case ToolUseContent toolUseContent:
_console.WriteLine(toolUseContent.Name);
break;
}
}
}
[Example]
public async Task CreateAMessageProvideAToolAndCallTool()
{
List<Message> messages = [
new(
MessageRole.User,
[new TextContent("What is the weather in New York?")]
)
];
List<Tool> tools = [Tool.CreateFromClass<GetWeatherTool>()];
var response = await _client.CreateMessageAsync(new MessageRequest(
AnthropicModels.Claude3Haiku,
messages,
tools: tools
));
if (response.IsSuccess is false)
{
_console.WriteLine($"Failed to create message");
_console.WriteLine($"Error Type: {0}", response.Error.Error.Type);
_console.WriteLine($"Error Message: {0}", response.Error.Error.Message);
return;
}
foreach (var content in response.Value.Content)
{
messages.Add(new(MessageRole.Assistant, [content]));
switch (content)
{
case TextContent textContent:
_console.WriteLine(textContent.Text);
break;
case ToolUseContent toolUseContent:
_console.WriteLine(toolUseContent.Name);
break;
}
}
if (response.Value.ToolCall is not null)
{
var toolCallResult = await response.Value.ToolCall.InvokeAsync<string>();
string toolResultContent;
if (toolCallResult.IsSuccess && toolCallResult.Value is not null)
{
_console.WriteLine(toolCallResult.Value);
toolResultContent = toolCallResult.Value;
}
else
{
_console.WriteLine(toolCallResult.Error.Message);
toolResultContent = toolCallResult.Error.Message;
}
messages.Add(
new(
MessageRole.User,
[
new ToolResultContent(
response.Value.ToolCall.ToolUse.Id,
toolResultContent
)
]
)
);
}
var finalResponse = await _client.CreateMessageAsync(new MessageRequest(
AnthropicModels.Claude3Haiku,
messages,
tools: tools
));
if (finalResponse.IsSuccess is false)
{
_console.WriteLine($"Failed to create message");
_console.WriteLine($"Error Type: {0}", finalResponse.Error.Error.Type);
_console.WriteLine($"Error Message: {0}", finalResponse.Error.Error.Message);
return;
}
foreach (var content in finalResponse.Value.Content)
{
switch (content)
{
case TextContent textContent:
_console.WriteLine(textContent.Text);
break;
}
}
}
[Example]
public async Task CreateAMessageProvideAToolStreamTheResponseAndCallTheTool()
{
var tool = (string location, string units) => $"The weather in {location} is 72 degrees {units}";
List<Message> messages = [
new(
MessageRole.User,
[new TextContent("What is the weather in New York?")]
)
];
List<Tool> tools = [Tool.CreateFromFunction(
"Get Weather",
"Get the weather for a location in the specified units",
tool
)];
var events = _client.CreateMessageAsync(new StreamMessageRequest(
AnthropicModels.Claude3Haiku,
messages,
tools: tools
));
MessageResponse? response = null;
await foreach (var e in events)
{
switch (e.Data)
{
case var data when data is MessageCompleteEventData msgData:
response = msgData.Message;
break;
}
}
if (response is null)
{
_console.WriteLine("Failed to get message response");
return;
}
foreach (var content in response.Content)
{
messages.Add(new(MessageRole.Assistant, [content]));
}
if (response?.ToolCall is not null)
{
var toolCallResult = await response.ToolCall.InvokeAsync<string>();
string toolResultContent;
if (toolCallResult.IsSuccess && toolCallResult.Value is not null)
{
toolResultContent = toolCallResult.Value;
}
else
{
toolResultContent = toolCallResult.Error.Message;
}
messages.Add(
new(
MessageRole.User,
[
new ToolResultContent(
response.ToolCall.ToolUse.Id,
toolResultContent
)
]
)
);
}
var finalResponse = await _client.CreateMessageAsync(new MessageRequest(
AnthropicModels.Claude3Haiku,
messages,
tools: tools
));
if (finalResponse.IsSuccess is false)
{
_console.WriteLine($"Failed to create message");
_console.WriteLine($"Error Type: {0}", finalResponse.Error.Error.Type);
_console.WriteLine($"Error Message: {0}", finalResponse.Error.Error.Message);
return;
}
foreach (var content in finalResponse.Value.Content)
{
switch (content)
{
case TextContent textContent:
_console.WriteLine(textContent.Text);
break;
}
}
}
}
class GetWeatherTool : ITool
{
public string Name => "Get Weather";
public string Description => "Get the weather for a location in the specified units";
public MethodInfo Function => typeof(GetWeatherTool).GetMethod(nameof(GetWeather))!;
public string GetWeather(string location, string units)
{
return $"The weather in {location} is 72 degrees {units}";
}
public string GetWeatherByInput(GetWeatherInput input)
{
return $"The weather in {input.Location} is 72 degrees {input.Units}";
}
public static string GetWeatherStatically(string location)
{
return $"The weather in {location} is 72 degrees Fahrenheit";
}
}
class GetWeatherInput
{
[FunctionProperty(
description: "The location of the weather being got",
required: true
)]
public string Location { get; } = string.Empty;
[FunctionProperty(
description: "The units to get the weather in",
required: false,
defaultValue: "Fahrenheit",
possibleValues: ["Fahrenheit", "Celsius"]
)]
public string Units { get; } = "Fahrenheit";
}
@@ -1,4 +1,4 @@
namespace AnthropicClient.Tests.EndToEnd; namespace AnthropicClient.Tests.Fixtures;
public class ConfigurationFixture public class ConfigurationFixture
{ {
@@ -4,7 +4,7 @@ public class AnthropicApiClientTests : IntegrationTest
{ {
[Theory] [Theory]
[ClassData(typeof(ErrorTestData))] [ClassData(typeof(ErrorTestData))]
public async Task CreateChatMessageAsync_WhenCalledAndErrorReturned_ItShouldHandleError( public async Task CreateMessageAsync_WhenCalledAndErrorReturned_ItShouldHandleError(
HttpStatusCode statusCode, HttpStatusCode statusCode,
string content, string content,
Type errorType Type errorType
@@ -18,12 +18,12 @@ public class AnthropicApiClientTests : IntegrationTest
content content
); );
var request = new ChatMessageRequest( var request = new MessageRequest(
model: AnthropicModels.Claude3Haiku, model: AnthropicModels.Claude3Haiku,
messages: [new(MessageRole.User, [new TextContent("Hello!")])] messages: [new(MessageRole.User, [new TextContent("Hello!")])]
); );
var result = await Client.CreateChatMessageAsync(request); var result = await Client.CreateMessageAsync(request);
result.IsSuccess.Should().BeFalse(); result.IsSuccess.Should().BeFalse();
result.Error.Should().BeOfType<AnthropicError>(); result.Error.Should().BeOfType<AnthropicError>();
@@ -33,7 +33,7 @@ public class AnthropicApiClientTests : IntegrationTest
} }
[Fact] [Fact]
public async Task CreateChatMessageAsync_WhenCalledAndMessageCreatedWithTextContent_ItShouldReturnMessage() public async Task CreateMessageAsync_WhenCalledAndMessageCreatedWithTextContent_ItShouldReturnMessage()
{ {
_mockHttpMessageHandler _mockHttpMessageHandler
.WhenCreateMessageRequest() .WhenCreateMessageRequest()
@@ -60,16 +60,15 @@ public class AnthropicApiClientTests : IntegrationTest
}" }"
); );
var request = new ChatMessageRequest( var request = new MessageRequest(
model: AnthropicModels.Claude35Sonnet, model: AnthropicModels.Claude35Sonnet,
messages: [new(MessageRole.User, [new TextContent("Hello!")])] messages: [new(MessageRole.User, [new TextContent("Hello!")])]
); );
var result = await Client.CreateChatMessageAsync(request); var result = await Client.CreateMessageAsync(request);
result.IsSuccess.Should().BeTrue(); result.IsSuccess.Should().BeTrue();
result.Value.Should().BeOfType<ChatResponse>(); result.Value.Should().BeOfType<MessageResponse>();
result.Error.Should().BeNull();
var message = result.Value; var message = result.Value;
message.Id.Should().Be("msg_013Zva2CMHLNnXjNJJKqJ2EF"); message.Id.Should().Be("msg_013Zva2CMHLNnXjNJJKqJ2EF");
@@ -90,7 +89,7 @@ public class AnthropicApiClientTests : IntegrationTest
} }
[Fact] [Fact]
public async Task CreateChatMessageAsync_WhenCalledAndMessageCreatedWithToolUseContentAndToolIsProvided_ItShouldReturnMessageWithToolCall() public async Task CreateMessageAsync_WhenCalledAndMessageCreatedWithToolUseContentAndToolIsProvided_ItShouldReturnMessageWithToolCall()
{ {
_mockHttpMessageHandler _mockHttpMessageHandler
.WhenCreateMessageRequest() .WhenCreateMessageRequest()
@@ -121,7 +120,7 @@ public class AnthropicApiClientTests : IntegrationTest
var func = (string ticker) => ticker; var func = (string ticker) => ticker;
var request = new ChatMessageRequest( var request = new MessageRequest(
model: AnthropicModels.Claude35Sonnet, model: AnthropicModels.Claude35Sonnet,
messages: [ messages: [
new(MessageRole.User, [new TextContent("Hello!")]), new(MessageRole.User, [new TextContent("Hello!")]),
@@ -131,11 +130,10 @@ public class AnthropicApiClientTests : IntegrationTest
] ]
); );
var result = await Client.CreateChatMessageAsync(request); var result = await Client.CreateMessageAsync(request);
result.IsSuccess.Should().BeTrue(); result.IsSuccess.Should().BeTrue();
result.Value.Should().BeOfType<ChatResponse>(); result.Value.Should().BeOfType<MessageResponse>();
result.Error.Should().BeNull();
var message = result.Value; var message = result.Value;
message.Id.Should().Be("msg_01D7FLrfh4GYq7yT1ULFeyMV"); message.Id.Should().Be("msg_01D7FLrfh4GYq7yT1ULFeyMV");
@@ -163,12 +161,11 @@ public class AnthropicApiClientTests : IntegrationTest
var toolCallResult = await message.ToolCall!.InvokeAsync(); var toolCallResult = await message.ToolCall!.InvokeAsync();
toolCallResult.IsSuccess.Should().BeTrue(); toolCallResult.IsSuccess.Should().BeTrue();
toolCallResult.Value!.ToString().Should().Be("^GSPC"); toolCallResult.Value!.ToString().Should().Be("^GSPC");
toolCallResult.Error.Should().BeNull();
} }
[Fact] [Fact]
public async Task CreateChatMessageAsync_WhenCalledAndMessageCreatedWithToolUseButNoToolProvided_ItShouldReturnMessageWithoutToolCall() public async Task CreateMessageAsync_WhenCalledAndMessageCreatedWithToolUseButNoToolProvided_ItShouldReturnMessageWithoutToolCall()
{ {
_mockHttpMessageHandler _mockHttpMessageHandler
.WhenCreateMessageRequest() .WhenCreateMessageRequest()
@@ -197,18 +194,17 @@ public class AnthropicApiClientTests : IntegrationTest
}" }"
); );
var request = new ChatMessageRequest( var request = new MessageRequest(
model: AnthropicModels.Claude35Sonnet, model: AnthropicModels.Claude35Sonnet,
messages: [ messages: [
new(MessageRole.User, [new TextContent("Hello!")]), new(MessageRole.User, [new TextContent("Hello!")]),
] ]
); );
var result = await Client.CreateChatMessageAsync(request); var result = await Client.CreateMessageAsync(request);
result.IsSuccess.Should().BeTrue(); result.IsSuccess.Should().BeTrue();
result.Value.Should().BeOfType<ChatResponse>(); result.Value.Should().BeOfType<MessageResponse>();
result.Error.Should().BeNull();
var message = result.Value; var message = result.Value;
message.Id.Should().Be("msg_01D7FLrfh4GYq7yT1ULFeyMV"); message.Id.Should().Be("msg_01D7FLrfh4GYq7yT1ULFeyMV");
@@ -236,7 +232,7 @@ public class AnthropicApiClientTests : IntegrationTest
[Theory] [Theory]
[ClassData(typeof(EventTestData))] [ClassData(typeof(EventTestData))]
public async Task CreateChatMessageAsync_WhenCalledAndMessageIsStreamed_ItShouldHandleAllEventTypes(string eventString, AnthropicEvent anthropicEvent) public async Task CreateMessageAsync_WhenCalledAndMessageIsStreamed_ItShouldHandleAllEventTypes(string eventString, AnthropicEvent anthropicEvent)
{ {
_mockHttpMessageHandler _mockHttpMessageHandler
.WhenCreateStreamMessageRequest() .WhenCreateStreamMessageRequest()
@@ -246,21 +242,21 @@ public class AnthropicApiClientTests : IntegrationTest
new MemoryStream(Encoding.UTF8.GetBytes(eventString)) new MemoryStream(Encoding.UTF8.GetBytes(eventString))
); );
var request = new StreamChatMessageRequest( var request = new StreamMessageRequest(
model: AnthropicModels.Claude35Sonnet, model: AnthropicModels.Claude35Sonnet,
messages: [ messages: [
new(MessageRole.User, [new TextContent("Hello!")]), new(MessageRole.User, [new TextContent("Hello!")]),
] ]
); );
var result = Client.CreateChatMessageAsync(request); var result = Client.CreateMessageAsync(request);
var e = await result.FirstOrDefaultAsync(); var e = await result.FirstOrDefaultAsync();
e.Should().BeEquivalentTo(anthropicEvent); e.Should().BeEquivalentTo(anthropicEvent);
} }
[Fact] [Fact]
public async Task CreateChatMessageAsync_WhenCalledAndMessageIsStreamed_ItShouldReturnAllExpectedEvents() public async Task CreateMessageAsync_WhenCalledAndMessageIsStreamed_ItShouldReturnAllExpectedEvents()
{ {
var eventStream = EventTestData.GetEventStream(); var eventStream = EventTestData.GetEventStream();
var events = EventTestData.GetAllEvents(); var events = EventTestData.GetAllEvents();
@@ -273,14 +269,14 @@ public class AnthropicApiClientTests : IntegrationTest
eventStream eventStream
); );
var request = new StreamChatMessageRequest( var request = new StreamMessageRequest(
model: AnthropicModels.Claude35Sonnet, model: AnthropicModels.Claude35Sonnet,
messages: [ messages: [
new(MessageRole.User, [new TextContent("Hello!")]), new(MessageRole.User, [new TextContent("Hello!")]),
] ]
); );
var result = Client.CreateChatMessageAsync(request); var result = Client.CreateMessageAsync(request);
var actualEvents = await result.ToListAsync(); var actualEvents = await result.ToListAsync();
@@ -288,7 +284,7 @@ public class AnthropicApiClientTests : IntegrationTest
} }
[Fact] [Fact]
public async Task CreateChatMessageAsync_WhenCalledMessageIsStreamedAndToolProvide_ItShouldReturnToolCall() public async Task CreateMessageAsync_WhenCalledMessageIsStreamedAndToolProvide_ItShouldReturnToolCall()
{ {
var eventStream = EventTestData.GetEventStream(); var eventStream = EventTestData.GetEventStream();
@@ -302,7 +298,7 @@ public class AnthropicApiClientTests : IntegrationTest
var getWeather = (string location, string unit) => $"The weather in {location} is 72°{unit}"; var getWeather = (string location, string unit) => $"The weather in {location} is 72°{unit}";
var request = new StreamChatMessageRequest( var request = new StreamMessageRequest(
model: AnthropicModels.Claude35Sonnet, model: AnthropicModels.Claude35Sonnet,
messages: [ messages: [
new(MessageRole.User, [new TextContent("Hello!")]), new(MessageRole.User, [new TextContent("Hello!")]),
@@ -312,7 +308,7 @@ public class AnthropicApiClientTests : IntegrationTest
] ]
); );
var result = Client.CreateChatMessageAsync(request); var result = Client.CreateMessageAsync(request);
var msgCompleteEvent = await result var msgCompleteEvent = await result
.Where(e => e.Type is EventType.MessageComplete) .Where(e => e.Type is EventType.MessageComplete)
.FirstAsync(); .FirstAsync();
@@ -323,7 +319,6 @@ public class AnthropicApiClientTests : IntegrationTest
var toolCallResult = await toolCall!.InvokeAsync<string>(); var toolCallResult = await toolCall!.InvokeAsync<string>();
toolCallResult.IsSuccess.Should().BeTrue(); toolCallResult.IsSuccess.Should().BeTrue();
toolCallResult.Error.Should().BeNull();
toolCallResult.Value.Should().Be(getWeather("San Francisco, CA","fahrenheit")); toolCallResult.Value.Should().Be(getWeather("San Francisco, CA","fahrenheit"));
} }
} }
@@ -28,13 +28,13 @@ public static class MockHttpMessageHandlerExtensions
{ {
return mockHttpMessageHandler return mockHttpMessageHandler
.SetupBaseRequest() .SetupBaseRequest()
.WithJsonContent<ChatMessageRequest>(r => r.Stream == false, JsonSerializationOptions.DefaultOptions); .WithJsonContent<MessageRequest>(r => r.Stream == false, JsonSerializationOptions.DefaultOptions);
} }
public static MockedRequest WhenCreateStreamMessageRequest(this MockHttpMessageHandler mockHttpMessageHandler) public static MockedRequest WhenCreateStreamMessageRequest(this MockHttpMessageHandler mockHttpMessageHandler)
{ {
return mockHttpMessageHandler return mockHttpMessageHandler
.SetupBaseRequest() .SetupBaseRequest()
.WithJsonContent<StreamChatMessageRequest>(r => r.Stream == true, JsonSerializationOptions.DefaultOptions); .WithJsonContent<StreamMessageRequest>(r => r.Stream == true, JsonSerializationOptions.DefaultOptions);
} }
} }
@@ -11,7 +11,12 @@ public class AnthropicResultTests
var actual = AnthropicResult<string>.Success(value, headers); var actual = AnthropicResult<string>.Success(value, headers);
actual.IsSuccess.Should().BeTrue(); actual.IsSuccess.Should().BeTrue();
actual.IsFailure.Should().BeFalse();
actual.Value.Should().Be(value); actual.Value.Should().Be(value);
var action = () => { var _ = actual.Error; };
action.Should().Throw<InvalidOperationException>();
actual.Headers.Should().Be(headers); actual.Headers.Should().Be(headers);
} }
@@ -24,7 +29,12 @@ public class AnthropicResultTests
var actual = AnthropicResult<string>.Failure(error, headers); var actual = AnthropicResult<string>.Failure(error, headers);
actual.IsSuccess.Should().BeFalse(); actual.IsSuccess.Should().BeFalse();
actual.IsFailure.Should().BeTrue();
actual.Error.Should().Be(error); actual.Error.Should().Be(error);
var action = () => { var _ = actual.Value; };
action.Should().Throw<InvalidOperationException>();
actual.Headers.Should().Be(headers); actual.Headers.Should().Be(headers);
} }
} }
@@ -5,7 +5,7 @@ public class MessageCompleteEventDataTests
[Fact] [Fact]
public void Constructor_WhenCalled_ItShouldInitializeProperties() public void Constructor_WhenCalled_ItShouldInitializeProperties()
{ {
var expectedMessage = new ChatResponse(); var expectedMessage = new MessageResponse();
var expectedHeaders = new AnthropicHeaders(); var expectedHeaders = new AnthropicHeaders();
var messageCompleteEventData = new MessageCompleteEventData(expectedMessage, expectedHeaders); var messageCompleteEventData = new MessageCompleteEventData(expectedMessage, expectedHeaders);
@@ -18,7 +18,7 @@ public class MessageDeltaEventDataTests : SerializationTest
public void Constructor_WhenCalled_ItShouldInitializeProperties() public void Constructor_WhenCalled_ItShouldInitializeProperties()
{ {
var expectedDelta = new MessageDelta("max_tokens", "max_tokens"); var expectedDelta = new MessageDelta("max_tokens", "max_tokens");
var expectedUsage = new ChatUsage { InputTokens = 1, OutputTokens = 1 }; var expectedUsage = new Usage { InputTokens = 1, OutputTokens = 1 };
var messageDeltaEventData = new MessageDeltaEventData(expectedDelta, expectedUsage); var messageDeltaEventData = new MessageDeltaEventData(expectedDelta, expectedUsage);
@@ -30,7 +30,7 @@ public class MessageDeltaEventDataTests : SerializationTest
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape() public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{ {
var expectedDelta = new MessageDelta("max_tokens", "max_tokens"); var expectedDelta = new MessageDelta("max_tokens", "max_tokens");
var expectedUsage = new ChatUsage { InputTokens = 1, OutputTokens = 1 }; var expectedUsage = new Usage { InputTokens = 1, OutputTokens = 1 };
var messageDeltaEventData = new MessageDeltaEventData(expectedDelta, expectedUsage); var messageDeltaEventData = new MessageDeltaEventData(expectedDelta, expectedUsage);
@@ -43,7 +43,7 @@ public class MessageDeltaEventDataTests : SerializationTest
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedValues() public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedValues()
{ {
var expectedDelta = new MessageDelta("max_tokens", "max_tokens"); var expectedDelta = new MessageDelta("max_tokens", "max_tokens");
var expectedUsage = new ChatUsage { InputTokens = 1, OutputTokens = 1 }; var expectedUsage = new Usage { InputTokens = 1, OutputTokens = 1 };
var messageDeltaEventData = Deserialize<MessageDeltaEventData>(_testJson); var messageDeltaEventData = Deserialize<MessageDeltaEventData>(_testJson);
@@ -1,6 +1,6 @@
namespace AnthropicClient.Tests.Unit.Models; namespace AnthropicClient.Tests.Unit.Models;
public class ChatMessageRequestTests : SerializationTest public class MessageRequestTests : SerializationTest
{ {
private readonly string _testJson = @"{ private readonly string _testJson = @"{
""model"": ""claude-3-sonnet-20240229"", ""model"": ""claude-3-sonnet-20240229"",
@@ -152,7 +152,7 @@ public class ChatMessageRequestTests : SerializationTest
public void Constructor_WhenCalled_ItShouldInitializeProperties() public void Constructor_WhenCalled_ItShouldInitializeProperties()
{ {
var model = AnthropicModels.Claude3Sonnet; var model = AnthropicModels.Claude3Sonnet;
var messages = new List<ChatMessage> { new() }; var messages = new List<Message> { new() };
var maxTokens = 512; var maxTokens = 512;
var system = "test-system"; var system = "test-system";
var metadata = new Dictionary<string, object> { ["test"] = "test" }; var metadata = new Dictionary<string, object> { ["test"] = "test" };
@@ -162,7 +162,7 @@ public class ChatMessageRequestTests : SerializationTest
var toolChoice = new AutoToolChoice(); var toolChoice = new AutoToolChoice();
var tools = new List<Tool>(); var tools = new List<Tool>();
var chatMessageRequest = new ChatMessageRequest( var messageRequest = new MessageRequest(
model: model, model: model,
messages: messages, messages: messages,
maxTokens: maxTokens, maxTokens: maxTokens,
@@ -175,23 +175,23 @@ public class ChatMessageRequestTests : SerializationTest
tools: tools tools: tools
); );
chatMessageRequest.Model.Should().Be(model); messageRequest.Model.Should().Be(model);
chatMessageRequest.Messages.Should().BeSameAs(messages); messageRequest.Messages.Should().BeSameAs(messages);
chatMessageRequest.MaxTokens.Should().Be(maxTokens); messageRequest.MaxTokens.Should().Be(maxTokens);
chatMessageRequest.System.Should().Be(system); messageRequest.System.Should().Be(system);
chatMessageRequest.Metadata.Should().BeSameAs(metadata); messageRequest.Metadata.Should().BeSameAs(metadata);
chatMessageRequest.Temperature.Should().Be(temperature); messageRequest.Temperature.Should().Be(temperature);
chatMessageRequest.TopK.Should().Be(topK); messageRequest.TopK.Should().Be(topK);
chatMessageRequest.TopP.Should().Be(topP); messageRequest.TopP.Should().Be(topP);
chatMessageRequest.ToolChoice.Should().Be(toolChoice); messageRequest.ToolChoice.Should().Be(toolChoice);
chatMessageRequest.Tools.Should().BeSameAs(tools); messageRequest.Tools.Should().BeSameAs(tools);
chatMessageRequest.Stream.Should().BeFalse(); messageRequest.Stream.Should().BeFalse();
} }
[Fact] [Fact]
public void Constructor_WhenCalledAndModelIsNull_ItShouldThrowArgumentNullException() public void Constructor_WhenCalledAndModelIsNull_ItShouldThrowArgumentNullException()
{ {
var action = () => new ChatMessageRequest( var action = () => new MessageRequest(
model: null!, model: null!,
messages: [new()] messages: [new()]
); );
@@ -202,7 +202,7 @@ public class ChatMessageRequestTests : SerializationTest
[Fact] [Fact]
public void Constructor_WhenCalledAndMessagesIsNull_ItShouldThrowArgumentNullException() public void Constructor_WhenCalledAndMessagesIsNull_ItShouldThrowArgumentNullException()
{ {
var action = () => new ChatMessageRequest( var action = () => new MessageRequest(
model: AnthropicModels.Claude3Sonnet, model: AnthropicModels.Claude3Sonnet,
messages: null! messages: null!
); );
@@ -213,7 +213,7 @@ public class ChatMessageRequestTests : SerializationTest
[Fact] [Fact]
public void Constructor_WhenCalledAndModelIsInvalid_ItShouldThrowArgumentException() public void Constructor_WhenCalledAndModelIsInvalid_ItShouldThrowArgumentException()
{ {
var action = () => new ChatMessageRequest( var action = () => new MessageRequest(
model: "invalid-model", model: "invalid-model",
messages: [new()] messages: [new()]
); );
@@ -224,7 +224,7 @@ public class ChatMessageRequestTests : SerializationTest
[Fact] [Fact]
public void Constructor_WhenCalledAndMessagesIsEmpty_ItShouldThrowArgumentException() public void Constructor_WhenCalledAndMessagesIsEmpty_ItShouldThrowArgumentException()
{ {
var action = () => new ChatMessageRequest( var action = () => new MessageRequest(
model: AnthropicModels.Claude3Sonnet, model: AnthropicModels.Claude3Sonnet,
messages: [] messages: []
); );
@@ -235,7 +235,7 @@ public class ChatMessageRequestTests : SerializationTest
[Fact] [Fact]
public void Constructor_WhenCalledAndMaxTokensIsInvalid_ItShouldThrowArgumentException() public void Constructor_WhenCalledAndMaxTokensIsInvalid_ItShouldThrowArgumentException()
{ {
var action = () => new ChatMessageRequest( var action = () => new MessageRequest(
model: AnthropicModels.Claude3Sonnet, model: AnthropicModels.Claude3Sonnet,
messages: [new()], messages: [new()],
maxTokens: 0 maxTokens: 0
@@ -249,7 +249,7 @@ public class ChatMessageRequestTests : SerializationTest
[InlineData(2)] [InlineData(2)]
public void Constructor_WhenCalledAndTemperatureIsInvalid_ItShouldThrowArgumentException(decimal temperature) public void Constructor_WhenCalledAndTemperatureIsInvalid_ItShouldThrowArgumentException(decimal temperature)
{ {
var action = () => new ChatMessageRequest( var action = () => new MessageRequest(
model: AnthropicModels.Claude3Sonnet, model: AnthropicModels.Claude3Sonnet,
messages: [new()], messages: [new()],
temperature: temperature temperature: temperature
@@ -261,7 +261,7 @@ public class ChatMessageRequestTests : SerializationTest
[Fact] [Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape() public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{ {
var messages = new List<ChatMessage>() var messages = new List<Message>()
{ {
new() new()
{ {
@@ -283,7 +283,7 @@ public class ChatMessageRequestTests : SerializationTest
var toolChoice = new AutoToolChoice(); var toolChoice = new AutoToolChoice();
var tools = new List<Tool>(); var tools = new List<Tool>();
var chatMessageRequest = new ChatMessageRequest( var messageRequest = new MessageRequest(
model: model, model: model,
messages: messages, messages: messages,
maxTokens: maxTokens, maxTokens: maxTokens,
@@ -296,7 +296,7 @@ public class ChatMessageRequestTests : SerializationTest
tools: tools tools: tools
); );
var actual = Serialize(chatMessageRequest); var actual = Serialize(messageRequest);
JsonAssert.Equal(_testJson, actual); JsonAssert.Equal(_testJson, actual);
} }
@@ -304,71 +304,71 @@ public class ChatMessageRequestTests : SerializationTest
[Fact] [Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape() public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
{ {
var chatMessageRequest = Deserialize<ChatMessageRequest>(_testJson); var messageRequest = Deserialize<MessageRequest>(_testJson);
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet); messageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
chatMessageRequest.System.Should().Be("test-system"); messageRequest.System.Should().Be("test-system");
chatMessageRequest.Messages.Should().HaveCount(1); messageRequest.Messages.Should().HaveCount(1);
chatMessageRequest.MaxTokens.Should().Be(512); messageRequest.MaxTokens.Should().Be(512);
chatMessageRequest.Metadata.Should().HaveCount(1); messageRequest.Metadata.Should().HaveCount(1);
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString(); var testValue = messageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
testValue.Should().Be("test"); testValue.Should().Be("test");
chatMessageRequest.Temperature.Should().Be(0.5m); messageRequest.Temperature.Should().Be(0.5m);
chatMessageRequest.TopK.Should().Be(10); messageRequest.TopK.Should().Be(10);
chatMessageRequest.TopP.Should().Be(0.5m); messageRequest.TopP.Should().Be(0.5m);
chatMessageRequest.ToolChoice.Should().BeOfType<AutoToolChoice>(); messageRequest.ToolChoice.Should().BeOfType<AutoToolChoice>();
chatMessageRequest.ToolChoice!.Type.Should().Be("auto"); messageRequest.ToolChoice!.Type.Should().Be("auto");
chatMessageRequest.Tools.Should().HaveCount(0); messageRequest.Tools.Should().HaveCount(0);
chatMessageRequest.Stream.Should().BeFalse(); messageRequest.Stream.Should().BeFalse();
} }
[Fact] [Fact]
public void JsonDeserialization_WhenDeserializedWithAnyToolChoice_ItShouldHaveExpectedShape() public void JsonDeserialization_WhenDeserializedWithAnyToolChoice_ItShouldHaveExpectedShape()
{ {
var chatMessageRequest = Deserialize<ChatMessageRequest>(_testJsonWithAnyToolChoice); var messageRequest = Deserialize<MessageRequest>(_testJsonWithAnyToolChoice);
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet); messageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
chatMessageRequest.System.Should().Be("test-system"); messageRequest.System.Should().Be("test-system");
chatMessageRequest.Messages.Should().HaveCount(1); messageRequest.Messages.Should().HaveCount(1);
chatMessageRequest.MaxTokens.Should().Be(512); messageRequest.MaxTokens.Should().Be(512);
chatMessageRequest.Metadata.Should().HaveCount(1); messageRequest.Metadata.Should().HaveCount(1);
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString(); var testValue = messageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
testValue.Should().Be("test"); testValue.Should().Be("test");
chatMessageRequest.Temperature.Should().Be(0.5m); messageRequest.Temperature.Should().Be(0.5m);
chatMessageRequest.TopK.Should().Be(10); messageRequest.TopK.Should().Be(10);
chatMessageRequest.TopP.Should().Be(0.5m); messageRequest.TopP.Should().Be(0.5m);
chatMessageRequest.ToolChoice.Should().BeOfType<AnyToolChoice>(); messageRequest.ToolChoice.Should().BeOfType<AnyToolChoice>();
chatMessageRequest.ToolChoice!.Type.Should().Be("any"); messageRequest.ToolChoice!.Type.Should().Be("any");
chatMessageRequest.Tools.Should().HaveCount(0); messageRequest.Tools.Should().HaveCount(0);
} }
[Fact] [Fact]
public void JsonDeserialization_WhenDeserializedWithSpecificToolChoice_ItShouldHaveExpectedShape() public void JsonDeserialization_WhenDeserializedWithSpecificToolChoice_ItShouldHaveExpectedShape()
{ {
var chatMessageRequest = Deserialize<ChatMessageRequest>(_testJsonWithSpecificToolChoice); var messageRequest = Deserialize<MessageRequest>(_testJsonWithSpecificToolChoice);
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet); messageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
chatMessageRequest.System.Should().Be("test-system"); messageRequest.System.Should().Be("test-system");
chatMessageRequest.Messages.Should().HaveCount(1); messageRequest.Messages.Should().HaveCount(1);
chatMessageRequest.MaxTokens.Should().Be(512); messageRequest.MaxTokens.Should().Be(512);
chatMessageRequest.Metadata.Should().HaveCount(1); messageRequest.Metadata.Should().HaveCount(1);
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString(); var testValue = messageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
testValue.Should().Be("test"); testValue.Should().Be("test");
chatMessageRequest.Temperature.Should().Be(0.5m); messageRequest.Temperature.Should().Be(0.5m);
chatMessageRequest.TopK.Should().Be(10); messageRequest.TopK.Should().Be(10);
chatMessageRequest.TopP.Should().Be(0.5m); messageRequest.TopP.Should().Be(0.5m);
chatMessageRequest.ToolChoice.Should().BeOfType<SpecificToolChoice>(); messageRequest.ToolChoice.Should().BeOfType<SpecificToolChoice>();
var specificToolChoice = chatMessageRequest.ToolChoice as SpecificToolChoice; var specificToolChoice = messageRequest.ToolChoice as SpecificToolChoice;
specificToolChoice!.Type.Should().Be("tool"); specificToolChoice!.Type.Should().Be("tool");
specificToolChoice.Name.Should().Be("test-tool"); specificToolChoice.Name.Should().Be("test-tool");
chatMessageRequest.Tools.Should().HaveCount(0); messageRequest.Tools.Should().HaveCount(0);
} }
[Fact] [Fact]
@@ -376,7 +376,7 @@ public class ChatMessageRequestTests : SerializationTest
{ {
var json = @"{""model"":""claude-3-sonnet-20240229"",""system"":""test-system"",""messages"":[{""role"":""user"",""content"":[{""text"":""Hello!"",""type"":""text""}]}],""max_tokens"":512,""metadata"":{""test"":""test""},""stop_sequences"":[],""temperature"":0.5,""topK"":10,""topP"":0.5,""tool_choice"":{""type"":""unknown""},""tools"":[{""name"":""test-tool"",""description"":""test-description"",""input_schema"":{""type"":""object"",""properties"":{""test-property"":{""type"":""string"",""description"":""test-description""}},""required"":[""test-property""]}}],""stream"":false}"; var json = @"{""model"":""claude-3-sonnet-20240229"",""system"":""test-system"",""messages"":[{""role"":""user"",""content"":[{""text"":""Hello!"",""type"":""text""}]}],""max_tokens"":512,""metadata"":{""test"":""test""},""stop_sequences"":[],""temperature"":0.5,""topK"":10,""topP"":0.5,""tool_choice"":{""type"":""unknown""},""tools"":[{""name"":""test-tool"",""description"":""test-description"",""input_schema"":{""type"":""object"",""properties"":{""test-property"":{""type"":""string"",""description"":""test-description""}},""required"":[""test-property""]}}],""stream"":false}";
var action = () => Deserialize<ChatMessageRequest>(json); var action = () => Deserialize<MessageRequest>(json);
action.Should().Throw<JsonException>(); action.Should().Throw<JsonException>();
} }
@@ -384,27 +384,27 @@ public class ChatMessageRequestTests : SerializationTest
[Fact] [Fact]
public void JsonDeserialization_WhenDeserializedWithImageContent_ItShouldHaveExpectedShape() public void JsonDeserialization_WhenDeserializedWithImageContent_ItShouldHaveExpectedShape()
{ {
var chatMessageRequest = Deserialize<ChatMessageRequest>(_testJsonWithImageContent); var messageRequest = Deserialize<MessageRequest>(_testJsonWithImageContent);
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet); messageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
chatMessageRequest.System.Should().Be("test-system"); messageRequest.System.Should().Be("test-system");
chatMessageRequest.Messages.Should().HaveCount(1); messageRequest.Messages.Should().HaveCount(1);
chatMessageRequest.MaxTokens.Should().Be(512); messageRequest.MaxTokens.Should().Be(512);
chatMessageRequest.Metadata.Should().HaveCount(1); messageRequest.Metadata.Should().HaveCount(1);
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString(); var testValue = messageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
testValue.Should().Be("test"); testValue.Should().Be("test");
chatMessageRequest.Temperature.Should().Be(0.5m); messageRequest.Temperature.Should().Be(0.5m);
chatMessageRequest.TopK.Should().Be(10); messageRequest.TopK.Should().Be(10);
chatMessageRequest.TopP.Should().Be(0.5m); messageRequest.TopP.Should().Be(0.5m);
chatMessageRequest.ToolChoice.Should().BeOfType<AutoToolChoice>(); messageRequest.ToolChoice.Should().BeOfType<AutoToolChoice>();
chatMessageRequest.ToolChoice!.Type.Should().Be("auto"); messageRequest.ToolChoice!.Type.Should().Be("auto");
chatMessageRequest.Tools.Should().HaveCount(0); messageRequest.Tools.Should().HaveCount(0);
chatMessageRequest.Messages[0].Content.Should().HaveCount(1); messageRequest.Messages[0].Content.Should().HaveCount(1);
chatMessageRequest.Messages[0].Content[0].Should().BeOfType<ImageContent>(); messageRequest.Messages[0].Content[0].Should().BeOfType<ImageContent>();
var imageContent = chatMessageRequest.Messages[0].Content[0] as ImageContent; var imageContent = messageRequest.Messages[0].Content[0] as ImageContent;
imageContent!.Type.Should().Be("image"); imageContent!.Type.Should().Be("image");
imageContent.Source.MediaType.Should().Be("image/jpeg"); imageContent.Source.MediaType.Should().Be("image/jpeg");
imageContent.Source.Data.Should().Be("data"); imageContent.Source.Data.Should().Be("data");
@@ -413,27 +413,27 @@ public class ChatMessageRequestTests : SerializationTest
[Fact] [Fact]
public void JsonDeserialization_WhenDeserializedWithToolUseContent_ItShouldHaveExpectedShape() public void JsonDeserialization_WhenDeserializedWithToolUseContent_ItShouldHaveExpectedShape()
{ {
var chatMessageRequest = Deserialize<ChatMessageRequest>(_testJsonWithToolUseContent); var messageRequest = Deserialize<MessageRequest>(_testJsonWithToolUseContent);
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet); messageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
chatMessageRequest.System.Should().Be("test-system"); messageRequest.System.Should().Be("test-system");
chatMessageRequest.Messages.Should().HaveCount(1); messageRequest.Messages.Should().HaveCount(1);
chatMessageRequest.MaxTokens.Should().Be(512); messageRequest.MaxTokens.Should().Be(512);
chatMessageRequest.Metadata.Should().HaveCount(1); messageRequest.Metadata.Should().HaveCount(1);
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString(); var testValue = messageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
testValue.Should().Be("test"); testValue.Should().Be("test");
chatMessageRequest.Temperature.Should().Be(0.5m); messageRequest.Temperature.Should().Be(0.5m);
chatMessageRequest.TopK.Should().Be(10); messageRequest.TopK.Should().Be(10);
chatMessageRequest.TopP.Should().Be(0.5m); messageRequest.TopP.Should().Be(0.5m);
chatMessageRequest.ToolChoice.Should().BeOfType<AutoToolChoice>(); messageRequest.ToolChoice.Should().BeOfType<AutoToolChoice>();
chatMessageRequest.ToolChoice!.Type.Should().Be("auto"); messageRequest.ToolChoice!.Type.Should().Be("auto");
chatMessageRequest.Tools.Should().HaveCount(0); messageRequest.Tools.Should().HaveCount(0);
chatMessageRequest.Messages[0].Content.Should().HaveCount(1); messageRequest.Messages[0].Content.Should().HaveCount(1);
chatMessageRequest.Messages[0].Content[0].Should().BeOfType<ToolUseContent>(); messageRequest.Messages[0].Content[0].Should().BeOfType<ToolUseContent>();
var toolUseContent = chatMessageRequest.Messages[0].Content[0] as ToolUseContent; var toolUseContent = messageRequest.Messages[0].Content[0] as ToolUseContent;
toolUseContent!.Type.Should().Be("tool_use"); toolUseContent!.Type.Should().Be("tool_use");
toolUseContent.Name.Should().Be("test-tool"); toolUseContent.Name.Should().Be("test-tool");
toolUseContent.Id.Should().Be("test-tool-id"); toolUseContent.Id.Should().Be("test-tool-id");
@@ -444,27 +444,27 @@ public class ChatMessageRequestTests : SerializationTest
[Fact] [Fact]
public void JsonDeserialization_WhenDeserializedWithToolResultContent_ItShouldHaveExpectedShape() public void JsonDeserialization_WhenDeserializedWithToolResultContent_ItShouldHaveExpectedShape()
{ {
var chatMessageRequest = Deserialize<ChatMessageRequest>(_testJsonWithToolResultContent); var messageRequest = Deserialize<MessageRequest>(_testJsonWithToolResultContent);
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet); messageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
chatMessageRequest.System.Should().Be("test-system"); messageRequest.System.Should().Be("test-system");
chatMessageRequest.Messages.Should().HaveCount(1); messageRequest.Messages.Should().HaveCount(1);
chatMessageRequest.MaxTokens.Should().Be(512); messageRequest.MaxTokens.Should().Be(512);
chatMessageRequest.Metadata.Should().HaveCount(1); messageRequest.Metadata.Should().HaveCount(1);
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString(); var testValue = messageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
testValue.Should().Be("test"); testValue.Should().Be("test");
chatMessageRequest.Temperature.Should().Be(0.5m); messageRequest.Temperature.Should().Be(0.5m);
chatMessageRequest.TopK.Should().Be(10); messageRequest.TopK.Should().Be(10);
chatMessageRequest.TopP.Should().Be(0.5m); messageRequest.TopP.Should().Be(0.5m);
chatMessageRequest.ToolChoice.Should().BeOfType<AutoToolChoice>(); messageRequest.ToolChoice.Should().BeOfType<AutoToolChoice>();
chatMessageRequest.ToolChoice!.Type.Should().Be("auto"); messageRequest.ToolChoice!.Type.Should().Be("auto");
chatMessageRequest.Tools.Should().HaveCount(0); messageRequest.Tools.Should().HaveCount(0);
chatMessageRequest.Messages[0].Content.Should().HaveCount(1); messageRequest.Messages[0].Content.Should().HaveCount(1);
chatMessageRequest.Messages[0].Content[0].Should().BeOfType<ToolResultContent>(); messageRequest.Messages[0].Content[0].Should().BeOfType<ToolResultContent>();
var toolResultContent = chatMessageRequest.Messages[0].Content[0] as ToolResultContent; var toolResultContent = messageRequest.Messages[0].Content[0] as ToolResultContent;
toolResultContent!.Type.Should().Be("tool_result"); toolResultContent!.Type.Should().Be("tool_result");
toolResultContent.ToolUseId.Should().Be("test-tool"); toolResultContent.ToolUseId.Should().Be("test-tool");
toolResultContent.Content.Should().Be("test-value"); toolResultContent.Content.Should().Be("test-value");
@@ -473,7 +473,7 @@ public class ChatMessageRequestTests : SerializationTest
[Fact] [Fact]
public void JsonDeserialization_WhenDeserializedWithUnknownContent_ItShouldThrowJsonException() public void JsonDeserialization_WhenDeserializedWithUnknownContent_ItShouldThrowJsonException()
{ {
var action = () => Deserialize<ChatMessageRequest>(_testJsonWithUnknownContent); var action = () => Deserialize<MessageRequest>(_testJsonWithUnknownContent);
action.Should().Throw<JsonException>(); action.Should().Throw<JsonException>();
} }
@@ -1,6 +1,6 @@
namespace AnthropicClient.Tests.Unit.Models; namespace AnthropicClient.Tests.Unit.Models;
public class ChatResponseTests : SerializationTest public class MessageResponseTests : SerializationTest
{ {
[Fact] [Fact]
public void Constructor_WhenCalled_ShouldInitializeProperties() public void Constructor_WhenCalled_ShouldInitializeProperties()
@@ -11,7 +11,7 @@ public class ChatResponseTests : SerializationTest
var expectedStopReason = "stop reason"; var expectedStopReason = "stop reason";
var expectedStopSequence = "stop sequence"; var expectedStopSequence = "stop sequence";
var expectedType = "type"; var expectedType = "type";
var expectedUsage = new ChatUsage var expectedUsage = new Usage
{ {
InputTokens = 1, InputTokens = 1,
OutputTokens = 2 OutputTokens = 2
@@ -21,7 +21,7 @@ public class ChatResponseTests : SerializationTest
new TextContent("text content"), new TextContent("text content"),
}; };
var chatResponse = new ChatResponse var messageResponse = new MessageResponse
{ {
Id = expectedId, Id = expectedId,
Model = expectedModel, Model = expectedModel,
@@ -33,14 +33,14 @@ public class ChatResponseTests : SerializationTest
Content = expectedContent Content = expectedContent
}; };
chatResponse.Id.Should().Be(expectedId); messageResponse.Id.Should().Be(expectedId);
chatResponse.Model.Should().Be(expectedModel); messageResponse.Model.Should().Be(expectedModel);
chatResponse.Role.Should().Be(expectedRole); messageResponse.Role.Should().Be(expectedRole);
chatResponse.StopReason.Should().Be(expectedStopReason); messageResponse.StopReason.Should().Be(expectedStopReason);
chatResponse.StopSequence.Should().Be(expectedStopSequence); messageResponse.StopSequence.Should().Be(expectedStopSequence);
chatResponse.Type.Should().Be(expectedType); messageResponse.Type.Should().Be(expectedType);
chatResponse.Usage.Should().BeEquivalentTo(expectedUsage); messageResponse.Usage.Should().BeEquivalentTo(expectedUsage);
chatResponse.Content.Should().BeEquivalentTo(expectedContent); messageResponse.Content.Should().BeEquivalentTo(expectedContent);
} }
[Fact] [Fact]
@@ -59,7 +59,7 @@ public class ChatResponseTests : SerializationTest
] ]
}"; }";
var chatResponse = new ChatResponse var messageResponse = new MessageResponse
{ {
Id = "id", Id = "id",
Model = "model", Model = "model",
@@ -67,7 +67,7 @@ public class ChatResponseTests : SerializationTest
StopReason = "stop reason", StopReason = "stop reason",
StopSequence = "stop sequence", StopSequence = "stop sequence",
Type = "type", Type = "type",
Usage = new ChatUsage Usage = new Usage
{ {
InputTokens = 1, InputTokens = 1,
OutputTokens = 2 OutputTokens = 2
@@ -78,7 +78,7 @@ public class ChatResponseTests : SerializationTest
] ]
}; };
var actual = Serialize(chatResponse); var actual = Serialize(messageResponse);
JsonAssert.Equal(expectedJson, actual); JsonAssert.Equal(expectedJson, actual);
} }
@@ -99,20 +99,20 @@ public class ChatResponseTests : SerializationTest
] ]
}"; }";
var chatResponse = Deserialize<ChatResponse>(json); var messageResponse = Deserialize<MessageResponse>(json);
chatResponse!.Id.Should().Be("id"); messageResponse!.Id.Should().Be("id");
chatResponse.Model.Should().Be("model"); messageResponse.Model.Should().Be("model");
chatResponse.Role.Should().Be("role"); messageResponse.Role.Should().Be("role");
chatResponse.StopReason.Should().Be("stop reason"); messageResponse.StopReason.Should().Be("stop reason");
chatResponse.StopSequence.Should().Be("stop sequence"); messageResponse.StopSequence.Should().Be("stop sequence");
chatResponse.Type.Should().Be("type"); messageResponse.Type.Should().Be("type");
chatResponse.Usage.Should().BeEquivalentTo(new ChatUsage messageResponse.Usage.Should().BeEquivalentTo(new Usage
{ {
InputTokens = 1, InputTokens = 1,
OutputTokens = 2 OutputTokens = 2
}); });
chatResponse.Content.Should().BeEquivalentTo(new List<Content> messageResponse.Content.Should().BeEquivalentTo(new List<Content>
{ {
new TextContent("text content"), new TextContent("text content"),
}); });
@@ -22,7 +22,7 @@ public class MessageStartEventDataTests : SerializationTest
[Fact] [Fact]
public void Constructor_WhenCalled_ItShouldInitializeProperties() public void Constructor_WhenCalled_ItShouldInitializeProperties()
{ {
var expectedMessage = new ChatResponse var expectedMessage = new MessageResponse
{ {
Id = "msg_1nZdL29xx5MUA1yADyHTEsnR8uuvGzszyY", Id = "msg_1nZdL29xx5MUA1yADyHTEsnR8uuvGzszyY",
Type = "message", Type = "message",
@@ -31,7 +31,7 @@ public class MessageStartEventDataTests : SerializationTest
Model = "claude-3-5-sonnet-20240620", Model = "claude-3-5-sonnet-20240620",
StopReason = string.Empty, StopReason = string.Empty,
StopSequence = string.Empty, StopSequence = string.Empty,
Usage = new ChatUsage { InputTokens = 25, OutputTokens = 1 } Usage = new Usage { InputTokens = 25, OutputTokens = 1 }
}; };
var messageStartEventData = new MessageStartEventData(expectedMessage); var messageStartEventData = new MessageStartEventData(expectedMessage);
@@ -42,7 +42,7 @@ public class MessageStartEventDataTests : SerializationTest
[Fact] [Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape() public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{ {
var expectedMessage = new ChatResponse var expectedMessage = new MessageResponse
{ {
Id = "msg_1nZdL29xx5MUA1yADyHTEsnR8uuvGzszyY", Id = "msg_1nZdL29xx5MUA1yADyHTEsnR8uuvGzszyY",
Type = "message", Type = "message",
@@ -51,7 +51,7 @@ public class MessageStartEventDataTests : SerializationTest
Model = "claude-3-5-sonnet-20240620", Model = "claude-3-5-sonnet-20240620",
StopReason = string.Empty, StopReason = string.Empty,
StopSequence = string.Empty, StopSequence = string.Empty,
Usage = new ChatUsage { InputTokens = 25, OutputTokens = 1 } Usage = new Usage { InputTokens = 25, OutputTokens = 1 }
}; };
var messageStartEventData = new MessageStartEventData(expectedMessage); var messageStartEventData = new MessageStartEventData(expectedMessage);
@@ -64,7 +64,7 @@ public class MessageStartEventDataTests : SerializationTest
[Fact] [Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedValues() public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedValues()
{ {
var expectedMessage = new ChatResponse var expectedMessage = new MessageResponse
{ {
Id = "msg_1nZdL29xx5MUA1yADyHTEsnR8uuvGzszyY", Id = "msg_1nZdL29xx5MUA1yADyHTEsnR8uuvGzszyY",
Type = "message", Type = "message",
@@ -73,7 +73,7 @@ public class MessageStartEventDataTests : SerializationTest
Model = "claude-3-5-sonnet-20240620", Model = "claude-3-5-sonnet-20240620",
StopReason = string.Empty, StopReason = string.Empty,
StopSequence = string.Empty, StopSequence = string.Empty,
Usage = new ChatUsage { InputTokens = 25, OutputTokens = 1 } Usage = new Usage { InputTokens = 25, OutputTokens = 1 }
}; };
var messageStartEventData = Deserialize<MessageStartEventData>(_testJson); var messageStartEventData = Deserialize<MessageStartEventData>(_testJson);
@@ -1,6 +1,6 @@
namespace AnthropicClient.Tests.Unit.Models; namespace AnthropicClient.Tests.Unit.Models;
public class ChatMessageTests : SerializationTest public class MessageTests : SerializationTest
{ {
private readonly string _testJson = @"{ private readonly string _testJson = @"{
""role"": ""assistant"", ""role"": ""assistant"",
@@ -15,10 +15,10 @@ public class ChatMessageTests : SerializationTest
var role = "assistant"; var role = "assistant";
var content = new List<Content> { new TextContent("text") }; var content = new List<Content> { new TextContent("text") };
var chatMessage = new ChatMessage(role, content); var message = new Message(role, content);
chatMessage.Role.Should().Be(role); message.Role.Should().Be(role);
chatMessage.Content.Should().BeSameAs(content); message.Content.Should().BeSameAs(content);
} }
[Fact] [Fact]
@@ -26,7 +26,7 @@ public class ChatMessageTests : SerializationTest
{ {
var content = new List<Content> { new TextContent("text") }; var content = new List<Content> { new TextContent("text") };
var action = () => new ChatMessage(null!, content); var action = () => new Message(null!, content);
action.Should().Throw<ArgumentNullException>(); action.Should().Throw<ArgumentNullException>();
} }
@@ -36,7 +36,7 @@ public class ChatMessageTests : SerializationTest
{ {
var role = "assistant"; var role = "assistant";
var action = () => new ChatMessage(role, null!); var action = () => new Message(role, null!);
action.Should().Throw<ArgumentNullException>(); action.Should().Throw<ArgumentNullException>();
} }
@@ -47,7 +47,7 @@ public class ChatMessageTests : SerializationTest
var role = "invalid"; var role = "invalid";
var content = new List<Content> { new TextContent("text") }; var content = new List<Content> { new TextContent("text") };
var action = () => new ChatMessage(role, content); var action = () => new Message(role, content);
action.Should().Throw<ArgumentException>(); action.Should().Throw<ArgumentException>();
} }
@@ -57,22 +57,22 @@ public class ChatMessageTests : SerializationTest
{ {
var role = "assistant"; var role = "assistant";
var content = new List<Content> { new TextContent("text") }; var content = new List<Content> { new TextContent("text") };
var chatMessage = new ChatMessage(role, content); var message = new Message(role, content);
var actual = Serialize(chatMessage); var actual = Serialize(message);
JsonAssert.Equal(_testJson, actual); JsonAssert.Equal(_testJson, actual);
} }
[Fact] [Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldReturnChatMessage() public void JsonDeserialization_WhenDeserialized_ItShouldReturnMessage()
{ {
var chatMessage = Deserialize<ChatMessage>(_testJson); var message = Deserialize<Message>(_testJson);
chatMessage.Should().NotBeNull(); message.Should().NotBeNull();
chatMessage!.Role.Should().Be("assistant"); message!.Role.Should().Be("assistant");
chatMessage.Content.Should().HaveCount(1); message.Content.Should().HaveCount(1);
chatMessage.Content[0].Should().BeOfType<TextContent>(); message.Content[0].Should().BeOfType<TextContent>();
chatMessage.Content[0].As<TextContent>().Text.Should().Be("text"); message.Content[0].As<TextContent>().Text.Should().Be("text");
} }
} }
@@ -1,6 +1,6 @@
namespace AnthropicClient.Tests.Unit.Models; namespace AnthropicClient.Tests.Unit.Models;
public class StreamChatMessageRequestTests : SerializationTest public class StreamMessageRequestTests : SerializationTest
{ {
private readonly string _testJson = @"{ private readonly string _testJson = @"{
""model"": ""claude-3-sonnet-20240229"", ""model"": ""claude-3-sonnet-20240229"",
@@ -23,7 +23,7 @@ public class StreamChatMessageRequestTests : SerializationTest
public void Constructor_WhenCalled_ItShouldInitializeProperties() public void Constructor_WhenCalled_ItShouldInitializeProperties()
{ {
var model = AnthropicModels.Claude3Sonnet; var model = AnthropicModels.Claude3Sonnet;
var messages = new List<ChatMessage> { new() }; var messages = new List<Message> { new() };
var maxTokens = 512; var maxTokens = 512;
var system = "test-system"; var system = "test-system";
var metadata = new Dictionary<string, object> { ["test"] = "test" }; var metadata = new Dictionary<string, object> { ["test"] = "test" };
@@ -33,7 +33,7 @@ public class StreamChatMessageRequestTests : SerializationTest
var toolChoice = new AutoToolChoice(); var toolChoice = new AutoToolChoice();
var tools = new List<Tool>(); var tools = new List<Tool>();
var chatMessageRequest = new StreamChatMessageRequest( var messageRequest = new StreamMessageRequest(
model: model, model: model,
messages: messages, messages: messages,
maxTokens: maxTokens, maxTokens: maxTokens,
@@ -46,23 +46,23 @@ public class StreamChatMessageRequestTests : SerializationTest
tools: tools tools: tools
); );
chatMessageRequest.Model.Should().Be(model); messageRequest.Model.Should().Be(model);
chatMessageRequest.Messages.Should().BeSameAs(messages); messageRequest.Messages.Should().BeSameAs(messages);
chatMessageRequest.MaxTokens.Should().Be(maxTokens); messageRequest.MaxTokens.Should().Be(maxTokens);
chatMessageRequest.System.Should().Be(system); messageRequest.System.Should().Be(system);
chatMessageRequest.Metadata.Should().BeSameAs(metadata); messageRequest.Metadata.Should().BeSameAs(metadata);
chatMessageRequest.Temperature.Should().Be(temperature); messageRequest.Temperature.Should().Be(temperature);
chatMessageRequest.TopK.Should().Be(topK); messageRequest.TopK.Should().Be(topK);
chatMessageRequest.TopP.Should().Be(topP); messageRequest.TopP.Should().Be(topP);
chatMessageRequest.ToolChoice.Should().Be(toolChoice); messageRequest.ToolChoice.Should().Be(toolChoice);
chatMessageRequest.Tools.Should().BeSameAs(tools); messageRequest.Tools.Should().BeSameAs(tools);
chatMessageRequest.Stream.Should().BeTrue(); messageRequest.Stream.Should().BeTrue();
} }
[Fact] [Fact]
public void Constructor_WhenCalledAndModelIsNull_ItShouldThrowArgumentNullException() public void Constructor_WhenCalledAndModelIsNull_ItShouldThrowArgumentNullException()
{ {
var action = () => new StreamChatMessageRequest( var action = () => new StreamMessageRequest(
model: null!, model: null!,
messages: [new()] messages: [new()]
); );
@@ -73,7 +73,7 @@ public class StreamChatMessageRequestTests : SerializationTest
[Fact] [Fact]
public void Constructor_WhenCalledAndMessagesIsNull_ItShouldThrowArgumentNullException() public void Constructor_WhenCalledAndMessagesIsNull_ItShouldThrowArgumentNullException()
{ {
var action = () => new StreamChatMessageRequest( var action = () => new StreamMessageRequest(
model: AnthropicModels.Claude3Sonnet, model: AnthropicModels.Claude3Sonnet,
messages: null! messages: null!
); );
@@ -84,7 +84,7 @@ public class StreamChatMessageRequestTests : SerializationTest
[Fact] [Fact]
public void Constructor_WhenCalledAndModelIsInvalid_ItShouldThrowArgumentException() public void Constructor_WhenCalledAndModelIsInvalid_ItShouldThrowArgumentException()
{ {
var action = () => new StreamChatMessageRequest( var action = () => new StreamMessageRequest(
model: "invalid-model", model: "invalid-model",
messages: [new()] messages: [new()]
); );
@@ -95,7 +95,7 @@ public class StreamChatMessageRequestTests : SerializationTest
[Fact] [Fact]
public void Constructor_WhenCalledAndMessagesIsEmpty_ItShouldThrowArgumentException() public void Constructor_WhenCalledAndMessagesIsEmpty_ItShouldThrowArgumentException()
{ {
var action = () => new StreamChatMessageRequest( var action = () => new StreamMessageRequest(
model: AnthropicModels.Claude3Sonnet, model: AnthropicModels.Claude3Sonnet,
messages: [] messages: []
); );
@@ -106,7 +106,7 @@ public class StreamChatMessageRequestTests : SerializationTest
[Fact] [Fact]
public void Constructor_WhenCalledAndMaxTokensIsInvalid_ItShouldThrowArgumentException() public void Constructor_WhenCalledAndMaxTokensIsInvalid_ItShouldThrowArgumentException()
{ {
var action = () => new StreamChatMessageRequest( var action = () => new StreamMessageRequest(
model: AnthropicModels.Claude3Sonnet, model: AnthropicModels.Claude3Sonnet,
messages: [new()], messages: [new()],
maxTokens: 0 maxTokens: 0
@@ -120,7 +120,7 @@ public class StreamChatMessageRequestTests : SerializationTest
[InlineData(2)] [InlineData(2)]
public void Constructor_WhenCalledAndTemperatureIsInvalid_ItShouldThrowArgumentException(decimal temperature) public void Constructor_WhenCalledAndTemperatureIsInvalid_ItShouldThrowArgumentException(decimal temperature)
{ {
var action = () => new StreamChatMessageRequest( var action = () => new StreamMessageRequest(
model: AnthropicModels.Claude3Sonnet, model: AnthropicModels.Claude3Sonnet,
messages: [new()], messages: [new()],
temperature: temperature temperature: temperature
@@ -132,7 +132,7 @@ public class StreamChatMessageRequestTests : SerializationTest
[Fact] [Fact]
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape() public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
{ {
var messages = new List<ChatMessage>() var messages = new List<Message>()
{ {
new() new()
{ {
@@ -154,7 +154,7 @@ public class StreamChatMessageRequestTests : SerializationTest
var toolChoice = new AutoToolChoice(); var toolChoice = new AutoToolChoice();
var tools = new List<Tool>(); var tools = new List<Tool>();
var chatMessageRequest = new StreamChatMessageRequest( var messageRequest = new StreamMessageRequest(
model: model, model: model,
messages: messages, messages: messages,
maxTokens: maxTokens, maxTokens: maxTokens,
@@ -167,7 +167,7 @@ public class StreamChatMessageRequestTests : SerializationTest
tools: tools tools: tools
); );
var actual = Serialize(chatMessageRequest); var actual = Serialize(messageRequest);
JsonAssert.Equal(_testJson, actual); JsonAssert.Equal(_testJson, actual);
} }
@@ -175,23 +175,23 @@ public class StreamChatMessageRequestTests : SerializationTest
[Fact] [Fact]
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape() public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape()
{ {
var chatMessageRequest = Deserialize<StreamChatMessageRequest>(_testJson); var messageRequest = Deserialize<StreamMessageRequest>(_testJson);
chatMessageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet); messageRequest!.Model.Should().Be(AnthropicModels.Claude3Sonnet);
chatMessageRequest.System.Should().Be("test-system"); messageRequest.System.Should().Be("test-system");
chatMessageRequest.Messages.Should().HaveCount(1); messageRequest.Messages.Should().HaveCount(1);
chatMessageRequest.MaxTokens.Should().Be(512); messageRequest.MaxTokens.Should().Be(512);
chatMessageRequest.Metadata.Should().HaveCount(1); messageRequest.Metadata.Should().HaveCount(1);
var testValue = chatMessageRequest.Metadata!.GetValueOrDefault("test")!.ToString(); var testValue = messageRequest.Metadata!.GetValueOrDefault("test")!.ToString();
testValue.Should().Be("test"); testValue.Should().Be("test");
chatMessageRequest.Temperature.Should().Be(0.5m); messageRequest.Temperature.Should().Be(0.5m);
chatMessageRequest.TopK.Should().Be(10); messageRequest.TopK.Should().Be(10);
chatMessageRequest.TopP.Should().Be(0.5m); messageRequest.TopP.Should().Be(0.5m);
chatMessageRequest.ToolChoice.Should().BeOfType<AutoToolChoice>(); messageRequest.ToolChoice.Should().BeOfType<AutoToolChoice>();
chatMessageRequest.ToolChoice!.Type.Should().Be("auto"); messageRequest.ToolChoice!.Type.Should().Be("auto");
chatMessageRequest.Tools.Should().HaveCount(0); messageRequest.Tools.Should().HaveCount(0);
chatMessageRequest.Stream.Should().BeTrue(); messageRequest.Stream.Should().BeTrue();
} }
} }
@@ -0,0 +1,34 @@
namespace AnthropicClient.Tests.Unit.Models;
public class ToolCallResultTests
{
[Fact]
public void Success_WhenCalled_ItShouldReturnSuccessResult()
{
var value = "success";
var actual = ToolCallResult<string>.Success(value);
actual.IsSuccess.Should().BeTrue();
actual.IsFailure.Should().BeFalse();
actual.Value.Should().Be(value);
var action = () => { var _ = actual.Error; };
action.Should().Throw<InvalidOperationException>();
}
[Fact]
public void Failure_WhenCalled_ItShouldReturnFailureResult()
{
var error = new Exception();
var actual = ToolCallResult<string>.Failure(error);
actual.IsSuccess.Should().BeFalse();
actual.IsFailure.Should().BeTrue();
actual.Error.Should().Be(error);
var action = () => { var _ = actual.Value; };
action.Should().Throw<InvalidOperationException>();
}
}
@@ -16,7 +16,6 @@ public class ToolCallTests : SerializationTest
result.IsSuccess.Should().BeTrue(); result.IsSuccess.Should().BeTrue();
result.Value.Should().Be("Hello, World!"); result.Value.Should().Be("Hello, World!");
result.Error.Should().BeNull();
} }
[Fact] [Fact]
@@ -30,7 +29,6 @@ public class ToolCallTests : SerializationTest
var result = await toolCall.InvokeAsync(); var result = await toolCall.InvokeAsync();
result.IsSuccess.Should().BeFalse(); result.IsSuccess.Should().BeFalse();
result.Value.Should().BeNull();
result.Error.Should().NotBeNull(); result.Error.Should().NotBeNull();
} }
@@ -46,7 +44,6 @@ public class ToolCallTests : SerializationTest
result.IsSuccess.Should().BeTrue(); result.IsSuccess.Should().BeTrue();
result.Value.Should().Be("Hello, World!"); result.Value.Should().Be("Hello, World!");
result.Error.Should().BeNull();
} }
[Fact] [Fact]
@@ -60,7 +57,6 @@ public class ToolCallTests : SerializationTest
var result = await toolCall.InvokeAsync(); var result = await toolCall.InvokeAsync();
result.IsSuccess.Should().BeFalse(); result.IsSuccess.Should().BeFalse();
result.Value.Should().BeNull();
result.Error.Should().NotBeNull(); result.Error.Should().NotBeNull();
} }
@@ -78,7 +74,6 @@ public class ToolCallTests : SerializationTest
result.IsSuccess.Should().BeTrue(); result.IsSuccess.Should().BeTrue();
result.Value.Should().Be("42"); result.Value.Should().Be("42");
result.Error.Should().BeNull();
} }
[Fact] [Fact]
@@ -94,7 +89,6 @@ public class ToolCallTests : SerializationTest
var result = await toolCall.InvokeAsync(); var result = await toolCall.InvokeAsync();
result.IsSuccess.Should().BeFalse(); result.IsSuccess.Should().BeFalse();
result.Value.Should().BeNull();
result.Error.Should().NotBeNull(); result.Error.Should().NotBeNull();
} }
@@ -112,7 +106,6 @@ public class ToolCallTests : SerializationTest
result.IsSuccess.Should().BeTrue(); result.IsSuccess.Should().BeTrue();
result.Value.Should().Be("42"); result.Value.Should().Be("42");
result.Error.Should().BeNull();
} }
[Fact] [Fact]
@@ -128,7 +121,6 @@ public class ToolCallTests : SerializationTest
var result = await toolCall.InvokeAsync(); var result = await toolCall.InvokeAsync();
result.IsSuccess.Should().BeFalse(); result.IsSuccess.Should().BeFalse();
result.Value.Should().BeNull();
result.Error.Should().NotBeNull(); result.Error.Should().NotBeNull();
} }
@@ -146,7 +138,6 @@ public class ToolCallTests : SerializationTest
result.IsSuccess.Should().BeTrue(); result.IsSuccess.Should().BeTrue();
result.Value.Should().Be("42"); result.Value.Should().Be("42");
result.Error.Should().BeNull();
} }
[Fact] [Fact]
@@ -162,7 +153,6 @@ public class ToolCallTests : SerializationTest
result.IsSuccess.Should().BeTrue(); result.IsSuccess.Should().BeTrue();
result.Value.Should().Be("42"); result.Value.Should().Be("42");
result.Error.Should().BeNull();
} }
[Fact] [Fact]
@@ -177,7 +167,6 @@ public class ToolCallTests : SerializationTest
var result = await toolCall.InvokeAsync(); var result = await toolCall.InvokeAsync();
result.IsSuccess.Should().BeFalse(); result.IsSuccess.Should().BeFalse();
result.Value.Should().BeNull();
result.Error.Should().NotBeNull(); result.Error.Should().NotBeNull();
} }
@@ -195,7 +184,6 @@ public class ToolCallTests : SerializationTest
result.IsSuccess.Should().BeTrue(); result.IsSuccess.Should().BeTrue();
result.Value.Should().Be("Monday"); result.Value.Should().Be("Monday");
result.Error.Should().BeNull();
} }
[Fact] [Fact]
@@ -219,7 +207,6 @@ public class ToolCallTests : SerializationTest
result.IsSuccess.Should().BeTrue(); result.IsSuccess.Should().BeTrue();
result.Value.Should().Be("John"); result.Value.Should().Be("John");
result.Error.Should().BeNull();
} }
[Fact] [Fact]
@@ -236,7 +223,6 @@ public class ToolCallTests : SerializationTest
result.IsSuccess.Should().BeTrue(); result.IsSuccess.Should().BeTrue();
result.Value.Should().BeNull(); result.Value.Should().BeNull();
result.Error.Should().BeNull();
} }
[Fact] [Fact]
@@ -253,7 +239,6 @@ public class ToolCallTests : SerializationTest
result.IsSuccess.Should().BeTrue(); result.IsSuccess.Should().BeTrue();
result.Value.Should().BeNull(); result.Value.Should().BeNull();
result.Error.Should().BeNull();
} }
} }
@@ -160,7 +160,7 @@ public class ToolTests : SerializationTest
public void CreateFromInstanceMethod_WhenCalled_ItShouldReturnTool() public void CreateFromInstanceMethod_WhenCalled_ItShouldReturnTool()
{ {
var instance = new TestClass(); var instance = new TestClass();
var tool = Tool.CreateFromInstanceMethod("test name", "description", instance, nameof(TestClass.TestInstanceMethod)); var tool = Tool.CreateFromInstanceMethod("test name", "description", instance, nameof(instance.TestInstanceMethod));
var expectedSchema = new JsonObject() var expectedSchema = new JsonObject()
{ {
@@ -301,7 +301,7 @@ public class ToolTests : SerializationTest
tool.DisplayName.Should().Be("Name"); tool.DisplayName.Should().Be("Name");
tool.Description.Should().Be("Description"); tool.Description.Should().Be("Description");
tool.Function.Method.Name.Should().Be(nameof(ProperTool.GetWeather)); tool.Function.Method.Name.Should().Be(nameof(ProperTool.GetWeather));
tool.Function.Instance.Should().BeNull(); tool.Function.Instance.Should().BeOfType<ProperTool>();
tool.InputSchema.Should().BeEquivalentTo( tool.InputSchema.Should().BeEquivalentTo(
expectedSchema, expectedSchema,
t => t.IgnoringCyclicReferences() t => t.IgnoringCyclicReferences()
@@ -1,6 +1,6 @@
namespace AnthropicClient.Tests.Unit.Models; namespace AnthropicClient.Tests.Unit.Models;
public class ChatUsageTests : SerializationTest public class UsageTests : SerializationTest
{ {
[Fact] [Fact]
public void Constructor_WhenCalled_ShouldInitializeProperties() public void Constructor_WhenCalled_ShouldInitializeProperties()
@@ -8,14 +8,14 @@ public class ChatUsageTests : SerializationTest
var expectedInputTokens = 1; var expectedInputTokens = 1;
var expectedOutputTokens = 2; var expectedOutputTokens = 2;
var chatUsage = new ChatUsage var usage = new Usage
{ {
InputTokens = expectedInputTokens, InputTokens = expectedInputTokens,
OutputTokens = expectedOutputTokens OutputTokens = expectedOutputTokens
}; };
chatUsage.InputTokens.Should().Be(expectedInputTokens); usage.InputTokens.Should().Be(expectedInputTokens);
chatUsage.OutputTokens.Should().Be(expectedOutputTokens); usage.OutputTokens.Should().Be(expectedOutputTokens);
} }
[Fact] [Fact]
@@ -23,13 +23,13 @@ public class ChatUsageTests : SerializationTest
{ {
var expectedJson = @"{ ""input_tokens"": 1, ""output_tokens"": 2 }"; var expectedJson = @"{ ""input_tokens"": 1, ""output_tokens"": 2 }";
var chatUsage = new ChatUsage var usage = new Usage
{ {
InputTokens = 1, InputTokens = 1,
OutputTokens = 2 OutputTokens = 2
}; };
var actual = Serialize(chatUsage); var actual = Serialize(usage);
JsonAssert.Equal(expectedJson, actual); JsonAssert.Equal(expectedJson, actual);
} }
@@ -39,9 +39,9 @@ public class ChatUsageTests : SerializationTest
{ {
var json = @"{ ""input_tokens"": 1, ""output_tokens"": 2 }"; var json = @"{ ""input_tokens"": 1, ""output_tokens"": 2 }";
var chatUsage = Deserialize<ChatUsage>(json); var usage = Deserialize<Usage>(json);
chatUsage!.InputTokens.Should().Be(1); usage!.InputTokens.Should().Be(1);
chatUsage.OutputTokens.Should().Be(2); usage.OutputTokens.Should().Be(2);
} }
} }
@@ -1,5 +1,3 @@
using System.Text.Json.Nodes;
namespace AnthropicClient.Tests.Unit.Utils; namespace AnthropicClient.Tests.Unit.Utils;
public class JsonSchemaGeneratorTests public class JsonSchemaGeneratorTests
+4 -1
View File
@@ -1,10 +1,13 @@
global using System.Text; global using System.Text;
global using System.Text.Json; global using System.Text.Json;
global using System.Text.Json.Nodes;
global using System.Text.Json.JsonDiffPatch.Xunit; global using System.Text.Json.JsonDiffPatch.Xunit;
global using AnthropicClient.Models; global using AnthropicClient.Models;
global using AnthropicClient.Utils; global using AnthropicClient.Utils;
global using AnthropicClient.Json; global using AnthropicClient.Json;
global using AnthropicClient.Tests.Data;
global using AnthropicClient.Tests.Fixtures;
global using FluentAssertions; global using FluentAssertions;
@@ -12,4 +15,4 @@ global using Microsoft.Extensions.Configuration;
global using RichardSzalay.MockHttp; global using RichardSzalay.MockHttp;
global using System.Net; global using System.Net;
global using System.Collections; global using System.Collections;