Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Count Message tokens endpoint #70

Merged
merged 2 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Anthropic.SDK.Tests/Messages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,21 @@ public async Task TestBasicClaude35HaikuMessage()
Assert.IsNotNull(res.Message.ToString());
}

[TestMethod]
public async Task TestBasicTokenCountMessage()
{
var client = new AnthropicClient();
var messages = new List<Message>();
messages.Add(new Message(RoleType.User, "Write me a sonnet about the Statue of Liberty"));
var parameters = new MessageCountTokenParameters
{
Messages = messages,
Model = AnthropicModels.Claude35Haiku
};
var res = await client.Messages.CountMessageTokensAsync(parameters);
Assert.IsTrue(res.InputTokens > 0);
}

[TestMethod]
public async Task TestStreamingClaude3HaikuMessage()
{
Expand Down
11 changes: 7 additions & 4 deletions Anthropic.SDK/EndpointBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Anthropic.SDK.Extensions;
using Anthropic.SDK.Extensions;
using System;
using System.Collections.Generic;
using System.IO;
Expand Down Expand Up @@ -111,7 +111,7 @@ private string GetErrorMessage(string resultAsString, HttpResponseMessage respon
return $"{resultAsString ?? "<no content>"}";
}

protected async Task<MessageResponse> HttpRequestMessages(string url = null, HttpMethod verb = null,
protected async Task<TResponse> HttpRequestMessages<TResponse>(string url = null, HttpMethod verb = null,
object postData = null, CancellationToken ctx = default)
{
var response = await HttpRequestRaw(url, verb, postData, ctx: ctx).ConfigureAwait(false);
Expand All @@ -124,10 +124,13 @@ protected async Task<MessageResponse> HttpRequestMessages(string url = null, Htt
{
Converters = { ContentConverter.Instance }
};
var res = await JsonSerializer.DeserializeAsync<MessageResponse>(
var res = await JsonSerializer.DeserializeAsync<TResponse>(
new MemoryStream(Encoding.UTF8.GetBytes(resultAsString)), options, cancellationToken: ctx);

res.RateLimits = GetRateLimits(response);
if (res is MessageResponse messageResponse)
{
messageResponse.RateLimits = GetRateLimits(response);
}

return res;
}
Expand Down
10 changes: 4 additions & 6 deletions Anthropic.SDK/Extensions/MessageParameterConverter.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Anthropic.SDK.Messaging;

namespace Anthropic.SDK.Extensions
{
public class MessageParametersConverter : JsonConverter<MessageParameters>
public class MessageParametersConverter<T> : JsonConverter<T> where T: MessageCountTokenParameters
{
public override MessageParameters Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// Implement the Read method if deserialization is needed
throw new NotImplementedException();
}

public override void Write(Utf8JsonWriter writer, MessageParameters value, JsonSerializerOptions options)
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
writer.WriteStartObject();

var properties = typeof(MessageParameters).GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

foreach (var property in properties)
{
Expand Down
9 changes: 9 additions & 0 deletions Anthropic.SDK/Messaging/MessageCountTokenResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Text.Json.Serialization;

namespace Anthropic.SDK.Messaging;

public class MessageCountTokenResponse
{
[JsonPropertyName("input_tokens")]
public int InputTokens { get; set; }
}
24 changes: 13 additions & 11 deletions Anthropic.SDK/Messaging/MessageParameters.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using Anthropic.SDK.Extensions;

namespace Anthropic.SDK.Messaging
{
[JsonConverter(typeof(MessageParametersConverter))]
public class MessageParameters
[JsonConverter(typeof(MessageParametersConverter<MessageCountTokenParameters>))]
public class MessageCountTokenParameters
{
[JsonPropertyName("model")]
public string Model { get; set; }
[JsonPropertyName("messages")]
public List<Message> Messages { get; set; }
[JsonPropertyName("system")]
public List<SystemMessage> System { get; set; }
[JsonPropertyName("tools")]
protected List<Common.Function> ToolsForClaude => Tools?.Select(p => p.Function).ToList();
[JsonIgnore]
public IList<Common.Tool> Tools { get; set; }
[JsonPropertyName("tool_choice")]
public ToolChoice ToolChoice { get; set; }
}

[JsonConverter(typeof(MessageParametersConverter<MessageParameters>))]
public class MessageParameters : MessageCountTokenParameters
{
[JsonPropertyName("max_tokens")]
public int MaxTokens { get; set; }
[JsonPropertyName("metadata")]
Expand All @@ -30,18 +39,11 @@ public class MessageParameters
public int? TopK { get; set; }
[JsonPropertyName("top_p")]
public decimal? TopP { get; set; }
[JsonPropertyName("tools")]
private List<Common.Function> ToolsForClaude => Tools?.Select(p => p.Function).ToList();
[JsonIgnore]
public IList<Common.Tool> Tools { get; set; }

/// <summary>
/// Prompt Cache Type Definitions. Designed to be used as a bitwise assignment if you want to cache multiple types and are caching enough context.
/// </summary>
[JsonIgnore]
public PromptCacheType PromptCaching { get; set; } = PromptCacheType.None;
[JsonPropertyName("tool_choice")]
public ToolChoice ToolChoice { get; set; }
}

}
8 changes: 6 additions & 2 deletions Anthropic.SDK/Messaging/MessagesEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public async Task<MessageResponse> GetClaudeMessageAsync(MessageParameters param
SetCacheControls(parameters);

parameters.Stream = false;
var response = await HttpRequestMessages(Url, HttpMethod.Post, parameters, ctx).ConfigureAwait(false);
var response = await HttpRequestMessages<MessageResponse>(Url, HttpMethod.Post, parameters, ctx).ConfigureAwait(false);

var toolCalls = new List<Function>();
foreach (var message in response.Content)
Expand Down Expand Up @@ -147,7 +147,11 @@ public async IAsyncEnumerable<MessageResponse> StreamClaudeMessageAsync(MessageP

yield return result;
}

}

public async Task<MessageCountTokenResponse> CountMessageTokensAsync(MessageCountTokenParameters parameters, CancellationToken ctx = default)
{
return await HttpRequestMessages<MessageCountTokenResponse>($"{Url}/count_tokens", HttpMethod.Post, parameters, ctx).ConfigureAwait(false);
}
}
}
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Anthropic.SDK is an unofficial C# client designed for interacting with the Claud
- [Examples](#examples)
- [Non-Streaming Call](#non-streaming-call)
- [Streaming Call](#streaming-call)
- [Token Count](#token-count)
- [IChatClient](#ichatclient)
- [Prompt Caching](#prompt-caching)
- [PDF Support](#pdf-support)
Expand Down Expand Up @@ -177,6 +178,22 @@ Console.WriteLine($@"Used Tokens - Input:{outputs.First().StreamStartMessage.Usa
Output: {outputs.Last().Usage.OutputTokens}");
```

### Token Count
The `AnthropicClient` has support for the [message token count endpoint](https://docs.anthropic.com/en/docs/build-with-claude/token-counting). Below is an example of how to use it.

```csharp
var client = new AnthropicClient();
var messages = new List<Message>();
messages.Add(new Message(RoleType.User, "Write me a sonnet about the Statue of Liberty"));
var parameters = new MessageCountTokenParameters
{
Messages = messages,
Model = AnthropicModels.Claude35Haiku
};
var response = await client.Messages.CountMessageTokensAsync(parameters);
Assert.IsTrue(res.InputTokens > 0);
```

### IChatClient

The `AnthropicClient` has support for the new `IChatClient` from Microsoft and offers a slightly different mechanism for using the `AnthropicClient`. Below are a few examples.
Expand Down
Loading