strands_agents/types/
content.rs

1//! Content types for messages and content blocks.
2
3use serde::{Deserialize, Serialize};
4
5use super::tools::{ToolResult, ToolUse};
6
7/// Represents the role of a message sender.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "lowercase")]
10pub enum Role {
11    User,
12    Assistant,
13}
14
15impl std::fmt::Display for Role {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        write!(f, "{}", self.as_str())
18    }
19}
20
21impl Role {
22    /// Returns the string representation of the role.
23    pub fn as_str(&self) -> &'static str {
24        match self {
25            Role::User => "user",
26            Role::Assistant => "assistant",
27        }
28    }
29}
30
31/// A block of content that can contain text, images, tools, or other media.
32#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
33#[serde(rename_all = "camelCase")]
34pub struct ContentBlock {
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub text: Option<String>,
37
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub image: Option<ImageContent>,
40
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub document: Option<DocumentContent>,
43
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub video: Option<VideoContent>,
46
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub tool_use: Option<ToolUse>,
49
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub tool_result: Option<ToolResult>,
52
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub reasoning_content: Option<ReasoningContentBlock>,
55
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub cache_point: Option<CachePoint>,
58
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub guard_content: Option<GuardContent>,
61
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub citations_content: Option<CitationsContentBlock>,
64}
65
66impl ContentBlock {
67    pub fn text(text: impl Into<String>) -> Self {
68        Self { text: Some(text.into()), ..Default::default() }
69    }
70
71    pub fn tool_use(tool_use: ToolUse) -> Self {
72        Self { tool_use: Some(tool_use), ..Default::default() }
73    }
74
75    pub fn tool_result(result: ToolResult) -> Self {
76        Self { tool_result: Some(result), ..Default::default() }
77    }
78
79    pub fn is_text(&self) -> bool { self.text.is_some() }
80    pub fn is_tool_use(&self) -> bool { self.tool_use.is_some() }
81    pub fn is_tool_result(&self) -> bool { self.tool_result.is_some() }
82    pub fn as_text(&self) -> Option<&str> { self.text.as_deref() }
83    pub fn as_tool_use(&self) -> Option<&ToolUse> { self.tool_use.as_ref() }
84    pub fn as_tool_result(&self) -> Option<&ToolResult> { self.tool_result.as_ref() }
85}
86
87impl From<String> for ContentBlock {
88    fn from(text: String) -> Self { Self::text(text) }
89}
90
91impl From<&str> for ContentBlock {
92    fn from(text: &str) -> Self { Self::text(text) }
93}
94
95/// Image content with format and source data.
96#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
97#[serde(rename_all = "camelCase")]
98pub struct ImageContent {
99    pub format: String,
100    pub source: ImageSource,
101}
102
103/// Source data for an image.
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105#[serde(rename_all = "camelCase")]
106pub struct ImageSource {
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub bytes: Option<String>,
109}
110
111/// Document content with format, name, and source data.
112#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
113#[serde(rename_all = "camelCase")]
114pub struct DocumentContent {
115    pub format: String,
116    pub name: String,
117    pub source: DocumentSource,
118}
119
120/// Source data for a document.
121#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
122#[serde(rename_all = "camelCase")]
123pub struct DocumentSource {
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub bytes: Option<String>,
126}
127
128/// Video content with format and source data.
129#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct VideoContent {
132    pub format: String,
133    pub source: VideoSource,
134}
135
136/// Source data for a video.
137#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
138#[serde(rename_all = "camelCase")]
139pub struct VideoSource {
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub bytes: Option<String>,
142}
143
144/// Content block containing model reasoning.
145#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
146#[serde(rename_all = "camelCase")]
147pub struct ReasoningContentBlock {
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub reasoning_text: Option<ReasoningTextBlock>,
150
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub redacted_content: Option<Vec<u8>>,
153}
154
155/// Text block within reasoning content.
156#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
157#[serde(rename_all = "camelCase")]
158pub struct ReasoningTextBlock {
159    pub text: String,
160
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub signature: Option<String>,
163}
164
165/// A cache point marker for prompt caching.
166#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
167#[serde(rename_all = "camelCase")]
168pub struct CachePoint {
169    #[serde(rename = "type")]
170    pub cache_type: String,
171}
172
173/// Content that has been processed by guardrails.
174#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
175#[serde(rename_all = "camelCase")]
176pub struct GuardContent {
177    pub text: GuardContentText,
178}
179
180/// Text content from guardrail processing.
181#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
182#[serde(rename_all = "camelCase")]
183pub struct GuardContentText {
184    pub qualifiers: Vec<String>,
185    pub text: String,
186}
187
188/// A message in a conversation.
189#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
190#[serde(rename_all = "camelCase")]
191pub struct Message {
192    pub role: Role,
193    pub content: Vec<ContentBlock>,
194}
195
196impl Message {
197    pub fn user(text: impl Into<String>) -> Self {
198        Self { role: Role::User, content: vec![ContentBlock::text(text)] }
199    }
200
201    pub fn assistant(text: impl Into<String>) -> Self {
202        Self { role: Role::Assistant, content: vec![ContentBlock::text(text)] }
203    }
204
205    pub fn new(role: Role, content: Vec<ContentBlock>) -> Self {
206        Self { role, content }
207    }
208
209    pub fn text_content(&self) -> String {
210        self.content.iter().filter_map(|b| b.as_text()).collect::<Vec<_>>().join("")
211    }
212
213    pub fn has_tool_use(&self) -> bool {
214        self.content.iter().any(|b| b.is_tool_use())
215    }
216
217    pub fn tool_uses(&self) -> Vec<&ToolUse> {
218        self.content.iter().filter_map(|b| b.as_tool_use()).collect()
219    }
220
221    pub fn has_tool_result(&self) -> bool {
222        self.content.iter().any(|b| b.is_tool_result())
223    }
224}
225
226/// A collection of messages representing a conversation.
227pub type Messages = Vec<Message>;
228
229/// A content block in a system prompt.
230#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
231#[serde(rename_all = "camelCase")]
232pub struct SystemContentBlock {
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub text: Option<String>,
235
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub cache_point: Option<CachePoint>,
238}
239
240impl SystemContentBlock {
241    pub fn text(text: impl Into<String>) -> Self {
242        Self { text: Some(text.into()), cache_point: None }
243    }
244}
245
246/// Content block containing citations.
247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
248#[serde(rename_all = "camelCase")]
249pub struct CitationsContentBlock {
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub citations: Option<Vec<Citation>>,
252
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub content: Option<Vec<CitationGeneratedContent>>,
255}
256
257/// A citation reference.
258#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
259#[serde(rename_all = "camelCase")]
260pub struct Citation {
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub location: Option<serde_json::Value>,
263
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub source_content: Option<Vec<CitationSourceContent>>,
266
267    #[serde(skip_serializing_if = "Option::is_none")]
268    pub title: Option<String>,
269}
270
271/// Source content for a citation.
272#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
273#[serde(rename_all = "camelCase")]
274pub struct CitationSourceContent {
275    #[serde(skip_serializing_if = "Option::is_none")]
276    pub text: Option<String>,
277}
278
279/// Generated content with citation.
280#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
281#[serde(rename_all = "camelCase")]
282pub struct CitationGeneratedContent {
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub text: Option<String>,
285}
286
287#[cfg(test)]
288mod tests {
289    use super::*;
290
291    #[test]
292    fn test_role_serialization() {
293        assert_eq!(serde_json::to_string(&Role::User).unwrap(), "\"user\"");
294        assert_eq!(serde_json::to_string(&Role::Assistant).unwrap(), "\"assistant\"");
295    }
296
297    #[test]
298    fn test_message_creation() {
299        let msg = Message::user("Hello, world!");
300        assert_eq!(msg.role, Role::User);
301        assert_eq!(msg.text_content(), "Hello, world!");
302    }
303
304    #[test]
305    fn test_content_block_from_string() {
306        let block: ContentBlock = "test".into();
307        assert!(block.is_text());
308        assert_eq!(block.as_text(), Some("test"));
309    }
310
311    #[test]
312    fn test_content_block_serialization() {
313        let block = ContentBlock::text("hello");
314        let json = serde_json::to_string(&block).unwrap();
315        assert_eq!(json, r#"{"text":"hello"}"#);
316    }
317}