use std::{
cmp::Ordering,
fmt,
sync::{Arc, RwLock},
};
use async_trait::async_trait;
use super::{PromptArgs, PromptError, PromptFromatter, PromptTemplate};
use crate::{embedding::Embedder, schemas::messages::value_to_text};
#[async_trait]
pub trait ExampleSelector: Send + Sync {
async fn add_example(&self, example: PromptArgs) -> Result<(), PromptError>;
async fn select_examples(
&self,
input_variables: &PromptArgs,
) -> Result<Vec<PromptArgs>, PromptError>;
}
type LengthFn = Arc<dyn Fn(&str) -> usize + Send + Sync>;
pub struct LengthBasedExampleSelector {
example_prompt: PromptTemplate,
max_length: usize,
length_fn: LengthFn,
examples: RwLock<Vec<(PromptArgs, usize)>>,
}
impl LengthBasedExampleSelector {
pub const DEFAULT_MAX_LENGTH: usize = 2048;
pub fn new(
example_prompt: PromptTemplate,
examples: Vec<PromptArgs>,
) -> Result<Self, PromptError> {
let selector = Self {
example_prompt,
max_length: Self::DEFAULT_MAX_LENGTH,
length_fn: Arc::new(word_count),
examples: RwLock::new(Vec::new()),
};
for example in examples {
selector.push(example)?;
}
Ok(selector)
}
pub fn with_max_length(mut self, max_length: usize) -> Self {
self.max_length = max_length;
self
}
pub fn with_length_fn<F>(mut self, length_fn: F) -> Self
where
F: Fn(&str) -> usize + Send + Sync + 'static,
{
self.length_fn = Arc::new(length_fn);
let examples = std::mem::take(self.examples.get_mut().unwrap_or_else(|e| e.into_inner()));
for (example, _) in examples {
let _ = self.push(example);
}
self
}
fn push(&self, example: PromptArgs) -> Result<(), PromptError> {
let length = (self.length_fn)(&self.example_prompt.format(example.clone())?);
self.examples
.write()
.unwrap_or_else(|e| e.into_inner())
.push((example, length));
Ok(())
}
}
impl fmt::Debug for LengthBasedExampleSelector {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LengthBasedExampleSelector")
.field("max_length", &self.max_length)
.field(
"examples",
&self
.examples
.read()
.unwrap_or_else(|e| e.into_inner())
.len(),
)
.finish()
}
}
#[async_trait]
impl ExampleSelector for LengthBasedExampleSelector {
async fn add_example(&self, example: PromptArgs) -> Result<(), PromptError> {
self.push(example)
}
async fn select_examples(
&self,
input_variables: &PromptArgs,
) -> Result<Vec<PromptArgs>, PromptError> {
let input_length: usize = input_variables
.values()
.map(|v| (self.length_fn)(&value_to_text(v)))
.sum();
let mut remaining = self.max_length.saturating_sub(input_length);
let mut selected = Vec::new();
for (example, length) in self
.examples
.read()
.unwrap_or_else(|e| e.into_inner())
.iter()
{
if *length > remaining {
break;
}
remaining -= length;
selected.push(example.clone());
}
Ok(selected)
}
}
fn word_count(text: &str) -> usize {
text.split_whitespace().count()
}
pub struct SemanticSimilarityExampleSelector {
embedder: Arc<dyn Embedder>,
k: usize,
input_keys: Option<Vec<String>>,
examples: RwLock<Vec<(PromptArgs, Vec<f64>)>>,
}
impl SemanticSimilarityExampleSelector {
pub const DEFAULT_K: usize = 4;
pub fn new<E: Embedder + 'static>(embedder: E) -> Self {
Self::with_embedder(Arc::new(embedder))
}
pub fn with_embedder(embedder: Arc<dyn Embedder>) -> Self {
Self {
embedder,
k: Self::DEFAULT_K,
input_keys: None,
examples: RwLock::new(Vec::new()),
}
}
pub async fn from_examples<E: Embedder + 'static>(
examples: Vec<PromptArgs>,
embedder: E,
k: usize,
) -> Result<Self, PromptError> {
let selector = Self::new(embedder).with_k(k);
selector.add_examples(examples).await?;
Ok(selector)
}
pub fn with_k(mut self, k: usize) -> Self {
self.k = k;
self
}
pub fn with_input_keys<I, S>(mut self, keys: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.input_keys = Some(keys.into_iter().map(Into::into).collect());
self
}
pub async fn add_examples(&self, examples: Vec<PromptArgs>) -> Result<(), PromptError> {
if examples.is_empty() {
return Ok(());
}
let texts: Vec<String> = examples.iter().map(|e| self.text_of(e)).collect();
let vectors = self
.embedder
.embed_documents(&texts)
.await
.map_err(|e| PromptError::OtherError(format!("embedding examples: {e}")))?;
if vectors.len() != examples.len() {
return Err(PromptError::OtherError(format!(
"embedder returned {} vectors for {} examples",
vectors.len(),
examples.len()
)));
}
self.examples
.write()
.unwrap_or_else(|e| e.into_inner())
.extend(examples.into_iter().zip(vectors));
Ok(())
}
#[cfg(test)]
pub(crate) fn examples_for_test(&self, examples: Vec<(PromptArgs, Vec<f64>)>) {
self.examples
.write()
.unwrap_or_else(|e| e.into_inner())
.extend(examples);
}
fn text_of(&self, args: &PromptArgs) -> String {
let mut keys: Vec<&String> = match &self.input_keys {
Some(keys) => keys.iter().filter(|k| args.contains_key(*k)).collect(),
None => args.keys().collect(),
};
keys.sort();
keys.iter()
.map(|k| value_to_text(&args[*k]))
.collect::<Vec<_>>()
.join(" ")
}
}
impl fmt::Debug for SemanticSimilarityExampleSelector {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SemanticSimilarityExampleSelector")
.field("k", &self.k)
.field("input_keys", &self.input_keys)
.field(
"examples",
&self
.examples
.read()
.unwrap_or_else(|e| e.into_inner())
.len(),
)
.finish()
}
}
#[async_trait]
impl ExampleSelector for SemanticSimilarityExampleSelector {
async fn add_example(&self, example: PromptArgs) -> Result<(), PromptError> {
self.add_examples(vec![example]).await
}
async fn select_examples(
&self,
input_variables: &PromptArgs,
) -> Result<Vec<PromptArgs>, PromptError> {
if self.k == 0
|| self
.examples
.read()
.unwrap_or_else(|e| e.into_inner())
.is_empty()
{
return Ok(Vec::new());
}
let query = self
.embedder
.embed_query(&self.text_of(input_variables))
.await
.map_err(|e| PromptError::OtherError(format!("embedding the input: {e}")))?;
let examples = self.examples.read().unwrap_or_else(|e| e.into_inner());
let mut scored: Vec<(f64, &PromptArgs)> = examples
.iter()
.map(|(example, vector)| (cosine_similarity(&query, vector), example))
.collect();
scored.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));
Ok(scored
.into_iter()
.take(self.k)
.map(|(_, example)| example.clone())
.collect())
}
}
fn cosine_similarity(a: &[f64], b: &[f64]) -> f64 {
let dot: f64 = a.iter().zip(b).map(|(x, y)| x * y).sum();
let norm = |v: &[f64]| v.iter().map(|x| x * x).sum::<f64>().sqrt();
let denominator = norm(a) * norm(b);
if denominator == 0.0 {
0.0
} else {
dot / denominator
}
}