Merge pull request #31 from StevanFreeborn/stevanfreeborn/feat/add-support-for-message-batching
feat: add support for message batching api
This commit is contained in:
@@ -971,3 +971,193 @@ foreach (var content in response.Value.Content)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Message Batches
|
||||
|
||||
Anthropic provides a feature called [Message Batches](https://docs.anthropic.com/en/docs/build-with-claude/message-batches) that allows you to send multiple messages in a single request. This feature is covered in depth in [Anthropic's API Documentation](https://docs.anthropic.com/en/docs/build-with-claude/message-batches).
|
||||
|
||||
#### Create a message batch
|
||||
|
||||
You can create a message batch that will consist of one or more requests to create messages.
|
||||
|
||||
```csharp
|
||||
using AnthropicClient;
|
||||
using AnthropicClient.Models;
|
||||
|
||||
var request = new MessageBatchRequest([
|
||||
new(
|
||||
Guid.NewGuid().ToString(),
|
||||
new(
|
||||
model: AnthropicModels.Claude3Haiku,
|
||||
messages: [new(MessageRole.User, [new TextContent("Hello!")])]
|
||||
)
|
||||
),
|
||||
]);
|
||||
|
||||
var response = await client.CreateMessageBatchAsync(request);
|
||||
|
||||
if (response.IsFailure)
|
||||
{
|
||||
Console.WriteLine("Failed to create message batch");
|
||||
Console.WriteLine("Error Type: {0}", response.Error.Error.Type);
|
||||
Console.WriteLine("Error Message: {0}", response.Error.Error.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine("Message Batch Id: {0}", response.Value.Id);
|
||||
```
|
||||
|
||||
#### Get a message batch
|
||||
|
||||
You can retrieve a message batch by its id.
|
||||
|
||||
```csharp
|
||||
using AnthropicClient;
|
||||
using AnthropicClient.Models;
|
||||
|
||||
var response = await client.GetMessageBatchAsync("batch-id");
|
||||
|
||||
if (response.IsFailure)
|
||||
{
|
||||
Console.WriteLine("Failed to get message batch");
|
||||
Console.WriteLine("Error Type: {0}", response.Error.Error.Type);
|
||||
Console.WriteLine("Error Message: {0}", response.Error.Error.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine("Message Batch Id: {0}", response.Value.Id);
|
||||
```
|
||||
|
||||
#### Get a message batch results
|
||||
|
||||
You can retrieve the results of a message batch by its id. The results are returned as an `IAsyncEnumerable` collection so that they can be streamed and processed as they are received.
|
||||
|
||||
```csharp
|
||||
using AnthropicClient;
|
||||
using AnthropicClient.Models;
|
||||
|
||||
var response = await client.GetMessageBatchResultsAsync("batch-id");
|
||||
|
||||
if (response.IsFailure)
|
||||
{
|
||||
Console.WriteLine("Failed to get message batch results");
|
||||
Console.WriteLine("Error Type: {0}", response.Error.Error.Type);
|
||||
Console.WriteLine("Error Message: {0}", response.Error.Error.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
await foreach (var item in response.Value)
|
||||
{
|
||||
Console.WriteLine("Item Custom Id: {0}", result.CustomId);
|
||||
|
||||
switch (item.Result)
|
||||
{
|
||||
case SucceededMessageBatchResult successResult:
|
||||
foreach (var content in successResult.Message.Content)
|
||||
{
|
||||
if (content is TextContent textContent)
|
||||
{
|
||||
Console.WriteLine("Message Batch Result: {0}", textContent.Text);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine("Message Batch Result: {0}", item.Result.Type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### List message batches
|
||||
|
||||
You can retrieve a page of message batches.
|
||||
|
||||
```csharp
|
||||
using AnthropicClient;
|
||||
using AnthropicClient.Models;
|
||||
|
||||
var response = await client.ListMessageBatchesAsync();
|
||||
|
||||
if (response.IsFailure)
|
||||
{
|
||||
Console.WriteLine("Failed to list message batches");
|
||||
Console.WriteLine("Error Type: {0}", response.Error.Error.Type);
|
||||
Console.WriteLine("Error Message: {0}", response.Error.Error.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var batch in response.Value.Data)
|
||||
{
|
||||
Console.WriteLine("Message Batch Id: {0}", batch.Id);
|
||||
}
|
||||
```
|
||||
|
||||
#### List all message batches
|
||||
|
||||
You can also retrieve all the pages of message batches without having to implement pagination yourself. This is done by returning an `IAsyncEnumerable` collection that can be streamed and processed as the pages are received.
|
||||
|
||||
```csharp
|
||||
using AnthropicClient;
|
||||
using AnthropicClient.Models;
|
||||
|
||||
var pageResponses = client.ListAllMessageBatchesAsync();
|
||||
|
||||
await foreach (var response in pageResponses)
|
||||
{
|
||||
if (response.IsFailure)
|
||||
{
|
||||
Console.WriteLine("Failed to list message batches");
|
||||
Console.WriteLine("Error Type: {0}", response.Error.Error.Type);
|
||||
Console.WriteLine("Error Message: {0}", response.Error.Error.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var batch in response.Value.Data)
|
||||
{
|
||||
Console.WriteLine("Message Batch Id: {0}", batch.Id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Cancel a message batch
|
||||
|
||||
You can cancel a message batch by its id.
|
||||
|
||||
```csharp
|
||||
using AnthropicClient;
|
||||
using AnthropicClient.Models;
|
||||
|
||||
var response = await client.CancelMessageBatchAsync("batch-id");
|
||||
|
||||
if (response.IsFailure)
|
||||
{
|
||||
Console.WriteLine("Failed to cancel message batch");
|
||||
Console.WriteLine("Error Type: {0}", response.Error.Error.Type);
|
||||
Console.WriteLine("Error Message: {0}", response.Error.Error.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine("Message Batch Id: {0}", response.Value.Id);
|
||||
Console.WriteLine("Message Batch Status: {0}", response.Value.ProcessingStatus);
|
||||
```
|
||||
|
||||
#### Delete a message batch
|
||||
|
||||
You can delete a message batch that is no longer being processed by its id.
|
||||
|
||||
```csharp
|
||||
using AnthropicClient;
|
||||
using AnthropicClient.Models;
|
||||
|
||||
var response = await client.DeleteMessageBatchAsync("batch-id");
|
||||
|
||||
if (response.IsFailure)
|
||||
{
|
||||
Console.WriteLine("Failed to delete message batch");
|
||||
Console.WriteLine("Error Type: {0}", response.Error.Error.Type);
|
||||
Console.WriteLine("Error Message: {0}", response.Error.Error.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine("Message Batch Id: {0}", response.Value.Id);
|
||||
```
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AnthropicClient.Json;
|
||||
using AnthropicClient.Models;
|
||||
@@ -9,62 +8,14 @@ using AnthropicClient.Utils;
|
||||
|
||||
namespace AnthropicClient;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a client for interacting with the Anthropic API.
|
||||
/// </summary>
|
||||
public interface IAnthropicApiClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a message asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="request">The message request to create.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/>.</returns>
|
||||
Task<AnthropicResult<MessageResponse>> CreateMessageAsync(MessageRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a message asynchronously and streams the response.
|
||||
/// </summary>
|
||||
/// <param name="request">The message request to create.</param>
|
||||
/// <returns>An asynchronous enumerable that yields the response event by event.</returns>
|
||||
IAsyncEnumerable<AnthropicEvent> CreateMessageAsync(StreamMessageRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Counts the tokens in a message asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="request">The count message tokens request.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="TokenCountResponse"/>.</returns>
|
||||
Task<AnthropicResult<TokenCountResponse>> CountMessageTokensAsync(CountMessageTokensRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Lists the models asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="request">The paging request to use for listing the models.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="Page{T}"/> where T is <see cref="AnthropicModel"/>.</returns>
|
||||
Task<AnthropicResult<Page<AnthropicModel>>> ListModelsAsync(PagingRequest? request = null);
|
||||
|
||||
/// <summary>
|
||||
/// Lists the models asynchronously
|
||||
/// </summary>
|
||||
/// <param name="limit">The maximum number of models to return in each page.</param>
|
||||
/// <returns>An asynchronous enumerable that yields the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="Page{T}"/> where T is <see cref="AnthropicModel"/>.</returns>
|
||||
///
|
||||
IAsyncEnumerable<AnthropicResult<Page<AnthropicModel>>> ListAllModelsAsync(int limit = 20);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a model by its ID asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="modelId">The ID of the model to get.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="AnthropicModel"/>.</returns>
|
||||
Task<AnthropicResult<AnthropicModel>> GetModelAsync(string modelId);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IAnthropicApiClient"/>
|
||||
public class AnthropicApiClient : IAnthropicApiClient
|
||||
{
|
||||
private const string BaseUrl = "https://api.anthropic.com/v1/";
|
||||
private const string ApiKeyHeader = "x-api-key";
|
||||
private const string MessagesEndpoint = "messages";
|
||||
private const string CountTokensEndpoint = "messages/count_tokens";
|
||||
private string CountTokensEndpoint => $"{MessagesEndpoint}/count_tokens";
|
||||
private string MessageBatchesEndpoint => $"{MessagesEndpoint}/batches";
|
||||
private const string ModelsEndpoint = "models";
|
||||
private const string JsonContentType = "application/json";
|
||||
private const string EventPrefix = "event:";
|
||||
@@ -288,20 +239,90 @@ public class AnthropicApiClient : IAnthropicApiClient
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<AnthropicResult<TokenCountResponse>> CountMessageTokensAsync(CountMessageTokensRequest request)
|
||||
public async Task<AnthropicResult<MessageBatchResponse>> CreateMessageBatchAsync(MessageBatchRequest request)
|
||||
{
|
||||
var response = await SendRequestAsync(CountTokensEndpoint, request);
|
||||
var response = await SendRequestAsync(MessageBatchesEndpoint, request);
|
||||
return await CreateResultAsync<MessageBatchResponse>(response);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<AnthropicResult<MessageBatchResponse>> GetMessageBatchAsync(string batchId)
|
||||
{
|
||||
var response = await SendRequestAsync($"{MessageBatchesEndpoint}/{batchId}");
|
||||
return await CreateResultAsync<MessageBatchResponse>(response);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<AnthropicResult<Page<MessageBatchResponse>>> ListMessageBatchesAsync(PagingRequest? request = null)
|
||||
{
|
||||
var pagingRequest = request ?? new PagingRequest();
|
||||
var endpoint = $"{MessageBatchesEndpoint}?{pagingRequest.ToQueryParameters()}";
|
||||
var response = await SendRequestAsync(endpoint);
|
||||
return await CreateResultAsync<Page<MessageBatchResponse>>(response);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<AnthropicResult<Page<MessageBatchResponse>>> ListAllMessageBatchesAsync(int limit = 20)
|
||||
{
|
||||
await foreach (var result in GetAllPagesAsync<MessageBatchResponse>(MessageBatchesEndpoint, limit))
|
||||
{
|
||||
yield return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<AnthropicResult<MessageBatchResponse>> CancelMessageBatchAsync(string batchId)
|
||||
{
|
||||
var endpoint = $"{MessageBatchesEndpoint}/{batchId}/cancel";
|
||||
var response = await SendRequestAsync(endpoint, HttpMethod.Post);
|
||||
return await CreateResultAsync<MessageBatchResponse>(response);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<AnthropicResult<MessageBatchDeleteResponse>> DeleteMessageBatchAsync(string batchId)
|
||||
{
|
||||
var endpoint = $"{MessageBatchesEndpoint}/{batchId}";
|
||||
var response = await SendRequestAsync(endpoint, HttpMethod.Delete);
|
||||
return await CreateResultAsync<MessageBatchDeleteResponse>(response);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<AnthropicResult<IAsyncEnumerable<MessageBatchResultItem>>> GetMessageBatchResultsAsync(string batchId)
|
||||
{
|
||||
var response = await SendRequestAsync($"{MessageBatchesEndpoint}/{batchId}/results");
|
||||
var anthropicHeaders = new AnthropicHeaders(response.Headers);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode is false)
|
||||
{
|
||||
var error = Deserialize<AnthropicError>(responseContent) ?? new AnthropicError();
|
||||
return AnthropicResult<TokenCountResponse>.Failure(error, anthropicHeaders);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
var error = Deserialize<AnthropicError>(content) ?? new AnthropicError();
|
||||
return AnthropicResult<IAsyncEnumerable<MessageBatchResultItem>>.Failure(error, anthropicHeaders);
|
||||
}
|
||||
|
||||
var msgResponse = Deserialize<TokenCountResponse>(responseContent) ?? new TokenCountResponse();
|
||||
return AnthropicResult<TokenCountResponse>.Success(msgResponse, anthropicHeaders);
|
||||
return AnthropicResult<IAsyncEnumerable<MessageBatchResultItem>>.Success(ReadResultsAsync(), anthropicHeaders);
|
||||
|
||||
async IAsyncEnumerable<MessageBatchResultItem> ReadResultsAsync()
|
||||
{
|
||||
using var responseContent = await response.Content.ReadAsStreamAsync();
|
||||
using var streamReader = new StreamReader(responseContent);
|
||||
|
||||
var line = await streamReader.ReadLineAsync();
|
||||
|
||||
while (line is not null)
|
||||
{
|
||||
var resultItem = Deserialize<MessageBatchResultItem>(line) ?? new MessageBatchResultItem();
|
||||
yield return resultItem;
|
||||
|
||||
line = await streamReader.ReadLineAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<AnthropicResult<TokenCountResponse>> CountMessageTokensAsync(CountMessageTokensRequest request)
|
||||
{
|
||||
var response = await SendRequestAsync(CountTokensEndpoint, request);
|
||||
return await CreateResultAsync<TokenCountResponse>(response);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -310,24 +331,30 @@ public class AnthropicApiClient : IAnthropicApiClient
|
||||
var pagingRequest = request ?? new PagingRequest();
|
||||
var endpoint = $"{ModelsEndpoint}?{pagingRequest.ToQueryParameters()}";
|
||||
var response = await SendRequestAsync(endpoint);
|
||||
var anthropicHeaders = new AnthropicHeaders(response.Headers);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode is false)
|
||||
{
|
||||
var error = Deserialize<AnthropicError>(responseContent) ?? new AnthropicError();
|
||||
return AnthropicResult<Page<AnthropicModel>>.Failure(error, anthropicHeaders);
|
||||
}
|
||||
|
||||
var page = Deserialize<Page<AnthropicModel>>(responseContent) ?? new Page<AnthropicModel>();
|
||||
return AnthropicResult<Page<AnthropicModel>>.Success(page, anthropicHeaders);
|
||||
return await CreateResultAsync<Page<AnthropicModel>>(response);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<AnthropicResult<Page<AnthropicModel>>> ListAllModelsAsync(int limit = 20)
|
||||
{
|
||||
await foreach (var result in GetAllPagesAsync<AnthropicModel>(ModelsEndpoint, limit))
|
||||
{
|
||||
yield return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<AnthropicResult<AnthropicModel>> GetModelAsync(string modelId)
|
||||
{
|
||||
var endpoint = $"{ModelsEndpoint}/{modelId}";
|
||||
var response = await SendRequestAsync(endpoint);
|
||||
return await CreateResultAsync<AnthropicModel>(response);
|
||||
}
|
||||
|
||||
private async IAsyncEnumerable<AnthropicResult<Page<T>>> GetAllPagesAsync<T>(string endpoint, int limit = 20)
|
||||
{
|
||||
var pagingRequest = new PagingRequest(limit: limit);
|
||||
string Endpoint() => $"{ModelsEndpoint}?{pagingRequest.ToQueryParameters()}";
|
||||
string Endpoint() => $"{endpoint}?{pagingRequest.ToQueryParameters()}";
|
||||
bool hasMore;
|
||||
|
||||
do
|
||||
@@ -339,11 +366,11 @@ public class AnthropicApiClient : IAnthropicApiClient
|
||||
if (response.IsSuccessStatusCode is false)
|
||||
{
|
||||
var error = Deserialize<AnthropicError>(responseContent) ?? new AnthropicError();
|
||||
yield return AnthropicResult<Page<AnthropicModel>>.Failure(error, anthropicHeaders);
|
||||
yield return AnthropicResult<Page<T>>.Failure(error, anthropicHeaders);
|
||||
yield break;
|
||||
}
|
||||
|
||||
var page = Deserialize<Page<AnthropicModel>>(responseContent) ?? new Page<AnthropicModel>();
|
||||
var page = Deserialize<Page<T>>(responseContent) ?? new Page<T>();
|
||||
|
||||
if (page.HasMore && page.LastId is not null)
|
||||
{
|
||||
@@ -355,28 +382,10 @@ public class AnthropicApiClient : IAnthropicApiClient
|
||||
hasMore = false;
|
||||
}
|
||||
|
||||
yield return AnthropicResult<Page<AnthropicModel>>.Success(page, anthropicHeaders);
|
||||
yield return AnthropicResult<Page<T>>.Success(page, anthropicHeaders);
|
||||
} while (hasMore);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<AnthropicResult<AnthropicModel>> GetModelAsync(string modelId)
|
||||
{
|
||||
var endpoint = $"{ModelsEndpoint}/{modelId}";
|
||||
var response = await SendRequestAsync(endpoint);
|
||||
var anthropicHeaders = new AnthropicHeaders(response.Headers);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode is false)
|
||||
{
|
||||
var error = Deserialize<AnthropicError>(responseContent) ?? new AnthropicError();
|
||||
return AnthropicResult<AnthropicModel>.Failure(error, anthropicHeaders);
|
||||
}
|
||||
|
||||
var model = Deserialize<AnthropicModel>(responseContent) ?? new AnthropicModel();
|
||||
return AnthropicResult<AnthropicModel>.Success(model, anthropicHeaders);
|
||||
}
|
||||
|
||||
private ToolCall? GetToolCall(MessageResponse response, List<Tool> tools)
|
||||
{
|
||||
var toolUse = response.Content.OfType<ToolUseContent>().FirstOrDefault();
|
||||
@@ -396,9 +405,25 @@ public class AnthropicApiClient : IAnthropicApiClient
|
||||
return new ToolCall(tool, toolUse);
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> SendRequestAsync(string endpoint)
|
||||
private async Task<AnthropicResult<T>> CreateResultAsync<T>(HttpResponseMessage response) where T : new()
|
||||
{
|
||||
return await _httpClient.GetAsync(endpoint);
|
||||
var anthropicHeaders = new AnthropicHeaders(response.Headers);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode is false)
|
||||
{
|
||||
var error = Deserialize<AnthropicError>(responseContent) ?? new AnthropicError();
|
||||
return AnthropicResult<T>.Failure(error, anthropicHeaders);
|
||||
}
|
||||
|
||||
var model = Deserialize<T>(responseContent) ?? new T();
|
||||
return AnthropicResult<T>.Success(model, anthropicHeaders);
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> SendRequestAsync(string endpoint, HttpMethod? method = null)
|
||||
{
|
||||
var request = new HttpRequestMessage(method ?? HttpMethod.Get, endpoint);
|
||||
return await _httpClient.SendAsync(request);
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> SendRequestAsync<T>(string endpoint, T request)
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
using AnthropicClient.Models;
|
||||
|
||||
namespace AnthropicClient;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a client for interacting with the Anthropic API.
|
||||
/// </summary>
|
||||
public interface IAnthropicApiClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a message asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="request">The message request to create.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/>.</returns>
|
||||
Task<AnthropicResult<MessageResponse>> CreateMessageAsync(MessageRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a message asynchronously and streams the response.
|
||||
/// </summary>
|
||||
/// <param name="request">The message request to create.</param>
|
||||
/// <returns>An asynchronous enumerable that yields the response event by event.</returns>
|
||||
IAsyncEnumerable<AnthropicEvent> CreateMessageAsync(StreamMessageRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a batch of messages asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="request">The message batch request to create.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="MessageBatchResponse"/>.</returns>
|
||||
Task<AnthropicResult<MessageBatchResponse>> CreateMessageBatchAsync(MessageBatchRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a message batch asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="batchId">The ID of the message batch to get.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="MessageBatchResponse"/>.</returns>
|
||||
Task<AnthropicResult<MessageBatchResponse>> GetMessageBatchAsync(string batchId);
|
||||
|
||||
/// <summary>
|
||||
/// Lists the message batches asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="request">The paging request to use for listing the message batches.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="Page{T}"/> where T is <see cref="MessageBatchResponse"/>.</returns>
|
||||
Task<AnthropicResult<Page<MessageBatchResponse>>> ListMessageBatchesAsync(PagingRequest? request = null);
|
||||
|
||||
/// <summary>
|
||||
/// Lists all message batches asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="limit">The maximum number of message batches to return in each page.</param>
|
||||
/// <returns>An asynchronous enumerable that yields the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="Page{T}"/> where T is <see cref="MessageBatchResponse"/>.</returns>
|
||||
IAsyncEnumerable<AnthropicResult<Page<MessageBatchResponse>>> ListAllMessageBatchesAsync(int limit = 20);
|
||||
|
||||
/// <summary>
|
||||
/// Cancels a message batch asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="batchId">The ID of the message batch to cancel.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="MessageBatchResponse"/>.</returns>
|
||||
Task<AnthropicResult<MessageBatchResponse>> CancelMessageBatchAsync(string batchId);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a message batch asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="batchId">The ID of the message batch to delete.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="MessageBatchDeleteResponse"/>.</returns>
|
||||
Task<AnthropicResult<MessageBatchDeleteResponse>> DeleteMessageBatchAsync(string batchId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the results of a message batch asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="batchId">The ID of the message batch to get the results for.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="IAsyncEnumerable{T}"/> where T is <see cref="MessageBatchResultItem"/>.</returns>
|
||||
Task<AnthropicResult<IAsyncEnumerable<MessageBatchResultItem>>> GetMessageBatchResultsAsync(string batchId);
|
||||
|
||||
/// <summary>
|
||||
/// Counts the tokens in a message asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="request">The count message tokens request.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="TokenCountResponse"/>.</returns>
|
||||
Task<AnthropicResult<TokenCountResponse>> CountMessageTokensAsync(CountMessageTokensRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Lists the models asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="request">The paging request to use for listing the models.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="Page{T}"/> where T is <see cref="AnthropicModel"/>.</returns>
|
||||
Task<AnthropicResult<Page<AnthropicModel>>> ListModelsAsync(PagingRequest? request = null);
|
||||
|
||||
/// <summary>
|
||||
/// Lists the models asynchronously
|
||||
/// </summary>
|
||||
/// <param name="limit">The maximum number of models to return in each page.</param>
|
||||
/// <returns>An asynchronous enumerable that yields the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="Page{T}"/> where T is <see cref="AnthropicModel"/>.</returns>
|
||||
///
|
||||
IAsyncEnumerable<AnthropicResult<Page<AnthropicModel>>> ListAllModelsAsync(int limit = 20);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a model by its ID asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="modelId">The ID of the model to get.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the response as an <see cref="AnthropicResult{T}"/> where T is <see cref="AnthropicModel"/>.</returns>
|
||||
Task<AnthropicResult<AnthropicModel>> GetModelAsync(string modelId);
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ static class JsonSerializationOptions
|
||||
new EventDataConverter(),
|
||||
new ContentDeltaConverter(),
|
||||
new JsonStringEnumConverter(),
|
||||
new MessageBatchResultConverter(),
|
||||
},
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using AnthropicClient.Models;
|
||||
|
||||
namespace AnthropicClient.Json;
|
||||
|
||||
class MessageBatchResultConverter : JsonConverter<MessageBatchResult>
|
||||
{
|
||||
public override MessageBatchResult Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
using var jsonDocument = JsonDocument.ParseValue(ref reader);
|
||||
var root = jsonDocument.RootElement;
|
||||
var type = root.GetProperty("type").GetString();
|
||||
return type switch
|
||||
{
|
||||
MessageBatchResultType.Succeeded => JsonSerializer.Deserialize<SucceededMessageBatchResult>(root.GetRawText(), options)!,
|
||||
MessageBatchResultType.Errored => JsonSerializer.Deserialize<ErroredMessageBatchResult>(root.GetRawText(), options)!,
|
||||
MessageBatchResultType.Canceled => JsonSerializer.Deserialize<CanceledMessageBatchResult>(root.GetRawText(), options)!,
|
||||
MessageBatchResultType.Expired => JsonSerializer.Deserialize<ExpiredMessageBatchResult>(root.GetRawText(), options)!,
|
||||
_ => throw new JsonException($"Unknown message batch result type: {type}")
|
||||
};
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, MessageBatchResult value, JsonSerializerOptions options)
|
||||
{
|
||||
JsonSerializer.Serialize(writer, value, value.GetType(), options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace AnthropicClient.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a message batch result that was cancelled.
|
||||
/// </summary>
|
||||
public class CanceledMessageBatchResult : MessageBatchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CanceledMessageBatchResult"/> class.
|
||||
/// </summary>
|
||||
public CanceledMessageBatchResult() : base(MessageBatchResultType.Canceled)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace AnthropicClient.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a message batch result that contains an error response.
|
||||
/// </summary>
|
||||
public class ErroredMessageBatchResult : MessageBatchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the error of the message batch result.
|
||||
/// </summary>
|
||||
public AnthropicError Error { get; init; } = new AnthropicError();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ErroredMessageBatchResult"/> class.
|
||||
/// </summary>
|
||||
public ErroredMessageBatchResult() : base(MessageBatchResultType.Errored)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace AnthropicClient.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a message batch result that has expired.
|
||||
/// </summary>
|
||||
public class ExpiredMessageBatchResult : MessageBatchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExpiredMessageBatchResult"/> class.
|
||||
/// </summary>
|
||||
public ExpiredMessageBatchResult() : base(MessageBatchResultType.Expired)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace AnthropicClient.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a message batch delete response.
|
||||
/// </summary>
|
||||
public class MessageBatchDeleteResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the ID of the message batch that was deleted.
|
||||
/// </summary>
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the message batch response.
|
||||
/// </summary>
|
||||
public string Type { get; init; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace AnthropicClient.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a request to create a batch of messages.
|
||||
/// </summary>
|
||||
public class MessageBatchRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the requests to create messages.
|
||||
/// </summary>
|
||||
public List<MessageBatchRequestItem> Requests { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MessageBatchRequest"/> class.
|
||||
/// </summary>
|
||||
/// <param name="requests">The requests to create messages.</param>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="requests"/> is empty.</exception>
|
||||
/// <returns>An instance of the <see cref="MessageBatchRequest"/> class.</returns>
|
||||
public MessageBatchRequest(List<MessageBatchRequestItem> requests)
|
||||
{
|
||||
if (requests.Count == 0)
|
||||
{
|
||||
throw new ArgumentException($"{nameof(requests)} must not be empty.", nameof(requests));
|
||||
}
|
||||
|
||||
Requests = requests;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using AnthropicClient.Utils;
|
||||
|
||||
namespace AnthropicClient.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an item in a batch of messages.
|
||||
/// </summary>
|
||||
public class MessageBatchRequestItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the custom identifier for the message.
|
||||
/// </summary>
|
||||
[JsonPropertyName("custom_id")]
|
||||
public string CustomId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the message request parameters.
|
||||
/// </summary>
|
||||
public MessageRequest Params { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MessageBatchRequestItem"/> class.
|
||||
/// </summary>
|
||||
/// <param name="customId">The custom identifier for the message.</param>
|
||||
/// <param name="messageRequest">The message request parameters.</param>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="customId"/> is null or whitespace.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="messageRequest"/> is null.</exception>
|
||||
/// <returns>An instance of the <see cref="MessageBatchRequestItem"/> class.</returns>
|
||||
public MessageBatchRequestItem(string customId, MessageRequest messageRequest)
|
||||
{
|
||||
ArgumentValidator.ThrowIfNullOrWhitespace(customId, nameof(customId));
|
||||
ArgumentValidator.ThrowIfNull(messageRequest, nameof(messageRequest));
|
||||
|
||||
CustomId = customId;
|
||||
Params = messageRequest;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AnthropicClient.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a response to a batch of messages.
|
||||
/// </summary>
|
||||
public class MessageBatchResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the identifier of the batch.
|
||||
/// </summary>
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the batch.
|
||||
/// </summary>
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the processing status of the batch.
|
||||
/// </summary>
|
||||
[JsonPropertyName("processing_status")]
|
||||
public string ProcessingStatus { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the counts of requests in the batch.
|
||||
/// </summary>
|
||||
[JsonPropertyName("request_counts")]
|
||||
public MessageBatchRequestCounts RequestCounts { get; init; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date and time when the batch ended.
|
||||
/// </summary>
|
||||
[JsonPropertyName("ended_at")]
|
||||
public DateTimeOffset? EndedAt { get; init; } = DateTimeOffset.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date and time when the batch was created.
|
||||
/// </summary>
|
||||
[JsonPropertyName("created_at")]
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date and time when the batch expires.
|
||||
/// </summary>
|
||||
[JsonPropertyName("expires_at")]
|
||||
public DateTimeOffset ExpiresAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date and time when the batch was archived.
|
||||
/// </summary>
|
||||
[JsonPropertyName("archived_at")]
|
||||
public DateTimeOffset? ArchivedAt { get; init; } = DateTimeOffset.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date and time when the batch cancellation was initiated.
|
||||
/// </summary>
|
||||
[JsonPropertyName("cancel_initiated_at")]
|
||||
public DateTimeOffset? CancelInitiatedAt { get; init; } = DateTimeOffset.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL to the results of the batch.
|
||||
/// </summary>
|
||||
[JsonPropertyName("results_url")]
|
||||
public string? ResultsUrl { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the counts of requests in a batch of messages.
|
||||
/// </summary>
|
||||
public class MessageBatchRequestCounts
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the number of requests in the batch that are processing.
|
||||
/// </summary>
|
||||
public int Processing { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of requests in the batch that succeeded.
|
||||
/// </summary>
|
||||
public int Succeeded { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of requests in the batch that errored.
|
||||
/// </summary>
|
||||
public int Errored { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of requests in the batch that were cancelled.
|
||||
/// </summary>
|
||||
public int Canceled { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of requests in the batch that expired.
|
||||
/// </summary>
|
||||
public int Expired { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using AnthropicClient.Utils;
|
||||
|
||||
namespace AnthropicClient.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a message batch result.
|
||||
/// </summary>
|
||||
public abstract class MessageBatchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the type of the message batch result.
|
||||
/// </summary>
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MessageBatchResult"/> class.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the message batch result.</param>
|
||||
/// <returns>An instance of the <see cref="MessageBatchResult"/> class.</returns>
|
||||
public MessageBatchResult(string type)
|
||||
{
|
||||
ArgumentValidator.ThrowIfNullOrWhitespace(type, nameof(type));
|
||||
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AnthropicClient.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a message batch result item.
|
||||
/// </summary>
|
||||
public class MessageBatchResultItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the custom ID of the message batch result item.
|
||||
/// </summary>
|
||||
[JsonPropertyName("custom_id")]
|
||||
public string CustomId { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the result of the message batch result item.
|
||||
/// </summary>
|
||||
public MessageBatchResult Result { get; init; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace AnthropicClient.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the types of message batch results.
|
||||
/// </summary>
|
||||
public static class MessageBatchResultType
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a succeeded message batch result.
|
||||
/// </summary>
|
||||
public const string Succeeded = "succeeded";
|
||||
|
||||
/// <summary>
|
||||
/// Represents an errored message batch result.
|
||||
/// </summary>
|
||||
public const string Errored = "errored";
|
||||
|
||||
/// <summary>
|
||||
/// Represents a canceled message batch result.
|
||||
/// </summary>
|
||||
public const string Canceled = "canceled";
|
||||
|
||||
/// <summary>
|
||||
/// Represents an expired message batch result.
|
||||
/// </summary>
|
||||
public const string Expired = "expired";
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace AnthropicClient.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the status of a message batch.
|
||||
/// </summary>
|
||||
public static class MessageBatchStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// The status of a message batch that is being canceled.
|
||||
/// </summary>
|
||||
public const string Canceling = "canceling";
|
||||
|
||||
/// <summary>
|
||||
/// The status of a message batch that is in progress.
|
||||
/// </summary>
|
||||
public const string InProgress = "in_progress";
|
||||
|
||||
/// <summary>
|
||||
/// The status of a message batch that has ended.
|
||||
/// </summary>
|
||||
public const string Ended = "ended";
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace AnthropicClient.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a message batch result that contains a message response.
|
||||
/// </summary>
|
||||
public class SucceededMessageBatchResult : MessageBatchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the message of the message batch result.
|
||||
/// </summary>
|
||||
public MessageResponse Message { get; init; } = new MessageResponse();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SucceededMessageBatchResult"/> class.
|
||||
/// </summary>
|
||||
/// <returns>An instance of the <see cref="SucceededMessageBatchResult"/> class.</returns>
|
||||
public SucceededMessageBatchResult() : base(MessageBatchResultType.Succeeded)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
using AnthropicClient.Tests.Files;
|
||||
|
||||
namespace AnthropicClient.Tests.EndToEnd;
|
||||
|
||||
public class ClientTests(ConfigurationFixture configFixture) : EndToEndTest(configFixture)
|
||||
public class AnthropicApiClientTests(ConfigurationFixture configFixture) : EndToEndTest(configFixture)
|
||||
{
|
||||
private string GetTestFilePath(string fileName) =>
|
||||
Path.Combine(Directory.GetCurrentDirectory(), "Files", fileName);
|
||||
|
||||
[Fact]
|
||||
public async Task CreateMessageAsync_WhenCalled_ItShouldReturnResponse()
|
||||
{
|
||||
@@ -63,7 +62,7 @@ public class ClientTests(ConfigurationFixture configFixture) : EndToEndTest(conf
|
||||
[Fact]
|
||||
public async Task CreateMessageAsync_WhenImageIsSent_ItShouldReturnResponse()
|
||||
{
|
||||
var imagePath = GetTestFilePath("elephant.jpg");
|
||||
var imagePath = TestFileHelper.GetTestFilePath("elephant.jpg");
|
||||
var mediaType = "image/jpeg";
|
||||
var bytes = await File.ReadAllBytesAsync(imagePath);
|
||||
var base64Data = Convert.ToBase64String(bytes);
|
||||
@@ -102,7 +101,7 @@ public class ClientTests(ConfigurationFixture configFixture) : EndToEndTest(conf
|
||||
{
|
||||
var client = CreateClient(new HttpClient());
|
||||
|
||||
var storyPath = GetTestFilePath("story.txt");
|
||||
var storyPath = TestFileHelper.GetTestFilePath("story.txt");
|
||||
var storyText = await File.ReadAllTextAsync(storyPath);
|
||||
|
||||
var request = new MessageRequest(
|
||||
@@ -141,7 +140,7 @@ public class ClientTests(ConfigurationFixture configFixture) : EndToEndTest(conf
|
||||
{
|
||||
var client = CreateClient(new HttpClient());
|
||||
|
||||
var storyPath = GetTestFilePath("story.txt");
|
||||
var storyPath = TestFileHelper.GetTestFilePath("story.txt");
|
||||
var storyText = await File.ReadAllTextAsync(storyPath);
|
||||
|
||||
var request = new MessageRequest(
|
||||
@@ -217,7 +216,7 @@ public class ClientTests(ConfigurationFixture configFixture) : EndToEndTest(conf
|
||||
[Fact]
|
||||
public async Task CreateMessageAsync_WhenProvidedWithPDF_ItShouldReturnResponse()
|
||||
{
|
||||
var pdfPath = GetTestFilePath("addendum.pdf");
|
||||
var pdfPath = TestFileHelper.GetTestFilePath("addendum.pdf");
|
||||
var bytes = await File.ReadAllBytesAsync(pdfPath);
|
||||
var base64Data = Convert.ToBase64String(bytes);
|
||||
|
||||
@@ -253,7 +252,7 @@ public class ClientTests(ConfigurationFixture configFixture) : EndToEndTest(conf
|
||||
[Fact]
|
||||
public async Task CreateMessageAsync_WhenProvidedWithPDFWithCacheControl_ItShouldUseCache()
|
||||
{
|
||||
var pdfPath = GetTestFilePath("addendum.pdf");
|
||||
var pdfPath = TestFileHelper.GetTestFilePath("addendum.pdf");
|
||||
var bytes = await File.ReadAllBytesAsync(pdfPath);
|
||||
var base64Data = Convert.ToBase64String(bytes);
|
||||
|
||||
@@ -341,4 +340,121 @@ public class ClientTests(ConfigurationFixture configFixture) : EndToEndTest(conf
|
||||
result.Value.Should().BeOfType<AnthropicModel>();
|
||||
result.Value.Id.Should().Be(AnthropicModels.Claude3Haiku);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateMessageBatchAsync_WhenCalled_ItShouldReturnResponse()
|
||||
{
|
||||
var request = new MessageBatchRequest([
|
||||
new(
|
||||
Guid.NewGuid().ToString(),
|
||||
new(
|
||||
model: AnthropicModels.Claude3Haiku,
|
||||
messages: [new(MessageRole.User, [new TextContent("Hello!")])]
|
||||
)
|
||||
),
|
||||
]);
|
||||
|
||||
var result = await _client.CreateMessageBatchAsync(request);
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value.Should().BeOfType<MessageBatchResponse>();
|
||||
result.Value.Id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetMessageBatchAsync_WhenCalled_ItShouldReturnResponse()
|
||||
{
|
||||
var request = new MessageBatchRequest([
|
||||
new(
|
||||
Guid.NewGuid().ToString(),
|
||||
new(
|
||||
model: AnthropicModels.Claude3Haiku,
|
||||
messages: [new(MessageRole.User, [new TextContent("Hello!")])]
|
||||
)
|
||||
),
|
||||
]);
|
||||
|
||||
var createResult = await _client.CreateMessageBatchAsync(request);
|
||||
var getResult = await _client.GetMessageBatchAsync(createResult.Value.Id);
|
||||
|
||||
getResult.IsSuccess.Should().BeTrue();
|
||||
getResult.Value.Should().BeOfType<MessageBatchResponse>();
|
||||
getResult.Value.Id.Should().Be(createResult.Value.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListMessageBatchesAsync_WhenCalled_ItShouldReturnResponse()
|
||||
{
|
||||
var request = new MessageBatchRequest([
|
||||
new(
|
||||
Guid.NewGuid().ToString(),
|
||||
new(
|
||||
model: AnthropicModels.Claude3Haiku,
|
||||
messages: [new(MessageRole.User, [new TextContent("Hello!")])]
|
||||
)
|
||||
),
|
||||
]);
|
||||
|
||||
var createResult = await _client.CreateMessageBatchAsync(request);
|
||||
var result = await _client.ListMessageBatchesAsync();
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value.Should().BeOfType<Page<MessageBatchResponse>>();
|
||||
result.Value.Data.Should().HaveCountGreaterThan(0);
|
||||
result.Value.Data.Should().ContainSingle(b => b.Id == createResult.Value.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListAllMessageBatchesAsync_WhenCalled_ItShouldReturnResponse()
|
||||
{
|
||||
var createRequest = (string id) => new MessageBatchRequest([
|
||||
new(
|
||||
id,
|
||||
new(
|
||||
model: AnthropicModels.Claude3Haiku,
|
||||
messages: [new(MessageRole.User, [new TextContent("Hello!")])]
|
||||
)
|
||||
),
|
||||
]);
|
||||
|
||||
var requestNumberOne = createRequest(Guid.NewGuid().ToString());
|
||||
var requestNumberTwo = createRequest(Guid.NewGuid().ToString());
|
||||
|
||||
var createResultOne = await _client.CreateMessageBatchAsync(requestNumberOne);
|
||||
var createResultTwo = await _client.CreateMessageBatchAsync(requestNumberTwo);
|
||||
|
||||
var responses = await _client.ListAllMessageBatchesAsync(limit: 1).ToListAsync();
|
||||
|
||||
responses.Should().HaveCountGreaterThan(2);
|
||||
|
||||
var batches = responses
|
||||
.Where(r => r.IsSuccess)
|
||||
.Select(r => r.Value)
|
||||
.SelectMany(r => r.Data);
|
||||
|
||||
batches.Should().ContainSingle(b => b.Id == createResultOne.Value.Id);
|
||||
batches.Should().ContainSingle(b => b.Id == createResultTwo.Value.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CancelMessageBatchAsync_WhenCalled_ItShouldReturnResponse()
|
||||
{
|
||||
var request = new MessageBatchRequest([
|
||||
new(
|
||||
Guid.NewGuid().ToString(),
|
||||
new(
|
||||
model: AnthropicModels.Claude3Haiku,
|
||||
messages: [new(MessageRole.User, [new TextContent("Hello!")])]
|
||||
)
|
||||
),
|
||||
]);
|
||||
|
||||
var createResult = await _client.CreateMessageBatchAsync(request);
|
||||
var result = await _client.CancelMessageBatchAsync(createResult.Value.Id);
|
||||
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value.Should().BeOfType<MessageBatchResponse>();
|
||||
result.Value.Id.Should().Be(createResult.Value.Id);
|
||||
result.Value.ProcessingStatus.Should().Be(MessageBatchStatus.Canceling);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace AnthropicClient.Tests.Files;
|
||||
|
||||
static class TestFileHelper
|
||||
{
|
||||
public static string GetTestFilePath(string fileName) =>
|
||||
Path.Combine(Directory.GetCurrentDirectory(), "Files", fileName);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"custom_id":"my-fifth-request","result":{"type":"errored","error":{"type":"error","error":{"type":"not_found_error","message":"The requested resource could not be found."}}}}
|
||||
{"custom_id":"my-fourth-request","result":{"type":"expired"}}
|
||||
{"custom_id":"my-third-request","result":{"type":"canceled"}}
|
||||
{"custom_id":"my-second-request","result":{"type":"succeeded","message":{"id":"msg_014VwiXbi91y3JMjcpyGBHX5","type":"message","role":"assistant","model":"claude-3-5-sonnet-20240620","content":[{"type":"text","text":"Hello again! It's nice to see you. How can I assist you today? Is there anything specific you'd like to chat about or any questions you have?"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":11,"output_tokens":36}}}}
|
||||
{"custom_id":"my-first-request","result":{"type":"succeeded","message":{"id":"msg_01FqfsLoHwgeFbguDgpz48m7","type":"message","role":"assistant","model":"claude-3-5-sonnet-20240620","content":[{"type":"text","text":"Hello! How can I assist you today? Feel free to ask me any questions or let me know if there's anything you'd like to chat about."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":10,"output_tokens":34}}}}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,8 @@
|
||||
using AnthropicClient.Tests.Unit;
|
||||
|
||||
namespace AnthropicClient.Tests.Integration;
|
||||
|
||||
public class IntegrationTest
|
||||
public class IntegrationTest : SerializationTest
|
||||
{
|
||||
protected readonly MockHttpMessageHandler _mockHttpMessageHandler = new();
|
||||
protected AnthropicApiClient Client => CreateClient();
|
||||
@@ -16,6 +18,7 @@ public static class MockHttpMessageHandlerExtensions
|
||||
private const string BaseUrl = "https://api.anthropic.com/v1";
|
||||
private static readonly string MessagesEndpoint = $"{BaseUrl}/messages";
|
||||
private static readonly string CountTokensEndpoint = $"{BaseUrl}/messages/count_tokens";
|
||||
private static readonly string MessageBatchesEndpoint = $"{BaseUrl}/messages/batches";
|
||||
private static readonly string ModelsEndpoint = $"{BaseUrl}/models";
|
||||
|
||||
private static MockedRequest SetupBaseRequest(
|
||||
@@ -64,4 +67,40 @@ public static class MockHttpMessageHandlerExtensions
|
||||
return mockHttpMessageHandler
|
||||
.SetupBaseRequest(HttpMethod.Get, $"{ModelsEndpoint}/{modelId}");
|
||||
}
|
||||
|
||||
public static MockedRequest WhenCreateMessageBatchRequest(this MockHttpMessageHandler mockHttpMessageHandler)
|
||||
{
|
||||
return mockHttpMessageHandler
|
||||
.SetupBaseRequest(HttpMethod.Post, MessageBatchesEndpoint);
|
||||
}
|
||||
|
||||
public static MockedRequest WhenGetMessageBatchRequest(this MockHttpMessageHandler mockHttpMessageHandler, string batchId)
|
||||
{
|
||||
return mockHttpMessageHandler
|
||||
.SetupBaseRequest(HttpMethod.Get, $"{MessageBatchesEndpoint}/{batchId}");
|
||||
}
|
||||
|
||||
public static MockedRequest WhenGetMessageBatchResultsRequest(this MockHttpMessageHandler mockHttpMessageHandler, string batchId)
|
||||
{
|
||||
return mockHttpMessageHandler
|
||||
.SetupBaseRequest(HttpMethod.Get, $"{MessageBatchesEndpoint}/{batchId}/results");
|
||||
}
|
||||
|
||||
public static MockedRequest WhenListMessageBatchesRequest(this MockHttpMessageHandler mockHttpMessageHandler)
|
||||
{
|
||||
return mockHttpMessageHandler
|
||||
.SetupBaseRequest(HttpMethod.Get, MessageBatchesEndpoint);
|
||||
}
|
||||
|
||||
public static MockedRequest WhenCancelMessageBatchRequest(this MockHttpMessageHandler mockHttpMessageHandler, string batchId)
|
||||
{
|
||||
return mockHttpMessageHandler
|
||||
.SetupBaseRequest(HttpMethod.Post, $"{MessageBatchesEndpoint}/{batchId}/cancel");
|
||||
}
|
||||
|
||||
public static MockedRequest WhenDeleteMessageBatchRequest(this MockHttpMessageHandler mockHttpMessageHandler, string batchId)
|
||||
{
|
||||
return mockHttpMessageHandler
|
||||
.SetupBaseRequest(HttpMethod.Delete, $"{MessageBatchesEndpoint}/{batchId}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace AnthropicClient.Tests.Unit.Models;
|
||||
|
||||
public class CanceledMessageBatchResultTests : SerializationTest
|
||||
{
|
||||
private const string SampleJson = @"{
|
||||
""type"": ""canceled""
|
||||
}";
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnAnInstanceWithPropertiesSet()
|
||||
{
|
||||
var result = new CanceledMessageBatchResult();
|
||||
|
||||
result.Type.Should().Be(MessageBatchResultType.Canceled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||
{
|
||||
var result = new CanceledMessageBatchResult();
|
||||
|
||||
var json = Serialize(result);
|
||||
|
||||
JsonAssert.Equal(SampleJson, json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedProperties()
|
||||
{
|
||||
var result = Deserialize<CanceledMessageBatchResult>(SampleJson);
|
||||
|
||||
result!.Type.Should().Be(MessageBatchResultType.Canceled);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
namespace AnthropicClient.Tests.Unit.Models;
|
||||
|
||||
public class ErroredMessageBatchResultTests : SerializationTest
|
||||
{
|
||||
private const string SampleJson = @"{
|
||||
""type"": ""errored"",
|
||||
""error"": {
|
||||
""type"": ""error"",
|
||||
""error"": {
|
||||
""type"": ""api_error"",
|
||||
""message"": ""An error occurred.""
|
||||
}
|
||||
}
|
||||
}";
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnAnInstanceWithPropertiesSet()
|
||||
{
|
||||
var result = new ErroredMessageBatchResult();
|
||||
|
||||
result.Type.Should().Be(MessageBatchResultType.Errored);
|
||||
result.Error.Error.Should().BeOfType<ApiError>();
|
||||
result.Error.Error.Message.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||
{
|
||||
var result = new ErroredMessageBatchResult
|
||||
{
|
||||
Error = new(new ApiError("An error occurred."))
|
||||
};
|
||||
|
||||
var json = Serialize(result);
|
||||
|
||||
JsonAssert.Equal(SampleJson, json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedProperties()
|
||||
{
|
||||
var result = Deserialize<ErroredMessageBatchResult>(SampleJson);
|
||||
|
||||
result!.Type.Should().Be(MessageBatchResultType.Errored);
|
||||
result.Error.Error.Should().BeOfType<ApiError>();
|
||||
result.Error.Error.Message.Should().Be("An error occurred.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace AnthropicClient.Tests.Unit.Models;
|
||||
|
||||
public class ExpiredMessageBatchResultTests : SerializationTest
|
||||
{
|
||||
private const string SampleJson = @"{
|
||||
""type"": ""expired""
|
||||
}";
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnAnInstanceWithPropertiesSet()
|
||||
{
|
||||
var result = new ExpiredMessageBatchResult();
|
||||
|
||||
result.Type.Should().Be(MessageBatchResultType.Expired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||
{
|
||||
var result = new ExpiredMessageBatchResult();
|
||||
|
||||
var json = Serialize(result);
|
||||
|
||||
JsonAssert.Equal(SampleJson, json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedProperties()
|
||||
{
|
||||
var result = Deserialize<ExpiredMessageBatchResult>(SampleJson);
|
||||
|
||||
result!.Type.Should().Be(MessageBatchResultType.Expired);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
namespace AnthropicClient.Tests.Unit.Models;
|
||||
|
||||
public class MessageBatchDeleteResponseTests : SerializationTest
|
||||
{
|
||||
private const string SampleJson = @"{
|
||||
""id"": ""test-id"",
|
||||
""type"": ""test-type""
|
||||
}";
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnAnInstanceWithPropertiesSet()
|
||||
{
|
||||
var result = new MessageBatchDeleteResponse();
|
||||
|
||||
result.Id.Should().BeEmpty();
|
||||
result.Type.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||
{
|
||||
var result = new MessageBatchDeleteResponse
|
||||
{
|
||||
Id = "test-id",
|
||||
Type = "test-type"
|
||||
};
|
||||
|
||||
var json = Serialize(result);
|
||||
|
||||
JsonAssert.Equal(SampleJson, json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedValues()
|
||||
{
|
||||
var result = Deserialize<MessageBatchDeleteResponse>(SampleJson);
|
||||
|
||||
result!.Id.Should().Be("test-id");
|
||||
result.Type.Should().Be("test-type");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
namespace AnthropicClient.Tests.Unit.Models;
|
||||
|
||||
public class MessageBatchRequestItemTests : SerializationTest
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnInstanceWithPropertiesSet()
|
||||
{
|
||||
var customId = "custom_id";
|
||||
var messageRequest = new MessageRequest();
|
||||
|
||||
var result = new MessageBatchRequestItem(customId, messageRequest);
|
||||
|
||||
result.Should().BeOfType<MessageBatchRequestItem>();
|
||||
result.CustomId.Should().Be(customId);
|
||||
result.Params.Should().BeSameAs(messageRequest);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData(null)]
|
||||
public void Constructor_WhenCalledAndCustomIdIsInvalid_ItShouldThrowException(string? customId)
|
||||
{
|
||||
var act = () => new MessageBatchRequestItem(customId!, new MessageRequest());
|
||||
|
||||
act.Should().Throw<ArgumentException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledAndMessageRequestIsNull_ItShouldThrowException()
|
||||
{
|
||||
var act = () => new MessageBatchRequestItem("custom_id", null!);
|
||||
|
||||
act.Should().Throw<ArgumentException>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
namespace AnthropicClient.Tests.Unit.Models;
|
||||
|
||||
public class MessageBatchRequestTests : SerializationTest
|
||||
{
|
||||
private const string SampleJson = @"{
|
||||
""requests"": [
|
||||
{
|
||||
""custom_id"": ""my-first-request"",
|
||||
""params"": {
|
||||
""model"": ""claude-3-5-sonnet-20241022"",
|
||||
""messages"": [
|
||||
{""role"": ""user"", ""content"": [{ ""text"": ""Hello, world"", ""type"": ""text"" }]}
|
||||
],
|
||||
""max_tokens"": 1024,
|
||||
""stop_sequences"": [],
|
||||
""temperature"": 0.0,
|
||||
""stream"": false
|
||||
}
|
||||
},
|
||||
{
|
||||
""custom_id"": ""my-second-request"",
|
||||
""params"": {
|
||||
""model"": ""claude-3-5-sonnet-20241022"",
|
||||
""messages"": [
|
||||
{""role"": ""user"", ""content"": [{ ""text"": ""Hi again, friend"", ""type"": ""text"" }]}
|
||||
],
|
||||
""max_tokens"": 1024,
|
||||
""stop_sequences"": [],
|
||||
""temperature"": 0.0,
|
||||
""stream"": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}";
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnInstanceWithPropertiesSet()
|
||||
{
|
||||
var requests = new List<MessageBatchRequestItem> { new("custom_id", new()) };
|
||||
|
||||
var result = new MessageBatchRequest(requests);
|
||||
|
||||
result.Should().BeOfType<MessageBatchRequest>();
|
||||
result.Requests.Should().BeSameAs(requests);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledWithEmptyRequests_ItShouldThrowException()
|
||||
{
|
||||
var act = () => new MessageBatchRequest([]);
|
||||
|
||||
act.Should().Throw<ArgumentException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||
{
|
||||
var requests = new List<MessageBatchRequestItem>
|
||||
{
|
||||
new("my-first-request", new()
|
||||
{
|
||||
Model = "claude-3-5-sonnet-20241022",
|
||||
MaxTokens = 1024,
|
||||
Messages = [
|
||||
new() { Role = "user", Content = [new TextContent("Hello, world")] }
|
||||
]
|
||||
}),
|
||||
new("my-second-request", new()
|
||||
{
|
||||
Model = "claude-3-5-sonnet-20241022",
|
||||
MaxTokens = 1024,
|
||||
Messages = [
|
||||
new() { Role = "user", Content = [new TextContent("Hi again, friend")] }
|
||||
]
|
||||
})
|
||||
};
|
||||
|
||||
var result = new MessageBatchRequest(requests);
|
||||
|
||||
var json = Serialize(result);
|
||||
|
||||
JsonAssert.Equal(SampleJson, json);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
namespace AnthropicClient.Tests.Unit.Models;
|
||||
|
||||
public class MessageBatchResponseTests : SerializationTest
|
||||
{
|
||||
private const string SampleJson = @"{
|
||||
""id"": ""msgbatch_013Zva2CMHLNnXjNJJKqJ2EF"",
|
||||
""type"": ""message_batch"",
|
||||
""processing_status"": ""in_progress"",
|
||||
""request_counts"": {
|
||||
""processing"": 100,
|
||||
""succeeded"": 50,
|
||||
""errored"": 30,
|
||||
""canceled"": 10,
|
||||
""expired"": 10
|
||||
},
|
||||
""ended_at"": ""2024-08-20T18:37:24.100435Z"",
|
||||
""created_at"": ""2024-08-20T18:37:24.100435Z"",
|
||||
""expires_at"": ""2024-08-20T18:37:24.100435Z"",
|
||||
""archived_at"": ""2024-08-20T18:37:24.100435Z"",
|
||||
""cancel_initiated_at"": ""2024-08-20T18:37:24.100435Z"",
|
||||
""results_url"": ""https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results""
|
||||
}";
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnInstanceWithPropertiesSet()
|
||||
{
|
||||
var result = new MessageBatchResponse();
|
||||
|
||||
result.Should().BeOfType<MessageBatchResponse>();
|
||||
result.Id.Should().BeEmpty();
|
||||
result.Type.Should().BeEmpty();
|
||||
result.ProcessingStatus.Should().BeEmpty();
|
||||
result.RequestCounts.Should().BeEquivalentTo(new MessageBatchRequestCounts());
|
||||
result.EndedAt.Should().Be(DateTimeOffset.MinValue);
|
||||
result.CreatedAt.Should().Be(DateTimeOffset.MinValue);
|
||||
result.ExpiresAt.Should().Be(DateTimeOffset.MinValue);
|
||||
result.ArchivedAt.Should().Be(DateTimeOffset.MinValue);
|
||||
result.CancelInitiatedAt.Should().Be(DateTimeOffset.MinValue);
|
||||
result.ResultsUrl.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonSerialization_WhenDeserialized_ItShouldHaveExpectedValues()
|
||||
{
|
||||
var result = Deserialize<MessageBatchResponse>(SampleJson);
|
||||
|
||||
result.Should().BeEquivalentTo(new MessageBatchResponse
|
||||
{
|
||||
Id = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF",
|
||||
Type = "message_batch",
|
||||
ProcessingStatus = "in_progress",
|
||||
RequestCounts = new()
|
||||
{
|
||||
Processing = 100,
|
||||
Succeeded = 50,
|
||||
Errored = 30,
|
||||
Canceled = 10,
|
||||
Expired = 10
|
||||
},
|
||||
EndedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
||||
CreatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
||||
ExpiresAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
||||
ArchivedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
||||
CancelInitiatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
||||
ResultsUrl = "https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results"
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||
{
|
||||
var expectedJson = @"{
|
||||
""id"": ""msgbatch_013Zva2CMHLNnXjNJJKqJ2EF"",
|
||||
""type"": ""message_batch"",
|
||||
""processing_status"": ""in_progress"",
|
||||
""request_counts"": {
|
||||
""processing"": 100,
|
||||
""succeeded"": 50,
|
||||
""errored"": 30,
|
||||
""canceled"": 10,
|
||||
""expired"": 10
|
||||
},
|
||||
""ended_at"": ""2024-08-20T18:37:24.100435+00:00"",
|
||||
""created_at"": ""2024-08-20T18:37:24.100435+00:00"",
|
||||
""expires_at"": ""2024-08-20T18:37:24.100435+00:00"",
|
||||
""archived_at"": ""2024-08-20T18:37:24.100435+00:00"",
|
||||
""cancel_initiated_at"": ""2024-08-20T18:37:24.100435+00:00"",
|
||||
""results_url"": ""https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results""
|
||||
}";
|
||||
|
||||
var result = Serialize(new MessageBatchResponse
|
||||
{
|
||||
Id = "msgbatch_013Zva2CMHLNnXjNJJKqJ2EF",
|
||||
Type = "message_batch",
|
||||
ProcessingStatus = "in_progress",
|
||||
RequestCounts = new()
|
||||
{
|
||||
Processing = 100,
|
||||
Succeeded = 50,
|
||||
Errored = 30,
|
||||
Canceled = 10,
|
||||
Expired = 10
|
||||
},
|
||||
EndedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
||||
CreatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
||||
ExpiresAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
||||
ArchivedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
||||
CancelInitiatedAt = DateTimeOffset.Parse("2024-08-20T18:37:24.100435Z"),
|
||||
ResultsUrl = "https://api.anthropic.com/v1/messages/batches/msgbatch_013Zva2CMHLNnXjNJJKqJ2EF/results"
|
||||
});
|
||||
|
||||
JsonAssert.Equal(expectedJson, result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace AnthropicClient.Tests.Unit.Models;
|
||||
|
||||
public class MessageBatchResultItemTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnInstanceWithPropertiesSet()
|
||||
{
|
||||
var result = new MessageBatchResultItem();
|
||||
|
||||
result.Should().BeOfType<MessageBatchResultItem>();
|
||||
result.CustomId.Should().BeEmpty();
|
||||
result.Result.Should().Be(default);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace AnthropicClient.Tests.Unit.Models;
|
||||
|
||||
public class MessageBatchResultTests : SerializationTest
|
||||
{
|
||||
[Fact]
|
||||
public void JsonDeserialization_WhenHasUnknownType_ItShouldThrowException()
|
||||
{
|
||||
var json = @"{""type"":""unknown""}";
|
||||
|
||||
var action = () => Deserialize<MessageBatchResult>(json);
|
||||
|
||||
action.Should().Throw<JsonException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||
{
|
||||
var expectedJson = @"{""type"":""expired""}";
|
||||
var messageBatchResult = new ExpiredMessageBatchResult();
|
||||
|
||||
var json = Serialize<MessageBatchResult>(messageBatchResult);
|
||||
|
||||
JsonAssert.Equal(expectedJson, json);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
namespace AnthropicClient.Tests.Unit.Models;
|
||||
|
||||
public class MessageBatchResultTypeTests
|
||||
{
|
||||
[Fact]
|
||||
public void Succeeded_WhenCalled_ItShouldReturnExpectedValue()
|
||||
{
|
||||
var result = MessageBatchResultType.Succeeded;
|
||||
|
||||
result.Should().Be("succeeded");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Errored_WhenCalled_ItShouldReturnExpectedValue()
|
||||
{
|
||||
var result = MessageBatchResultType.Errored;
|
||||
|
||||
result.Should().Be("errored");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Canceled_WhenCalled_ItShouldReturnExpectedValue()
|
||||
{
|
||||
var result = MessageBatchResultType.Canceled;
|
||||
|
||||
result.Should().Be("canceled");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Expired_WhenCalled_ItShouldReturnExpectedValue()
|
||||
{
|
||||
var result = MessageBatchResultType.Expired;
|
||||
|
||||
result.Should().Be("expired");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace AnthropicClient.Tests.Unit.Models;
|
||||
|
||||
public class MessageBatchStatusTests
|
||||
{
|
||||
[Fact]
|
||||
public void Canceling_WhenCalled_ItShouldReturnCancelingStatus()
|
||||
{
|
||||
MessageBatchStatus.Canceling.Should().Be("canceling");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InProgress_WhenCalled_ItShouldReturnCancelingStatus()
|
||||
{
|
||||
MessageBatchStatus.InProgress.Should().Be("in_progress");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ended_WhenCalled_ItShouldReturnCancelingStatus()
|
||||
{
|
||||
MessageBatchStatus.Ended.Should().Be("ended");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
namespace AnthropicClient.Tests.Unit.Models;
|
||||
|
||||
public class SucceededMessageBatchResultTests : SerializationTest
|
||||
{
|
||||
private const string SampleJson = @"{
|
||||
""message"": {
|
||||
""id"": ""msg_01FqfsLoHwgeFbguDgpz48m7"",
|
||||
""model"": ""claude-3-5-sonnet-20240620"",
|
||||
""role"": ""assistant"",
|
||||
""stop_reason"": ""end_turn"",
|
||||
""type"": ""message"",
|
||||
""usage"": {
|
||||
""input_tokens"": 10,
|
||||
""output_tokens"": 34,
|
||||
""cache_creation_input_tokens"": 0,
|
||||
""cache_read_input_tokens"": 0
|
||||
},
|
||||
""content"": [
|
||||
{
|
||||
""text"": ""Hello! How can I assist you today? Feel free to ask me any questions or let me know if there\u0027s anything you\u0027d like to chat about."",
|
||||
""type"": ""text""
|
||||
}
|
||||
]
|
||||
},
|
||||
""type"": ""succeeded""
|
||||
}";
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnAnInstanceWithPropertiesSet()
|
||||
{
|
||||
var result = new SucceededMessageBatchResult();
|
||||
|
||||
result.Type.Should().Be(MessageBatchResultType.Succeeded);
|
||||
result.Message.Should().BeEquivalentTo(new MessageResponse());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape()
|
||||
{
|
||||
var result = new SucceededMessageBatchResult
|
||||
{
|
||||
Message = new MessageResponse
|
||||
{
|
||||
Id = "msg_01FqfsLoHwgeFbguDgpz48m7",
|
||||
Type = "message",
|
||||
Role = "assistant",
|
||||
Model = "claude-3-5-sonnet-20240620",
|
||||
Content = [
|
||||
new TextContent()
|
||||
{
|
||||
Text = "Hello! How can I assist you today? Feel free to ask me any questions or let me know if there's anything you'd like to chat about."
|
||||
}
|
||||
],
|
||||
StopReason = "end_turn",
|
||||
Usage = new()
|
||||
{
|
||||
InputTokens = 10,
|
||||
OutputTokens = 34
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var json = Serialize(result);
|
||||
|
||||
JsonAssert.Equal(SampleJson, json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedValues()
|
||||
{
|
||||
var result = Deserialize<SucceededMessageBatchResult>(SampleJson);
|
||||
|
||||
result!.Type.Should().Be(MessageBatchResultType.Succeeded);
|
||||
result.Message.Should().BeEquivalentTo(new MessageResponse
|
||||
{
|
||||
Id = "msg_01FqfsLoHwgeFbguDgpz48m7",
|
||||
Type = "message",
|
||||
Role = "assistant",
|
||||
Model = "claude-3-5-sonnet-20240620",
|
||||
Content = [
|
||||
new TextContent()
|
||||
{
|
||||
Text = "Hello! How can I assist you today? Feel free to ask me any questions or let me know if there's anything you'd like to chat about."
|
||||
}
|
||||
],
|
||||
StopReason = "end_turn",
|
||||
StopSequence = null,
|
||||
Usage = new()
|
||||
{
|
||||
InputTokens = 10,
|
||||
OutputTokens = 34
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace AnthropicClient.Tests.Unit.Models;
|
||||
|
||||
public class ToolCallTests : SerializationTest
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using AnthropicClient.Json;
|
||||
|
||||
namespace AnthropicClient.Tests.Unit;
|
||||
|
||||
public class SerializationTest
|
||||
|
||||
Reference in New Issue
Block a user