8.2 KiB
8.2 KiB
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 应逐步收敛为类似结构:
{
"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 选择规则:
- 解析 provider。
- 解析 model。
- 从
models[].transport找模型级 transport。 - 若没有模型级 transport,使用 provider
default_transport。 - 将解析结果传给
aiprovider。 aiprovider只按transport组装请求,不认识 provider 专属模型名。
Implementation Plan
Phase 1: Stabilize Current Metadata Path
- Keep
model_provider_apisas the immediate compatibility bridge. - Ensure
_provider_defaults()includes provider metadata such asmodel_provider_apis. - Ensure
_runtime_config_from_ai_payload()sends the resolved metadata throughAIProviderClient. - Ensure
AIProviderClientforwards metadata toaiproviderwith a structured header. - Ensure
aiprovider.ProviderServicereads model metadata and resolvesprovider_api = model_provider_apis[model] ?? provider_api. - Add tests proving
aiproviderdoes 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.pypreset shape withmodels_metadata. - Preserve old
modelsas 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
modelsandmodels_metadatafrom 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
- OpenAI-compatible
- 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-completionsanthropic-messagesollama-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_metadatato provider presets and refresh responses. - Add runtime resolver helper for provider/model transport selection.
- Remove any remaining provider/model-specific literals from
aiproviderruntime routing. - Add tests that
opencode-go/minimax-m2.7resolves 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.
/modelsmissing 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.pybackend/app/api/v1/settings.pybackend/app/services/ai_client.pyaiprovider/main.pyaiprovider/provider_service.pyfrontend/src/admin/pages/PlainResourcePages.tsxdocs/deprecated/admin-next-parity-audit-closeout-plan.md