Crate clust

source ·
Expand description

An unofficial Rust client for the Anthropic/Claude API.

§Supported APIs

§Feature flags

§Usages

§API key and client

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();

§Models and max tokens

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()
};

§Prompt

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()
};

§Messages and contents

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",
    )),
]);

§Request body

The request body is defined by clust::messages::MessagesRequestBody.

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();

§API calling

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(())
}

§Streaming

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(())
}

§Tool use

Support tool use for two methods:

§1. Use clust_tool attribute macro for Rust function

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.

§2. Manually implement clust::messages::Tool or clust::messages::AsyncTool

You can manually implement clust::messages::Tool or clust::messages::AsyncTool for your tool.

§Examples

§Create a message

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(())
}

§Streaming messages with tokio backend

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::MessageChunk;
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 `futures_util::StreamExt` run on the single thread.
    while let Some(chunk) = stream.next().await {
        match chunk {
            | Ok(chunk) => {
                println!("Chunk:\n{}", chunk);
                match chunk {
                    | MessageChunk::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(())
}

§Create a message with vision

See an example with vision.

§Conversation

See a conversation example.

§Tool use

See a tool use example.

§Other examples

See also the examples directory for more examples.

Re-exports§

Modules§

Structs§

Enums§