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 =MessagesBuilder::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 MessagesResponse {
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    #[builder(default)]
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub thinking: Option<Thinking>,
214    /// Tool choice specification.
215    #[builder(default)]
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub tool_choice: Option<ToolChoice>,
218    /// Tools the assistant can use.
219    #[builder(default)]
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub tools: Option<Vec<Tool>>,
222    /// Top-k sampling parameter.
223    #[builder(default)]
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub top_k: Option<u32>,
226    /// Top-p (nucleus) sampling parameter.
227    #[builder(default)]
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub top_p: Option<f64>,
230    /// Credentials for authentication (not serialized).
231    #[serde(skip_serializing)]
232    #[builder(default)]
233    pub credentials: Option<Credentials>,
234}
235
236/// Message in the conversation.
237///
238/// Represents a single message in the conversation history,
239/// with a role (user or assistant) and content.
240#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
241pub struct Message {
242    /// The role of the message sender (user or assistant)
243    pub role: MessageRole,
244    /// The content of the message (text or content blocks)
245    pub content: MessageContent,
246}
247
248/// Role of the message sender.
249///
250/// In the Messages API, messages can be from either the user or the assistant.
251#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
252#[serde(rename_all = "lowercase")]
253pub enum MessageRole {
254    /// Message from the user
255    User,
256    /// Message from the assistant (Claude)
257    Assistant,
258}
259
260/// Content of a message, either text or content blocks.
261///
262/// Messages can contain either simple text or structured content blocks
263/// that can include text and images.
264#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
265#[serde(untagged)]
266pub enum MessageContent {
267    /// Simple text content
268    Text(String),
269    /// Structured content blocks (text and images)
270    ContentBlocks(Vec<RequestContentBlock>),
271}
272
273/// Content block in a request.
274///
275/// Request content blocks can be either text or images.
276#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
277#[serde(tag = "type")]
278pub enum RequestContentBlock {
279    /// A text content block
280    #[serde(rename = "text")]
281    Text { text: String },
282    /// An image content block
283    #[serde(rename = "image")]
284    Image { source: ImageSource },
285}
286
287/// Source of an image content block.
288///
289/// Currently, images must be provided as base64-encoded data.
290#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
291pub struct ImageSource {
292    /// The type of image source (currently only "base64" is supported)
293    #[serde(rename = "type")]
294    pub source_type: String,
295    /// The MIME type of the image (e.g., "image/png", "image/jpeg")
296    pub media_type: String,
297    /// The base64-encoded image data
298    pub data: String,
299}
300
301#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
302#[serde(tag = "type")]
303pub enum ThinkingType {
304    /// Whether Claude is to use thinking
305    #[serde(rename = "enabled")]
306    Enabled,
307    /// Whether Claude is not to use thinking
308    #[serde(rename = "disabled")]
309    Disabled,
310}
311
312#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
313pub struct Thinking {
314    #[serde(rename = "type")]
315    pub thinking_type: ThinkingType,
316    /// The budget for the thinking in tokens
317    #[serde(rename = "budget_tokens")]
318    pub budget_tokens: u64,
319}
320
321/// Tool definition.
322///
323/// Tools allow Claude to perform actions outside its context,
324/// such as calculations or API calls.
325#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
326pub struct Tool {
327    /// The name of the tool
328    pub name: String,
329    /// A description of what the tool does
330    pub description: String,
331    /// JSON Schema defining the input format for the tool
332    pub input_schema: Value,
333}
334
335/// Tool choice specification.
336///
337/// Controls how Claude decides whether to use tools.
338#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
339#[serde(tag = "type")]
340pub enum ToolChoice {
341    /// Claude decides whether to use tools
342    #[serde(rename = "auto")]
343    Auto,
344    /// Claude can use any available tool
345    #[serde(rename = "any")]
346    Any,
347    /// Claude must use the specified tool
348    #[serde(rename = "tool")]
349    Tool { name: String },
350    /// Claude must not use any tools
351    #[serde(rename = "none")]
352    None,
353}
354
355/// Metadata for the request.
356///
357/// Additional information about the request that isn't
358/// directly related to generation behavior.
359#[derive(Serialize, Debug, Clone, Eq, PartialEq)]
360pub struct Metadata {
361    /// Optional user identifier for tracking purposes
362    pub user_id: Option<String>,
363}
364
365// Implementation for non-streaming response
366impl MessagesResponse {
367    /// Creates a new message request and returns the response.
368    ///
369    /// This method sends a request to the Messages API and returns
370    /// the complete response.
371    ///
372    /// # Example
373    ///
374    /// ```no_run
375    /// # use anthropic_api::{messages::*, Credentials};
376    /// # #[tokio::main]
377    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
378    /// let credentials = Credentials::from_env();
379    /// let request = MessagesRequest {
380    ///     model: "claude-3-7-sonnet-20250219".to_string(),
381    ///     messages: vec![Message {
382    ///         role: MessageRole::User,
383    ///         content: MessageContent::Text("Hello!".to_string()),
384    ///     }],
385    ///     max_tokens: 100,
386    ///     credentials: Some(credentials),
387    ///     metadata: None,
388    ///     stop_sequences: None,
389    ///     stream: None,
390    ///     system: None,
391    ///     temperature: None,
392    ///     thinking: None,
393    ///     tool_choice: None,
394    ///     tools: None,
395    ///     top_k: None,
396    ///     top_p: None,
397    /// };
398    ///
399    /// let response = MessagesResponse::create(request).await?;
400    /// # Ok(())
401    /// # }
402    /// ```
403    pub async fn create(request: MessagesRequest) -> ApiResponseOrError<Self> {
404        let credentials_opt = request.credentials.clone();
405        anthropic_post("messages", &request, credentials_opt).await
406    }
407}
408
409// Implementation for streaming response
410impl StreamEvent {
411    /// Creates a new streaming message request and returns a channel of events.
412    ///
413    /// This method sends a request to the Messages API in streaming mode
414    /// and returns a channel that will receive the streaming events.
415    ///
416    /// # Example
417    ///
418    /// ```no_run
419    /// # use anthropic_api::{messages::*, Credentials};
420    /// # #[tokio::main]
421    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
422    /// let credentials = Credentials::from_env();
423    /// let mut request = MessagesRequest {
424    ///     model: "claude-3-7-sonnet-20250219".to_string(),
425    ///     messages: vec![Message {
426    ///         role: MessageRole::User,
427    ///         content: MessageContent::Text("Hello!".to_string()),
428    ///     }],
429    ///     max_tokens: 100,
430    ///     credentials: Some(credentials),
431    ///     metadata: None,
432    ///     stop_sequences: None,
433    ///     stream: Some(true),
434    ///     system: None,
435    ///     temperature: None,
436    ///     thinking: None,
437    ///     tool_choice: None,
438    ///     tools: None,
439    ///     top_k: None,
440    ///     top_p: None,
441    /// };
442    ///
443    /// let mut stream = StreamEvent::create_stream(request).await?;
444    ///
445    /// while let Some(event) = stream.recv().await {
446    ///     // Process streaming events
447    ///     println!("{:?}", event);
448    /// }
449    /// # Ok(())
450    /// # }
451    /// ```
452    pub async fn create_stream(
453        request: MessagesRequest,
454    ) -> Result<Receiver<Self>, CannotCloneRequestError> {
455        let credentials_opt = request.credentials.clone();
456        let stream = anthropic_request_stream(
457            Method::POST,
458            "messages",
459            |r| r.json(&request),
460            credentials_opt,
461        )
462        .await?;
463        let (tx, rx) = channel::<Self>(32);
464        tokio::spawn(forward_deserialized_anthropic_stream(stream, tx));
465        Ok(rx)
466    }
467}
468
469/// Processes the event stream and forwards events to the channel.
470///
471/// This internal function handles the raw event stream from the API
472/// and deserializes events into the `StreamEvent` enum.
473async fn forward_deserialized_anthropic_stream(
474    mut stream: EventSource,
475    tx: Sender<StreamEvent>,
476) -> anyhow::Result<()> {
477    while let Some(event) = stream.next().await {
478        let event = event?;
479        if let Event::Message(event) = event {
480            let stream_event = serde_json::from_str::<StreamEvent>(&event.data)?;
481            if matches!(stream_event, StreamEvent::Ping) {
482                continue; // Ignore ping events
483            }
484            tx.send(stream_event).await?;
485        }
486    }
487    Ok(())
488}
489
490// Builder convenience methods
491impl MessagesBuilder {
492    pub fn builder(model: &str, messages: impl Into<Vec<Message>>, max_tokens: u64) -> Self {
493        Self::create_empty()
494            .model(model)
495            .messages(messages)
496            .max_tokens(max_tokens)
497    }
498
499    /// Creates a new message request and returns the response.
500    ///
501    /// This is a convenience method that builds the request from the builder
502    /// and sends it to the Messages API.
503    ///
504    /// # Example
505    ///
506    /// ```no_run
507    /// # use anthropic_api::{messages::*, Credentials};
508    /// # #[tokio::main]
509    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
510    /// let credentials = Credentials::from_env();
511    ///
512    /// let response =MessagesBuilder::builder("claude-3-7-sonnet-20250219",[], 1024)
513    ///     .credentials(credentials.clone())
514    ///     .create()
515    ///     .await
516    ///     .unwrap();
517    /// # Ok(())
518    /// # }
519    /// ```
520    pub async fn create(self) -> ApiResponseOrError<MessagesResponse> {
521        let request = self.build().unwrap();
522        MessagesResponse::create(request).await
523    }
524
525    /// Creates a new streaming message request and returns a channel of events.
526    ///
527    /// This is a convenience method that builds the request from the builder
528    /// and sends it to the Messages API in streaming mode.
529    ///
530    /// # Example
531    ///
532    /// ```no_run
533    /// # use anthropic_api::{messages::*, Credentials};
534    /// # #[tokio::main]
535    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
536    /// let credentials = Credentials::from_env();
537    ///
538    /// let mut stream =MessagesBuilder::builder("claude-3-7-sonnet-20250219", [], 1024)
539    ///     .credentials(credentials)
540    ///     .create_stream()
541    ///     .await?;
542    ///
543    /// while let Some(event) = stream.recv().await {
544    ///     // Process streaming events
545    ///     println!("{:?}", event);
546    /// }
547    /// # Ok(())
548    /// # }
549    /// ```
550    pub async fn create_stream(self) -> Result<Receiver<StreamEvent>, CannotCloneRequestError> {
551        let mut request = self.build().expect("Failed to build MessagesRequest");
552        request.stream = Some(true);
553        StreamEvent::create_stream(request).await
554    }
555}
556
557// Helper to create a builder with required fields
558impl MessagesResponse {
559    /// Creates a new builder with the required fields.
560    ///
561    /// This is a convenience method to create a builder with the
562    /// minimum required fields for a message request.
563    ///
564    /// # Example
565    ///
566    /// ```no_run
567    /// # use anthropic_api::{messages::*, Credentials};
568    /// # #[tokio::main]
569    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
570    /// let credentials = Credentials::from_env();
571    ///
572    /// let response =MessagesBuilder::builder(
573    ///     "claude-3-7-sonnet-20250219",
574    ///     vec![Message {
575    ///         role: MessageRole::User,
576    ///         content: MessageContent::Text("Hello!".to_string()),
577    ///     }],
578    ///     100,
579    /// )
580    /// .credentials(credentials)
581    /// .create()
582    /// .await?;
583    /// # Ok(())
584    /// # }
585    /// ```
586    pub fn builder(
587        model: &str,
588        messages: impl Into<Vec<Message>>,
589        max_tokens: u64,
590    ) -> MessagesBuilder {
591        MessagesBuilder::create_empty()
592            .model(model)
593            .messages(messages)
594            .max_tokens(max_tokens)
595    }
596}
597
598#[cfg(test)]
599mod tests {
600    use super::*;
601
602    #[tokio::test]
603    async fn test_simple_message() {
604        let credentials = Credentials::from_env();
605
606        let response = MessagesResponse::builder(
607            "claude-3-7-sonnet-20250219",
608            vec![Message {
609                role: MessageRole::User,
610                content: MessageContent::Text("Hello!".to_string()),
611            }],
612            100,
613        )
614        .credentials(credentials)
615        .create()
616        .await
617        .unwrap();
618
619        assert!(!response.content.is_empty());
620    }
621
622    #[tokio::test]
623    async fn test_streaming_message() {
624        let credentials = Credentials::from_env();
625
626        let mut stream = MessagesResponse::builder(
627            "claude-3-7-sonnet-20250219",
628            vec![Message {
629                role: MessageRole::User,
630                content: MessageContent::Text("Hello!".to_string()),
631            }],
632            100,
633        )
634        .credentials(credentials)
635        .create_stream()
636        .await
637        .unwrap();
638
639        while let Some(event) = stream.recv().await {
640            match event {
641                StreamEvent::ContentBlockDelta { delta, .. } => {
642                    if let ContentBlockDelta::Text { text } = delta {
643                        print!("{}", text);
644                    }
645                }
646                _ => {}
647            }
648        }
649    }
650}