1use std::{
21 collections::HashMap,
22 fmt::Debug,
23 hash::{Hash, Hasher},
24 os::unix::ffi::OsStrExt,
25 path::PathBuf,
26};
27
28use derive_builder::Builder;
29use itertools::Itertools;
30use serde::{Deserialize, Serialize};
31
32use crate::{Embedding, SparseEmbedding, metadata::Metadata};
33
34pub trait Chunk: Clone + Send + Sync + Debug + AsRef<[u8]> + 'static {}
40impl<T> Chunk for T where T: Clone + Send + Sync + Debug + AsRef<[u8]> + 'static {}
41
42#[derive(Default, Clone, Serialize, Deserialize, PartialEq, Builder)]
48#[builder(setter(into, strip_option), build_fn(error = "anyhow::Error"))]
49pub struct Node<T: Chunk> {
50 #[builder(default)]
52 pub path: PathBuf,
53 pub chunk: T,
55 #[builder(default)]
57 pub vectors: Option<HashMap<EmbeddedField, Embedding>>,
58 #[builder(default)]
60 pub sparse_vectors: Option<HashMap<EmbeddedField, SparseEmbedding>>,
61 #[builder(default)]
63 pub metadata: Metadata,
64 #[builder(default)]
66 pub embed_mode: EmbedMode,
67 #[builder(default)]
69 pub original_size: usize,
70 #[builder(default)]
73 pub offset: usize,
74 #[builder(default)]
78 pub parent_id: Option<uuid::Uuid>,
79}
80
81pub type TextNode = Node<String>;
82
83impl<T: Chunk> NodeBuilder<T> {
84 pub fn maybe_sparse_vectors(
85 &mut self,
86 sparse_vectors: Option<HashMap<EmbeddedField, SparseEmbedding>>,
87 ) -> &mut Self {
88 self.sparse_vectors = Some(sparse_vectors);
89 self
90 }
91
92 pub fn maybe_vectors(
93 &mut self,
94 vectors: Option<HashMap<EmbeddedField, Embedding>>,
95 ) -> &mut Self {
96 self.vectors = Some(vectors);
97 self
98 }
99}
100
101impl<T: Chunk> Debug for Node<T> {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 f.debug_struct("Node")
108 .field("id", &self.id())
109 .field("path", &self.path)
110 .field("chunk", &self.chunk)
111 .field("metadata", &self.metadata)
112 .field(
113 "vectors",
114 &self
115 .vectors
116 .iter()
117 .flat_map(HashMap::iter)
118 .map(|(embed_type, vec)| format!("'{embed_type}': {}", vec.len()))
119 .join(","),
120 )
121 .field(
122 "sparse_vectors",
123 &self
124 .sparse_vectors
125 .iter()
126 .flat_map(HashMap::iter)
127 .map(|(embed_type, vec)| {
128 format!(
129 "'{embed_type}': indices({}), values({})",
130 vec.indices.len(),
131 vec.values.len()
132 )
133 })
134 .join(","),
135 )
136 .field("embed_mode", &self.embed_mode)
137 .finish()
138 }
139}
140
141impl<T: Chunk> Node<T> {
142 pub fn build_from_other(node: &Node<T>) -> NodeBuilder<T> {
147 NodeBuilder::default()
148 .path(node.path.clone())
149 .chunk(node.chunk.clone())
150 .metadata(node.metadata.clone())
151 .maybe_vectors(node.vectors.clone())
152 .maybe_sparse_vectors(node.sparse_vectors.clone())
153 .embed_mode(node.embed_mode)
154 .original_size(node.original_size)
155 .offset(node.offset)
156 .parent_id(node.parent_id.unwrap_or_else(|| node.id()))
157 .to_owned()
158 }
159
160 pub fn builder<VALUE: Chunk + Clone>() -> NodeBuilder<VALUE> {
162 NodeBuilder::default()
163 }
164
165 pub fn new(chunk: impl Into<String>) -> Node<String> {
169 let chunk = chunk.into();
170 let original_size = chunk.len();
171 Node {
172 chunk,
173 original_size,
174 ..Default::default()
175 }
176 }
177
178 pub fn with_metadata(&mut self, metadata: impl Into<Metadata>) -> &mut Self {
179 self.metadata = metadata.into();
180 self
181 }
182
183 pub fn with_vectors(
184 &mut self,
185 vectors: impl Into<HashMap<EmbeddedField, Embedding>>,
186 ) -> &mut Self {
187 self.vectors = Some(vectors.into());
188 self
189 }
190
191 pub fn with_sparse_vectors(
192 &mut self,
193 sparse_vectors: impl Into<HashMap<EmbeddedField, SparseEmbedding>>,
194 ) -> &mut Self {
195 self.sparse_vectors = Some(sparse_vectors.into());
196 self
197 }
198
199 pub fn id(&self) -> uuid::Uuid {
206 let bytes = [self.path.as_os_str().as_bytes(), self.chunk.as_ref()].concat();
208
209 uuid::Uuid::new_v3(&uuid::Uuid::NAMESPACE_OID, &bytes)
210 }
211
212 pub fn parent_id(&self) -> Option<uuid::Uuid> {
214 self.parent_id
215 }
216}
217
218impl Node<String> {
219 pub fn into_chunks(
224 self,
225 chunks: impl IntoIterator<Item = String>,
226 ) -> impl Iterator<Item = Self> {
227 let parent_id = self.parent_id.unwrap_or_else(|| self.id());
228 let mut template = self;
229 template.chunk.clear();
230 template.parent_id = Some(parent_id);
231
232 chunks.into_iter().map(move |chunk| {
233 let mut node = template.clone();
234 node.chunk = chunk;
235 node
236 })
237 }
238
239 pub fn as_embeddables(&self) -> Vec<(EmbeddedField, String)> {
245 let mut embeddables = Vec::new();
247
248 if self.embed_mode == EmbedMode::SingleWithMetadata || self.embed_mode == EmbedMode::Both {
249 embeddables.push((EmbeddedField::Combined, self.combine_chunk_with_metadata()));
250 }
251
252 if self.embed_mode == EmbedMode::PerField || self.embed_mode == EmbedMode::Both {
253 embeddables.push((EmbeddedField::Chunk, self.chunk.clone()));
254 for (name, value) in &self.metadata {
255 let value = value
256 .as_str()
257 .map_or_else(|| value.to_string(), ToString::to_string);
258 embeddables.push((EmbeddedField::Metadata(name.clone()), value));
259 }
260 }
261
262 embeddables
263 }
264
265 fn combine_chunk_with_metadata(&self) -> String {
274 let metadata = self
276 .metadata
277 .iter()
278 .map(|(k, v)| {
279 let v = v
280 .as_str()
281 .map_or_else(|| v.to_string(), ToString::to_string);
282
283 format!("{k}: {v}")
284 })
285 .collect::<Vec<String>>()
286 .join("\n");
287
288 format!("{}\n{}", metadata, self.chunk)
289 }
290}
291
292impl Hash for Node<String> {
293 fn hash<H: Hasher>(&self, state: &mut H) {
297 self.path.hash(state);
298 self.chunk.hash(state);
299 }
300}
301
302impl<T: Into<String>> From<T> for Node<String> {
303 fn from(value: T) -> Self {
304 let value: String = value.into();
305 Node::<String>::new(value)
306 }
307}
308
309#[derive(Copy, Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
311pub enum EmbedMode {
312 #[default]
313 SingleWithMetadata,
315 PerField,
317 Both,
320}
321
322#[derive(
324 Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash, strum_macros::Display, Debug,
325)]
326pub enum EmbeddedField {
327 #[default]
328 Combined,
330 Chunk,
332 #[strum(to_string = "Metadata: {0}")]
335 Metadata(String),
336}
337
338impl EmbeddedField {
339 pub fn sparse_field_name(&self) -> String {
341 format!("{self}_sparse")
342 }
343
344 pub fn field_name(&self) -> String {
346 format!("{self}")
347 }
348}
349
350#[allow(clippy::from_over_into)]
351impl Into<String> for EmbeddedField {
352 fn into(self) -> String {
353 self.to_string()
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360 use test_case::test_case;
361
362 #[test_case(&EmbeddedField::Combined, ["Combined", "Combined_sparse"])]
363 #[test_case(&EmbeddedField::Chunk, ["Chunk", "Chunk_sparse"])]
364 #[test_case(&EmbeddedField::Metadata("test".into()), ["Metadata: test", "Metadata: test_sparse"])]
365 fn field_name_tests(embedded_field: &EmbeddedField, expected: [&str; 2]) {
366 assert_eq!(embedded_field.field_name(), expected[0]);
367 assert_eq!(embedded_field.sparse_field_name(), expected[1]);
368 }
369
370 #[test]
371 fn test_debugging_node_with_utf8_char_boundary() {
372 let node = Node::from("🦀".repeat(101));
373 let _ = format!("{node:?}");
375
376 let node = Node::from("Jürgen".repeat(100));
378 let _ = format!("{node:?}");
379 }
380
381 #[test]
382 fn test_build_from_other_without_vectors() {
383 let original_node = Node::from("test_chunk")
384 .with_metadata(Metadata::default())
385 .with_vectors(HashMap::new())
386 .with_sparse_vectors(HashMap::new())
387 .to_owned();
388
389 let builder = Node::build_from_other(&original_node);
390 let new_node = builder.build().unwrap();
391
392 assert_eq!(new_node.parent_id(), Some(original_node.id()));
393 assert_eq!(new_node.chunk, original_node.chunk);
394 assert_eq!(new_node.path, original_node.path);
395 assert_eq!(new_node.metadata, original_node.metadata);
396 assert_eq!(new_node.vectors, original_node.vectors);
397 assert_eq!(new_node.sparse_vectors, original_node.sparse_vectors);
398 }
399
400 #[test]
401 fn test_build_from_other_with_vectors() {
402 let mut vectors = HashMap::new();
403 vectors.insert(EmbeddedField::Chunk, Embedding::default());
404
405 let mut sparse_vectors = HashMap::new();
406 sparse_vectors.insert(
407 EmbeddedField::Chunk,
408 SparseEmbedding {
409 indices: vec![],
410 values: vec![],
411 },
412 );
413
414 let original_node = Node::from("test_chunk")
415 .with_metadata(Metadata::default())
416 .with_vectors(vectors.clone())
417 .with_sparse_vectors(sparse_vectors.clone())
418 .to_owned();
419
420 let builder = Node::build_from_other(&original_node);
421 let new_node = builder.build().unwrap();
422
423 assert_eq!(new_node.parent_id(), Some(original_node.id()));
424 assert_eq!(new_node.chunk, original_node.chunk);
425 assert_eq!(new_node.vectors, original_node.vectors);
426 assert_eq!(new_node.sparse_vectors, original_node.sparse_vectors);
427 }
428
429 #[test]
430 fn into_chunks_preserves_fields_and_first_ancestor() {
431 let source = TextNode::builder()
432 .path("fixtures/日本語.txt")
433 .chunk("αβγ parent".to_string())
434 .metadata(Metadata::from([("language", "日本語")]))
435 .vectors(HashMap::from([(EmbeddedField::Chunk, vec![1.0, 2.0])]))
436 .sparse_vectors(HashMap::from([(
437 EmbeddedField::Chunk,
438 SparseEmbedding {
439 indices: vec![2],
440 values: vec![0.5],
441 },
442 )]))
443 .embed_mode(EmbedMode::Both)
444 .original_size(100usize)
445 .offset(7usize)
446 .build()
447 .unwrap();
448 let parent_id = source.id();
449 let chunks = vec!["αβγ".to_string(), "日本語".to_string()];
450 let outputs: Vec<_> = source.clone().into_chunks(chunks.clone()).collect();
451 assert_eq!(outputs.len(), 2);
452 for (output, chunk) in outputs.iter().zip(chunks) {
453 assert_eq!(output.chunk, chunk);
454 assert_eq!(output.path, source.path);
455 assert_eq!(output.metadata, source.metadata);
456 assert_eq!(output.vectors, source.vectors);
457 assert_eq!(output.sparse_vectors, source.sparse_vectors);
458 assert_eq!(output.embed_mode, source.embed_mode);
459 assert_eq!(output.original_size, source.original_size);
460 assert_eq!(output.offset, source.offset);
461 assert_eq!(output.parent_id, Some(parent_id));
462 }
463
464 let descendant: Vec<_> = outputs[0]
465 .clone()
466 .into_chunks(["child".to_string()])
467 .collect();
468 assert_eq!(descendant[0].parent_id, Some(parent_id));
469
470 let no_outputs: Vec<_> = source.into_chunks(Vec::<String>::new()).collect();
471 assert!(no_outputs.is_empty());
472 }
473
474 #[test]
475 fn into_chunks_preserves_explicit_parent_and_live_id() {
476 let mut source = Node::from("original");
477 let original_id = source.id();
478 source.path = "changed.txt".into();
479 source.chunk = "changed".into();
480 assert_ne!(source.id(), original_id);
481
482 let explicit_parent = uuid::Uuid::new_v4();
483 source.parent_id = Some(explicit_parent);
484 let children: Vec<_> = source.into_chunks(["one".into(), "two".into()]).collect();
485 assert_eq!(children.len(), 2);
486 assert!(
487 children
488 .iter()
489 .all(|child| child.parent_id == Some(explicit_parent))
490 );
491 }
492
493 #[test]
494 fn test_build_from_other_keeps_first_ancestor_as_parent() {
495 let original_node = Node::from("test_chunk");
496 let child_node = Node::build_from_other(&original_node).build().unwrap();
497 let grandchild_node = Node::build_from_other(&child_node).build().unwrap();
498
499 assert_eq!(original_node.parent_id(), None);
500 assert_eq!(child_node.parent_id(), Some(original_node.id()));
501 assert_eq!(grandchild_node.parent_id(), Some(original_node.id()));
502 }
503}