Skip to main content

anthropic_async/types/
messages.rs

1use derive_builder::Builder;
2use serde::{Deserialize, Serialize};
3
4use super::common::{Metadata, Usage};
5use super::content::{ContentBlock, MessageParam, MessageRole, SystemParam};
6use super::tools::{Tool, ToolChoice};
7
8/// Output format for structured outputs (beta)
9///
10/// Used to constrain the assistant's response to match a specific JSON schema.
11/// Requires a structured outputs beta header to be enabled via [`BetaFeature`](crate::BetaFeature).
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13#[serde(tag = "type", rename_all = "snake_case")]
14#[allow(clippy::derive_partial_eq_without_eq)] // serde_json::Value doesn't impl Eq
15pub enum OutputFormat {
16    /// Structured outputs via JSON schema
17    #[serde(rename = "json_schema")]
18    JsonSchema {
19        /// JSON Schema object that the response must conform to
20        schema: serde_json::Value,
21    },
22}
23
24/// Request to create a message
25#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Builder, Default)]
26#[builder(setter(into, strip_option), default)]
27pub struct MessagesCreateRequest {
28    /// Model to use for generation
29    #[builder(default)]
30    pub model: String,
31    /// Maximum tokens to generate
32    #[builder(default)]
33    pub max_tokens: u32,
34    /// Optional system prompt
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub system: Option<SystemParam>,
37    /// Conversation messages
38    #[builder(default)]
39    pub messages: Vec<MessageParam>,
40    /// Optional temperature for sampling
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub temperature: Option<f32>,
43    /// Optional stop sequences
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub stop_sequences: Option<Vec<String>>,
46    /// Optional nucleus sampling parameter
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub top_p: Option<f32>,
49    /// Optional top-k sampling parameter
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub top_k: Option<u32>,
52    /// Optional metadata
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub metadata: Option<Metadata>,
55    /// Optional tools for Claude to use
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub tools: Option<Vec<Tool>>,
58    /// Optional tool choice strategy
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub tool_choice: Option<ToolChoice>,
61    /// Enable streaming responses; set automatically by `create_stream()`
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub stream: Option<bool>,
64    /// Structured outputs: set a JSON schema to constrain assistant outputs (beta)
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub output_format: Option<OutputFormat>,
67}
68
69/// Response from creating a message
70#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
71pub struct MessagesCreateResponse {
72    /// Message ID
73    pub id: String,
74    /// Type of response (always "message")
75    #[serde(rename = "type")]
76    pub kind: String,
77    /// Role of the response
78    pub role: MessageRole,
79    /// Content blocks in the response
80    pub content: Vec<ContentBlock>,
81    /// Model used for generation
82    pub model: String,
83    /// Optional stop reason
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub stop_reason: Option<String>,
86    /// Optional token usage information
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub usage: Option<Usage>,
89}
90
91/// Request to count tokens for a message
92#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
93pub struct MessageTokensCountRequest {
94    /// Model to use for token counting
95    pub model: String,
96    /// Optional system prompt
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub system: Option<SystemParam>,
99    /// Conversation messages
100    pub messages: Vec<MessageParam>,
101    /// Optional tools for Claude to use
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub tools: Option<Vec<Tool>>,
104    /// Optional tool choice strategy
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub tool_choice: Option<ToolChoice>,
107}
108
109/// Response from counting tokens
110#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
111pub struct MessageTokensCountResponse {
112    /// Number of input tokens
113    pub input_tokens: u64,
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119    use crate::types::content::{ContentBlockParam, MessageContentParam};
120
121    #[test]
122    fn message_request_ser() {
123        let req = MessagesCreateRequest {
124            model: "claude-3-5-sonnet-20241022".into(),
125            max_tokens: 128,
126            system: None,
127            messages: vec![MessageParam {
128                role: MessageRole::User,
129                content: "Hello".into(),
130            }],
131            temperature: None,
132            stop_sequences: None,
133            top_p: None,
134            top_k: None,
135            metadata: None,
136            tools: None,
137            tool_choice: None,
138            stream: None,
139            output_format: None,
140        };
141        let s = serde_json::to_string(&req).unwrap();
142        assert!(s.contains(r#""model":"claude-3-5-sonnet-20241022""#));
143        assert!(s.contains(r#""max_tokens":128"#));
144        assert!(s.contains(r#""Hello""#));
145        // stream and output_format should not appear when None
146        assert!(!s.contains("stream"));
147        assert!(!s.contains("output_format"));
148    }
149
150    #[test]
151    fn message_request_with_system_string() {
152        let req = MessagesCreateRequest {
153            model: "claude-3-5-sonnet-20241022".into(),
154            max_tokens: 128,
155            system: Some("You are helpful".into()),
156            messages: vec![MessageParam {
157                role: MessageRole::User,
158                content: "Hello".into(),
159            }],
160            temperature: None,
161            stop_sequences: None,
162            top_p: None,
163            top_k: None,
164            metadata: None,
165            tools: None,
166            tool_choice: None,
167            stream: None,
168            output_format: None,
169        };
170        let s = serde_json::to_string(&req).unwrap();
171        assert!(s.contains(r#""system":"You are helpful""#));
172    }
173
174    #[test]
175    fn message_request_with_blocks() {
176        let req = MessagesCreateRequest {
177            model: "claude-3-5-sonnet-20241022".into(),
178            max_tokens: 128,
179            system: None,
180            messages: vec![MessageParam {
181                role: MessageRole::User,
182                content: MessageContentParam::Blocks(vec![ContentBlockParam::Text {
183                    text: "Block content".into(),
184                    cache_control: None,
185                }]),
186            }],
187            temperature: Some(0.7),
188            stop_sequences: None,
189            top_p: None,
190            top_k: None,
191            metadata: None,
192            tools: None,
193            tool_choice: None,
194            stream: None,
195            output_format: None,
196        };
197        let s = serde_json::to_string(&req).unwrap();
198        assert!(s.contains(r#""Block content""#));
199        assert!(s.contains(r#""temperature":0.7"#));
200    }
201}