cloudiful_docling_convert/
conversion.rs1use std::time::Duration;
2
3use crate::api::{DoclingClient, DoclingConfig};
4use crate::document::{
5 ChunkerKind, ChunkingOptions, ConvertOptions, InputKind, PipelineKind, RemoteConvertOptions,
6 TextConvertOptions,
7};
8use crate::error::Result;
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 pub tenant_id: Option<String>,
19 pub openai_api_key: Option<String>,
20 pub request_timeout: Option<Duration>,
21}
22
23impl DoclingRuntimeConfig {
24 pub fn without_vlm(docling_base_url: impl Into<String>) -> Self {
25 Self {
26 docling_base_url: docling_base_url.into(),
27 openai_base_url: String::new(),
28 vlm_pipeline_model: String::new(),
29 picture_description_model: String::new(),
30 code_formula_model: String::new(),
31 api_key: None,
32 tenant_id: None,
33 openai_api_key: None,
34 request_timeout: None,
35 }
36 }
37
38 pub fn from_env(docling_base_url: impl Into<String>) -> Self {
39 let mut config = Self::without_vlm(docling_base_url);
40 config.api_key = std::env::var("DOCLING_API_KEY").ok();
41 config.tenant_id = std::env::var("DOCLING_TENANT_ID").ok();
42 config.openai_api_key = std::env::var("OPENAI_API_KEY").ok();
43 config.request_timeout = std::env::var("DOCLING_HTTP_TIMEOUT_SECS")
44 .ok()
45 .and_then(|value| value.parse::<u64>().ok())
46 .map(Duration::from_secs);
47 config
48 }
49
50 pub fn into_docling_config(self) -> DoclingConfig {
51 DoclingConfig {
52 base_url: self.docling_base_url,
53 openai_base_url: self.openai_base_url,
54 vlm_pipeline_model: self.vlm_pipeline_model,
55 picture_description_model: self.picture_description_model,
56 code_formula_model: self.code_formula_model,
57 api_key: self
58 .api_key
59 .or_else(|| std::env::var("DOCLING_API_KEY").ok()),
60 openai_api_key: self
61 .openai_api_key
62 .or_else(|| std::env::var("OPENAI_API_KEY").ok()),
63 tenant_id: self
64 .tenant_id
65 .or_else(|| std::env::var("DOCLING_TENANT_ID").ok()),
66 request_timeout: self.request_timeout,
67 }
68 }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct ConversionBehavior {
73 pub chunker: ChunkerKind,
74 pub chunking: ChunkingOptions,
75 pub pipeline: Option<PipelineKind>,
76}
77
78impl Default for ConversionBehavior {
79 fn default() -> Self {
80 Self {
81 chunker: ChunkerKind::None,
82 chunking: ChunkingOptions::hybrid_defaults(),
83 pipeline: None,
84 }
85 }
86}
87
88pub fn build_docling_client(config: DoclingRuntimeConfig) -> Result<DoclingClient> {
89 DoclingClient::new(config.into_docling_config())
90}
91
92pub fn build_convert_options(
93 input_kind: InputKind,
94 behavior: &ConversionBehavior,
95) -> Result<ConvertOptions> {
96 let remote_options = RemoteConvertOptions {
97 chunker: behavior.chunker,
98 chunking: behavior.chunking.clone(),
99 pipeline: behavior.pipeline,
100 };
101
102 if matches!(input_kind, InputKind::Pdf) {
103 Ok(ConvertOptions::Pdf(remote_options))
104 } else if input_kind.uses_generic_convert_options() {
105 Ok(ConvertOptions::Generic(remote_options))
106 } else {
107 Ok(ConvertOptions::Text(TextConvertOptions::default()))
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn default_behavior_uses_normal_conversion() {
117 assert_eq!(ConversionBehavior::default().chunker, ChunkerKind::None);
118 }
119
120 #[test]
121 fn generic_inputs_use_native_chunker_options() {
122 let options = build_convert_options(
123 InputKind::Docx,
124 &ConversionBehavior {
125 chunker: ChunkerKind::Hierarchical,
126 ..ConversionBehavior::default()
127 },
128 )
129 .unwrap();
130
131 assert!(matches!(options, ConvertOptions::Generic(_)));
132 }
133
134 #[test]
135 fn text_inputs_remain_local() {
136 let options =
137 build_convert_options(InputKind::Text, &ConversionBehavior::default()).unwrap();
138 assert!(matches!(options, ConvertOptions::Text(_)));
139 }
140
141 #[test]
142 fn runtime_config_keeps_docling_and_openai_keys_separate() {
143 let config =
144 DoclingRuntimeConfig::without_vlm("http://localhost:5001/v1").into_docling_config();
145 assert!(config.api_key.is_none());
146 assert!(config.openai_api_key.is_none());
147 }
148}