1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
use std::sync::Arc;
use swiftide_core::{
    indexing::SimplePrompt,
    prelude::*,
    prompt::PromptTemplate,
    querying::{states, Query},
    TransformResponse,
};

#[derive(Debug, Clone, Builder)]
pub struct Summary {
    #[builder(setter(custom))]
    client: Arc<dyn SimplePrompt>,
    #[builder(default = "default_prompt()")]
    prompt_template: PromptTemplate,
}

impl Summary {
    pub fn builder() -> SummaryBuilder {
        SummaryBuilder::default()
    }

    /// Builds a new summary generator from a client that implements [`SimplePrompt`].
    ///
    /// Will try to summarize documents using an llm, instructed to preserve as much information as
    /// possible.
    ///
    /// # Panics
    ///
    /// Panics if the build failed
    pub fn from_client(client: impl SimplePrompt + 'static) -> Summary {
        SummaryBuilder::default()
            .client(client)
            .to_owned()
            .build()
            .expect("Failed to build Summary")
    }
}

impl SummaryBuilder {
    pub fn client(&mut self, client: impl SimplePrompt + 'static) -> &mut Self {
        self.client = Some(Arc::new(client));
        self
    }
}

fn default_prompt() -> PromptTemplate {
    indoc::indoc!(
        "
    Your job is to help a query tool find the right context.

    Summarize the following documents.

    ## Constraints
    * Do not add any information that is not available in the documents.
    * Summarize comprehensively and ensure no data that might be important is left out.
    * Summarize as a single markdown document

    ## Documents

    {% for document in documents -%}
    ---
    {{ document }}
    ---
    {% endfor -%}
    "
    )
    .into()
}

#[async_trait]
impl TransformResponse for Summary {
    #[tracing::instrument]
    async fn transform_response(
        &self,
        mut query: Query<states::Retrieved>,
    ) -> Result<Query<states::Retrieved>> {
        let new_response = self
            .client
            .prompt(
                self.prompt_template
                    .to_prompt()
                    .with_context_value("documents", query.documents()),
            )
            .await?;
        query.transformed_response(new_response);

        Ok(query)
    }
}

#[cfg(test)]
mod test {
    use super::*;

    assert_default_prompt_snapshot!("documents" => vec!["First document", "Second Document"]);
}