use docling_core::chunker::{contextualize, DocChunk, HierarchicalChunker};
use docling_core::DoclingDocument;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ChunkerKind {
Hierarchical,
Hybrid,
}
impl ChunkerKind {
pub fn parse(s: &str) -> Result<Self, String> {
match s {
"hierarchical" => Ok(Self::Hierarchical),
"hybrid" => Ok(Self::Hybrid),
other => Err(format!(
"unknown chunker {other:?} (expected: hierarchical, hybrid)"
)),
}
}
}
#[derive(Default, Clone, Debug)]
pub struct ChunkOptions {
pub chunker: Option<ChunkerKind>,
pub tokenizer: Option<String>,
pub max_tokens: Option<usize>,
pub merge_peers: Option<bool>,
}
fn records(chunks: &[DocChunk]) -> serde_json::Value {
serde_json::Value::Array(
chunks
.iter()
.map(|c| {
serde_json::json!({
"text": c.text,
"headings": c.headings,
"doc_items": c.doc_items.iter().map(|i| i.self_ref.clone()).collect::<Vec<_>>(),
"contextualize": contextualize(c),
})
})
.collect(),
)
}
pub fn chunk_records(
document: &DoclingDocument,
warn: &mut dyn FnMut(String),
) -> serde_json::Value {
chunk_records_with(document, &ChunkOptions::default(), warn)
.expect("default options never error")
}
pub fn chunk_records_with(
document: &DoclingDocument,
options: &ChunkOptions,
warn: &mut dyn FnMut(String),
) -> Result<serde_json::Value, String> {
let mut out = serde_json::json!({});
if options.chunker != Some(ChunkerKind::Hybrid) {
let hierarchical = HierarchicalChunker.chunk(document);
out["hierarchical"] = records(&hierarchical);
}
if options.chunker == Some(ChunkerKind::Hierarchical) {
return Ok(out);
}
#[cfg(feature = "chunking")]
{
let explicit = options.chunker == Some(ChunkerKind::Hybrid);
let tok_path = match options
.tokenizer
.clone()
.or_else(|| docling_core::env::nonempty("DOCLING_CHUNK_TOKENIZER"))
{
Some(p) => Some(p),
None => match docling_core::chunker::resolve_tokenizer_path(None) {
Ok(p) => Some(p),
Err(e) if explicit => return Err(e),
Err(_) => None,
},
};
if let Some(tok_path) = tok_path {
let max_tokens = options
.max_tokens
.or_else(|| docling_core::env::parse("DOCLING_CHUNK_MAX_TOKENS"))
.unwrap_or(256);
match docling_core::chunker::HuggingFaceTokenizer::from_file(&tok_path, max_tokens) {
Ok(tok) => {
let mut chunker = docling_core::chunker::HybridChunker::new(tok);
if let Some(mp) = options.merge_peers {
chunker = chunker.with_merge_peers(mp);
}
out["hybrid"] = records(&chunker.chunk(document));
}
Err(e) if explicit => return Err(e),
Err(e) => warn(e),
}
}
}
#[cfg(not(feature = "chunking"))]
{
let _ = warn;
if options.chunker == Some(ChunkerKind::Hybrid) {
return Err(
"the hybrid chunker needs the `chunking` build feature (not enabled)".into(),
);
}
}
Ok(out)
}