实现统一模型 ID 格式 (provider_id/model_name),支持跨协议模型标识和 Smart Passthrough。 核心变更: - 新增 pkg/modelid 包:解析、格式化、校验统一模型 ID - 数据库迁移:models 表使用 UUID 主键 + UNIQUE(provider_id, model_name) 约束 - Repository 层:FindByProviderAndModelName、ListEnabled 方法 - Service 层:联合唯一校验、provider ID 字符集校验 - Conversion 层:ExtractModelName、RewriteRequestModelName/RewriteResponseModelName 方法 - Handler 层:统一模型 ID 路由、Smart Passthrough、Models API 本地聚合 - 新增 error-responses、unified-model-id 规范 测试覆盖: - 单元测试:modelid、conversion、handler、service、repository - 集成测试:统一模型 ID 路由、Smart Passthrough 保真性、跨协议转换 - 迁移测试:UUID 主键、UNIQUE 约束、级联删除 OpenSpec: - 归档 unified-model-id 变更到 archive/2026-04-21-unified-model-id - 同步 11 个 delta specs 到 main specs - 新增 error-responses、unified-model-id 规范文件
14 KiB
Conversion Engine
Purpose
定义协议无关的 Canonical Model 和 ConversionEngine 转换引擎,作为所有协议间请求/响应转换的统一枢纽。
Requirement: 定义 CanonicalRequest 规范模型
系统 SHALL 定义协议无关的 CanonicalRequest 结构体,作为所有协议间请求转换的统一枢纽。
CanonicalRequest SHALL 包含以下字段:
model: 字符串,模型名称system: 可选,字符串或 SystemBlock 数组messages: CanonicalMessage 数组tools: 可选,CanonicalTool 数组tool_choice: 可选,ToolChoice 联合体parameters: RequestParametersthinking: 可选,ThinkingConfigstream: 布尔值user_id: 可选字符串output_format: 可选,OutputFormatparallel_tool_use: 可选布尔值
Scenario: CanonicalRequest 包含所有公共字段
- WHEN 从任意协议解码请求为 CanonicalRequest
- THEN CanonicalRequest SHALL 包含协议间可映射的所有公共语义字段
- THEN 协议特有字段 SHALL NOT 出现在 CanonicalRequest 中
Scenario: System 消息独立存储
- WHEN 协议使用顶层 system 字段(如 Anthropic)
- THEN SHALL 提取为
canonical.system - WHEN 协议使用 messages 数组中的 system 角色(如 OpenAI)
- THEN SHALL 从 messages 中提取为
canonical.system
Requirement: 定义 CanonicalMessage 和 ContentBlock
系统 SHALL 定义 CanonicalMessage 结构体和 ContentBlock 联合体。
CanonicalMessage SHALL 包含 role(枚举:system/user/assistant/tool)和 content(ContentBlock 数组)。
ContentBlock SHALL 支持以下类型:
text: 文本内容块tool_use: 工具调用块(id, name, input)tool_result: 工具结果块(tool_use_id, content, is_error)thinking: 思考内容块(thinking)
Scenario: ContentBlock 类型化表示
- WHEN 编码 ContentBlock
- THEN SHALL 使用
type字段标识块类型 - THEN 不同类型 SHALL 携带各自特有的字段
Scenario: Tool 输入保留原始 JSON
- WHEN 解码工具调用的 input 字段
- THEN SHALL 使用
json.RawMessage保留原始 JSON 数据 - THEN SHALL NOT 强制解析为 map 或 struct
Requirement: 定义 CanonicalTool 和 ToolChoice
系统 SHALL 定义 CanonicalTool(name, description, input_schema)和 ToolChoice 联合体(auto/none/any/tool+name)。
Scenario: ToolChoice 覆盖所有语义
- WHEN 协议使用
"required"表示必须调用工具(如 OpenAI) - THEN SHALL 映射为
{type: "any"} - WHEN 协议使用
{type: "tool", name}指定工具 - THEN SHALL 保持
{type: "tool", name}映射
Requirement: 定义 CanonicalResponse 规范模型
系统 SHALL 定义 CanonicalResponse 结构体,包含 id、model、content(ContentBlock 数组)、stop_reason(可选枚举)、usage(CanonicalUsage)。
CanonicalUsage SHALL 包含 input_tokens、output_tokens、可选的 cache_read_tokens、cache_creation_tokens、reasoning_tokens。
stop_reason 枚举 SHALL 包含:end_turn、max_tokens、tool_use、stop_sequence、content_filter、refusal、pause_turn。
Scenario: 响应内容块与请求共用类型
- WHEN 编码响应中的 content
- THEN SHALL 使用与请求相同的 ContentBlock 类型系统
- THEN TextBlock 和 ToolUseBlock SHALL 在请求和响应中通用
Requirement: 定义 CanonicalStreamEvent 联合体
系统 SHALL 定义类型化的 CanonicalStreamEvent 联合体,包含显式的 start/stop 生命周期。
事件类型 SHALL 包含:
message_start: 包含 message(id, model, usage)content_block_start: 包含 index 和 content_blockcontent_block_delta: 包含 index 和 delta(text_delta/input_json_delta/thinking_delta)content_block_stop: 包含 indexmessage_delta: 包含 delta(stop_reason)和 usagemessage_stop: 无额外数据error: 包含 error(type, message)ping: 无额外数据
Scenario: 流式事件具有完整生命周期
- WHEN 解码协议的 SSE 流为 CanonicalStreamEvent
- THEN SHALL 包含 message_start → content_block_start/delta/stop → message_delta → message_stop 的完整生命周期
- THEN 每个事件 SHALL 包含足够的信息用于编码为任意目标协议的 SSE 格式
Requirement: 定义 ProtocolAdapter 接口
系统 SHALL 定义 ProtocolAdapter 接口,每种协议实现完整适配。
ProtocolAdapter SHALL 包含以下方法:
protocolName(): 返回协议唯一标识protocolVersion(): 返回协议版本supportsPassthrough(): 返回是否支持同协议透传detectInterfaceType(nativePath): 根据协议的 URL 路径识别接口类型buildUrl(nativePath, interfaceType): 映射 URL 路径buildHeaders(provider): 构建认证和必要 HeadersupportsInterface(interfaceType): 判断是否支持某接口类型decodeRequest(raw): 协议格式 → CanonicalRequestencodeRequest(canonical, provider): CanonicalRequest → 协议格式decodeResponse(raw): 协议格式 → CanonicalResponseencodeResponse(canonical): CanonicalResponse → 协议格式createStreamDecoder(): 创建 StreamDecoder 实例createStreamEncoder(): 创建 StreamEncoder 实例encodeError(error): ConversionError → 协议错误格式- 扩展层编解码方法(Models/Embeddings/Rerank)
Scenario: Adapter 注册到 Registry
- WHEN 应用启动
- THEN SHALL 将所有 Adapter 注册到 AdapterRegistry
- THEN 可通过
registry.get(protocolName)获取 Adapter
Scenario: Adapter 自描述接口能力
- WHEN 调用
supportsInterface(interfaceType) - THEN SHALL 返回布尔值表示是否支持该接口类型
- THEN 不支持的接口 SHALL 由引擎走透传逻辑
Scenario: Adapter 识别接口类型
- WHEN 调用
detectInterfaceType(nativePath) - THEN SHALL 根据协议的 URL 路径模式识别接口类型
- THEN SHALL 返回对应的 InterfaceType(CHAT/MODELS/MODEL_INFO/EMBEDDINGS/RERANK)
- THEN 无法识别的路径 SHALL 返回 PASSTHROUGH 类型
Requirement: 实现 AdapterRegistry
系统 SHALL 实现 AdapterRegistry,支持 Adapter 的注册和查询。
Scenario: 注册 Adapter
- WHEN 调用
registry.register(adapter) - THEN SHALL 以 adapter 的 protocolName 为 key 存储
- THEN 重复注册同名协议 SHALL 返回错误
Scenario: 查询 Adapter
- WHEN 调用
registry.get(protocolName) - THEN SHALL 返回已注册的 ProtocolAdapter
- THEN 未注册的协议 SHALL 返回错误
Requirement: 实现 ConversionEngine 门面
系统 SHALL 实现 ConversionEngine 作为无状态的转换引擎门面,线程安全、可复用。
ConversionEngine SHALL 提供:
registerAdapter(adapter): 注册协议适配器use(middleware): 注册转换中间件isPassthrough(clientProtocol, providerProtocol): 判断是否同协议透传convertHttpRequest(request, clientProtocol, providerProtocol, provider): 请求转换convertHttpResponse(response, clientProtocol, providerProtocol, interfaceType): 响应转换createStreamConverter(clientProtocol, providerProtocol, provider): 创建流式转换器
Scenario: 跨协议 Chat 请求转换
- WHEN clientProtocol != providerProtocol 且 interfaceType == CHAT
- THEN SHALL 使用 clientAdapter.detectInterfaceType 识别接口类型
- THEN SHALL 使用 clientAdapter.decodeRequest 解码为 CanonicalRequest
- THEN SHALL 经 middlewareChain 处理
- THEN SHALL 使用 providerAdapter.encodeRequest 编码为目标协议格式
- THEN SHALL 使用 providerAdapter.buildUrl 映射目标 URL
- THEN SHALL 使用 providerAdapter.buildHeaders 构建目标 Header
- THEN SHALL 返回 HTTPRequestSpec{URL, Method, Headers, Body}
Scenario: 同协议透传
- WHEN clientProtocol == providerProtocol 且 supportsPassthrough == true
- THEN SHALL 使用 providerAdapter.buildHeaders 重建 Header
- THEN SHALL 原样传递请求 Body
- THEN SHALL NOT 执行 decode/encode 转换
Scenario: 未知接口透传
- WHEN clientAdapter.detectInterfaceType 返回 PASSTHROUGH 类型
- THEN SHALL 使用 providerAdapter.buildUrl 和 buildHeaders 适配 URL 和 Header
- THEN SHALL 原样传递请求 Body
Requirement: 定义 StreamDecoder/StreamEncoder/StreamConverter 接口
系统 SHALL 定义流式转换的三层接口。
StreamDecoder SHALL 接口:
processChunk(rawChunk): 原始字节 → CanonicalStreamEvent 数组flush(): 刷新缓冲区 → CanonicalStreamEvent 数组
StreamEncoder SHALL 接口:
encodeEvent(event): CanonicalStreamEvent → SSE 字节数组flush(): 刷新缓冲区 → SSE 字节数组
StreamConverter SHALL 接口:
processChunk(rawChunk): 原始字节 → SSE 字节数组flush(): 刷新 → SSE 字节数组
Scenario: PassthroughStreamConverter
- WHEN 同协议透传时
- THEN SHALL 直接传递原始 SSE 字节,不做任何解析或转换
Scenario: CanonicalStreamConverter
- WHEN 跨协议转换时
- THEN SHALL 使用 providerAdapter 的 StreamDecoder 解码原始 SSE
- THEN SHALL 经 middlewareChain 处理事件
- THEN SHALL 使用 clientAdapter 的 StreamEncoder 编码为目标 SSE 格式
Requirement: 定义 ConversionMiddleware 接口
系统 SHALL 定义 ConversionMiddleware 接口,用于在 decode→encode 之间拦截转换。
intercept(canonical, clientProtocol, providerProtocol, context): 修改或拒绝 CanonicalinterceptStreamEvent(event, clientProtocol, providerProtocol, context): 修改或拒绝流式事件
Scenario: Middleware 链式执行
- WHEN 注册多个 Middleware
- THEN SHALL 按注册顺序链式执行
- THEN 任一 Middleware 返回错误 SHALL 中断后续执行
Scenario: Middleware 中断转换
- WHEN Middleware 返回 ConversionError
- THEN SHALL 停止转换流程
- THEN SHALL 使用 clientAdapter.encodeError 编码错误响应
Requirement: 定义 ConversionError 错误体系
系统 SHALL 定义 ConversionError 和 ErrorCode 枚举。
ErrorCode SHALL 包含:INVALID_INPUT、MISSING_REQUIRED_FIELD、INCOMPATIBLE_FEATURE、FIELD_MAPPING_FAILURE、TOOL_CALL_PARSE_ERROR、JSON_PARSE_ERROR、STREAM_STATE_ERROR、UTF8_DECODE_ERROR、PROTOCOL_CONSTRAINT_VIOLATION、ENCODING_FAILURE、INTERFACE_NOT_SUPPORTED。
Scenario: 转换错误包含上下文
- WHEN 转换过程中发生错误
- THEN ConversionError SHALL 包含 code、message、clientProtocol、providerProtocol、interfaceType 等上下文
- THEN SHALL 支持包装原始错误(cause)
Requirement: 定义 InterfaceType 枚举和接口分层
系统 SHALL 定义 InterfaceType 枚举(CHAT、MODELS、MODEL_INFO、EMBEDDINGS、RERANK)和接口分层策略。
- 核心层(CHAT):使用 Canonical Model 深度转换
- 扩展层(MODELS、MODEL_INFO、EMBEDDINGS、RERANK):使用轻量 Canonical Models 做字段映射
- 透传层(未知接口):URL+Header 适配后 Body 原样转发
Scenario: 扩展层接口转换
- WHEN interfaceType 为 MODELS/MODEL_INFO/EMBEDDINGS/RERANK
- THEN SHALL 使用对应扩展层 Canonical Model 做轻量字段映射
- THEN 双方都不支持时 SHALL 走透传逻辑
Requirement: <20><>义 TargetProvider 结构体
系统 SHALL 定义 TargetProvider 结构体,包含 base_url、api_key、model_name、adapter_config。
Scenario: Adapter 从 TargetProvider 获取配置
- WHEN Adapter 调用 buildHeaders(provider)
- THEN SHALL 从 provider.api_key 提取认证信息
- THEN SHALL 从 provider.adapter_config 提取协议专属配置
- THEN SHALL 使用 provider.model_name 覆盖请求中的 model 字段
Requirement: 跨协议响应转换支持 model 覆写
ConversionEngine SHALL 在跨协议响应转换时支持 model 字段覆写。
Scenario: ConvertHttpResponse 接收 modelOverride 参数
- WHEN 调用
ConvertHttpResponse时传入modelOverride参数(跨协议场景,非空字符串) - THEN SHALL 在解码上游响应到 canonical 后,将
Model字段设为modelOverride - THEN SHALL 使用覆写后的 canonical 编码为客户端协议格式
Scenario: modelOverride 为空
- WHEN 调用
ConvertHttpResponse时modelOverride为空字符串 - THEN SHALL NOT 覆写 canonical 的 Model 字段,保持上游原始值
Scenario: Chat 响应 model 覆写
- WHEN 跨协议转换 Chat 类型响应且
modelOverride非空 - THEN
CanonicalResponse.ModelSHALL 被设为modelOverride
Scenario: Embedding 响应 model 覆写
- WHEN 跨协议转换 Embedding 类型响应且
modelOverride非空 - THEN
CanonicalEmbeddingResponse.ModelSHALL 被设为modelOverride
Scenario: Rerank 响应 model 覆写
- WHEN 跨协议转换 Rerank 类型响应且
modelOverride非空 - THEN
CanonicalRerankResponse.ModelSHALL 被设为modelOverride
Requirement: 跨协议流式转换支持 model 覆写
ConversionEngine SHALL 在跨协议流式转换时支持 model 字段覆写。
Scenario: CreateStreamConverter 接收 modelOverride 参数
- WHEN 调用
CreateStreamConverter时传入modelOverride参数(跨协议场景) - THEN SHALL 在流式 canonical 事件中将
Model字段设为modelOverride
Requirement: TargetProvider 字段语义
TargetProvider 的 ModelName 字段 SHALL 存储上游供应商的模型名称(即 model_name 字段值),语义保持不变。
Scenario: encoder 使用 TargetProvider.ModelName
- WHEN 协议适配器编码请求时
- THEN SHALL 使用
TargetProvider.ModelName作为发给上游的model字段值(值为路由结果中的 model_name)