2024-06-28 16:30:09 -05:00
using System.Net.Http.Headers ;
using System.Text ;
using System.Text.Json ;
2025-01-05 21:20:27 -06:00
using System.Threading.Tasks ;
2024-06-28 16:30:09 -05:00
using AnthropicClient.Json ;
using AnthropicClient.Models ;
2024-07-01 22:46:02 -05:00
using AnthropicClient.Utils ;
2024-06-28 16:30:09 -05:00
namespace AnthropicClient ;
/// <summary>
/// Represents a client for interacting with the Anthropic API.
/// </summary>
public interface IAnthropicApiClient
{
/// <summary>
2024-07-03 16:57:27 -05:00
/// Creates a message asynchronously.
2024-06-28 16:30:09 -05:00
/// </summary>
2024-07-03 16:57:27 -05:00
/// <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 );
2024-06-28 16:30:09 -05:00
/// <summary>
2024-07-03 16:57:27 -05:00
/// Creates a message asynchronously and streams the response.
2024-06-28 16:30:09 -05:00
/// </summary>
2024-07-03 16:57:27 -05:00
/// <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 );
2025-01-01 20:13:01 -06:00
2025-01-08 20:37:58 -06:00
/// <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 );
2025-01-08 23:15:24 -06:00
/// <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 );
2025-01-01 20:13:01 -06:00
/// <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 );
2025-01-05 16:57:30 -06:00
/// <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>
2025-01-05 21:20:27 -06:00
Task < AnthropicResult < Page < AnthropicModel >>> ListModelsAsync ( PagingRequest ? request = null );
2025-01-05 16:57:30 -06:00
/// <summary>
2025-01-05 21:20:27 -06:00
/// Lists the models asynchronously
2025-01-05 16:57:30 -06:00
/// </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>
///
2025-01-05 21:20:27 -06:00
IAsyncEnumerable < AnthropicResult < Page < AnthropicModel >>> ListAllModelsAsync ( int limit = 20 );
2025-01-05 21:36:34 -06:00
/// <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 );
2024-06-28 16:30:09 -05:00
}
/// <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" ;
2025-01-08 20:39:16 -06:00
private string CountTokensEndpoint => $"{MessagesEndpoint}/count_tokens" ;
2025-01-08 20:37:58 -06:00
private string MessageBatchesEndpoint => $"{MessagesEndpoint}/batches" ;
2025-01-05 21:20:27 -06:00
private const string ModelsEndpoint = "models" ;
2024-06-28 16:30:09 -05:00
private const string JsonContentType = "application/json" ;
private const string EventPrefix = "event:" ;
private const string DataPrefix = "data:" ;
private readonly Dictionary < string , string > _defaultHeaders = new ()
{
{ "anthropic-version" , "2023-06-01" },
};
private readonly HttpClient _httpClient ;
/// <summary>
/// Initializes a new instance of the <see cref="AnthropicApiClient"/> class.
/// </summary>
/// <param name="apiKey">The API key to use for the client.</param>
/// <param name="httpClient">The HTTP client to use for the client.</param>
/// <exception cref="ArgumentNullException">Thrown when the API key or HTTP client is null.</exception>
/// <returns>A new instance of the <see cref="AnthropicApiClient"/> class.</returns>
public AnthropicApiClient ( string apiKey , HttpClient httpClient )
{
ArgumentValidator . ThrowIfNull ( apiKey , nameof ( apiKey ));
ArgumentValidator . ThrowIfNull ( httpClient , nameof ( httpClient ));
_httpClient = httpClient ;
_httpClient . BaseAddress = new Uri ( BaseUrl );
_httpClient . DefaultRequestHeaders . Add ( ApiKeyHeader , apiKey );
_httpClient . DefaultRequestHeaders
. Accept
. Add ( new MediaTypeWithQualityHeaderValue ( JsonContentType ));
foreach ( var pair in _defaultHeaders )
{
_httpClient . DefaultRequestHeaders . Add ( pair . Key , pair . Value );
}
}
2025-01-05 16:57:30 -06:00
/// <inheritdoc/>
2024-07-03 16:57:27 -05:00
public async Task < AnthropicResult < MessageResponse >> CreateMessageAsync ( MessageRequest request )
2024-06-28 16:30:09 -05:00
{
2025-01-01 20:13:01 -06:00
var response = await SendRequestAsync ( MessagesEndpoint , request );
2024-06-28 16:30:09 -05:00
var anthropicHeaders = new AnthropicHeaders ( response . Headers );
var responseContent = await response . Content . ReadAsStringAsync ();
if ( response . IsSuccessStatusCode is false )
{
var error = Deserialize < AnthropicError >( responseContent ) ?? new AnthropicError ();
2024-07-03 16:57:27 -05:00
return AnthropicResult < MessageResponse >. Failure ( error , anthropicHeaders );
2024-06-28 16:30:09 -05:00
}
2024-07-03 16:57:27 -05:00
var msgResponse = Deserialize < MessageResponse >( responseContent ) ?? new MessageResponse ();
2024-07-01 22:46:02 -05:00
2024-07-01 21:00:27 -05:00
if ( request . Tools is not null && request . Tools . Count > 0 )
{
2024-07-03 16:57:27 -05:00
msgResponse . ToolCall = GetToolCall ( msgResponse , request . Tools );
2024-07-01 21:00:27 -05:00
}
2024-07-01 22:46:02 -05:00
2024-07-03 16:57:27 -05:00
return AnthropicResult < MessageResponse >. Success ( msgResponse , anthropicHeaders );
2024-06-28 16:30:09 -05:00
}
2025-01-05 16:57:30 -06:00
/// <inheritdoc/>
2024-07-03 16:57:27 -05:00
public async IAsyncEnumerable < AnthropicEvent > CreateMessageAsync ( StreamMessageRequest request )
2024-06-28 16:30:09 -05:00
{
2025-01-01 20:13:01 -06:00
var response = await SendRequestAsync ( MessagesEndpoint , request );
2024-07-19 21:15:59 -05:00
if ( response . IsSuccessStatusCode is false )
{
var error = Deserialize < AnthropicError >( await response . Content . ReadAsStringAsync ()) ?? new AnthropicError ();
yield return new AnthropicEvent ( EventType . Error , new ErrorEventData ( error . Error ));
yield break ;
}
2024-06-28 16:30:09 -05:00
var anthropicHeaders = new AnthropicHeaders ( response . Headers );
using var responseContent = await response . Content . ReadAsStreamAsync ();
using var streamReader = new StreamReader ( responseContent );
2024-07-03 16:57:27 -05:00
MessageResponse ? msgResponse = null ;
2024-06-28 16:30:09 -05:00
Content ? content = null ;
var toolInputJsonStringBuilder = new StringBuilder ();
var currentEvent = new AnthropicEvent ();
do
{
var line = await streamReader . ReadLineAsync ();
2024-07-01 22:46:02 -05:00
2024-06-28 16:30:09 -05:00
// I know...this is not pretty, but here is why...
// as events are being yielded I want to also
2024-07-03 16:57:27 -05:00
// build up the complete response
2024-06-28 16:30:09 -05:00
// so I can yield it as a special event to make tool
// calling easier to handle
2024-07-01 22:46:02 -05:00
2024-07-03 16:57:27 -05:00
// initialize response on message start
2024-06-28 16:30:09 -05:00
if ( currentEvent . Type is EventType . MessageStart && currentEvent . Data is MessageStartEventData msgStartData )
{
2024-07-03 16:57:27 -05:00
msgResponse = msgStartData . Message ;
2024-06-28 16:30:09 -05:00
}
// initialize content block on content block start
if ( currentEvent . Type is EventType . ContentBlockStart && currentEvent . Data is ContentStartEventData contentStartData )
{
content = contentStartData . ContentBlock ;
}
// update content block with deltas based on
// current content type and delta type
if ( currentEvent . Type is EventType . ContentBlockDelta && currentEvent . Data is ContentDeltaEventData contentDeltaData )
{
if ( content is TextContent textContent && contentDeltaData . Delta is TextDelta textDelta )
{
var newText = textContent . Text + textDelta . Text ;
content = new TextContent ( newText );
}
if ( content is ToolUseContent toolUseContent && contentDeltaData . Delta is JsonDelta jsonDelta )
{
toolInputJsonStringBuilder . Append ( jsonDelta . PartialJson );
}
}
// finalize content block on content block stop
2024-07-03 16:57:27 -05:00
// and add it to the response
2024-06-28 16:30:09 -05:00
if ( currentEvent . Type is EventType . ContentBlockStop )
{
2024-07-03 16:57:27 -05:00
if ( content is not null && msgResponse is not null )
2024-06-28 16:30:09 -05:00
{
if ( content is TextContent textContent )
{
2024-07-03 16:57:27 -05:00
msgResponse . Content . Add ( textContent );
2024-06-28 16:30:09 -05:00
}
if ( content is ToolUseContent toolUseContent )
{
var input = Deserialize < Dictionary < string , object? >>( toolInputJsonStringBuilder . ToString ());
var newToolUseContent = new ToolUseContent ()
{
Id = toolUseContent . Id ,
Name = toolUseContent . Name ,
Input = input !,
};
2024-07-03 16:57:27 -05:00
msgResponse . Content . Add ( newToolUseContent );
2024-06-28 16:30:09 -05:00
}
content = null ;
}
}
2024-07-03 16:57:27 -05:00
// update response with message delta data
2024-06-28 16:30:09 -05:00
if (
2024-07-01 22:46:02 -05:00
currentEvent . Type is EventType . MessageDelta &&
2024-06-28 16:30:09 -05:00
currentEvent . Data is MessageDeltaEventData msgDeltaData &&
2024-07-03 16:57:27 -05:00
msgResponse is not null
2024-06-28 16:30:09 -05:00
)
{
2024-07-03 16:57:27 -05:00
var existingUsage = msgResponse . Usage ;
var newUsage = new Usage ()
2024-06-29 22:07:49 -05:00
{
InputTokens = existingUsage . InputTokens + msgDeltaData . Usage . InputTokens ,
OutputTokens = existingUsage . OutputTokens + msgDeltaData . Usage . OutputTokens ,
2024-08-16 09:21:43 -05:00
CacheCreationInputTokens = existingUsage . CacheCreationInputTokens + msgDeltaData . Usage . CacheCreationInputTokens ,
CacheReadInputTokens = existingUsage . CacheReadInputTokens + msgDeltaData . Usage . CacheReadInputTokens ,
2024-06-29 22:07:49 -05:00
};
2024-07-03 16:57:27 -05:00
msgResponse = new MessageResponse ()
2024-06-28 16:30:09 -05:00
{
2024-07-03 16:57:27 -05:00
Id = msgResponse . Id ,
Model = msgResponse . Model ,
Role = msgResponse . Role ,
2024-06-28 16:30:09 -05:00
StopReason = msgDeltaData . Delta . StopReason ,
StopSequence = msgDeltaData . Delta . StopSequence ,
2024-07-03 16:57:27 -05:00
Type = msgResponse . Type ,
2024-06-29 22:07:49 -05:00
Usage = newUsage ,
2024-07-03 16:57:27 -05:00
Content = msgResponse . Content ,
2024-06-28 16:30:09 -05:00
};
2024-07-01 21:00:27 -05:00
if ( request . Tools is not null && request . Tools . Count > 0 )
{
2024-07-03 16:57:27 -05:00
msgResponse . ToolCall = GetToolCall ( msgResponse , request . Tools );
2024-07-01 21:00:27 -05:00
}
2024-06-28 16:30:09 -05:00
}
2024-07-03 16:57:27 -05:00
// yield response on message stop
if ( currentEvent . Type is EventType . MessageStop && msgResponse is not null )
2024-06-28 16:30:09 -05:00
{
2024-07-03 16:57:27 -05:00
var eventData = new MessageCompleteEventData ( msgResponse , anthropicHeaders );
2024-06-28 16:30:09 -05:00
yield return new AnthropicEvent ( EventType . MessageComplete , eventData );
2024-07-03 16:57:27 -05:00
msgResponse = null ;
2024-06-28 16:30:09 -05:00
}
if ( line is null )
{
2024-07-03 00:11:48 -05:00
if ( string . IsNullOrWhiteSpace ( currentEvent . Type ) is false )
{
yield return currentEvent ;
currentEvent = new AnthropicEvent ();
}
2024-06-28 16:30:09 -05:00
break ;
}
if ( line == string . Empty )
{
yield return currentEvent ;
currentEvent = new AnthropicEvent ();
}
if ( line . StartsWith ( EventPrefix ))
{
var eventType = line . Substring ( EventPrefix . Length ). Trim ();
currentEvent = new AnthropicEvent ( eventType , currentEvent . Data );
continue ;
}
if ( line . StartsWith ( DataPrefix ))
{
var eventData = line . Substring ( DataPrefix . Length ). Trim ();
var eventDataJson = Deserialize < EventData >( eventData );
currentEvent = new AnthropicEvent ( currentEvent . Type , eventDataJson !);
continue ;
}
} while ( true );
}
2025-01-08 20:37:58 -06:00
/// <inheritdoc/>
public async Task < AnthropicResult < MessageBatchResponse >> CreateMessageBatchAsync ( MessageBatchRequest request )
{
var response = await SendRequestAsync ( MessageBatchesEndpoint , request );
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 < MessageBatchResponse >. Failure ( error , anthropicHeaders );
}
var msgBatchResponse = Deserialize < MessageBatchResponse >( responseContent ) ?? new MessageBatchResponse ();
return AnthropicResult < MessageBatchResponse >. Success ( msgBatchResponse , anthropicHeaders );
}
2025-01-08 23:15:24 -06:00
/// <inheritdoc/>
public async Task < AnthropicResult < MessageBatchResponse >> GetMessageBatchAsync ( string batchId )
{
var response = await SendRequestAsync ( $"{MessageBatchesEndpoint}/{batchId}" );
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 < MessageBatchResponse >. Failure ( error , anthropicHeaders );
}
var msgBatchResponse = Deserialize < MessageBatchResponse >( responseContent ) ?? new MessageBatchResponse ();
return AnthropicResult < MessageBatchResponse >. Success ( msgBatchResponse , anthropicHeaders );
}
2025-01-05 16:57:30 -06:00
/// <inheritdoc/>
2025-01-02 20:40:38 -06:00
public async Task < AnthropicResult < TokenCountResponse >> CountMessageTokensAsync ( CountMessageTokensRequest request )
{
var response = await SendRequestAsync ( CountTokensEndpoint , request );
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 msgResponse = Deserialize < TokenCountResponse >( responseContent ) ?? new TokenCountResponse ();
return AnthropicResult < TokenCountResponse >. Success ( msgResponse , anthropicHeaders );
}
2025-01-05 21:20:27 -06:00
/// <inheritdoc/>
public async Task < AnthropicResult < Page < AnthropicModel >>> ListModelsAsync ( PagingRequest ? request = null )
{
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 );
}
/// <inheritdoc/>
public async IAsyncEnumerable < AnthropicResult < Page < AnthropicModel >>> ListAllModelsAsync ( int limit = 20 )
{
var pagingRequest = new PagingRequest ( limit : limit );
string Endpoint () => $"{ModelsEndpoint}?{pagingRequest.ToQueryParameters()}" ;
bool hasMore ;
do
{
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 ();
yield return AnthropicResult < Page < AnthropicModel >>. Failure ( error , anthropicHeaders );
yield break ;
}
var page = Deserialize < Page < AnthropicModel >>( responseContent ) ?? new Page < AnthropicModel >();
if ( page . HasMore && page . LastId is not null )
{
hasMore = true ;
pagingRequest = new PagingRequest ( limit : limit , afterId : page . LastId );
}
else
{
hasMore = false ;
}
yield return AnthropicResult < Page < AnthropicModel >>. Success ( page , anthropicHeaders );
} while ( hasMore );
}
2025-01-05 21:36:34 -06:00
/// <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 );
}
2024-07-03 16:57:27 -05:00
private ToolCall ? GetToolCall ( MessageResponse response , List < Tool > tools )
2024-07-01 21:00:27 -05:00
{
var toolUse = response . Content . OfType < ToolUseContent >(). FirstOrDefault ();
if ( toolUse is null )
{
return null ;
}
var tool = tools . FirstOrDefault ( t => t . Name == toolUse . Name );
if ( tool is null )
{
return null ;
}
return new ToolCall ( tool , toolUse );
}
2025-01-05 21:20:27 -06:00
private async Task < HttpResponseMessage > SendRequestAsync ( string endpoint )
{
return await _httpClient . GetAsync ( endpoint );
}
2025-01-01 20:13:01 -06:00
private async Task < HttpResponseMessage > SendRequestAsync < T >( string endpoint , T request )
2024-08-18 14:59:47 -05:00
{
2024-08-18 12:53:08 -05:00
var requestJson = Serialize ( request );
var requestContent = new StringContent ( requestJson , Encoding . UTF8 , JsonContentType );
2025-01-01 20:13:01 -06:00
return await _httpClient . PostAsync ( endpoint , requestContent );
2024-06-28 16:30:09 -05:00
}
private string Serialize < T >( T obj ) => JsonSerializer . Serialize ( obj , JsonSerializationOptions . DefaultOptions );
private T ? Deserialize < T >( string json ) => JsonSerializer . Deserialize < T >( json , JsonSerializationOptions . DefaultOptions );
}