{"id":13467900,"url":"https://github.com/mochi-neko/clust","last_synced_at":"2025-03-26T03:31:14.850Z","repository":{"id":227175310,"uuid":"769471108","full_name":"mochi-neko/clust","owner":"mochi-neko","description":"An unofficial Rust client for the Anthropic/Claude API.","archived":false,"fork":false,"pushed_at":"2025-03-02T10:29:32.000Z","size":179,"stargazers_count":35,"open_issues_count":4,"forks_count":13,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-15T15:03:11.804Z","etag":null,"topics":["api-client","claude-3","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mochi-neko.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-09T06:57:50.000Z","updated_at":"2025-03-14T19:24:29.000Z","dependencies_parsed_at":"2024-03-12T02:24:55.576Z","dependency_job_id":"cad170cc-8ca3-47a2-add1-725e10a8a492","html_url":"https://github.com/mochi-neko/clust","commit_stats":null,"previous_names":["mochi-neko/clust"],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mochi-neko%2Fclust","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mochi-neko%2Fclust/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mochi-neko%2Fclust/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mochi-neko%2Fclust/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mochi-neko","download_url":"https://codeload.github.com/mochi-neko/clust/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245584732,"owners_count":20639619,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-client","claude-3","rust"],"created_at":"2024-07-31T15:01:02.224Z","updated_at":"2025-03-26T03:31:14.837Z","avatar_url":"https://github.com/mochi-neko.png","language":"Rust","funding_links":[],"categories":["Rust","API Wrappers"],"sub_categories":["Rust"],"readme":"# clust\n\nAn unofficial Rust client\nfor [the Anthropic/Claude API](https://docs.anthropic.com/claude/reference/getting-started-with-the-api).\n\n## Installation\n\nRun the following Cargo command in your project directory:\n\n```shell\ncargo add clust\n```\n\nor add the following line to your Cargo.toml:\n\n```toml\n[dependencies]\nclust = \"0.9.0\"\n```\n\n## Supported APIs\n\n- Messages\n    - [x] [Create a Message](https://docs.anthropic.com/claude/reference/messages_post)\n    - [x] [Streaming Messages](https://docs.anthropic.com/claude/reference/messages-streaming)\n\n## Feature flags\n\n- `macros`: Enable the `clust::attributse::clust_tool` attribute macro for generating `clust::messages::Tool`\n  or `clust::messages::AsyncTool` from a Rust function.\n\n## Usages\n\n### API key and client\n\nFirst you need to create a new API client: `clust::Client` with your Anthropic API key from environment variable: `ANTHROPIC_API_KEY`.\n\n```rust,no_run\nuse clust::Client;\n\nlet client = Client::from_env().unwrap();\n```\n\nor specify the API key directly:\n\n```rust,no_run\nuse clust::Client;\nuse clust::ApiKey;\n\nlet client = Client::from_api_key(ApiKey::new(\"your-api-key\"));\n```\n\nIf you want to customize the client, you can use builder pattern by `clust::ClientBuilder`:\n\n```rust,no_run\nuse clust::ClientBuilder;\nuse clust::ApiKey;\nuse clust::Version;\n\nlet client = ClientBuilder::new(ApiKey::new(\"your-api-key\"))\n    .version(Version::V2023_06_01)\n    .client(reqwest::ClientBuilder::new().timeout(std::time::Duration::from_secs(10)).build().unwrap())\n    .build();\n```\n\n### Models and max tokens\n\nYou can specify the model by `clust::messages::ClaudeModel`.\n\n```rust,no_run\nuse clust::messages::ClaudeModel;\nuse clust::messages::MessagesRequestBody;\n\nlet model = ClaudeModel::Claude3Sonnet20240229;\n\nlet request_body = MessagesRequestBody {\n    model,\n    ..Default::default ()\n};\n```\n\nBecause max number of tokens of text generation: `clust::messages::MaxTokens` depends on the model,\nyou need to create `clust::messages::MaxTokens` with the model.\n\n```rust,no_run\nuse clust::messages::ClaudeModel;\nuse clust::messages::MaxTokens;\nuse clust::messages::MessagesRequestBody;\n\nlet model = ClaudeModel::Claude3Sonnet20240229;\nlet max_tokens = MaxTokens::new(1024, model).unwrap();\n\nlet request_body = MessagesRequestBody {\n    model,\n    max_tokens,\n    ..Default::default ()\n};\n```\n\n### Prompt\n\nYou can specify the system prompt by `clust::messages::SystemPrompt` and there is no \"system\" role in the message.\n\n```rust,no_run\nuse clust::messages::SystemPrompt;\nuse clust::messages::MessagesRequestBody;\n\nlet system_prompt = SystemPrompt::new(\"You are an excellent AI assistant.\");\n\nlet request_body = MessagesRequestBody {\n    system: Some(system_prompt),\n    ..Default::default ()\n};\n```\n\n### Messages and contents\n\nBuild messages by a vector of `clust::messages::Message`:\n\n```rust,no_run\nuse clust::messages::Role;\nuse clust::messages::Content;\n\n/// The message.\npub struct Message {\n    /// The role of the message.\n    pub role: Role,\n    /// The content of the message.\n    pub content: Content,\n}\n```\n\nYou can create each role message as follows:\n\n```rust,no_run\nuse clust::messages::Message;\n\nlet message = Message::user(\"Hello, Claude!\");\nlet message = Message::assistant(\"Hello, user!\");\n```\n\nand a content: `clust::messages::Content`.\n\n```rust,no_run\nuse clust::messages::ContentBlock;\n\n/// The content of the message.\npub enum Content {\n    /// The single text content.\n    SingleText(String),\n    /// The multiple content blocks.\n    MultipleBlocks(Vec\u003cContentBlock\u003e),\n}\n```\n\nMultiple blocks is a vector of content block: `clust::messages::ContentBlock`:\n\n```rust,no_run\nuse clust::messages::TextContentBlock;\nuse clust::messages::ImageContentBlock;\n\n/// The content block of the message.\npub enum ContentBlock {\n    /// The text content block.\n    Text(TextContentBlock),\n    /// The image content block.\n    Image(ImageContentBlock),\n}\n```\n\nYou can create a content as follows:\n\n```rust,no_run\nuse clust::messages::Content;\nuse clust::messages::ContentBlock;\nuse clust::messages::TextContentBlock;\nuse clust::messages::ImageContentBlock;\nuse clust::messages::ImageContentSource;\nuse clust::messages::ImageMediaType;\n\n// Single text content\nlet content = Content::SingleText(\"Hello, Claude!\".to_string());\n// or use `From` trait\nlet content = Content::from(\"Hello, Claude!\");\n\n// Multiple content blocks\nlet content = Content::MultipleBlocks(vec![\n    ContentBlock::Text(TextContentBlock::new(\"Hello, Claude!\")),\n    ContentBlock::Image(ImageContentBlock::new(ImageContentSource::base64(\n        ImageMediaType::Png,\n        \"Base64 encoded image data\",\n    ))),\n]);\n// or use `From` trait for `String` or `ImageContentSource`\nlet content = Content::from(vec![\n    ContentBlock::from(\"Hello, Claude!\"),\n    ContentBlock::from(ImageContentSource::base64(\n        ImageMediaType::Png,\n        \"Base64 encoded image data\",\n    )),\n]);\n\n```\n\n### Request body\n\nThe request body is defined by `clust::messages::MessagesRequestBody`.\n\nSee also `MessagesRequestBody` for other options.\n\n```rust,no_run\nuse clust::messages::MessagesRequestBody;\nuse clust::messages::ClaudeModel;\nuse clust::messages::Message;\nuse clust::messages::MaxTokens;\nuse clust::messages::SystemPrompt;\n\nlet request_body = MessagesRequestBody {\n    model: ClaudeModel::Claude3Sonnet20240229,\n    messages: vec![Message::user(\"Hello, Claude!\")],\n    max_tokens: MaxTokens::new(1024, ClaudeModel::Claude3Sonnet20240229).unwrap(),\n    system: Some(SystemPrompt::new(\"You are an excellent AI assistant.\")),\n    ..Default::default ()\n};\n```\n\nYou can also use the builder pattern with `clust::messages::MessagesRequestBuilder`:\n\n```rust,no_run\nuse clust::messages::MessagesRequestBuilder;\nuse clust::messages::ClaudeModel;\nuse clust::messages::Message;\nuse clust::messages::SystemPrompt;\n\nlet request_body = MessagesRequestBuilder::new_with_max_tokens(\n    ClaudeModel::Claude3Sonnet20240229,\n    1024,\n).unwrap()\n.messages(vec![Message::user(\"Hello, Claude!\")])\n.system(SystemPrompt::new(\"You are an excellent AI assistant.\"))\n.build();\n```\n\n### API calling\n\nCall the API by `clust::Client::create_a_message` with the request body.\n\n```rust,no_run\nuse clust::Client;\nuse clust::messages::MessagesRequestBody;\n\n#[tokio::main]\nasync fn main() -\u003e anyhow::Result\u003c()\u003e {\n    let client = Client::from_env()?;\n    let request_body = MessagesRequestBody::default();\n\n    // Call the async API.\n    let response = client\n        .create_a_message(request_body)\n        .await?;\n\n    // You can extract the text content from `clust::messages::MessagesResponseBody.content.flatten_into_text()`.\n    println!(\"Content: {}\", response.content.flatten_into_text()?);\n\n    Ok(())\n}\n```\n\n### Streaming\n\nWhen you want to stream the response incrementally,\nyou can use `clust::Client::create_a_message_stream` with the stream option: `StreamOption::ReturnStream`.\n\n```rust,no_run\nuse clust::Client;\nuse clust::messages::MessagesRequestBody;\nuse clust::messages::StreamOption;\n\nuse tokio_stream::StreamExt;\n\n#[tokio::main]\nasync fn main() -\u003e anyhow::Result\u003c()\u003e {\n    let client = Client::from_env()?;\n    let request_body = MessagesRequestBody {\n        stream: Some(StreamOption::ReturnStream),\n        ..Default::default()\n    };\n\n    // Call the async API and get the stream.\n    let mut stream = client\n        .create_a_message_stream(request_body)\n        .await?;\n\n    // Poll the stream.\n    while let Some(chunk) = stream.next().await {\n         // Handle the chunk.\n    }\n\n    Ok(())\n}\n```\n\n### Tool use\n\nSupport [tool use](https://docs.anthropic.com/en/docs/build-with-claude/tool-use) for two methods:\n\n#### 1. Use `clust_tool` attribute macro for Rust function\n\nWhen you define a tool as Rust function with documentation comment like this:\n\n```txt\n/// Get the current weather in a given location\n///\n/// ## Arguments\n/// - `location` - The city and state, e.g. San Francisco, CA\nfn get_weather(location: String) -\u003e String {\n    \"15 degrees\".to_string() // Dummy response\n}\n```\n\nyou can use the `clust::clust_macros::clust_tool` attribute macro with `macros` feature flag to generate code:\n\n```txt\n/// Get the current weather in a given location\n///\n/// ## Arguments\n/// - `location` - The city and state, e.g. San Francisco, CA\n#[clust_tool] // \u003c- Generate `clust::messages::Tool` for this function\nfn get_weather(location: String) -\u003e String {\n    \"15 degrees\".to_string() // Dummy response\n}\n```\n\nand create an instance of `clust::messages::Tool` that named by `ClustTool_{function_name}` from the function:\n\n```txt\nlet tool = ClustTool_get_weather {};\n```\n\nGet the tool definition from `clust::messages::Tool` for API request:\n\n```txt\nlet tool_definition = tool.definition();\n```\n\nand call the tool with tool use got from the API response:\n\n```txt\nlet tool_result = tool.call(tool_use);\n```\n\nSee also [a tool use example](./examples/tool_use.rs) and [clust_tool](./clust_macros/src/lib.rs) for details.\n\n#### 2. Manually implement `clust::messages::Tool` or `clust::messages::AsyncTool`\n\nYou can manually implement `clust::messages::Tool` or `clust::messages::AsyncTool` for your tool.\n\n## Examples\n\n### Create a message\n\nAn example of creating a message with the API key loaded from the environment variable: `ANTHROPIC_API_KEY`\n\n```env\nANTHROPIC_API_KEY={your-api-key}\n```\n\nis as follows:\n\n```rust,no_run\nuse clust::messages::ClaudeModel;\nuse clust::messages::MaxTokens;\nuse clust::messages::Message;\nuse clust::messages::MessagesRequestBody;\nuse clust::messages::SystemPrompt;\nuse clust::Client;\n\n#[tokio::main]\nasync fn main() -\u003e anyhow::Result\u003c()\u003e {\n    // 1. Create a new API client with the API key loaded from the environment variable: `ANTHROPIC_API_KEY`.\n    let client = Client::from_env()?;\n\n    // 2. Create a request body.\n    let model = ClaudeModel::Claude3Sonnet20240229;\n    let messages = vec![Message::user(\n        \"Where is the capital of France?\",\n    )];\n    let max_tokens = MaxTokens::new(1024, model)?;\n    let system_prompt = SystemPrompt::new(\"You are an excellent AI assistant.\");\n    let request_body = MessagesRequestBody {\n        model,\n        messages,\n        max_tokens,\n        system: Some(system_prompt),\n        ..Default::default()\n    };\n\n    // 3. Call the API.\n    let response = client\n        .create_a_message(request_body)\n        .await?;\n\n    println!(\"Result:\\n{}\", response);\n\n    Ok(())\n}\n```\n\n### Streaming messages with `tokio` backend\n\nAn example of creating a message stream with the API key loaded from the environment variable: `ANTHROPIC_API_KEY`\n\n```env\nANTHROPIC_API_KEY={your-api-key}\n```\n\nwith [tokio-stream](https://docs.rs/tokio-stream/latest/tokio_stream/) is as follows:\n\n```rust,no_run\nuse clust::messages::ClaudeModel;\nuse clust::messages::MaxTokens;\nuse clust::messages::Message;\nuse clust::messages::MessagesRequestBody;\nuse clust::messages::SystemPrompt;\nuse clust::messages::StreamOption;\nuse clust::messages::MessageChunk;\nuse clust::Client;\n\nuse tokio_stream::StreamExt;\n\n#[tokio::main]\nasync fn main() -\u003e anyhow::Result\u003c()\u003e {\n    // 1. Create a new API client with the API key loaded from the environment variable: `ANTHROPIC_API_KEY`.\n    let client = Client::from_env()?;\n\n    // 2. Create a request body with `stream` option.\n    let model = ClaudeModel::Claude3Sonnet20240229;\n    let messages = vec![Message::user(\n        \"Where is the capital of France?\",\n    )];\n    let max_tokens = MaxTokens::new(1024, model)?;\n    let system_prompt = SystemPrompt::new(\"You are an excellent AI assistant.\");\n    let request_body = MessagesRequestBody {\n        model,\n        messages,\n        max_tokens,\n        system: Some(system_prompt),\n        stream: Some(StreamOption::ReturnStream),\n        ..Default::default()\n    };\n\n    // 3. Call the streaming API.\n    let mut stream = client\n        .create_a_message_stream(request_body)\n        .await?;\n\n    let mut buffer = String::new();\n\n    // 4. Poll the stream.\n    // NOTE: The `tokio_stream::StreamExt` run on the `tokio` runtime.\n    while let Some(chunk) = stream.next().await {\n        match chunk {\n            | Ok(chunk) =\u003e {\n                println!(\"Chunk:\\n{}\", chunk);\n                match chunk {\n                    | MessageChunk::ContentBlockDelta(content_block_delta) =\u003e {\n                        // Buffer message delta.\n                        buffer.push_str(\u0026content_block_delta.delta.text);\n                    }\n                    | _ =\u003e {}\n                }\n            }\n            | Err(error) =\u003e {\n                eprintln!(\"Chunk error:\\n{:?}\", error);\n            }\n        }\n    }\n\n    println!(\"Result:\\n{}\", buffer);\n\n    Ok(())\n}\n```\n\n### Create a message with vision\n\nSee [an example with vision](./examples/create_a_message_with_vision.rs).\n\n### Conversation\n\nSee [a conversation example](./examples/conversation.rs).\n\n### Tool use\n\nSee [a tool use example](./examples/tool_use.rs).\n\n### Other examples\n\nSee also the [examples](./examples) directory for more examples.\n\n## Changelog\n\nSee [CHANGELOG](./CHANGELOG.md).\n\n## License\n\nLicensed under either of the [Apache License, Version 2.0](./LICENSE-APACHE) or the [MIT](./LICENSE-MIT) license at your\noption.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmochi-neko%2Fclust","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmochi-neko%2Fclust","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmochi-neko%2Fclust/lists"}