agent_chain_core/messages/
system.rs

1//! System message type.
2//!
3//! This module contains the `SystemMessage` and `SystemMessageChunk` types which represent
4//! system instructions for priming AI behavior. Mirrors `langchain_core.messages.system`.
5
6use crate::utils::uuid7;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[cfg(feature = "specta")]
11use specta::Type;
12
13use super::base::merge_content;
14
15/// A system message in the conversation.
16///
17/// The system message is usually passed in as the first of a sequence
18/// of input messages. It's used to prime AI behavior with instructions.
19///
20/// This corresponds to `SystemMessage` in LangChain Python.
21#[cfg_attr(feature = "specta", derive(Type))]
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
23pub struct SystemMessage {
24    /// The message content
25    content: String,
26    /// Optional unique identifier
27    id: Option<String>,
28    /// Optional name for the message
29    #[serde(skip_serializing_if = "Option::is_none")]
30    name: Option<String>,
31    /// Additional metadata
32    #[serde(default)]
33    additional_kwargs: HashMap<String, serde_json::Value>,
34}
35
36impl SystemMessage {
37    /// Create a new system message.
38    pub fn new(content: impl Into<String>) -> Self {
39        Self {
40            content: content.into(),
41            id: Some(uuid7(None).to_string()),
42            name: None,
43            additional_kwargs: HashMap::new(),
44        }
45    }
46
47    /// Create a new system message with an explicit ID.
48    ///
49    /// Use this when deserializing or reconstructing messages where the ID must be preserved.
50    pub fn with_id(id: impl Into<String>, content: impl Into<String>) -> Self {
51        Self {
52            content: content.into(),
53            id: Some(id.into()),
54            name: None,
55            additional_kwargs: HashMap::new(),
56        }
57    }
58
59    /// Set the name for this message.
60    pub fn with_name(mut self, name: impl Into<String>) -> Self {
61        self.name = Some(name.into());
62        self
63    }
64
65    /// Get the message content.
66    pub fn content(&self) -> &str {
67        &self.content
68    }
69
70    /// Get the message ID.
71    pub fn id(&self) -> Option<&str> {
72        self.id.as_deref()
73    }
74
75    /// Get the message name.
76    pub fn name(&self) -> Option<&str> {
77        self.name.as_deref()
78    }
79
80    /// Get additional kwargs.
81    pub fn additional_kwargs(&self) -> &HashMap<String, serde_json::Value> {
82        &self.additional_kwargs
83    }
84}
85
86/// System message chunk (yielded when streaming).
87///
88/// This corresponds to `SystemMessageChunk` in LangChain Python.
89#[cfg_attr(feature = "specta", derive(Type))]
90#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
91pub struct SystemMessageChunk {
92    /// The message content (may be partial during streaming)
93    content: String,
94    /// Optional unique identifier
95    id: Option<String>,
96    /// Optional name for the message
97    #[serde(skip_serializing_if = "Option::is_none")]
98    name: Option<String>,
99    /// Additional metadata
100    #[serde(default)]
101    additional_kwargs: HashMap<String, serde_json::Value>,
102    /// Response metadata
103    #[serde(default)]
104    response_metadata: HashMap<String, serde_json::Value>,
105}
106
107impl SystemMessageChunk {
108    /// Create a new system message chunk.
109    pub fn new(content: impl Into<String>) -> Self {
110        Self {
111            content: content.into(),
112            id: None,
113            name: None,
114            additional_kwargs: HashMap::new(),
115            response_metadata: HashMap::new(),
116        }
117    }
118
119    /// Create a new system message chunk with an ID.
120    pub fn with_id(id: impl Into<String>, content: impl Into<String>) -> Self {
121        Self {
122            content: content.into(),
123            id: Some(id.into()),
124            name: None,
125            additional_kwargs: HashMap::new(),
126            response_metadata: HashMap::new(),
127        }
128    }
129
130    /// Get the message content.
131    pub fn content(&self) -> &str {
132        &self.content
133    }
134
135    /// Get the message ID.
136    pub fn id(&self) -> Option<&str> {
137        self.id.as_deref()
138    }
139
140    /// Get the message name.
141    pub fn name(&self) -> Option<&str> {
142        self.name.as_deref()
143    }
144
145    /// Get additional kwargs.
146    pub fn additional_kwargs(&self) -> &HashMap<String, serde_json::Value> {
147        &self.additional_kwargs
148    }
149
150    /// Get response metadata.
151    pub fn response_metadata(&self) -> &HashMap<String, serde_json::Value> {
152        &self.response_metadata
153    }
154
155    /// Concatenate this chunk with another chunk.
156    pub fn concat(&self, other: &SystemMessageChunk) -> SystemMessageChunk {
157        let content = merge_content(&self.content, &other.content);
158
159        // Merge additional_kwargs
160        let mut additional_kwargs = self.additional_kwargs.clone();
161        for (k, v) in &other.additional_kwargs {
162            additional_kwargs.insert(k.clone(), v.clone());
163        }
164
165        // Merge response_metadata
166        let mut response_metadata = self.response_metadata.clone();
167        for (k, v) in &other.response_metadata {
168            response_metadata.insert(k.clone(), v.clone());
169        }
170
171        SystemMessageChunk {
172            content,
173            id: self.id.clone().or_else(|| other.id.clone()),
174            name: self.name.clone().or_else(|| other.name.clone()),
175            additional_kwargs,
176            response_metadata,
177        }
178    }
179
180    /// Convert this chunk to a complete SystemMessage.
181    pub fn to_message(&self) -> SystemMessage {
182        SystemMessage {
183            content: self.content.clone(),
184            id: self.id.clone(),
185            name: self.name.clone(),
186            additional_kwargs: self.additional_kwargs.clone(),
187        }
188    }
189}
190
191impl std::ops::Add for SystemMessageChunk {
192    type Output = SystemMessageChunk;
193
194    fn add(self, other: SystemMessageChunk) -> SystemMessageChunk {
195        self.concat(&other)
196    }
197}