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}