483 lines
16 KiB
Markdown
483 lines
16 KiB
Markdown
# 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 <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/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: <shared-secret>`
|
|
|
|
Optional tracing header:
|
|
|
|
- `X-Request-ID: <caller-generated-id>`
|
|
|
|
## Request Example
|
|
|
|
### Call through backend
|
|
|
|
```bash
|
|
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
|
|
|
|
```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: <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": "<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:
|
|
|
|
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.
|