package application import ( "context" "fmt" "github.com/mudler/LocalAI/core/backend" "github.com/mudler/LocalAI/core/config" ) // adapterConfig resolves a model name to its runtime ModelConfig, or nil when // unknown. LoadModelConfigFileByNameDefaultOptions never returns nil — for an // unknown name it returns a defaults-filled stub with an empty Name (the YAML // `name:` field is required by Validate), which is how we tell the two apart. func (a *Application) adapterConfig(modelName string) *config.ModelConfig { cfg, err := a.backendLoader.LoadModelConfigFileByNameDefaultOptions(modelName, a.applicationConfig) if err != nil || cfg == nil || cfg.Name == "" { return nil } return cfg } // ModelConfigLookup is the lookup the router middleware's classifier validator // uses to confirm classifier_model declares FLAG_SCORE before binding it. func (a *Application) ModelConfigLookup() func(modelName string) *config.ModelConfig { return a.adapterConfig } // The router-facing factories below (Scorer, Embedder, Reranker, TokenCounter) // bind a model NAME at construction and re-resolve the CONFIG on every call. // Capturing the config at construction would bake in whatever state // adapterConfig saw first — including a stub returned before the YAML reached // bcl.configs (e.g. /import-model or gallery install racing startup). The // classifier registry caches factories by router-config fingerprint, so a // once-stale capture stays stale until the router config is edited. func (a *Application) Scorer(modelName string) backend.Scorer { if a.adapterConfig(modelName) == nil { return nil } return &lazyScorer{app: a, modelName: modelName} } type lazyScorer struct { app *Application modelName string } func (l *lazyScorer) Score(ctx context.Context, prompt string, candidates []string) ([]backend.CandidateScore, error) { cfg := l.app.adapterConfig(l.modelName) if cfg == nil { return nil, fmt.Errorf("scorer: model %q no longer available", l.modelName) } return backend.NewScorer(l.app.modelLoader, *cfg, l.app.applicationConfig).Score(ctx, prompt, candidates) } // TokenCounter returns a func so the middleware's literal field type accepts // it as a method value without importing core/http/middleware from here. func (a *Application) TokenCounter(modelName string) func(string) (int, error) { if a.adapterConfig(modelName) == nil { return nil } return func(text string) (int, error) { cfg := a.adapterConfig(modelName) if cfg == nil { return 0, fmt.Errorf("token counter: model %q no longer available", modelName) } resp, err := backend.ModelTokenize(text, a.modelLoader, *cfg, a.applicationConfig) if err != nil { return 0, err } return len(resp.Tokens), nil } } func (a *Application) Reranker(modelName string) backend.Reranker { if a.adapterConfig(modelName) == nil { return nil } return &lazyReranker{app: a, modelName: modelName} } type lazyReranker struct { app *Application modelName string } func (l *lazyReranker) Rerank(ctx context.Context, query string, documents []string) ([]backend.RerankResult, error) { cfg := l.app.adapterConfig(l.modelName) if cfg == nil { return nil, fmt.Errorf("reranker: model %q no longer available", l.modelName) } return backend.NewReranker(l.app.modelLoader, *cfg, l.app.applicationConfig).Rerank(ctx, query, documents) } func (a *Application) Embedder(modelName string) backend.Embedder { if a.adapterConfig(modelName) == nil { return nil } return &lazyEmbedder{app: a, modelName: modelName} } type lazyEmbedder struct { app *Application modelName string } func (l *lazyEmbedder) Embed(ctx context.Context, text string) ([]float32, error) { cfg := l.app.adapterConfig(l.modelName) if cfg == nil { return nil, fmt.Errorf("embedder: model %q no longer available", l.modelName) } return backend.NewEmbedder(l.app.modelLoader, *cfg, l.app.applicationConfig).Embed(ctx, text) } // VectorStore takes a store name, not a model name — no adapterConfig, no // staleness to avoid. func (a *Application) VectorStore(storeName string) backend.VectorStore { return backend.NewVectorStore(a.modelLoader, a.applicationConfig, storeName) }