anthropic_api/
messages.rs

1//! # Messages API
2//!
3//! This module provides a Rust interface to Anthropic's Messages API, which allows you to interact
4//! with Claude models in a conversational manner.
5//!
6//! ## Key Features
7//!
8//! - Send messages to Claude models and receive responses
9//! - Support for streaming responses
10//! - Tool usage capabilities
11//! - Image input support
12//!
13//! ## Basic Usage
14//!
15//! ```no_run
16//! use anthropic_api::{messages::*, Credentials};
17//!
18//! #[tokio::main]
19//! async fn main() {
20//!     let credentials = Credentials::from_env();
21//!
22//!     let response = MessagesAPI::builder(
23//!         "claude-3-7-sonnet-20250219",
24//!         vec![Message {
25//!             role: MessageRole::User,
26//!             content: MessageContent::Text("Hello, Claude!".to_string()),
27//!         }],
28//!         1024,
29//!     )
30//!     .credentials(credentials)
31//!     .create()
32//!     .await
33//!     .unwrap();
34//!
35//!     println!("Claude says: {:?}", response.content);
36//! }
37//! ```
38
39use crate::{anthropic_post, anthropic_request_stream, ApiResponseOrError, Credentials, Usage};
40use anyhow::Result;
41use derive_builder::Builder;
42use futures_util::StreamExt;
43use reqwest::Method;
44use reqwest_eventsource::{CannotCloneRequestError, Event, EventSource};
45use serde::{Deserialize, Serialize};
46use serde_json::Value;
47use tokio::sync::mpsc::{channel, Receiver, Sender};
48
49/// Represents a full message response from the Anthropic API.
50///
51/// This struct contains the complete response from a message request, including
52/// the model's generated content and usage statistics.
53#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
54pub struct MessagesAPI {
55    /// Unique identifier for this message
56    pub id: String,
57    /// The model that generated the response
58    pub model: String,
59    /// The role of the message sender (always Assistant for responses)
60    pub role: MessageRole,
61    /// The content blocks in the response (text or tool use)
62    pub content: Vec<ResponseContentBlock>,
63    /// Reason why the model stopped generating, if applicable
64    pub stop_reason: Option<String>,
65    /// The specific sequence that caused generation to stop, if applicable
66    pub stop_sequence: Option<String>,
67    /// The type of the response (always "message")
68    #[serde(rename = "type")]
69    pub typ: String,
70    /// Token usage statistics for the request and response
71    pub usage: Usage,
72}
73
74/// Content block in a response, can be text or tool use.
75///
76/// Claude's responses can contain different types of content blocks.
77/// Currently, this can be either text or a tool use request.
78#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
79#[serde(tag = "type")]
80pub enum ResponseContentBlock {
81    /// A text content block containing natural language
82    #[serde(rename = "text")]
83    Text { text: String },
84    /// A tool use request from the model
85    #[serde(rename = "tool_use")]
86    ToolUse {
87        id: String,
88        name: String,
89        input: Value,
90    },
91}
92
93/// Streaming events from the Anthropic API.
94///
95/// When using streaming mode, the API returns a series of events that
96/// incrementally build up the complete response.
97#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
98#[serde(tag = "type")]
99pub enum StreamEvent {
100    /// Indicates the start of a message
101    #[serde(rename = "message_start")]
102    MessageStart { message: MessageStart },
103    /// Indicates the start of a content block
104    #[serde(rename = "content_block_start")]
105    ContentBlockStart {
106        index: u32,
107        content_block: ContentBlockStart,
108    },
109    /// Contains a delta (incremental update) to a content block
110    #[serde(rename = "content_block_delta")]
111    ContentBlockDelta {
112        index: u32,
113        delta: ContentBlockDelta,
114    },
115    /// Indicates the end of a content block
116    #[serde(rename = "content_block_stop")]
117    ContentBlockStop { index: u32 },
118    /// Contains final message information like stop reason
119    #[serde(rename = "message_delta")]
120    MessageDelta { delta: MessageDelta, usage: Usage },
121    /// Indicates the end of the message
122    #[serde(rename = "message_stop")]
123    MessageStop,
124    /// A keepalive event that can be ignored
125    #[serde(rename = "ping")]
126    Ping,
127}
128
129/// Initial message information in a streaming response.
130#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
131pub struct MessageStart {
132    /// Unique identifier for this message
133    pub id: String,
134    /// The model generating the response
135    pub model: String,
136    /// The role of the message sender (always Assistant for responses)
137    pub role: MessageRole,
138    /// Initial content blocks in the response
139    pub content: Vec<ContentBlockStart>,
140}
141
142/// Initial content block in a streaming response.
143#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
144#[serde(untagged)]
145pub enum ContentBlockStart {
146    /// A text content block
147    Text { text: String },
148    /// A tool use request
149    ToolUse {
150        id: String,
151        name: String,
152        input: Value,
153    },
154}
155
156/// Incremental update to a content block in a streaming response.
157#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
158#[serde(untagged)]
159pub enum ContentBlockDelta {
160    /// Text delta for a text content block
161    Text { text: String },
162    /// JSON delta for a tool use input
163    InputJsonDelta { partial_json: String },
164}
165
166/// Final message information in a streaming response.
167#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
168pub struct MessageDelta {
169    /// Reason why the model stopped generating, if applicable
170    pub stop_reason: Option<String>,
171    /// The specific sequence that caused generation to stop, if applicable
172    pub stop_sequence: Option<String>,
173}
174
175/// Request to the Anthropic Messages API.
176///
177/// This struct represents a complete request to the Messages API,
178/// including all parameters that control generation behavior.
179#[derive(Serialize, Builder, Debug, Clone)]
180#[builder(derive(Clone, Debug, PartialEq))]
181#[builder(pattern = "owned")]
182#[builder(name = "MessagesBuilder")]
183#[builder(setter(strip_option, into))]
184pub struct MessagesRequest {
185    /// The model to use (e.g., "claude-3-7-sonnet-20250219").
186    pub model: String,
187    /// The conversation messages.
188    pub messages: Vec<Message>,
189    /// Maximum number of tokens to generate.
190    pub max_tokens: u64,
191    /// Optional metadata.
192    #[builder(default)]
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub metadata: Option<Metadata>,
195    /// Sequences where generation should stop.
196    #[builder(default)]
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub stop_sequences: Option<Vec<String>>,
199    /// Whether to stream the response.
200    #[builder(default)]
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub stream: Option<bool>,
203    /// System prompt to guide the assistant's behavior.
204    #[builder(default)]
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub system: Option<String>,
207    /// Sampling temperature (0.0 to 1.0).
208    #[builder(default)]
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub temperature: Option<f64>,
211    /// Tool choice specification.
212    #[builder(default)]
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub tool_choice: Option<ToolChoice>,
215    /// Tools the assistant can use.
216    #[builder(default)]
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub tools: Option<Vec<Tool>>,
219    /// Top-k sampling parameter.
220    #[builder(default)]
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub top_k: Option<u32>,
223    /// Top-p (nucleus) sampling parameter.
224    #[builder(default)]
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub top_p: Option<f64>,
227    /// Credentials for authentication (not serialized).
228    #[serde(skip_serializing)]
229    #[builder(default)]
230    pub credentials: Option<Credentials>,
231}
232
233/// Message in the conversation.
234///
235/// Represents a single message in the conversation history,
236/// with a role (user or assistant) and content.
237#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
238pub struct Message {
239    /// The role of the message sender (user or assistant)
240    pub role: MessageRole,
241    /// The content of the message (text or content blocks)
242    pub content: MessageContent,
243}
244
245/// Role of the message sender.
246///
247/// In the Messages API, messages can be from either the user or the assistant.
248#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
249#[serde(rename_all = "lowercase")]
250pub enum MessageRole {
251    /// Message from the user
252    User,
253    /// Message from the assistant (Claude)
254    Assistant,
255}
256
257/// Content of a message, either text or content blocks.
258///
259/// Messages can contain either simple text or structured content blocks
260/// that can include text and images.
261#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
262#[serde(untagged)]
263pub enum MessageContent {
264    /// Simple text content
265    Text(String),
266    /// Structured content blocks (text and images)
267    ContentBlocks(Vec<RequestContentBlock>),
268}
269
270/// Content block in a request.
271///
272/// Request content blocks can be either text or images.
273#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
274#[serde(tag = "type")]
275pub enum RequestContentBlock {
276    /// A text content block
277    #[serde(rename = "text")]
278    Text { text: String },
279    /// An image content block
280    #[serde(rename = "image")]
281    Image { source: ImageSource },
282}
283
284/// Source of an image content block.
285///
286/// Currently, images must be provided as base64-encoded data.
287#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
288pub struct ImageSource {
289    /// The type of image source (currently only "base64" is supported)
290    #[serde(rename = "type")]
291    pub source_type: String,
292    /// The MIME type of the image (e.g., "image/png", "image/jpeg")
293    pub media_type: String,
294    /// The base64-encoded image data
295    pub data: String,
296}
297
298/// Tool definition.
299///
300/// Tools allow Claude to perform actions outside its context,
301/// such as calculations or API calls.
302#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
303pub struct Tool {
304    /// The name of the tool
305    pub name: String,
306    /// A description of what the tool does
307    pub description: String,
308    /// JSON Schema defining the input format for the tool
309    pub input_schema: Value,
310}
311
312/// Tool choice specification.
313///
314/// Controls how Claude decides whether to use tools.
315#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
316#[serde(tag = "type")]
317pub enum ToolChoice {
318    /// Claude decides whether to use tools
319    #[serde(rename = "auto")]
320    Auto,
321    /// Claude can use any available tool
322    #[serde(rename = "any")]
323    Any,
324    /// Claude must use the specified tool
325    #[serde(rename = "tool")]
326    Tool { name: String },
327    /// Claude must not use any tools
328    #[serde(rename = "none")]
329    None,
330}
331
332/// Metadata for the request.
333///
334/// Additional information about the request that isn't
335/// directly related to generation behavior.
336#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
337pub struct Metadata {
338    /// Optional user identifier for tracking purposes
339    pub user_id: Option<String>,
340}
341
342// Implementation for non-streaming response
343impl MessagesAPI {
344    /// Creates a new message request and returns the response.
345    ///
346    /// This method sends a request to the Messages API and returns
347    /// the complete response.
348    ///
349    /// # Example
350    ///
351    /// ```no_run
352    /// # use anthropic_api::{messages::*, Credentials};
353    /// # #[tokio::main]
354    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
355    /// let credentials = Credentials::from_env();
356    /// let request = MessagesRequest {
357    ///     model: "claude-3-7-sonnet-20250219".to_string(),
358    ///     messages: vec![Message {
359    ///         role: MessageRole::User,
360    ///         content: MessageContent::Text("Hello!".to_string()),
361    ///     }],
362    ///     max_tokens: 100,
363    ///     credentials: Some(credentials),
364    ///     metadata: None,
365    ///     stop_sequences: None,
366    ///     stream: None,
367    ///     system: None,
368    ///     temperature: None,
369    ///     tool_choice: None,
370    ///     tools: None,
371    ///     top_k: None,
372    ///     top_p: None,
373    /// };
374    ///
375    /// let response = MessagesAPI::create(request).await?;
376    /// # Ok(())
377    /// # }
378    /// ```
379    pub async fn create(request: MessagesRequest) -> ApiResponseOrError<Self> {
380        let credentials_opt = request.credentials.clone();
381        anthropic_post("messages", &request, credentials_opt).await
382    }
383}
384
385// Implementation for streaming response
386impl StreamEvent {
387    /// Creates a new streaming message request and returns a channel of events.
388    ///
389    /// This method sends a request to the Messages API in streaming mode
390    /// and returns a channel that will receive the streaming events.
391    ///
392    /// # Example
393    ///
394    /// ```no_run
395    /// # use anthropic_api::{messages::*, Credentials};
396    /// # #[tokio::main]
397    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
398    /// let credentials = Credentials::from_env();
399    /// let mut request = MessagesRequest {
400    ///     model: "claude-3-7-sonnet-20250219".to_string(),
401    ///     messages: vec![Message {
402    ///         role: MessageRole::User,
403    ///         content: MessageContent::Text("Hello!".to_string()),
404    ///     }],
405    ///     max_tokens: 100,
406    ///     credentials: Some(credentials),
407    ///     metadata: None,
408    ///     stop_sequences: None,
409    ///     stream: Some(true),
410    ///     system: None,
411    ///     temperature: None,
412    ///     tool_choice: None,
413    ///     tools: None,
414    ///     top_k: None,
415    ///     top_p: None,
416    /// };
417    ///
418    /// let mut stream = StreamEvent::create_stream(request).await?;
419    ///
420    /// while let Some(event) = stream.recv().await {
421    ///     // Process streaming events
422    ///     println!("{:?}", event);
423    /// }
424    /// # Ok(())
425    /// # }
426    /// ```
427    pub async fn create_stream(
428        request: MessagesRequest,
429    ) -> Result<Receiver<Self>, CannotCloneRequestError> {
430        let credentials_opt = request.credentials.clone();
431        let stream = anthropic_request_stream(
432            Method::POST,
433            "messages",
434            |r| r.json(&request),
435            credentials_opt,
436        )
437        .await?;
438        let (tx, rx) = channel::<Self>(32);
439        tokio::spawn(forward_deserialized_anthropic_stream(stream, tx));
440        Ok(rx)
441    }
442}
443
444/// Processes the event stream and forwards events to the channel.
445///
446/// This internal function handles the raw event stream from the API
447/// and deserializes events into the `StreamEvent` enum.
448async fn forward_deserialized_anthropic_stream(
449    mut stream: EventSource,
450    tx: Sender<StreamEvent>,
451) -> anyhow::Result<()> {
452    while let Some(event) = stream.next().await {
453        let event = event?;
454        if let Event::Message(event) = event {
455            let stream_event = serde_json::from_str::<StreamEvent>(&event.data)?;
456            if matches!(stream_event, StreamEvent::Ping) {
457                continue; // Ignore ping events
458            }
459            tx.send(stream_event).await?;
460        }
461    }
462    Ok(())
463}
464
465// Builder convenience methods
466impl MessagesBuilder {
467    /// Creates a new message request and returns the response.
468    ///
469    /// This is a convenience method that builds the request from the builder
470    /// and sends it to the Messages API.
471    ///
472    /// # Example
473    ///
474    /// ```no_run
475    /// # use anthropic_api::{messages::*, Credentials};
476    /// # #[tokio::main]
477    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
478    /// let credentials = Credentials::from_env();
479    ///
480    /// let response = MessagesAPI::builder("claude-3-7-sonnet-20250219",[], 1024)
481    ///     .credentials(credentials.clone())
482    ///     .create()
483    ///     .await
484    ///     .unwrap();
485    /// # Ok(())
486    /// # }
487    /// ```
488    pub async fn create(self) -> ApiResponseOrError<MessagesAPI> {
489        let request = self.build().unwrap();
490        MessagesAPI::create(request).await
491    }
492
493    /// Creates a new streaming message request and returns a channel of events.
494    ///
495    /// This is a convenience method that builds the request from the builder
496    /// and sends it to the Messages API in streaming mode.
497    ///
498    /// # Example
499    ///
500    /// ```no_run
501    /// # use anthropic_api::{messages::*, Credentials};
502    /// # #[tokio::main]
503    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
504    /// let credentials = Credentials::from_env();
505    ///
506    /// let mut stream = MessagesAPI::builder("claude-3-7-sonnet-20250219", [], 1024)
507    ///     .credentials(credentials)
508    ///     .create_stream()
509    ///     .await?;
510    ///
511    /// while let Some(event) = stream.recv().await {
512    ///     // Process streaming events
513    ///     println!("{:?}", event);
514    /// }
515    /// # Ok(())
516    /// # }
517    /// ```
518    pub async fn create_stream(self) -> Result<Receiver<StreamEvent>, CannotCloneRequestError> {
519        let mut request = self.build().expect("Failed to build MessagesRequest");
520        request.stream = Some(true);
521        StreamEvent::create_stream(request).await
522    }
523}
524
525// Helper to create a builder with required fields
526impl MessagesAPI {
527    /// Creates a new builder with the required fields.
528    ///
529    /// This is a convenience method to create a builder with the
530    /// minimum required fields for a message request.
531    ///
532    /// # Example
533    ///
534    /// ```no_run
535    /// # use anthropic_api::{messages::*, Credentials};
536    /// # #[tokio::main]
537    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
538    /// let credentials = Credentials::from_env();
539    ///
540    /// let response = MessagesAPI::builder(
541    ///     "claude-3-7-sonnet-20250219",
542    ///     vec![Message {
543    ///         role: MessageRole::User,
544    ///         content: MessageContent::Text("Hello!".to_string()),
545    ///     }],
546    ///     100,
547    /// )
548    /// .credentials(credentials)
549    /// .create()
550    /// .await?;
551    /// # Ok(())
552    /// # }
553    /// ```
554    pub fn builder(
555        model: &str,
556        messages: impl Into<Vec<Message>>,
557        max_tokens: u64,
558    ) -> MessagesBuilder {
559        MessagesBuilder::create_empty()
560            .model(model)
561            .messages(messages)
562            .max_tokens(max_tokens)
563    }
564}
565
566#[cfg(test)]
567mod tests {
568    use super::*;
569
570    #[tokio::test]
571    async fn test_simple_message() {
572        let credentials = Credentials::from_env();
573
574        let response = MessagesAPI::builder(
575            "claude-3-7-sonnet-20250219",
576            vec![Message {
577                role: MessageRole::User,
578                content: MessageContent::Text("Hello!".to_string()),
579            }],
580            100,
581        )
582        .credentials(credentials)
583        .create()
584        .await
585        .unwrap();
586
587        assert!(!response.content.is_empty());
588    }
589
590    #[tokio::test]
591    async fn test_streaming_message() {
592        let credentials = Credentials::from_env();
593
594        let mut stream = MessagesAPI::builder(
595            "claude-3-7-sonnet-20250219",
596            vec![Message {
597                role: MessageRole::User,
598                content: MessageContent::Text("Hello!".to_string()),
599            }],
600            100,
601        )
602        .credentials(credentials)
603        .create_stream()
604        .await
605        .unwrap();
606
607        while let Some(event) = stream.recv().await {
608            match event {
609                StreamEvent::ContentBlockDelta { delta, .. } => {
610                    if let ContentBlockDelta::Text { text } = delta {
611                        print!("{}", text);
612                    }
613                }
614                _ => {}
615            }
616        }
617    }
618}