1
0
Files
nex/openspec/specs/conversion-engine/spec.md

20 KiB
Raw Blame History

Conversion Engine

Purpose

定义协议无关的 Canonical Model 和 ConversionEngine 转换引擎,作为所有协议间请求/响应转换的统一枢纽。

Requirement: 定义 CanonicalRequest 规范模型

系统 SHALL 定义协议无关的 CanonicalRequest 结构体,作为所有协议间请求转换的统一枢纽。

CanonicalRequest SHALL 包含以下字段:

  • model: 字符串,模型名称
  • system: 可选,字符串或 SystemBlock 数组
  • messages: CanonicalMessage 数组
  • tools: 可选CanonicalTool 数组
  • tool_choice: 可选ToolChoice 联合体
  • parameters: RequestParameters
  • thinking: 可选ThinkingConfig
  • stream: 布尔值
  • user_id: 可选字符串
  • output_format: 可选OutputFormat
  • parallel_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/toolcontentContentBlock 数组)。

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 定义 CanonicalToolname, description, input_schemaToolChoice 联合体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 结构体,包含 idmodelcontentContentBlock 数组)、stop_reason(可选枚举)、usageCanonicalUsage

CanonicalUsage SHALL 包含 input_tokensoutput_tokens、可选的 cache_read_tokenscache_creation_tokensreasoning_tokens

stop_reason 枚举 SHALL 包含:end_turnmax_tokenstool_usestop_sequencecontent_filterrefusalpause_turn

Scenario: 响应内容块与请求共用类型

  • WHEN 编码响应中的 content
  • THEN SHALL 使用与请求相同的 ContentBlock 类型系统
  • THEN TextBlock 和 ToolUseBlock SHALL 在请求和响应中通用

Requirement: 定义 CanonicalStreamEvent 联合体

系统 SHALL 定义类型化的 CanonicalStreamEvent 联合体,包含显式的 start/stop 生命周期。

