///|
/// The input to an embeddings request: one or more strings to embed.
pub(all) enum EmbeddingInput {
/// A single text to embed.
Single(String)
/// A batch of texts to embed in one request.
Batch(Array[String])
} derive(Eq, Debug)
///|
pub impl ToJson for EmbeddingInput with fn to_json(self : EmbeddingInput) -> Json {
match self {
Single(s) => Json::string(s)
Batch(arr) => arr.to_json()
}
}
///|
/// A request to the `/embeddings` endpoint.
pub(all) struct EmbeddingRequest {
model : String
input : EmbeddingInput
/// Optional number of dimensions to truncate the embedding to
/// (supported by newer models).
mut dimensions : Int?
/// Optional end-user identifier for abuse monitoring.
mut user : String?
/// The encoding format: `"float"` (default) or `"base64"`.
mut encoding_format : String?
}
///|
/// Create an embeddings request for a single text.
pub fn EmbeddingRequest::new(
model : String,
input : EmbeddingInput,
) -> EmbeddingRequest {
{ model, input, dimensions: None, user: None, encoding_format: None }
}
///|
/// Convenience: build a request embedding a single string.
pub fn EmbeddingRequest::of_text(
model : String,
text : String,
) -> EmbeddingRequest {
EmbeddingRequest::new(model, Single(text))
}
///|
/// Convenience: build a request embedding a batch of strings.
pub fn EmbeddingRequest::of_batch(
model : String,
texts : Array[String],
) -> EmbeddingRequest {
EmbeddingRequest::new(model, Batch(texts))
}
///|
/// Set the target dimensionality of the returned embeddings.
pub fn EmbeddingRequest::dimensions(
self : EmbeddingRequest,
n : Int,
) -> EmbeddingRequest {
self.dimensions = Some(n)
self
}
///|
/// Set the end-user identifier.
pub fn EmbeddingRequest::user(
self : EmbeddingRequest,
u : String,
) -> EmbeddingRequest {
self.user = Some(u)
self
}
///|
pub impl ToJson for EmbeddingRequest with fn to_json(self : EmbeddingRequest) -> Json {
let obj : Map[String, Json] = {
"model": Json::string(self.model),
"input": self.input.to_json(),
}
if self.dimensions is Some(d) {
obj["dimensions"] = Json::number(d.to_double())
}
if self.user is Some(u) {
obj["user"] = Json::string(u)
}
if self.encoding_format is Some(f) {
obj["encoding_format"] = Json::string(f)
}
Json::object(obj)
}
///|
/// A single embedding vector in an embeddings response.
pub(all) struct Embedding {
index : Int
embedding : Array[Double]
} derive(Debug)
///|
pub impl @json.FromJson for Embedding with fn from_json(
json : Json,
path : @json.JsonPath,
) -> Embedding {
guard json is Object(obj) else {
raise @json.JsonDecodeError((path, "Embedding: expected object"))
}
let index = match obj.get("index") {
Some(Number(n, ..)) => n.to_int()
_ => 0
}
let embedding = match obj.get("embedding") {
Some(Array(arr)) => {
let out = []
for v in arr {
match v {
Number(n, ..) => out.push(n)
_ => ()
}
}
out
}
_ => []
}
{ index, embedding }
}
///|
/// The response from the `/embeddings` endpoint.
pub(all) struct EmbeddingResponse {
model : String
data : Array[Embedding]
usage : Usage?
} derive(Debug)
///|
pub impl @json.FromJson for EmbeddingResponse with fn from_json(
json : Json,
path : @json.JsonPath,
) -> EmbeddingResponse {
guard json is Object(obj) else {
raise @json.JsonDecodeError((path, "EmbeddingResponse: expected object"))
}
let model = match obj.get("model") {
Some(String(s)) => s
_ => ""
}
let data = match obj.get("data") {
Some(Array(_) as d) => @json.from_json(d)
_ => []
}
let usage = match obj.get("usage") {
Some(Object(_) as u) => Some(@json.from_json(u))
_ => None
}
{ model, data, usage }
}
///|
/// The first embedding vector, if any.
pub fn EmbeddingResponse::vector(self : EmbeddingResponse) -> Array[Double] {
match self.data.get(0) {
Some(e) => e.embedding
None => []
}
}
///|
/// Perform an embeddings request.
pub async fn Client::embeddings(
self : Client,
request : EmbeddingRequest,
) -> EmbeddingResponse raise LLMError {
let json = self.post_json("/embeddings", request.to_json())
@json.from_json(json) catch {
err => raise Decode(err.to_string())
}
}
///|
/// Compute the cosine similarity between two equal-length vectors.
///
/// Returns 0.0 if either vector is empty or their lengths differ.
pub fn cosine_similarity(a : Array[Double], b : Array[Double]) -> Double {
if a.length() != b.length() || a.length() == 0 {
return 0.0
}
let mut dot = 0.0
let mut norm_a = 0.0
let mut norm_b = 0.0
for i in 0..