qwen3_vl/error.rs
1//! Top-level error types for the `qwen3-vl` crate.
2//!
3//! `LoadError` is split out from `Error` because it has different recovery
4//! semantics in service layers (one-shot at startup; failure typically
5//! aborts the worker), while `Error` covers per-call failures that the
6//! caller may want to swallow into a default response.
7
8use std::path::PathBuf;
9
10/// Errors returned by [`crate::Engine::load`].
11#[derive(thiserror::Error, Debug)]
12pub enum LoadError {
13 /// The model directory does not exist on disk.
14 #[error("model path not found: {0}")]
15 NotFound(PathBuf),
16 /// mistralrs's builder returned an error during model load.
17 #[error("mistralrs build failed: {0}")]
18 Build(String),
19}
20
21/// Errors returned by [`crate::Engine::run`] and [`crate::Engine::warmup`].
22#[derive(thiserror::Error, Debug)]
23pub enum Error {
24 /// Caller passed an empty image list.
25 #[error("at least one image required")]
26 NoImages,
27 /// `RequestOptions` carried a value outside its valid range
28 /// (e.g. negative temperature, top_p > 1.0, top_k = 0). Issue #1
29 /// H-002 — sampler parameters were previously accepted without
30 /// validation; passing an out-of-range value to mistralrs's
31 /// sampler produces undefined behavior in most LLM engines.
32 #[error("invalid RequestOptions: {0}")]
33 InvalidRequest(&'static str),
34 /// Inference exceeded the configured timeout. Issue #1 H-001 —
35 /// `send_chat_request` was previously awaited without a deadline;
36 /// a stuck model (Metal JIT stall, GPU memory exhaustion) would
37 /// block the caller indefinitely. Returned by [`Engine::run`] /
38 /// [`Engine::run_with`] when the inference duration exceeds
39 /// `EngineOptions::inference_timeout`.
40 #[error("inference timed out after {0:?}")]
41 InferenceTimeout(std::time::Duration),
42 /// mistralrs's `MultimodalMessages` builder rejected the message.
43 ///
44 /// **Reserved variant.** mistralrs 0.8's
45 /// `MultimodalMessages::add_image_message` is infallible, so no current
46 /// code path constructs this. It exists for forward compatibility with
47 /// future mistralrs versions that may surface builder-validation errors,
48 /// and to keep the migration arms in
49 /// `docs/superpowers/specs/2026-04-28-qwen-engine-design.md`
50 /// §"`findit-qwen` migration" exhaustive.
51 #[error("vision message build failed: {0}")]
52 BuildMessage(String),
53 /// mistralrs returned an inference error.
54 #[error("inference failed: {0}")]
55 Inference(String),
56 /// The model returned empty content (after trimming).
57 #[error("model returned empty content")]
58 Empty,
59 /// The model hit `max_tokens` before producing a natural stop
60 /// (mistralrs surfaces this via `Choice::finish_reason = "length"`).
61 /// The raw text is included so callers can decide whether to
62 /// retry with a higher `EngineOptions::max_tokens` or accept
63 /// the partial output. finding: the engine
64 /// previously parsed length-truncated JSON as success, which
65 /// can persist incomplete metadata to a search index.
66 #[error(
67 "generation truncated by max_tokens (finish_reason={finish_reason:?}); raw output {raw_len} bytes"
68 )]
69 Truncated {
70 /// The non-`stop` finish_reason mistralrs reported (e.g.,
71 /// `"length"`, `"model_length"`).
72 finish_reason: String,
73 /// Length of the raw output in bytes (not the full text — that
74 /// would inflate error logs without aiding diagnosis).
75 raw_len: usize,
76 },
77 /// The model's output failed Task::parse — boxed because the
78 /// generic `Task::ParseError` type varies per Task. JSON tasks
79 /// surface as `Parse(Box::new(JsonParseError::...))`; custom
80 /// Tasks surface as `Parse(Box::new(MyParseError))`. A concrete
81 /// `From<JsonParseError>` bound at the engine call site would
82 /// compile-time block any Task that uses a different ParseError
83 /// type — including ones whose only purpose is to receive
84 /// `UnsupportedGrammar` for routing to a different engine.
85 #[error("parse failed: {0}")]
86 Parse(Box<dyn core::error::Error + Send + Sync + 'static>),
87 /// The supplied [`llmtask::Task`] returned a [`llmtask::Grammar`]
88 /// variant qwen3-vl cannot route to mistralrs. mistralrs 0.8 only
89 /// accepts JSON Schema; Lark / Regex tasks must run on an
90 /// llguidance-backed engine (e.g., the `lfm` crate).
91 #[error("{0}")]
92 UnsupportedGrammar(#[from] llmtask::UnsupportedGrammar),
93}