Initial TrueGrowth source import
This commit is contained in:
179
openspec/changes/add-provider-protocol-routing/design.md
Normal file
179
openspec/changes/add-provider-protocol-routing/design.md
Normal file
@@ -0,0 +1,179 @@
|
||||
## Context
|
||||
|
||||
当前代码已经具备以下基础能力:
|
||||
|
||||
- `settings-manager` 可以管理 `ProviderProfile / ProviderCatalog / ModelRef / InvocationPreset`
|
||||
- `runtime-model-discovery` 可以按 `profileId` 隔离发现和选择模型
|
||||
- 图片和视频侧已经存在若干专用 adapter,分别处理 Flux、MJ、Kling、Seedance 等协议变体
|
||||
|
||||
但当前结构仍存在三个核心缺口:
|
||||
|
||||
1. `resolveInvocationRoute` 只解析凭证和模型,不解析协议与请求体策略
|
||||
2. `runtime-model-discovery` 仍以统一 `/models + Bearer` 的假设执行发现
|
||||
3. `model-adapters` 仍然按模型 / 厂商 / 标签进行猜测式匹配,无法表达“同名模型在不同 profile 下走不同协议”的事实
|
||||
|
||||
这会导致下列问题:
|
||||
|
||||
- 同名模型在不同上游中发生协议冲突
|
||||
- 同协议不同请求体的差异只能堆在 adapter 内的条件分支中,难以扩展
|
||||
- 文本链路继续绕过统一路由层,无法和图片 / 视频共享同一套供应商协议规划
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
- Goals:
|
||||
- 让运行时调用基于 `profileId + modelId + operation` 选择协议绑定
|
||||
- 支持同一模型 ID 在不同供应商下走不同协议
|
||||
- 支持同一协议下的不同请求体 schema
|
||||
- 统一文本、图片、视频三类调用的运行时规划流程
|
||||
- 尽量降低用户配置成本,优先自动推断协议绑定,仅在歧义时暴露高级覆盖项
|
||||
- Non-Goals:
|
||||
- 本次不实现跨供应商自动故障切换
|
||||
- 本次不实现按价格 / 延迟 / 健康度自动择优调度
|
||||
- 本次不为所有第三方平台实现完全自动化、零误差的协议识别
|
||||
|
||||
## Decisions
|
||||
|
||||
- Decision: 区分“模型标识”和“可执行绑定”
|
||||
- `modelId` 表示用户看到的模型
|
||||
- `ProviderModelBinding` 表示该模型在特定供应商下针对特定操作的调用方式
|
||||
- 绑定的主键必须至少包含 `profileId + modelId + operation`
|
||||
|
||||
- Decision: 引入 `InvocationPlanner`
|
||||
- 输入:`routeType + ModelRef + optional override`
|
||||
- 输出:`InvocationPlan`
|
||||
- Planner 负责在多个绑定中选择当前最合适的协议,而不是让 UI 或 service 直接拼 URL
|
||||
|
||||
- Decision: 将适配器从“按模型注册”迁移为“按协议注册”
|
||||
- 现有 Flux / MJ / Kling / Seedance 等实现可以保留,但角色改为 `ProtocolAdapter`
|
||||
- `ProtocolAdapter` 负责请求体构建、响应解析、可选轮询策略
|
||||
|
||||
- Decision: 将鉴权和基础 URL 处理下沉到 `ProviderTransport`
|
||||
- 统一处理 `bearer / header / query / custom` 等认证方式
|
||||
- 统一处理额外 Header、查询参数和供应商级发现接口
|
||||
- 避免每个 adapter 再重复拼装认证信息
|
||||
|
||||
- Decision: discovery 必须保留原始元数据和绑定推断结果
|
||||
- 不能只把远端模型压成扁平 `ModelConfig`
|
||||
- 必须保留 `raw`、`capabilityHints`、`bindingCandidates`
|
||||
- 否则后续运行时无法稳定判断同名模型的协议归属
|
||||
|
||||
- Decision: 同名模型不得按裸 `modelId` 全局去重
|
||||
- 选择器和运行时缓存应使用 `selectionKey = profileId::modelId`
|
||||
- 运行时绑定必须保留 `profileId`
|
||||
|
||||
## Proposed Data Model
|
||||
|
||||
```ts
|
||||
interface ResolvedProviderContext {
|
||||
profileId: string;
|
||||
profileName: string;
|
||||
providerType: string;
|
||||
baseUrl: string;
|
||||
apiKey: string;
|
||||
authType: 'bearer' | 'header' | 'query' | 'custom';
|
||||
extraHeaders?: Record<string, string>;
|
||||
}
|
||||
|
||||
interface ProviderModelBinding {
|
||||
id: string;
|
||||
profileId: string;
|
||||
modelId: string;
|
||||
operation: 'text' | 'image' | 'video';
|
||||
protocol:
|
||||
| 'openai.chat.completions'
|
||||
| 'openai.images.generations'
|
||||
| 'openai.async.video'
|
||||
| 'google.generateContent'
|
||||
| 'mj.imagine'
|
||||
| 'flux.task'
|
||||
| 'kling.video'
|
||||
| 'seedance.task';
|
||||
requestSchema: string;
|
||||
responseSchema: string;
|
||||
submitPath: string;
|
||||
pollPathTemplate?: string;
|
||||
priority: number;
|
||||
confidence: 'high' | 'medium' | 'low';
|
||||
source: 'discovered' | 'template' | 'manual';
|
||||
}
|
||||
|
||||
interface DiscoveredProviderModel {
|
||||
profileId: string;
|
||||
modelId: string;
|
||||
selectionKey: string;
|
||||
raw: unknown;
|
||||
capabilityHints: {
|
||||
supportsText: boolean;
|
||||
supportsImage: boolean;
|
||||
supportsVideo: boolean;
|
||||
};
|
||||
bindings: ProviderModelBinding[];
|
||||
}
|
||||
|
||||
interface InvocationPlan {
|
||||
provider: ResolvedProviderContext;
|
||||
modelRef: { profileId: string; modelId: string };
|
||||
binding: ProviderModelBinding;
|
||||
}
|
||||
```
|
||||
|
||||
## Execution Flow
|
||||
|
||||
1. 用户在设置中保存 `ProviderProfile`
|
||||
2. `ProviderTransport` 根据 `providerType/baseUrl` 选择供应商模板或探测策略
|
||||
3. discovery 同步远端模型,并推断出每个模型的 `bindings[]`
|
||||
4. 用户在运行时选择 `ModelRef(profileId + modelId)`
|
||||
5. `InvocationPlanner` 根据 `operation` 找到最高优先级的可执行 binding
|
||||
6. `ProtocolAdapterRegistry` 按 `binding.protocol` 取得协议适配器
|
||||
7. 适配器根据 `requestSchema` 构建请求,并通过 `ProviderTransport` 发送
|
||||
8. 如为异步任务,则由对应 `PollingStrategy` 轮询并标准化结果
|
||||
|
||||
## Binding Inference Strategy
|
||||
|
||||
- OpenAI 兼容供应商:
|
||||
- 优先使用 `/v1/models` 返回的字段、模型 ID 和供应商模板推断绑定
|
||||
- 默认优先生成 `openai.chat.completions / openai.images.generations / openai.async.video`
|
||||
- Gemini 官方供应商:
|
||||
- 不再强依赖 `/models`
|
||||
- 允许由 provider template 直接提供 `google.generateContent` 绑定
|
||||
- 自定义供应商:
|
||||
- 若 discovery 无法高置信度推断协议,则保留低置信度绑定
|
||||
- 仅在需要时暴露高级覆盖入口
|
||||
|
||||
## Request Schema Strategy
|
||||
|
||||
同一协议下的请求体差异不再通过大量 `if modelId.includes(...)` 解决,而是显式建模:
|
||||
|
||||
- `openai.image.basic-json`
|
||||
- `openai.video.form.input-reference`
|
||||
- `google.gemini.generate-content.image`
|
||||
- `kling.video.text2video`
|
||||
- `kling.video.image2video`
|
||||
- `seedance.video.first-last-frame`
|
||||
|
||||
这样新增模型时,优先复用已有协议,仅为其指派合适的 `requestSchema`。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. 新增 provider protocol routing 类型与 planner,不改 UI
|
||||
2. 将现有图片 / 视频 adapter 包装为 `ProtocolAdapter`
|
||||
3. 将文本链路迁移到同一 planner 和 protocol registry
|
||||
4. 扩展 discovery 存储,保留 `bindings[]`
|
||||
5. 最后再清理旧的“按模型猜 adapter”逻辑
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- 风险: 架构抽象增加初始复杂度
|
||||
- Mitigation: 分阶段迁移,先保留现有 adapter 实现,只调整其注册方式
|
||||
|
||||
- 风险: 自动推断协议可能存在误判
|
||||
- Mitigation: 为 binding 引入 `confidence` 与 `source`,仅在低置信度场景允许高级覆盖
|
||||
|
||||
- 风险: 文本 / 图片 / 视频同时迁移会影响面较大
|
||||
- Mitigation: 先完成 planner 和 image/video 迁移,再迁移 text
|
||||
|
||||
## Open Questions
|
||||
|
||||
- 是否需要将用户手动覆盖的 binding 保存在 `ProviderCatalog`,还是单独存为 `ProviderBindingOverrides`
|
||||
- `authType` 是否需要从当前的 `bearer/header` 扩展为更通用的 `query/custom`
|
||||
- 官方 Gemini 等不提供 OpenAI 风格 `/models` 时,设置页是否需要显式展示“供应商模板已接管模型能力”
|
||||
45
openspec/changes/add-provider-protocol-routing/proposal.md
Normal file
45
openspec/changes/add-provider-protocol-routing/proposal.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Change: 增加供应商协议路由与请求体适配架构
|
||||
|
||||
## Why
|
||||
|
||||
当前多供应商架构已经能够保存多个 `baseUrl + apiKey`,并通过 `ModelRef(profileId + modelId)` 将模型归属到特定供应商。
|
||||
但运行时请求仍然主要依赖“模型 ID / 厂商标签 -> adapter”的方式来决定执行链路,这在以下场景下会失效:
|
||||
|
||||
- 同一个模型 ID 在不同上游中需要走不同协议,例如:
|
||||
- 上游 A 将 `gemini-3-pro-image-preview` 暴露为 `/v1/images/generations`
|
||||
- 上游 B 将同名模型暴露为 Google 官方 `:generateContent`
|
||||
- 同一个上游中的同一个模型可能支持多个调用协议,需要为不同操作或高级模式选择不同入口
|
||||
- 即使协议相同,不同模型之间也可能要求不同的请求体字段、序列化格式、轮询方式和结果提取规则
|
||||
- 文本链路目前仍然绕过现有图片/视频适配层,无法与多供应商路由形成统一模型
|
||||
|
||||
这意味着当前系统已经具备“凭证路由”,但还不具备“协议路由”和“请求体路由”。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 引入 `ProviderModelBinding`,将“模型如何被调用”从 `modelId` 中解耦出来
|
||||
- 引入 `InvocationPlanner`,统一根据 `profileId + modelId + operation` 选择协议绑定
|
||||
- 引入 `ProviderTransport`,统一处理鉴权、基础 URL 归一化、额外 Header、查询参数和供应商级发现入口
|
||||
- 将现有 `model-adapters` 重构为按 `protocol` 注册的协议适配器,而不是按模型猜测执行器
|
||||
- 为协议适配器引入 `requestSchema / responseSchema / pollingStrategy` 概念,覆盖同协议不同请求体格式的场景
|
||||
- 将文本、图片、视频三类调用统一接入同一套规划器与协议注册器
|
||||
- 扩展运行时模型发现存储,保留每个供应商下模型的原始元数据、推断出的能力和绑定信息,避免仅依赖扁平化 `ModelConfig`
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs:
|
||||
- `provider-protocol-routing`
|
||||
- Affected code:
|
||||
- `packages/drawnix/src/utils/settings-manager.ts`
|
||||
- `packages/drawnix/src/utils/runtime-model-discovery.ts`
|
||||
- `packages/drawnix/src/services/model-adapters/*`
|
||||
- `packages/drawnix/src/services/generation-api-service.ts`
|
||||
- `packages/drawnix/src/utils/gemini-api/*`
|
||||
- `packages/drawnix/src/services/video-api-service.ts`
|
||||
- `packages/drawnix/src/services/async-image-api-service.ts`
|
||||
- `packages/drawnix/src/services/media-executor/*`
|
||||
|
||||
## Relationship To Existing Changes
|
||||
|
||||
- 本变更建立在 `add-multi-provider-profiles` 的 `profiles + catalogs + presets` 结构之上
|
||||
- 本变更进一步落实 `add-multi-provider-profiles/design.md` 中“将 `ProviderType/AuthType/capabilities` 下沉到适配层”的决策
|
||||
- 本变更会扩展 `add-runtime-model-discovery` 的职责,使其不只负责模型列表同步,还要为运行时路由保留协议与绑定信息
|
||||
@@ -0,0 +1,81 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Route Provider-Backed Models Through Explicit Protocol Bindings
|
||||
|
||||
The system SHALL resolve provider-backed invocations through explicit protocol bindings instead of inferring the execution path from the bare model identifier alone.
|
||||
|
||||
#### Scenario: Same model ID uses different protocols in different provider profiles
|
||||
|
||||
- **GIVEN** two enabled provider profiles both expose a model with the ID `gemini-3-pro-image-preview`
|
||||
- **AND** one profile binds image generation to an OpenAI-compatible `/images/generations` protocol while the other binds image generation to a Gemini official `:generateContent` protocol
|
||||
- **WHEN** the user invokes image generation with a `ModelRef` that points to one of those provider profiles
|
||||
- **THEN** the system SHALL choose the protocol binding that belongs to that provider profile
|
||||
- **AND** SHALL not assume that the same model ID implies the same protocol across providers
|
||||
|
||||
#### Scenario: Same provider model has multiple protocol bindings
|
||||
|
||||
- **GIVEN** a provider-backed model has more than one protocol binding for the same operation
|
||||
- **WHEN** the user invokes that operation without a manual binding override
|
||||
- **THEN** the system SHALL choose the highest-priority valid binding for that invocation
|
||||
- **AND** SHALL allow lower-priority bindings to remain available for advanced override flows
|
||||
|
||||
### Requirement: Adapt Request Bodies Through Request Schemas
|
||||
|
||||
The system SHALL separate protocol selection from request body construction so that models using the same protocol can still be invoked with different request schemas.
|
||||
|
||||
#### Scenario: Same protocol requires different fields for different models
|
||||
|
||||
- **GIVEN** two models use the same logical protocol family
|
||||
- **AND** one requires a JSON body while the other requires multipart form fields such as `input_reference` or `first_frame_image`
|
||||
- **WHEN** the system builds a request for either model
|
||||
- **THEN** it SHALL use the request schema associated with that model binding
|
||||
- **AND** SHALL not rely on a single shared payload shape for every model in that protocol family
|
||||
|
||||
#### Scenario: Same operation uses different request schemas under one provider
|
||||
|
||||
- **GIVEN** a provider exposes multiple video-capable models
|
||||
- **AND** those models require different request field layouts for the same video generation operation
|
||||
- **WHEN** the user invokes video generation
|
||||
- **THEN** the system SHALL select the request schema attached to the chosen binding
|
||||
- **AND** SHALL serialize the request body accordingly
|
||||
|
||||
### Requirement: Unify Text, Image, And Video Invocation Planning
|
||||
|
||||
The system SHALL use one invocation planner for text, image, and video requests so that every modality resolves through the same provider context and binding model.
|
||||
|
||||
#### Scenario: Text invocation follows the same planner as image and video
|
||||
|
||||
- **GIVEN** a provider profile exposes text and image models through different protocols
|
||||
- **WHEN** the user invokes a text request and then an image request
|
||||
- **THEN** both requests SHALL resolve through the same invocation planning pipeline
|
||||
- **AND** each request SHALL select its own binding based on the requested operation
|
||||
|
||||
### Requirement: Apply Provider Transport Strategies At Runtime
|
||||
|
||||
The system SHALL execute each binding through a provider transport that applies the binding's provider-specific authentication and request transport rules.
|
||||
|
||||
#### Scenario: Different providers require different authentication styles
|
||||
|
||||
- **GIVEN** one provider binding requires Bearer authentication
|
||||
- **AND** another provider binding requires a provider-specific header or query parameter
|
||||
- **WHEN** the system executes invocations for those bindings
|
||||
- **THEN** the provider transport SHALL apply the correct authentication strategy for each provider
|
||||
- **AND** the protocol adapter SHALL not hardcode a single authentication style for all providers
|
||||
|
||||
### Requirement: Preserve Binding Identity Across Discovery And Selection
|
||||
|
||||
The system SHALL preserve provider and binding provenance across discovery, model selection, and runtime invocation.
|
||||
|
||||
#### Scenario: Discovery keeps same model ID separate by provider
|
||||
|
||||
- **GIVEN** two provider profiles discover models with the same `modelId`
|
||||
- **WHEN** the discovery results are stored and exposed to selectors
|
||||
- **THEN** the system SHALL preserve each model's `profileId` and selection identity
|
||||
- **AND** SHALL not collapse those entries into one global runtime model solely by `modelId`
|
||||
|
||||
#### Scenario: Runtime invocation can resolve binding metadata from selection
|
||||
|
||||
- **GIVEN** the user selected a provider-backed model in a selector
|
||||
- **WHEN** a request is submitted later from another entry point
|
||||
- **THEN** the system SHALL be able to recover the provider context and binding metadata from that selection
|
||||
- **AND** SHALL route the request through the same provider-specific protocol binding
|
||||
49
openspec/changes/add-provider-protocol-routing/tasks.md
Normal file
49
openspec/changes/add-provider-protocol-routing/tasks.md
Normal file
@@ -0,0 +1,49 @@
|
||||
## 1. Types And Storage
|
||||
|
||||
- [ ] 1.1 为运行时路由新增 `ResolvedProviderContext / ProviderModelBinding / InvocationPlan` 类型
|
||||
- [ ] 1.2 扩展 provider catalog 存储结构,保存每个模型的原始元数据与绑定信息
|
||||
- [ ] 1.3 停止按裸 `modelId` 作为跨 profile 的全局唯一键,统一使用 `selectionKey`
|
||||
|
||||
## 2. Provider Transport Layer
|
||||
|
||||
- [ ] 2.1 新增 `ProviderTransport` 抽象,统一处理 base URL、认证方式、额外 Header 和查询参数
|
||||
- [ ] 2.2 为 `openai-compatible / gemini-compatible / custom` 提供 transport 模板或探测入口
|
||||
- [ ] 2.3 将 discovery 从固定 `/models + Bearer` 改为通过 transport 执行
|
||||
|
||||
## 3. Binding Inference
|
||||
|
||||
- [ ] 3.1 为 discovery 增加 `bindingCandidates` 推断逻辑
|
||||
- [ ] 3.2 支持同一模型在同一 profile 下生成多个协议绑定
|
||||
- [ ] 3.3 为推断结果标记 `priority / confidence / source`
|
||||
|
||||
## 4. Invocation Planner
|
||||
|
||||
- [ ] 4.1 新增 `InvocationPlanner`,输入 `routeType + ModelRef`,输出 `InvocationPlan`
|
||||
- [ ] 4.2 让 planner 支持显式模型、默认 preset 模型和 legacy 回退
|
||||
- [ ] 4.3 当存在多个 binding 时,按优先级和能力筛选规则选择默认协议
|
||||
|
||||
## 5. Protocol Adapter Registry
|
||||
|
||||
- [ ] 5.1 将现有 `model-adapters` 重构为按 `protocol` 注册的适配器
|
||||
- [ ] 5.2 为现有 Flux / MJ / Kling / Seedance / Seedream 适配器补齐 `requestSchema` 与标准化输出
|
||||
- [ ] 5.3 为文本链路新增 `openai.chat.completions` 与 `google.generateContent` 适配器
|
||||
- [ ] 5.4 为异步任务引入可复用的 `PollingStrategy`
|
||||
|
||||
## 6. Integration
|
||||
|
||||
- [ ] 6.1 将 `generation-api-service` 改为先走 planner,再取 protocol adapter
|
||||
- [ ] 6.2 将 `gemini-api` 文本 / 图片 / 视频调用改为通过 planner 解析协议
|
||||
- [ ] 6.3 将 `media-executor` 与 fallback 执行链改为复用相同的 planner 和 adapter registry
|
||||
|
||||
## 7. UI And Advanced Overrides
|
||||
|
||||
- [ ] 7.1 设置页继续保持“最少配置”,默认自动推断 binding
|
||||
- [ ] 7.2 在歧义场景下,为高级用户提供协议绑定覆盖入口
|
||||
- [ ] 7.3 在模型管理界面中展示模型来源和可用协议摘要,而不是暴露底层细节给普通用户
|
||||
|
||||
## 8. Verification
|
||||
|
||||
- [ ] 8.1 补充“同名模型不同 profile 不同协议”的路由测试
|
||||
- [ ] 8.2 补充“同协议不同 requestSchema”的请求体构建测试
|
||||
- [ ] 8.3 补充 discovery、planner、adapter registry 的集成测试
|
||||
- [ ] 8.4 手工验证 `gemini-3-pro-image-preview` 在 OpenAI 兼容上游与 Gemini 官方上游中的双协议路由行为
|
||||
Reference in New Issue
Block a user