AI Providers
fabric-server does not run an LLM itself. Every AI turn is delegated to fabric-harness (the sole AI engine) — the agent loop, tool execution, and provider calls all live in the harness. fabric-server has only two backends:
ai.backend: harness(default) — forwards each turn to the harnessai.backend: stub— an in-process echo engine for CI / UI dev; hits no provider
The former in-process
AnthropicEngine/GeminiEngine/OpenAIEnginewere removed 2026-06-08. BYOK now forwards the user’s key to the harness as a per-run override, rather than running a parallel in-process engine.
Which provider the harness runs on
The harness holds the provider keys (configured on the harness side; it picks a
provider for which a key is registered, falling back to the echo stub when none
is). A session runs by default on the provider fabric-server names via
ai.harness_provider, with the model overridden by ai.model.
The harness natively supports four provider kinds:
ai.harness_provider | Vendor | Protocol |
|---|---|---|
deepseek (default) | DeepSeek | OpenAI-compatible |
openai | OpenAI | OpenAI-compatible |
anthropic | Anthropic Claude | native Messages API |
google | Google Gemini | native API |
The wider OpenAI-compatible vendor registry lives in
providers.go (Providers) —
it supplies each vendor’s base_url + default model, reused by BYOK and the
OpenAI-compatible endpoints (below).
Model catalog (the single source of truth)
Which models exist, what they cost, how big their context is, and how to ask
them to reason — all four come from one place: llmgw’s model
catalog, GET /v1/catalog.
Before it, fabric-server kept four private copies (providers.go’s default
models, byok_catalog.go’s BYOK picker, platform_models.go’s platform picker,
usage/pricing.go’s price table) with nothing tying them together. That is how
claude-opus-4-7 came to bill at claude-opus-4-1’s price — 3x too high, on
every turn’s cost and quota — with no test able to notice, and how a retired
model id could sit in a picker until a user hit a 404.
Those four are now views over the catalog:
| Surface | Reads | Without a catalog |
|---|---|---|
Platform picker (GET /ai/models) | ai.PlatformModelsNow() | falls back to platform_models.go |
BYOK vendor picker (GET /byok/vendors) | ai.BYOKVendorsNow() | falls back to byok_catalog.go |
| BYOK vendor → endpoint | ai.BYOKHarnessTarget() | falls back to providers.go |
| Billing rates (incl. tiers) | usage.SetPriceSource (injected by ai) | falls back to usage/pricing.go |
FABRIC_AI_CATALOG_URL points at llmgw; fabric-server polls it hourly
(llmgw refreshes the catalog daily at 00:00 CST). An unreachable llmgw never
blocks fabric-server’s boot — every consumer falls back to its release-time
table, so an outage costs freshness, not availability.
Public face: https://www.appunvs.com/models, served from
catalog.appunvs.com/v1/catalog — path-whitelisted, because the rest of llmgw
spends platform provider keys.
Tiers: who may use which models
- Platform-paid (user spends credits) =
platform_eligible, currently mainland vendors only — foreign vendors need our own egress and cost more. - BYOK (user’s key, user’s egress) =
byok_selectable, includes Claude / Gemini / ChatGPT. - llmgw’s protocol layer supports all four kinds regardless.
Vendors are a closed set — the user picks from the catalog and the server
supplies the base_url; there is no free-text endpoint field. Since 2026-07 the
model is validated against the catalog too (ai.ValidBYOKModel): daily refresh
is what made a closed list practical without a server release per vendor launch.
OpenAI-compatible registry (providers.go, fallback)
| Backend id | Vendor | Env var (key convention) |
|---|---|---|
openai | OpenAI | OPENAI_API_KEY |
deepseek | DeepSeek | DEEPSEEK_API_KEY |
moonshot | Moonshot (Kimi) | MOONSHOT_API_KEY |
zhipu | Zhipu GLM | ZHIPU_API_KEY |
dashscope | Aliyun Bailian (Qwen) | DASHSCOPE_API_KEY |
minimax | MiniMax | MINIMAX_API_KEY |
doubao | ByteDance Doubao | ARK_API_KEY |
xiaomi | Xiaomi MiMo | MIMO_API_KEY |
Each vendor’s default model id is authoritative in
providers.go— this doc does not copy specific model ids (they drift).go test ./internal/ai/’sTestProviderRegistryShapeenforces that every provider fills in Name / BaseURL / EnvAPIKey. The env vars above are just each SDK/CLI’s default convention.
Configuration
On the fabric-server side (config.yaml or FABRIC_AI_*):
ai:
backend: harness # "harness" (default) or "stub"
harness_url: http://localhost:8090 # fabric-harness internal address
harness_secret: ${FABRIC_AI_HARNESS_SECRET} # = the harness's FABRIC_HARNESS_INTERNAL_SECRET
harness_provider: deepseek # deepseek | openai | anthropic | google (empty → deepseek)
model: "" # optional; overrides the provider's default model
max_iters: 10
max_tokens: 8000
Provider API keys are configured on the harness side — fabric-server no
longer holds provider keys. The harness picks a provider by which key is
registered; see fabric/harness.
BYOK (bring your own key)
A user picks a BYOK vendor and supplies their own key; that turn’s
{kind, base_url, key, model} is forwarded to the harness as a per-run
override — never persisted, never reused on another turn. The vendor catalog
is byok_catalog.go:
- OpenAI-compatible (base_url from the
providers.goregistry):deepseek/kimi/qwen/doubao/glm/minimax/xiaomi/chatgpt - Anthropic:
claude - Gemini:
gemini
Adding an OpenAI-compatible provider
- Add a row to the
Providersmap inproviders.go(ID/Name/BaseURL/ModelChat/DocsURL/EnvAPIKey). - If it isn’t strictly OpenAI-compatible (tool-call protocol / field-name
differences), first verify with
curlthatPOST /chat/completions+tools+streamyields propertool_callsdeltas before adding it. - Run
go test ./internal/ai/—TestProviderRegistryShaperequires the mandatory fields. - Add it to
byok_catalog.goto make it available as a BYOK vendor.
Routing (multi-provider dispatch)
There is no fabric-server-side runtime routing across providers — a session is
bound to one harness_provider, and per-user switching goes through the BYOK
per-run override. Picking a provider by turn characteristics (needs reasoning?
vision? a fast-apply small edit?) is the harness’s job, not fabric-server’s.