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}