20 KiB
AI Provider Guide
Overview
aiprovider is the model-adapter service for Planet.
It isolates model-vendor details from the main backend so the rest of the system can call a stable business API:
- Caller service ->
planet backend planet backend->aiprovideraiprovider-> concrete model provider
The recommended default is:
- External and cross-service callers use
planet backend - Only infrastructure-grade internal jobs call
aiproviderdirectly
Responsibilities
backend is responsible for:
- authentication and authorization
- business-level request shaping
- stable
/api/v1/ai/...endpoints - internal service-to-service authentication toward
aiprovider - reading the default provider, model, and per-provider keys saved in Settings, then overriding
aiprovider.envdefaults through internal headers
aiprovider is responsible for:
- model protocol adaptation
- provider selection by
.envwhen no backend override headers are present - timeout and lightweight retry
- request tracing via
X-Request-ID
This now follows an OpenClaw-like seam:
AI_PROVIDERidentifies the vendor or logical providerAI_PROVIDER_APIidentifies the wire adapter
That split makes MiniMax, Claude-compatible gateways, and self-hosted OpenAI-compatible services easier to model without overloading one config field.
Supported Providers
aiprovider currently supports these provider identities:
openaianthropicminimaxollama
Supported request adapters:
openai-completionsopenai-responsesanthropic-messagesollama-generate
Backward-compatible aliases still accepted:
openai_compatibleanthropic_compatibleclaude_compatible
Provider mapping:
vLLM,LM Studio,One API:AI_PROVIDER=openai,AI_PROVIDER_API=openai-completionsMiniMax:AI_PROVIDER=minimax,AI_PROVIDER_API=anthropic-messages- Claude-compatible gateways:
AI_PROVIDER=anthropic,AI_PROVIDER_API=anthropic-messages Ollama:AI_PROVIDER=ollama,AI_PROVIDER_API=ollama-generate
API Surfaces
Main backend API
Preferred stable entrypoints:
GET /api/v1/ai/provider/statusPOST /api/v1/ai/situational-awareness/analyze
Authentication:
Authorization: Bearer <jwt>
Optional tracing header:
X-Request-ID: <caller-generated-id>
The backend will propagate X-Request-ID to aiprovider and return the same header in the response.
Settings API
The AI settings page uses:
GET /api/v1/settings/integrationsPUT /api/v1/settings/integrationsPOST /api/v1/settings/integrations/ai-provider/connectGET /api/v1/settings/integrations/ai-provider/secretsGET /api/v1/settings/integrations/ai-provider/presetsPOST /api/v1/settings/integrations/ai-provider/presets/{provider}/refreshGET /api/v1/settings/ai-promptsPUT /api/v1/settings/ai-prompts/{task_key}POST /api/v1/settings/ai-prompts/{task_key}/reset
These endpoints require an authenticated user. The secrets endpoint is only used when the settings page reveals a key or token; hiding the field restores the masked preview.
Model refresh and lightweight checks share backend/app/services/llm_model_catalog.py and query provider endpoints directly, without models.dev. Refresh uses the draft base URL, protocol and credentials, or saved configuration when no draft is supplied. Regional hosts and custom gateway paths are preserved. Missing required credentials return 400; upstream failures return safe errors and retain the previous catalog.
Successful catalogs and refreshed_at are stored under llm_provider_preset:<provider> in system_settings. Dynamic categories use the provider preset as their defaults and are validated before committing. Listing prefers saved catalogs and otherwise shows bundled suggestions. Refresh never modifies the active model, protocol, URL or credentials in external_integrations. The frontend retains the draft and previously loaded models when listing fails, with an explicit error message.
Model Catalog Endpoints
These endpoints were checked against official documentation on 2026-09-13. The table lists discovery URLs; the form still takes the generation base URL. Credentials must belong to the configured service region.
| Provider | Model list GET endpoint | Authentication and parsing |
|---|---|---|
| MiniMax | https://api.minimaxi.com/anthropic/v1/models; international: api.minimax.io |
x-api-key; data[].id; has_more/last_id pagination |
| OpenAI | https://api.openai.com/v1/models |
Bearer; data[].id |
| Anthropic | https://api.anthropic.com/v1/models |
x-api-key, anthropic-version; paginate with after_id |
| DeepSeek | https://api.deepseek.com/v1/models |
Bearer; data[].id |
| Alibaba Model Studio | /api/v1/models on the configured regional host |
Bearer; output.models[].model; page_no/page_size/output.total; filter capabilities=TG |
| Moonshot / Kimi | https://api.moonshot.ai/v1/models; China: api.moonshot.cn |
Bearer; data[].id |
| OpenRouter | https://openrouter.ai/api/v1/models |
Public catalog; Bearer when supplied; data[].id |
| OpenCode Go | https://opencode.ai/zen/go/v1/models |
Public catalog; Bearer when supplied; data[].id |
| Ollama | /api/tags on the configured server |
Local servers need no key; models[].model/name; empty means no installed models |
New Model Studio endpoints for Beijing, Tokyo, Frankfurt and Virginia require the actual workspace host, such as <WorkspaceId>.cn-beijing.maas.aliyuncs.com. Singapore uses dashscope-intl.aliyuncs.com; Hong Kong uses cn-hongkong.dashscope.aliyuncs.com. Discovery changes only the path, never guesses a workspace or switches credential regions. If a legacy Beijing host no longer accepts the account, update the base URL from the provider console.
Pagination and retries have a 30-second overall deadline. Network failures and 502/503/504 allow up to two attempts; authentication failures are not retried. Models are sorted by supplied creation/release dates, preserving upstream order on ties, without arbitrary list truncation. Public catalog membership does not establish account-specific generation permission.
Admin keeps the AI page aligned with the legacy information architecture:
Model Providers- Manages provider, wire adapter, default model, LLM API key, proxy URL, proxy token, model refresh, set-as-default, and lightweight connectivity testing.
Tool Calling- Manages tools such as WebSearch and OCR. Each tool first selects a provider, then edits that provider's API, key, and advanced parameters.
Prompts- Edits system/user prompts by prompt group and task key. Save and reset only affect the current task.
Playground- Runs real conversations with the active provider and prompt configuration. AI responses are rendered as Markdown.
Save, set-as-default, and connectivity testing are separate responsibilities: save only persists the form, set-as-default only changes the active provider/tool, and connectivity testing only validates the current draft. It must not implicitly save or switch defaults.
The ai-prompts endpoints back the Prompts tab in AI settings. Shipped defaults come from versioned backend resources, while business code references stable task keys. The API stores only operator overrides. Resetting a prompt removes the override and falls back to the current shipped default.
Prompt Boundary
aiprovider is a pure model adapter and does not inject a global business system prompt. News localization, alert briefing, BGP briefing, location factcheck, datasource mapping, and credential guide generation each resolve their own effective prompt by task key. Alert-analysis system prompts are only sent by alert-related tasks and do not leak into other LLM calls.
Agent And Tool Boundary
Agent workflows belong in the backend, not in aiprovider. Future Earth LLM commands, situational awareness, multi-role simulation, WebSearch, database queries, evidence storage, and configuration proposal application should be orchestrated by the backend Agent Runtime. aiprovider should receive model-ready requests from the backend and return normalized model responses.
If a provider supports native tool calling, aiprovider may pass through protocol fields and normalize response blocks, but tool whitelists, argument validation, permission policy, run records, and write approvals must stay in the backend. When a provider does not support native tools, the backend uses JSON tool-call fallback; business tools should not move into aiprovider for a provider-specific shortcut.
AI provider internal API
Internal-only endpoints:
GET /v1/provider/statusPOST /v1/analyze
Authentication:
X-Provider-Token: <shared-secret>
Optional tracing header:
X-Request-ID: <caller-generated-id>
Request Example
Call through backend
curl -X POST http://localhost:8000/api/v1/ai/situational-awareness/analyze \
-H "Authorization: Bearer <access_token>" \
-H "X-Request-ID: bgp-incident-20260407-001" \
-H "Content-Type: application/json" \
-d '{
"title": "BGP异常研判",
"objective": "总结当前风险并给出处置建议",
"observations": [
"collector A 在 5 分钟内出现多次 origin 变更",
"异常集中在同一地区前缀"
],
"constraints": [
"不要编造不存在的数据",
"区分事实和推断"
],
"context": {
"source": "bgp-monitor",
"severity": "high"
}
}'
Call aiprovider directly
curl -X POST http://localhost:8010/v1/analyze \
-H "X-Provider-Token: change_me" \
-H "X-Request-ID: ai-batch-job-001" \
-H "Content-Type: application/json" \
-d '{
"title": "链路波动分析",
"objective": "给出简要态势摘要和下一步建议",
"observations": [
"多个节点出现延迟上升"
],
"constraints": [
"不要假设根因已经确认"
],
"context": {
"region": "APAC"
}
}'
Response Shape
Both backend and aiprovider return the same payload shape:
{
"provider": "minimax",
"api": "anthropic-messages",
"model": "MiniMax-M2.7",
"content": "1) 态势摘要 ...",
"content_blocks": [],
"text_blocks": [],
"thinking_blocks": [],
"raw_response": {}
}
Both services also return:
X-Request-ID: <id>
Configuration
Runtime Configuration Flow
The backend Settings system owns the global LLM default. The runtime flow is:
- Frontend or application code calls a
backend/api/v1/ai/...endpoint. backendreadscategory = external_integrationsfrom the PostgreSQLsystem_settingstable.payload.ai_provider.default_providerselects the active provider.payload.ai_provider.providers[provider]supplies that provider'sapi_key,provider_api,base_url,model,max_tokens, andanthropic_version.backendconverts those values to internal headers such asX-AI-Provider,X-AI-Provider-API,X-AI-Base-URL,X-AI-API-Key, andX-AI-Model.aiprovideruses those headers to override its.envdefaults before calling the real model vendor.
After the AI settings page saves a new default provider/model/key, Playground, alert briefs, datasource mapping generation, and other backend AI calls all use that same default.
Persistence Shape
AI settings are persisted in PostgreSQL, not a JSON file. The core payload shape is:
{
"ai_provider": {
"service_url": "http://localhost:8010",
"service_token": "",
"default_provider": "openai",
"providers": {
"openai": {
"provider_api": "openai-completions",
"base_url": "https://api.openai.com/v1",
"model": "gpt-5.1",
"api_key": "<saved secret>",
"max_tokens": 4096,
"anthropic_version": "2023-06-01"
},
"minimax": {
"provider_api": "anthropic-messages",
"base_url": "https://api.minimaxi.com/anthropic",
"model": "MiniMax-M2.7",
"api_key": "<saved secret>",
"max_tokens": 1200,
"anthropic_version": "2023-06-01"
}
},
"timeout_seconds": 60,
"retry_attempts": 2
}
}
Legacy single-slot settings are mapped to providers[provider] on read and are written back in the new shape on save.
Key Fallback
Each provider has its own key slot. Resolution order is:
providers[provider].api_keyin PostgreSQL- the provider-specific variable in
aiprovider/.env, such asOPENAI_API_KEY,MINIMAX_API_KEY, orANTHROPIC_API_KEY - the generic
AI_API_KEYinaiprovider/.env
.env is only a fallback. After the settings page saves successfully, or after the connection test succeeds, PostgreSQL becomes the global default source.
Admin must compute key status per provider or tool:
- If the database has a key for the current provider/tool, show
configured. - If the database has no key but the fallback provider, model, or tool matches the current item, show the fallback masked preview.
- If neither database nor matching fallback exists, show
not configured; a generic.envkey for another provider must not make this item appear configured. - Masking keeps the prefix before the first
-, for examplesk-********. Plaintext reveal is only available inside the authorized configuration page.
Tool keys follow the same rule. WebSearch and OCR must match the current tool and provider before they can use fallback credentials.
Lightweight Connectivity Testing
The Admin plug button checks proxy configuration and queries the current provider catalog. It returns success=true only when discovery succeeds and contains the selected model. A 404, authentication error, invalid response or missing model never becomes success based on bundled suggestions. The message distinguishes discovery from generation; Playground or a business task verifies actual generation. Testing never saves the draft or switches the default provider.
OpenCode Go Routing Model
OpenCode Go protocol mappings are owned by the backend catalog: MiniMax M3/M2.7/M2.5 and the verified Qwen3.6/3.7/3.8 models use Anthropic Messages; GPT-5.6 Luna, Grok 4.6 and Muse Spark Contributor use Responses; other supported models use Chat Completions. The official /models response currently supplies IDs only, so new models still require checking the documented endpoint table. Selecting a model updates its protocol in the draft, and backend mappings supersede known stale mappings. Responses uses input, max_output_tokens and store=false, and parses text and reasoning summaries. OpenCode calls carry an application User-Agent and a stable Playground conversation identifier. Generic thinking=enabled is translated to MiniMax M3’s adaptive format.
Settings Page Behavior
- The Provider select controls the global default provider.
- The model select saves the default model for the selected provider.
- The LLM API Key field shows a masked preview while hidden; keys with a
-prefix keep the prefix, for examplesk-********, and keys without a prefix are fully masked. - Clicking the eye icon fetches and displays the full plaintext value; hiding restores the masked preview.
Save AI Configurationsaves the current form as the global default.Test Connectionuses the current form for a real model-chain test, then saves it as the global default only when the test succeeds.- Leaving a key field empty keeps the old key; it does not delete it.
Backend
Recommended backend .env:
AI_PROVIDER_SERVICE_URL=http://localhost:8010
AI_PROVIDER_SERVICE_TOKEN=change_me
AI_PROVIDER_TIMEOUT_SECONDS=60
AI_PROVIDER_RETRY_ATTEMPTS=2
Reference file:
AI Provider
Reference file:
Frontend local reference:
Common settings:
SERVICE_NAME=planet-ai-provider
SERVICE_VERSION=0.1.0
AI_PROVIDER_SERVICE_TOKEN=change_me
AI_TIMEOUT_SECONDS=60
AI_HTTP_RETRY_ATTEMPTS=2
Optional provider-specific keys:
MINIMAX_API_KEY=sk-cp-xxxxx
OPENAI_API_KEY=sk-xxxxx
ANTHROPIC_API_KEY=sk-ant-xxxxx
DEEPSEEK_API_KEY=sk-xxxxx
DASHSCOPE_API_KEY=sk-xxxxx
MOONSHOT_API_KEY=sk-xxxxx
OPENROUTER_API_KEY=sk-or-xxxxx
OpenAI-compatible example
AI_PROVIDER=openai
AI_PROVIDER_API=openai-completions
AI_BASE_URL=http://127.0.0.1:8001/v1
AI_API_KEY=local-key
AI_MODEL=your-local-model
MiniMax CN example
AI_PROVIDER=minimax
AI_PROVIDER_API=anthropic-messages
AI_BASE_URL=https://api.minimaxi.com/anthropic
AI_API_KEY=sk-cp-xxxxx
AI_MODEL=MiniMax-M2.7
AI_MAX_TOKENS=1200
AI_ANTHROPIC_VERSION=2023-06-01
MiniMax note:
- This follows the same Anthropic Messages request shape as the official MiniMax examples.
- For MiniMax,
aiprovidernow disablesthinkingby default unless the caller explicitly passes athinkingobject. - This mirrors OpenClaw's caution around MiniMax Anthropic-compatible behavior.
Anthropic-compatible example
AI_PROVIDER=anthropic
AI_PROVIDER_API=anthropic-messages
AI_BASE_URL=https://your-claude-compatible-endpoint.example.com/anthropic
AI_API_KEY=your_api_key
AI_MODEL=your-model
AI_MAX_TOKENS=1200
AI_ANTHROPIC_VERSION=2023-06-01
Ollama example
AI_PROVIDER=ollama
AI_PROVIDER_API=ollama-generate
AI_BASE_URL=http://127.0.0.1:11434
AI_API_KEY=
AI_MODEL=qwen2.5:7b
Deployment Modes
Single machine
Recommended local flow:
backendonlocalhost:8000aiprovideronlocalhost:8010- local model gateway on
localhost:11434or another local port
Helpers already included:
Multi-machine
Example topology:
- app machine:
backend - AI gateway machine:
aiprovider - model machine: local model service or cloud proxy
In that case, this becomes service-to-service HTTP RPC:
- caller -> backend
- backend ->
http://10.0.0.12:8010 aiprovider-> model endpoint
Recommended cross-machine backend config:
AI_PROVIDER_SERVICE_URL=http://10.0.0.12:8010
AI_PROVIDER_SERVICE_TOKEN=change_me
AI_PROVIDER_TIMEOUT_SECONDS=60
AI_PROVIDER_RETRY_ATTEMPTS=2
Recommended operating rules:
- keep
aiprovideron a private network - protect it with
X-Provider-Tokenat minimum - always send
X-Request-ID - keep callers on the backend API unless they are infrastructure jobs
Retry And Failure Behavior
backend -> aiprovider:
- retries lightweight network / 5xx failures
- returns
502when the provider service is unavailable
aiprovider -> model provider:
- retries lightweight network / 5xx failures
- returns
502when the model provider is unavailable
This is intentionally conservative. It avoids masking persistent errors while still absorbing short hiccups.
Operational Notes
./planet.sh startnow startsaiproviderautomatically./planet.sh restart -arestarts onlyaiprovider./planet.sh log -atailsaiproviderlogs./planet.sh healthreportsaiproviderhealth
Recommended Calling Policy
- Frontend and application services: call
backend - Scheduled infra jobs and diagnostics: optionally call
aiprovider - Do not let multiple business services integrate model vendors independently
That keeps provider switching centralized and avoids model-specific drift across the system.