langdb_clust/lib.rs
1//! An unofficial Rust client for [the Anthropic/Claude API](https://docs.anthropic.com/claude/reference/getting-started-with-the-api).
2//!
3//! ## Supported APIs
4//! - [Messages](`crate::messages`)
5//! - [x] [Create a Message](https://docs.anthropic.com/claude/reference/messages_post)
6//! - [x] [Streaming Messages](https://docs.anthropic.com/claude/reference/messages-streaming)
7//!
8//! ## Feature flags
9//! - `macros`: Enable the [`attributes::clust_tool`] attribute macro for generating [`messages::ToolDefinition`]
10//! or [`messages::AsyncTool`] from a Rust function.
11//!
12//! ## Usages
13//!
14//! ### API key and client
15//! First you need to create a new API client: `clust::Client` with your Anthropic API key from environment variable: "ANTHROPIC_API_KEY"
16//!
17//! ```rust,no_run
18//! use clust::Client;
19//!
20//! let client = Client::from_env().unwrap();
21//! ```
22//!
23//! or specify the API key directly:
24//!
25//! ```rust
26//! use clust::Client;
27//! use clust::ApiKey;
28//!
29//! let client = Client::from_api_key(ApiKey::new("your-api-key"));
30//! ```
31//!
32//! If you want to customize the client, you can use builder pattern by `clust::ClientBuilder`:
33//! ```rust
34//! use clust::ClientBuilder;
35//! use clust::ApiKey;
36//! use clust::Version;
37//!
38//! let client = ClientBuilder::new(ApiKey::new("your-api-key"))
39//! .version(Version::V2023_06_01)
40//! .client(reqwest::ClientBuilder::new().timeout(std::time::Duration::from_secs(10)).build().unwrap())
41//! .build();
42//! ```
43//!
44//! ### Models and max tokens
45//! You can specify the model by `clust::messages::ClaudeModel`.
46//!
47//! ```rust
48//! use clust::messages::ClaudeModel;
49//! use clust::messages::MessagesRequestBody;
50//!
51//! let model = ClaudeModel::Claude3Sonnet20240229;
52//!
53//! let request_body = MessagesRequestBody {
54//! model,
55//! ..Default::default()
56//! };
57//! ```
58//!
59//! Because max number of tokens of text generation: `clust::messages::MaxTokens` depends on the model,
60//! you need to create `clust::messages::MaxTokens` with the model.
61//!
62//! ```rust
63//! use clust::messages::ClaudeModel;
64//! use clust::messages::MaxTokens;
65//! use clust::messages::MessagesRequestBody;
66//!
67//! let model = ClaudeModel::Claude3Sonnet20240229;
68//! let max_tokens = MaxTokens::new(1024, model).unwrap();
69//!
70//! let request_body = MessagesRequestBody {
71//! model,
72//! max_tokens,
73//! ..Default::default()
74//! };
75//! ```
76//!
77//! ### Prompt
78//! You can specify the system prompt by `clust::messages::SystemPrompt` and there is no "system" role in the message.
79//!
80//! ```rust
81//! use clust::messages::SystemPrompt;
82//! use clust::messages::MessagesRequestBody;
83//!
84//! let system_prompt = SystemPrompt::new("You are an excellent AI assistant.");
85//!
86//! let request_body = MessagesRequestBody {
87//! system: Some(system_prompt),
88//! ..Default::default()
89//! };
90//! ```
91//!
92//! ### Messages and contents
93//! Build messages by a vector of `clust::messages::Message`:
94//!
95//! ```rust
96//! use clust::messages::Role;
97//! use clust::messages::Content;
98//!
99//! /// The message.
100//! pub struct Message {
101//! /// The role of the message.
102//! pub role: Role,
103//! /// The content of the message.
104//! pub content: Content,
105//! }
106//! ```
107//!
108//! You can create each role message as follows:
109//!
110//! ```rust
111//! use clust::messages::Message;
112//!
113//! let message = Message::user("Hello, Claude!");
114//! let message = Message::assistant("Hello, user!");
115//! ```
116//!
117//! and a content: `clust::messages::Content`.
118//!
119//! ```rust
120//! use clust::messages::ContentBlock;
121//!
122//! /// The content of the message.
123//! pub enum Content {
124//! /// The single text content.
125//! SingleText(String),
126//! /// The multiple content blocks.
127//! MultipleBlocks(Vec<ContentBlock>),
128//! }
129//! ```
130//!
131//! Multiple blocks is a vector of content block: `clust::messages::ContentBlock`:
132//!
133//! ```rust
134//! use clust::messages::TextContentBlock;
135//! use clust::messages::ImageContentBlock;
136//!
137//! /// The content block of the message.
138//! pub enum ContentBlock {
139//! /// The text content block.
140//! Text(TextContentBlock),
141//! /// The image content block.
142//! Image(ImageContentBlock),
143//! }
144//! ```
145//!
146//! You can create a content as follows:
147//!
148//! ```rust
149//! use clust::messages::Content;
150//! use clust::messages::ContentBlock;
151//! use clust::messages::TextContentBlock;
152//! use clust::messages::ImageContentBlock;
153//! use clust::messages::ImageContentSource;
154//! use clust::messages::ImageMediaType;
155//!
156//! // Single text content
157//! let content = Content::SingleText("Hello, Claude!".to_string());
158//! // or use `From` trait
159//! let content = Content::from("Hello, Claude!");
160//!
161//! // Multiple content blocks
162//! let content = Content::MultipleBlocks(vec![
163//! ContentBlock::Text(TextContentBlock::new("Hello, Claude!")),
164//! ContentBlock::Image(ImageContentBlock::new(ImageContentSource::base64(
165//! ImageMediaType::Png,
166//! "Base64 encoded image data",
167//! ))),
168//! ]);
169//! // or use `From` trait for `String` or `ImageContentSource`
170//! let content = Content::from(vec![
171//! ContentBlock::from("Hello, Claude!"),
172//! ContentBlock::from(ImageContentSource::base64(
173//! ImageMediaType::Png,
174//! "Base64 encoded image data",
175//! )),
176//! ]);
177//!
178//! ```
179//!
180//! ### Request body
181//! The request body is defined by `clust::messages::MessagesRequestBody`.
182//!
183//! ```rust
184//! use clust::messages::MessagesRequestBody;
185//! use clust::messages::ClaudeModel;
186//! use clust::messages::Message;
187//! use clust::messages::MaxTokens;
188//! use clust::messages::SystemPrompt;
189//!
190//! let request_body = MessagesRequestBody {
191//! model: ClaudeModel::Claude3Sonnet20240229,
192//! messages: vec![Message::user("Hello, Claude!")],
193//! max_tokens: MaxTokens::new(1024, ClaudeModel::Claude3Sonnet20240229).unwrap(),
194//! system: Some(SystemPrompt::new("You are an excellent AI assistant.")),
195//! ..Default::default()
196//! };
197//! ```
198//!
199//! You can also use the builder pattern with `clust::messages::MessagesRequestBuilder`:
200//!
201//! ```rust
202//! use clust::messages::MessagesRequestBuilder;
203//! use clust::messages::ClaudeModel;
204//! use clust::messages::Message;
205//! use clust::messages::SystemPrompt;
206//!
207//! let request_body = MessagesRequestBuilder::new_with_max_tokens(
208//! ClaudeModel::Claude3Sonnet20240229,
209//! 1024,
210//! ).unwrap()
211//! .messages(vec![Message::user("Hello, Claude!")])
212//! .system(SystemPrompt::new("You are an excellent AI assistant."))
213//! .build();
214//! ```
215//!
216//! ### API calling
217//! Call the API by `clust::Client::create_a_message` with the request body.
218//!
219//! ```rust,no_run
220//! use clust::Client;
221//! use clust::messages::MessagesRequestBody;
222//!
223//! #[tokio::main]
224//! async fn main() -> anyhow::Result<()> {
225//! let client = Client::from_env()?;
226//! let request_body = MessagesRequestBody::default();
227//!
228//! // Call the async API.
229//! let response = client
230//! .create_a_message(request_body)
231//! .await?;
232//!
233//! // You can extract the text content from `clust::messages::MessagesResponseBody.content.flatten_into_text()`.
234//! println!("Content: {}", response.content.flatten_into_text()?);
235//!
236//! Ok(())
237//! }
238//! ```
239//!
240//! ### Streaming
241//! When you want to stream the response incrementally,
242//! you can use `clust::Client::create_a_message_stream` with the stream option: `StreamOption::ReturnStream`.
243//!
244//! ```rust,no_run
245//! use clust::Client;
246//! use clust::messages::MessagesRequestBody;
247//! use clust::messages::StreamOption;
248//!
249//! use tokio_stream::StreamExt;
250//!
251//! #[tokio::main]
252//! async fn main() -> anyhow::Result<()> {
253//! let client = Client::from_env()?;
254//! let request_body = MessagesRequestBody {
255//! stream: Some(StreamOption::ReturnStream),
256//! ..Default::default()
257//! };
258//!
259//! // Call the async API and get the stream.
260//! let mut stream = client
261//! .create_a_message_stream(request_body)
262//! .await?;
263//!
264//! // Poll the stream.
265//! while let Some(chunk) = stream.next().await {
266//! // Handle the chunk.
267//! }
268//!
269//! Ok(())
270//! }
271//! ```
272//!
273//! ### Tool use
274//!
275//! Support [tool use](https://docs.anthropic.com/en/docs/build-with-claude/tool-use) for two methods:
276//!
277//! #### 1. Use `clust_tool` attribute macro for Rust function
278//!
279//! When you define a tool as Rust function with documentation comment like this:
280//!
281//! ```rust,no_run
282//! /// Get the current weather in a given location
283//! ///
284//! /// ## Arguments
285//! /// - `location` - The city and state, e.g. San Francisco, CA
286//! fn get_weather(location: String) -> String {
287//! "15 degrees".to_string() // Dummy response
288//! }
289//! ```
290//!
291//! you can use the `clust::clust_macros::clust_tool` attribute macro with `macros` feature flag to generate code:
292//!
293//! ```txt
294//! /// Get the current weather in a given location
295//! ///
296//! /// ## Arguments
297//! /// - `location` - The city and state, e.g. San Francisco, CA
298//! #[clust_tool] // <- Generate `clust::messages::Tool` for this function
299//! fn get_weather(location: String) -> String {
300//! "15 degrees".to_string() // Dummy response
301//! }
302//! ```
303//!
304//! and create an instance of `clust::messages::Tool` that named by `ClustTool_{function_name}` from the function:
305//!
306//! ```txt
307//! let tool = ClustTool_get_weather {};
308//! ```
309//!
310//! Get the tool definition from `clust::messages::Tool` for API request:
311//!
312//! ```txt
313//! let tool_definition = tool.definition();
314//! ```
315//!
316//! and call the tool with tool use got from the API response:
317//!
318//! ```txt
319//! let tool_result = tool.call(tool_use);
320//! ```
321//!
322//! See also [a tool use example](./examples/tool_use.rs) and [clust_tool](./clust_macros/src/lib.rs) for details.
323//!
324//! #### 2. Manually implement `clust::messages::Tool` or `clust::messages::AsyncTool`
325//!
326//! You can manually implement `clust::messages::Tool` or `clust::messages::AsyncTool` for your tool.
327//!
328//! ## Examples
329//!
330//! ### Create a message
331//! An example of creating a message with the API key loaded from the environment variable: `ANTHROPIC_API_KEY`
332//!
333//! ```env
334//! ANTHROPIC_API_KEY={your-api-key}
335//! ```
336//!
337//! is as follows:
338//!
339//! ```rust,no_run
340//! use clust::messages::ClaudeModel;
341//! use clust::messages::MaxTokens;
342//! use clust::messages::Message;
343//! use clust::messages::MessagesRequestBody;
344//! use clust::messages::SystemPrompt;
345//! use clust::Client;
346//!
347//! #[tokio::main]
348//! async fn main() -> anyhow::Result<()> {
349//! // 1. Create a new API client with the API key loaded from the environment variable: `ANTHROPIC_API_KEY`.
350//! let client = Client::from_env()?;
351//!
352//! // 2. Create a request body.
353//! let model = ClaudeModel::Claude3Sonnet20240229;
354//! let messages = vec![Message::user(
355//! "Where is the capital of France?",
356//! )];
357//! let max_tokens = MaxTokens::new(1024, model)?;
358//! let system_prompt = SystemPrompt::new("You are an excellent AI assistant.");
359//! let request_body = MessagesRequestBody {
360//! model,
361//! messages,
362//! max_tokens,
363//! system: Some(system_prompt),
364//! ..Default::default()
365//! };
366//!
367//! // 3. Call the API.
368//! let response = client
369//! .create_a_message(request_body)
370//! .await?;
371//!
372//! println!("Result:\n{}", response);
373//!
374//! Ok(())
375//! }
376//! ```
377//!
378//! ### Streaming messages with `tokio` backend
379//! An example of creating a message stream with the API key loaded from the environment variable: `ANTHROPIC_API_KEY`
380//!
381//! ```env
382//! ANTHROPIC_API_KEY={your-api-key}
383//! ```
384//!
385//! with [tokio-stream](https://docs.rs/tokio-stream/latest/tokio_stream/) is as follows:
386//!
387//! ```rust,no_run
388//! use clust::messages::ClaudeModel;
389//! use clust::messages::MaxTokens;
390//! use clust::messages::Message;
391//! use clust::messages::MessagesRequestBody;
392//! use clust::messages::SystemPrompt;
393//! use clust::messages::StreamOption;
394//! use clust::messages::MessageChunk;
395//! use clust::Client;
396//!
397//! use tokio_stream::StreamExt;
398//!
399//! #[tokio::main]
400//! async fn main() -> anyhow::Result<()> {
401//! // 1. Create a new API client with the API key loaded from the environment variable: `ANTHROPIC_API_KEY`.
402//! let client = Client::from_env()?;
403//!
404//! // 2. Create a request body with `stream` option.
405//! let model = ClaudeModel::Claude3Sonnet20240229;
406//! let messages = vec![Message::user(
407//! "Where is the capital of France?",
408//! )];
409//! let max_tokens = MaxTokens::new(1024, model)?;
410//! let system_prompt = SystemPrompt::new("You are an excellent AI assistant.");
411//! let request_body = MessagesRequestBody {
412//! model,
413//! messages,
414//! max_tokens,
415//! system: Some(system_prompt),
416//! stream: Some(StreamOption::ReturnStream),
417//! ..Default::default()
418//! };
419//!
420//! // 3. Call the streaming API.
421//! let mut stream = client
422//! .create_a_message_stream(request_body)
423//! .await?;
424//!
425//! let mut buffer = String::new();
426//!
427//! // 4. Poll the stream.
428//! // NOTE: The `futures_util::StreamExt` run on the single thread.
429//! while let Some(chunk) = stream.next().await {
430//! match chunk {
431//! | Ok(chunk) => {
432//! println!("Chunk:\n{}", chunk);
433//! match chunk {
434//! | MessageChunk::ContentBlockDelta(content_block_delta) => {
435//! // Buffer message delta.
436//! buffer.push_str(&content_block_delta.delta.text);
437//! }
438//! | _ => {}
439//! }
440//! }
441//! | Err(error) => {
442//! eprintln!("Chunk error:\n{:?}", error);
443//! }
444//! }
445//! }
446//!
447//! println!("Result:\n{}", buffer);
448//!
449//! Ok(())
450//! }
451//! ```
452//!
453//! ### Create a message with vision
454//!
455//! See [an example with vision](./examples/create_a_message_with_vision.rs).
456//!
457//! ### Conversation
458//!
459//! See [a conversation example](./examples/conversation.rs).
460//!
461//! ### Tool use
462//!
463//! See [a tool use example](./examples/tool_use.rs).
464//!
465//! ### Other examples
466//!
467//! See also the [examples](./examples) directory for more examples.
468
469mod api_key;
470mod beta;
471mod client;
472mod error;
473mod version;
474
475pub(crate) mod macros;
476
477pub mod messages;
478
479#[cfg(feature = "macros")]
480pub mod attributes;
481
482pub use api_key::ApiKey;
483pub use beta::Beta;
484pub use client::Client;
485pub use client::ClientBuilder;
486pub use error::ApiError;
487pub use error::ApiErrorBody;
488pub use error::ApiErrorResponse;
489pub use error::ApiErrorType;
490pub use error::ClientError;
491pub use error::ValidationError;
492pub use version::Version;
493
494pub use futures_core;
495pub use reqwest;
496pub use serde_json;
497
498#[cfg(feature = "macros")]
499pub use clust_macros;