From 7fa5af483b6b2d567f70a5830b44a4d8fafad9bd Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Wed, 22 Apr 2026 10:54:30 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=20conversion=20?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3=E4=BB=A5=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要更新内容: - 新增三车道数据流模型(透传/智能透传/完整转换) - 补充 ProtocolAdapter 智能透传相关 4 个方法 - 更新 InterfaceType 枚举(移除 AUDIO/IMAGES,增加 PASSTHROUGH) - 新增 HTTPRequestSpec/HTTPResponseSpec 类型定义 - 更新引擎方法签名(增加 modelOverride 和 interfaceType 参数) - 明确接口类型分发策略和中间件应用范围 - 新增三种流式转换器变体 - 重写错误处理策略为分层宽容策略 - 标记多模态和扩展点为 Deferred - 更新附录 B 接口速查和附录 D 协议适配清单 --- docs/conversion_design.md | 592 ++++++++++++++++++++++++++++++-------- 1 file changed, 471 insertions(+), 121 deletions(-) diff --git a/docs/conversion_design.md b/docs/conversion_design.md index 8e66e11..3f72ff1 100644 --- a/docs/conversion_design.md +++ b/docs/conversion_design.md @@ -126,24 +126,54 @@ ### 2.3 请求处理流程 -每个 HTTP 请求的转换流程: +#### 2.3.1 三车道数据流模型 + +引擎根据协议匹配情况和参数条件,选择三条数据流通道之一: ``` -客户端入站 调用方(协议识别+前缀剥离) 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: │ -│ 协议原生格式 │ │ 目标协议格式 │ -└──────────────────┘ └──────────────────┘ +┌──────────────────────────────────────────────────────────────────────────────────┐ +│ 数据流三车道模型 │ +├────────────────┬─────────────────────────┬────────────────────────────────────────┤ +│ 透传车道 │ 智能透传车道 │ 完整转换车道 │ +├────────────────┼─────────────────────────┼────────────────────────────────────────┤ +│ 触发条件 │ 同协议 │ 同协议 + 接口∈{Chat,Embed,Rerank} │ 不同协议 │ +│ │ │ + Body非空 + ModelName非空 │ │ +│ │ │ │ │ +│ 请求处理 │ 重建Headers │ 重建Headers │ Decode→Middleware→Encode│ +│ │ Body原样转发 │ RewriteRequestModelName(body) │ │ +│ │ │ (最小化JSON字段手术) │ │ +│ │ │ │ │ +│ 响应处理 │ 原样返回 │ modelOverride非空时 │ Decode→modelOverride │ +│ │ │ RewriteResponseModelName(body) │ →Encode │ +│ │ │ │ │ +│ 流式处理 │ chunk→[chunk] │ chunk→RewriteResponseModelName │ Decode→Middleware │ +│ │ │ →[rewritten] │ →modelOverride→Encode │ +│ │ │ │ │ +│ 性能开销 │ 最低 │ 低(仅JSON字段改写) │ 高(完整序列化) │ +└────────────────┴─────────────────────────┴────────────────────────────────────────┘ ``` -响应方向同理(含流式)。D=Decoder, C=Canonical, E=Encoder。 +**智能透传的设计动机**:同协议场景下,若仅需改写 `model` 字段(如客户端请求模型 "X",上游需要模型 "Y"),无需完整解码/编码。直接在 JSON 层面手术式改写该字段,既保留原始请求的所有细节,又避免序列化开销。 + +#### 2.3.2 完整请求处理流程 + +``` +客户端入站 调用方职责 SDK 内部处理 上游出站 +┌──────────────┐ ┌──────────────┐ +│ URL: │ 调用方完成: 1. 接口识别 │ URL: │ +│ // │ · clientProtocol 2. IsPassthrough? │ 目标协议 │ +│ v1/... │ · nativePath ├─ yes ─┬─ 无 modelOverride → 透传车道 │ 原生路径 │ +│ Headers: │ · providerProtocol │ └─ 有 modelOverride → 智能透传车道│ Headers: │ +│ 协议原生格式 │ │ │ 目标协议格式 │ +│ Body: │ └─ no → 完整转换车道 │ Body: │ +│ 协议原生格式 │ │ 目标协议格式 │ +└──────────────┘ └──────────────┘ +``` + +响应方向同理(含流式)。 **同协议透传**:client == provider 时,仅重建 Header 后原样转发到上游。 +**智能透传**:同协议且需改写 model 字段时,最小化 JSON 改写后转发。 **未知接口透传**:无法识别的路径,URL+Header 适配后 Body 原样转发。 --- @@ -229,13 +259,15 @@ ContentBlock = Union< 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: ... } // 多模态预留 + ImageBlock, { type: "image", source: ... } // Deferred:多模态预留 + AudioBlock, { type: "audio", source: ... } // Deferred:多模态预留 + VideoBlock, { type: "video", source: ... } // Deferred:多模态预留 + FileBlock { type: "file", source: ... } // Deferred:多模态预留 > ``` +**当前实现状态**:仅实现了 TextBlock、ToolUseBlock、ToolResultBlock、ThinkingBlock。多模态类型(ImageBlock、AudioBlock、VideoBlock、FileBlock)为预留扩展点。 + ### 4.4 CanonicalTool / ToolChoice ``` @@ -400,7 +432,7 @@ interface ProtocolAdapter { createStreamEncoder(): StreamEncoder // 错误编码 - encodeError(error: ConversionError): RawResponse + encodeError(error: ConversionError): (body, statusCode) // 扩展层 decodeModelsResponse(raw): CanonicalModelList @@ -415,20 +447,40 @@ interface ProtocolAdapter { encodeRerankRequest(canonical, provider): RawRequest decodeRerankResponse(raw): CanonicalRerankResponse encodeRerankResponse(canonical): RawResponse + + // 智能透传支持 + extractUnifiedModelID(nativePath: String): (modelID, error) // 从路径提取统一模型 ID + extractModelName(body: Raw, interfaceType: InterfaceType): (modelName, error) // 从请求体提取 model 字段值 + rewriteRequestModelName(body: Raw, newModel: String, interfaceType: InterfaceType): RawRequest // 最小化改写请求体中的 model 字段 + rewriteResponseModelName(body: Raw, newModel: String, interfaceType: InterfaceType): RawResponse // 最小化改写响应体中的 model 字段 } ``` **`buildHeaders` 的设计**:Adapter 只需从 `provider` 中提取自己协议需要的认证和配置信息,构建自己的 Header 格式。不再需要理解其他协议的 Header。 +**智能透传方法的契约**: +- `rewriteRequestModelName` / `rewriteResponseModelName` 必须**幂等**(多次调用结果相同) +- Rewrite 方法必须**最小化**(仅修改 model 字段,不触碰其他字段) +- Rewrite 失败时,引擎使用宽容策略:记录警告日志,使用原始 body 继续处理 +- `extractModelName` 支持的接口类型:CHAT、EMBEDDINGS、RERANK(这些接口的请求体包含 model 字段) + ### 5.3 InterfaceType ``` InterfaceType = Enum< - CHAT, MODELS, MODEL_INFO, EMBEDDINGS, RERANK, - AUDIO, IMAGES // 预留:多模态扩展时启用 + CHAT, MODELS, MODEL_INFO, EMBEDDINGS, RERANK, PASSTHROUGH > ``` +| 类型 | 说明 | +|------|------| +| CHAT | 核心对话接口(各协议的 Chat/Messages 接口) | +| MODELS | 模型列表接口 | +| MODEL_INFO | 模型详情接口 | +| EMBEDDINGS | 向量嵌入接口 | +| RERANK | 重排序接口 | +| PASSTHROUGH | 未知接口,透传处理 | + ### 5.4 StreamDecoder / StreamEncoder ``` @@ -463,68 +515,138 @@ ConversionEngine 是无状态的格式转换工具,仅做协议间的编解码 **协议识别**:`clientProtocol` 和 `providerProtocol` 由调用方确定并传入引擎(详见 §2.2)。 +#### 6.1.1 HTTP 规格 + +``` +HTTPRequestSpec { + url: String // 请求路径(不含 base_url) + method: String // HTTP 方法 + headers: Map + body: ByteArray // 原始请求体 +} + +HTTPResponseSpec { + statusCode: Integer + headers: Map + body: ByteArray // 原始响应体 +} +``` + +#### 6.1.2 引擎接口 + ``` class ConversionEngine { registry: AdapterRegistry middlewareChain: MiddlewareChain + // 生命周期 registerAdapter(adapter): void use(middleware): void + getRegistry(): AdapterRegistry + // 核心转换 isPassthrough(clientProtocol, providerProtocol): Boolean { return clientProtocol == providerProtocol && registry.get(clientProtocol).supportsPassthrough() } - // 非流式请求转换 - convertHttpRequest(request, clientProtocol, providerProtocol, provider): HttpRequest { - nativePath = request.url + convertHttpRequest(request, clientProtocol, providerProtocol, provider): HTTPRequestSpec + convertHttpResponse(response, clientProtocol, providerProtocol, interfaceType, modelOverride): HTTPResponseSpec + createStreamConverter(clientProtocol, providerProtocol, modelOverride, interfaceType): StreamConverter - 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) - } + // 辅助方法 + detectInterfaceType(nativePath, clientProtocol): InterfaceType + encodeError(error, clientProtocol): (body, statusCode) } ``` +#### 6.1.3 请求转换流程 + +``` +function convertHttpRequest(request, clientProtocol, providerProtocol, provider): + nativePath = request.url + + if isPassthrough(clientProtocol, providerProtocol): + providerAdapter = registry.get(providerProtocol) + interfaceType = providerAdapter.detectInterfaceType(nativePath) + + // 智能透传:对 Chat/Embeddings/Rerank 接口改写 model 字段 + if interfaceType in {CHAT, EMBEDDINGS, RERANK} and request.body非空 and provider.modelName非空: + rewrittenBody = providerAdapter.rewriteRequestModelName(request.body, provider.modelName, interfaceType) + if rewrite失败: + log.warn("智能透传改写失败,使用原始请求体") + rewrittenBody = request.body + else: + rewrittenBody = request.body + + return HTTPRequestSpec { + url: provider.base_url + nativePath, + method: request.method, + headers: providerAdapter.buildHeaders(provider), + body: rewrittenBody + } + + // 完整转换车道 + clientAdapter = registry.get(clientProtocol) + providerAdapter = registry.get(providerProtocol) + interfaceType = clientAdapter.detectInterfaceType(nativePath) + + providerUrl = providerAdapter.buildUrl(nativePath, interfaceType) + providerHeaders = providerAdapter.buildHeaders(provider) + providerBody = convertBody(interfaceType, clientAdapter, providerAdapter, provider, request.body) + + return HTTPRequestSpec { + url: provider.base_url + providerUrl, + method: request.method, + headers: providerHeaders, + body: providerBody + } +``` + +#### 6.1.4 响应转换流程 + +``` +function convertHttpResponse(response, clientProtocol, providerProtocol, interfaceType, modelOverride): + if isPassthrough(clientProtocol, providerProtocol): + // 智能透传:改写响应体中的 model 字段 + if modelOverride非空 and response.body非空: + adapter = registry.get(clientProtocol) + rewrittenBody = adapter.rewriteResponseModelName(response.body, modelOverride, interfaceType) + if rewrite失败: + log.warn("智能透传改写失败,使用原始响应体") + return response + return HTTPResponseSpec { statusCode: response.statusCode, headers: response.headers, body: rewrittenBody } + return response + + // 完整转换车道 + clientAdapter = registry.get(clientProtocol) + providerAdapter = registry.get(providerProtocol) + convertedBody = convertResponseBody(interfaceType, clientAdapter, providerAdapter, response.body, modelOverride) + + return HTTPResponseSpec { statusCode: response.statusCode, headers: response.headers, body: convertedBody } +``` + +**modelOverride 参数语义**:跨协议场景下,客户端期望看到的模型名。在响应转换时直接覆写 `canonicalResponse.model = modelOverride`,在流式转换时覆写 `event.message.model = modelOverride`。 + ### 6.2 Body 转换分发 +#### 6.2.1 接口类型分发策略 + +| 接口类型 | 请求转换 | 响应转换 | 中间件 | modelOverride | +|---------|---------|---------|--------|---------------| +| CHAT | Decode→**Middleware**→Encode | Decode→modelOverride→Encode | **应用** | 支持(响应) | +| MODELS | Body透传(GET) | Decode→Encode | 不应用 | 不支持 | +| MODEL_INFO | Body透传(GET) | Decode→Encode | 不应用 | 不支持 | +| EMBEDDINGS | Decode→Encode | Decode→modelOverride→Encode | 不应用 | 支持(响应) | +| RERANK | Decode→Encode | Decode→modelOverride→Encode | 不应用 | 支持(响应) | +| PASSTHROUGH | Body透传 | Body透传 | 不应用 | 不支持 | + +**关键说明**: +- **只有 CHAT 接口**走完整的 `decode → middleware → encode` 管道 +- **扩展层接口**(EMBEDDINGS、RERANK)**跳过中间件**,直接 decode → encode +- **扩展层响应支持 modelOverride**,在 encode 前直接覆写 canonical 字段 + +#### 6.2.2 请求体转换 + ``` function convertBody(interfaceType, clientAdapter, providerAdapter, provider, body): switch interfaceType: @@ -546,41 +668,146 @@ function convertBody(interfaceType, clientAdapter, providerAdapter, provider, bo // 同 EMBEDDINGS 模式 default: return body // 透传层:原样转发 +``` -function convertResponseBody(interfaceType, clientAdapter, providerAdapter, body): - // 结构与 convertBody 对称,CHAT 走 Canonical 深度转换,扩展层走轻量映射,默认透传 - // 各接口的具体响应转换逻辑详见各协议适配文档(附录 E) +#### 6.2.3 响应体转换 + +``` +function convertResponseBody(interfaceType, clientAdapter, providerAdapter, body, modelOverride): + switch interfaceType: + case CHAT: + canonical = providerAdapter.decodeResponse(body) + if modelOverride非空: canonical.model = modelOverride + return clientAdapter.encodeResponse(canonical) + case MODELS: + if !clientAdapter.supportsInterface(MODELS) || !providerAdapter.supportsInterface(MODELS): + return body + return clientAdapter.encodeModelsResponse(providerAdapter.decodeModelsResponse(body)) + case MODEL_INFO: + // 同 MODELS 模式 + case EMBEDDINGS: + if !clientAdapter.supportsInterface(EMBEDDINGS) || !providerAdapter.supportsInterface(EMBEDDINGS): + return body + canonical = providerAdapter.decodeEmbeddingResponse(body) + if modelOverride非空: canonical.model = modelOverride + return clientAdapter.encodeEmbeddingResponse(canonical) + case RERANK: + // 同 EMBEDDINGS 模式,支持 modelOverride + default: + return body // 透传层:原样转发 ``` ### 6.3 StreamConverter +#### 6.3.1 接口定义 + ``` interface StreamConverter { processChunk(rawChunk): Array flush(): Array } +``` +#### 6.3.2 三种转换器变体 + +| 转换器 | 触发条件 | processChunk | flush | +|--------|---------|--------------|-------| +| `PassthroughStreamConverter` | 同协议 + 无 modelOverride | `[rawChunk]` | `[]` | +| `SmartPassthroughStreamConverter` | 同协议 + 有 modelOverride | `[rewriteResponseModelName(rawChunk)]` | `[]` | +| `CanonicalStreamConverter` | 不同协议 | Decode→Middleware→modelOverride→Encode | decoder.flush()→encoder.flush() | + +#### 6.3.3 PassthroughStreamConverter + +``` class PassthroughStreamConverter implements StreamConverter { - headers: Map - constructor(headers) { this.headers = headers } processChunk(rawChunk): Array { return [rawChunk] } flush(): Array { return [] } } +``` +#### 6.3.4 SmartPassthroughStreamConverter + +``` +class SmartPassthroughStreamConverter implements StreamConverter { + adapter: ProtocolAdapter + modelOverride: String + interfaceType: InterfaceType + + processChunk(rawChunk): Array { + if rawChunk为空: return [] + rewrittenChunk = adapter.rewriteResponseModelName(rawChunk, modelOverride, interfaceType) + if rewrite失败: + log.warn("智能透传改写失败,使用原始 chunk") + return [rawChunk] + return [rewrittenChunk] + } + flush(): Array { return [] } +} +``` + +#### 6.3.5 CanonicalStreamConverter + +``` class CanonicalStreamConverter implements StreamConverter { decoder: StreamDecoder encoder: StreamEncoder middleware: MiddlewareChain + context: ConversionContext + clientProtocol: String + providerProtocol: String + modelOverride: String processChunk(rawChunk): - events = decoder.processChunk(rawChunk).map(e => middleware.applyStreamEvent(e)) - return events.flatMap(e => encoder.encodeEvent(e)) + events = decoder.processChunk(rawChunk) + result = [] + for each event in events: + // 中间件:转换 canonical 事件 + if middleware != null: + processed, err = middleware.applyStreamEvent(event, clientProtocol, providerProtocol, context) + if err != null: + continue // 宽容策略:跳过错误事件,继续处理 + event = processed + + // modelOverride:覆写 model 字段 + if modelOverride非空 and event.message != null: + event.message.model = modelOverride + + // 编码为目标协议 SSE + chunks = encoder.encodeEvent(event) + result.append(chunks) + return result flush(): - return decoder.flush().flatMap(e => encoder.encodeEvent(e)) + encoder.flush() + events = decoder.flush() + // 同 processChunk 的中间件 + modelOverride + encode 管道 + result = [经过管道处理的所有事件] + encoderChunks = encoder.flush() + result.append(encoderChunks) + return result } ``` +#### 6.3.6 流式转换器创建 + +``` +function createStreamConverter(clientProtocol, providerProtocol, modelOverride, interfaceType): + if isPassthrough(clientProtocol, providerProtocol): + if modelOverride非空: + adapter = registry.get(clientProtocol) + return new SmartPassthroughStreamConverter(adapter, modelOverride, interfaceType) + return new PassthroughStreamConverter() + + providerAdapter = registry.get(providerProtocol) + clientAdapter = registry.get(clientProtocol) + + return new CanonicalStreamConverter( + decoder: providerAdapter.createStreamDecoder(), + encoder: clientAdapter.createStreamEncoder(), + middleware: middlewareChain, + modelOverride: modelOverride + ) +``` + ### 6.4 Middleware 引擎内部的拦截钩子,在 decode → encode 之间对 Canonical 进行变换。 @@ -588,48 +815,73 @@ class CanonicalStreamConverter implements StreamConverter { ``` interface ConversionMiddleware { intercept(canonical, clientProtocol, providerProtocol, context): canonical | error - interceptStreamEvent?(event, clientProtocol, providerProtocol, context): event | error + interceptStreamEvent(event, clientProtocol, providerProtocol, context): event | error } -ConversionContext { conversionId, interfaceType, timestamp, metadata } +ConversionContext { + conversionId: String // 唯一转换 ID(UUID) + interfaceType: InterfaceType + timestamp: DateTime + metadata: Map +} ``` +#### 6.4.1 中间件执行规则 + - `intercept` 返回修改后的 canonical,或返回 ConversionError 以**中断转换** -- `interceptStreamEvent` 同理,返回错误可中断流式转换 +- `interceptStreamEvent` 返回修改后的 event,或返回 error - 多个 Middleware 按注册顺序链式执行,任一中断则后续不再执行 +#### 6.4.2 错误处理差异 + +| 场景 | 错误处理策略 | +|------|-------------| +| 请求中间件 `intercept` 返回 error | **严格模式**:中断整个转换,返回错误 | +| 流式中间件 `interceptStreamEvent` 返回 error | **宽容模式**:跳过该事件,继续处理后续事件 | + +**设计动机**:流式场景下,单个事件的错误不应中断整个流。请求场景下,错误请求应被明确拒绝。 + ### 6.5 使用示例 ``` engine = new ConversionEngine() -engine.registerAdapter(new ProtocolAAdapter()) -engine.registerAdapter(new ProtocolBAdapter()) +engine.registerAdapter(new OpenAIAdapter()) +engine.registerAdapter(new AnthropicAdapter()) // 场景1: 跨协议 Chat 转换 -// 入站: /protocol_a/v1/chat/completions +// 入站: /openai/v1/chat/completions provider = TargetProvider { - base_url: "https://api.protocol-b.com", + base_url: "https://api.anthropic.com", api_key: "xxx", - model_name: "model-b", - adapter_config: { ... } + model_name: "claude-3-opus", + adapter_config: { anthropic_version: "2023-06-01" } } -out = engine.convertHttpRequest(inRequest, "protocol_a", "protocol_b", provider) -// 出站: 目标协议路径 + 目标协议 headers + 转换后的 body +out = engine.convertHttpRequest(inRequest, "openai", "anthropic", provider) +// 出站: /v1/messages + Anthropic headers + 转换后的 body // 场景2: /models 跨协议 -out = engine.convertHttpRequest(inRequest, "protocol_a", "protocol_b", provider) -// URL: /v1/models(通常不变), headers 按目标协议格式重建 +out = engine.convertHttpRequest(inRequest, "openai", "anthropic", provider) +// URL: /v1/models, headers 按 Anthropic 格式重建 // 场景3: 同协议透传 -out = engine.convertHttpRequest(inRequest, "protocol_a", "protocol_a", provider) -// client == provider → 剥离前缀, 用 provider 重建 headers 后原样转发 +out = engine.convertHttpRequest(inRequest, "openai", "openai", provider) +// client == provider → 透传车道:重建 headers 后原样转发 -// 场景4: 流式转换(从 provider 协议解码,编码为 client 协议) -converter = engine.createStreamConverter("protocol_a", "protocol_b", provider) +// 场景4: 同协议智能透传(改写 model 字段) +provider = TargetProvider { model_name: "gpt-4-turbo", ... } +out = engine.convertHttpRequest(inRequest, "openai", "openai", provider) +// 智能透传车道:请求体中的 model 字段改写为 "gpt-4-turbo" + +// 场景5: 流式转换(从 provider 协议解码,编码为 client 协议) +converter = engine.createStreamConverter("openai", "anthropic", "claude-3-opus", CHAT) for chunk in upstreamSSE { for out in converter.processChunk(chunk) { sendToClient(out) } } converter.flush() + +// 场景6: 同协议流式智能透传 +converter = engine.createStreamConverter("openai", "openai", "gpt-4-turbo", CHAT) +// 使用 SmartPassthroughStreamConverter,逐 chunk 改写 model 字段 ``` --- @@ -641,9 +893,13 @@ converter.flush() ``` 上游 SSE 流 │ - ├── 同协议: PassthroughStreamConverter(用 provider 重建 Headers 后逐块转发) + ├─ 同协议 + 无 modelOverride: PassthroughStreamConverter + │ chunk → [chunk] │ - └── 跨协议: CanonicalStreamConverter + ├─ 同协议 + 有 modelOverride: SmartPassthroughStreamConverter + │ chunk → [rewriteResponseModelName(chunk)] + │ + └─ 不同协议: CanonicalStreamConverter StreamDecoder StreamEncoder ┌───────────┐ ┌───────────┐ │ SSE Parser│ │SSE Writer │ @@ -653,9 +909,17 @@ converter.flush() │ Event │──────────────────────▶│ Event │ │ Translator │ ┌──────────┐ │ Translator │ │ (状态机) │ │Middleware│ │ │ - └───────────┘ └──────────┘ └───────────┘ + └───────────┘ │(宽容模式) │ └───────────┘ + └──────────┘ + │ + ┌─────▼─────┐ + │modelOverride│ + │(覆写 model) │ + └───────────┘ ``` +**流式中间件错误处理**:`interceptStreamEvent` 返回 error 时,跳过该事件继续处理后续事件(宽容模式),而非中断整个流。 + ### 7.2 StreamDecoder 通用状态 StreamDecoder 需要跟踪以下通用状态。具体协议的 Decoder 可根据需要扩展: @@ -697,12 +961,16 @@ StreamDecoder 将协议原生 SSE 事件翻译为 CanonicalStreamEvent,StreamE ### 8.2 多模态扩展 +**状态**:Deferred(未实现) + Canonical Model 已预留 ImageBlock / AudioBlock / VideoBlock / FileBlock。实现路径: 1. 在各 ProtocolAdapter 中实现多模态块的编解码 2. 在 StreamDecoder/StreamEncoder 中处理多模态增量数据 ### 8.3 有状态特性扩展 +**状态**:Deferred(未实现) + ``` interface StatefulMiddleware extends ConversionMiddleware { stateStore: SessionStateStore @@ -722,6 +990,8 @@ interface StatefulMiddleware extends ConversionMiddleware { ### 8.5 自定义接口支持 +**状态**:Deferred(未实现) + ``` interface CustomInterfaceHandler { interfaceType(): InterfaceType @@ -732,6 +1002,8 @@ interface CustomInterfaceHandler { engine.registerCustomHandler(handler) ``` +当前实现中,未知接口直接走 PASSTHROUGH 透传。 + --- ## 9. 错误处理 @@ -759,16 +1031,36 @@ ErrorCode = Enum< ### 9.2 错误处理策略 -``` -ErrorHandler { mode: "strict" | "lenient" } +引擎采用**分层宽容策略**,根据接口层级和场景选择不同的错误处理方式: -strict: 任何错误抛出异常 -lenient: 尽力继续 - INCOMPATIBLE_FEATURE → 降级继续 - INTERFACE_NOT_SUPPORTED → 透传或返回空响应 - TOOL_CALL_PARSE_ERROR → 保留原始内容继续 - PROTOCOL_CONSTRAINT_VIOLATION → 自动修复 ``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 错误处理分层策略 │ +├─────────────────┬───────────────────────────────────────────────────┤ +│ 核心层(CHAT) │ 严格模式:任何错误返回 ConversionError,中断转换 │ +│ │ - Decode 失败 → 返回 JSON_PARSE_ERROR │ +│ │ - Middleware 失败 → 返回错误 │ +│ │ - Encode 失败 → 返回 ENCODING_FAILURE │ +├─────────────────┼───────────────────────────────────────────────────┤ +│ 扩展层 │ 宽容模式:记录警告日志,返回原始 body 透传 │ +│ (Models/Embed/ │ - Decode 失败 → log.warn + 返回原始 body │ +│ Rerank) │ - Encode 失败 → log.warn + 返回原始 body │ +├─────────────────┼───────────────────────────────────────────────────┤ +│ 流式中间件 │ 宽容模式:跳过错误事件,继续处理后续事件 │ +│ │ - interceptStreamEvent 返回 error → continue │ +├─────────────────┼───────────────────────────────────────────────────┤ +│ 智能透传 │ 宽容模式:重写失败则使用原始 body/chunk │ +│ │ - Rewrite 失败 → log.warn + 返回原始 body/chunk │ +├─────────────────┼───────────────────────────────────────────────────┤ +│ 请求中间件 │ 严格模式:返回错误则中断整个转换 │ +│ │ - intercept 返回 error → 返回 error │ +└─────────────────┴───────────────────────────────────────────────────┘ +``` + +**设计动机**: +- 核心接口(CHAT)必须保证语义正确性,错误应明确暴露 +- 扩展层接口优先保证可用性,错误应降级处理 +- 流式场景不应因单个事件错误中断整个流 **不支持接口的处理**(`INTERFACE_NOT_SUPPORTED`): @@ -778,7 +1070,7 @@ lenient: 尽力继续 | 返回空响应 | 不影响核心功能 | 返回空列表 `{data: []}` | | 返回错误 | 客户端明确需要此功能 | 返回 501 或协议格式错误 | -具体策略通过配置或 Middleware 决定。 +具体策略由 `supportsInterface` 返回值决定:返回 false 时引擎直接透传 body。 ### 9.3 错误响应格式 @@ -786,6 +1078,24 @@ lenient: 尽力继续 Middleware 中断转换时同理,引擎调用 clientAdapter.encodeError 将 ConversionError 编码为客户端可理解的格式。 +#### 9.3.1 EncodeError Fallback 行为 + +当客户端适配器不可用时,引擎使用通用 JSON 错误作为 fallback: + +``` +function encodeError(error, clientProtocol): + adapter = registry.get(clientProtocol) + if adapter不存在: + // Fallback: 通用 JSON 错误 + return { + "error": { + "message": error.message, + "type": "internal_error" + } + }, 500 + return adapter.encodeError(error) +``` + --- ## 附录 A:模块依赖 @@ -796,21 +1106,25 @@ Middleware 中断转换时同理,引擎调用 clientAdapter.encodeError 将 Co │ 门面:HTTP 转换 / 透传判断 / 流式转换 │ │ 无状态;协议识别见 §2.2 │ ├──────────────────────────────────────────────────┤ +│ HTTPRequestSpec / HTTPResponseSpec │ +│ url, method, headers, body / statusCode, ... │ +├──────────────────────────────────────────────────┤ │ TargetProvider │ │ base_url / api_key / model_name / adapter_config │ ├──────────────────┬───────────────────────────────┤ │ AdapterRegistry │ MiddlewareChain │ ├──────────────────┴───────────────────────────────┤ -│ StreamConverter: Passthrough | Canonical │ +│ StreamConverter: Passthrough | SmartPassthrough | Canonical │ ├──────────────────────────────────────────────────┤ │ ProtocolAdapter: 各协议实现 │ │ · buildHeaders(provider) · URL 映射 │ │ · Chat/Models/ModelInfo/Embeddings/Rerank/... 编解码 │ │ · encodeError · StreamDecoder / StreamEncoder │ +│ · rewriteRequestModelName / rewriteResponseModelName (智能透传) │ ├──────────────────────────────────────────────────┤ │ Canonical Model (Core + Extended) │ ├──────────────────────────────────────────────────┤ -│ Error Handling │ +│ Error Handling (分层宽容策略) │ ├──────────────────────────────────────────────────┤ │ Utility: UTF-8 Buffer / SSE Parser / Detector │ └──────────────────────────────────────────────────┘ @@ -823,22 +1137,25 @@ Middleware 中断转换时同理,引擎调用 clientAdapter.encodeError 将 Co ``` // ─── 核心入口 ─── ConversionEngine - .registerAdapter(adapter) - .use(middleware) + .registerAdapter(adapter): void + .use(middleware): void + .getRegistry(): AdapterRegistry .isPassthrough(clientProtocol, providerProtocol): Boolean - .convertHttpRequest(request, clientProtocol, providerProtocol, provider): HttpRequest - .convertHttpResponse(response, clientProtocol, providerProtocol, interfaceType): HttpResponse - .createStreamConverter(clientProtocol, providerProtocol, provider): StreamConverter + .convertHttpRequest(request, clientProtocol, providerProtocol, provider): HTTPRequestSpec + .convertHttpResponse(response, clientProtocol, providerProtocol, interfaceType, modelOverride): HTTPResponseSpec + .createStreamConverter(clientProtocol, providerProtocol, modelOverride, interfaceType): StreamConverter + .detectInterfaceType(nativePath, clientProtocol): InterfaceType + .encodeError(error, clientProtocol): (body, statusCode) + +// ─── HTTP 规格 ─── +HTTPRequestSpec { url, method, headers, body } +HTTPResponseSpec { statusCode, headers, body } // ─── 目标上游信息 ─── -TargetProvider - .base_url: String - .api_key: String - .model_name: String - .adapter_config: Map +TargetProvider { base_url, api_key, model_name, adapter_config } -// ─── URL 路由 ─── -// 协议识别见 §2.2;出站: provider.base_url + 目标协议原生路径 +// ─── 接口类型 ─── +InterfaceType = CHAT | MODELS | MODEL_INFO | EMBEDDINGS | RERANK | PASSTHROUGH // ─── 协议适配器 ─── ProtocolAdapter @@ -847,19 +1164,30 @@ ProtocolAdapter .decodeRequest(raw) / .encodeRequest(canonical, provider) .decodeResponse(raw) / .encodeResponse(canonical) .createStreamDecoder() / .createStreamEncoder() - .encodeError(error): RawResponse + .encodeError(error): (body, statusCode) + // 扩展层 .decodeModelsResponse / .encodeModelsResponse .decodeModelInfoResponse / .encodeModelInfoResponse - .decodeEmbeddingRequest / .encodeEmbeddingRequest(canonical, provider) / ...Response - .decodeRerankRequest / .encodeRerankRequest(canonical, provider) / ...Response + .decodeEmbeddingRequest / .encodeEmbeddingRequest / ...Response + .decodeRerankRequest / .encodeRerankRequest / ...Response + // 智能透传支持 + .extractUnifiedModelID(nativePath) + .extractModelName(body, interfaceType) + .rewriteRequestModelName(body, newModel, interfaceType) + .rewriteResponseModelName(body, newModel, interfaceType) // ─── 流式处理 ─── StreamConverter: .processChunk(raw) / .flush() - ├─ PassthroughStreamConverter [raw] → [raw](用 provider 重建 Headers) - └─ CanonicalStreamConverter decode → middleware → encode + ├─ PassthroughStreamConverter [raw] → [raw] + ├─ SmartPassthroughStreamConverter [raw] → [rewrite(raw)] + └─ CanonicalStreamConverter decode → middleware → modelOverride → encode -// ─── 接口类型 ─── -InterfaceType = CHAT | MODELS | MODEL_INFO | EMBEDDINGS | RERANK | AUDIO | IMAGES +// ─── 中间件 ─── +ConversionMiddleware + .intercept(req, clientProtocol, providerProtocol, ctx): (req, error) + .interceptStreamEvent(event, clientProtocol, providerProtocol, ctx): (event, error) + +ConversionContext { conversionId, interfaceType, timestamp, metadata } ``` --- @@ -1070,6 +1398,28 @@ Canonical Model 是**活的公共契约**,不是固定不变的。其字段集 | D.6 | [ ] 流式 StreamDecoder 和 StreamEncoder 已实现(对照 §4.8) | | D.7 | [ ] 扩展层接口的编解码已实现(支持的接口) | | D.8 | [ ] `encodeError` 已实现 | +| D.10 | [ ] `extractUnifiedModelID(nativePath)` 已实现 | +| D.10 | [ ] `extractModelName(body, interfaceType)` 已实现(覆盖 Chat/Embeddings/Rerank) | +| D.10 | [ ] `rewriteRequestModelName` 已实现(幂等、最小化) | +| D.10 | [ ] `rewriteResponseModelName` 已实现(按接口类型处理 model 字段存在性) | + +### D.10 智能透传支持 + +| 项目 | 说明 | +|------|------| +| extractUnifiedModelID | 从路径提取统一模型 ID 的规则(如 `/models/{provider_id}/{model_name}`) | +| extractModelName | 从请求体提取 model 字段值的规则(按接口类型:Chat/Embeddings/Rerank) | +| rewriteRequestModelName | 请求体 model 字段改写规则(最小化 JSON 手术,仅修改 model 字段) | +| rewriteResponseModelName | 响应体 model 字段改写规则(按接口类型处理 model 字段存在性) | + +**rewriteResponseModelName 的接口类型差异**: + +| 接口类型 | model 字段处理 | +|---------|---------------| +| CHAT | 存在则改写,不存在则添加(协议要求必须有 model 字段) | +| EMBEDDINGS | 存在则改写,不存在则添加(协议要求必须有 model 字段) | +| RERANK | 存在则改写,不存在则不添加(model 字段可选) | +| 其他 | 直接返回原始 body | ---