mirror of
https://github.com/mudler/LocalAI.git
synced 2026-07-30 01:48:06 -04:00
* feat(3d): add Generate3D RPC, FLAG_3D capability, and /v1/3d/generations endpoint Adds the plumbing for image-conditioned 3D asset generation (binary glTF / GLB output), modeled on the video generation path: - backend.proto: Generate3D RPC + Generate3DRequest (staged image src, glb dst, seed/step/cfg_scale/texture_steps, quality and background enums, params map for backend-specific extras) - pkg/grpc: thread Generate3D through client, server, embed, base and the backend interfaces; connection-evicting and distributed-node wrappers (in-flight tracking + file staging) included - core/config: FLAG_3D usecase (guessed only for the trellis2cpp backend), '3d' canonical usecase string mapped to the Generate3D method, and a '3d' output modality - REST: POST /v1/3d/generations (+ unversioned alias) returning OpenAIResponse with a /generated-3d URL or b64_json; conditioning image accepted as URL, base64, or data URI; quality/background validated at the edge; .glb served as model/gltf-binary - auth: '3d' route feature (default ON); /api/instructions entry Assisted-by: Claude:claude-fable-5 [Claude Code] Signed-off-by: Richard Palethorpe <io@richiejp.com> * feat(trellis2cpp): add the trellis2.cpp image-to-3D backend Wraps localai-org/trellis2cpp (C++/GGML port of Microsoft TRELLIS.2, pbr-textures branch) as a Go+purego backend, following the stablediffusion-ggml pattern: - backend/go/trellis2cpp: purego bindings to the flat C ABI (v9, asserted at startup), eager pipeline load with model-set validation (refuses non-trellis GGUFs; degrades coarse/geometry-only/textured exactly like the upstream demo), Generate3D via t2_generate + t2_bake_glb writing a binary glTF to dst. Weight-free unit tests cover resolution/validation/param mapping — CI never downloads the multi-GB GGUF set or runs inference. - CPU SIMD variants build into per-variant directories (the shared libggml sonames collide across variants, unlike sd-ggml's flat renamed-.so scheme); run.sh picks one via /proc/cpuinfo. - CI wiring: backend-matrix entries (cpu, cuda12/13, vulkan amd64+arm64, l4t, l4t-cuda13, darwin metal), index.yaml meta + latest/master image entries, bump_deps tracking of the pbr-textures branch, changed-backends.js mapping, top-level Makefile targets. - Importer: auto-detects trellis GGUF repos/URIs (registered before llama-cpp so the .gguf match isn't stolen) and expands any trellis URI to the full 10-file component set spanning the three LocalAI-io HF repos. - Gallery: trellis2-4b (full PBR + 1024 cascade) and trellis2-4b-geometry (512 untextured) with verified sha256s. Assisted-by: Claude:claude-fable-5 [Claude Code] Signed-off-by: Richard Palethorpe <io@richiejp.com> * feat(ui): 3D generation page with native GLB viewer and IndexedDB history Adds a Studio tab + /app/3d page for the new image-to-3D endpoint: - GlbViewer ports the trellis2cpp demo's dependency-free WebGL2 renderer (quaternion trackball, metallic-roughness PBR, ACES, hidden-line wireframe with a bounded index budget) and pairs it with a minimal GLB parser for the two forms t2_bake_glb emits — dense vertex-PBR (linear COLOR_0 + _METALLIC_ROUGHNESS, uploaded as normalized integers) and the opt-in UV-atlas textured form. Parsing happens before any GL so stats and errors render without WebGL2. - use3DHistory stores past generations (params, input thumbnail, and the GLB blob itself) in IndexedDB with keep-newest-20 eviction — GLBs are multi-MB binaries localStorage can't hold — and the page offers a download button for the active GLB. - Wiring: CAP_3D capability constant (FLAG_3D — the exact string /api/models/capabilities serves), threeDApi, router entries, Studio tab, vite dev proxy, en locale keys. - e2e: render-smoke entry plus a focused spec that feeds a real one-triangle vertex-PBR GLB through the parser/viewer and exercises IndexedDB persistence, selection, deletion, and API errors. Assisted-by: Claude:claude-fable-5 [Claude Code] Signed-off-by: Richard Palethorpe <io@richiejp.com> * fix(3d): address API correctness and UX issues Keep 3D generation on the LocalAI-specific /3d/generations route and ensure authentication and permissions cover it. Propagate distributed transfer failures, publish a portable ARM64 backend image, honor importer overrides, and align discovery, upload validation, and touch controls. Assisted-by: Codex:gpt-5 Signed-off-by: Richard Palethorpe <io@richiejp.com> * feat(3d): add previewable print remeshing Add a single-detail CGAL Alpha Wrap workflow for existing Trellis GLBs, including PBR reprojection, API documentation, tracing, and an in-browser preview before download. Allow the remesh route to enforce its 512 MiB upload cap independently of the smaller global default so generated high-resolution meshes can be processed. Assisted-by: Codex:gpt-5 Signed-off-by: Richard Palethorpe <io@richiejp.com> * build(trellis2cpp): centralize remesh dependency pins Assisted-by: Codex:GPT-5 [apply_patch] [exec_command] Signed-off-by: Richard Palethorpe <io@richiejp.com> * fix(kokoros): implement Generate3D stub for new proto RPC The Generate3D RPC added to backend.proto for the trellis2cpp backend made tonic's generated Backend trait require generate3_d, breaking the kokoros-grpc build. Return unimplemented like the other unsupported modalities. Assisted-by: Claude Code:claude-fable-5 Signed-off-by: Richard Palethorpe <io@richiejp.com> --------- Signed-off-by: Richard Palethorpe <io@richiejp.com> Co-authored-by: localai-org-maint-bot <bot-opensource@localaisrl.com>
223 lines
9.1 KiB
Go
223 lines
9.1 KiB
Go
package config
|
|
|
|
import "slices"
|
|
|
|
// This file is the single source of truth for deriving a model's user-facing
|
|
// capabilities and input/output modalities from its ModelConfig. Both the
|
|
// OpenAI-compatible /v1/models/capabilities endpoint and the Ollama-compatible
|
|
// /api/tags|/api/show surface consume these, so the vocabulary stays consistent
|
|
// across clients. Keep the detection heuristics here rather than duplicating
|
|
// them per endpoint.
|
|
|
|
// Canonical model modality values used by config declarations and discovery APIs.
|
|
const (
|
|
ModalityText = "text"
|
|
ModalityImage = "image"
|
|
ModalityAudio = "audio"
|
|
ModalityVideo = "video"
|
|
Modality3D = "3d"
|
|
)
|
|
|
|
var modalityOrder = []string{ModalityText, ModalityImage, ModalityAudio, ModalityVideo, Modality3D}
|
|
|
|
func declaredModalities(modalities []string) map[string]bool {
|
|
declared := make(map[string]bool, len(modalities))
|
|
for _, modality := range modalities {
|
|
if slices.Contains(modalityOrder, modality) {
|
|
declared[modality] = true
|
|
}
|
|
}
|
|
return declared
|
|
}
|
|
|
|
func orderedModalities(modalities map[string]bool) []string {
|
|
result := make([]string, 0, len(modalityOrder))
|
|
for _, modality := range modalityOrder {
|
|
if modalities[modality] {
|
|
result = append(result, modality)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// VisionSupported reports whether the model can accept image inputs.
|
|
//
|
|
// We deliberately avoid HasUsecases(FLAG_VISION): GuessUsecases has no
|
|
// FLAG_VISION branch and reports true for any chat model, so it would paint
|
|
// vision onto text-only models. Instead we look for explicit signals: the
|
|
// declared input modality or KnownUsecases bit, a multimodal projector, or a
|
|
// template/backend multimodal marker.
|
|
func (c *ModelConfig) VisionSupported() bool {
|
|
if slices.Contains(c.KnownInputModalities, ModalityImage) {
|
|
return true
|
|
}
|
|
if c.KnownUsecases != nil && (*c.KnownUsecases&FLAG_VISION) == FLAG_VISION {
|
|
return true
|
|
}
|
|
if c.MMProj != "" {
|
|
return true
|
|
}
|
|
if c.TemplateConfig.Multimodal != "" {
|
|
return true
|
|
}
|
|
if c.MediaMarker != "" {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ToolSupported reports whether the model is wired up for tool / function
|
|
// calling. We look for any of the explicit knobs LocalAI uses to drive
|
|
// function-call extraction (regex match, response regex, grammar triggers, XML
|
|
// format) or the auto-detected tool-format markers the llama.cpp backend
|
|
// populates during model load.
|
|
func (c *ModelConfig) ToolSupported() bool {
|
|
fc := c.FunctionsConfig
|
|
if fc.ToolFormatMarkers != nil && fc.ToolFormatMarkers.FormatType != "" {
|
|
return true
|
|
}
|
|
if len(fc.JSONRegexMatch) > 0 || len(fc.ResponseRegex) > 0 {
|
|
return true
|
|
}
|
|
if fc.XMLFormatPreset != "" || fc.XMLFormat != nil {
|
|
return true
|
|
}
|
|
if len(fc.GrammarConfig.GrammarTriggers) > 0 || fc.GrammarConfig.SchemaType != "" {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ThinkingSupported reports whether the model has reasoning / thinking enabled.
|
|
// LocalAI sets DisableReasoning=false (or leaves thinking markers configured)
|
|
// when the backend probe reports that the model supports thinking.
|
|
func (c *ModelConfig) ThinkingSupported() bool {
|
|
rc := c.ReasoningConfig
|
|
if rc.DisableReasoning != nil && !*rc.DisableReasoning {
|
|
return true
|
|
}
|
|
if len(rc.ThinkingStartTokens) > 0 || len(rc.TagPairs) > 0 {
|
|
// Explicit thinking markers imply support unless explicitly disabled.
|
|
return rc.DisableReasoning == nil || !*rc.DisableReasoning
|
|
}
|
|
return false
|
|
}
|
|
|
|
// AudioInputSupported reports whether a chat/generation model accepts audio as
|
|
// input. Model configs can declare this directly; vLLM-family configs can also
|
|
// signal it through the per-prompt audio limit. Transcription models are
|
|
// handled separately in InputModalities via FLAG_TRANSCRIPT.
|
|
func (c *ModelConfig) AudioInputSupported() bool {
|
|
return slices.Contains(c.KnownInputModalities, ModalityAudio) ||
|
|
c.LimitMMPerPrompt.LimitAudioPerPrompt > 0
|
|
}
|
|
|
|
// VideoInputSupported reports whether a chat/generation model accepts video as
|
|
// input. Model configs can declare this directly; vLLM-family configs can also
|
|
// signal it through the per-prompt video limit. This is distinct from
|
|
// FLAG_VIDEO, which denotes video generation — an output modality.
|
|
func (c *ModelConfig) VideoInputSupported() bool {
|
|
return slices.Contains(c.KnownInputModalities, ModalityVideo) ||
|
|
c.LimitMMPerPrompt.LimitVideoPerPrompt > 0
|
|
}
|
|
|
|
// Capabilities returns the ordered list of capability strings the model
|
|
// supports, using the canonical usecase vocabulary (chat, vision, transcript,
|
|
// tts, embeddings, image, video, ...) plus the modifier capabilities "tools"
|
|
// and "thinking". Vision is resolved via VisionSupported (not HasUsecases) to
|
|
// avoid the guess-heuristic false positive.
|
|
func (c *ModelConfig) Capabilities() []string {
|
|
chat := c.HasUsecases(FLAG_CHAT)
|
|
completion := c.HasUsecases(FLAG_COMPLETION)
|
|
|
|
var caps []string
|
|
add := func(cond bool, name string) {
|
|
if cond {
|
|
caps = append(caps, name)
|
|
}
|
|
}
|
|
|
|
add(chat, UsecaseChat)
|
|
add(completion, UsecaseCompletion)
|
|
add(c.HasUsecases(FLAG_EDIT), UsecaseEdit)
|
|
add(c.HasUsecases(FLAG_EMBEDDINGS), UsecaseEmbeddings)
|
|
add(c.HasUsecases(FLAG_RERANK), UsecaseRerank)
|
|
// Vision is only meaningful as an image-understanding modifier on a chat/
|
|
// completion model. Gating on (chat||completion) matches the Ollama surface
|
|
// and avoids a false positive when config defaults hydrate a MediaMarker on
|
|
// a non-chat model (e.g. a pure ASR/TTS backend).
|
|
add((chat || completion) && c.VisionSupported(), UsecaseVision)
|
|
// tools/thinking are modifiers on the chat/completion surface.
|
|
add((chat || completion) && c.ToolSupported(), "tools")
|
|
add((chat || completion) && c.ThinkingSupported(), "thinking")
|
|
add(c.HasUsecases(FLAG_TRANSCRIPT), UsecaseTranscript)
|
|
add(c.HasUsecases(FLAG_TTS), UsecaseTTS)
|
|
add(c.HasUsecases(FLAG_SOUND_GENERATION), UsecaseSoundGeneration)
|
|
add(c.HasUsecases(FLAG_IMAGE), UsecaseImage)
|
|
add(c.HasUsecases(FLAG_VIDEO), UsecaseVideo)
|
|
add(c.HasUsecases(FLAG_3D), Usecase3D)
|
|
add(c.HasUsecases(FLAG_VAD), UsecaseVAD)
|
|
add(c.HasUsecases(FLAG_DETECTION), UsecaseDetection)
|
|
add(c.HasUsecases(FLAG_DEPTH), UsecaseDepth)
|
|
add(c.HasUsecases(FLAG_AUDIO_TRANSFORM), UsecaseAudioTransform)
|
|
add(c.HasUsecases(FLAG_DIARIZATION), UsecaseDiarization)
|
|
add(c.HasUsecases(FLAG_SOUND_CLASSIFICATION), UsecaseSoundClassification)
|
|
add(c.HasUsecases(FLAG_REALTIME_AUDIO), UsecaseRealtimeAudio)
|
|
add(c.HasUsecases(FLAG_FACE_RECOGNITION), UsecaseFaceRecognition)
|
|
add(c.HasUsecases(FLAG_SPEAKER_RECOGNITION), UsecaseSpeakerRecognition)
|
|
return caps
|
|
}
|
|
|
|
// InputModalities returns the set of modalities (text, image, audio, video) the
|
|
// model accepts as input, ordered text→image→audio→video. This is what an
|
|
// attachment router consults to decide whether an image/audio/video file can be
|
|
// handed to the active model directly.
|
|
func (c *ModelConfig) InputModalities() []string {
|
|
modalities := declaredModalities(c.KnownInputModalities)
|
|
imageGen := c.HasUsecases(FLAG_IMAGE)
|
|
videoGen := c.HasUsecases(FLAG_VIDEO)
|
|
chatish := c.HasUsecases(FLAG_CHAT) || c.HasUsecases(FLAG_COMPLETION)
|
|
|
|
textIn := chatish || c.HasUsecases(FLAG_EDIT) ||
|
|
c.HasUsecases(FLAG_EMBEDDINGS) || c.HasUsecases(FLAG_RERANK) || c.HasUsecases(FLAG_TOKENIZE) ||
|
|
c.HasUsecases(FLAG_TTS) || c.HasUsecases(FLAG_SOUND_GENERATION) || imageGen || videoGen
|
|
|
|
// Image input via a chat model requires vision (gated on chat, like the
|
|
// Ollama surface); detection/depth/face/3D models consume images directly.
|
|
imageIn := (chatish && c.VisionSupported()) || c.LimitMMPerPrompt.LimitImagePerPrompt > 0 ||
|
|
c.HasUsecases(FLAG_DETECTION) || c.HasUsecases(FLAG_DEPTH) || c.HasUsecases(FLAG_FACE_RECOGNITION) ||
|
|
c.HasUsecases(FLAG_3D)
|
|
|
|
audioIn := c.AudioInputSupported() || c.HasUsecases(FLAG_TRANSCRIPT) || c.HasUsecases(FLAG_AUDIO_TRANSFORM) ||
|
|
c.HasUsecases(FLAG_REALTIME_AUDIO) || c.HasUsecases(FLAG_VAD) || c.HasUsecases(FLAG_DIARIZATION) ||
|
|
c.HasUsecases(FLAG_SOUND_CLASSIFICATION) || c.HasUsecases(FLAG_SPEAKER_RECOGNITION)
|
|
|
|
videoIn := c.VideoInputSupported()
|
|
|
|
modalities[ModalityText] = modalities[ModalityText] || textIn
|
|
modalities[ModalityImage] = modalities[ModalityImage] || imageIn
|
|
modalities[ModalityAudio] = modalities[ModalityAudio] || audioIn
|
|
modalities[ModalityVideo] = modalities[ModalityVideo] || videoIn
|
|
return orderedModalities(modalities)
|
|
}
|
|
|
|
// OutputModalities returns the set of modalities (text, image, audio, video)
|
|
// the model produces, ordered text→image→audio→video.
|
|
func (c *ModelConfig) OutputModalities() []string {
|
|
modalities := declaredModalities(c.KnownOutputModalities)
|
|
textOut := c.HasUsecases(FLAG_CHAT) || c.HasUsecases(FLAG_COMPLETION) || c.HasUsecases(FLAG_EDIT) ||
|
|
c.HasUsecases(FLAG_TRANSCRIPT)
|
|
imageOut := c.HasUsecases(FLAG_IMAGE)
|
|
audioOut := c.HasUsecases(FLAG_TTS) || c.HasUsecases(FLAG_SOUND_GENERATION) ||
|
|
c.HasUsecases(FLAG_AUDIO_TRANSFORM) || c.HasUsecases(FLAG_REALTIME_AUDIO)
|
|
videoOut := c.HasUsecases(FLAG_VIDEO)
|
|
threeDOut := c.HasUsecases(FLAG_3D)
|
|
|
|
modalities[ModalityText] = modalities[ModalityText] || textOut
|
|
modalities[ModalityImage] = modalities[ModalityImage] || imageOut
|
|
modalities[ModalityAudio] = modalities[ModalityAudio] || audioOut
|
|
modalities[ModalityVideo] = modalities[ModalityVideo] || videoOut
|
|
modalities[Modality3D] = modalities[Modality3D] || threeDOut
|
|
return orderedModalities(modalities)
|
|
}
|