1use 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
18pub trait PromptValue: Serializable {
25 fn to_string(&self) -> String;
27
28 fn to_messages(&self) -> Vec<BaseMessage>;
30}
31
32#[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 #[default]
39 Auto,
40 Low,
42 High,
44}
45
46#[cfg_attr(feature = "specta", derive(Type))]
52#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
53pub struct ImageURL {
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub url: Option<String>,
57 #[serde(skip_serializing_if = "Option::is_none")]
60 pub detail: Option<ImageDetailLevel>,
61}
62
63impl ImageURL {
64 pub fn new(url: impl Into<String>) -> Self {
66 Self {
67 url: Some(url.into()),
68 detail: None,
69 }
70 }
71
72 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 pub fn get_url(&self) -> &str {
82 self.url.as_deref().unwrap_or("")
83 }
84}
85
86#[cfg_attr(feature = "specta", derive(Type))]
92#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
93pub struct StringPromptValue {
94 pub text: String,
96}
97
98impl StringPromptValue {
99 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#[cfg_attr(feature = "specta", derive(Type))]
141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
142pub struct ChatPromptValue {
143 pub messages: Vec<BaseMessage>,
145}
146
147impl ChatPromptValue {
148 pub fn new(messages: Vec<BaseMessage>) -> Self {
150 Self { messages }
151 }
152
153 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#[cfg_attr(feature = "specta", derive(Type))]
197#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
198pub struct ImagePromptValue {
199 pub image_url: ImageURL,
201}
202
203impl ImagePromptValue {
204 pub fn new(image_url: ImageURL) -> Self {
206 Self { image_url }
207 }
208
209 pub fn from_url(url: impl Into<String>) -> Self {
211 Self {
212 image_url: ImageURL::new(url),
213 }
214 }
215
216 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#[cfg_attr(feature = "specta", derive(Type))]
274#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
275pub struct ChatPromptValueConcrete {
276 pub messages: Vec<AnyMessage>,
278}
279
280impl ChatPromptValueConcrete {
281 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}