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}