Skip to main content

gglib_core/ports/
model_catalog.rs

1//! Model catalog port for listing and resolving models.
2//!
3//! This port defines the interface for querying the model catalog.
4//! It provides domain-level model information without exposing
5//! database or storage implementation details.
6
7use async_trait::async_trait;
8use std::fmt;
9use std::path::PathBuf;
10use thiserror::Error;
11
12use crate::domain::DefaultsOrigin;
13use crate::domain::InferenceConfig;
14use crate::domain::KvElemsPerToken;
15use crate::domain::ModelCapabilities;
16use crate::domain::ServerConfig;
17
18/// Domain model summary for catalog operations (listing).
19///
20/// This is a domain type (not an `OpenAI` API type). The proxy layer
21/// is responsible for mapping this to OpenAI-compatible formats.
22///
23/// Note: Does NOT include `file_path` to avoid leaking filesystem details
24/// in catalog/listing operations.
25#[derive(Debug, Clone)]
26pub struct ModelSummary {
27    /// Database ID of the model.
28    pub id: u32,
29    /// Model name (used as identifier).
30    pub name: String,
31    /// Tags/labels associated with the model.
32    pub tags: Vec<String>,
33    /// Detected and persisted capability flags for this model.
34    ///
35    /// This is the single source of truth for model behaviour constraints
36    /// (strict-turn alternation, system-role support, tool calls, reasoning).
37    /// The proxy uses these directly rather than inferring from tags at
38    /// request time, eliminating the split-brain between tags and capabilities.
39    pub capabilities: ModelCapabilities,
40    /// Parameter count as string (e.g., "7B", "13B", "70B").
41    pub param_count: String,
42    /// Quantization type (e.g., "`Q4_K_M`", "`Q8_0`").
43    pub quantization: Option<String>,
44    /// Model architecture (e.g., "llama", "mistral", "qwen2").
45    pub architecture: Option<String>,
46    /// Unix timestamp when the model was added.
47    pub created_at: i64,
48    /// File size in bytes.
49    pub file_size: u64,
50    /// Maximum context length the model supports, in tokens (from GGUF
51    /// metadata). `None` when unknown.
52    ///
53    /// This is a static, per-model ceiling — it does not reflect the
54    /// `--ctx-size` a currently-running instance was actually launched
55    /// with, which can be smaller (see `gglib_core::ports::model_runtime::RunningTarget::effective_ctx`
56    /// for the live value). Consumers that need the true, currently-running
57    /// context size should prefer `effective_ctx` when the model is running
58    /// and fall back to this field otherwise.
59    pub context_length: Option<u64>,
60    /// Per-model inference parameter defaults.
61    ///
62    /// When `Some`, these are resolved per-request via
63    /// [`InferenceConfig::resolve_with_defaults`] before forwarding to llama-server.
64    /// Used by `gglib proxy` to inject resolved defaults into OpenAI-format
65    /// request bodies, and by the agentic loop (`gglib chat`, `gglib q`) to
66    /// apply model-specific sampling parameters.
67    pub inference_defaults: Option<InferenceConfig>,
68    /// Whether [`Self::inference_defaults`] was set by the user or
69    /// auto-detected. See [`DefaultsOrigin`] and
70    /// [`InferenceConfig::resolve_with_profile`](crate::domain::InferenceConfig::resolve_with_profile).
71    pub defaults_origin: Option<DefaultsOrigin>,
72    /// Per-model server defaults (`context_length`, etc.) from the database.
73    pub server_defaults: Option<ServerConfig>,
74}
75
76/// Launch specification for running a model.
77///
78/// Contains all information needed to actually launch a model,
79/// including the file path. Separate from `ModelSummary` to avoid
80/// leaking filesystem details in catalog operations.
81#[derive(Debug, Clone)]
82pub struct ModelLaunchSpec {
83    /// Database ID of the model.
84    pub id: u32,
85    /// Model name.
86    pub name: String,
87    /// Absolute path to the GGUF file.
88    pub file_path: PathBuf,
89    /// Tags/labels associated with the model.
90    pub tags: Vec<String>,
91    /// Model architecture (for runtime configuration).
92    pub architecture: Option<String>,
93    /// Quantization label (`Q4_K_M`), when the catalog recorded one.
94    ///
95    /// Carried purely so the launch can name what it loaded — see
96    /// [`crate::domain::LaunchNarration`]. `None` for models whose GGUF
97    /// metadata did not identify a quantization.
98    pub quantization: Option<String>,
99    /// Maximum context length the model supports.
100    pub context_length: Option<u64>,
101    /// Per-model server defaults (e.g., `context_length` for launch).
102    pub server_defaults: Option<ServerConfig>,
103    /// Total on-disk size of the model weights in bytes, summed across all
104    /// shards for multi-part GGUFs.
105    ///
106    /// Used to budget host memory at launch (see
107    /// [`crate::server_config::compute_auto_cache_ram_mb`]). `0` when the
108    /// size could not be determined — callers must treat that as "unknown"
109    /// rather than "free".
110    pub file_size_bytes: u64,
111    /// Estimated K/V element counts consumed per token of context, derived
112    /// from the model's GGUF metadata (see
113    /// [`crate::domain::estimate_kv_elems_per_token`]). Type-agnostic —
114    /// callers convert to bytes via [`crate::domain::kv_bytes_per_token`]
115    /// once the launch's resolved K/V cache types are known.
116    ///
117    /// `None` when the metadata doesn't carry the layer/head counts needed;
118    /// callers substitute a conservative allowance.
119    pub kv_elems_per_token: Option<KvElemsPerToken>,
120    /// True when the model's KV memory retains only part of the token history
121    /// — sliding-window, hybrid, or recurrent attention (see
122    /// [`crate::domain::kv_memory_is_partial`]).
123    ///
124    /// Such models cannot be resumed from llama-server's disk slot files: the
125    /// save/restore path does not carry the context checkpoints they need, so
126    /// a "successful" restore still forces a full prompt re-prefill. Callers
127    /// disable the disk slot layer for these models and rely on the in-RAM
128    /// prompt cache, which does preserve checkpoints.
129    pub kv_memory_is_partial: bool,
130}
131
132impl ModelSummary {
133    /// Create a description string for this model.
134    #[must_use]
135    pub fn description(&self) -> String {
136        let arch = self.architecture.as_deref().unwrap_or("unknown");
137        let quant = self.quantization.as_deref().unwrap_or("unknown");
138        format!("{} - {} parameters, {}", arch, self.param_count, quant)
139    }
140}
141
142/// Errors that can occur during catalog operations.
143#[derive(Debug, Error)]
144pub enum CatalogError {
145    /// Failed to query the catalog.
146    #[error("Failed to query catalog: {0}")]
147    QueryFailed(String),
148
149    /// Internal error during catalog operations.
150    #[error("Internal error: {0}")]
151    Internal(String),
152}
153
154/// Port for querying the model catalog.
155///
156/// This interface provides read-only access to the model catalog
157/// for listing and resolving models. It does not handle model
158/// registration or deletion.
159#[async_trait]
160pub trait ModelCatalogPort: Send + Sync + fmt::Debug {
161    /// List all models in the catalog.
162    ///
163    /// Returns a list of model summaries ordered by name.
164    ///
165    /// # Errors
166    ///
167    /// Returns `CatalogError` if the catalog cannot be queried.
168    async fn list_models(&self) -> Result<Vec<ModelSummary>, CatalogError>;
169
170    /// Resolve a model by name or alias.
171    ///
172    /// This method performs model resolution:
173    /// 1. Exact name match
174    /// 2. Case-insensitive name match
175    /// 3. Fuzzy/partial match (implementation-defined)
176    ///
177    /// Returns `None` if no matching model is found.
178    ///
179    /// # Arguments
180    ///
181    /// * `name` - Model name or alias to resolve
182    ///
183    /// # Errors
184    ///
185    /// Returns `CatalogError` if the catalog cannot be queried.
186    async fn resolve_model(&self, name: &str) -> Result<Option<ModelSummary>, CatalogError>;
187
188    /// Resolve a model for launching.
189    ///
190    /// Returns full launch specification including file path.
191    /// Use this when you need to actually run a model, not just list it.
192    ///
193    /// # Arguments
194    ///
195    /// * `name` - Model name or alias to resolve
196    ///
197    /// # Errors
198    ///
199    /// Returns `CatalogError` if the catalog cannot be queried.
200    async fn resolve_for_launch(&self, name: &str)
201    -> Result<Option<ModelLaunchSpec>, CatalogError>;
202}