mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-11-02 14:11:49 +08:00
546 lines
33 KiB
Markdown
546 lines
33 KiB
Markdown
# Models
|
||
|
||
**Pick a model. Just one. Add more later.**
|
||
|
||
MateClaw doesn't care which LLM you use. It talks to every mainstream provider through five protocol adapters, supports 15+ cloud providers and 4 local runtimes, and lets you swap models at runtime without touching agent configuration. The only opinion MateClaw has is that you should **start with one and add more when you need them** — not configure everything on day one.
|
||
|
||
---
|
||
|
||
## What's supported
|
||
|
||
### Cloud providers
|
||
|
||
| Provider | Example models | Protocol | Notes |
|
||
|----------|---------------|----------|-------|
|
||
| **DashScope** (Alibaba) | Qwen-Max, Qwen-Plus, Qwen-Turbo, Qwen-VL, Qwen-Long | dashscope | Default out of the box |
|
||
| **DashScope (OpenAI-compatible)** | Qwen3.5-Plus, Qwen3.6-Plus, Qwen3 VL Plus, etc. (dot-versioned families) | openai | See "Two DashScope variants" below |
|
||
| **Bailian Token Plan** | Bailian token-bundle plan | dashscope | 7 seeded models; long tokens supported |
|
||
| **OpenAI** | GPT-4o, GPT-4o-mini, GPT-5.5, o1, o3, o4-mini | openai | Standard OpenAI API |
|
||
| **OpenAI OAuth (ChatGPT Plus/Pro)** | GPT-4o, o3, o4-mini via subscription | openai | Browser-based OAuth — no API key |
|
||
| **Anthropic** | **Claude Opus 4.8 / 4.8 Fast** (1.5.0+), Claude 4.7, Claude 4.6 Sonnet, Claude 4.5 Haiku | anthropic | Native Messages API; both 4.8 variants support the `xhigh` thinking tier |
|
||
| **Anthropic Claude Code OAuth** | Claude Opus 4.8 / 4.7 / 4.6 via Claude Pro/Max/Team subscription | anthropic | Browser OAuth + manual-paste flow — no API key |
|
||
| **Google Gemini** _(native)_ | gemini-2.5-flash, gemini-3-pro-image-preview, gemini-2.5-flash-image | gemini | Native `generateContent` API (not OpenAI-compatible) — see "Native Gemini" below |
|
||
| **xAI / Grok** | Grok 3, Grok 4 | openai | OpenAI-compatible (base URL + API key); xAI brand icon in the UI |
|
||
| **DeepSeek** | deepseek-chat, deepseek-coder, **DeepSeek V4 flash + pro** (thinking-mode) | openai | OpenAI-compatible |
|
||
| **Kimi (Moonshot)** | moonshot-v1-8k/32k/128k | openai | OpenAI-compatible |
|
||
| **Zhipu AI** | GLM-5-Turbo, GLM-5V-Turbo, GLM-5, GLM-5.1, **GLM-5.2** | openai | OpenAI-compatible; CN + international standard endpoints plus two Coding Plan subscription endpoints |
|
||
| **Volcano Engine Agent Plan** _(1.8.0+)_ | GLM-5.2 (primary) and more | openai | OpenAI-compatible; agent-plan endpoint (`/api/plan/v3`) |
|
||
| **MiniMax** | abab6.5, abab5.5; expanded video catalog + CN endpoint | anthropic | Anthropic Messages API-compatible (endpoint `/anthropic`) |
|
||
| **SiliconFlow CN/INTL** | Routed inference across hosted models | openai | Two endpoints, OpenAI-compatible |
|
||
| **OpenCode** | Code-tuned routing | openai | OpenAI-compatible |
|
||
| **OpenRouter** | 200+ models with free tier | openai | Routes to any upstream with one key |
|
||
| **Any OpenAI-compatible** | Your own vLLM, etc. | openai | Custom base URL |
|
||
|
||
### Local runtimes
|
||
|
||
| Runtime | Example models | Protocol | Notes |
|
||
|---------|---------------|----------|-------|
|
||
| **Ollama** | Gemma 3/4, Qwen 3, Llama 3.1, DeepSeek R1, Mistral | ollama | **Auto-detected at startup** on `localhost:11434` |
|
||
| **LM Studio** | Any GGUF model | openai | OpenAI-compatible server |
|
||
| **llama.cpp** | Any GGUF model | openai | Via llama-server |
|
||
| **MLX** | Apple Silicon via mlx-lm | openai | mlx-lm's OpenAI-compatible server |
|
||
|
||
### Protocol adapters
|
||
|
||
Five protocols cover everything:
|
||
|
||
| Protocol | Used by |
|
||
|----------|---------|
|
||
| **OpenAI** | OpenAI, Kimi, DeepSeek, Zhipu, OpenRouter, LM Studio, llama.cpp, MLX |
|
||
| **Anthropic** | Claude family, MiniMax |
|
||
| **DashScope** | Qwen family |
|
||
| **Gemini** | Google Gemini family |
|
||
| **Ollama** | Locally hosted models via Ollama |
|
||
|
||
Any OpenAI-compatible service works — just point `base-url` at it.
|
||
|
||
---
|
||
|
||
## Two DashScope variants
|
||
|
||
Same `sk-` API key, **two endpoints** that ship different model families:
|
||
|
||
| Item | DashScope | DashScope (OpenAI-compatible) |
|
||
|---|---|---|
|
||
| Endpoint | `dashscope.aliyuncs.com/api/v1` (native) | `dashscope.aliyuncs.com/compatible-mode/v1` (OpenAI-compatible) |
|
||
| Protocol | DashScope native | OpenAI standard (same shape as GPT-4 / DeepSeek / Kimi) |
|
||
| Built-in web search (`enable_search`) | ✅ Supported | ❌ Not supported |
|
||
| Models | Qwen-Max / Plus / Turbo / Long, Qwen-VL, Qwen3-Max, DeepSeek-V3.2, etc. | **Dot-versioned** new families: Qwen3.5-Plus, Qwen3.6-Plus, Qwen3 VL-Plus, etc. |
|
||
|
||
**Why two providers**: Alibaba publishes the dot-versioned families (`qwen3.5-*` / `qwen3.6-*` / `qwen3-vl-*`) only on the OpenAI-compatible endpoint; the native protocol returns `400 InvalidParameter` for them. The two providers **share the same sk- key** — paste it once, it works for both.
|
||
|
||
**Which to pick**:
|
||
- Want Qwen-Max / Plus / Turbo + built-in search / DeepSeek-V3.2 → **DashScope**
|
||
- Want Qwen3.5-Plus / Qwen3.6-Plus / Qwen3 vision-language → **DashScope (OpenAI-compatible)**
|
||
- **Enable both** if you want — same key, models just appear under different cards
|
||
|
||
---
|
||
|
||
## Native Gemini
|
||
|
||
::: tip New in 1.4.0
|
||
Gemini no longer rides on an OpenAI-compatibility shim — MateClaw talks to Google's **native `generateContent` API** directly.
|
||
:::
|
||
|
||
Plenty of products bolt Gemini on as "just another OpenAI-compatible endpoint" and then hit walls around system instructions, function calling, and inline images. MateClaw speaks Gemini's own protocol instead:
|
||
|
||
- **Native chat builder** — maps `systemInstruction`, `functionCall` / `functionResponse` (tool-call turns), and inline image parts (multimodal input) correctly
|
||
- **Streaming SSE parsing** — parses Gemini's streaming response format chunk by chunk
|
||
- **JSON Schema sanitizing** — automatically strips JSON Schema keywords Gemini rejects, so tool definitions aren't refused
|
||
- **Startup liveness probe** — sends a lightweight request at startup to confirm the credentials and model are reachable
|
||
|
||
Configure it under `Settings → Models → Add Provider`, pick the **Gemini** provider, paste your API key. Example models: `gemini-2.5-flash`, `gemini-3-pro-image-preview`, `gemini-2.5-flash-image`. Image generation runs through the same native path — see [Multimodal → Image generation](./multimodal#image-generation-six-providers).
|
||
|
||
---
|
||
|
||
## Adding a provider
|
||
|
||
**A fresh MateClaw install has an empty provider list. That's deliberate.**
|
||
|
||
You don't need to see 16 providers. You need **one that works.**
|
||
|
||
`Settings → Models → Add Provider` opens a drawer with the full catalog. Local runtimes (Ollama, LM Studio, llama.cpp, MLX — no API key required) appear first; cloud providers (DashScope, OpenAI, Anthropic, DeepSeek, etc.) follow.
|
||
|
||
Three steps:
|
||
|
||
1. **Find the row you want and click Enable** — the provider joins your main list
|
||
2. **Fill in the base URL** (pre-filled for known providers) **and paste your API key** — encrypted at rest, masked in UI
|
||
3. **Save → Test Connection** — the system sends a lightweight request and reports success or error
|
||
|
||
Close the drawer and the main list shows only the providers you've enabled. **Model picker, chat page, agent editor — every place that surfaces models, surfaces only the ones you opted in.**
|
||
|
||
::: tip Existing installs (V55 migration)
|
||
Providers already in use are **not** turned off. V55 auto-marks a provider as enabled if any of these are true:
|
||
- Has a real API key configured
|
||
- Has an OAuth token
|
||
- Has been used by a chat session in the last 30 days
|
||
- Owns the current default model
|
||
|
||
Untouched, never-used placeholder providers go back into the drawer — flip them on the next time you need them.
|
||
:::
|
||
|
||
---
|
||
|
||
## Enabling / disabling a provider
|
||
|
||
Every provider card in the main list has an **Enable / Disable** toggle. **You must enable a provider before you can use it** — that's the core product contract from v1.1.0 onward.
|
||
|
||
- **Disable** — the provider disappears from the model picker, chat page, and agent editor immediately. **Configuration is preserved**; flip it back on and everything is exactly where you left it.
|
||
- **If you disable the provider that owns the current default model**, the system automatically promotes a model on a still-enabled provider as the new default — no broken next-message.
|
||
- **Enable** — the provider reappears everywhere. If it has never had an API key set, you'll be prompted to configure it.
|
||
|
||
This separates "I have a key for this provider but I'm not using it today" from "I don't have this provider." Switching providers temporarily no longer means deleting configuration.
|
||
|
||
### ChatGPT OAuth — no API key needed
|
||
|
||
Have a ChatGPT Plus or Pro account? MateClaw can talk to OpenAI's chat endpoint through **browser-based OAuth** — log in the way you normally would, your subscription is used directly. GPT-4o, o3, and o4-mini become available immediately.
|
||
|
||
`Settings → Models → Add Provider → OpenAI OAuth`. A browser window opens. Token exchange happens on the backend; **credentials never leave your machine**.
|
||
|
||
### Device authorization grant — for remote / headless deployments
|
||
|
||
Browser-callback OAuth needs the IDP's redirect to land back on a `localhost` port that *your* browser can reach. That's fine when MateClaw runs on your laptop and breaks the moment you put it on a server, in a container, or on a host that doesn't expose a loopback socket to your client.
|
||
|
||
For those cases, OpenAI OAuth automatically switches to **Device Authorization Grant (RFC 8628)** — the same flow ChatGPT desktop and `gh auth login` use. No callback, no port mapping.
|
||
|
||
`Settings → Models → Add Provider → OpenAI OAuth` on a non-localhost host pops a dialog showing:
|
||
|
||
- A short **user code** (monospace, copyable)
|
||
- A **verification URL** at `auth.openai.com/codex/device` — open it in any browser on any device
|
||
- A live **countdown** until the device code expires (default 15 min)
|
||
|
||
Enter the user code in your browser, authorize, and the dialog closes itself the moment the backend's poll loop sees `COMPLETED`.
|
||
|
||
**How MateClaw decides which flow to use:**
|
||
|
||
| `mateclaw.oauth.openai.deployment-mode` | Behaviour |
|
||
|---|---|
|
||
| `auto` *(default)* | `localhost` / `127.0.0.1` / `::1` → browser callback; everything else → device code |
|
||
| `local` | Force browser callback (loopback server) |
|
||
| `device_code` | Force device code |
|
||
| `manual_paste` | Force the legacy paste-the-callback-URL flow |
|
||
|
||
If `local` mode can't bind a loopback port (port in use, sandbox refused), it falls through to `manual_paste` automatically.
|
||
|
||
**Backend endpoints:**
|
||
|
||
| Method | Path | Purpose |
|
||
|---|---|---|
|
||
| `POST` | `/api/v1/oauth/openai/device/start` | Begin a session — returns `deviceAuthId`, `userCode`, `verificationUrl`, `intervalSeconds`, `expiresInSeconds` |
|
||
| `POST` | `/api/v1/oauth/openai/device/poll` | Poll one session by `deviceAuthId` — returns `PENDING` / `COMPLETED` / `EXPIRED` |
|
||
| `POST` | `/api/v1/oauth/openai/device/cancel` | Drop the session (e.g. user closed the dialog) |
|
||
|
||
The frontend respects the `intervalSeconds` OpenAI returns (typically 5 s); the server enforces a min poll interval (default 3 s) to keep load bounded. Expired sessions are swept every 5 minutes.
|
||
|
||
Token persistence and refresh use the **same code path** as the browser-callback flow, so once the dialog closes there's no behavioural difference.
|
||
|
||
### Anthropic Claude Code OAuth
|
||
|
||
Same pattern, same outcome: have a Claude Pro / Max / Team subscription? Sign in with the **same OAuth flow Claude Code itself uses** — no `sk-ant-…` API key required. Claude 4.7 / 4.6 / 4.5 Haiku come online through your subscription.
|
||
|
||
`Settings → Models → Add Provider → Anthropic Claude Code OAuth`. Two flows are supported:
|
||
|
||
- **Browser callback** — local install, browser pops up, you click through, token lands in MateClaw
|
||
- **MANUAL_PASTE** — for remote-server deployments where the browser can't reach the backend, you complete the auth in your local browser and paste the token in
|
||
|
||
Anti-abuse-gate compliant: Claude Code identity is injected into the system prompt, the request shape (UA / accept headers / `system` array form / `mcp_` tool-name prefixes) matches Claude Code's wire format exactly so the requests aren't rejected.
|
||
|
||
---
|
||
|
||
## Model discovery
|
||
|
||
Providers that expose a model list (OpenAI, Ollama, LM Studio, OpenRouter, etc.) support **Model Discovery** — one click and MateClaw fetches every model the provider offers.
|
||
|
||
- `Settings → Models → [provider card] → Discover Models`
|
||
- System queries the provider's `/v1/models` endpoint
|
||
- Discovered models appear with name, context window, pricing
|
||
- Add them one by one or all at once
|
||
|
||
For OpenRouter specifically, Model Discovery surfaces the **200+ free-tier models** — pick a free model and you have a working setup with zero cost.
|
||
|
||
### Custom (self-added) providers
|
||
|
||
Compatible endpoints you create via "Add provider" (vLLM / Xinference / LocalAI / gateways) enable discovery by protocol: `openai-compatible`, `dashscope-native`, `gemini-native`, and `anthropic-messages` get the **Discover models** button by default; OAuth protocols (ChatGPT OAuth, Claude Code OAuth) do not — their discovery runs through a dedicated sign-in callback, unrelated to `baseUrl`.
|
||
|
||
If the endpoint's model-listing path is not the standard `/v1/models` (e.g. a reverse proxy adds a `/openai/v1/models` prefix), override it with a `modelsPath` entry in the provider's Generate Kwargs (JSON):
|
||
|
||
```json
|
||
{ "modelsPath": "/openai/v1/models" }
|
||
```
|
||
|
||
The sibling `completionsPath` key overrides the chat-completions path (default `/v1/chat/completions`); the two are independent. If the endpoint exposes no OpenAI-style listing at all, just use "Add model" to enter model ids manually.
|
||
|
||
### Ollama auto-detection on startup
|
||
|
||
No manual configuration needed. On startup:
|
||
|
||
1. **Ping** `http://127.0.0.1:11434`
|
||
2. **Discover** — fetch pulled models via `/v1/models`
|
||
3. **Register** — add to `mate_model_config`
|
||
4. **Enable** — auto-enable matching pre-configured models
|
||
5. **Tag rewrite** — rewrites seed `:latest` tags to actual installed versions (`deepseek-r1:latest` → `deepseek-r1:7b`), no more `model not found` 404s
|
||
|
||
If Ollama isn't running, silently skipped.
|
||
|
||
::: tip Default behavior
|
||
- Models without tool support (`deepseek-r1`, `gemma*`, `phi3/4`, etc.) won't accidentally activate as default — they're blocklisted
|
||
- Models that are not callable on DashScope native protocol are auto-purged on startup; dot-versioned Qwen families now live on the DashScope (OpenAI-compatible) provider instead
|
||
- DashScope model discovery uses protocol-aware probing, skipping non-chat modalities
|
||
:::
|
||
|
||
**Pre-configured Ollama models** (disabled until discovered, then auto-enabled):
|
||
|
||
| Model | `model_name` |
|
||
|-------|-------------|
|
||
| Gemma 3 | `gemma3:latest` |
|
||
| Gemma 4 | `gemma4:latest` |
|
||
| Qwen 3 | `qwen3:latest` |
|
||
| Llama 3.1 | `llama3.1:latest` |
|
||
| DeepSeek R1 | `deepseek-r1:latest` |
|
||
| Mistral | `mistral:latest` |
|
||
|
||
Setup:
|
||
|
||
```bash
|
||
# Install Ollama from ollama.com, then:
|
||
ollama pull gemma3
|
||
ollama pull qwen3
|
||
```
|
||
|
||
Restart MateClaw. Auto-discovered, added, enabled.
|
||
|
||
---
|
||
|
||
## Database schema
|
||
|
||
### `mate_model_provider`
|
||
|
||
| Column | Purpose |
|
||
|--------|---------|
|
||
| `id` | Primary key |
|
||
| `name` | Provider identifier |
|
||
| `display_name` | Human-readable name |
|
||
| `protocol` | `dashscope` / `openai` / `ollama` / `anthropic` / `gemini` |
|
||
| `base_url` | API base URL |
|
||
| `api_key` | Encrypted API key |
|
||
| `oauth_tokens` | OAuth tokens (ChatGPT Plus/Pro) |
|
||
| `is_local` | True for local runtimes |
|
||
| `enabled` | Provider master switch — when off, hidden from every model picker; configuration is preserved (v1.1.0+) |
|
||
|
||
### `mate_model_config`
|
||
|
||
| Column | Purpose |
|
||
|--------|---------|
|
||
| `id` | Primary key |
|
||
| `provider_id` | FK to `mate_model_provider` |
|
||
| `model_name` | Actual model identifier |
|
||
| `display_name` | Human-readable name |
|
||
| `temperature` | Default temperature (0.0 – 2.0) |
|
||
| `max_tokens` | Max output tokens |
|
||
| `top_p` | Top-p sampling |
|
||
| `group_name` | UI grouping (e.g., "Reasoning", "Fast", "Vision") |
|
||
| `enabled` | Whether the model is available |
|
||
|
||
### Embedding models
|
||
|
||
No `EMBEDDING_API_KEY` env vars. Embedding models are regular rows in `mate_model_config` with `model_type='embedding'`. They show up alongside chat models in `Settings → Models`. Knowledge bases pick their embedding model from a dropdown.
|
||
|
||
::: tip New in 1.4.0 ([issue #79](https://github.com/mateaix/mateclaw/issues/79))
|
||
**Embedding models from any provider.** In the embedding section of `Settings → Models`, configure an embedding model from any provider — it **reuses that provider's API key**, so there's no separate `EMBEDDING_API_KEY`. Each knowledge base picks its embedding model from a dropdown. Keyless local proxies use a no-op placeholder key; the protocol is resolved from the provider's chat-model / protocol setting, so you never hand-enter it.
|
||
:::
|
||
|
||
### Anthropic prompt caching
|
||
|
||
System prompts, agent personas, tool definitions — automatically marked with `cache_control: ephemeral` on Anthropic-compatible endpoints. First request warms the cache, every follow-up gets a cache hit. The Dashboard tracks `cache_read_tokens` / `cache_write_tokens` daily.
|
||
|
||
### Thinking depth / `reasoning_effort`
|
||
|
||
**Which models honor this parameter**: `reasoning_effort` is only valid for the OpenAI reasoning family (`gpt-5*` / `o1*` / `o3*` / `o4*`), and only when delivered through the OpenAI or Azure-OpenAI providers. Every other provider (DeepSeek, Kimi, DashScope, Ollama, self-hosted OpenAI-compatible gateways, etc.) will either error or behave oddly if this parameter reaches them.
|
||
|
||
**Three product contracts**:
|
||
|
||
1. **Chat models that don't support chain-of-thought** ignore the front-end "deep thinking = high" selector entirely — this is a capability property, not a UI setting. The thinking-depth selector automatically grays out when the current model is not reasoning-capable.
|
||
2. **`generateKwargs.reasoningEffort` at provider level** only takes effect on whitelisted providers. Setting it on DeepSeek / Kimi / other OpenAI-compatible providers is silently dropped with a WARN log; the parameter is never sent.
|
||
3. **Failover** re-checks at egress time: if the primary is GPT-5 and the fallback is DeepSeek, `reasoning_effort` is stripped before hitting DeepSeek, so leaked primary options can't 400 the fallback.
|
||
|
||
**How to enable DeepSeek thinking**: DeepSeek's thinking mode does **not** use `reasoning_effort`.
|
||
|
||
- `deepseek-reasoner`: thinking is on by default; no config needed.
|
||
- `deepseek-chat` with thinking: follow DeepSeek's official docs and set `{"thinking": {...}}` under the provider's `generateKwargs.extra_body`. **Do not** set `reasoningEffort`.
|
||
|
||
**Kimi K2.5 thinking**: the model activates thinking natively; don't set `reasoning_effort`.
|
||
|
||
**Multi-round tool calls + thinking**: thinking-capable models (DeepSeek-Reasoner / GPT-5 / Kimi K2.5 / Xiaomi MiMo) correctly round-trip historical `reasoning_content` during ReAct multi-round tool calls. Cross-user-turn history is cleared at the boundary, in-turn history is preserved — matching DeepSeek's "pass back within a turn, reset across turns" contract.
|
||
|
||
**Xiaomi MiMo thinking-mode multi-turn fix** ([issue #189](https://github.com/mateaix/mateclaw/issues/189)): MiMo's `reasoning_content` is now kept correctly across turns in thinking mode, instead of being lost on subsequent turns.
|
||
|
||
---
|
||
|
||
## Grouped model selector
|
||
|
||
When your deployment has a lot of models configured, the chat model picker groups them by provider and tag. Searchable dropdown lets you filter by name, provider, or group — "all Qwen", "all reasoning models", "everything under 7B". Groups are defined in the `group_name` column.
|
||
|
||
Became a real thing when agents could be bound to different models per task — a reasoning model for Plan-Execute, a fast cheap model for Chat, a vision model for image understanding.
|
||
|
||
---
|
||
|
||
## Active model switching at runtime
|
||
|
||
MateClaw uses a single **active model** as the global default. Agents that don't specify their own use it.
|
||
|
||
- **UI:** `Settings → Models → [model card] → Set as Active`
|
||
- **API:** `PUT /api/v1/models/active`
|
||
|
||
Takes effect **immediately** — no restart. Next message uses the new model. In-flight conversations unaffected.
|
||
|
||
Per-agent override supported: bind a specific agent to a specific model config.
|
||
|
||
::: tip New in 1.4.0
|
||
- **Per-conversation model selection** ([issue #150](https://github.com/mateaix/mateclaw/issues/150)): in the chat UI you can switch the model for **just the current conversation**, without touching the global active model or any other conversation. See [Chat & Messaging](./chat).
|
||
- **A single bad model id no longer evicts the whole provider**: when discovery / probing hits one invalid model identifier, only that model is skipped — the rest of the provider's models stay available.
|
||
:::
|
||
|
||
---
|
||
|
||
## Per-model testing
|
||
|
||
Every model card has a **Test** button. Click it, system sends a simple prompt, shows:
|
||
|
||
- Actual response text
|
||
- Latency
|
||
- Token usage
|
||
- Any error
|
||
|
||
Use it whenever you add a new provider or suspect a stale key.
|
||
|
||
---
|
||
|
||
## Multimodal sidecar (system-wide)
|
||
|
||
::: tip Added in 1.3.0
|
||
Lets a text-only primary model still answer questions about uploaded images. See [issue #87](https://github.com/mateaix/mateclaw/issues/87).
|
||
:::
|
||
|
||
Entry point: **Settings → Models → Multimodal sidecar**. Two independent cards:
|
||
|
||
| Card | Purpose | Status |
|
||
|------|---------|--------|
|
||
| **Vision sidecar model** | Captions an uploaded image once, then hands the structured description to the primary chat model | Live |
|
||
| **Video sidecar model** | Same idea for video | Reserved (config persisted but not yet wired in v1) |
|
||
|
||
The setting stores `mate_model_config.id` rather than `model_name` — the same `model_name` can exist under multiple providers (e.g. `qwen-vl-max` lives on both DashScope and an OpenAI-Compatible custom row), so a name-keyed setting would collide. Two setting keys:
|
||
|
||
- `default.vision_model`
|
||
- `default.video_model`
|
||
|
||
The dropdown only lists models that **actually support the relevant modality** — filtered by `ModelCapabilityService.supports(...)` on the backend; disabled providers or models without a declared vision capability never appear. Each card has its own Save button, independent of the other.
|
||
|
||
When does it fire? `MultimodalRouter` ([source](https://github.com/mateaix/mateclaw/blob/main/mateclaw-server/src/main/java/vip/mate/llm/routing/MultimodalRouter.java)) decides per turn:
|
||
|
||
- Primary already supports vision → no routing (native multimodal path)
|
||
- Primary lacks vision + vision sidecar configured → SIDECAR strategy, captions to text
|
||
- Primary lacks vision + no sidecar → skip the attachment + tell the user to configure one
|
||
|
||
For the end-user flow (badge, hint above the input box) see [Chat → Primary model can't see images? "Multimodal sidecar" routing](./chat#primary-model-cant-see-images-multimodal-sidecar-routing).
|
||
|
||
---
|
||
|
||
## Multi-model failover
|
||
|
||
::: tip OpenAI was down for 30 minutes. My AI didn't stop for a second.
|
||
During the last 30-minute DashScope rate-limit hiccup, our service uptime was 100%.
|
||
|
||
Users saw their answers come through cleanly — no red error toast, no "service unavailable, please try again." **Mid-answer, mid-token**, the runtime quietly rolled to the next healthy provider. The next token after the cut landed normally.
|
||
|
||
This isn't "automatic retry" in the engineering sense. It's **failover the user can't perceive**.
|
||
:::
|
||
|
||
Every provider you add joins an `AvailableProviderPool` that's probed at startup and re-probed on config change.
|
||
|
||
- **Automatic fallback** — if the primary provider returns an `AUTH_ERROR`, `BILLING`, `MODEL_NOT_FOUND`, `NETWORK`, or `5xx`, the runtime rolls forward to the next provider in the chain instead of bubbling up the error
|
||
- **Per-agent priority** — bind an agent to "OpenAI first, then Anthropic, then DashScope" via the drag-to-reorder editor in `Settings → Models`
|
||
- **Live pool state** — green / amber / red badges show each provider's health
|
||
- **5-protocol probe** — DashScope, OpenAI-compatible, Anthropic, Gemini, Ollama-style
|
||
- **Manual reprobe + auto-reprobe on config change** — no restart after rotating a key
|
||
- **Egress sanitizer** — provider-specific options (e.g., `reasoning_effort` for OpenAI reasoning models) are stripped at egress when failing over to a provider that doesn't support them, so leaked options can't 400 the fallback
|
||
- **UI distinguishes 401 from session expiry** — provider auth errors and user session expiry now show different messages with different remediation
|
||
|
||
### Policy-driven error recovery and rate-limit-aware backoff (2.0.0+)
|
||
|
||
Failover decides *who to switch to*; 2.0.0 also makes *how each error recovers* a matter of **classification-as-policy** — every error type carries its recovery attributes (retryable, compress context, rotate, fall back), and the retry loop consumes the policy instead of scattering if-chains. The key semantics:
|
||
|
||
- **"Server overloaded" and "my key is rate-limited" are treated differently.** A new OVERLOADED class: 503/529-style **server overload** means everyone is queuing — switching providers just burns the whole chain for nothing (and single-key users have nowhere to switch) — so the right move is **back off on the same provider**; a 429 on **your own key** is what deserves a fast rotation. These used to be conflated with opposite policies.
|
||
- **When the provider says when it recovers, we believe it.** `Retry-After` / ratelimit-reset response headers used to go only to logs; they now **feed directly into backoff duration and health cooldown** — no more blind backoff against a known rate-limit window.
|
||
- **Eviction is a TTL cooldown, not a death sentence.** Providers hard-evicted for auth failures or billing now get TTL-based readmission (swap in a new key or top up the account and the system heals itself, no restart required); a provider-stated recovery time overrides the default.
|
||
- **Randomized jitter prevents retry storms.** Concurrent conversations hitting the same rate-limited provider back off with randomized jitter (±30% on the overload backoff tiers, exponential backoff plus a random component on the generic path) — no more lockstep mass retries that keep re-triggering the limit.
|
||
|
||
### Preferred provider drives the primary model (1.5.0)
|
||
|
||
Before 1.5.0, "per-agent priority" only affected the **failover order** — the primary model was still the global default. 1.5.0 makes that preference **actually decide primary-model selection**. The full precedence is:
|
||
|
||
1. **A conversation-pinned model wins** — the chat-header ModelSelector bound a model to this conversation, so it's used (see [per-conversation model selection](./chat#per-conversation-model-selection))
|
||
2. **then the per-agent model override (`modelName`)** — the employee has a model pinned on it
|
||
3. **then the global default model**
|
||
4. **only when none of those are set does preferred-provider routing kick in** — picking the preferred provider's primary model
|
||
|
||
Preferred-provider routing has a **capability gate**: if the employee's bound skills declare a need like `requires-model: vision`, routing first picks a provider that can satisfy those modalities; only if none can does it fall back unconstrained. Preferences are stored in `mate_agent_provider_preference` (ascending `sortOrder` = higher priority).
|
||
|
||
---
|
||
|
||
## Configuration via API
|
||
|
||
```bash
|
||
# List enabled providers (what the main list shows)
|
||
curl http://localhost:18088/api/v1/models \
|
||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||
|
||
# List the full catalog (including disabled) — what the Add Provider drawer uses
|
||
curl http://localhost:18088/api/v1/models/catalog \
|
||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||
|
||
# Enable a provider
|
||
curl -X POST http://localhost:18088/api/v1/models/{providerId}/enable \
|
||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||
|
||
# Disable a provider (auto-switches default model if needed)
|
||
curl -X POST http://localhost:18088/api/v1/models/{providerId}/disable \
|
||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||
|
||
# Add a model configuration
|
||
curl -X POST http://localhost:18088/api/v1/models \
|
||
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"providerId": 1,
|
||
"modelName": "qwen-plus",
|
||
"displayName": "Qwen Plus",
|
||
"temperature": 0.7,
|
||
"maxTokens": 4096,
|
||
"groupName": "Fast",
|
||
"enabled": true
|
||
}'
|
||
|
||
# Set active model
|
||
curl -X PUT http://localhost:18088/api/v1/models/active \
|
||
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"providerId": "openai", "model": "gpt-4o"}'
|
||
|
||
# Discover models
|
||
curl -X POST http://localhost:18088/api/v1/models/{providerId}/discover \
|
||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||
|
||
# Test connection
|
||
curl -X POST http://localhost:18088/api/v1/models/{providerId}/test-connection \
|
||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||
```
|
||
|
||
---
|
||
|
||
## Everything goes through the UI
|
||
|
||
::: tip
|
||
**Model configuration is 100% UI-driven.** There's no `spring.ai.*` YAML you need to touch. All providers, all API keys, all model configs, all switching — it all lives in `Settings → Models`, backed by the `mate_model_provider` and `mate_model_config` database tables.
|
||
:::
|
||
|
||
The UI handles everything you'd otherwise do in YAML, plus several things YAML can't do:
|
||
|
||
- **Add a provider** — pick a type, paste a key, save. Encrypted at rest, masked in the UI.
|
||
- **Test connection** — verify a provider before you trust it in production.
|
||
- **Discover models** — for providers that support `/v1/models`, one click pulls the whole list.
|
||
- **Per-model test** — send a test prompt and see the exact response, latency, and token usage.
|
||
- **Switch active model at runtime** — no restart, no config reload, takes effect on the next message.
|
||
- **Per-agent override** — bind a specific agent to a specific model config.
|
||
|
||
LLM API keys are **no longer read from environment variables** — setting `DASHSCOPE_API_KEY` / `OPENAI_API_KEY` and similar has no effect. Every provider, key, and model lives in the UI. A fresh install starts with no providers configured; add your first one under `Settings → Models → Add Provider`.
|
||
|
||
### Reference: which Qwen model to pick
|
||
|
||
If you're on DashScope, here's the rough shape of the lineup:
|
||
|
||
| Model | Context | Best for |
|
||
|-------|---------|----------|
|
||
| `qwen-max` | 32K | Complex reasoning, analysis |
|
||
| `qwen-plus` | 32K | General-purpose |
|
||
| `qwen-turbo` | 8K | Fast responses |
|
||
| `qwen-vl-max` | 32K | Vision + language |
|
||
| `qwen-long` | 1M | Very long documents |
|
||
|
||
---
|
||
|
||
## Per-model context windows (2.1.0+)
|
||
|
||
MateClaw no longer treats every model as a global 128K window. Runtime resolution follows **operator override → live local-model probe or provider limit-error cache → built-in model catalog → the existing global fallback**. To avoid I/O, model-list rendering shows only override or catalog values; unknown models still use the caller's global default. The result budgets system prompts, memory, Wiki context, history, and tool schemas.
|
||
|
||
- known models, including GLM-5V-Turbo and Kimi coding aliases, use catalogued windows;
|
||
- custom/private models can declare an accurate maximum input-token count in model management;
|
||
- the API is `PUT /api/v1/models/{providerId}/models/context-window` with `modelId` and `maxInputTokens`; null or non-positive clears the override;
|
||
- workspace members reading provider binding choices receive only id/display name, never keys or connection settings.
|
||
|
||
The OpenAI-compatible path also preserves `integer` / `number` in tool JSON Schemas. Only non-reserved top-level `generateKwargs` entries pass through to the request body; temperature, token limits, `topP`, `reasoningEffort`, search, headers, and path keys use one consistent reserved-key reader. Unknown nested `chatOptions` keys are not forwarded, and `reasoningEffort` is sent only to explicitly supported model families.
|
||
|
||
---
|
||
|
||
## Next
|
||
|
||
- [Configuration](./config) — full config reference
|
||
- [Agents](./agents) — how agents use models
|
||
- [Admin Console](./console) — UI for model management
|
||
|
||
## Model probes and cancellation (3.0.0)
|
||
|
||
A probe requires a valid completed response; connection success or empty output does not prove model availability. Stopping generation cancels HTTP calls owned by the request and prevents subsequent failover.
|
||
|
||
## Context budgets and request preparation (3.0.0)
|
||
|
||
The native AgentScope runtime continues to call the existing Spring AI/provider adapters. `PromptBudget` estimates the effective request without changing its prompt or options, using Spring AI's native option combination to inherit defaults when runtime options are partial.
|
||
|
||
- Count messages (including structured tool-call content), effective tool callback schemas and one output reserve.
|
||
- A non-null runtime callback list replaces default callbacks; an explicitly empty list contributes no inherited tool schemas.
|
||
- Reserve the declared output maximum, including OpenAI `maxCompletionTokens`; retain a provider-specific runtime maximum as a conservative floor when generic options cannot represent it. Use the caller's fallback only when no maximum is available.
|
||
- Use long arithmetic for totals so a large output limit cannot overflow into an apparently safe request.
|
||
|
||
Initial preparation, structured-compaction retries and empty-response retries share this estimate. Summary-model calls check the same components before claiming their call budget or reaching the provider. Existing call limits, usage accounting, cancellation and Goal protections remain in force.
|
||
|
||
These are local safety estimates, not exact tokenizer or billing measurements. A provider can still reject a request, and fitting a context window does not establish that the answer is correct.
|