mirror of
https://github.com/mudler/LocalAI.git
synced 2026-09-21 21:54:52 -04:00
Review round 1 on the change that stopped workers listening. One blocking item
and seven notes.
LOCALAI_WORKER_TUNNEL=false was the blocking one, and the ruling was to make it
fatal rather than to correct the comment that still promised it fell back to the
advertised address. There is no fallback left: a worker on this branch
advertises nothing and binds only loopback, so turning the tunnel off leaves it
reachable by nothing while it registers, heartbeats and reports healthy, and the
scheduler keeps placing models on it. That is the worst available failure shape,
so a new Config.validateStartup refuses it before prefetch, registration and
NATS, while the worker is still invisible to the cluster. It absorbs the
pre-existing empty-registration-token check, which had the same shape and no
spec. The flag is kept rather than deleted so an operator who set it is told the
promise is gone instead of having the setting ignored, and the guard around
StartTunnel is removed, because a branch nothing can take reads as a supported
no-tunnel mode that does not exist.
The justification for erroring on an install that names no address was wrong,
and the review is right that this is the dangerous form of overclaiming, because
the conclusion holds and the mechanism does not. It said the resulting empty
target would be refused as an invalid stream and that the refusal would read as
the worker answering about its backend. Nothing in this repo branches on
cluster.ErrNoRoute, and nodes.unroutable treats any recorded dial error as
unroutable, so that refusal reaches every reap guard as ProbeUnknown and deletes
nothing. The site now stands on what holds, that an install naming no port
produced nothing routable and the failure belongs to the install rather than to
a later probe, and records the retracted claim so nobody re-derives it. This
retracts the same paragraph in the body of 1cf847f29.
The reviewer deleted the whole tryWarmPath unnamed-replica guard and the suite
stayed green, including the reservation release. It is specced now, and the
asymmetry the review asked about is decided at the site: the row stays, unlike
the sibling !alive branch which removes it. That branch has observed a backend
dead; this one has observed only that the row is unreadable, which says nothing
about whether a process is running, and the row is the last record that one
might be, since the acknowledged stop path refuses a stop whose ExpectedAddress
does not match and an empty one cannot be cleaned up through it either.
The cross-version wire claim rested on two struct tags nobody asserted:
renaming only the json keys survived mutation while the gorm column rename went
red through raw SQL. Both keys are pinned now, marshal and unmarshal, per
struct.
A worker-first upgrade showed the operator a status code and not the reason. The
registration client discarded the body, so "address is required for backend
workers" was read off the socket and thrown away, and the ladder then spent four
minutes on a verdict the frontend reached instantly. Refusals now quote the body
and carry ErrRegistrationRejected, and both the ladder and the credential
manager's Acquire stop on the first one. Acquire matters more than the ladder:
it is the default path and its bound is 100 attempts, not 10. 408 and 429 are
deliberately not refusals, since both are the frontend asking for the same
request again.
Also: the stale "not blocked by firewalls" troubleshooting line, which now names
the real cause and the knobs that move the port range; and the inert address
fields on the MCP Node DTO, which the Assistant was still being handed. The
review named http_address there and I removed address too, because it is inert
by the same argument and leaving one of a pair is arbitrary.
Five mutations, all red. Deleting the warm-path guard reddens four specs and
falsifying only its reservation release reddens one, so the two halves are
pinned separately. Renaming only the json keys reddens both wire suites.
Discarding the refusal body reddens two. Dropping the rejection classification
does not fail the suite, it hangs it, which is the operator-visible symptom, so
it is recorded red under a ginkgo timeout.
The verify list is now derived from the diff rather than from the brief, which
is what let the previous round ship a spec asserting 200 where the endpoint
returns 201: nine ginkgo suites, the e2e vet, route auth coverage, the leaf
check, build, the healthcheck shell suite and lint. The two jsx files have no
harness in this worktree and are recorded as the one unverified surface.
Assisted-by: Claude Opus 5 [claude-code]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
420 lines
23 KiB
Go
420 lines
23 KiB
Go
package localaitools
|
|
|
|
import "github.com/mudler/LocalAI/core/services/voiceprofile"
|
|
|
|
// DTOs for the LocalAIClient interface. Where the same shape already exists
|
|
// elsewhere (config.Gallery, gallery.Metadata, schema.KnownBackend,
|
|
// vram.EstimateResult) we surface that type directly via the interface
|
|
// instead of maintaining a parallel DTO. The remaining types in this file
|
|
// are LLM-shaped views of internal state where the source struct carries
|
|
// fields the LLM shouldn't see (auth tokens, filesystem paths) or
|
|
// non-JSON-friendly fields (e.g. galleryop.OpStatus.Error which marshals
|
|
// to "{}" because it's an interface).
|
|
|
|
// GallerySearchQuery is the input for gallery_search.
|
|
type GallerySearchQuery struct {
|
|
Query string `json:"query" jsonschema:"Free-text query matched against model name, gallery and tags. Empty returns the first Limit models."`
|
|
Limit int `json:"limit,omitempty" jsonschema:"Maximum number of results to return. Defaults to 20 when zero or negative."`
|
|
Tag string `json:"tag,omitempty" jsonschema:"Optional tag filter (e.g. chat, embed, image)."`
|
|
Gallery string `json:"gallery,omitempty" jsonschema:"Restrict results to a specific gallery name."`
|
|
}
|
|
|
|
// InstalledModel is one entry in list_installed_models. Distinct from
|
|
// config.ModelConfig (which is the full on-disk YAML — far too large to
|
|
// serialise per request); this is a summary the LLM can scan cheaply.
|
|
type InstalledModel struct {
|
|
Name string `json:"name"`
|
|
Backend string `json:"backend,omitempty"`
|
|
Capabilities []string `json:"capabilities,omitempty"`
|
|
Pinned bool `json:"pinned,omitempty"`
|
|
Disabled bool `json:"disabled,omitempty"`
|
|
}
|
|
|
|
// JobStatus is a JSON-friendly mirror of galleryop.OpStatus. We don't surface
|
|
// OpStatus directly because its `Error error` field marshals to `{}` (the
|
|
// json.Marshal default for an error interface), and the underlying status
|
|
// map keys jobs by UUID rather than carrying the ID on the value, so we
|
|
// add the ID here too. Keep field names aligned with OpStatus where they
|
|
// overlap so callers comparing the two don't have to translate.
|
|
type JobStatus struct {
|
|
ID string `json:"id"`
|
|
Processed bool `json:"processed"`
|
|
Cancelled bool `json:"cancelled,omitempty"`
|
|
Progress float64 `json:"progress"`
|
|
TotalFileSize string `json:"total_file_size,omitempty"`
|
|
DownloadedFileSize string `json:"downloaded_file_size,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
ErrorMessage string `json:"error,omitempty"`
|
|
}
|
|
|
|
// ModelConfigView is a JSON view of a model config file.
|
|
type ModelConfigView struct {
|
|
Name string `json:"name"`
|
|
YAML string `json:"yaml,omitempty" jsonschema:"Full YAML serialization of the model config."`
|
|
JSON map[string]any `json:"json,omitempty" jsonschema:"Parsed JSON view of the same config (convenience for diffing)."`
|
|
}
|
|
|
|
// AliasInfo is one alias -> target pair, the shape list_aliases returns and
|
|
// GET /api/aliases emits. Kept aligned with localai.AliasInfo so the
|
|
// MCP wire output matches the REST endpoint by construction.
|
|
type AliasInfo struct {
|
|
Name string `json:"name"`
|
|
Target string `json:"target"`
|
|
}
|
|
|
|
// InstallModelRequest is the input for install_model.
|
|
type InstallModelRequest struct {
|
|
GalleryName string `json:"gallery_name,omitempty" jsonschema:"The gallery the model lives in (from gallery_search). Optional when ModelName is unique across galleries."`
|
|
ModelName string `json:"model_name" jsonschema:"The canonical model name as returned by gallery_search."`
|
|
Overrides map[string]any `json:"overrides,omitempty" jsonschema:"Optional config overrides to merge into the installed model's YAML."`
|
|
Variant string `json:"variant,omitempty" jsonschema:"Optional. Installs one specific build of an entry that offers several (different quantizations or engines), named exactly as it appears in that entry's variant list. Leave empty unless the user explicitly asked for a particular build: by default LocalAI drops builds this machine cannot run or cannot fit, prefers the engine this hardware is best served by, and uses size only to decide between equally preferred builds."`
|
|
}
|
|
|
|
// InstallBackendRequest is the input for install_backend.
|
|
type InstallBackendRequest struct {
|
|
GalleryName string `json:"gallery_name,omitempty" jsonschema:"Source backend gallery."`
|
|
BackendName string `json:"backend_name" jsonschema:"Backend identifier (e.g. llama-cpp)."`
|
|
}
|
|
|
|
// Backend is the LLM-facing summary returned by list_backends. We don't
|
|
// expose gallery.SystemBackend directly because it carries filesystem
|
|
// paths (RunFile, IsSystem, IsMeta, the full Metadata) the LLM doesn't
|
|
// need and the tokens add up. ListKnownBackends returns schema.KnownBackend
|
|
// directly — that one is already the canonical wire shape.
|
|
type Backend struct {
|
|
Name string `json:"name"`
|
|
Installed bool `json:"installed"`
|
|
}
|
|
|
|
// SystemInfo summarises the LocalAI deployment.
|
|
type SystemInfo struct {
|
|
Version string `json:"version"`
|
|
Distributed bool `json:"distributed"`
|
|
BackendsPath string `json:"backends_path,omitempty"`
|
|
ModelsPath string `json:"models_path,omitempty"`
|
|
LoadedModels []string `json:"loaded_models,omitempty"`
|
|
InstalledBackends []string `json:"installed_backends,omitempty"`
|
|
}
|
|
|
|
// Node is one entry in list_nodes.
|
|
//
|
|
// It carries no address. A worker holds one outbound tunnel to a frontend
|
|
// replica and advertises no endpoint, so both `address` and `http_address` are
|
|
// empty on every node registered by a current worker. They were dropped rather
|
|
// than left empty because this struct is read by the LocalAI Assistant, and an
|
|
// always-blank field an operator can ask about invites an answer built on it.
|
|
type Node struct {
|
|
ID string `json:"id"`
|
|
TotalVRAM uint64 `json:"total_vram,omitempty"`
|
|
Healthy bool `json:"healthy"`
|
|
LastSeen string `json:"last_seen,omitempty"`
|
|
}
|
|
|
|
// SetNodeVRAMBudgetRequest is the input for set_node_vram_budget. It PUTs
|
|
// the value to /api/nodes/{node_id}/vram-budget, where the server validates
|
|
// and resolves the budget against the node's total VRAM. An empty Budget
|
|
// clears the admin override so the worker's own default takes over again.
|
|
type SetNodeVRAMBudgetRequest struct {
|
|
NodeID string `json:"node_id" jsonschema:"The federated node id (from list_nodes) whose VRAM budget to set."`
|
|
Budget string `json:"budget,omitempty" jsonschema:"VRAM allocation cap as a percentage (e.g. 80%) or absolute amount (e.g. 12GB). Empty string clears the override."`
|
|
}
|
|
|
|
// ModelSchedulingConfig is the MCP wire shape for one per-model distributed
|
|
// scheduling rule. Keep this DTO explicit instead of aliasing the node-registry
|
|
// model so the MCP contract only exposes operator-facing scheduling fields.
|
|
type ModelSchedulingConfig struct {
|
|
ModelName string `json:"model_name"`
|
|
NodeSelector string `json:"node_selector,omitempty"`
|
|
MinReplicas int `json:"min_replicas"`
|
|
MaxReplicas int `json:"max_replicas"`
|
|
SpreadAll bool `json:"spread_all,omitempty"`
|
|
RoutePolicy string `json:"route_policy,omitempty"`
|
|
BalanceAbsThreshold int `json:"balance_abs_threshold,omitempty"`
|
|
BalanceRelThreshold float64 `json:"balance_rel_threshold,omitempty"`
|
|
MinPrefixMatch float64 `json:"min_prefix_match,omitempty"`
|
|
// TargetModel is the model the rule governs. It differs from ModelName when
|
|
// the rule is keyed by an alias, in which case it follows the alias.
|
|
TargetModel string `json:"target_model,omitempty"`
|
|
// Shadowed reports that another rule already governs TargetModel, leaving
|
|
// this one with no effect.
|
|
Shadowed bool `json:"shadowed,omitempty"`
|
|
}
|
|
|
|
// SetSchedulingRequest is the input for set_scheduling. It mirrors
|
|
// /api/nodes/scheduling so standalone MCP and REST callers preserve the same
|
|
// PATCH-style semantics for the optional prefix-cache routing fields.
|
|
type SetSchedulingRequest struct {
|
|
ModelName string `json:"model_name" jsonschema:"Installed model name, or model alias, whose distributed scheduling rule should be created or updated. A rule keyed by an alias follows that alias to whatever model it currently points at."`
|
|
NodeSelector map[string]string `json:"node_selector,omitempty" jsonschema:"Optional node-label selector. Empty means any healthy backend node."`
|
|
MinReplicas int `json:"min_replicas" jsonschema:"Minimum desired replicas. Mutually exclusive with spread_all."`
|
|
MaxReplicas int `json:"max_replicas" jsonschema:"Maximum desired replicas. Must be >= min_replicas when non-zero. Mutually exclusive with spread_all."`
|
|
SpreadAll bool `json:"spread_all,omitempty" jsonschema:"When true, keep one replica on every matching node. Mutually exclusive with min_replicas/max_replicas."`
|
|
RoutePolicy *string `json:"route_policy,omitempty" jsonschema:"Optional prefix-cache route policy override. Omit to preserve the existing value on updates."`
|
|
BalanceAbsThreshold *int `json:"balance_abs_threshold,omitempty" jsonschema:"Optional absolute imbalance threshold override. Omit to preserve the existing value on updates."`
|
|
BalanceRelThreshold *float64 `json:"balance_rel_threshold,omitempty" jsonschema:"Optional relative imbalance threshold override. Omit to preserve the existing value on updates."`
|
|
MinPrefixMatch *float64 `json:"min_prefix_match,omitempty" jsonschema:"Optional minimum prefix match threshold override. Omit to preserve the existing value on updates."`
|
|
}
|
|
|
|
// DeleteSchedulingRequest identifies the model scheduling rule to remove.
|
|
type DeleteSchedulingRequest struct {
|
|
ModelName string `json:"model_name" jsonschema:"Installed model name whose scheduling config should be removed."`
|
|
}
|
|
|
|
// ImportModelURIRequest is the input for import_model_uri. It mirrors the
|
|
// REST surface (`/models/import-uri`) closely so both clients can produce
|
|
// identical responses; the BackendPreference is a flat field rather than the
|
|
// REST `preferences` JSON blob since the LLM only needs to specify a backend
|
|
// name when it disambiguates a multi-backend match.
|
|
type ImportModelURIRequest struct {
|
|
URI string `json:"uri" jsonschema:"The model source. Accepts HuggingFace URLs (https://huggingface.co/...), OCI image references, http(s) URLs to a manifest, file:// paths, or a bare HF repo (e.g. Qwen/Qwen3-4B-GGUF)."`
|
|
BackendPreference string `json:"backend_preference,omitempty" jsonschema:"Optional backend name (e.g. llama-cpp). Required as the second-step retry when a previous import_model_uri call returned ambiguous_backend=true."`
|
|
Overrides map[string]any `json:"overrides,omitempty" jsonschema:"Optional config overrides applied to the discovered model (e.g. context_size)."`
|
|
}
|
|
|
|
// ImportModelURIResponse is what import_model_uri returns. When
|
|
// AmbiguousBackend is true the LLM must surface the candidates to the user
|
|
// and call again with BackendPreference set; the JobID is empty in that case.
|
|
type ImportModelURIResponse struct {
|
|
JobID string `json:"job_id,omitempty"`
|
|
DiscoveredModelName string `json:"discovered_model_name,omitempty"`
|
|
AmbiguousBackend bool `json:"ambiguous_backend,omitempty"`
|
|
Modality string `json:"modality,omitempty"`
|
|
BackendCandidates []string `json:"backend_candidates,omitempty"`
|
|
Hint string `json:"hint,omitempty"`
|
|
}
|
|
|
|
// Branding is the LLM-facing view of the instance's whitelabel settings.
|
|
// Only the configurable text fields and the resolved asset URLs are
|
|
// surfaced — the backing filenames on disk stay an implementation detail.
|
|
type Branding struct {
|
|
InstanceName string `json:"instance_name"`
|
|
InstanceTagline string `json:"instance_tagline"`
|
|
LogoURL string `json:"logo_url"`
|
|
LogoHorizontalURL string `json:"logo_horizontal_url"`
|
|
FaviconURL string `json:"favicon_url"`
|
|
}
|
|
|
|
// SetBrandingRequest is the input for set_branding. Both fields are
|
|
// optional; nil leaves the existing value untouched. Asset uploads are
|
|
// deliberately excluded from MCP — admins use the Settings UI for that.
|
|
type SetBrandingRequest struct {
|
|
InstanceName *string `json:"instance_name,omitempty" jsonschema:"New instance display name (replaces \"LocalAI\" in headers, footers, and the browser tab). Pass an empty string to reset to default."`
|
|
InstanceTagline *string `json:"instance_tagline,omitempty" jsonschema:"Optional short subtitle shown beneath the instance name. Pass an empty string to clear."`
|
|
}
|
|
|
|
// VoiceProfile is the same path-free shape returned by the REST library.
|
|
// Keeping the service type avoids REST/MCP field drift.
|
|
type VoiceProfile = voiceprofile.Profile
|
|
|
|
// CreateVoiceProfileRequest is the MCP/JSON form of profile creation. Audio
|
|
// must be a base64-encoded 16-bit PCM WAV (mono 24 kHz is recommended for
|
|
// portability); the service enforces the same 50 MiB and duration limits as
|
|
// the browser upload route.
|
|
type CreateVoiceProfileRequest struct {
|
|
Name string `json:"name" jsonschema:"Display name for the reusable voice profile."`
|
|
Description string `json:"description,omitempty" jsonschema:"Optional note describing tone, source, or intended use."`
|
|
Language string `json:"language,omitempty" jsonschema:"Optional BCP-47-style language tag such as en-US."`
|
|
Transcript string `json:"transcript" jsonschema:"Exact transcript of the words spoken in the reference clip."`
|
|
AudioBase64 string `json:"audio_base64" jsonschema:"Base64-encoded 16-bit PCM WAV reference, preferably mono 24 kHz, 1-120 seconds and at most 50 MiB decoded."`
|
|
ConsentConfirmed bool `json:"consent_confirmed" jsonschema:"Must be true to confirm authorization to clone this voice."`
|
|
References []CreateVoiceProfileReferenceRequest `json:"references,omitempty" jsonschema:"Optional ordered audio/transcript pairs. When set, these replace the legacy audio_base64 and transcript fields."`
|
|
}
|
|
|
|
type CreateVoiceProfileReferenceRequest struct {
|
|
Transcript string `json:"transcript" jsonschema:"Exact transcript of this reference clip."`
|
|
AudioBase64 string `json:"audio_base64" jsonschema:"Base64-encoded 16-bit PCM WAV reference."`
|
|
}
|
|
|
|
// DeleteVoiceProfileRequest identifies the profile to remove.
|
|
type DeleteVoiceProfileRequest struct {
|
|
ID string `json:"id" jsonschema:"Opaque voice profile UUID returned by list_voice_profiles."`
|
|
}
|
|
|
|
// UsageStatsQuery is the input for get_usage_stats. UserID is optional;
|
|
// when empty the tool returns the calling user's own usage in auth-on
|
|
// mode, or the synthetic local user's usage in single-user no-auth
|
|
// mode. Admins (or the local user) may pass UserID to inspect another
|
|
// user; the LocalAIClient implementation enforces the role check.
|
|
type UsageStatsQuery struct {
|
|
Period string `json:"period,omitempty" jsonschema:"Time window. One of: day, week, month, all. Defaults to month."`
|
|
UserID string `json:"user_id,omitempty" jsonschema:"Optional user id to query. Empty = caller's own usage. Querying another user requires admin role."`
|
|
All bool `json:"all,omitempty" jsonschema:"When true, returns the cluster-wide /api/usage/all view (admin-only when auth is on)."`
|
|
}
|
|
|
|
// UsageStats is the response shape for get_usage_stats. Mirrors what
|
|
// /api/usage and /api/usage/all return so the LLM can correlate
|
|
// dashboard numbers with what it pulls via MCP.
|
|
type UsageStats struct {
|
|
Viewer UsageViewer `json:"viewer"`
|
|
Period string `json:"period"`
|
|
Totals UsageTotals `json:"totals"`
|
|
Buckets []UsageBucket `json:"buckets"`
|
|
}
|
|
|
|
type UsageViewer struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Role string `json:"role,omitempty"`
|
|
}
|
|
|
|
type UsageTotals struct {
|
|
PromptTokens int64 `json:"prompt_tokens"`
|
|
CompletionTokens int64 `json:"completion_tokens"`
|
|
TotalTokens int64 `json:"total_tokens"`
|
|
RequestCount int64 `json:"request_count"`
|
|
}
|
|
|
|
type UsageBucket struct {
|
|
Bucket string `json:"bucket"`
|
|
Model string `json:"model"`
|
|
UserID string `json:"user_id,omitempty"`
|
|
UserName string `json:"user_name,omitempty"`
|
|
PromptTokens int64 `json:"prompt_tokens"`
|
|
CompletionTokens int64 `json:"completion_tokens"`
|
|
TotalTokens int64 `json:"total_tokens"`
|
|
RequestCount int64 `json:"request_count"`
|
|
}
|
|
|
|
// ---- PII / sensitive data tools ----
|
|
|
|
// PIIEventsQuery filters get_pii_events.
|
|
type PIIEventsQuery struct {
|
|
CorrelationID string `json:"correlation_id,omitempty" jsonschema:"Optional X-Correlation-ID join key (binds events to the request and usage record)."`
|
|
UserID string `json:"user_id,omitempty" jsonschema:"Optional user id to scope the query."`
|
|
PatternID string `json:"pattern_id,omitempty" jsonschema:"Optional detector group id (e.g. ner:EMAIL)."`
|
|
Limit int `json:"limit,omitempty" jsonschema:"Maximum events. Defaults to 100."`
|
|
}
|
|
|
|
// PIIEvent is the LLM-facing view of one redaction record. The matched
|
|
// value is never exposed; admins audit by hash_prefix.
|
|
type PIIEvent struct {
|
|
ID string `json:"id"`
|
|
CorrelationID string `json:"correlation_id"`
|
|
UserID string `json:"user_id"`
|
|
Direction string `json:"direction"`
|
|
PatternID string `json:"pattern_id"`
|
|
ByteOffset int `json:"byte_offset"`
|
|
Length int `json:"length"`
|
|
HashPrefix string `json:"hash_prefix"`
|
|
Action string `json:"action"`
|
|
CreatedAt string `json:"created_at"`
|
|
}
|
|
|
|
// MiddlewareStatus is the aggregated /api/middleware/status payload —
|
|
// the React Middleware page renders this in one go. Routing is a
|
|
// placeholder until subsystem 2 lands.
|
|
type MiddlewareStatus struct {
|
|
PII MiddlewarePIIStatus `json:"pii"`
|
|
Router MiddlewareRouterStatus `json:"router"`
|
|
}
|
|
|
|
// MiddlewarePIIStatus shows which models opt in to PII redaction and the
|
|
// NER detector models they reference. The detection policy itself lives
|
|
// on each detector model's pii_detection block.
|
|
type MiddlewarePIIStatus struct {
|
|
EnabledGlobally bool `json:"enabled_globally"`
|
|
Reason string `json:"reason,omitempty"`
|
|
DefaultEnabledForBackends []string `json:"default_enabled_for_backends,omitempty"`
|
|
Models []MiddlewarePIIModel `json:"models"`
|
|
RecentEventCount int `json:"recent_event_count"`
|
|
}
|
|
|
|
// MiddlewarePIIModel is one model row in the per-model PII table.
|
|
type MiddlewarePIIModel struct {
|
|
Name string `json:"name"`
|
|
Backend string `json:"backend"`
|
|
Enabled bool `json:"enabled"`
|
|
Explicit bool `json:"explicit"` // Did YAML set Enabled, or did the backend prefix decide?
|
|
DefaultForBackend bool `json:"default_for_backend"` // Backend matches the auto-on rule (proxy-*).
|
|
Detectors []string `json:"detectors,omitempty"` // NER detector model names this config references.
|
|
}
|
|
|
|
// MiddlewareRouterStatus is the placeholder shape the Routing tab
|
|
// reads. Subsystem 2 fills in Models with real RouterDecision rows.
|
|
type MiddlewareRouterStatus struct {
|
|
Configured bool `json:"configured"`
|
|
Models []string `json:"models"`
|
|
Note string `json:"note,omitempty"`
|
|
}
|
|
|
|
// RouterDecisionsQuery filters get_router_decisions.
|
|
type RouterDecisionsQuery struct {
|
|
CorrelationID string `json:"correlation_id,omitempty" jsonschema:"Optional X-Correlation-ID join key (binds decisions to the request and usage record)."`
|
|
UserID string `json:"user_id,omitempty" jsonschema:"Optional user id to scope the query."`
|
|
RouterModel string `json:"router_model,omitempty" jsonschema:"Optional router model name to filter by (e.g. smart-router)."`
|
|
Limit int `json:"limit,omitempty" jsonschema:"Maximum decisions. Defaults to 100."`
|
|
}
|
|
|
|
// RouterDecision is the LLM-facing view of one routing decision. The
|
|
// prompt is NEVER stored; admins audit by hash if they need to dedupe
|
|
// recurring routing patterns.
|
|
type RouterDecision struct {
|
|
ID string `json:"id"`
|
|
CorrelationID string `json:"correlation_id"`
|
|
UserID string `json:"user_id"`
|
|
RouterModel string `json:"router_model"`
|
|
RequestedModel string `json:"requested_model"`
|
|
ServedModel string `json:"served_model"`
|
|
Classifier string `json:"classifier"`
|
|
Label string `json:"label"`
|
|
Score float64 `json:"score"`
|
|
LatencyMs int64 `json:"latency_ms"`
|
|
Cached bool `json:"cached"`
|
|
CreatedAt string `json:"created_at"`
|
|
}
|
|
|
|
// RouterCorpusEntry is one labelled exemplar for seed_router_corpus.
|
|
type RouterCorpusEntry struct {
|
|
Text string `json:"text" jsonschema:"Example prompt text. Embedded server-side and persisted; NEVER returned by any tool or endpoint."`
|
|
Labels []string `json:"labels" jsonschema:"Policy labels this exemplar activates. Every label must be declared in the router's policies."`
|
|
}
|
|
|
|
// RouterCorpusSeedRequest is the input for seed_router_corpus.
|
|
type RouterCorpusSeedRequest struct {
|
|
Router string `json:"router" jsonschema:"Router model name — the ModelConfig with classifier: knn and a router.knn block."`
|
|
Entries []RouterCorpusEntry `json:"entries" jsonschema:"Labelled exemplars to add. Duplicate texts are skipped, not double-weighted."`
|
|
}
|
|
|
|
// RouterCorpusSeedResult reports the outcome of seed_router_corpus.
|
|
type RouterCorpusSeedResult struct {
|
|
Router string `json:"router"`
|
|
Added int `json:"added"`
|
|
Skipped int `json:"skipped"`
|
|
Total int `json:"total"`
|
|
LabelCounts map[string]int `json:"label_counts"`
|
|
}
|
|
|
|
// RouterCorpusQuery names the router whose corpus to inspect or clear.
|
|
type RouterCorpusQuery struct {
|
|
Router string `json:"router" jsonschema:"Router model name."`
|
|
}
|
|
|
|
// RouterCorpusStats is the count-only inspection surface for a
|
|
// router's KNN corpus. Entry texts are never exposed.
|
|
type RouterCorpusStats 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 []string `json:"embedding_models,omitempty"`
|
|
}
|
|
|
|
// RouterCorpusClearResult reports how many entries clear_router_corpus
|
|
// removed.
|
|
type RouterCorpusClearResult struct {
|
|
Router string `json:"router"`
|
|
Cleared int `json:"cleared"`
|
|
}
|
|
|
|
// VRAMEstimateRequest is the input for vram_estimate. The output type is
|
|
// pkg/vram.EstimateResult — used directly via the LocalAIClient interface
|
|
// so the LLM sees the same shape (size_bytes/size_display/vram_bytes/
|
|
// vram_display) that the REST endpoint returns.
|
|
type VRAMEstimateRequest struct {
|
|
ModelName string `json:"model_name" jsonschema:"Installed model name."`
|
|
ContextSize int `json:"context_size,omitempty" jsonschema:"Context size in tokens."`
|
|
GPULayers int `json:"gpu_layers,omitempty" jsonschema:"Number of layers to offload to GPU. -1 for all."`
|
|
KVQuantBits int `json:"kv_quant_bits,omitempty" jsonschema:"KV cache quantization bits (e.g. 4, 8, 16)."`
|
|
}
|