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