# AI Provider OpenClaw-Style Routing Plan Last updated: 2026-05-20 ## Summary Planet 的 AI Provider 路由要从“运行时识别特殊 provider / 特殊模型”收敛到 OpenClaw 风格的配置驱动模型:模型引用、协议、鉴权、轻量探测、真实调用和模型级例外都由 provider catalog / preset / runtime metadata 描述,运行时只解释这些元数据,不再散落 `if provider == ... and model == ...` 这类硬编码。 这份计划覆盖 Admin Next 的 Provider 配置体验、backend settings API、`aiprovider` 适配服务和未来模型目录同步方式。目标是让 OpenCode Go、MiniMax、DeepSeek、OpenAI-compatible、Anthropic-compatible、Ollama、OpenRouter / One API 类代理都能用同一套规则扩展。 ## Background 当前实现已经完成了两步临时修正: - OpenCode Go 模型目录不再使用普通 Zen free 列表,而是使用 `https://opencode.ai/zen/go/v1/models`。 - `minimax-m2.7` / `minimax-m2.5` 的协议例外已从 `aiprovider` 运行逻辑移到 `model_provider_apis` 元数据中。 但整体还没有完全达到 OpenClaw 式结构。OpenClaw 的关键思想是: - 模型引用使用 `provider/model`,由 provider 前缀确定 runtime provider。 - provider 插件或 catalog 拥有 `normalizeModelId`、`normalizeTransport`、`normalizeConfig`、`prepareRuntimeAuth`、`createStreamFn` 等 provider 行为。 - 主推理循环不认识具体模型名,只使用解析后的 provider config、transport 和 request adapter。 - 上游网关能自己路由时,尽量透传 provider routing metadata,不在本地复制上游逻辑。 Planet 不需要完整复制 OpenClaw 插件系统,但需要学习它的边界划分。 ## Design Principles - Provider catalog 是路由事实来源,runtime 不是。 - 模型级协议例外必须是 metadata,例如 `model_provider_apis`,不能是 Python set / if 分支。 - 轻量连通性测试只验证网络、鉴权和模型目录,不发真实 prompt。 - 真实模型调用只发生在 Playground、AI brief、分析任务等明确需要生成的路径。 - 保存配置不自动设为默认,不自动触发连接测试;保存、设默认、测试三种按钮职责分离。 - 目录刷新使用增量合并语义:发现新模型,标记旧模型 stale,不直接删除用户选择或自定义模型。 - 如果 provider 不提供可靠 `/models`,可以用内置 preset 确认已知模型,但 UI 必须说清楚这是 preset confirmation,不是假装 provider 返回了目录。 ## Target Data Model Provider preset / runtime config 应逐步收敛为类似结构: ```json { "provider": "opencode-go", "label": "OpenCode Go", "default_transport": "openai-completions", "base_url": "https://opencode.ai/zen/go/v1", "auth": { "type": "bearer", "api_key_env": "OPENCODE_GO_API_KEY" }, "models": [ { "id": "glm-5.1", "label": "GLM 5.1", "transport": "openai-completions", "context_window": null, "capabilities": ["text"] }, { "id": "minimax-m2.7", "label": "MiniMax M2.7", "transport": "anthropic-messages", "capabilities": ["text", "reasoning"] } ], "discovery": { "type": "openai-models", "url": "https://opencode.ai/zen/go/v1/models", "auth": "provider-api-key" } } ``` Runtime 选择规则: 1. 解析 provider。 2. 解析 model。 3. 从 `models[].transport` 找模型级 transport。 4. 若没有模型级 transport,使用 provider `default_transport`。 5. 将解析结果传给 `aiprovider`。 6. `aiprovider` 只按 `transport` 组装请求,不认识 provider 专属模型名。 ## Implementation Plan ### Phase 1: Stabilize Current Metadata Path - Keep `model_provider_apis` as the immediate compatibility bridge. - Ensure `_provider_defaults()` includes provider metadata such as `model_provider_apis`. - Ensure `_runtime_config_from_ai_payload()` sends the resolved metadata through `AIProviderClient`. - Ensure `AIProviderClient` forwards metadata to `aiprovider` with a structured header. - Ensure `aiprovider.ProviderService` reads model metadata and resolves `provider_api = model_provider_apis[model] ?? provider_api`. - Add tests proving `aiprovider` does not contain provider/model-specific literals for routing decisions. ### Phase 2: Replace `model_provider_apis` With Structured Model Catalog - Extend `backend/app/services/llm_provider_catalog.py` preset shape with `models_metadata`. - Preserve old `models` as a compatibility list for the UI. - Add helpers: - `get_provider_model_metadata(provider, model)` - `resolve_provider_transport(provider_config, model)` - `merge_discovered_models(existing, discovered)` - Return both `models` and `models_metadata` from refresh endpoints. - Admin Next should render model labels, capabilities and transport hints from metadata. ### Phase 3: Provider Discovery And Incremental Sync - Add provider discovery descriptors: - OpenAI-compatible `/models` - Anthropic-compatible no-models / preset-confirmed path - Ollama `/api/tags` - OpenCode Go `/zen/go/v1/models` - OpenRouter / One API passthrough model discovery - Add incremental merge behavior: - New discovered model: add. - Existing discovered model: update `last_seen_at`, metadata. - Missing discovered model: mark `stale`, do not delete. - User custom model: keep unless explicitly removed. - Surface discovery source in Admin Next: `实时发现 / 内置预设 / 用户自定义 / 已过期`. ### Phase 4: Transport Adapters - Replace provider-specific request decisions with adapter descriptors: - `openai-completions` - `anthropic-messages` - `ollama-generate` - future `openai-responses` - future `gemini-generate-content` - Each adapter owns: - path - auth header format - request body transform - response text extraction - reasoning/thinking block extraction - models endpoint strategy - `ProviderService.analyze()` should select adapter by resolved transport and call the adapter. ### Phase 5: Admin Next UX - Model provider page should show: - provider status tag - default model tag - source tag: env / runtime / preset / discovered - model list with transport/capability hint - separate buttons for save, set default, refresh model catalog, lightweight test - The connect plug button remains lightweight. - Full generation test lives only in Playground or a clearly named “试运行” action. - If lightweight test falls back to preset confirmation, toast must say so explicitly. ## TODO - [ ] Add `models_metadata` to provider presets and refresh responses. - [ ] Add runtime resolver helper for provider/model transport selection. - [ ] Remove any remaining provider/model-specific literals from `aiprovider` runtime routing. - [ ] Add tests that `opencode-go/minimax-m2.7` resolves through metadata, not through runtime hardcode. - [ ] Add tests for lightweight connectivity: - [ ] 401 / 403 fail as auth error. - [ ] 404 with known preset model passes as preset-confirmed. - [ ] `/models` missing alias passes only when preset contains the alias. - [ ] unknown model fails. - [ ] Add discovery descriptors for OpenCode Go, OpenAI-compatible, Anthropic-compatible, Ollama, OpenRouter / One API. - [ ] Add incremental model catalog merge semantics with stale marking. - [ ] Update Admin Next model list to show model source, transport and capability. - [ ] Keep save / set default / lightweight test / full test as separate actions. - [ ] Document the final provider catalog schema in technical docs after implementation. ## Current Acceptance Criteria - No runtime routing branch may depend on concrete model names like `minimax-m2.7`. - OpenCode Go model refresh must not use the ordinary Zen free-model endpoint. - Lightweight connect must not call `analyze()` or consume generation quota. - Saving a provider must not automatically set it as default. - Provider UI must distinguish configured key, fallback key, preset model and live-discovered model. ## Related Files - `backend/app/services/llm_provider_catalog.py` - `backend/app/api/v1/settings.py` - `backend/app/services/ai_client.py` - `aiprovider/main.py` - `aiprovider/provider_service.py` - `frontend/src/admin/pages/PlainResourcePages.tsx` - `docs/deprecated/admin-next-parity-audit-closeout-plan.md`