Skip to main content

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}