Skip to main content

ModelService

Struct ModelService 

Source
pub struct ModelService {
    repo: Arc<dyn ModelRepository>,
}
Expand description

Service for model operations.

This service provides high-level model management by delegating to the injected ModelRepository. It adds no business logic beyond what the repository provides - it’s a thin facade.

Fields§

§repo: Arc<dyn ModelRepository>

Implementations§

Source§

impl ModelService

Source

pub fn new(repo: Arc<dyn ModelRepository>) -> Self

Create a new model service with the given repository.

Source

pub async fn list(&self) -> Result<Vec<Model>, CoreError>

List all models.

Source

pub async fn get(&self, identifier: &str) -> Result<Option<Model>, CoreError>

Get a model by its identifier (numeric database id, then exact name).

Thin wrapper over ModelRepository::get_by_identifier, which owns the lookup-key policy so this service and the ModelCatalogPort adapter cannot disagree about what a given string resolves to.

Source

pub async fn get_by_id(&self, id: i64) -> Result<Option<Model>, CoreError>

Get a model by its database ID.

Source

pub async fn find_by_identifier( &self, identifier: &str, ) -> Result<Model, CoreError>

Find a model by identifier — a numeric id, then an exact name (not an HF id, despite what this said for a long time). Errors if not found.

Source

pub async fn add(&self, model: NewModel) -> Result<Model, CoreError>

Add a new model from an already-built row.

This is the raw registration door: it inherits ModelRepository::insert’s upsert, so a row whose key already exists is overwritten rather than reported. That is right for registration-after-download, which has to be safe to retry, and wrong for “add this file to my library” — which is Self::import_from_file, the door that checks first.

Source

pub async fn import_from_file( &self, file_path: &Path, gguf_parser: &dyn GgufParserPort, param_count_override: Option<f64>, mode: ImportMode, ) -> Result<Model, CoreError>

Import a model from a local GGUF file with full metadata extraction.

Validates file, parses GGUF metadata, detects capabilities, and registers with rich metadata. This is the canonical way to add local models.

§Arguments
  • file_path - Absolute path to the GGUF file
  • gguf_parser - Parser implementation for metadata extraction
  • param_count_override - Optional user override for parameter count
  • mode - Whether a file already in the library is a conflict (ImportMode::Fresh) or should be re-derived in place (ImportMode::Refresh)
§Returns

Returns the registered Model with full metadata, or validation error.

§Errors

CoreError::Validation if the file is missing, is not a readable GGUF, or cannot be resolved to a canonical path; RepositoryError::AlreadyExists under ImportMode::Fresh when the file is already registered.

§Design

This method validates and parses the GGUF file, then delegates naming, capability detection, and tag generation to build_new_model — the construction path shared with the HuggingFace download path — before persisting the result.

The path is resolved once here, immediately after validation has established that the file exists, and the resolved form is what every later step sees: the duplicate lookup, the row that gets built, and the model_key derived from it. Normalising at the entry point rather than at each consumer is what keeps those three from disagreeing about which file is which.

§Concurrency

The duplicate check and the insert are separate statements with no transaction around them, and file_path carries no unique index — only model_key does. Two simultaneous adds of one file can therefore both pass the check; the ON CONFLICT(model_key) clause still collapses them onto a single row, so the library stays correct, but the loser is told it added a model that in fact already existed rather than receiving a conflict. Closing that would take a unique index on the path or a transaction spanning both statements, and is not something this guard attempts.

Source

pub async fn find_by_path( &self, file_path: &Path, ) -> Result<Option<Model>, CoreError>

Look up the model registered under file_path, if any.

Resolves the path the same way Self::import_from_file does, so a caller can ask “is this already here?” before doing expensive or interactive work and get the same answer the import would.

§Errors

CoreError::Validation if the path cannot be resolved, and CoreError::Repository if the lookup itself fails. Neither is reported as “no duplicate”.

Source

pub async fn update(&self, model: &Model) -> Result<(), CoreError>

Update a model.

Source

pub async fn delete(&self, id: i64) -> Result<(), CoreError>

Delete a model by ID.

Source

pub async fn list_tags(&self) -> Result<Vec<String>, CoreError>

List all unique tags used across all models.

Source

pub async fn add_tag(&self, model_id: i64, tag: String) -> Result<(), CoreError>

Add a tag to a model.

If the tag already exists on the model, this is a no-op.

Source

pub async fn remove_tag( &self, model_id: i64, tag: &str, ) -> Result<(), CoreError>

Remove a tag from a model.

If the tag doesn’t exist on the model, this is a no-op. System tags (see crate::domain::is_system_tag) are protected and cannot be removed through this API — use Self::remove_tag_force for admin/debug paths that intentionally need to drop them.

Source

pub async fn remove_tag_force( &self, model_id: i64, tag: &str, ) -> Result<(), CoreError>

Force-remove a tag from a model, including system tags.

Bypasses the system-tag protection enforced by Self::remove_tag. Intended for admin/debug paths (e.g. the gglib model retag --full rebuild) where the caller intentionally needs to drop a format:* tag before re-detecting capabilities.

Source

pub async fn get_tags(&self, model_id: i64) -> Result<Vec<String>, CoreError>

Get all tags for a specific model.

Source

pub async fn get_filter_options(&self) -> Result<ModelFilterOptions, CoreError>

Get filter options aggregated from all models.

Returns distinct quantizations, parameter count range, and context length range for use in the GUI filter popover.

Note: Uses in-memory aggregation for simplicity. This is acceptable for typical model libraries (<100 models). Revisit if libraries grow large.

Source

pub async fn bootstrap_capabilities(&self) -> Result<(), CoreError>

Backfill capabilities for models that don’t have them set.

This runs on startup to handle models with unknown capabilities. Only infers if capabilities are empty (0/unknown).

§INVARIANT

Never overwrite explicitly-set capabilities. Only infer when unknown.

Source

pub async fn retag_model( &self, model_id: i64, gguf_parser: &dyn GgufParserPort, full: bool, ) -> Result<Option<RetagDiff>, CoreError>

Re-derive auto-tags for a single model from its persisted GGUF metadata.

full = false (default) is additive: any newly-detected tag that isn’t already present is appended; nothing is ever removed. This is the safe path for backfilling format:* tags on models imported before format-tag detection landed.

full = true performs a full rebuild: every previously auto-generated tag (the predefined capability tag namespace plus every existing format:* tag) is dropped and the freshly-detected set is added in its place. User-curated tags outside that namespace are preserved.

Returns None when the tag set is unchanged (no write occurred) and Some(diff) when the model was updated, carrying the full added/removed delta.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more