mirror of
https://github.com/mudler/LocalAI.git
synced 2026-10-28 09:08:54 -04:00
* feat(grpc): add a Forecast RPC for time-series models Add Forecast to the backend service with columnar request and response messages: flat row-major arrays, NaN for missing values, and one message per series so a caller can send series of different shapes in one call. The response keeps the request order. Wire the RPC through the server, client, embedded client, connection evicting client, in-flight tracking client and the base backend, which answers unimplemented. core/backend gains a Forecast helper that stamps the model identity and records a trace, like the other single-purpose RPCs. Assisted-by: Claude:claude-sonnet-5-5 go-vet * feat(config): add the forecast usecase FLAG_FORECAST marks a model as a time-series forecaster. The timestral-cpp backend declares it as its default usecase, so a config with that backend needs no extra lines. The backend is also listed as a non-text backend, so a forecast model is never offered as a chat model. It shows up in the gallery filter and in the capability list. Assisted-by: Claude:claude-sonnet-5-5 go-vet * feat(api): add POST /v1/forecast Take one or more numeric series and return quantile trajectories for the next prediction_length steps. Targets are [variates][steps] or a flat array for one variate; JSON null marks a missing value. Past and known covariates and categorical codes are checked for length before the backend sees them. Quantiles default to 0.1, 0.5 and 0.9 in the backend. The route is gated by a new default-on forecast API feature. The endpoint is listed in the swagger spec, the /api/instructions index, the UI model filter and the capability symbols. Assisted-by: Claude:claude-sonnet-5-5 go-vet * feat(backends): add the timestral-cpp forecast backend Wrap the timestral.cpp C ABI with purego, the way the other ggml backends do: libtimestral is built from a pinned commit with ggml embedded, and the Go binary loads it at start. Forecast validates the counts against the array lengths, groups the series by shape, makes one library call per group and puts the results back in the order the caller sent them. Quantiles default to 0.1, 0.5 and 0.9, and a level the model does not have is refused with the list of valid ones. A mutex guards the handle because the library is not reentrant. Request size is bounded by two model options. The unit specs use a fake library. The integration specs and a gRPC spec run against the 5M F32 model that test.sh downloads and checks against a fixed SHA-256. Assisted-by: Claude:claude-sonnet-5-5 go-vet * build(backends): register timestral-cpp in the build and the library Add the CPU image to the backend matrix for amd64 and arm64, plus an Apple silicon entry that is not built or tested yet. The image has no GPU keys because the engine runs on the CPU only. Wire the make targets, the path filter and the dependency bump bot. The library entries were generated with backendcatalog. The images are not published yet, so the release entries are unpinned until a maintainer runs --repin. The gallery reader test now counts the names in the library instead of a fixed number, so adding a backend does not break it. Assisted-by: Claude:claude-sonnet-5-5 go-vet * feat(gallery): add the Timestral forecast models Add Timestral 5M, 28M, 123M and 209M as F32 entries, each with its F16 build as a variant. Q8_0 and the K-quants are left out until the quality question is settled. Each entry runs on the timestral-cpp backend, on the CPU only. The weights use Mistral's Modified MIT licence, which bars use by companies above a monthly revenue threshold. The first paragraph after the summary says so, the licence file is installed next to the weights, and the revenue-restricted-license tag makes the entries filterable. The memory each build needs has not been measured, so no size is set. Assisted-by: Claude:claude-sonnet-5-5 * docs(forecast): document /v1/forecast and the Timestral models Add a feature page with the request and response shapes, the error cases, the model options and the licence note. List the engine in the README, the compatibility table and the backend categories. Assisted-by: Claude:claude-sonnet-5-5 --------- Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
806 lines
36 KiB
Go
806 lines
36 KiB
Go
package schema
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"time"
|
||
|
||
gopsutil "github.com/shirou/gopsutil/v3/process"
|
||
)
|
||
|
||
type BackendMonitorRequest struct {
|
||
BasicModelRequest
|
||
}
|
||
|
||
// ModelLoadRequest asks LocalAI to pre-load a model into memory by name, so the
|
||
// first request that uses it pays no cold-start load cost. For a realtime
|
||
// pipeline model, every configured sub-model (VAD, transcription, LLM, TTS,
|
||
// sound_detection, voice_recognition) is loaded instead of the pipeline stub.
|
||
// It is the inverse of the /backend/shutdown request.
|
||
type ModelLoadRequest struct {
|
||
BasicModelRequest
|
||
}
|
||
|
||
// ModelLoadResponse reports the outcome of a /backend/load call.
|
||
type ModelLoadResponse struct {
|
||
// Loaded lists the model names actually resident in memory after the call.
|
||
// For a pipeline model these are its sub-models, not the pipeline name.
|
||
Loaded []string `json:"loaded"`
|
||
// Message is a short human-readable status ("model loaded", or an error).
|
||
Message string `json:"message"`
|
||
}
|
||
|
||
type TokenMetricsRequest struct {
|
||
BasicModelRequest
|
||
}
|
||
|
||
type BackendMonitorResponse struct {
|
||
MemoryInfo *gopsutil.MemoryInfoStat
|
||
MemoryPercent float32
|
||
CPUPercent float64
|
||
}
|
||
|
||
type GalleryResponse struct {
|
||
ID string `json:"uuid"`
|
||
StatusURL string `json:"status"`
|
||
|
||
EstimatedVRAMBytes uint64 `json:"estimated_vram_bytes,omitempty"`
|
||
EstimatedVRAMDisplay string `json:"estimated_vram_display,omitempty"`
|
||
EstimatedSizeBytes uint64 `json:"estimated_size_bytes,omitempty"`
|
||
EstimatedSizeDisplay string `json:"estimated_size_display,omitempty"`
|
||
}
|
||
|
||
// VideoReference is one reference video, given as an ordered list of frames.
|
||
type VideoReference struct {
|
||
Frames []string `json:"frames" yaml:"frames"` // ordered frame images: URL, base64 or data URI
|
||
FPS int32 `json:"fps,omitempty" yaml:"fps,omitempty"` // frame rate of the source frames; 0 means the backend default
|
||
Audio string `json:"audio,omitempty" yaml:"audio,omitempty"` // optional audio paired with this video
|
||
}
|
||
|
||
type VideoRequest struct {
|
||
BasicModelRequest
|
||
Prompt string `json:"prompt" yaml:"prompt"` // text description of the video to generate
|
||
NegativePrompt string `json:"negative_prompt" yaml:"negative_prompt"` // things to avoid in the output
|
||
StartImage string `json:"start_image" yaml:"start_image"` // URL or base64 of the first frame
|
||
EndImage string `json:"end_image" yaml:"end_image"` // URL or base64 of the last frame
|
||
Audio string `json:"audio,omitempty" yaml:"audio,omitempty"` // URL or base64 audio for audio-conditioned generation
|
||
RefImages []string `json:"ref_images,omitempty" yaml:"ref_images,omitempty"` // reference images (URL, base64 or data URI); exclusive with start_image/end_image
|
||
RefVideos []VideoReference `json:"ref_videos,omitempty" yaml:"ref_videos,omitempty"` // reference videos as frame lists; exclusive with start_image/end_image
|
||
RefAudios []string `json:"ref_audios,omitempty" yaml:"ref_audios,omitempty"` // reference audio clips (WAV); exclusive with start_image/end_image
|
||
Width int32 `json:"width" yaml:"width"` // output width in pixels
|
||
Height int32 `json:"height" yaml:"height"` // output height in pixels
|
||
NumFrames int32 `json:"num_frames" yaml:"num_frames"` // total number of frames to generate
|
||
FPS int32 `json:"fps" yaml:"fps"` // frames per second
|
||
Seconds string `json:"seconds,omitempty" yaml:"seconds,omitempty"` // duration in seconds (alternative to num_frames)
|
||
Size string `json:"size,omitempty" yaml:"size,omitempty"` // WxH shorthand (e.g. "512x512")
|
||
InputReference string `json:"input_reference,omitempty" yaml:"input_reference,omitempty"` // reference image or video URL
|
||
Seed int32 `json:"seed" yaml:"seed"` // random seed for reproducibility
|
||
CFGScale float32 `json:"cfg_scale" yaml:"cfg_scale"` // classifier-free guidance scale
|
||
Step int32 `json:"step" yaml:"step"` // number of diffusion steps
|
||
ResponseFormat string `json:"response_format" yaml:"response_format"` // output format (url or b64_json)
|
||
Params map[string]string `json:"params,omitempty" yaml:"params,omitempty"` // backend-specific generation parameters
|
||
}
|
||
|
||
// @Description 3D asset generation request body. Generation is image-conditioned
|
||
// (TRELLIS.2 has no text-prompt path); the response is a binary glTF (.glb).
|
||
type Model3DRequest struct {
|
||
BasicModelRequest
|
||
Image string `json:"image" yaml:"image"` // conditioning image: URL, base64, or data URI (required)
|
||
Seed int32 `json:"seed,omitempty" yaml:"seed,omitempty"` // random seed; <=0 picks a random seed
|
||
Step int32 `json:"step,omitempty" yaml:"step,omitempty"` // flow sampling steps (backend default 12)
|
||
CFGScale float32 `json:"cfg_scale,omitempty" yaml:"cfg_scale,omitempty"` // classifier-free guidance scale (backend default 7.5)
|
||
TextureSteps int32 `json:"texture_steps,omitempty" yaml:"texture_steps,omitempty"` // texture flow sampling steps (backend default 12)
|
||
Quality string `json:"quality,omitempty" yaml:"quality,omitempty"` // mesh pipeline: auto|coarse|512|1024
|
||
Background string `json:"background,omitempty" yaml:"background,omitempty"` // background handling: auto|keep|black|white
|
||
ResponseFormat string `json:"response_format,omitempty" yaml:"response_format,omitempty"` // output format (url or b64_json)
|
||
Params map[string]string `json:"params,omitempty" yaml:"params,omitempty"` // backend-specific generation parameters
|
||
}
|
||
|
||
// @Description Print-remesh an existing trellis2.cpp GLB. The multipart mesh
|
||
// is wrapped into a watertight manifold; detail is a percentage of the source
|
||
// bounding-box diagonal and the enclosing offset is derived automatically.
|
||
type Model3DRemeshRequest struct {
|
||
BasicModelRequest
|
||
Detail float32 `json:"detail,omitempty" yaml:"detail,omitempty" form:"detail"` // detail size in percent (0.35–2.5; default 0.5)
|
||
}
|
||
|
||
// @Description TTS request body
|
||
type TTSRequest struct {
|
||
BasicModelRequest
|
||
Input string `json:"input" yaml:"input"` // text input
|
||
Voice string `json:"voice" yaml:"voice"` // voice audio file or speaker id
|
||
Backend string `json:"backend" yaml:"backend"` // backend engine override
|
||
Language string `json:"language,omitempty" yaml:"language,omitempty"` // (optional) language to use with TTS model
|
||
Format string `json:"response_format,omitempty" yaml:"response_format,omitempty"` // (optional) output format
|
||
Stream bool `json:"stream,omitempty" yaml:"stream,omitempty"` // (optional) enable streaming TTS
|
||
SampleRate int `json:"sample_rate,omitempty" yaml:"sample_rate,omitempty"` // (optional) desired output sample rate
|
||
// Speed is the OpenAI `speed` field (0.25-4.0). It is a pointer so an
|
||
// explicit `"speed": 0` (invalid, rejected with 400) is distinguishable
|
||
// from an omitted field (left at the backend default). It is normalised
|
||
// into Params["speed"] so it reaches the backend over the same channel as
|
||
// the other per-request generation parameters.
|
||
Speed *float32 `json:"speed,omitempty" yaml:"speed,omitempty"`
|
||
// Instructions is a free-form, per-request style/voice description. It maps to
|
||
// the OpenAI `instructions` field and is forwarded to the backend so expressive
|
||
// TTS models (e.g. Qwen3-TTS CustomVoice/VoiceDesign) can vary tone or designed
|
||
// voice per request instead of only via the static YAML option.
|
||
Instructions string `json:"instructions,omitempty" yaml:"instructions,omitempty"`
|
||
// Params carries optional, backend-specific per-request generation parameters
|
||
// (LocalAI extension, e.g. Chatterbox exaggeration/cfg_weight/temperature).
|
||
Params map[string]string `json:"params,omitempty" yaml:"params,omitempty"`
|
||
}
|
||
|
||
// @Description VAD request body
|
||
type VADRequest struct {
|
||
BasicModelRequest
|
||
Audio []float32 `json:"audio" yaml:"audio"` // raw audio samples as float32 PCM
|
||
}
|
||
|
||
type VADSegment struct {
|
||
Start float32 `json:"start" yaml:"start"`
|
||
End float32 `json:"end" yaml:"end"`
|
||
}
|
||
|
||
type VADResponse struct {
|
||
Segments []VADSegment `json:"segments" yaml:"segments"`
|
||
}
|
||
|
||
type StoreCommon struct {
|
||
Backend string `json:"backend,omitempty" yaml:"backend,omitempty"`
|
||
}
|
||
type StoresSet struct {
|
||
Store string `json:"store,omitempty" yaml:"store,omitempty"`
|
||
|
||
Keys [][]float32 `json:"keys" yaml:"keys"`
|
||
Values []string `json:"values" yaml:"values"`
|
||
StoreCommon
|
||
}
|
||
|
||
type StoresDelete struct {
|
||
Store string `json:"store,omitempty" yaml:"store,omitempty"`
|
||
|
||
Keys [][]float32 `json:"keys"`
|
||
StoreCommon
|
||
}
|
||
|
||
type StoresGet struct {
|
||
Store string `json:"store,omitempty" yaml:"store,omitempty"`
|
||
|
||
Keys [][]float32 `json:"keys" yaml:"keys"`
|
||
StoreCommon
|
||
}
|
||
|
||
type StoresGetResponse struct {
|
||
Keys [][]float32 `json:"keys" yaml:"keys"`
|
||
Values []string `json:"values" yaml:"values"`
|
||
}
|
||
|
||
type StoresFind struct {
|
||
Store string `json:"store,omitempty" yaml:"store,omitempty"`
|
||
|
||
Key []float32 `json:"key" yaml:"key"`
|
||
Topk int `json:"topk" yaml:"topk"`
|
||
StoreCommon
|
||
}
|
||
|
||
type StoresFindResponse struct {
|
||
Keys [][]float32 `json:"keys" yaml:"keys"`
|
||
Values []string `json:"values" yaml:"values"`
|
||
Similarities []float32 `json:"similarities" yaml:"similarities"`
|
||
}
|
||
|
||
type NodeData struct {
|
||
Name string
|
||
ID string
|
||
TunnelAddress string
|
||
ServiceID string
|
||
LastSeen time.Time
|
||
}
|
||
|
||
func (d NodeData) IsOnline() bool {
|
||
now := time.Now()
|
||
// if the node was seen in the last 40 seconds, it's online
|
||
return now.Sub(d.LastSeen) < 40*time.Second
|
||
}
|
||
|
||
type P2PNodesResponse struct {
|
||
LlamaCPPNodes []NodeData `json:"llama_cpp_nodes" yaml:"llama_cpp_nodes"`
|
||
FederatedNodes []NodeData `json:"federated_nodes" yaml:"federated_nodes"`
|
||
MLXNodes []NodeData `json:"mlx_nodes" yaml:"mlx_nodes"`
|
||
}
|
||
|
||
type SysInfoModel struct {
|
||
ID string `json:"id"`
|
||
// Backend is the engine serving this model. The loader knows only the ID,
|
||
// so it is resolved from the model's config; empty when the model was
|
||
// loaded without one (a loose file, or a config since removed).
|
||
Backend string `json:"backend,omitempty"`
|
||
// Process is the backend process serving the model on this host. Absent
|
||
// when the model has no local process (a distributed worker holds it) or
|
||
// the process could not be read.
|
||
Process *SysInfoProcess `json:"process,omitempty"`
|
||
// SizeVRAM is DRM-accounted resident device memory in bytes. Nil means
|
||
// the backend process tree has no complete supported reading.
|
||
SizeVRAM *uint64 `json:"size_vram,omitempty"`
|
||
}
|
||
|
||
// SysInfoProcess is a point-in-time reading of one backend process.
|
||
type SysInfoProcess struct {
|
||
PID int32 `json:"pid"`
|
||
// RSSBytes is resident host memory. Weights offloaded to a GPU are not
|
||
// in it.
|
||
RSSBytes uint64 `json:"rss_bytes"`
|
||
MemoryPercent float32 `json:"memory_percent"`
|
||
// CPUPercent is the share of the whole host's CPU used since the previous
|
||
// reading, 0-100. Absent on the first reading of a process.
|
||
CPUPercent *float64 `json:"cpu_percent,omitempty"`
|
||
StartedAt time.Time `json:"started_at,omitzero"`
|
||
}
|
||
|
||
type SystemInformationResponse struct {
|
||
Backends []string `json:"backends"` // available backend engines
|
||
Models []SysInfoModel `json:"loaded_models"` // currently loaded models
|
||
}
|
||
|
||
type DetectionRequest struct {
|
||
BasicModelRequest
|
||
Image string `json:"image"` // URL or base64-encoded image to analyze
|
||
Prompt string `json:"prompt,omitempty"` // Text prompt (for SAM 3 PCS mode)
|
||
Points []float32 `json:"points,omitempty"` // Point coordinates as [x,y,label,...] triples (label: 1=pos, 0=neg)
|
||
Boxes []float32 `json:"boxes,omitempty"` // Box coordinates as [x1,y1,x2,y2,...] quads
|
||
Threshold float32 `json:"threshold,omitempty"` // Detection confidence threshold
|
||
}
|
||
|
||
type DetectionResponse struct {
|
||
Detections []Detection `json:"detections"`
|
||
}
|
||
|
||
type Detection struct {
|
||
X float32 `json:"x"`
|
||
Y float32 `json:"y"`
|
||
Width float32 `json:"width"`
|
||
Height float32 `json:"height"`
|
||
ClassName string `json:"class_name"`
|
||
Confidence float32 `json:"confidence,omitempty"`
|
||
Mask string `json:"mask,omitempty"` // base64-encoded PNG segmentation mask
|
||
}
|
||
|
||
// DepthRequest is the request body for the /v1/depth endpoint. It exposes the
|
||
// full Depth Anything 3 output surface; the include_* flags and exports let a
|
||
// caller ask for less work (e.g. depth only, or depth+pose without the point
|
||
// cloud).
|
||
type DepthRequest struct {
|
||
BasicModelRequest
|
||
Image string `json:"image"` // URL or base64-encoded image to analyze
|
||
Dst string `json:"dst,omitempty"` // optional output directory for exports (glb/colmap)
|
||
IncludeDepth bool `json:"include_depth,omitempty"` // return the per-pixel depth map
|
||
IncludeConfidence bool `json:"include_confidence,omitempty"` // return the per-pixel confidence map (DualDPT)
|
||
IncludePose bool `json:"include_pose,omitempty"` // return camera extrinsics/intrinsics (DualDPT)
|
||
IncludeSky bool `json:"include_sky,omitempty"` // return the per-pixel sky map (mono models)
|
||
IncludePoints bool `json:"include_points,omitempty"` // back-project to a 3D point cloud (DualDPT)
|
||
PointsConfThresh float32 `json:"points_conf_thresh,omitempty"` // keep points with confidence >= this threshold
|
||
Exports []string `json:"exports,omitempty"` // requested exports: "glb", "colmap"
|
||
}
|
||
|
||
// ForecastRows is a list of equally long numeric rows (variates or
|
||
// covariates), oldest step first. A JSON null is a missing value and is held
|
||
// as a nil pointer.
|
||
type ForecastRows [][]*float64
|
||
|
||
// ForecastTarget is ForecastRows that also accepts a flat array as shorthand
|
||
// for a single variate: [1, 2, 3] reads as [[1, 2, 3]].
|
||
type ForecastTarget ForecastRows
|
||
|
||
// UnmarshalJSON accepts both the nested form and the flat shorthand.
|
||
func (t *ForecastTarget) UnmarshalJSON(data []byte) error {
|
||
var rows [][]*float64
|
||
if err := json.Unmarshal(data, &rows); err == nil {
|
||
*t = rows
|
||
return nil
|
||
}
|
||
var flat []*float64
|
||
if err := json.Unmarshal(data, &flat); err != nil {
|
||
return fmt.Errorf("target must be an array of numbers or an array of arrays of numbers")
|
||
}
|
||
*t = ForecastTarget{flat}
|
||
return nil
|
||
}
|
||
|
||
// ForecastSeries is one time series of a /v1/forecast request.
|
||
type ForecastSeries struct {
|
||
ID string `json:"id,omitempty"` // echoed back in the matching result
|
||
Target ForecastTarget `json:"target"` // [V][T] or [T], null = missing
|
||
// PastCovariates is [C][T]: numeric covariates known only for the past.
|
||
PastCovariates ForecastRows `json:"past_covariates,omitempty"`
|
||
// KnownCovariates is [C][T+H]: numeric covariates known for the future too.
|
||
KnownCovariates ForecastRows `json:"known_covariates,omitempty"`
|
||
// PastCategorical is [C][T]: integer category codes for the past.
|
||
PastCategorical ForecastRows `json:"past_categorical,omitempty"`
|
||
// KnownCategorical is [C][T+H]: integer category codes for the future too.
|
||
KnownCategorical ForecastRows `json:"known_categorical,omitempty"`
|
||
}
|
||
|
||
// ForecastRequest is the request body for the /v1/forecast endpoint.
|
||
type ForecastRequest struct {
|
||
BasicModelRequest
|
||
PredictionLength int `json:"prediction_length"` // steps to forecast
|
||
Quantiles []float64 `json:"quantiles,omitempty"` // levels to return; default 0.1, 0.5, 0.9
|
||
MaxContext int `json:"max_context,omitempty"` // cap on history used; 0 = model default
|
||
SymmetricAveraging *bool `json:"symmetric_averaging,omitempty"` // null = model default
|
||
ClipNonnegative *bool `json:"clip_nonnegative,omitempty"` // null = model default
|
||
Series []ForecastSeries `json:"series"`
|
||
}
|
||
|
||
// ForecastVariate holds the forecast of one variate.
|
||
type ForecastVariate struct {
|
||
Index int `json:"index"`
|
||
// Values is [Q][H]: Values[q][h] is quantile_levels[q] at step h.
|
||
Values [][]float32 `json:"values"`
|
||
}
|
||
|
||
// ForecastSeriesResult is the forecast of one input series.
|
||
type ForecastSeriesResult struct {
|
||
Index int `json:"index"` // position of the series in the request
|
||
ID string `json:"id,omitempty"`
|
||
Variates []ForecastVariate `json:"variates"`
|
||
}
|
||
|
||
// ForecastResponse is the JSON response for the /v1/forecast endpoint.
|
||
type ForecastResponse struct {
|
||
Object string `json:"object"`
|
||
Model string `json:"model"`
|
||
PredictionLength int `json:"prediction_length"`
|
||
QuantileLevels []float64 `json:"quantile_levels"`
|
||
Data []ForecastSeriesResult `json:"data"`
|
||
}
|
||
|
||
// DepthResponse is the JSON response for the /v1/depth endpoint, mirroring the
|
||
// DepthResponse proto.
|
||
type DepthResponse struct {
|
||
Width int32 `json:"width"`
|
||
Height int32 `json:"height"`
|
||
Depth []float32 `json:"depth,omitempty"` // width*height row-major metric depth
|
||
Confidence []float32 `json:"confidence,omitempty"` // width*height row-major confidence (DualDPT)
|
||
Sky []float32 `json:"sky,omitempty"` // width*height row-major sky map (mono)
|
||
Extrinsics []float32 `json:"extrinsics,omitempty"` // 12 floats, 3x4 row-major (world-to-camera)
|
||
Intrinsics []float32 `json:"intrinsics,omitempty"` // 9 floats, 3x3 row-major
|
||
NumPoints int32 `json:"num_points,omitempty"` // number of 3D points
|
||
Points []float32 `json:"points,omitempty"` // num_points*3 xyz, world space
|
||
PointColors string `json:"point_colors,omitempty"` // base64-encoded num_points*3 uint8 rgb
|
||
ExportPaths []string `json:"export_paths,omitempty"` // paths written for the requested exports
|
||
IsMetric bool `json:"is_metric"` // depth is in metric units
|
||
}
|
||
|
||
// ─── Face recognition ──────────────────────────────────────────────
|
||
//
|
||
// FacialArea describes a bounding box for a detected face.
|
||
type FacialArea struct {
|
||
X float32 `json:"x"`
|
||
Y float32 `json:"y"`
|
||
W float32 `json:"w"`
|
||
H float32 `json:"h"`
|
||
}
|
||
|
||
// FaceVerifyRequest compares two images to decide whether they depict
|
||
// the same person. Img1 and Img2 accept URL, base64, or data-URI.
|
||
type FaceVerifyRequest struct {
|
||
BasicModelRequest
|
||
Img1 string `json:"img1"`
|
||
Img2 string `json:"img2"`
|
||
Threshold float32 `json:"threshold,omitempty"`
|
||
AntiSpoofing bool `json:"anti_spoofing,omitempty"`
|
||
}
|
||
|
||
type FaceVerifyResponse struct {
|
||
Verified bool `json:"verified"`
|
||
Distance float32 `json:"distance"`
|
||
Threshold float32 `json:"threshold"`
|
||
Confidence float32 `json:"confidence"`
|
||
Model string `json:"model"`
|
||
Img1Area FacialArea `json:"img1_area"`
|
||
Img2Area FacialArea `json:"img2_area"`
|
||
ProcessingTimeMs float32 `json:"processing_time_ms,omitempty"`
|
||
// Liveness fields are only populated when the request set
|
||
// anti_spoofing=true. Pointers keep them fully absent from the
|
||
// JSON response otherwise, so callers can tell "not checked"
|
||
// apart from "checked and fake" (which would collapse to zero
|
||
// values with plain bool+omitempty).
|
||
Img1IsReal *bool `json:"img1_is_real,omitempty"`
|
||
Img1AntispoofScore *float32 `json:"img1_antispoof_score,omitempty"`
|
||
Img2IsReal *bool `json:"img2_is_real,omitempty"`
|
||
Img2AntispoofScore *float32 `json:"img2_antispoof_score,omitempty"`
|
||
}
|
||
|
||
// FaceAnalyzeRequest asks the backend for demographic attributes on
|
||
// every face detected in Img.
|
||
type FaceAnalyzeRequest struct {
|
||
BasicModelRequest
|
||
Img string `json:"img"`
|
||
Actions []string `json:"actions,omitempty"` // subset of {"age","gender","emotion","race"}
|
||
AntiSpoofing bool `json:"anti_spoofing,omitempty"`
|
||
}
|
||
|
||
type FaceAnalyzeResponse struct {
|
||
Faces []FaceAnalysis `json:"faces"`
|
||
}
|
||
|
||
type FaceAnalysis struct {
|
||
Region FacialArea `json:"region"`
|
||
FaceConfidence float32 `json:"face_confidence"`
|
||
Age float32 `json:"age,omitempty"`
|
||
DominantGender string `json:"dominant_gender,omitempty"`
|
||
Gender map[string]float32 `json:"gender,omitempty"`
|
||
DominantEmotion string `json:"dominant_emotion,omitempty"`
|
||
Emotion map[string]float32 `json:"emotion,omitempty"`
|
||
DominantRace string `json:"dominant_race,omitempty"`
|
||
Race map[string]float32 `json:"race,omitempty"`
|
||
// Liveness fields — see FaceVerifyResponse for why these are pointers.
|
||
IsReal *bool `json:"is_real,omitempty"`
|
||
AntispoofScore *float32 `json:"antispoof_score,omitempty"`
|
||
}
|
||
|
||
// FaceEmbedRequest extracts a face embedding from an image. Distinct
|
||
// from /v1/embeddings (which is OpenAI-compatible and text-only); this
|
||
// endpoint accepts URL / base64 / data-URI image inputs.
|
||
type FaceEmbedRequest struct {
|
||
BasicModelRequest
|
||
Img string `json:"img"`
|
||
}
|
||
|
||
type FaceEmbedResponse struct {
|
||
Embedding []float32 `json:"embedding"`
|
||
Dim int `json:"dim"`
|
||
Model string `json:"model,omitempty"`
|
||
}
|
||
|
||
// FaceRegisterRequest enrolls a face into the 1:N recognition store.
|
||
type FaceRegisterRequest struct {
|
||
BasicModelRequest
|
||
RegisteredAt time.Time `json:"registered_at,omitempty"` // original enrollment time when replaying a saved embedding
|
||
Embedding []float32 `json:"embedding,omitempty"`
|
||
Img string `json:"img"`
|
||
Name string `json:"name"`
|
||
Labels map[string]string `json:"labels,omitempty"`
|
||
Store string `json:"store,omitempty"` // vector store model; empty = local-store default
|
||
}
|
||
|
||
type FaceRegisterResponse struct {
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
RegisteredAt time.Time `json:"registered_at"`
|
||
}
|
||
|
||
// FaceIdentifyRequest runs 1:N recognition: embed the probe and
|
||
// return the top-K nearest registered faces.
|
||
type FaceIdentifyRequest struct {
|
||
BasicModelRequest
|
||
Img string `json:"img"`
|
||
TopK int `json:"top_k,omitempty"`
|
||
Threshold float32 `json:"threshold,omitempty"` // optional cutoff on distance
|
||
Store string `json:"store,omitempty"`
|
||
}
|
||
|
||
type FaceIdentifyResponse struct {
|
||
Matches []FaceIdentifyMatch `json:"matches"`
|
||
}
|
||
|
||
type FaceIdentifyMatch struct {
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
Labels map[string]string `json:"labels,omitempty"`
|
||
Distance float32 `json:"distance"`
|
||
Confidence float32 `json:"confidence"`
|
||
Match bool `json:"match"` // true when distance <= threshold
|
||
}
|
||
|
||
// FaceForgetRequest removes a previously-registered face by ID.
|
||
type FaceForgetRequest struct {
|
||
BasicModelRequest
|
||
ID string `json:"id"`
|
||
Store string `json:"store,omitempty"`
|
||
}
|
||
|
||
// ─── Voice (speaker) recognition ───────────────────────────────────
|
||
//
|
||
// VoiceVerifyRequest compares two audio clips and reports whether they
|
||
// were spoken by the same speaker. Audio1/Audio2 accept URL, base64,
|
||
// or data-URI (the HTTP layer materialises the bytes to a temp file
|
||
// before calling the gRPC backend).
|
||
type VoiceVerifyRequest struct {
|
||
BasicModelRequest
|
||
Audio1 string `json:"audio1"`
|
||
Audio2 string `json:"audio2"`
|
||
Threshold float32 `json:"threshold,omitempty"`
|
||
AntiSpoofing bool `json:"anti_spoofing,omitempty"`
|
||
}
|
||
|
||
type VoiceVerifyResponse struct {
|
||
Verified bool `json:"verified"`
|
||
Distance float32 `json:"distance"`
|
||
Threshold float32 `json:"threshold"`
|
||
Confidence float32 `json:"confidence"`
|
||
Model string `json:"model"`
|
||
ProcessingTimeMs float32 `json:"processing_time_ms,omitempty"`
|
||
}
|
||
|
||
// VoiceAnalyzeRequest asks the backend for demographic attributes
|
||
// (age, gender, emotion) inferred from the audio clip.
|
||
type VoiceAnalyzeRequest struct {
|
||
BasicModelRequest
|
||
Audio string `json:"audio"`
|
||
Actions []string `json:"actions,omitempty"` // subset of {"age","gender","emotion"}
|
||
}
|
||
|
||
type VoiceAnalyzeResponse struct {
|
||
Segments []VoiceAnalysis `json:"segments"`
|
||
}
|
||
|
||
type VoiceAnalysis struct {
|
||
Start float32 `json:"start"`
|
||
End float32 `json:"end"`
|
||
Age float32 `json:"age,omitempty"`
|
||
DominantGender string `json:"dominant_gender,omitempty"`
|
||
Gender map[string]float32 `json:"gender,omitempty"`
|
||
DominantEmotion string `json:"dominant_emotion,omitempty"`
|
||
Emotion map[string]float32 `json:"emotion,omitempty"`
|
||
}
|
||
|
||
// VoiceEmbedRequest extracts a speaker embedding from an audio clip.
|
||
// Distinct from /v1/embeddings (OpenAI-compatible, text-only) — this
|
||
// endpoint accepts URL / base64 / data-URI audio inputs.
|
||
type VoiceEmbedRequest struct {
|
||
BasicModelRequest
|
||
Audio string `json:"audio"`
|
||
}
|
||
|
||
type VoiceEmbedResponse struct {
|
||
Embedding []float32 `json:"embedding"`
|
||
Dim int `json:"dim"`
|
||
Model string `json:"model,omitempty"`
|
||
}
|
||
|
||
// VoiceRegisterRequest enrolls a speaker into the 1:N identification store.
|
||
type VoiceRegisterRequest struct {
|
||
SpeakerProfiles *SpeakerProfiles `json:"speaker_profiles,omitempty"`
|
||
SpeakerSlot *int `json:"speaker_slot,omitempty"`
|
||
BasicModelRequest
|
||
Audio string `json:"audio"`
|
||
Name string `json:"name"`
|
||
Labels map[string]string `json:"labels,omitempty"`
|
||
Store string `json:"store,omitempty"`
|
||
}
|
||
|
||
type VoiceRegisterResponse struct {
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
RegisteredAt time.Time `json:"registered_at"`
|
||
}
|
||
|
||
// VoiceIdentifyRequest runs 1:N recognition: embed the probe and
|
||
// return the top-K nearest registered speakers.
|
||
type VoiceIdentifyRequest struct {
|
||
BasicModelRequest
|
||
Audio string `json:"audio"`
|
||
TopK int `json:"top_k,omitempty"`
|
||
Threshold float32 `json:"threshold,omitempty"`
|
||
Store string `json:"store,omitempty"`
|
||
}
|
||
|
||
type VoiceIdentifyResponse struct {
|
||
Matches []VoiceIdentifyMatch `json:"matches"`
|
||
}
|
||
|
||
type VoiceIdentifyMatch struct {
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
Labels map[string]string `json:"labels,omitempty"`
|
||
Distance float32 `json:"distance"`
|
||
Confidence float32 `json:"confidence"`
|
||
Match bool `json:"match"`
|
||
}
|
||
|
||
// VoiceForgetRequest removes a previously-registered speaker by ID.
|
||
type VoiceForgetRequest struct {
|
||
BasicModelRequest
|
||
ID string `json:"id"`
|
||
Store string `json:"store,omitempty"`
|
||
}
|
||
|
||
type ImportModelRequest struct {
|
||
URI string `json:"uri"`
|
||
Preferences json.RawMessage `json:"preferences,omitempty"`
|
||
}
|
||
|
||
// KnownBackend describes a backend that the importer knows about.
|
||
// Used by GET /backends/known to populate the import form dropdown.
|
||
type KnownBackend struct {
|
||
Name string `json:"name"`
|
||
Modality string `json:"modality"`
|
||
AutoDetect bool `json:"auto_detect"`
|
||
Description string `json:"description,omitempty"`
|
||
// Installed is true when the backend is currently present on disk — i.e. it
|
||
// appears in gallery.ListSystemBackends(systemState). Importer-registered or
|
||
// curated pref-only backends default to false unless they also show up on
|
||
// disk. The import form uses this to warn users that submitting an import
|
||
// may trigger an automatic backend download.
|
||
Installed bool `json:"installed"`
|
||
}
|
||
|
||
// SettingsResponse is the response type for settings API operations
|
||
type SettingsResponse struct {
|
||
Success bool `json:"success"`
|
||
Error string `json:"error,omitempty"`
|
||
Message string `json:"message,omitempty"`
|
||
RestartRequired bool `json:"restart_required"`
|
||
}
|
||
|
||
// RouterDecideRequest is the input for POST /api/router/decide — the
|
||
// programmatic decision-oracle endpoint. Given the name of a router
|
||
// model (a ModelConfig that carries a `router:` block) and a prompt,
|
||
// the endpoint returns the classifier's label set plus the candidate
|
||
// model the in-band RouteModel middleware would have chosen. The
|
||
// endpoint does NOT rewrite any request, forward to a backend, or
|
||
// record a row in the decision store — it is a pure decision oracle
|
||
// for external routers that want LocalAI's classifier opinion without
|
||
// committing LocalAI to handle the request.
|
||
type RouterDecideRequest struct {
|
||
// Router is the name of the router model (a ModelConfig with a
|
||
// `router:` block). Required.
|
||
Router string `json:"router"`
|
||
// Input is the user-visible prompt text to classify. Required.
|
||
// Schema-shape extraction (chat-message concatenation, etc.) is
|
||
// the caller's responsibility — matches the Probe contract used
|
||
// by the in-band middleware.
|
||
Input string `json:"input"`
|
||
}
|
||
|
||
// RouterDecideResponse carries the classifier's decision plus the
|
||
// resolved candidate. Mirrors router.Decision with the addition of
|
||
// Candidate/Fallback so the caller learns which downstream model
|
||
// would have served the request without re-implementing the
|
||
// label-set → candidate match locally.
|
||
type RouterDecideResponse struct {
|
||
// Router echoes the requested router model.
|
||
Router string `json:"router"`
|
||
// Classifier is the classifier name that produced the decision
|
||
// (e.g. "score").
|
||
Classifier string `json:"classifier"`
|
||
// Labels is the set of active policy labels.
|
||
Labels []string `json:"labels"`
|
||
// Candidate is the model that would be routed to. Empty when no
|
||
// candidate covers Labels AND no fallback is configured.
|
||
Candidate string `json:"candidate,omitempty"`
|
||
// Fallback is true when Candidate is the router's configured
|
||
// fallback because no candidate covered Labels. Lets callers
|
||
// distinguish "matched" from "fell back" without comparing names.
|
||
Fallback bool `json:"fallback,omitempty"`
|
||
// Score is the top label's softmax probability (the
|
||
// classifier-side confidence signal).
|
||
Score float64 `json:"score"`
|
||
// LatencyMs is the classifier's wall-clock cost.
|
||
LatencyMs int64 `json:"latency_ms"`
|
||
// Cached is true when the decision came from the L2 embedding
|
||
// cache rather than a fresh classifier run.
|
||
Cached bool `json:"cached,omitempty"`
|
||
// CacheSimilarity carries the cosine similarity of the cache hit
|
||
// (0 when not cached).
|
||
CacheSimilarity float64 `json:"cache_similarity,omitempty"`
|
||
// NearestSimilarity is the cosine similarity of the closest KNN
|
||
// corpus entry — populated by the knn classifier even when the
|
||
// decision fell back because the probe was out of corpus range.
|
||
// 0 for other classifiers.
|
||
NearestSimilarity float64 `json:"nearest_similarity,omitempty"`
|
||
// Neighbors lists the corpus entries the knn classifier retrieved,
|
||
// by descending similarity, including ones below the similarity
|
||
// gate. Empty for other classifiers.
|
||
Neighbors []RouterDecideNeighbor `json:"neighbors,omitempty"`
|
||
}
|
||
|
||
// RouterDecideNeighbor is one KNN corpus neighbour in a decide
|
||
// response (mirrors router.NeighborRef; schema stays import-free of
|
||
// the routing packages). ID is the corpus entry's content hash — the
|
||
// first 8 bytes of the SHA-256 of its text, hex-encoded — so callers
|
||
// that seeded the corpus can recompute text→ID locally and bucket
|
||
// decisions by corpus region; the text itself never leaves the server.
|
||
type RouterDecideNeighbor struct {
|
||
ID string `json:"id,omitempty"`
|
||
Similarity float64 `json:"similarity"`
|
||
Labels []string `json:"labels,omitempty"`
|
||
}
|
||
|
||
// RouterCorpusEntry is one labelled exemplar submitted to
|
||
// POST /api/router/{name}/corpus. The text is embedded server-side
|
||
// with the router's knn.embedding_model; labels must be declared in
|
||
// the router's policies.
|
||
type RouterCorpusEntry struct {
|
||
Text string `json:"text"`
|
||
Labels []string `json:"labels"`
|
||
}
|
||
|
||
// RouterCorpusAddRequest is the input for POST /api/router/{name}/corpus —
|
||
// bulk-seeds the KNN routing corpus. Corpus input is API-only by
|
||
// design: entries may contain example user content, so they are never
|
||
// entered through (or displayed in) the UI.
|
||
type RouterCorpusAddRequest struct {
|
||
Entries []RouterCorpusEntry `json:"entries"`
|
||
}
|
||
|
||
// RouterCorpusAddResponse reports the outcome of a corpus seed call.
|
||
type RouterCorpusAddResponse struct {
|
||
Router string `json:"router"`
|
||
// Added is how many entries were embedded, persisted, and indexed.
|
||
Added int `json:"added"`
|
||
// Skipped counts entries whose text was already in the corpus —
|
||
// duplicates are rejected rather than double-weighted.
|
||
Skipped int `json:"skipped"`
|
||
// Total is the corpus size after the call.
|
||
Total int `json:"total"`
|
||
// LabelCounts is the per-label exemplar count after the call.
|
||
LabelCounts map[string]int `json:"label_counts"`
|
||
}
|
||
|
||
// RouterCorpusStatsResponse is the inspection surface for a router's
|
||
// KNN corpus: counts and configuration only — entry texts are never
|
||
// returned by any endpoint.
|
||
type RouterCorpusStatsResponse struct {
|
||
Router string `json:"router"`
|
||
StoreName string `json:"store_name"`
|
||
EmbeddingModel string `json:"embedding_model"`
|
||
Total int `json:"total"`
|
||
LabelCounts map[string]int `json:"label_counts"`
|
||
// EmbeddingModels lists the embedder fingerprints present in the
|
||
// persisted corpus; more than one means part of the corpus is
|
||
// pending re-embedding on the next load.
|
||
EmbeddingModels []string `json:"embedding_models,omitempty"`
|
||
}
|
||
|
||
// RouterCorpusClearResponse reports how many entries a
|
||
// DELETE /api/router/{name}/corpus removed.
|
||
type RouterCorpusClearResponse struct {
|
||
Router string `json:"router"`
|
||
Cleared int `json:"cleared"`
|
||
}
|
||
|
||
// PIIDecideRequest is the input for POST /api/pii/decide — the
|
||
// programmatic PII-decision oracle. External routers call it before
|
||
// dispatching a request to learn whether the content carries PII and
|
||
// what action the configured pattern set would take. The endpoint
|
||
// inspects the text and returns findings + a suggested action; it
|
||
// does NOT mutate the input, record an audit event, or rewrite any
|
||
// downstream request. The caller composes the decision with its own
|
||
// policy (mask, block, or allow).
|
||
type PIIDecideRequest struct {
|
||
// Text is the user-visible content to inspect. Required.
|
||
Text string `json:"text"`
|
||
}
|
||
|
||
// PIIDecideResponse carries the redactor's findings.
|
||
// SuggestedAction is derived from the action ordering used by the
|
||
// internal redactor (block > mask > allow) so callers don't need to
|
||
// replicate that logic.
|
||
type PIIDecideResponse struct {
|
||
// Findings is one entry per matched span — pattern id, byte
|
||
// range, and audit-safe hash prefix (never the matched value).
|
||
Findings []PIIFinding `json:"findings"`
|
||
// SuggestedAction is the strongest action across all findings:
|
||
// "block", "mask", or "allow" (no findings, or all findings
|
||
// resolved to the allow action).
|
||
SuggestedAction string `json:"suggested_action"`
|
||
// RedactedPreview is the input with mask-action spans replaced
|
||
// by their placeholders. Identical to Text when no findings or
|
||
// when the strongest action is block/allow (which don't rewrite
|
||
// content).
|
||
RedactedPreview string `json:"redacted_preview"`
|
||
}
|
||
|
||
// PIIFinding mirrors pii.Span on the wire. Pattern is the pattern id
|
||
// that matched (e.g. "email"). HashPrefix is the first 8 chars of
|
||
// sha256(matched value) — lets admins correlate recurring leaks
|
||
// without recovering the value itself.
|
||
type PIIFinding struct {
|
||
Start int `json:"start"`
|
||
End int `json:"end"`
|
||
Pattern string `json:"pattern"`
|
||
HashPrefix string `json:"hash_prefix"`
|
||
}
|