An unofficial Rust client for the Anthropic/Claude API.
Run the following Cargo command in your project directory:
cargo add clust
or add the following line to your Cargo.toml:
[dependencies]
clust = "0.9.0"
- Messages
macros
: Enable theclust::attributse::clust_tool
attribute macro for generatingclust::messages::Tool
orclust::messages::AsyncTool
from a Rust function.
First you need to create a new API client: clust::Client
with your Anthropic API key from environment variable: "
ANTHROPIC_API_KEY"
use clust::Client;
let client = Client::from_env().unwrap();
or specify the API key directly:
use clust::Client;
use clust::ApiKey;
let client = Client::from_api_key(ApiKey::new("your-api-key"));
If you want to customize the client, you can use builder pattern by clust::ClientBuilder
:
use clust::ClientBuilder;
use clust::ApiKey;
use clust::Version;
let client = ClientBuilder::new(ApiKey::new("your-api-key"))
.version(Version::V2023_06_01)
.client(reqwest::ClientBuilder::new().timeout(std::time::Duration::from_secs(10)).build().unwrap())
.build();
You can specify the model by clust::messages::ClaudeModel
.
use clust::messages::ClaudeModel;
use clust::messages::MessagesRequestBody;
let model = ClaudeModel::Claude3Sonnet20240229;
let request_body = MessagesRequestBody {
model,
..Default::default ()
};
Because max number of tokens of text generation: clust::messages::MaxTokens
depends on the model,
you need to create clust::messages::MaxTokens
with the model.
use clust::messages::ClaudeModel;
use clust::messages::MaxTokens;
use clust::messages::MessagesRequestBody;
let model = ClaudeModel::Claude3Sonnet20240229;
let max_tokens = MaxTokens::new(1024, model).unwrap();
let request_body = MessagesRequestBody {
model,
max_tokens,
..Default::default ()
};
You can specify the system prompt by clust::messages::SystemPrompt
and there is no "system" role in the message.
use clust::messages::SystemPrompt;
use clust::messages::MessagesRequestBody;
let system_prompt = SystemPrompt::new("You are an excellent AI assistant.");
let request_body = MessagesRequestBody {
system: Some(system_prompt),
..Default::default ()
};
Build messages by a vector of clust::messages::Message
:
use clust::messages::Role;
use clust::messages::Content;
/// The message.
pub struct Message {
/// The role of the message.
pub role: Role,
/// The content of the message.
pub content: Content,
}
You can create each role message as follows:
use clust::messages::Message;
let message = Message::user("Hello, Claude!");
let message = Message::assistant("Hello, user!");
and a content: clust::messages::Content
.
use clust::messages::ContentBlock;
/// The content of the message.
pub enum Content {
/// The single text content.
SingleText(String),
/// The multiple content blocks.
MultipleBlocks(Vec<ContentBlock>),
}
Multiple blocks is a vector of content block: clust::messages::ContentBlock
:
use clust::messages::TextContentBlock;
use clust::messages::ImageContentBlock;
/// The content block of the message.
pub enum ContentBlock {
/// The text content block.
Text(TextContentBlock),
/// The image content block.
Image(ImageContentBlock),
}
You can create a content as follows:
use clust::messages::Content;
use clust::messages::ContentBlock;
use clust::messages::TextContentBlock;
use clust::messages::ImageContentBlock;
use clust::messages::ImageContentSource;
use clust::messages::ImageMediaType;
// Single text content
let content = Content::SingleText("Hello, Claude!".to_string());
// or use `From` trait
let content = Content::from("Hello, Claude!");
// Multiple content blocks
let content = Content::MultipleBlocks(vec![
ContentBlock::Text(TextContentBlock::new("Hello, Claude!")),
ContentBlock::Image(ImageContentBlock::new(ImageContentSource::base64(
ImageMediaType::Png,
"Base64 encoded image data",
))),
]);
// or use `From` trait for `String` or `ImageContentSource`
let content = Content::from(vec![
ContentBlock::from("Hello, Claude!"),
ContentBlock::from(ImageContentSource::base64(
ImageMediaType::Png,
"Base64 encoded image data",
)),
]);
The request body is defined by clust::messages::MessagesRequestBody
.
See also MessagesRequestBody
for other options.
use clust::messages::MessagesRequestBody;
use clust::messages::ClaudeModel;
use clust::messages::Message;
use clust::messages::MaxTokens;
use clust::messages::SystemPrompt;
let request_body = MessagesRequestBody {
model: ClaudeModel::Claude3Sonnet20240229,
messages: vec![Message::user("Hello, Claude!")],
max_tokens: MaxTokens::new(1024, ClaudeModel::Claude3Sonnet20240229).unwrap(),
system: Some(SystemPrompt::new("You are an excellent AI assistant.")),
..Default::default ()
};
You can also use the builder pattern with clust::messages::MessagesRequestBuilder
:
use clust::messages::MessagesRequestBuilder;
use clust::messages::ClaudeModel;
use clust::messages::Message;
use clust::messages::SystemPrompt;
let request_body = MessagesRequestBuilder::new_with_max_tokens(
ClaudeModel::Claude3Sonnet20240229,
1024,
).unwrap()
.messages(vec![Message::user("Hello, Claude!")])
.system(SystemPrompt::new("You are an excellent AI assistant."))
.build();
Call the API by clust::Client::create_a_message
with the request body.
use clust::Client;
use clust::messages::MessagesRequestBody;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let client = Client::from_env()?;
let request_body = MessagesRequestBody::default();
// Call the async API.
let response = client
.create_a_message(request_body)
.await?;
// You can extract the text content from `clust::messages::MessagesResponseBody.content.flatten_into_text()`.
println!("Content: {}", response.content.flatten_into_text()?);
Ok(())
}
When you want to stream the response incrementally,
you can use clust::Client::create_a_message_stream
with the stream option: StreamOption::ReturnStream
.
use clust::Client;
use clust::messages::MessagesRequestBody;
use clust::messages::StreamOption;
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let client = Client::from_env()?;
let request_body = MessagesRequestBody {
stream: Some(StreamOption::ReturnStream),
..Default::default()
};
// Call the async API and get the stream.
let mut stream = client
.create_a_message_stream(request_body)
.await?;
// Poll the stream.
while let Some(chunk) = stream.next().await {
// Handle the chunk.
}
Ok(())
}
Support tool use for two methods:
When you define a tool as Rust function with documentation comment like this:
/// Get the current weather in a given location
///
/// ## Arguments
/// - `location` - The city and state, e.g. San Francisco, CA
fn get_weather(location: String) -> String {
"15 degrees".to_string() // Dummy response
}
you can use the clust::clust_macros::clust_tool
attribute macro with macros
feature flag to generate code:
/// Get the current weather in a given location
///
/// ## Arguments
/// - `location` - The city and state, e.g. San Francisco, CA
#[clust_tool] // <- Generate `clust::messages::Tool` for this function
fn get_weather(location: String) -> String {
"15 degrees".to_string() // Dummy response
}
and create an instance of clust::messages::Tool
that named by ClustTool_{function_name}
from the function:
let tool = ClustTool_get_weather {};
Get the tool definition from clust::messages::Tool
for API request:
let tool_definition = tool.definition();
and call the tool with tool use got from the API response:
let tool_result = tool.call(tool_use);
See also a tool use example and clust_tool for details.
You can manually implement clust::messages::Tool
or clust::messages::AsyncTool
for your tool.
An example of creating a message with the API key loaded from the environment variable: ANTHROPIC_API_KEY
ANTHROPIC_API_KEY={your-api-key}
is as follows:
use clust::messages::ClaudeModel;
use clust::messages::MaxTokens;
use clust::messages::Message;
use clust::messages::MessagesRequestBody;
use clust::messages::SystemPrompt;
use clust::Client;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// 1. Create a new API client with the API key loaded from the environment variable: `ANTHROPIC_API_KEY`.
let client = Client::from_env()?;
// 2. Create a request body.
let model = ClaudeModel::Claude3Sonnet20240229;
let messages = vec![Message::user(
"Where is the capital of France?",
)];
let max_tokens = MaxTokens::new(1024, model)?;
let system_prompt = SystemPrompt::new("You are an excellent AI assistant.");
let request_body = MessagesRequestBody {
model,
messages,
max_tokens,
system: Some(system_prompt),
..Default::default()
};
// 3. Call the API.
let response = client
.create_a_message(request_body)
.await?;
println!("Result:\n{}", response);
Ok(())
}
An example of creating a message stream with the API key loaded from the environment variable: ANTHROPIC_API_KEY
ANTHROPIC_API_KEY={your-api-key}
with tokio-stream is as follows:
use clust::messages::ClaudeModel;
use clust::messages::MaxTokens;
use clust::messages::Message;
use clust::messages::MessagesRequestBody;
use clust::messages::SystemPrompt;
use clust::messages::StreamOption;
use clust::messages::StreamChunk;
use clust::Client;
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// 1. Create a new API client with the API key loaded from the environment variable: `ANTHROPIC_API_KEY`.
let client = Client::from_env()?;
// 2. Create a request body with `stream` option.
let model = ClaudeModel::Claude3Sonnet20240229;
let messages = vec![Message::user(
"Where is the capital of France?",
)];
let max_tokens = MaxTokens::new(1024, model)?;
let system_prompt = SystemPrompt::new("You are an excellent AI assistant.");
let request_body = MessagesRequestBody {
model,
messages,
max_tokens,
system: Some(system_prompt),
stream: Some(StreamOption::ReturnStream),
..Default::default()
};
// 3. Call the streaming API.
let mut stream = client
.create_a_message_stream(request_body)
.await?;
let mut buffer = String::new();
// 4. Poll the stream.
// NOTE: The `tokio_stream::StreamExt` run on the `tokio` runtime.
while let Some(chunk) = stream.next().await {
match chunk {
| Ok(chunk) => {
println!("Chunk:\n{}", chunk);
match chunk {
| StreamChunk::ContentBlockDelta(content_block_delta) => {
// Buffer message delta.
buffer.push_str(&content_block_delta.delta.text);
}
| _ => {}
}
}
| Err(error) => {
eprintln!("Chunk error:\n{:?}", error);
}
}
}
println!("Result:\n{}", buffer);
Ok(())
}
See a tool use example.
See also the examples directory for more examples.
See CHANGELOG.
Licensed under either of the Apache License, Version 2.0 or the MIT license at your option.