use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LateInteractionConfig {
#[serde(
default = "default_late_interaction_model",
deserialize_with = "deserialize_null_model"
)]
pub model: LateInteractionModelType,
#[serde(default = "default_batch_size")]
pub batch_size: usize,
#[serde(default = "default_max_length")]
pub max_length: usize,
#[serde(default = "default_query_max_length")]
pub query_max_length: usize,
#[serde(default)]
pub show_download_progress: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_dir: Option<PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub acceleration: Option<super::acceleration::AccelerationConfig>,
#[serde(default = "default_max_embed_duration_secs", skip_serializing_if = "Option::is_none")]
pub max_embed_duration_secs: Option<u64>,
}
impl Default for LateInteractionConfig {
fn default() -> Self {
Self {
model: default_late_interaction_model(),
batch_size: default_batch_size(),
max_length: default_max_length(),
query_max_length: default_query_max_length(),
show_download_progress: false,
cache_dir: None,
acceleration: None,
max_embed_duration_secs: default_max_embed_duration_secs(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum LateInteractionModelType {
Preset {
name: String,
},
Custom {
model_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
model_file: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
additional_files: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
max_length: Option<i64>,
},
Plugin {
name: String,
},
}
impl Default for LateInteractionModelType {
fn default() -> Self {
Self::Preset {
name: "gte-moderncolbert".to_string(),
}
}
}
fn default_late_interaction_model() -> LateInteractionModelType {
LateInteractionModelType::default()
}
fn default_batch_size() -> usize {
16
}
fn default_max_length() -> usize {
512
}
fn default_query_max_length() -> usize {
32
}
fn default_max_embed_duration_secs() -> Option<u64> {
Some(60)
}
fn deserialize_null_model<'de, D>(deserializer: D) -> Result<LateInteractionModelType, D::Error>
where
D: serde::Deserializer<'de>,
{
let opt = Option::<LateInteractionModelType>::deserialize(deserializer)?;
Ok(opt.unwrap_or_default())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_uses_gte_moderncolbert_preset() {
let config = LateInteractionConfig::default();
assert!(matches!(config.model, LateInteractionModelType::Preset { name } if name == "gte-moderncolbert"));
assert_eq!(config.batch_size, 16);
assert_eq!(config.max_length, 512);
assert_eq!(config.query_max_length, 32);
}
#[test]
fn null_model_deserializes_to_default() {
let json = r#"{"model": null}"#;
let config: LateInteractionConfig = serde_json::from_str(json).unwrap();
assert!(matches!(config.model, LateInteractionModelType::Preset { name } if name == "gte-moderncolbert"));
}
#[test]
fn custom_model_roundtrips() {
let config = LateInteractionConfig {
model: LateInteractionModelType::Custom {
model_id: "org/colbert".to_string(),
model_file: Some("onnx/model.onnx".to_string()),
additional_files: vec![],
max_length: Some(512),
},
..Default::default()
};
let json = serde_json::to_string(&config).unwrap();
let back: LateInteractionConfig = serde_json::from_str(&json).unwrap();
assert!(matches!(back.model, LateInteractionModelType::Custom { model_id, .. } if model_id == "org/colbert"));
}
}