use std::borrow::Cow;
use serde::{Deserialize, Serialize};
use crate::{ProviderData, Subject, Usage, Warning, WarningKind};
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct EmbeddingResponse {
pub embeddings: Vec<Vec<f32>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider: Option<Cow<'static, str>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub warnings: Vec<Warning>,
#[serde(default, skip_serializing_if = "ProviderData::is_empty")]
pub provider_data: ProviderData,
}
impl EmbeddingResponse {
#[must_use]
pub fn new(embeddings: Vec<Vec<f32>>) -> Self {
Self {
embeddings,
..Self::default()
}
}
#[must_use]
pub fn model(mut self, model: impl Into<String>) -> Self {
self.model = Some(model.into());
self
}
#[must_use]
pub fn usage(mut self, usage: Usage) -> Self {
self.usage = Some(usage);
self
}
#[must_use]
pub fn provider(mut self, provider: impl Into<Cow<'static, str>>) -> Self {
self.provider = Some(provider.into());
self
}
#[must_use]
pub fn warning(mut self, warning: Warning) -> Self {
self.warnings.push(warning);
self
}
#[must_use]
pub fn provider_data(mut self, data: ProviderData) -> Self {
self.provider_data = data;
self
}
pub fn extend(&mut self, other: EmbeddingResponse) {
self.embeddings.extend(other.embeddings);
if self.model.is_none() {
self.model = other.model;
}
let mixed = match (&self.provider, &other.provider) {
(Some(first), Some(other)) if first != other => Some(Warning::new(
WarningKind::Fallback,
Subject::Response,
format!(
"vectors from different providers may not be comparable; the chunks were \
served by `{first}` and `{other}`"
),
)),
_ => None,
};
if self.provider.is_none() {
self.provider = other.provider;
}
self.usage = match (self.usage.take(), other.usage) {
(Some(a), Some(b)) => Some(a + b),
(a, b) => a.or(b),
};
for warning in other.warnings {
if !self.warnings.contains(&warning) {
self.warnings.push(warning);
}
}
if let Some(warning) = mixed
&& !self.warnings.contains(&warning)
{
self.warnings.push(warning);
}
self.provider_data.merge(other.provider_data);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::RequestField;
#[test]
fn extend_reports_a_repeated_warning_once() {
let hint = || Warning::unsupported(RequestField::InputType, "no input type");
let other = Warning::unsupported(RequestField::Dimensions, "other");
let mut combined = EmbeddingResponse::new(vec![vec![1.0]]).warning(hint());
combined.extend(
EmbeddingResponse::new(vec![vec![2.0]])
.warning(hint())
.warning(other.clone()),
);
combined.extend(EmbeddingResponse::new(vec![vec![3.0]]).warning(hint()));
assert_eq!(combined.embeddings, vec![vec![1.0], vec![2.0], vec![3.0]]);
assert_eq!(combined.warnings, vec![hint(), other]);
}
#[test]
fn extend_keeps_the_first_provider_and_warns_when_another_served() {
let mut combined = EmbeddingResponse::new(vec![vec![1.0]]).provider("primary");
combined.extend(EmbeddingResponse::new(vec![vec![2.0]]).provider("primary"));
assert!(combined.warnings.is_empty(), "{:?}", combined.warnings);
combined.extend(EmbeddingResponse::new(vec![vec![3.0]]).provider("backup"));
combined.extend(EmbeddingResponse::new(vec![vec![4.0]]).provider("backup"));
assert_eq!(combined.provider.as_deref(), Some("primary"));
assert_eq!(combined.warnings.len(), 1, "{:?}", combined.warnings);
let warning = &combined.warnings[0];
assert_eq!(warning.kind, WarningKind::Fallback);
assert_eq!(warning.subject, Subject::Response);
assert!(
warning.message.contains("`primary` and `backup`"),
"{}",
warning.message
);
let mut combined = EmbeddingResponse::new(vec![vec![1.0]]);
combined.extend(EmbeddingResponse::new(vec![vec![2.0]]).provider("primary"));
combined.extend(EmbeddingResponse::new(vec![vec![3.0]]));
assert_eq!(combined.provider.as_deref(), Some("primary"));
assert!(combined.warnings.is_empty(), "{:?}", combined.warnings);
}
}