# 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` 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. ### 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. #### 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 AI_ANALYSIS_SYSTEM_PROMPT=你是态势感知分析助手。请基于输入的上下文、观测与约束,输出结构化、克制、可执行的分析。 ``` 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.