mirror of
https://github.com/mudler/LocalAI.git
synced 2026-06-02 05:06:34 -04:00
Add a routing middleware stack and a cloud-proxy backend. * cloud-proxy: a Go gRPC backend that forwards OpenAI- and Anthropic-shaped chat requests to upstream providers, with an optional translate mode (OpenAI request -> Anthropic /v1/messages -> OpenAI response) and full tool-calling support. * routing: admission control, content-aware model routing (embedding cache + classifier + rerank + Arch-Router score), PII detection/redaction (regex + NER) with streaming filter and OpenAI/Anthropic adapters, and a per-user/per-key billing recorder backed by GORM or in-memory storage. * middleware: UsageMiddleware records usage via the billing recorder, plus admission, route-model, usage-stamp and trace middlewares. * observability: BackendTrace ring buffer stores full request bodies (capped), MITM proxy emits structured trace events, and router classifier decisions surface at /api/router/decide. * gallery: Arch-Router-1.5B (Q4_K_M and Q8_0). * UI: cloud-proxy model-editor fields, classifier system-prompt and score-normalization config, and a Traces page rendering request bodies. Assisted-by: claude-code:claude-opus-4-7 [Read] [Edit] [Bash] Signed-off-by: Richard Palethorpe <io@richiejp.com>
100 lines
4.4 KiB
Go
100 lines
4.4 KiB
Go
package localaitools
|
|
|
|
import (
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
// toolToHTTPRoute is the canonical mapping between MCP tools and the
|
|
// LocalAI admin REST endpoints they wrap. The httpapi.Client MUST hit the
|
|
// listed route for the tool; the inproc.Client may bypass HTTP and call
|
|
// services directly, but the on-the-wire shape is documented here so the
|
|
// two sides stay aligned.
|
|
//
|
|
// Updating the map is REQUIRED when:
|
|
// - You add a Tool* constant (tools.go).
|
|
// - You change which REST endpoint the httpapi.Client calls.
|
|
//
|
|
// The TestToolHTTPRouteMappingComplete spec below FAILS until every Tool*
|
|
// is in the map. That is the drift detector — see
|
|
// .agents/localai-assistant-mcp.md for the contributor contract.
|
|
//
|
|
// "(none)" is a deliberate sentinel for tools whose data is not exposed
|
|
// over a single REST endpoint (e.g. system_info aggregates data the
|
|
// inproc client picks up directly from services). The httpapi.Client may
|
|
// approximate via the welcome JSON; the test still requires an entry so
|
|
// the contributor explicitly acknowledges the asymmetry.
|
|
var toolToHTTPRoute = map[string]string{
|
|
// Read-only tools.
|
|
ToolGallerySearch: "GET /models/available",
|
|
ToolListInstalledModels: "GET / (welcome JSON, ModelsConfig field)",
|
|
ToolListGalleries: "GET /models/galleries",
|
|
ToolGetJobStatus: "GET /models/jobs/:uuid",
|
|
ToolGetModelConfig: "(none) — no JSON-only REST yet; httpapi.Client returns a documented stub",
|
|
ToolListBackends: "GET /backends",
|
|
ToolListKnownBackends: "GET /backends/known",
|
|
ToolSystemInfo: "GET / (welcome JSON)",
|
|
ToolListNodes: "GET /api/nodes",
|
|
ToolVRAMEstimate: "POST /api/models/vram-estimate",
|
|
ToolGetBranding: "GET /api/branding",
|
|
ToolGetUsageStats: "GET /api/usage (or /api/usage/all when all=true)",
|
|
ToolListPIIPatterns: "GET /api/pii/patterns",
|
|
ToolGetPIIEvents: "GET /api/pii/events",
|
|
ToolTestPIIRedaction: "POST /api/pii/test",
|
|
ToolGetMiddlewareStatus: "GET /api/middleware/status",
|
|
ToolGetRouterDecisions: "GET /api/router/decisions",
|
|
|
|
// Mutating tools.
|
|
ToolInstallModel: "POST /models/apply",
|
|
ToolImportModelURI: "POST /models/import-uri",
|
|
ToolDeleteModel: "POST /models/delete/:name",
|
|
ToolEditModelConfig: "PATCH /api/models/config-json/:name",
|
|
ToolReloadModels: "POST /models/reload",
|
|
ToolInstallBackend: "POST /backends/apply",
|
|
ToolUpgradeBackend: "POST /backends/upgrade/:name",
|
|
ToolToggleModelState: "PUT /models/toggle-state/:name/:action",
|
|
ToolToggleModelPinned: "PUT /models/toggle-pinned/:name/:action",
|
|
ToolSetBranding: "POST /api/settings (instance_name, instance_tagline)",
|
|
ToolSetPIIPatternAction: "PUT /api/pii/patterns/:id",
|
|
ToolPersistPIIPatterns: "POST /api/pii/patterns/persist",
|
|
}
|
|
|
|
// allKnownTools is the union of expectedFullCatalog (defined in
|
|
// server_test.go). Keeping a single source of truth — the slice from
|
|
// server_test — and asserting the route map covers every entry catches
|
|
// the case "you added a Tool* but forgot to register it as MCP" indirectly
|
|
// (it'd be missing from expectedFullCatalog, which has its own assertion
|
|
// in TestServerRegistersExpectedToolCatalog).
|
|
var _ = Describe("Tool ↔ HTTP route coverage map", func() {
|
|
It("has an entry for every Tool* in the published catalog", func() {
|
|
for _, name := range expectedFullCatalog {
|
|
_, ok := toolToHTTPRoute[name]
|
|
Expect(ok).To(BeTrue(),
|
|
"Tool %q is in expectedFullCatalog but not in toolToHTTPRoute. "+
|
|
"When adding an MCP tool, update toolToHTTPRoute in coverage_test.go "+
|
|
"with the REST endpoint the httpapi.Client calls (or '(none)' with a reason).",
|
|
name)
|
|
}
|
|
})
|
|
|
|
It("does not document tools that no longer exist in the catalog", func() {
|
|
catalog := map[string]struct{}{}
|
|
for _, name := range expectedFullCatalog {
|
|
catalog[name] = struct{}{}
|
|
}
|
|
for name := range toolToHTTPRoute {
|
|
_, ok := catalog[name]
|
|
Expect(ok).To(BeTrue(),
|
|
"toolToHTTPRoute documents %q but the tool is not registered. "+
|
|
"Remove the stale entry.",
|
|
name)
|
|
}
|
|
})
|
|
|
|
// Deliberate non-test: we don't enumerate admin REST routes here. That
|
|
// would require booting Application or parsing core/http/routes/localai.go,
|
|
// both of which are brittle. The contract for "new admin REST endpoint
|
|
// → MCP tool" is enforced by the PR checklist in
|
|
// .agents/api-endpoints-and-auth.md, not by this test.
|
|
})
|