Skip to main content

cloudiful_docling_convert/
conversion.rs

1use crate::api::{DoclingClient, DoclingConfig};
2use crate::document::{
3    ConvertOptions, GenericFileConvertOptions, InputDocument, InputKind, PdfConvertOptions,
4    TextConvertOptions,
5};
6use crate::error::{PdfConvertError, Result};
7use crate::pdf::PdfInfo;
8use crate::processor::build_pdf_chunk_plan;
9
10#[derive(Debug, Clone)]
11pub struct DoclingRuntimeConfig {
12    pub docling_base_url: String,
13    pub openai_base_url: String,
14    pub vlm_pipeline_model: String,
15    pub picture_description_model: String,
16    pub code_formula_model: String,
17    pub api_key: Option<String>,
18}
19
20impl DoclingRuntimeConfig {
21    pub fn without_vlm(docling_base_url: impl Into<String>) -> Self {
22        Self {
23            docling_base_url: docling_base_url.into(),
24            openai_base_url: String::new(),
25            vlm_pipeline_model: String::new(),
26            picture_description_model: String::new(),
27            code_formula_model: String::new(),
28            api_key: None,
29        }
30    }
31
32    pub fn into_docling_config(self) -> DoclingConfig {
33        DoclingConfig {
34            base_url: self.docling_base_url,
35            openai_base_url: self.openai_base_url,
36            vlm_pipeline_model: self.vlm_pipeline_model,
37            picture_description_model: self.picture_description_model,
38            code_formula_model: self.code_formula_model,
39            api_key: self.api_key,
40        }
41    }
42}
43
44#[derive(Debug, Clone)]
45pub struct ConversionBehavior {
46    pub pages_per_file: u32,
47    pub split_input: bool,
48    pub split_by_bookmark: bool,
49    pub chunking: bool,
50    pub batch_size: usize,
51}
52
53impl Default for ConversionBehavior {
54    fn default() -> Self {
55        Self {
56            pages_per_file: 5,
57            split_input: true,
58            split_by_bookmark: false,
59            chunking: false,
60            batch_size: 2,
61        }
62    }
63}
64
65pub fn build_docling_client(config: DoclingRuntimeConfig) -> Result<DoclingClient> {
66    DoclingClient::new(config.into_docling_config())
67}
68
69pub fn build_convert_options(
70    input_kind: InputKind,
71    behavior: &ConversionBehavior,
72) -> Result<ConvertOptions> {
73    match input_kind {
74        InputKind::Pdf => Ok(ConvertOptions::Pdf(PdfConvertOptions {
75            pages_per_file: behavior.pages_per_file,
76            split_input: behavior.split_input,
77            split_by_bookmark: behavior.split_by_bookmark,
78            chunking: behavior.chunking,
79            batch_size: behavior.batch_size,
80        })),
81        InputKind::Docx | InputKind::Markdown => {
82            reject_pdf_only_options(input_kind, behavior)?;
83            Ok(ConvertOptions::Generic(GenericFileConvertOptions {
84                chunking: behavior.chunking,
85            }))
86        }
87        InputKind::Text => {
88            reject_pdf_only_options(input_kind, behavior)?;
89            Ok(ConvertOptions::Text(TextConvertOptions::default()))
90        }
91    }
92}
93
94pub fn build_pdf_options(behavior: &ConversionBehavior) -> Result<PdfConvertOptions> {
95    let options = PdfConvertOptions {
96        pages_per_file: behavior.pages_per_file,
97        split_input: behavior.split_input,
98        split_by_bookmark: behavior.split_by_bookmark,
99        chunking: behavior.chunking,
100        batch_size: behavior.batch_size,
101    };
102    options.validate()?;
103    Ok(options)
104}
105
106pub fn count_input_chunks(input: &InputDocument, behavior: &ConversionBehavior) -> Result<usize> {
107    match input.kind()? {
108        InputKind::Pdf => {
109            let pdf_info = PdfInfo::load_from_bytes(input.bytes.as_ref())?;
110            let total = build_pdf_chunk_plan(&build_pdf_options(behavior)?, &pdf_info)
111                .len()
112                .max(1);
113            Ok(total)
114        }
115        _ => Ok(1),
116    }
117}
118
119fn reject_pdf_only_options(input_kind: InputKind, behavior: &ConversionBehavior) -> Result<()> {
120    if behavior.split_by_bookmark {
121        return Err(PdfConvertError::validation_error(
122            "split_by_bookmark",
123            format!(
124                "bookmark splitting is only available for PDF inputs, got {:?}",
125                input_kind
126            ),
127        ));
128    }
129
130    Ok(())
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn generic_input_rejects_bookmark_splitting() {
139        let err = build_convert_options(
140            InputKind::Docx,
141            &ConversionBehavior {
142                split_by_bookmark: true,
143                ..ConversionBehavior::default()
144            },
145        )
146        .unwrap_err();
147
148        assert!(err.to_string().contains("bookmark splitting"));
149    }
150}