事件类型 SHALL 包含:

  • message_start: 包含 messageid, model, usage
  • content_block_start: 包含 index 和 content_block
  • content_block_delta: 包含 index 和 deltatext_delta/input_json_delta/thinking_delta
  • content_block_stop: 包含 index
  • message_delta: 包含 deltastop_reason和 usage
  • message_stop: 无额外数据
  • error: 包含 errortype, 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): 构建认证和必要 Header
  • supportsInterface(interfaceType): 判断是否支持某接口类型
  • decodeRequest(raw): 协议格式 → CanonicalRequest
  • encodeRequest(canonical, provider): CanonicalRequest → 协议格式
  • decodeResponse(raw): 协议格式 → CanonicalResponse
  • encodeResponse(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 返回对应的 InterfaceTypeCHAT/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): 修改或拒绝 Canonical
  • interceptStreamEvent(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 定义 ConversionErrorErrorCode 枚举。

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_urlapi_keymodel_nameadapter_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 调用 ConvertHttpResponsemodelOverride 为空字符串
  • THEN SHALL NOT 覆写 canonical 的 Model 字段,保持上游原始值

Scenario: Chat 响应 model 覆写

  • WHEN 跨协议转换 Chat 类型响应且 modelOverride 非空
  • THEN CanonicalResponse.Model SHALL 被设为 modelOverride

Scenario: Embedding 响应 model 覆写

  • WHEN 跨协议转换 Embedding 类型响应且 modelOverride 非空
  • THEN CanonicalEmbeddingResponse.Model SHALL 被设为 modelOverride

Scenario: Rerank 响应 model 覆写

  • WHEN 跨协议转换 Rerank 类型响应且 modelOverride 非空
  • THEN CanonicalRerankResponse.Model SHALL 被设为 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

Requirement: 同协议请求 URL 使用 Adapter 映射

ConversionEngine SHALL 在同协议透传和 Smart Passthrough 场景下使用 providerAdapter.BuildUrl 构建上游 URL 路径。

Scenario: 同协议 Chat URL 映射

  • WHEN clientProtocol == providerProtocol 且 interfaceType 为 CHAT
  • THEN ConversionEngine SHALL 调用 providerAdapter.BuildUrl(nativePath, interfaceType)
  • THEN 上游 URL SHALL 为 provider.BaseURL 与 BuildUrl 返回路径的组合
  • THEN ConversionEngine SHALL NOT 直接将 provider.BaseURL 与 nativePath 拼接为上游 URL

Scenario: 未知接口 URL 映射

  • WHEN interfaceType 为 PASSTHROUGH
  • THEN providerAdapter.BuildUrl SHALL 返回适合目标协议的路径或原 nativePath
  • THEN ConversionEngine SHALL 使用该路径构建上游 URL

Requirement: 上游 URL 构建使用目标协议 Adapter

ConversionEngine SHALL 始终使用目标供应商协议的 adapter 构建上游 URL 路径,避免客户端协议 nativePath 中的版本段泄露到目标协议上游 URL。

Scenario: OpenAI 客户端到 OpenAI 供应商

  • WHEN clientProtocol 为 openaiproviderProtocol 为 openainativePath 为 /v1/chat/completions
  • THEN ConversionEngine SHALL 调用 OpenAI adapter 的 BuildUrl(nativePath, InterfaceTypeChat)
  • THEN 上游 path SHALL 为 /chat/completions
  • THEN 当 provider.base_url 为 https://api.openai.com/v1 时,最终上游 URL SHALL 为 https://api.openai.com/v1/chat/completions

Scenario: OpenAI 客户端到 Anthropic 供应商

  • WHEN clientProtocol 为 openaiproviderProtocol 为 anthropicnativePath 为 /v1/chat/completions
  • THEN ConversionEngine SHALL 使用 OpenAI adapter 识别接口类型为 InterfaceTypeChat
  • THEN ConversionEngine SHALL 调用 Anthropic adapter 的 BuildUrl(nativePath, InterfaceTypeChat)
  • THEN 上游 path SHALL 为 /v1/messages
  • THEN OpenAI nativePath 中的 /v1/chat/completions SHALL NOT 被直接拼接到 Anthropic 上游 URL

Scenario: Anthropic 客户端到 OpenAI 供应商

  • WHEN clientProtocol 为 anthropicproviderProtocol 为 openainativePath 为 /v1/messages
  • THEN ConversionEngine SHALL 使用 Anthropic adapter 识别接口类型为 InterfaceTypeChat
  • THEN ConversionEngine SHALL 调用 OpenAI adapter 的 BuildUrl(nativePath, InterfaceTypeChat)
  • THEN 上游 path SHALL 为 /chat/completions
  • THEN 当 provider.base_url 为 https://api.openai.com/v1 时,最终上游 URL SHALL 为 https://api.openai.com/v1/chat/completions

Requirement: SSE Frame 级 Smart Passthrough

系统 SHALL 支持同协议流式 Smart Passthrough 对 SSE frame 中的 model 字段进行最小化改写。

Scenario: 改写 SSE data JSON

  • WHEN 同协议流式响应需要将上游模型名改写为统一模型 ID
  • THEN 系统 SHALL 按 SSE frame 解析上游字节流
  • THEN 对包含 JSON payload 的 data 行 SHALL 调用 adapter.RewriteResponseModelName 改写 model 字段
  • THEN SHALL 重建合法 SSE frame 输出

Scenario: 保留 DONE 事件

  • WHEN SSE frame 的 data payload 为 [DONE]
  • THEN 系统 SHALL 原样输出 [DONE]
  • THEN SHALL NOT 尝试按 JSON 解析

Scenario: 改写失败宽容降级

  • WHEN SSE frame 解析或 model 改写失败
  • THEN 系统 SHALL 记录 warn 日志
  • THEN SHALL 输出原始 SSE frame
  • THEN SHALL 继续处理后续 frame

Requirement: Adapter 模型提取边界

ProtocolAdapter SHALL 只对明确适配的接口提供 model 提取和 model 改写能力。

Scenario: 已适配接口提取 model

  • WHEN ifaceType 为 adapter 明确支持提取 model 的接口
  • THEN ExtractModelName SHALL 按该协议和接口的请求格式提取 model 字段

Scenario: 未适配接口不提取 model

  • WHEN ifaceType 为 PASSTHROUGH 或 adapter 未明确支持提取 model 的接口
  • THEN ExtractModelName SHALL 返回错误或空结果
  • THEN 调用方 SHALL 按无 model 请求处理

Requirement: 跨协议多模态暂不支持

ConversionEngine SHALL 在跨协议完整转换中对当前暂不支持的多模态内容返回明确错误。

Scenario: 跨协议请求包含多模态内容块

  • WHEN clientProtocol != providerProtocol 且 CanonicalRequest 中包含 image、audio、video 或 file 内容块
  • THEN ConversionEngine SHALL 中断转换
  • THEN SHALL 返回网关层 UNSUPPORTED_MULTIMODAL 错误
  • THEN SHALL NOT 静默丢弃多模态内容

Scenario: 同协议多模态请求

  • WHEN clientProtocol == providerProtocol 且请求通过 Smart Passthrough 或 raw passthrough 处理
  • THEN 系统 SHALL 保留原始请求体中未改写字段
  • THEN SHALL NOT 因多模态字段存在而执行跨协议多模态校验

Requirement: 上游非 2xx 响应不进入转换

ConversionEngine SHALL 只转换调用方传入的成功响应ProxyHandler SHALL 在调用转换前过滤上游非 2xx 响应。

Scenario: 非 2xx 响应绕过响应转换

  • WHEN 上游响应状态码不是 2xx
  • THEN ProxyHandler SHALL NOT 调用 ConversionEngine.ConvertHttpResponse
  • THEN ProxyHandler SHALL 直接透传该响应

Scenario: 流式非 2xx 响应绕过流式转换

  • WHEN 流式请求收到上游非 2xx 响应
  • THEN ProxyHandler SHALL NOT 调用 ConversionEngine.CreateStreamConverter
  • THEN ProxyHandler SHALL 直接透传该响应

Requirement: 协议路径来源

ProtocolAdapter SHALL 以对应协议的本地 API reference 文档作为 URL 识别和 URL 映射的事实来源。

Scenario: OpenAI 路径来源

  • WHEN 实现或测试 OpenAI adapter 的 DetectInterfaceType 或 BuildUrl
  • THEN SHALL 参考 docs/api_reference/openai 中的接口路径
  • THEN SHALL 忽略 docs/api_reference/openai/responses 目录
  • THEN SHALL NOT 因其他协议包含 /v1 而给 OpenAI nativePath 添加 /v1

Scenario: Anthropic 路径来源

  • WHEN 实现或测试 Anthropic adapter 的 DetectInterfaceType 或 BuildUrl
  • THEN SHALL 参考 docs/api_reference/anthropic 中的接口路径
  • THEN SHALL 保留文档中接口路径自带的 /v1 前缀
  • THEN SHALL NOT 因 OpenAI nativePath 不含 /v1 而移除 Anthropic nativePath 中的 /v1