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