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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
use std::borrow::{Borrow, Cow};
use serde::Serialize;
mod completion;
mod http;
mod semantic_embedding;
pub use self::{
completion::{CompletionOutput, Sampling, Stopping, TaskCompletion},
http::{Client, Error, Task},
semantic_embedding::{SemanticRepresentation, TaskSemanticEmbedding},
};
/// A prompt which is passed to the model for inference. Usually it is one text item, but it could
/// also be a combination of several modalities like images and text.
#[derive(Serialize, Debug, Clone, PartialEq, Eq)]
pub struct Prompt<'a>([Modality<'a>; 1]);
impl<'a> Prompt<'a> {
/// Create a prompt from a single text item.
pub fn from_text(text: impl Into<Cow<'a, str>>) -> Self {
Self([Modality::from_text(text)])
}
/// Allows you to borrow the contents of the prompt without allocating a new one.
pub fn borrow(&'a self) -> Prompt<'a> {
Self([self.0[0].borrow()])
}
}
/// The prompt for models can be a combination of different modalities (Text and Image). The type of
/// modalities which are supported depend on the Model in question.
#[derive(Serialize, Debug, Clone, PartialEq, Eq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Modality<'a> {
/// The only type of prompt which can be used with pure language models
Text { data: Cow<'a, str> },
}
impl<'a> Modality<'a> {
/// Instantiates a text prompt
pub fn from_text(text: impl Into<Cow<'a, str>>) -> Self {
Modality::Text { data: text.into() }
}
/// Create a semantically idetical entry of modality which borrows the contents of this one.
///
/// It is very practical to allow Modality of e.g. Text to take both ownership of the string it
/// contains as well as borrow a slice. However then we are creating a body from the user input
/// we want to avoid copying everything and needing to allocate for that modality again. This is
/// there this borrow function really shines.
pub fn borrow(&self) -> Modality<'_> {
match self {
Modality::Text { data } => Modality::Text {
data: Cow::Borrowed(data.borrow()),
},
}
}
}
/// Controls of how to execute a task
#[derive(Clone, PartialEq, Eq, Hash, Default)]
pub struct How {
/// Set this to `true` if you want to not put any load on the API in case it is already pretty
/// busy for the models you intend to use. All this does from the user perspective is that it
/// makes it more likely you get a `Busy` response from the server. One of the reasons you may
/// want to set is that you are an employee or associate of Aleph Alpha and want to perform
/// experiments without hurting paying customers.
pub be_nice: bool,
}
/// Intended to compare embeddings.
///
/// ```no_run
/// use aleph_alpha_client::{
/// Client, Prompt, TaskSemanticEmbedding, cosine_similarity, SemanticRepresentation, How
/// };
///
/// async fn semanitc_search_with_luminous_base(client: &Client) {
/// // Given
/// let robot_fact = Prompt::from_text(
/// "A robot is a machine—especially one programmable by a computer—capable of carrying out a \
/// complex series of actions automatically.",
/// );
/// let pizza_fact = Prompt::from_text(
/// "Pizza (Italian: [ˈpittsa], Neapolitan: [ˈpittsə]) is a dish of Italian origin consisting \
/// of a usually round, flat base of leavened wheat-based dough topped with tomatoes, cheese, \
/// and often various other ingredients (such as various types of sausage, anchovies, \
/// mushrooms, onions, olives, vegetables, meat, ham, etc.), which is then baked at a high \
/// temperature, traditionally in a wood-fired oven.",
/// );
/// let query = Prompt::from_text("What is Pizza?");
/// let model = "luminous-base";
/// let how = How::default();
///
/// // When
/// let robot_embedding_task = TaskSemanticEmbedding {
/// prompt: robot_fact,
/// representation: SemanticRepresentation::Document,
/// compress_to_size: Some(128),
/// };
/// let robot_embedding = client.execute(
/// model,
/// &robot_embedding_task,
/// &how,
/// ).await.unwrap().embedding;
///
/// let pizza_embedding_task = TaskSemanticEmbedding {
/// prompt: pizza_fact,
/// representation: SemanticRepresentation::Document,
/// compress_to_size: Some(128),
/// };
/// let pizza_embedding = client.execute(
/// model,
/// &pizza_embedding_task,
/// &how,
/// ).await.unwrap().embedding;
///
/// let query_embedding_task = TaskSemanticEmbedding {
/// prompt: query,
/// representation: SemanticRepresentation::Query,
/// compress_to_size: Some(128),
/// };
/// let query_embedding = client.execute(
/// model,
/// &query_embedding_task,
/// &how,
/// ).await.unwrap().embedding;
/// let similarity_pizza = cosine_similarity(&query_embedding, &pizza_embedding);
/// println!("similarity pizza: {similarity_pizza}");
/// let similarity_robot = cosine_similarity(&query_embedding, &robot_embedding);
/// println!("similarity robot: {similarity_robot}");
///
/// // Then
///
/// // The fact about pizza should be more relevant to the "What is Pizza?" question than a fact
/// // about robots.
/// assert!(similarity_pizza > similarity_robot);
/// }
/// ```
pub fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
let ab: f32 = a.iter().zip(b).map(|(a, b)| a * b).sum();
let aa: f32 = a.iter().map(|a| a * a).sum();
let bb: f32 = b.iter().map(|b| b * b).sum();
let prod_len = (aa * bb).sqrt();
ab / prod_len
}
#[cfg(test)]
mod tests {
use crate::Prompt;
#[test]
fn ability_to_generate_prompt_in_local_function() {
fn local_function() -> Prompt<'static> {
Prompt::from_text(String::from("My test prompt"))
}
assert_eq!(Prompt::from_text("My test prompt"), local_function())
}
}