Skip to main content

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}