mirror of
https://github.com/mudler/LocalAI.git
synced 2026-07-08 15:28:23 -04:00
Additive superset of /v1/models that enriches each model entry with the capabilities it supports plus its input/output modalities (text / image / audio / video). Clients that only understand /v1/models are unaffected -- they simply never call the new route. Audio and video *input* are derived from the model's multimodal limits (vLLM limit_mm_per_prompt), which no single usecase FLAG expresses. That gap is exactly why a plain capability list is insufficient and this enriched endpoint exists: an attachment router can now decide whether an image/audio/video file can go to the active model directly, or must be converted/transcribed first. Capability derivation lives in core/config as the single source of truth (ModelConfig.Capabilities / InputModalities / OutputModalities / VisionSupported / ...); the Ollama capability surface now delegates to it instead of keeping a parallel copy. Vision is gated on chat/completion capability so a MediaMarker hydrated onto a non-chat model (e.g. a pure ASR/TTS backend) no longer reports a false vision capability. Read-only listing: no new FLAG_* flag, reuses the existing `models` swagger tag, and intentionally exposes no MCP admin tool (there is nothing to manage conversationally). Assisted-by: Claude:claude-opus-4-8 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
104 lines
3.2 KiB
Go
104 lines
3.2 KiB
Go
package ollama
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/mudler/LocalAI/core/config"
|
|
)
|
|
|
|
// modelCapabilities maps a LocalAI ModelConfig to the Ollama capability strings
|
|
// (https://github.com/ollama/ollama/blob/main/docs/api.md#show-model-information).
|
|
//
|
|
// Ollama clients use these to decide which models are eligible for a given task
|
|
// (e.g. only allow embedding models in an "embedding model" picker). Returning
|
|
// an empty list makes clients assume "completion" everywhere, which is wrong
|
|
// for embedding/rerank/audio backends — see issue #9760.
|
|
func modelCapabilities(cfg *config.ModelConfig) []string {
|
|
if cfg == nil {
|
|
return nil
|
|
}
|
|
|
|
var caps []string
|
|
|
|
if cfg.HasUsecases(config.FLAG_EMBEDDINGS) {
|
|
caps = append(caps, "embedding")
|
|
}
|
|
|
|
chatCapable := cfg.HasUsecases(config.FLAG_CHAT) || cfg.HasUsecases(config.FLAG_COMPLETION)
|
|
if chatCapable {
|
|
caps = append(caps, "completion")
|
|
}
|
|
|
|
if chatCapable && hasVisionSupport(cfg) {
|
|
caps = append(caps, "vision")
|
|
}
|
|
|
|
if chatCapable && hasToolSupport(cfg) {
|
|
caps = append(caps, "tools")
|
|
}
|
|
|
|
if chatCapable && hasThinkingSupport(cfg) {
|
|
caps = append(caps, "thinking")
|
|
}
|
|
|
|
if chatCapable && cfg.TemplateConfig.Completion != "" {
|
|
caps = append(caps, "insert")
|
|
}
|
|
|
|
return caps
|
|
}
|
|
|
|
// hasVisionSupport reports whether the model can accept image inputs.
|
|
// The detection heuristic is the canonical config.ModelConfig.VisionSupported —
|
|
// kept as a thin wrapper here so the Ollama capability mapping reads cleanly.
|
|
func hasVisionSupport(cfg *config.ModelConfig) bool {
|
|
return cfg.VisionSupported()
|
|
}
|
|
|
|
// hasToolSupport reports whether the model is wired up for tool / function
|
|
// calling. Delegates to the canonical config.ModelConfig.ToolSupported.
|
|
func hasToolSupport(cfg *config.ModelConfig) bool {
|
|
return cfg.ToolSupported()
|
|
}
|
|
|
|
// hasThinkingSupport reports whether the model has reasoning / thinking enabled.
|
|
// Delegates to the canonical config.ModelConfig.ThinkingSupported.
|
|
func hasThinkingSupport(cfg *config.ModelConfig) bool {
|
|
return cfg.ThinkingSupported()
|
|
}
|
|
|
|
// quantRegex matches GGUF-style quantization suffixes (Q4_K_M, Q8_0, IQ3_XS, F16, ...).
|
|
// Matches the convention used by GGUF tooling and what ggml-org/llama.cpp report.
|
|
var quantRegex = regexp.MustCompile(`(?i)(IQ\d+(?:_[A-Z0-9]+)*|Q\d+(?:_[A-Z0-9]+)*|F16|F32|BF16)`)
|
|
|
|
// paramSizeRegex matches a parameter-size token surrounded by separators
|
|
// (e.g. "-7B-", "_3b.", ".70B-"). Avoids matching the "7" inside "Qwen3".
|
|
var paramSizeRegex = regexp.MustCompile(`(?i)(?:^|[-_.])(\d+(?:\.\d+)?[BM])(?:[-_.]|$)`)
|
|
|
|
// extractQuantizationLevel pulls the quantization tag from the model filename.
|
|
// Returns the uppercased token (e.g. "Q4_K_M") or "" when not present.
|
|
func extractQuantizationLevel(modelFile string) string {
|
|
if modelFile == "" {
|
|
return ""
|
|
}
|
|
base := strings.TrimSuffix(modelFile, ".gguf")
|
|
if m := quantRegex.FindString(base); m != "" {
|
|
return strings.ToUpper(m)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// extractParameterSize pulls the parameter count from the model filename.
|
|
// Returns "" when no recognizable token is present.
|
|
func extractParameterSize(modelFile string) string {
|
|
if modelFile == "" {
|
|
return ""
|
|
}
|
|
base := strings.TrimSuffix(modelFile, ".gguf")
|
|
if m := paramSizeRegex.FindStringSubmatch(base); len(m) > 1 {
|
|
return strings.ToUpper(m[1])
|
|
}
|
|
return ""
|
|
}
|