Skip to main content

deepseek_recipe_image/
lib.rs

1//! Image fetching and preprocessing for DeepSeek V4.1 model input.
2//!
3//! [`ImageResolver`] turns the [`ImageSource`](deepseek_recipe_core::multimodal::ImageSource)
4//! values produced by protocol conversion into
5//! [`MultiModalData`](deepseek_recipe_core::multimodal::MultiModalData). Fetching
6//! external URLs and preprocessing are supplied by [`ImageFetcher`] and
7//! [`ImagePreprocessor`] implementations.
8//!
9//! The default `opencv-preprocess` feature provides `OpenCvImagePreprocessor`,
10//! and the default `reqwest-fetch` feature provides `ReqwestImageFetcher`. A
11//! caller that supplies its own preprocessor disables default features and
12//! builds without OpenCV.
13
14use deepseek_recipe_core::multimodal::ImageInfo;
15
16mod error;
17#[cfg(feature = "reqwest-fetch")]
18mod fetcher;
19mod limits;
20#[cfg(feature = "opencv-preprocess")]
21mod opencv;
22mod resolver;
23mod retry;
24
25pub use error::ImageError;
26#[cfg(feature = "reqwest-fetch")]
27pub use fetcher::ReqwestImageFetcher;
28pub use limits::{ImageByteBudget, ImageLimits, ImageQuota, PreprocessOptions};
29#[cfg(feature = "opencv-preprocess")]
30pub use opencv::OpenCvImagePreprocessor;
31pub use resolver::ImageResolver;
32pub use retry::RetryPolicy;
33
34use std::future::Future;
35
36/// Fetches the bytes of an image identified by an external URL.
37///
38/// The default implementation rejects every URL. Supply an implementation when
39/// requests may reference external images.
40pub trait ImageFetcher: Send + Sync {
41    /// Fetch the bytes of `url`.
42    ///
43    /// The resolver checks a case-insensitive `http` prefix before calling this
44    /// method. The fetcher must validate the complete URL and allowed destinations.
45    ///
46    /// The fetches of one resolve call run concurrently, so an implementation
47    /// keeps their total size within the limit by reserving every chunk it keeps
48    /// from `budget` before it keeps the chunk, and it rejects a body larger than
49    /// [`ImageByteBudget::max_image_bytes`]. An implementation releases the bytes
50    /// it has reserved before it returns a retryable error, so the retry of that
51    /// attempt reserves them once. The resolver checks the size of the returned
52    /// bytes against [`ImageLimits`] as well.
53    ///
54    /// # Errors
55    ///
56    /// Returns [`ImageError`] when the URL cannot be fetched or the image
57    /// exceeds a limit of `budget`.
58    fn fetch(
59        &self,
60        url: &str,
61        budget: &ImageByteBudget,
62    ) -> impl Future<Output = Result<Vec<u8>, ImageError>> + Send {
63        let _ = (url, budget);
64        async {
65            Err(ImageError::Unsupported(
66                "image fetching requires an ImageFetcher implementation",
67            ))
68        }
69    }
70}
71
72/// Decodes, resizes, and encodes one image for the inference backend.
73///
74/// The default implementation rejects every image.
75pub trait ImagePreprocessor: Send + Sync {
76    /// Preprocess one encoded image.
77    ///
78    /// # Errors
79    ///
80    /// Returns [`ImageError`] when the image is unsupported, exceeds a limit, or
81    /// cannot be preprocessed.
82    fn preprocess(
83        &self,
84        data: Vec<u8>,
85        options: PreprocessOptions,
86    ) -> impl Future<Output = Result<ImageInfo, ImageError>> + Send {
87        let _ = (data, options);
88        async {
89            Err(ImageError::Unsupported(
90                "image preprocessing requires an ImagePreprocessor implementation",
91            ))
92        }
93    }
94}