Merge branch 'dev-conversion-docs'
This commit is contained in:
@@ -126,24 +126,54 @@
|
||||
|
||||
### 2.3 请求处理流程
|
||||
|
||||
每个 HTTP 请求的转换流程:
|
||||
#### 2.3.1 三车道数据流模型
|
||||
|
||||
引擎根据协议匹配情况和参数条件,选择三条数据流通道之一:
|
||||
|
||||
```
|
||||
客户端入站 调用方(协议识别+前缀剥离) SDK 内部处理 上游出站
|
||||
┌──────────────────┐ ┌──────────────────┐
|
||||
│ URL: │ 调用方完成: 1. 接口识别: CHAT │ URL: │
|
||||
│ /<protocol>/ │ · 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: │
|
||||
│ /<protocol>/ │ · 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<String, Array<ContentBlock>>,
|
||||
is_error: Option<Boolean> }
|
||||
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<String, String>
|
||||
body: ByteArray // 原始请求体
|
||||
}
|
||||
|
||||
HTTPResponseSpec {
|
||||
statusCode: Integer
|
||||
headers: Map<String, String>
|
||||
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<RawSSEChunk>
|
||||
flush(): Array<RawSSEChunk>
|
||||
}
|
||||
```
|
||||
|
||||
#### 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<String, String>
|
||||
constructor(headers) { this.headers = headers }
|
||||
processChunk(rawChunk): Array<RawSSEChunk> { return [rawChunk] }
|
||||
flush(): Array<RawSSEChunk> { return [] }
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.3.4 SmartPassthroughStreamConverter
|
||||
|
||||
```
|
||||
class SmartPassthroughStreamConverter implements StreamConverter {
|
||||
adapter: ProtocolAdapter
|
||||
modelOverride: String
|
||||
interfaceType: InterfaceType
|
||||
|
||||
processChunk(rawChunk): Array<RawSSEChunk> {
|
||||
if rawChunk为空: return []
|
||||
rewrittenChunk = adapter.rewriteResponseModelName(rawChunk, modelOverride, interfaceType)
|
||||
if rewrite失败:
|
||||
log.warn("智能透传改写失败,使用原始 chunk")
|
||||
return [rawChunk]
|
||||
return [rewrittenChunk]
|
||||
}
|
||||
flush(): Array<RawSSEChunk> { 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<String, Any>
|
||||
}
|
||||
```
|
||||
|
||||
#### 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<String, Any>
|
||||
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 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user