use crate::types::ExtractedDocument;
#[cfg(feature = "ner")]
use std::sync::Arc;
#[cfg(feature = "classification")]
use crate::ClassificationLabel;
#[cfg(feature = "ner")]
use crate::types::entity::{Entity, EntityCategory};
#[cfg(feature = "ner")]
pub struct NerEnrichmentConfig {
pub backend: Arc<dyn crate::text::ner::NerBackend>,
pub categories: Vec<EntityCategory>,
}
#[cfg(feature = "classification")]
pub struct ClassificationEnrichmentConfig {
pub config: crate::core::config::PageClassificationConfig,
}
#[cfg(feature = "classification")]
pub struct ChunkClassificationEnrichmentConfig {
pub config: crate::core::config::ChunkClassificationConfig,
}
#[cfg(feature = "captioning")]
pub struct CaptioningEnrichmentConfig {
pub config: crate::core::config::LlmConfig,
pub custom_prompt: Option<String>,
}
#[derive(Default)]
pub struct EnrichmentConfig {
#[cfg(feature = "ner")]
pub ner: Option<NerEnrichmentConfig>,
#[cfg(feature = "classification")]
pub classification: Option<ClassificationEnrichmentConfig>,
#[cfg(feature = "classification")]
pub chunk_classification: Option<ChunkClassificationEnrichmentConfig>,
#[cfg(feature = "captioning")]
pub captioning: Option<CaptioningEnrichmentConfig>,
}
pub struct EnrichedResult {
pub extraction: ExtractedDocument,
#[cfg(feature = "ner")]
pub entities: Option<Vec<Entity>>,
#[cfg(feature = "classification")]
pub classification: Option<Vec<ClassificationLabel>>,
#[cfg(feature = "captioning")]
pub captions: Option<Vec<String>>,
}
#[cfg_attr(alef, alef(skip))]
#[cfg_attr(not(feature = "classification"), allow(unused_mut))]
pub async fn enrich(mut extraction: ExtractedDocument, config: &EnrichmentConfig) -> crate::Result<EnrichedResult> {
#[cfg(not(any(feature = "classification", feature = "ner", feature = "captioning",)))]
let _ = config;
#[cfg(feature = "classification")]
let classification = if let Some(ref cfg) = config.classification {
Some(crate::text::classification::classify_document_onto(&mut extraction, &cfg.config).await?)
} else {
None
};
#[cfg(feature = "classification")]
if let Some(ref cfg) = config.chunk_classification {
crate::text::classification::classify_chunks(&mut extraction, &cfg.config).await?;
}
#[cfg(feature = "ner")]
let entities = if let Some(ref cfg) = config.ner {
let detected =
crate::text::ner::detect_entities(&extraction.content, cfg.backend.as_ref(), &cfg.categories).await?;
extraction.entities = Some(detected.clone());
Some(detected)
} else {
None
};
#[cfg(feature = "captioning")]
let captions = if let Some(ref cfg) = config.captioning {
Some(caption_images_onto(&mut extraction, cfg).await?)
} else {
None
};
Ok(EnrichedResult {
extraction,
#[cfg(feature = "ner")]
entities,
#[cfg(feature = "classification")]
classification,
#[cfg(feature = "captioning")]
captions,
})
}
#[cfg(feature = "captioning")]
async fn caption_images_onto(
extraction: &mut ExtractedDocument,
cfg: &CaptioningEnrichmentConfig,
) -> crate::Result<Vec<String>> {
fn commit(
extraction: &mut ExtractedDocument,
images: Vec<crate::types::ExtractedImage>,
usages: Vec<crate::types::LlmUsage>,
) {
extraction.images = Some(images);
if !usages.is_empty() {
extraction.llm_usage.get_or_insert_with(Vec::new).extend(usages);
}
}
let Some(mut images) = extraction.images.take() else {
return Ok(Vec::new());
};
let mut captions = Vec::with_capacity(images.len());
let mut usages: Vec<crate::types::LlmUsage> = Vec::new();
for index in 0..images.len() {
let data = images[index].data.clone();
if data.is_empty() {
captions.push(String::new());
continue;
}
match crate::captioning::caption_image_with_usage(&data, &cfg.config, cfg.custom_prompt.as_deref()).await {
Ok((caption, usage)) => {
let caption = caption.trim().to_string();
if !caption.is_empty() {
if images[index].description.is_none() {
images[index].description = Some(caption.clone());
}
images[index].caption = Some(caption.clone());
}
if let Some(mut usage) = usage {
if usage.source.is_empty() || usage.source == "vlm_ocr" {
usage.source = "captioning".to_string();
}
usages.push(usage);
}
captions.push(caption);
}
Err(error) => {
commit(extraction, images, usages);
return Err(error);
}
}
}
commit(extraction, images, usages);
Ok(captions)
}