Skip to main content

langchain_rust/prompt/
few_shot.rs

1use std::{fmt, sync::Arc};
2
3use super::{
4    example_selector::ExampleSelector, FormatPrompter, PromptArgs, PromptError, PromptFromatter,
5    PromptTemplate,
6};
7use crate::schemas::{messages::Message, prompt::PromptValue};
8
9/// Where a [`FewShotPromptTemplate`] gets its examples.
10#[derive(Clone)]
11enum Examples {
12    Fixed(Vec<PromptArgs>),
13    Selector(Arc<dyn ExampleSelector>),
14}
15
16/// A prompt that shows the model some examples before the actual input: an optional
17/// prefix, each example formatted with `example_prompt`, then an optional suffix
18/// (usually holding the input), joined by a separator (`"\n\n"` by default).
19///
20/// Examples are either a fixed list or picked per input by an [`ExampleSelector`]
21/// (e.g. the most similar ones). Only the prefix and suffix are formatted with the
22/// input, so braces inside examples are left alone.
23///
24/// ```rust,ignore
25/// let prompt = FewShotPromptTemplate::new(
26///     template_fstring!("Word: {word}\nAntonym: {antonym}", "word", "antonym"),
27///     vec![
28///         prompt_args! {"word" => "happy", "antonym" => "sad"},
29///         prompt_args! {"word" => "tall", "antonym" => "short"},
30///     ],
31/// )
32/// .with_prefix(template_fstring!("Give the antonym of every input.",))
33/// .with_suffix(template_fstring!("Word: {input}\nAntonym:", "input"));
34///
35/// let chain = LLMChainBuilder::new().prompt(prompt).llm(llm).build()?;
36/// chain.invoke(prompt_args! {"input" => "big"}).await?;
37/// ```
38///
39/// With a selector the examples are chosen asynchronously, so format it with
40/// [`FormatPrompter::format_prompt_async`] or [`format`](Self::format) (chains do
41/// this). The sync [`FormatPrompter::format_prompt`] only works with fixed examples.
42#[derive(Clone)]
43pub struct FewShotPromptTemplate {
44    examples: Examples,
45    example_prompt: PromptTemplate,
46    prefix: Option<PromptTemplate>,
47    suffix: Option<PromptTemplate>,
48    separator: String,
49}
50
51impl FewShotPromptTemplate {
52    /// A few-shot prompt with a fixed list of examples, each formatted with `example_prompt`.
53    pub fn new(example_prompt: PromptTemplate, examples: Vec<PromptArgs>) -> Self {
54        Self::build(example_prompt, Examples::Fixed(examples))
55    }
56
57    /// A few-shot prompt whose examples are chosen per input by `selector`.
58    pub fn with_selector<S: ExampleSelector + 'static>(
59        example_prompt: PromptTemplate,
60        selector: S,
61    ) -> Self {
62        Self::with_shared_selector(example_prompt, Arc::new(selector))
63    }
64
65    /// Like [`with_selector`](Self::with_selector), keeping a handle to the selector
66    /// (e.g. to add examples later).
67    pub fn with_shared_selector(
68        example_prompt: PromptTemplate,
69        selector: Arc<dyn ExampleSelector>,
70    ) -> Self {
71        Self::build(example_prompt, Examples::Selector(selector))
72    }
73
74    fn build(example_prompt: PromptTemplate, examples: Examples) -> Self {
75        Self {
76            examples,
77            example_prompt,
78            prefix: None,
79            suffix: None,
80            separator: "\n\n".into(),
81        }
82    }
83
84    /// Text before the examples, formatted with the input.
85    pub fn with_prefix(mut self, prefix: PromptTemplate) -> Self {
86        self.prefix = Some(prefix);
87        self
88    }
89
90    /// Text after the examples, formatted with the input. Usually where the input goes.
91    pub fn with_suffix(mut self, suffix: PromptTemplate) -> Self {
92        self.suffix = Some(suffix);
93        self
94    }
95
96    /// What goes between the prefix, each example and the suffix. Defaults to `"\n\n"`.
97    pub fn with_separator<S: Into<String>>(mut self, separator: S) -> Self {
98        self.separator = separator.into();
99        self
100    }
101
102    /// The prompt text for `input_variables`, choosing examples with the selector if
103    /// there is one.
104    pub async fn format(&self, input_variables: PromptArgs) -> Result<String, PromptError> {
105        let examples = match &self.examples {
106            Examples::Fixed(examples) => examples.clone(),
107            Examples::Selector(selector) => selector.select_examples(&input_variables).await?,
108        };
109        self.assemble(&examples, input_variables)
110    }
111
112    /// Formats the prefix, the examples and the suffix and joins the non-empty ones.
113    fn assemble(
114        &self,
115        examples: &[PromptArgs],
116        input_variables: PromptArgs,
117    ) -> Result<String, PromptError> {
118        let mut pieces = Vec::with_capacity(examples.len() + 2);
119        if let Some(prefix) = &self.prefix {
120            pieces.push(prefix.format(input_variables.clone())?);
121        }
122        for (i, example) in examples.iter().enumerate() {
123            let text = self.example_prompt.format(example.clone()).map_err(|e| {
124                PromptError::OtherError(format!("formatting example {}: {e}", i + 1))
125            })?;
126            pieces.push(text);
127        }
128        if let Some(suffix) = &self.suffix {
129            pieces.push(suffix.format(input_variables)?);
130        }
131        pieces.retain(|piece| !piece.is_empty());
132        Ok(pieces.join(&self.separator))
133    }
134}
135
136impl fmt::Debug for FewShotPromptTemplate {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        let examples = match &self.examples {
139            Examples::Fixed(examples) => format!("{} fixed", examples.len()),
140            Examples::Selector(_) => "selector".to_string(),
141        };
142        f.debug_struct("FewShotPromptTemplate")
143            .field("examples", &examples)
144            .field("example_prompt", &self.example_prompt.template())
145            .field("prefix", &self.prefix.as_ref().map(|p| p.template()))
146            .field("suffix", &self.suffix.as_ref().map(|p| p.template()))
147            .field("separator", &self.separator)
148            .finish()
149    }
150}
151
152#[async_trait::async_trait]
153impl FormatPrompter for FewShotPromptTemplate {
154    /// Fixed examples only; with a selector use
155    /// [`format_prompt_async`](FormatPrompter::format_prompt_async).
156    fn format_prompt(&self, input_variables: PromptArgs) -> Result<PromptValue, PromptError> {
157        match &self.examples {
158            Examples::Fixed(examples) => {
159                let text = self.assemble(examples, input_variables)?;
160                Ok(PromptValue::from_messages(vec![
161                    Message::new_human_message(text),
162                ]))
163            }
164            Examples::Selector(_) => Err(PromptError::OtherError(
165                "a FewShotPromptTemplate with an example selector has to be formatted with \
166                 format_prompt_async (chains already do)"
167                    .into(),
168            )),
169        }
170    }
171
172    async fn format_prompt_async(
173        &self,
174        input_variables: PromptArgs,
175    ) -> Result<PromptValue, PromptError> {
176        let text = self.format(input_variables).await?;
177        Ok(PromptValue::from_messages(vec![
178            Message::new_human_message(text),
179        ]))
180    }
181
182    /// The variables of the prefix and the suffix (examples bring their own values).
183    fn get_input_variables(&self) -> Vec<String> {
184        let mut variables: Vec<String> = Vec::new();
185        for template in self.prefix.iter().chain(self.suffix.iter()) {
186            for variable in template.variables() {
187                if !variables.contains(&variable) {
188                    variables.push(variable);
189                }
190            }
191        }
192        variables
193    }
194}