use serde::{Deserialize, Serialize};
use crate::error::EMBEDDING_METADATA_KEY;
use crate::{Error, Usage};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ChunkedEmbeddingProgress {
pub completed_inputs: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
}
impl ChunkedEmbeddingProgress {
#[must_use]
pub fn from_error(error: &Error) -> Option<Self> {
let record = error.context()?.metadata.get(EMBEDDING_METADATA_KEY)?;
Self::deserialize(record).ok()
}
pub(crate) fn add_usage(&mut self, usage: Option<&Usage>) {
if let Some(usage) = usage {
self.usage = Some(self.usage.take().unwrap_or_default() + usage);
}
}
pub(crate) fn attach_to(&self, mut error: Error) -> Error {
if let Some(context) = error.context_mut()
&& let Ok(record) = serde_json::to_value(self)
{
context
.metadata
.insert(EMBEDDING_METADATA_KEY.to_owned(), record);
}
error
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips_through_the_error_context() {
let mut progress = ChunkedEmbeddingProgress::default();
progress.add_usage(Some(&Usage::new().input_tokens(3)));
progress.add_usage(None);
progress.add_usage(Some(&Usage::new().input_tokens(4)));
progress.completed_inputs = 5;
let mut err = progress.attach_to(
Error::overloaded("busy")
.authored()
.with_body("secret body"),
);
assert_eq!(ChunkedEmbeddingProgress::from_error(&err), Some(progress));
let record = serde_json::json!({"completed_inputs": 5, "usage": {"input_tokens": 7}});
assert_eq!(err.context().unwrap().metadata["allama.embedding"], record);
assert!(matches!(err, Error::Overloaded { .. }));
err.context_mut()
.unwrap()
.metadata
.insert("vendor.detail".into(), serde_json::json!("may quote input"));
let log = serde_json::to_value(err.as_log()).unwrap();
assert_eq!(log["allama.embedding"], record);
assert!(log.get("vendor.detail").is_none());
assert_eq!(
err.as_log().to_string(),
"kind=overloaded body_len=11 allama.embedding.completed_inputs=5 \
allama.embedding.input_tokens=7 message=\"busy\""
);
}
#[test]
fn rides_on_the_error_that_ended_a_failover() {
let progress = ChunkedEmbeddingProgress {
completed_inputs: 2,
usage: None,
};
let err = progress
.attach_to(Error::rate_limited("second").with_fallback_from(Error::timeout("first")));
assert_eq!(ChunkedEmbeddingProgress::from_error(&err), Some(progress));
assert_eq!(
ChunkedEmbeddingProgress::from_error(err.fallback_from().unwrap()),
None
);
}
#[test]
fn absent_without_a_record() {
assert_eq!(
ChunkedEmbeddingProgress::from_error(&Error::timeout("slow")),
None
);
}
}