# 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` -> `aiprovider` - `aiprovider` -> concrete model provider The recommended default is: - External and cross-service callers use `planet backend` - Only infrastructure-grade internal jobs call `aiprovider` directly ## 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` `.env` defaults through internal headers `aiprovider` is responsible for: - model protocol adaptation - provider selection by `.env` when no backend override headers are present - timeout and lightweight retry - request tracing via `X-Request-ID` This now follows an OpenClaw-like seam: - `AI_PROVIDER` identifies the vendor or logical provider - `AI_PROVIDER_API` identifies 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: - `openai` - `anthropic` - `minimax` - `ollama` Supported request adapters: - `openai-completions` - `anthropic-messages` - `ollama-generate` Backward-compatible aliases still accepted: - `openai_compatible` - `anthropic_compatible` - `claude_compatible` Provider mapping: - `vLLM`, `LM Studio`, `One API`: `AI_PROVIDER=openai`, `AI_PROVIDER_API=openai-completions` - `MiniMax`: `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/status` - `POST /api/v1/ai/situational-awareness/analyze` Authentication: - `Authorization: Bearer ` Optional tracing header: - `X-Request-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/integrations` - `PUT /api/v1/settings/integrations` - `POST /api/v1/settings/integrations/ai-provider/connect` - `GET /api/v1/settings/integrations/ai-provider/secrets` - `GET /api/v1/settings/integrations/ai-provider/presets` - `GET /api/v1/settings/ai-prompts` - `PUT /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. 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/status` - `POST /v1/analyze` Authentication: - `X-Provider-Token: ` Optional tracing header: - `X-Request-ID: ` ## Request Example ### Call through backend ```bash curl -X POST http://localhost:8000/api/v1/ai/situational-awareness/analyze \ -H "Authorization: Bearer " \ -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 ```bash 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: ```json { "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: ` ## Configuration ### Runtime Configuration Flow The backend Settings system owns the global LLM default. The runtime flow is: 1. Frontend or application code calls a `backend` `/api/v1/ai/...` endpoint. 2. `backend` reads `category = external_integrations` from the PostgreSQL `system_settings` table. 3. `payload.ai_provider.default_provider` selects the active provider. 4. `payload.ai_provider.providers[provider]` supplies that provider's `api_key`, `provider_api`, `base_url`, `model`, `max_tokens`, and `anthropic_version`. 5. `backend` converts those values to internal headers such as `X-AI-Provider`, `X-AI-Provider-API`, `X-AI-Base-URL`, `X-AI-API-Key`, and `X-AI-Model`. 6. `aiprovider` uses those headers to override its `.env` defaults 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: ```json { "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": "", "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": "", "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: 1. `providers[provider].api_key` in PostgreSQL 2. the provider-specific variable in `aiprovider/.env`, such as `OPENAI_API_KEY`, `MINIMAX_API_KEY`, or `ANTHROPIC_API_KEY` 3. the generic `AI_API_KEY` in `aiprovider/.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 `.env` key for another provider must not make this item appear configured. - Masking keeps the prefix before the first `-`, for example `sk-********`. 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 performs a lightweight connectivity check and does not save configuration. Common API-platform practice is two-tiered: - Check a provider catalog or low-cost endpoint to validate base URL, authentication, and model reachability. - Send full model requests only when the user explicitly runs Playground or a business task. Connectivity results should be explicit: - `ok`: authentication, route, and model catalog are usable. - `warning`: service is reachable, but the current model is missing from the catalog or capability metadata is incomplete. - `error`: authentication, network, protocol, or model lookup failed. Toast titles must match the result; failures must not be titled as a successful connection. ### OpenCode Go Routing Model Subscription channels such as OpenCode Go should not be handled by hard-coded frontend model sets. Prefer provider catalog or backend capability discovery that records per-model capabilities such as `chat_completions`, `anthropic_messages`, `models_endpoint`, and whether a subscription key is required. The frontend should display capabilities; the backend should map provider, base URL, model, and adapter into the real request. #### 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 example `sk-********`, 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 Configuration` saves the current form as the global default. - `Test Connection` uses 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`: ```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: - [backend/.env.example](/home/ray/dev/linkong/planet/backend/.env.example) ### AI Provider Reference file: - [aiprovider/.env.example](/home/ray/dev/linkong/planet/aiprovider/.env.example) Frontend local reference: - [frontend/.env.example](/home/ray/dev/linkong/planet/frontend/.env.example) Common settings: ```env 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: ```env 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 ```env 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 ```env 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, `aiprovider` now disables `thinking` by default unless the caller explicitly passes a `thinking` object. - This mirrors OpenClaw's caution around MiniMax Anthropic-compatible behavior. ### Anthropic-compatible example ```env 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 ```env 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: - `backend` on `localhost:8000` - `aiprovider` on `localhost:8010` - local model gateway on `localhost:11434` or another local port Helpers already included: - [planet.sh](/home/ray/dev/linkong/planet/planet.sh) - [docker-compose.local-model.yml](/home/ray/dev/linkong/planet/docker-compose.local-model.yml) ### 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: ```env 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 `aiprovider` on a private network - protect it with `X-Provider-Token` at 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 `502` when the provider service is unavailable `aiprovider -> model provider`: - retries lightweight network / 5xx failures - returns `502` when the model provider is unavailable This is intentionally conservative. It avoids masking persistent errors while still absorbing short hiccups. ## Operational Notes - `./planet.sh start` now starts `aiprovider` automatically - `./planet.sh restart -a` restarts only `aiprovider` - `./planet.sh log -a` tails `aiprovider` logs - `./planet.sh health` reports `aiprovider` health ## 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.