agent_chain_core/
prompt_values.rs

1//! Prompt values for language model prompts.
2//!
3//! Prompt values are used to represent different pieces of prompts.
4//! They can be used to represent text, images, or chat message pieces.
5//!
6//! This module mirrors `langchain_core.prompt_values` in Python.
7
8use serde::{Deserialize, Serialize};
9
10#[cfg(feature = "specta")]
11use specta::Type;
12
13use crate::load::Serializable;
14use crate::messages::{
15    AnyMessage, BaseMessage, ContentPart, HumanMessage, ImageDetail, ImageSource, get_buffer_string,
16};
17
18/// Base trait for inputs to any language model.
19///
20/// `PromptValue` types can be converted to both LLM (pure text-generation) inputs
21/// and chat model inputs.
22///
23/// This corresponds to the abstract `PromptValue` class in LangChain Python.
24pub trait PromptValue: Serializable {
25    /// Return prompt value as a string.
26    fn to_string(&self) -> String;
27
28    /// Return prompt as a list of messages.
29    fn to_messages(&self) -> Vec<BaseMessage>;
30}
31
32/// Image detail level for OpenAI-compatible APIs.
33#[cfg_attr(feature = "specta", derive(Type))]
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
35#[serde(rename_all = "lowercase")]
36pub enum ImageDetailLevel {
37    /// Automatic detail level selection.
38    #[default]
39    Auto,
40    /// Low detail level.
41    Low,
42    /// High detail level.
43    High,
44}
45
46/// Image URL structure for image prompts.
47///
48/// This follows OpenAI's Chat Completion API's image URL format.
49///
50/// Corresponds to `ImageURL` TypedDict in Python.
51#[cfg_attr(feature = "specta", derive(Type))]
52#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
53pub struct ImageURL {
54    /// Either a URL of the image or the base64 encoded image data.
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub url: Option<String>,
57    /// Specifies the detail level of the image.
58    /// Can be `auto`, `low`, or `high`.
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub detail: Option<ImageDetailLevel>,
61}
62
63impl ImageURL {
64    /// Create a new ImageURL with just a URL.
65    pub fn new(url: impl Into<String>) -> Self {
66        Self {
67            url: Some(url.into()),
68            detail: None,
69        }
70    }
71
72    /// Create a new ImageURL with URL and detail level.
73    pub fn with_detail(url: impl Into<String>, detail: ImageDetailLevel) -> Self {
74        Self {
75            url: Some(url.into()),
76            detail: Some(detail),
77        }
78    }
79
80    /// Get the URL, or empty string if not set.
81    pub fn get_url(&self) -> &str {
82        self.url.as_deref().unwrap_or("")
83    }
84}
85
86/// String prompt value.
87///
88/// A simple prompt value containing just text content.
89///
90/// Corresponds to `StringPromptValue` in Python.
91#[cfg_attr(feature = "specta", derive(Type))]
92#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
93pub struct StringPromptValue {
94    /// Prompt text.
95    pub text: String,
96}
97
98impl StringPromptValue {
99    /// Create a new StringPromptValue.
100    pub fn new(text: impl Into<String>) -> Self {
101        Self { text: text.into() }
102    }
103}
104
105impl PromptValue for StringPromptValue {
106    fn to_string(&self) -> String {
107        self.text.clone()
108    }
109
110    fn to_messages(&self) -> Vec<BaseMessage> {
111        vec![BaseMessage::Human(HumanMessage::new(&self.text))]
112    }
113}
114
115impl Serializable for StringPromptValue {
116    fn is_lc_serializable() -> bool
117    where
118        Self: Sized,
119    {
120        true
121    }
122
123    fn get_lc_namespace() -> Vec<String>
124    where
125        Self: Sized,
126    {
127        vec![
128            "langchain".to_string(),
129            "prompts".to_string(),
130            "base".to_string(),
131        ]
132    }
133}
134
135/// Chat prompt value.
136///
137/// A type of prompt value that is built from messages.
138///
139/// Corresponds to `ChatPromptValue` in Python.
140#[cfg_attr(feature = "specta", derive(Type))]
141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
142pub struct ChatPromptValue {
143    /// List of messages.
144    pub messages: Vec<BaseMessage>,
145}
146
147impl ChatPromptValue {
148    /// Create a new ChatPromptValue from a list of messages.
149    pub fn new(messages: Vec<BaseMessage>) -> Self {
150        Self { messages }
151    }
152
153    /// Create a new ChatPromptValue from a single message.
154    pub fn from_message(message: impl Into<BaseMessage>) -> Self {
155        Self {
156            messages: vec![message.into()],
157        }
158    }
159}
160
161impl PromptValue for ChatPromptValue {
162    fn to_string(&self) -> String {
163        get_buffer_string(&self.messages, "Human", "AI")
164    }
165
166    fn to_messages(&self) -> Vec<BaseMessage> {
167        self.messages.clone()
168    }
169}
170
171impl Serializable for ChatPromptValue {
172    fn is_lc_serializable() -> bool
173    where
174        Self: Sized,
175    {
176        true
177    }
178
179    fn get_lc_namespace() -> Vec<String>
180    where
181        Self: Sized,
182    {
183        vec![
184            "langchain".to_string(),
185            "prompts".to_string(),
186            "chat".to_string(),
187        ]
188    }
189}
190
191/// Image prompt value.
192///
193/// A prompt value containing an image URL.
194///
195/// Corresponds to `ImagePromptValue` in Python.
196#[cfg_attr(feature = "specta", derive(Type))]
197#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
198pub struct ImagePromptValue {
199    /// Image URL.
200    pub image_url: ImageURL,
201}
202
203impl ImagePromptValue {
204    /// Create a new ImagePromptValue from an ImageURL.
205    pub fn new(image_url: ImageURL) -> Self {
206        Self { image_url }
207    }
208
209    /// Create a new ImagePromptValue from a URL string.
210    pub fn from_url(url: impl Into<String>) -> Self {
211        Self {
212            image_url: ImageURL::new(url),
213        }
214    }
215
216    /// Create a new ImagePromptValue from a URL string with detail level.
217    pub fn from_url_with_detail(url: impl Into<String>, detail: ImageDetailLevel) -> Self {
218        Self {
219            image_url: ImageURL::with_detail(url, detail),
220        }
221    }
222}
223
224impl PromptValue for ImagePromptValue {
225    fn to_string(&self) -> String {
226        self.image_url.get_url().to_string()
227    }
228
229    fn to_messages(&self) -> Vec<BaseMessage> {
230        let url = self.image_url.get_url().to_string();
231        let detail = self.image_url.detail.as_ref().map(|d| match d {
232            ImageDetailLevel::Auto => ImageDetail::Auto,
233            ImageDetailLevel::Low => ImageDetail::Low,
234            ImageDetailLevel::High => ImageDetail::High,
235        });
236
237        let content_part = ContentPart::Image {
238            source: ImageSource::Url { url },
239            detail,
240        };
241
242        vec![BaseMessage::Human(HumanMessage::with_content(vec![
243            content_part,
244        ]))]
245    }
246}
247
248impl Serializable for ImagePromptValue {
249    fn is_lc_serializable() -> bool
250    where
251        Self: Sized,
252    {
253        true
254    }
255
256    fn get_lc_namespace() -> Vec<String>
257    where
258        Self: Sized,
259    {
260        vec![
261            "langchain".to_string(),
262            "schema".to_string(),
263            "prompt".to_string(),
264        ]
265    }
266}
267
268/// Chat prompt value which explicitly lists out the message types it accepts.
269///
270/// For use in external schemas.
271///
272/// Corresponds to `ChatPromptValueConcrete` in Python.
273#[cfg_attr(feature = "specta", derive(Type))]
274#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
275pub struct ChatPromptValueConcrete {
276    /// Sequence of messages.
277    pub messages: Vec<AnyMessage>,
278}
279
280impl ChatPromptValueConcrete {
281    /// Create a new ChatPromptValueConcrete from a list of messages.
282    pub fn new(messages: Vec<AnyMessage>) -> Self {
283        Self { messages }
284    }
285}
286
287impl PromptValue for ChatPromptValueConcrete {
288    fn to_string(&self) -> String {
289        get_buffer_string(&self.messages, "Human", "AI")
290    }
291
292    fn to_messages(&self) -> Vec<BaseMessage> {
293        self.messages.clone()
294    }
295}
296
297impl Serializable for ChatPromptValueConcrete {
298    fn is_lc_serializable() -> bool
299    where
300        Self: Sized,
301    {
302        true
303    }
304
305    fn get_lc_namespace() -> Vec<String>
306    where
307        Self: Sized,
308    {
309        vec![
310            "langchain".to_string(),
311            "prompts".to_string(),
312            "chat".to_string(),
313        ]
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320    use crate::messages::{AIMessage, SystemMessage};
321
322    #[test]
323    fn test_string_prompt_value() {
324        let pv = StringPromptValue::new("Hello, world!");
325        assert_eq!(pv.to_string(), "Hello, world!");
326
327        let messages = pv.to_messages();
328        assert_eq!(messages.len(), 1);
329        assert_eq!(messages[0].content(), "Hello, world!");
330    }
331
332    #[test]
333    fn test_chat_prompt_value() {
334        let messages = vec![
335            BaseMessage::System(SystemMessage::new("You are a helpful assistant.")),
336            BaseMessage::Human(HumanMessage::new("Hello!")),
337            BaseMessage::AI(AIMessage::new("Hi there!")),
338        ];
339        let pv = ChatPromptValue::new(messages.clone());
340
341        let result = pv.to_string();
342        assert!(result.contains("System:"));
343        assert!(result.contains("Human:"));
344        assert!(result.contains("AI:"));
345
346        let returned_messages = pv.to_messages();
347        assert_eq!(returned_messages.len(), 3);
348    }
349
350    #[test]
351    fn test_image_url() {
352        let url = ImageURL::new("https://example.com/image.jpg");
353        assert_eq!(url.get_url(), "https://example.com/image.jpg");
354        assert!(url.detail.is_none());
355
356        let url_with_detail =
357            ImageURL::with_detail("https://example.com/image.jpg", ImageDetailLevel::High);
358        assert_eq!(url_with_detail.detail, Some(ImageDetailLevel::High));
359    }
360
361    #[test]
362    fn test_image_prompt_value() {
363        let pv = ImagePromptValue::from_url("https://example.com/image.jpg");
364        assert_eq!(pv.to_string(), "https://example.com/image.jpg");
365
366        let messages = pv.to_messages();
367        assert_eq!(messages.len(), 1);
368    }
369
370    #[test]
371    fn test_chat_prompt_value_concrete() {
372        let messages = vec![
373            BaseMessage::Human(HumanMessage::new("Hello!")),
374            BaseMessage::AI(AIMessage::new("Hi!")),
375        ];
376        let pv = ChatPromptValueConcrete::new(messages);
377
378        assert_eq!(pv.to_messages().len(), 2);
379    }
380
381    #[test]
382    fn test_serializable_namespaces() {
383        assert_eq!(
384            StringPromptValue::get_lc_namespace(),
385            vec!["langchain", "prompts", "base"]
386        );
387        assert_eq!(
388            ChatPromptValue::get_lc_namespace(),
389            vec!["langchain", "prompts", "chat"]
390        );
391        assert_eq!(
392            ImagePromptValue::get_lc_namespace(),
393            vec!["langchain", "schema", "prompt"]
394        );
395    }
396}