Skip to main content

claude_api/messages/
cache.rs

1//! Cache breakpoints for Anthropic prompt caching.
2//!
3//! # Where to place breakpoints
4//!
5//! A `cache_control: ephemeral` marker tells the server "cache the request
6//! up to and including this block." Typical strategies:
7//!
8//! - **Cache system + tools + first user turn.** The longest-lived prefix.
9//!   Use [`CreateMessageRequestBuilder::cache_control_on_system`],
10//!   [`CreateMessageRequestBuilder::cache_control_on_tools`], and either
11//!   [`CreateMessageRequestBuilder::cache_control_on_last_user`] (called
12//!   once before the first send) or [`ContentBlock::text_cached`] to mark
13//!   that user turn at construction time.
14//! - **Refresh on each turn.** For long conversations, place a fresh
15//!   ephemeral breakpoint on the most recent user turn each request.
16//!   `Conversation::with_auto_cache(AutoCacheMode::SystemAndLastUser)`
17//!   does this for you.
18//!
19//! TTL choice: the default 5-minute cache is right for nearly all
20//! interactive workloads. `"1h"` requires the
21//! `extended-cache-ttl-2025-04-11` beta header and is meant for batch /
22//! long-running pipelines where the same prefix sees sustained traffic.
23//!
24//! [`CreateMessageRequestBuilder::cache_control_on_system`]:
25//! crate::messages::request::CreateMessageRequestBuilder::cache_control_on_system
26//! [`CreateMessageRequestBuilder::cache_control_on_tools`]:
27//! crate::messages::request::CreateMessageRequestBuilder::cache_control_on_tools
28//! [`CreateMessageRequestBuilder::cache_control_on_last_user`]:
29//! crate::messages::request::CreateMessageRequestBuilder::cache_control_on_last_user
30//! [`ContentBlock::text_cached`]:
31//! crate::messages::content::ContentBlock::text_cached
32
33use serde::{Deserialize, Serialize};
34
35/// Marks a cache breakpoint on a content block, system prompt, or tool definition.
36///
37/// Currently only `Ephemeral` is supported. The `ttl` field is optional and
38/// defaults server-side to `"5m"`; `"1h"` requires the appropriate beta header.
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(tag = "type", rename_all = "snake_case")]
41#[non_exhaustive]
42pub enum CacheControl {
43    /// Ephemeral cache entry.
44    Ephemeral {
45        /// Cache TTL hint, e.g. `"5m"` or `"1h"`. Server applies a default if omitted.
46        #[serde(default, skip_serializing_if = "Option::is_none")]
47        ttl: Option<String>,
48    },
49}
50
51impl CacheControl {
52    /// Convenience constructor for an ephemeral cache breakpoint with the default TTL.
53    pub fn ephemeral() -> Self {
54        Self::Ephemeral { ttl: None }
55    }
56
57    /// Convenience constructor for an ephemeral cache breakpoint with a specific TTL.
58    pub fn ephemeral_ttl(ttl: impl Into<String>) -> Self {
59        Self::Ephemeral {
60            ttl: Some(ttl.into()),
61        }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use pretty_assertions::assert_eq;
69    use serde_json::json;
70
71    #[test]
72    fn ephemeral_round_trips_without_ttl() {
73        let cc = CacheControl::ephemeral();
74        let v = serde_json::to_value(&cc).unwrap();
75        assert_eq!(v, json!({"type": "ephemeral"}));
76        let parsed: CacheControl = serde_json::from_value(v).unwrap();
77        assert_eq!(parsed, cc);
78    }
79
80    #[test]
81    fn ephemeral_round_trips_with_ttl() {
82        let cc = CacheControl::ephemeral_ttl("1h");
83        let v = serde_json::to_value(&cc).unwrap();
84        assert_eq!(v, json!({"type": "ephemeral", "ttl": "1h"}));
85        let parsed: CacheControl = serde_json::from_value(v).unwrap();
86        assert_eq!(parsed, cc);
87    }
88}