unparser_shared/ai/config.rs
1//! Configuration shared by both `ai` capabilities.
2
3use std::time::Duration;
4
5/// Configuration for [`super::understand_image`] and [`super::refine_markdown`].
6///
7/// Constructed with [`AiConfig::new`], then adjusted by setting the remaining public
8/// fields directly (the struct is `#[non_exhaustive]` only to keep adding a field
9/// non-breaking — it does not restrict field access on an existing value).
10#[non_exhaustive]
11#[derive(Debug, Clone)]
12pub struct AiConfig {
13 /// The OpenAI-compatible endpoint's base URL (no trailing `/chat/completions`).
14 pub base_url: String,
15 /// Bearer token sent as `Authorization: Bearer {api_key}`.
16 pub api_key: String,
17 /// The model name to request.
18 pub model: String,
19 /// Which images a caller sends through [`super::understand_image`].
20 ///
21 /// Read by the caller deciding what to send (e.g. `unpdf`'s wiring) —
22 /// `unparser_shared::ai` itself never inspects this field. It exists on this
23 /// struct so a single `AiConfig` can carry the setting end-to-end from a CLI flag
24 /// or binding option down to the call site that needs it.
25 pub image_scope: ImageScope,
26 /// Per-attempt timeout. Deliberately generous by default — a page or image dense
27 /// with tables/formulas has been observed taking tens of seconds to a few
28 /// minutes.
29 pub timeout: Duration,
30 /// Maximum number of attempts (the first try plus retries) before giving up.
31 /// Applies to both transport failures and a truncated (`finish_reason == "length"`)
32 /// response.
33 pub max_retries: u32,
34}
35
36impl AiConfig {
37 /// Constructs a config with the required fields and the documented defaults for
38 /// everything else (`image_scope: All`, `timeout: 120s`, `max_retries: 3`).
39 pub fn new(
40 base_url: impl Into<String>,
41 api_key: impl Into<String>,
42 model: impl Into<String>,
43 ) -> Self {
44 Self {
45 base_url: base_url.into(),
46 api_key: api_key.into(),
47 model: model.into(),
48 image_scope: ImageScope::All,
49 timeout: Duration::from_secs(120),
50 max_retries: 3,
51 }
52 }
53}
54
55/// Which images [`super::understand_image`] is meant to be called for.
56///
57/// Purely a signal the caller reads before deciding to call — see
58/// [`AiConfig::image_scope`]'s docs for why this module never inspects it itself.
59#[non_exhaustive]
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
61pub enum ImageScope {
62 /// Every parsed image resource. Quality-first — the default once a caller
63 /// supplies an [`AiConfig`] at all.
64 #[default]
65 All,
66 /// Only pages a low-confidence OCR gate already flagged as a full-page scan with
67 /// no extractable text layer. Narrows cost/latency for a caller that wants VLM
68 /// calls only where the existing text extraction produced nothing.
69 LowConfidencePagesOnly,
70}