swiftide_query/answers/
simple.rs1use std::sync::Arc;
3use swiftide_core::{
4 Answer,
5 document::Document,
6 indexing::SimplePrompt,
7 prelude::*,
8 prompt::Prompt,
9 querying::{Query, states},
10};
11
12#[derive(Debug, Clone, Builder)]
28pub struct Simple {
29 #[builder(setter(custom))]
30 client: Arc<dyn SimplePrompt>,
31 #[builder(default = "default_prompt()")]
32 prompt_template: Prompt,
33 #[builder(default, setter(into, strip_option))]
34 document_template: Option<Prompt>,
35}
36
37impl Simple {
38 pub fn builder() -> SimpleBuilder {
39 SimpleBuilder::default()
40 }
41
42 pub fn from_client(client: impl SimplePrompt + 'static) -> Simple {
48 SimpleBuilder::default()
49 .client(client)
50 .to_owned()
51 .build()
52 .expect("Failed to build Simple")
53 }
54}
55
56impl SimpleBuilder {
57 pub fn client(&mut self, client: impl SimplePrompt + 'static) -> &mut Self {
58 self.client = Some(Arc::new(client) as Arc<dyn SimplePrompt>);
59 self
60 }
61}
62
63fn default_prompt() -> Prompt {
64 indoc::indoc! {"
65 Answer the following question based on the context provided:
66 {{ question }}
67
68 ## Constraints
69 * Do not include any information that is not in the provided context.
70 * If the question cannot be answered by the provided context, state that it cannot be answered.
71 * Answer the question completely and format it as markdown.
72
73 ## Context
74
75 ---
76 {{ documents }}
77 ---
78 "}
79 .into()
80}
81
82#[async_trait]
83impl Answer for Simple {
84 #[tracing::instrument(skip_all)]
85 async fn answer(&self, query: Query<states::Retrieved>) -> Result<Query<states::Answered>> {
86 let mut context = tera::Context::new();
87
88 context.insert("question", query.original());
89 context.insert("original", query.original());
90 context.insert("current", query.current());
91
92 let documents = if !query.current().is_empty()
95 && query
96 .history()
97 .iter()
98 .rfind(|e| e.is_retrieval())
99 .is_some_and(|h| h.before() != query.current())
100 {
101 query.current().to_string()
102 } else if let Some(template) = &self.document_template {
103 let mut rendered_documents = Vec::new();
104 for document in query.documents() {
105 let rendered = template
106 .clone()
107 .with_context(tera::Context::from_serialize(document)?)
108 .render()?;
109 rendered_documents.push(rendered);
110 }
111
112 rendered_documents.join("\n---\n")
113 } else {
114 query
115 .documents()
116 .iter()
117 .map(Document::content)
118 .collect::<Vec<_>>()
119 .join("\n---\n")
120 };
121 context.insert("documents", &documents);
122
123 let answer = self
124 .client
125 .prompt(self.prompt_template.clone().with_context(context))
126 .await?;
127
128 Ok(query.answered(answer))
129 }
130}
131
132#[cfg(test)]
133mod test {
134 use std::sync::Mutex;
135
136 use insta::assert_snapshot;
137 use swiftide_core::{MockSimplePrompt, indexing::Metadata, querying::TransformationEvent};
138
139 use super::*;
140
141 assert_default_prompt_snapshot!("question" => "What is love?", "documents" => "My context");
142
143 #[tokio::test]
144 async fn test_uses_current_if_present() {
145 let mut mock_client = MockSimplePrompt::new();
146
147 let received_prompt = Arc::new(Mutex::new(None));
149 let cloned = received_prompt.clone();
150 mock_client
151 .expect_prompt()
152 .withf(move |prompt| {
153 cloned.lock().unwrap().replace(prompt.clone());
154 true
155 })
156 .once()
157 .returning(|_| Ok(String::default()));
158
159 let documents = vec![
160 Document::new("First document", Some(Metadata::from(("some", "metadata")))),
161 Document::new(
162 "Second document",
163 Some(Metadata::from(("other", "metadata"))),
164 ),
165 ];
166 let query: Query<states::Retrieved> = Query::builder()
167 .original("original")
168 .current("A fictional generated summary")
169 .state(states::Retrieved)
170 .transformation_history(vec![TransformationEvent::Retrieved {
171 before: "abc".to_string(),
172 after: "abc".to_string(),
173 documents: documents.clone(),
174 }])
175 .documents(documents)
176 .build()
177 .unwrap();
178
179 let transformer = Simple::builder().client(mock_client).build().unwrap();
180
181 transformer.answer(query).await.unwrap();
182
183 let received_prompt = received_prompt.lock().unwrap().take().unwrap();
184 let rendered = received_prompt.render().unwrap();
185 assert_snapshot!(rendered);
186 }
187
188 #[tokio::test]
189 async fn test_custom_document_template() {
190 let mut mock_client = MockSimplePrompt::new();
191
192 let received_prompt = Arc::new(Mutex::new(None));
194 let cloned = received_prompt.clone();
195 mock_client
196 .expect_prompt()
197 .withf(move |prompt| {
198 cloned.lock().unwrap().replace(prompt.clone());
199 true
200 })
201 .once()
202 .returning(|_| Ok(String::default()));
203
204 let documents = vec![
205 Document::new("First document", Some(Metadata::from(("some", "metadata")))),
206 Document::new(
207 "Second document",
208 Some(Metadata::from(("other", "metadata"))),
209 ),
210 ];
211 let query: Query<states::Retrieved> = Query::builder()
212 .original("original")
213 .current(String::default())
214 .state(states::Retrieved)
215 .transformation_history(vec![TransformationEvent::Retrieved {
216 before: "abc".to_string(),
217 after: "abc".to_string(),
218 documents: documents.clone(),
219 }])
220 .documents(documents)
221 .build()
222 .unwrap();
223
224 let transformer = Simple::builder()
225 .client(mock_client)
226 .document_template(indoc::indoc! {"
227 {% for key, value in metadata -%}
228 {{ key }}: {{ value }}
229 {% endfor -%}
230
231 {{ content }}"})
232 .build()
233 .unwrap();
234
235 transformer.answer(query).await.unwrap();
236
237 let received_prompt = received_prompt.lock().unwrap().take().unwrap();
238 let rendered = received_prompt.render().unwrap();
239 assert_snapshot!(rendered);
240 }
241}