Skip to main content

async_llm/anthropic/
types.rs

1use std::{
2    ops::{Deref, DerefMut},
3    pin::Pin,
4};
5
6use derive_builder::Builder;
7use serde::{Deserialize, Serialize, Serializer};
8use serde_json::Value;
9use tokio_stream::Stream;
10
11use super::{errors::AnthropicError, messages};
12
13#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Default)]
14pub struct Usage {
15    pub input_tokens: Option<u32>,
16    pub output_tokens: Option<u32>,
17    /// Tokens written to the prompt cache on this request.
18    /// Populated when one or more `cache_control` markers caused a cache miss.
19    #[serde(default, skip_serializing_if = "Option::is_none")]
20    pub cache_creation_input_tokens: Option<u32>,
21    /// Tokens served from the prompt cache on this request.
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub cache_read_input_tokens: Option<u32>,
24}
25
26/// Marker placed on a content block / tool / system block to define a cache breakpoint.
27///
28/// A breakpoint caches the entire prefix that precedes it (in serialized order:
29/// `tools → system → messages`). Up to four breakpoints per request.
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
31#[serde(tag = "type", rename_all = "snake_case")]
32pub enum CacheControl {
33    Ephemeral {
34        /// Cache TTL — `"5m"` (default) or `"1h"`. `None` means the API default.
35        #[serde(default, skip_serializing_if = "Option::is_none")]
36        ttl: Option<String>,
37    },
38}
39
40impl CacheControl {
41    /// Default 5-minute ephemeral breakpoint.
42    #[must_use]
43    pub fn ephemeral() -> Self {
44        CacheControl::Ephemeral { ttl: None }
45    }
46
47    /// Ephemeral breakpoint with an explicit TTL (e.g. `"5m"`, `"1h"`).
48    #[must_use]
49    pub fn ephemeral_with_ttl(ttl: impl Into<String>) -> Self {
50        CacheControl::Ephemeral {
51            ttl: Some(ttl.into()),
52        }
53    }
54}
55
56/// A thinking block returned by extended-thinking models.
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
58pub struct Thinking {
59    pub thinking: String,
60    /// Provider replay signature. `None` when the provider did not supply one,
61    /// or when the caller deliberately dropped it — genuine Anthropic validates
62    /// this on replay, but Anthropic-compatible endpoints generally do not.
63    /// Omitted from the request when `None`; an empty string is *not* a valid
64    /// substitute.
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub signature: Option<String>,
67    #[serde(default, skip_serializing_if = "Option::is_none")]
68    pub cache_control: Option<CacheControl>,
69}
70
71impl From<Thinking> for MessageContent {
72    fn from(thinking: Thinking) -> Self {
73        MessageContent::Thinking(thinking)
74    }
75}
76
77impl From<Thinking> for MessageContentList {
78    fn from(thinking: Thinking) -> Self {
79        MessageContentList(vec![thinking.into()])
80    }
81}
82
83/// Configuration for extended thinking.
84#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
85#[serde(tag = "type", rename_all = "snake_case")]
86pub enum ThinkingConfig {
87    Enabled { budget_tokens: u32 },
88    Disabled,
89}
90
91/// Output-shaping controls. Currently just `effort`, which sets reasoning depth
92/// on models that support it (`low`/`medium`/`high`/`xhigh`/`max` on current
93/// Anthropic models). The accepted set is model-specific, so this is an
94/// unvalidated string and the caller owns the vocabulary.
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
96pub struct OutputConfig {
97    #[serde(default, skip_serializing_if = "Option::is_none")]
98    pub effort: Option<String>,
99}
100
101#[derive(Clone, Debug, Deserialize)]
102pub enum ToolChoice {
103    Auto,
104    Any,
105    Tool(String),
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize, Builder, PartialEq, Default)]
109#[builder(setter(into, strip_option), default)]
110pub struct Message {
111    pub role: MessageRole,
112    pub content: MessageContentList,
113}
114
115impl Message {
116    /// Returns all the tool uses in the message
117    pub fn tool_uses(&self) -> Vec<ToolUse> {
118        self.content
119            .0
120            .iter()
121            .filter(|c| matches!(c, MessageContent::ToolUse(_)))
122            .map(|c| match c {
123                MessageContent::ToolUse(tool_use) => tool_use.clone(),
124                _ => unreachable!(),
125            })
126            .collect()
127    }
128
129    /// Returns the first text content in the message
130    pub fn text(&self) -> Option<String> {
131        self.content
132            .0
133            .iter()
134            .filter_map(|c| match c {
135                MessageContent::Text(text) => Some(text.text.clone()),
136                _ => None,
137            })
138            .next()
139    }
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
143pub struct MessageContentList(pub Vec<MessageContent>);
144
145impl Deref for MessageContentList {
146    type Target = Vec<MessageContent>;
147
148    fn deref(&self) -> &Self::Target {
149        &self.0
150    }
151}
152
153impl DerefMut for MessageContentList {
154    fn deref_mut(&mut self) -> &mut Self::Target {
155        &mut self.0
156    }
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
160#[serde(rename_all = "snake_case")]
161pub enum MessageRole {
162    #[default]
163    User,
164    Assistant,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize, Builder)]
168#[builder(setter(into, strip_option))]
169pub struct CreateMessagesRequest {
170    pub messages: Vec<Message>,
171    pub model: String,
172    #[builder(default = messages::DEFAULT_MAX_TOKENS)]
173    pub max_tokens: i32,
174    #[builder(default)]
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub metadata: Option<serde_json::Map<String, Value>>,
177    #[serde(skip_serializing_if = "Option::is_none")]
178    #[builder(default)]
179    pub stop_sequences: Option<Vec<String>>,
180    #[builder(default = "false")]
181    pub stream: bool, // Optional default false
182    #[serde(skip_serializing_if = "Option::is_none")]
183    #[builder(default)]
184    pub temperature: Option<f32>, // 0 < x < 1
185    #[serde(skip_serializing_if = "Option::is_none")]
186    #[builder(default)]
187    pub tool_choice: Option<ToolChoice>,
188    // TODO: Type this
189    #[serde(skip_serializing_if = "Option::is_none")]
190    #[builder(default)]
191    pub tools: Option<Vec<serde_json::Map<String, Value>>>,
192    #[serde(skip_serializing_if = "Option::is_none")]
193    #[builder(default)]
194    pub top_k: Option<u32>, // > 0
195    #[serde(skip_serializing_if = "Option::is_none")]
196    #[builder(default)]
197    pub top_p: Option<f32>, // 0 < x < 1
198    #[serde(skip_serializing_if = "Option::is_none")]
199    #[builder(default)]
200    pub system: Option<String>,
201    #[serde(skip_serializing_if = "Option::is_none")]
202    #[builder(default)]
203    pub thinking: Option<ThinkingConfig>,
204    #[serde(skip_serializing_if = "Option::is_none")]
205    #[builder(default)]
206    pub output_config: Option<OutputConfig>,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize, Builder)]
210#[builder(setter(into, strip_option))]
211pub struct CreateMessagesResponse {
212    #[serde(default)]
213    pub id: Option<String>,
214    #[serde(default)]
215    pub content: Option<Vec<MessageContent>>,
216    #[serde(default)]
217    pub model: Option<String>,
218    #[serde(default)]
219    pub stop_reason: Option<String>,
220    #[serde(default)]
221    pub stop_sequence: Option<String>,
222    #[serde(default)]
223    pub usage: Option<Usage>,
224}
225
226impl CreateMessagesResponse {
227    /// Returns the content as Messages so they are more easily reusable
228    pub fn messages(&self) -> Vec<Message> {
229        let Some(content) = &self.content else {
230            return vec![];
231        };
232        content
233            .iter()
234            .map(|c| Message {
235                role: MessageRole::Assistant,
236                content: c.clone().into(),
237            })
238            .collect()
239    }
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
243#[serde(tag = "type", rename_all = "snake_case")]
244pub enum MessageContent {
245    ToolUse(ToolUse),
246    ToolResult(ToolResult),
247    Text(Text),
248    Thinking(Thinking),
249    Image(Image),
250    Document(Document),
251}
252
253impl MessageContent {
254    pub fn as_tool_use(&self) -> Option<&ToolUse> {
255        if let MessageContent::ToolUse(tool_use) = self {
256            Some(tool_use)
257        } else {
258            None
259        }
260    }
261
262    pub fn as_tool_result(&self) -> Option<&ToolResult> {
263        if let MessageContent::ToolResult(tool_result) = self {
264            Some(tool_result)
265        } else {
266            None
267        }
268    }
269
270    pub fn as_text(&self) -> Option<&Text> {
271        if let MessageContent::Text(text) = self {
272            Some(text)
273        } else {
274            None
275        }
276    }
277
278    pub fn as_thinking(&self) -> Option<&Thinking> {
279        if let MessageContent::Thinking(thinking) = self {
280            Some(thinking)
281        } else {
282            None
283        }
284    }
285
286    pub fn as_image(&self) -> Option<&Image> {
287        if let MessageContent::Image(image) = self {
288            Some(image)
289        } else {
290            None
291        }
292    }
293
294    pub fn as_document(&self) -> Option<&Document> {
295        if let MessageContent::Document(document) = self {
296            Some(document)
297        } else {
298            None
299        }
300    }
301}
302
303/// Where a media block's bytes come from.
304///
305/// Anthropic accepts the bytes inline as base64, or a URL it fetches itself.
306/// The two are a tagged union on the wire rather than two optional fields, so
307/// a block carrying neither — or both — cannot be built.
308#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
309#[serde(tag = "type", rename_all = "snake_case")]
310pub enum MediaSource {
311    Base64(Base64Source),
312    Url(UrlSource),
313}
314
315impl Default for MediaSource {
316    fn default() -> Self {
317        MediaSource::Base64(Base64Source::default())
318    }
319}
320
321/// Bytes inline. `media_type` is what the API dispatches on, so it must be the
322/// real type of `data` — "image/png", "application/pdf" and so on.
323#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, Builder)]
324#[builder(setter(into, strip_option), default)]
325pub struct Base64Source {
326    pub media_type: String,
327    /// Standard base64, no data-URL prefix.
328    pub data: String,
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, Builder)]
332#[builder(setter(into, strip_option), default)]
333pub struct UrlSource {
334    pub url: String,
335}
336
337/// An image the model can see.
338#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, Builder)]
339#[builder(setter(into, strip_option), default)]
340pub struct Image {
341    pub source: MediaSource,
342    #[serde(default, skip_serializing_if = "Option::is_none")]
343    pub cache_control: Option<CacheControl>,
344}
345
346impl Image {
347    /// An image from inline bytes, already base64-encoded.
348    pub fn base64(media_type: impl Into<String>, data: impl Into<String>) -> Self {
349        Image {
350            source: MediaSource::Base64(Base64Source {
351                media_type: media_type.into(),
352                data: data.into(),
353            }),
354            cache_control: None,
355        }
356    }
357}
358
359impl From<Image> for MessageContent {
360    fn from(image: Image) -> Self {
361        MessageContent::Image(image)
362    }
363}
364
365/// A document — a PDF — the model can read.
366#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, Builder)]
367#[builder(setter(into, strip_option), default)]
368pub struct Document {
369    pub source: MediaSource,
370    /// Shown to the model as the document's name, when supplied.
371    #[serde(default, skip_serializing_if = "Option::is_none")]
372    pub title: Option<String>,
373    #[serde(default, skip_serializing_if = "Option::is_none")]
374    pub cache_control: Option<CacheControl>,
375}
376
377impl Document {
378    /// A document from inline bytes, already base64-encoded.
379    pub fn base64(media_type: impl Into<String>, data: impl Into<String>) -> Self {
380        Document {
381            source: MediaSource::Base64(Base64Source {
382                media_type: media_type.into(),
383                data: data.into(),
384            }),
385            title: None,
386            cache_control: None,
387        }
388    }
389}
390
391impl From<Document> for MessageContent {
392    fn from(document: Document) -> Self {
393        MessageContent::Document(document)
394    }
395}
396
397#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, Builder)]
398#[builder(setter(into, strip_option), default)]
399pub struct ToolUse {
400    pub id: String,
401    pub input: Value,
402    pub name: String,
403    #[serde(default, skip_serializing_if = "Option::is_none")]
404    pub cache_control: Option<CacheControl>,
405}
406
407impl From<ToolUse> for MessageContent {
408    fn from(tool_use: ToolUse) -> Self {
409        MessageContent::ToolUse(tool_use)
410    }
411}
412
413impl From<ToolUse> for MessageContentList {
414    fn from(tool_use: ToolUse) -> Self {
415        MessageContentList(vec![tool_use.into()])
416    }
417}
418
419#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, Builder)]
420#[builder(setter(into, strip_option), default)]
421pub struct ToolResult {
422    pub tool_use_id: String,
423    pub content: Option<ToolResultContent>,
424    pub is_error: bool,
425    #[serde(default, skip_serializing_if = "Option::is_none")]
426    pub cache_control: Option<CacheControl>,
427}
428
429/// What a tool answered with.
430///
431/// A bare string for the common case, or a block list when the tool produced
432/// something that is not text — a screenshot, say. Untagged, so a plain string
433/// still goes over the wire as a plain string and existing callers are
434/// unaffected.
435#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
436#[serde(untagged)]
437pub enum ToolResultContent {
438    Text(String),
439    Blocks(Vec<ToolResultBlock>),
440}
441
442impl Default for ToolResultContent {
443    fn default() -> Self {
444        ToolResultContent::Text(String::new())
445    }
446}
447
448// Concrete rather than a blanket `AsRef<str>`: a blanket impl would overlap
449// with `From<Vec<ToolResultBlock>>` under coherence, since a future `Vec` could
450// implement `AsRef<str>`.
451impl From<String> for ToolResultContent {
452    fn from(s: String) -> Self {
453        ToolResultContent::Text(s)
454    }
455}
456
457impl From<&str> for ToolResultContent {
458    fn from(s: &str) -> Self {
459        ToolResultContent::Text(s.to_string())
460    }
461}
462
463impl From<Vec<ToolResultBlock>> for ToolResultContent {
464    fn from(blocks: Vec<ToolResultBlock>) -> Self {
465        ToolResultContent::Blocks(blocks)
466    }
467}
468
469/// A block inside a `tool_result`.
470///
471/// Only text and images: Anthropic accepts nothing else there, and reusing
472/// [`MessageContent`] would let a caller nest a tool result inside a tool
473/// result.
474#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
475#[serde(tag = "type", rename_all = "snake_case")]
476pub enum ToolResultBlock {
477    Text(Text),
478    Image(Image),
479}
480
481impl From<Text> for ToolResultBlock {
482    fn from(text: Text) -> Self {
483        ToolResultBlock::Text(text)
484    }
485}
486
487impl From<Image> for ToolResultBlock {
488    fn from(image: Image) -> Self {
489        ToolResultBlock::Image(image)
490    }
491}
492
493impl From<ToolResult> for MessageContent {
494    fn from(tool_result: ToolResult) -> Self {
495        MessageContent::ToolResult(tool_result)
496    }
497}
498
499impl From<ToolResult> for MessageContentList {
500    fn from(tool_result: ToolResult) -> Self {
501        MessageContentList(vec![tool_result.into()])
502    }
503}
504
505#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, Builder)]
506#[builder(setter(into, strip_option), default)]
507pub struct Text {
508    pub text: String,
509    #[serde(default, skip_serializing_if = "Option::is_none")]
510    pub cache_control: Option<CacheControl>,
511}
512
513impl<S: AsRef<str>> From<S> for Text {
514    fn from(s: S) -> Self {
515        Text {
516            text: s.as_ref().to_string(),
517            ..Default::default()
518        }
519    }
520}
521
522impl From<Text> for MessageContent {
523    fn from(text: Text) -> Self {
524        MessageContent::Text(text)
525    }
526}
527
528impl From<Text> for MessageContentList {
529    fn from(text: Text) -> Self {
530        MessageContentList(vec![text.into()])
531    }
532}
533
534impl<S: AsRef<str>> From<S> for MessageContent {
535    fn from(s: S) -> Self {
536        MessageContent::Text(Text {
537            text: s.as_ref().to_string(),
538            ..Default::default()
539        })
540    }
541}
542
543impl<S: AsRef<str>> From<S> for Message {
544    fn from(s: S) -> Self {
545        MessageBuilder::default()
546            .role(MessageRole::User)
547            .content(s.as_ref().to_string())
548            .build()
549            .expect("infallible")
550    }
551}
552
553// Any single AsRef<str> can be converted to a MessageContent, in a list as a single item
554impl<S: AsRef<str>> From<S> for MessageContentList {
555    fn from(s: S) -> Self {
556        MessageContentList(vec![s.as_ref().into()])
557    }
558}
559
560impl From<MessageContent> for MessageContentList {
561    fn from(content: MessageContent) -> Self {
562        MessageContentList(vec![content])
563    }
564}
565
566impl Serialize for ToolChoice {
567    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
568    where
569        S: Serializer,
570    {
571        match self {
572            ToolChoice::Auto => {
573                serde::Serialize::serialize(&serde_json::json!({"type": "auto"}), serializer)
574            }
575            ToolChoice::Any => {
576                serde::Serialize::serialize(&serde_json::json!({"type": "any"}), serializer)
577            }
578            ToolChoice::Tool(name) => serde::Serialize::serialize(
579                &serde_json::json!({"type": "tool", "name": name}),
580                serializer,
581            ),
582        }
583    }
584}
585#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
586#[serde(rename_all = "snake_case", tag = "type")]
587pub enum ContentBlockDelta {
588    TextDelta { text: String },
589    InputJsonDelta { partial_json: String },
590    ThinkingDelta { thinking: String },
591    SignatureDelta { signature: String },
592}
593
594#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
595pub struct MessageDelta {
596    pub stop_reason: Option<String>,
597    pub stop_sequence: Option<String>,
598}
599
600#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
601#[serde(rename_all = "snake_case", tag = "type")]
602pub enum MessagesStreamEvent {
603    MessageStart {
604        message: MessageStart,
605        usage: Option<Usage>,
606    },
607    ContentBlockStart {
608        index: usize,
609        content_block: MessageContent,
610    },
611    ContentBlockDelta {
612        index: usize,
613        delta: ContentBlockDelta,
614    },
615    ContentBlockStop {
616        index: usize,
617    },
618    MessageDelta {
619        delta: MessageDelta,
620        #[serde(default)]
621        usage: Option<Usage>,
622    },
623    MessageStop,
624}
625#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
626pub struct MessageStart {
627    pub id: String,
628    pub model: String,
629    pub role: String,
630    pub content: Vec<MessageContent>,
631    #[serde(default)]
632    pub stop_reason: Option<String>,
633    #[serde(default)]
634    pub stop_sequence: Option<String>,
635    #[serde(default)]
636    pub usage: Option<Usage>,
637}
638
639pub type CreateMessagesResponseStream =
640    Pin<Box<dyn Stream<Item = Result<MessagesStreamEvent, AnthropicError>> + Send>>;
641
642#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
643pub struct ListModelsResponse {
644    #[serde(default)]
645    pub data: Vec<Model>,
646
647    #[serde(default)]
648    pub first_id: Option<String>,
649    pub has_more: bool,
650    #[serde(default)]
651    pub last_id: Option<String>,
652}
653
654#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
655pub struct Model {
656    pub created_at: String,
657    pub display_name: String,
658    pub id: String,
659    #[serde(rename = "type")]
660    pub model_type: String,
661}
662
663pub type GetModelResponse = Model;
664
665#[cfg(test)]
666mod tests {
667    use serde_json::json;
668
669    use super::*;
670
671    #[test_log::test(tokio::test)]
672    async fn test_deserialize_response() {
673        let response = json!({
674        "id":"msg_01KkaCASJuaAgTWD2wqdbwC8",
675        "type":"message",
676        "role":"assistant",
677        "model":"claude-3-5-sonnet-20241022",
678        "content":[
679            {"type":"text",
680        "text":"Hi! How can I help you today?"}],
681        "stop_reason":"end_turn",
682        "stop_sequence":null,
683        "usage":{"input_tokens":10,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":12}}).to_string();
684
685        let response = serde_json::from_str::<CreateMessagesResponse>(&response).unwrap();
686
687        let usage = response.usage.as_ref().unwrap();
688
689        assert_eq!(usage.input_tokens, Some(10));
690        assert_eq!(usage.output_tokens, Some(12));
691        assert_eq!(usage.cache_creation_input_tokens, Some(0));
692        assert_eq!(usage.cache_read_input_tokens, Some(0));
693        assert_eq!(
694            response.id,
695            Some("msg_01KkaCASJuaAgTWD2wqdbwC8".to_string())
696        );
697        assert_eq!(
698            response.model,
699            Some("claude-3-5-sonnet-20241022".to_string())
700        );
701        assert_eq!(response.stop_reason, Some("end_turn".to_string()));
702        assert_eq!(response.stop_sequence, None);
703        assert_eq!(
704            response
705                .messages()
706                .first()
707                .unwrap()
708                .content
709                .first()
710                .unwrap()
711                .as_text(),
712            Some(&Text {
713                text: "Hi! How can I help you today?".to_string(),
714                cache_control: None,
715            })
716        );
717    }
718
719    #[test_log::test(tokio::test)]
720    async fn test_from_str() {
721        let message: Message = "Hello world!".into();
722
723        assert_eq!(
724            message,
725            Message {
726                role: MessageRole::User,
727                content: MessageContentList(vec![MessageContent::Text(Text {
728                    text: "Hello world!".to_string(),
729                    cache_control: None,
730                })]),
731            }
732        );
733
734        assert_eq!(message.text(), Some("Hello world!".to_string()));
735    }
736}