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}