mod build;
mod encoding_choice;
mod payload;
#[cfg(test)]
mod tests;
mod text_codec;
pub use encoding_choice::{EmbeddingDType, REPRODUCIBLE_CREATED, SectionEncoding};
use crate::chunk::ChunkInput;
use crate::manifest::Manifest;
use std::path::Path;
pub struct UrnaFileBuilder {
pub(super) manifest: Manifest,
pub(super) chunks: Vec<ChunkInput>,
pub(super) provenance: serde_json::Value,
pub(super) reproducible: bool,
pub(super) text_encoding: SectionEncoding,
pub(super) dtype: EmbeddingDType,
pub(super) hnsw_index: Option<Vec<u8>>,
pub(super) bm25_index: Option<Vec<u8>>,
pub(super) graph_adjacency: Option<Vec<u8>>,
pub(super) blob_refs: Option<Vec<u8>>,
pub(super) blob_data: Option<Vec<u8>>,
pub(super) blob_span_overlay: Option<Vec<u8>>,
pub(super) space_table: Option<Vec<u8>>,
pub(super) space_bands: Vec<(u32, u32, Vec<u8>)>,
}
impl UrnaFileBuilder {
pub fn new(manifest: Manifest) -> Self {
Self {
manifest,
chunks: Vec::new(),
provenance: serde_json::json!({}),
reproducible: false,
text_encoding: SectionEncoding::Raw,
dtype: EmbeddingDType::Float32,
hnsw_index: None,
bm25_index: None,
graph_adjacency: None,
blob_refs: None,
blob_data: None,
blob_span_overlay: None,
space_table: None,
space_bands: Vec::new(),
}
}
pub fn add_chunk(mut self, c: ChunkInput) -> Self {
self.chunks.push(c);
self
}
pub fn add_chunks<I: IntoIterator<Item = ChunkInput>>(mut self, chunks: I) -> Self {
self.chunks.extend(chunks);
self
}
pub fn with_provenance(mut self, v: serde_json::Value) -> Self {
self.provenance = v;
self
}
pub fn reproducible(mut self, on: bool) -> Self {
self.reproducible = on;
self
}
pub fn text_encoding(mut self, enc: SectionEncoding) -> Self {
self.text_encoding = enc;
self
}
pub fn embedding_dtype(mut self, dt: EmbeddingDType) -> Self {
self.dtype = dt;
self.manifest.dtype = dt.manifest_str().to_string();
self
}
pub fn hnsw_index(mut self, payload: Vec<u8>) -> Self {
self.hnsw_index = Some(payload);
self.manifest.index_type = "hnsw".into();
self.manifest.rerank_policy = "exact".into();
self.manifest.capabilities.supports_ann = true;
self
}
pub fn bm25_index(mut self, payload: Vec<u8>) -> Self {
self.bm25_index = Some(payload);
self.manifest.capabilities.supports_bm25 = true;
self
}
pub fn graph_adjacency(mut self, payload: Vec<u8>) -> Self {
self.graph_adjacency = Some(payload);
let mut ext = self.manifest.capabilities_ext.take().unwrap_or_default();
ext.graph_present = Some(true);
self.manifest.capabilities_ext = Some(ext);
self
}
pub fn blob_refs(mut self, payload: Vec<u8>) -> Self {
self.blob_refs = Some(payload);
let mut ext = self.manifest.capabilities_ext.take().unwrap_or_default();
ext.blobs_present = Some(true);
self.manifest.capabilities_ext = Some(ext);
self
}
pub fn blob_data(mut self, payload: Vec<u8>) -> Self {
self.blob_data = Some(payload);
let mut ext = self.manifest.capabilities_ext.take().unwrap_or_default();
ext.blobs_present = Some(true);
self.manifest.capabilities_ext = Some(ext);
self
}
pub fn blob_span_overlay(mut self, payload: Vec<u8>) -> Self {
self.blob_span_overlay = Some(payload);
let mut ext = self.manifest.capabilities_ext.take().unwrap_or_default();
ext.blobs_present = Some(true);
self.manifest.capabilities_ext = Some(ext);
self
}
pub fn space_table(mut self, payload: Vec<u8>) -> Self {
self.space_table = Some(payload);
let mut ext = self.manifest.capabilities_ext.take().unwrap_or_default();
ext.supports_multimodal = Some(true);
self.manifest.capabilities_ext = Some(ext);
self
}
pub fn space_band(mut self, space_index: u8, encoding: u32, payload: Vec<u8>) -> Self {
let band_id = crate::layout::SECTION_SPACE_EMBEDDINGS_BASE + space_index as u32;
self.space_bands.push((band_id, encoding, payload));
let mut ext = self.manifest.capabilities_ext.take().unwrap_or_default();
ext.supports_multimodal = Some(true);
self.manifest.capabilities_ext = Some(ext);
self
}
pub fn hybrid(mut self) -> Self {
self.manifest.index_type = "hybrid".into();
self.manifest.rerank_policy = "exact".into();
self.manifest.score_type = "hybrid_rrf".into();
self.manifest.capabilities.supports_bm25 = true;
self
}
pub fn write_to_path(self, path: impl AsRef<Path>) -> crate::Result<()> {
let buf = self.build_bytes()?;
std::fs::write(path, buf)?;
Ok(())
}
}