# LLM API Protocol Conversion Layer — Architecture Design > 语言无关的 HTTP 层 SDK。以 Hub-and-Spoke 架构实现多协议间的双向转换,覆盖完整 HTTP 接口体系,同协议自动透传,为新协议和多模态预留扩展点。 --- ## 目录 1. [设计目标与约束](#1-设计目标与约束) 2. [架构总览](#2-架构总览) 3. [接口体系分层](#3-接口体系分层) 4. [Canonical Model(规范模型)](#4-canonical-model规范模型) 5. [Protocol Adapter 接口](#5-protocol-adapter-接口) 6. [Conversion Engine(转换引擎)](#6-conversion-engine转换引擎) 7. [流式转换架构](#7-流式转换架构) 8. [扩展点设计](#8-扩展点设计) 9. [错误处理](#9-错误处理) 10. [附录 A:模块依赖](#附录-a模块依赖) 11. [附录 B:接口速查](#附录-b接口速查) 12. [附录 C:字段晋升规范](#附录-c字段晋升规范) 13. [附录 D:协议适配清单模板](#附录-d协议适配清单模板) 14. [附录 E:协议适配文档索引](#附录-e协议适配文档索引) --- ## 1. 设计目标与约束 ### 1.1 核心目标 | 目标 | 说明 | |------|------| | **完整 HTTP 接口体系转换** | 覆盖 /models、/embeddings、/rerank 等全部接口的 URL 路由映射、请求头转换、请求体/响应体格式转换 | | **输入输出解耦** | 客户端协议和服务端协议独立指定,任意组合 | | **同协议透传** | client == provider 时跳过转换,零语义损失、零序列化开销 | | **尽力转换** | 能对接的参数尽可能对接,不能对接的各自忽略,保障最大覆盖面 | | **协议可扩展** | 添加新协议只需实现 Adapter,不修改核心引擎 | | **流式优先** | SSE 流式转换作为核心能力,与非流式同等地位 | | **Tool Calling 核心** | 工具调用是编程场景的一等公民 | | **语言无关** | 不绑定编程语言,用伪类型描述接口 | ### 1.2 约束 | 约束 | 说明 | |------|------| | 部署形态 | HTTP 层 SDK,处理 HTTP 请求/响应转换(URL + Headers + Body),不包含 HTTP 服务器启动/监听 | | 当前模态 | 仅文本(含 Tool Calling),接口体系覆盖 /models、/embeddings、/rerank | | Provider 必传 | 每次转换调用需传入 TargetProvider,提供目标上游的地址、认证、模型名等信息 | | 适配器注册 | 所有 ProtocolAdapter 通过代码注册,不支持动态增减 | | 有状态特性 | 初始不实现,架构预留扩展点 | ### 1.3 设计决策溯源 | 决策 | 依据 | |------|------| | HTTP 层 SDK | 编程工具启动时调用 /models 等接口,缺失会报错或功能降级 | | Hub-and-Spoke | 以规范格式为枢纽,将 O(n²) 的协议对转换降为 O(n) | | 自定义 Canonical Model | 不选用厂商格式,避免语义损失和厂商锁定 | | 协议识别外部化 | 协议识别是调用方职责,引擎不绑定具体识别方式 | | 接口分层 + 尽力转换 | 不同接口差异程度不同,分层处理最大化覆盖面 | | 同协议透传 | 零转换的性能优势已验证 | --- ## 2. 架构总览 ### 2.1 分层架构 ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ 上层 HTTP 框架(用户自选) │ │ Express / FastAPI / Axum / Gin / 任意框架 │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ 协议识别 + 前缀剥离(调用方职责) │ │ │ │ │ │ │ │ 入站: /{protocol}/{native_path} │ │ │ │ │ │ │ │ //v1/chat/completions → client=protocol_a, /v1/... │ │ │ │ //v1/messages → client=protocol_b, /v1/... │ │ │ │ │ │ │ │ Step 1: 识别 client protocol(URL 前缀 / 配置映射 / 任意方式) │ │ │ │ Step 2: 剥离前缀 → 得到 nativePath │ │ │ │ Step 3: 确定 provider protocol(配置决定) │ │ │ └─────────────────────────────┬───────────────────────────────────┘ │ └───────────────────────────────┬─────────────────────────────────────────┘ │ nativePath + clientProtocol + providerProtocol ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ Conversion Engine (SDK) │ │ │ │ ┌────────────┼────────────┐ │ │ ▼ ▼ ▼ │ │ ┌──────────────┐ ┌─────────┐ ┌──────────┐ │ │ │ Passthrough │ │核心接口层│ │扩展接口层 │ │ │ │ 同协议直通 │ │ Chat │ │ Models │ │ │ │ 或未知接口 │ │ 流式 │ │ Embed │ │ │ │ 直接转发 │ │ Tool │ │ Rerank │ │ │ └──────────────┘ │ Thinking│ └──────────┘ │ │ └────┬────┘ │ │ ┌──────────▼───────────────┐ │ │ │ ProtocolAdapter 层 │ │ │ │ ┌────────┐ ┌─────────┐ │ │ │ │ │Protocol│ │Protocol │ │ │ │ │ │ A │ │ B │ │ │ │ │ └────────┘ └─────────┘ │ │ │ └──────────────┬────────────┘ │ │ ┌──────────────▼────────────┐ │ │ │ Canonical Model │ │ │ └───────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────┘ ``` ### 2.2 URL 路由规则 调用方负责识别协议并剥离前缀,将 `nativePath`、`clientProtocol`、`providerProtocol` 传入引擎。调用方可自行决定识别方式(URL 前缀、配置映射等)。 ``` 入站 URL 调用方剥离前缀后 引擎出站 ────────────────────────────────────────────────────────────────────────────── //v1/chat/completions → /v1/chat/completions → 目标协议路径 //v1/messages → /v1/messages → 目标协议路径 //v1/models → /v1/models → /v1/models(通常不变) ``` 出站到上游 API 时使用服务端协议原生路径(无前缀)。 ### 2.3 请求处理流程 每个 HTTP 请求的转换流程: ``` 客户端入站 调用方(协议识别+前缀剥离) SDK 内部处理 上游出站 ┌──────────────────┐ ┌──────────────────┐ │ URL: │ 调用方完成: 1. 接口识别: CHAT │ URL: │ │ // │ · clientProtocol 2. 同协议? ──yes──▶ 直接转发│ 目标协议 │ │ v1/... │ · nativePath └──no──▶ 继续转换 │ 原生路径 │ │ Headers: │ · providerProtocol 3. URL 映射: 目标路径 │ Headers: │ │ 协议原生格式 │ 4. Header 构建: 目标格式 │ 目标协议格式 │ │ Body: │ 5. Body 转换: D→C→E │ Body: │ │ 协议原生格式 │ │ 目标协议格式 │ └──────────────────┘ └──────────────────┘ ``` 响应方向同理(含流式)。D=Decoder, C=Canonical, E=Encoder。 **同协议透传**:client == provider 时,仅重建 Header 后原样转发到上游。 **未知接口透传**:无法识别的路径,URL+Header 适配后 Body 原样转发。 --- ## 3. 接口体系分层 ### 3.1 分层策略 | 层级 | 接口 | 转换方式 | |------|------|---------| | **核心层** | Chat 接口(各协议的核心对话接口) | Canonical Model 深度转换 | | **扩展层** | /models, /models/{model}, /embeddings, /rerank | 轻量级字段映射 | | **透传层** | /files, /count_tokens, /batches, /fine-tuning, /audio/*, /images/*, /video/*, /moderations 等未知路径 | URL+Header 适配后 Body 原样转发 | **接口纳入原则**:只有 ≥2 个协议提供相同功能的接口才纳入转换层(核心层或扩展层),其余走透传。 **尽力转换策略**: - 双方都支持且可映射 → 完整转换(如 /models) - 服务端协议不支持该接口 → 透传到上游或返回空响应/错误(可配置) - 未知接口 → 始终透传 **各协议的接口清单**详见各自的协议适配文档(附录 E)。 --- ## 4. Canonical Model(规范模型) 协议无关的统一内部表示,Hub-and-Spoke 架构的枢纽。**核心层**使用完整 Canonical Model 深度转换;**扩展层**使用轻量 Canonical Models 做字段映射;**透传层**不使用。 设计原则:超集设计、类型化 discriminated union、前向兼容。Canonical Model 面向当前已适配协议的公共语义进行抽象,字段随协议扩展演进(详见附录 C:字段晋升规范)。 **适配新协议时**:对照本节定义逐字段确认映射关系。协议特有字段不纳入 Canonical,同协议透传时自然保留,跨协议时丢弃(详见附录 C 字段分类规则)。 ### 4.1 CanonicalRequest ``` CanonicalRequest { model: String system: Union> SystemBlock { text: String } messages: Array tools: Option> tool_choice: Option parameters: RequestParameters thinking: Option stream: Boolean user_id: Option output_format: Option parallel_tool_use: Option // true = 允许并行, false = 禁止并行 } ``` ### 4.2 RequestParameters ``` RequestParameters { max_tokens: Option temperature: Option top_p: Option top_k: Option frequency_penalty: Option presence_penalty: Option stop_sequences: Option> } ``` ### 4.3 CanonicalMessage / ContentBlock ``` CanonicalMessage { role: Enum content: Array } ContentBlock = Union< TextBlock, { type: "text", text: String } ToolUseBlock, { type: "tool_use", id: String, name: String, input: Object } ToolResultBlock, { type: "tool_result", tool_use_id: String, content: Union>, is_error: Option } ThinkingBlock, { type: "thinking", thinking: String } ImageBlock, { type: "image", source: ... } // 多模态预留 AudioBlock, { type: "audio", source: ... } // 多模态预留 VideoBlock, { type: "video", source: ... } // 多模态预留 FileBlock { type: "file", source: ... } // 多模态预留 > ``` ### 4.4 CanonicalTool / ToolChoice ``` CanonicalTool { name: String description: Option input_schema: Object } ToolChoice = Union< {type: "auto"}, {type: "none"}, {type: "any"}, {type: "tool", name: String} > ``` ### 4.5 ThinkingConfig ``` ThinkingConfig { type: Enum budget_tokens: Option // token 级控制 effort: Option> // 抽象级别控制 } ``` ### 4.6 OutputFormat ``` OutputFormat = Union< { type: "json_object" }, { type: "json_schema", json_schema: { name: String, schema: Object, strict: Option } }, { type: "text" } > ``` ### 4.7 CanonicalResponse ``` CanonicalResponse { id: String model: String content: Array stop_reason: Option // end_turn | max_tokens | tool_use | stop_sequence | content_filter | refusal usage: CanonicalUsage } CanonicalUsage { input_tokens: Integer output_tokens: Integer cache_read_tokens: Option cache_creation_tokens: Option reasoning_tokens: Option } ``` ### 4.8 CanonicalStreamEvent 采用类型化事件模型,具有显式的 start/stop 生命周期,语义明确,便于任意协议间的双向转换: ``` CanonicalStreamEvent = Union< MessageStartEvent, { type: "message_start", message: {id, model, usage} } ContentBlockStartEvent, { type: "content_block_start", index, content_block } ContentBlockDeltaEvent, { type: "content_block_delta", index, delta } ContentBlockStopEvent, { type: "content_block_stop", index } MessageDeltaEvent, { type: "message_delta", delta: {stop_reason}, usage } MessageStopEvent, { type: "message_stop" } ErrorEvent, { type: "error", error: {type, message} } PingEvent { type: "ping" } > content_block: Union< {type: "text", text: ""}, {type: "tool_use", id, name, input: {}}, {type: "thinking", thinking: ""}, {type: "image"} | {type: "audio"} | {type: "video"} // 预留 > delta: Union< {type: "text_delta", text}, {type: "input_json_delta", partial_json}, {type: "thinking_delta", thinking}, {type: "image_delta", data} | {type: "audio_delta", data} // 预留 > ``` ### 4.9 扩展层 Canonical Models ``` // /models CanonicalModelList { models: Array } CanonicalModel { id, name, created, owned_by } // /models/{model} CanonicalModelInfo { id, name, created, owned_by } // /embeddings CanonicalEmbeddingRequest { model, input, encoding_format?, dimensions? } CanonicalEmbeddingResponse { data: [{index, embedding}], model, usage } // /rerank CanonicalRerankRequest { model, query, documents, top_n?, return_documents? } CanonicalRerankResponse { results: [{index, relevance_score, document?}], model } ``` ### 4.10 设计说明 **`system` 为什么独立于 `messages`?** 不同协议对系统消息的处理方式不同:有的用顶层字段,有的嵌入 messages 数组。独立 `system` 语义更清晰,Decoder 提取、Encoder 注入,适配时按各自协议规范处理即可。 **流式事件为什么用类型化事件模型?** 类型化事件有显式的 start/stop 生命周期,语义明确。delta 模型需要状态机推断语义,增加双向转换的复杂度。新协议适配时,如协议原生支持类似生命周期,直接映射;如使用 delta 模型,需在 StreamDecoder 中用状态机转换。 **`user_id`、`output_format`、`parallel_tool_use` 为什么独立为顶层字段?** 这些是多个协议共同表达的公共语义,只是字段名、嵌套位置或语义方向(正/反)有差异。统一为协议中立的顶层字段,避免命名偏向。适配新协议时,对照各字段在 Canonical 中的定义,实现提取(Decoder)和注入(Encoder)即可。 --- ## 5. Protocol Adapter 接口 ### 5.1 TargetProvider 每次转换调用传入的目标上游信息。Adapter 通过 Provider 获取认证和配置,**不需要理解其他协议的 Header 格式**。 ``` TargetProvider { base_url: String // 上游 API 地址 api_key: String // 上游认证密钥 model_name: String // 目标模型名(调用方完成映射后传入) adapter_config: Map // 协议专属配置 } ``` `adapter_config` 由各 Adapter 自行定义所需的 key,引擎不感知含义,原样透传。各 Adapter 需在协议适配文档中声明自己从 `adapter_config` 读取的 key 列表和默认值。 ### 5.2 ProtocolAdapter 每种协议的完整适配器,融入所有接口类型的处理能力。所有 Adapter 通过代码注册,不支持动态增减。新协议可通过空函数(如不支持的扩展层接口直接透传)渐进适配,接口集中定义方便明确所有应实现的内容。 ``` interface ProtocolAdapter { protocolName(): String protocolVersion(): String supportsPassthrough(): Boolean // 同协议透传开关,默认 true // HTTP 级别 detectInterfaceType(nativePath: String): InterfaceType // 根据协议的 URL 路径识别接口类型 buildUrl(nativePath: String, interfaceType: InterfaceType): String // 始终返回有效 URL;未知接口返回 nativePath buildHeaders(provider: TargetProvider): Map supportsInterface(interfaceType: InterfaceType): Boolean // 核心层:Chat decodeRequest(raw): CanonicalRequest encodeRequest(canonical, provider): RawRequest decodeResponse(raw): CanonicalResponse encodeResponse(canonical): RawResponse // 核心层:流式 createStreamDecoder(): StreamDecoder createStreamEncoder(): StreamEncoder // 错误编码 encodeError(error: ConversionError): RawResponse // 扩展层 decodeModelsResponse(raw): CanonicalModelList encodeModelsResponse(canonical): RawResponse decodeModelInfoResponse(raw): CanonicalModelInfo encodeModelInfoResponse(canonical): RawResponse decodeEmbeddingRequest(raw): CanonicalEmbeddingRequest encodeEmbeddingRequest(canonical, provider): RawRequest decodeEmbeddingResponse(raw): CanonicalEmbeddingResponse encodeEmbeddingResponse(canonical): RawResponse decodeRerankRequest(raw): CanonicalRerankRequest encodeRerankRequest(canonical, provider): RawRequest decodeRerankResponse(raw): CanonicalRerankResponse encodeRerankResponse(canonical): RawResponse } ``` **`buildHeaders` 的设计**:Adapter 只需从 `provider` 中提取自己协议需要的认证和配置信息,构建自己的 Header 格式。不再需要理解其他协议的 Header。 ### 5.3 InterfaceType ``` InterfaceType = Enum< CHAT, MODELS, MODEL_INFO, EMBEDDINGS, RERANK, AUDIO, IMAGES // 预留:多模态扩展时启用 > ``` ### 5.4 StreamDecoder / StreamEncoder ``` interface StreamDecoder { processChunk(rawChunk): Array flush(): Array } interface StreamEncoder { encodeEvent(event): Array flush(): Array } ``` ### 5.5 AdapterRegistry ``` interface AdapterRegistry { register(adapter: ProtocolAdapter): void get(protocolName: String): ProtocolAdapter listProtocols(): Array } ``` --- ## 6. Conversion Engine(转换引擎) ### 6.1 ConversionEngine ConversionEngine 是无状态的格式转换工具,仅做协议间的编解码和字段映射,不持有会话状态,线程安全,可复用。 **协议识别**:`clientProtocol` 和 `providerProtocol` 由调用方确定并传入引擎(详见 §2.2)。 ``` class ConversionEngine { registry: AdapterRegistry middlewareChain: MiddlewareChain registerAdapter(adapter): void use(middleware): void isPassthrough(clientProtocol, providerProtocol): Boolean { return clientProtocol == providerProtocol && registry.get(clientProtocol).supportsPassthrough() } // 非流式请求转换 convertHttpRequest(request, clientProtocol, providerProtocol, provider): HttpRequest { nativePath = request.url if isPassthrough(clientProtocol, providerProtocol): providerAdapter = registry.get(providerProtocol) return {url: provider.base_url + nativePath, method: request.method, headers: providerAdapter.buildHeaders(provider), body: request.body} clientAdapter = registry.get(clientProtocol) providerAdapter = registry.get(providerProtocol) // 使用 clientAdapter 识别接口类型 interfaceType = clientAdapter.detectInterfaceType(nativePath) providerUrl = providerAdapter.buildUrl(nativePath, interfaceType) providerHeaders = providerAdapter.buildHeaders(provider) providerBody = convertBody(interfaceType, clientAdapter, providerAdapter, provider, request.body) return {url: provider.base_url + providerUrl, method: request.method, headers: providerHeaders, body: providerBody} } // 非流式响应转换 convertHttpResponse(response, clientProtocol, providerProtocol, interfaceType): HttpResponse { if isPassthrough(clientProtocol, providerProtocol): return response clientAdapter = registry.get(clientProtocol) providerAdapter = registry.get(providerProtocol) providerBody = convertResponseBody(interfaceType, clientAdapter, providerAdapter, response.body) return {status: response.status, headers: response.headers, body: providerBody} } // 流式转换:从 provider 协议解码,编码为 client 协议 createStreamConverter(clientProtocol, providerProtocol, provider): StreamConverter { if isPassthrough(clientProtocol, providerProtocol): providerAdapter = registry.get(providerProtocol) return new PassthroughStreamConverter(providerAdapter.buildHeaders(provider)) providerAdapter = registry.get(providerProtocol) clientAdapter = registry.get(clientProtocol) return new CanonicalStreamConverter( providerAdapter.createStreamDecoder(), clientAdapter.createStreamEncoder(), middlewareChain) } } ``` ### 6.2 Body 转换分发 ``` function convertBody(interfaceType, clientAdapter, providerAdapter, provider, body): switch interfaceType: case CHAT: canonical = clientAdapter.decodeRequest(body) canonical = middlewareChain.apply(canonical) return providerAdapter.encodeRequest(canonical, provider) case MODELS: return body // GET 请求,无 body case MODEL_INFO: return body // GET 请求,无 body case EMBEDDINGS: if !clientAdapter.supportsInterface(EMBEDDINGS) || !providerAdapter.supportsInterface(EMBEDDINGS): return body // 尽力转换:不支持则透传 return providerAdapter.encodeEmbeddingRequest( clientAdapter.decodeEmbeddingRequest(body), provider) case RERANK: // 同 EMBEDDINGS 模式 default: return body // 透传层:原样转发 function convertResponseBody(interfaceType, clientAdapter, providerAdapter, body): // 结构与 convertBody 对称,CHAT 走 Canonical 深度转换,扩展层走轻量映射,默认透传 // 各接口的具体响应转换逻辑详见各协议适配文档(附录 E) ``` ### 6.3 StreamConverter ``` interface StreamConverter { processChunk(rawChunk): Array flush(): Array } class PassthroughStreamConverter implements StreamConverter { headers: Map constructor(headers) { this.headers = headers } processChunk(rawChunk): Array { return [rawChunk] } flush(): Array { return [] } } class CanonicalStreamConverter implements StreamConverter { decoder: StreamDecoder encoder: StreamEncoder middleware: MiddlewareChain processChunk(rawChunk): events = decoder.processChunk(rawChunk).map(e => middleware.applyStreamEvent(e)) return events.flatMap(e => encoder.encodeEvent(e)) flush(): return decoder.flush().flatMap(e => encoder.encodeEvent(e)) + encoder.flush() } ``` ### 6.4 Middleware 引擎内部的拦截钩子,在 decode → encode 之间对 Canonical 进行变换。 ``` interface ConversionMiddleware { intercept(canonical, clientProtocol, providerProtocol, context): canonical | error interceptStreamEvent?(event, clientProtocol, providerProtocol, context): event | error } ConversionContext { conversionId, interfaceType, timestamp, metadata } ``` - `intercept` 返回修改后的 canonical,或返回 ConversionError 以**中断转换** - `interceptStreamEvent` 同理,返回错误可中断流式转换 - 多个 Middleware 按注册顺序链式执行,任一中断则后续不再执行 ### 6.5 使用示例 ``` engine = new ConversionEngine() engine.registerAdapter(new ProtocolAAdapter()) engine.registerAdapter(new ProtocolBAdapter()) // 场景1: 跨协议 Chat 转换 // 入站: /protocol_a/v1/chat/completions provider = TargetProvider { base_url: "https://api.protocol-b.com", api_key: "xxx", model_name: "model-b", adapter_config: { ... } } out = engine.convertHttpRequest(inRequest, "protocol_a", "protocol_b", provider) // 出站: 目标协议路径 + 目标协议 headers + 转换后的 body // 场景2: /models 跨协议 out = engine.convertHttpRequest(inRequest, "protocol_a", "protocol_b", provider) // URL: /v1/models(通常不变), headers 按目标协议格式重建 // 场景3: 同协议透传 out = engine.convertHttpRequest(inRequest, "protocol_a", "protocol_a", provider) // client == provider → 剥离前缀, 用 provider 重建 headers 后原样转发 // 场景4: 流式转换(从 provider 协议解码,编码为 client 协议) converter = engine.createStreamConverter("protocol_a", "protocol_b", provider) for chunk in upstreamSSE { for out in converter.processChunk(chunk) { sendToClient(out) } } converter.flush() ``` --- ## 7. 流式转换架构 ### 7.1 转换管道 ``` 上游 SSE 流 │ ├── 同协议: PassthroughStreamConverter(用 provider 重建 Headers 后逐块转发) │ └── 跨协议: CanonicalStreamConverter StreamDecoder StreamEncoder ┌───────────┐ ┌───────────┐ │ SSE Parser│ │SSE Writer │ └─────┬─────┘ └─────▲─────┘ │ │ ┌─────▼─────┐ CanonicalEvent[] ┌─────┴─────┐ │ Event │──────────────────────▶│ Event │ │ Translator │ ┌──────────┐ │ Translator │ │ (状态机) │ │Middleware│ │ │ └───────────┘ └──────────┘ └───────────┘ ``` ### 7.2 StreamDecoder 通用状态 StreamDecoder 需要跟踪以下通用状态。具体协议的 Decoder 可根据需要扩展: ``` StreamDecoderState { messageStarted: Boolean openBlocks: Set currentBlockType: Map currentBlockId: Map utf8Remainder: Option // UTF-8 跨 chunk 安全 accumulatedUsage: Option } ``` **协议特有状态**:某些协议的 SSE 格式需要额外状态(如工具调用参数累积、工具调用索引映射等),在各自的协议适配文档中定义。 ### 7.3 事件映射概述 StreamDecoder 将协议原生 SSE 事件翻译为 CanonicalStreamEvent,StreamEncoder 做反向翻译。映射复杂度取决于协议的 SSE 格式: - **类型化事件协议**(事件有明确的 start/delta/stop 生命周期):与 CanonicalStreamEvent 接近 1:1 映射,状态机最简单 - **Delta 模型协议**(只有增量 delta chunk,无显式生命周期):需要状态机推断 block 边界,管理工具调用索引和参数累积 - **缓冲策略**:部分协议的 Encoder 需要缓冲 ContentBlockStart 事件,等待首次 ContentBlockDelta 时合并输出 各协议的具体事件映射表详见各自的协议适配文档(附录 E)。 --- ## 8. 扩展点设计 ### 8.1 新协议接入 1. 实现 ProtocolAdapter(URL 映射 + Header 映射 + 各接口编解码) 2. 注册到 AdapterRegistry 3. 完成 按附录 D 的清单模板逐项确认,所有项目确认后即可与引擎对接。 ### 8.2 多模态扩展 Canonical Model 已预留 ImageBlock / AudioBlock / VideoBlock / FileBlock。实现路径: 1. 在各 ProtocolAdapter 中实现多模态块的编解码 2. 在 StreamDecoder/StreamEncoder 中处理多模态增量数据 ### 8.3 有状态特性扩展 ``` interface StatefulMiddleware extends ConversionMiddleware { stateStore: SessionStateStore } ``` 适用场景:跨轮次保留协议特有的状态(如 thinking signature)。 ### 8.4 特性降级策略 当 Canonical 公共字段在目标协议中无直接等价物时: - 有语义等价物 → 自动映射 - 无等价物 → 丢弃,日志 warn - 有替代方案 → 降级策略处理(如注入合成工具实现 JSON 模式) 各协议的具体降级规则详见各自的协议适配文档(附录 E)。 ### 8.5 自定义接口支持 ``` interface CustomInterfaceHandler { interfaceType(): InterfaceType matchUrl(url): Boolean convertRequest(client, provider, raw): raw convertResponse(client, provider, raw): raw } engine.registerCustomHandler(handler) ``` --- ## 9. 错误处理 ### 9.1 错误分类 ``` ConversionError { code: ErrorCode, message, clientProtocol?, providerProtocol?, interfaceType?, details?, cause? } ErrorCode = Enum< INVALID_INPUT, // 请求格式不符合协议规范 MISSING_REQUIRED_FIELD, // 缺少必填字段 INCOMPATIBLE_FEATURE, // 特性在目标协议不可用 FIELD_MAPPING_FAILURE, // 字段映射逻辑错误 TOOL_CALL_PARSE_ERROR, // 工具调用参数解析失败 JSON_PARSE_ERROR, // JSON 解析失败 STREAM_STATE_ERROR, // 流式状态机异常 UTF8_DECODE_ERROR, // UTF-8 解码错误 PROTOCOL_CONSTRAINT_VIOLATION, // 违反协议约束 ENCODING_FAILURE, // 编码失败 INTERFACE_NOT_SUPPORTED // 目标协议不支持此接口 > ``` ### 9.2 错误处理策略 ``` ErrorHandler { mode: "strict" | "lenient" } strict: 任何错误抛出异常 lenient: 尽力继续 INCOMPATIBLE_FEATURE → 降级继续 INTERFACE_NOT_SUPPORTED → 透传或返回空响应 TOOL_CALL_PARSE_ERROR → 保留原始内容继续 PROTOCOL_CONSTRAINT_VIOLATION → 自动修复 ``` **不支持接口的处理**(`INTERFACE_NOT_SUPPORTED`): | 策略 | 适用场景 | 实现 | |------|---------|------| | 透传 | 上游可能有自己的实现 | URL+Header 适配后 Body 原样转发 | | 返回空响应 | 不影响核心功能 | 返回空列表 `{data: []}` | | 返回错误 | 客户端明确需要此功能 | 返回 501 或协议格式错误 | 具体策略通过配置或 Middleware 决定。 ### 9.3 错误响应格式 转换失败时,错误响应用**客户端协议(client protocol)**的格式编码。由 `clientAdapter.encodeError(error)` 完成。各协议的错误响应 JSON 结构和 HTTP 状态码映射详见各自的协议适配文档(附录 E)。 Middleware 中断转换时同理,引擎调用 clientAdapter.encodeError 将 ConversionError 编码为客户端可理解的格式。 --- ## 附录 A:模块依赖 ``` ┌──────────────────────────────────────────────────┐ │ ConversionEngine │ │ 门面:HTTP 转换 / 透传判断 / 流式转换 │ │ 无状态;协议识别见 §2.2 │ ├──────────────────────────────────────────────────┤ │ TargetProvider │ │ base_url / api_key / model_name / adapter_config │ ├──────────────────┬───────────────────────────────┤ │ AdapterRegistry │ MiddlewareChain │ ├──────────────────┴───────────────────────────────┤ │ StreamConverter: Passthrough | Canonical │ ├──────────────────────────────────────────────────┤ │ ProtocolAdapter: 各协议实现 │ │ · buildHeaders(provider) · URL 映射 │ │ · Chat/Models/ModelInfo/Embeddings/Rerank/... 编解码 │ │ · encodeError · StreamDecoder / StreamEncoder │ ├──────────────────────────────────────────────────┤ │ Canonical Model (Core + Extended) │ ├──────────────────────────────────────────────────┤ │ Error Handling │ ├──────────────────────────────────────────────────┤ │ Utility: UTF-8 Buffer / SSE Parser / Detector │ └──────────────────────────────────────────────────┘ ``` --- ## 附录 B:接口速查 ``` // ─── 核心入口 ─── ConversionEngine .registerAdapter(adapter) .use(middleware) .isPassthrough(clientProtocol, providerProtocol): Boolean .convertHttpRequest(request, clientProtocol, providerProtocol, provider): HttpRequest .convertHttpResponse(response, clientProtocol, providerProtocol, interfaceType): HttpResponse .createStreamConverter(clientProtocol, providerProtocol, provider): StreamConverter // ─── 目标上游信息 ─── TargetProvider .base_url: String .api_key: String .model_name: String .adapter_config: Map // ─── URL 路由 ─── // 协议识别见 §2.2;出站: provider.base_url + 目标协议原生路径 // ─── 协议适配器 ─── ProtocolAdapter .protocolName() / .protocolVersion() / .supportsPassthrough() .detectInterfaceType(nativePath) / .buildUrl(nativePath, type) / .buildHeaders(provider) / .supportsInterface(type) .decodeRequest(raw) / .encodeRequest(canonical, provider) .decodeResponse(raw) / .encodeResponse(canonical) .createStreamDecoder() / .createStreamEncoder() .encodeError(error): RawResponse .decodeModelsResponse / .encodeModelsResponse .decodeModelInfoResponse / .encodeModelInfoResponse .decodeEmbeddingRequest / .encodeEmbeddingRequest(canonical, provider) / ...Response .decodeRerankRequest / .encodeRerankRequest(canonical, provider) / ...Response // ─── 流式处理 ─── StreamConverter: .processChunk(raw) / .flush() ├─ PassthroughStreamConverter [raw] → [raw](用 provider 重建 Headers) └─ CanonicalStreamConverter decode → middleware → encode // ─── 接口类型 ─── InterfaceType = CHAT | MODELS | MODEL_INFO | EMBEDDINGS | RERANK | AUDIO | IMAGES ``` --- ## 附录 C:字段晋升规范 ### 原则 Canonical Model 是**活的公共契约**,不是固定不变的。其字段集反映的是**当前已适配协议的公共语义**,随协议扩展而演进。 ### 字段分类 | 分类 | 判定条件 | 处理方式 | |------|---------|---------| | **公共字段** | ≥2 个协议表达相同含义 | 升级为 Canonical Model 的正式字段 | | **协议特有字段** | 仅 1 个协议使用 | 不在中间层传播;同协议透传时自然保留;跨协议时丢弃 | | **未知字段** | 新协议带来的新概念 | 先按特有字段处理;当第二个协议也出现时晋升 | ### 晋升流程 ``` 1. 发现:适配新协议时,识别出无法映射到现有 Canonical 字段的语义 2. 判定:该语义是否已被 ≥1 个现有协议所表达? - 是 → 晋升为 Canonical 公共字段 - 否 → 暂不纳入,记录在适配清单中 3. 设计:确定字段名(协议中立)、类型、在 Canonical 中的位置 4. 更新:修改 Canonical Model 定义,所有现有 Adapter 的 Decoder/Encoder 同步更新 5. 文档:更新本节说明 ``` ### 晋升示例 | 字段 | 晋升原因 | |------|---------| | `user_id` | 多协议均支持用户标识,但字段名和嵌套位置不同 | | `output_format` | 多协议均支持控制输出格式,但字段名不同 | | `parallel_tool_use` | 多协议均支持并行工具调用控制,但语义方向(允许/禁止)不同 | | `top_k` | 多协议(Gemini、OpenAI 兼容厂商、Ollama 等)均支持,但字段名和嵌套位置不同 | | `frequency_penalty` / `presence_penalty` | OpenAI 生态广泛使用,多协议均支持类似概念 | | `stop_reason: refusal` | OpenAI 和 Gemini 均有安全拒绝/内容过滤场景,语义一致 | ### 降级规则 当公共字段的目标协议不支持时: - 有语义等价物 → 自动映射 - 无等价物 → 丢弃,日志 warn - 有替代方案 → 降级策略处理 --- ## 附录 D:协议适配清单模板 适配新协议时,按以下清单逐项确认。所有项目确认后即可与引擎对接。 **方法调用时机**:理解各方法何时被引擎调用,有助于正确实现: - `decodeRequest` / `encodeRequest`:引擎在 `convertBody(CHAT)` 中调用(§6.2),对每个 Chat 请求执行 decode → middleware → encode 管道 - `decodeResponse` / `encodeResponse`:引擎在 `convertResponseBody(CHAT)` 中调用,对每个 Chat 响应执行反向管道 - `decodeXxxRequest` / `encodeXxxRequest`:扩展层接口仅在 `supportsInterface` 返回 true 时被调用(§6.2 convertBody 分支);返回 false 时引擎直接透传 body - `createStreamDecoder` / `createStreamEncoder`:引擎在 `createStreamConverter` 中调用(§6.1),Decoder 来自 provider 协议(解码上游 SSE),Encoder 来自 client 协议(编码给客户端) - `buildHeaders`:每次请求出站时调用,同协议透传也会调用 - `encodeError`:转换失败或 Middleware 中断时调用,使用 client 协议格式编码错误响应 ### D.1 协议基本信息 | 项目 | 说明 | |------|------| | 协议名称 | 用于 URL 前缀和 Adapter 注册的唯一标识 | | 协议版本 | 当前适配的 API 版本 | | Base URL | API 服务地址 | | 认证方式 | Header 名称和格式 | ### D.2 接口识别 | 项目 | 说明 | |------|------| | URL 路径模式 | 列出所有接口的 URL 路径和对应的 InterfaceType,由 `detectInterfaceType` 实现 | | 接口能力矩阵 | 每种 InterfaceType 的支持状态(`supportsInterface`) | | URL 映射表 | 每种 InterfaceType 的目标 URL 路径(`buildUrl`) | **重要**:`detectInterfaceType` 由各协议 Adapter 实现,因为不同协议有不同的 URL 路径约定。例如: - OpenAI: `/v1/chat/completions` → CHAT - Anthropic: `/v1/messages` → CHAT ### D.3 请求头构建 | 项目 | 说明 | |------|------| | 认证头 | 如何从 `provider.api_key` 构建认证 Header | | 必需 Header | 协议要求的固定 Header(如 Content-Type) | | 可选 Header | 根据功能动态添加的 Header | | adapter_config 契约 | 定义本 Adapter 从 `provider.adapter_config` 读取的 key 列表和默认值 | ### D.4 核心层 — Chat 请求编解码 逐字段对照 §4.1 CanonicalRequest 和 §4.2 RequestParameters 确认映射关系。 #### Decoder(协议 → Canonical) | 项目 | 说明 | |------|------| | 系统消息 | 如何提取为 `canonical.system`(顶层字段 / messages 中 / 不支持) | | 消息角色 | 协议角色与 Canonical 角色(§4.3:system/user/assistant/tool)的映射 | | 内容块 | 逐类型对照 §4.3 ContentBlock 联合体,确认每种类型的解码规则 | | 工具定义 | 对照 §4.4 CanonicalTool,确认字段名映射 | | 工具选择 | 对照 §4.4 ToolChoice 联合体,确认各变体的映射规则 | | 参数映射 | 对照 §4.2 RequestParameters,逐字段确认映射和类型转换 | | 公共字段 | 逐字段对照 §4.1 中 `user_id`、`output_format`、`parallel_tool_use`、`thinking`,确认提取规则 | | 协议特有字段 | 仅本协议使用的字段列表,以及处理方式(忽略/记录) | | 协议约束 | 消息顺序要求、角色交替要求、必填字段等 | #### Encoder(Canonical → 协议) | 项目 | 说明 | |------|------| | 模型名称 | 使用 `provider.model_name` 覆盖 `canonical.model` | | 系统消息注入 | 如何将 `canonical.system` 编码为协议格式 | | 消息编码 | 各角色的编码规则(特别注意 role 映射、content 结构差异) | | 角色约束处理 | 是否需要 enforceAlternation?具体策略? | | 工具编码 | 对照 §4.4 确认 tools/tool_choice 的编码规则 | | 参数编码 | 对照 §4.2 确认参数映射 | | 公共字段编码 | 逐字段确认注入规则 | | 降级处理 | 对照 §8.4 三级降级策略(自动映射/丢弃/替代方案),确认每个不支持字段的处理方式 | ### D.5 核心层 — Chat 响应编解码 逐字段对照 §4.7 CanonicalResponse 确认映射关系。 #### Decoder(协议 → Canonical) | 项目 | 说明 | |------|------| | 响应结构 | 协议响应的顶层结构解析 | | 内容块解码 | 逐类型对照 §4.3 ContentBlock 联合体,确认解码规则 | | 停止原因 | 协议原生值与 §4.7 StopReason 枚举的映射表 | | Token 用量 | 对照 §4.7 CanonicalUsage,逐字段确认映射和命名差异 | | 推理内容 | ThinkingBlock 的解码规则 | | 协议特有内容 | 仅本协议返回的特有字段的处理(忽略/丢弃/记录) | #### Encoder(Canonical → 协议) | 项目 | 说明 | |------|------| | 响应结构 | 如何从 CanonicalResponse 构建协议响应的顶层结构 | | 内容块编码 | 各 ContentBlock 类型到协议格式的编码规则 | | 停止原因 | §4.7 StopReason 到协议原生值的映射表 | | Token 用量 | CanonicalUsage 到协议 usage 字段的编码规则 | | 推理内容 | ThinkingBlock 的编码规则 | | 降级处理 | Canonical 中协议不支持的 stop_reason 等值的降级映射 | ### D.6 核心层 — 流式编解码 对照 §4.8 CanonicalStreamEvent 和 §7.2 StreamDecoderState 确认映射和状态设计。 #### StreamDecoder(协议 SSE → Canonical 事件) | 项目 | 说明 | |------|------| | SSE 格式 | 协议的 SSE 事件格式(named events vs delta chunks) | | 事件映射表 | 每种协议 SSE 事件 → §4.8 CanonicalStreamEvent 的映射规则 | | 状态机设计 | 在 §7.2 通用状态基础上,声明本协议需要的额外状态 | | UTF-8 安全 | 是否需要处理跨 chunk 的 UTF-8 截断 | | 特殊情况 | 工具调用参数乱序、无限空白检测、延迟字段等 | #### StreamEncoder(Canonical 事件 → 协议 SSE) | 项目 | 说明 | |------|------| | 事件映射表 | §4.8 每个 CanonicalStreamEvent → 协议 SSE chunk 的映射规则 | | 缓冲策略 | 哪些事件需要缓冲、何时输出 | | SSE 格式 | event type / data 字段的编码方式 | | 结束标记 | 如何输出流结束信号(如 `[DONE]`) | ### D.7 扩展层接口 对每种扩展层接口逐项确认。当前扩展层 InterfaceType 见 §5.3 枚举(MODELS、MODEL_INFO、EMBEDDINGS、RERANK),随协议扩展可能增加。 | 项目 | 说明 | |------|------| | 接口是否存在 | 该协议是否原生支持此接口 | | URL 路径 | 接口的 URL 路径 | | 请求格式 | 对照 §4.9 扩展层 Canonical Models,确认请求体映射 | | 响应格式 | 对照 §4.9 扩展层 Canonical Models,确认响应体映射 | | 不支持时策略 | 透传 / 返回空响应 / 返回错误 | ### D.8 错误编码 | 项目 | 说明 | |------|------| | 错误响应格式 | 协议的错误响应 JSON 结构 | | encodeError | §9.1 ConversionError → 协议错误格式的编码规则 | | HTTP 状态码 | 协议常用的错误状态码映射 | ### D.9 自检清单 | 章节 | 检查项 | |------|--------| | D.2 | [ ] `detectInterfaceType(nativePath)` 已实现,所有已知路径已覆盖 | | D.2 | [ ] 所有 InterfaceType 的 `supportsInterface` 返回值已确定 | | D.2 | [ ] 所有 InterfaceType 的 `buildUrl` 映射已确定 | | D.3 | [ ] `buildHeaders(provider)` 已实现,adapter_config 契约已文档化 | | D.4 | [ ] Chat 请求的 Decoder 和 Encoder 已实现(逐字段对照 §4.1/§4.2) | | D.4 | [ ] 角色映射和消息顺序约束已处理 | | D.4 | [ ] 工具调用(tool_use / tool_result)的编解码已处理 | | D.4 | [ ] 协议特有字段已识别并确定处理方式(忽略/降级) | | D.5 | [ ] Chat 响应的 Decoder 和 Encoder 已实现(逐字段对照 §4.7) | | D.5 | [ ] stop_reason 映射表已确认 | | D.5 | [ ] usage 字段映射已确认 | | D.6 | [ ] 流式 StreamDecoder 和 StreamEncoder 已实现(对照 §4.8) | | D.7 | [ ] 扩展层接口的编解码已实现(支持的接口) | | D.8 | [ ] `encodeError` 已实现 | --- ## 附录 E:协议适配文档索引 | 协议 | 适配文档 | |------|---------| | OpenAI | [conversion_openai.md](./conversion_openai.md) | | Anthropic | [conversion_anthropic.md](./conversion_anthropic.md) | 新增协议时,按附录 D 模板编撰适配文档,并将链接添加到上表。