1
0
Files
nex/docs/conversion_design.md

1076 lines
47 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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} │ │
│ │ │ │
│ │ /<protocol_a>/v1/chat/completions → client=protocol_a, /v1/... │ │
│ │ /<protocol_b>/v1/messages → client=protocol_b, /v1/... │ │
│ │ │ │
│ │ Step 1: 识别 client protocolURL 前缀 / 配置映射 / 任意方式) │ │
│ │ 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 调用方剥离前缀后 引擎出站
──────────────────────────────────────────────────────────────────────────────
/<protocol_a>/v1/chat/completions → /v1/chat/completions → 目标协议路径
/<protocol_b>/v1/messages → /v1/messages → 目标协议路径
/<protocol_a>/v1/models → /v1/models → /v1/models通常不变
```
出站到上游 API 时使用服务端协议原生路径(无前缀)。
### 2.3 请求处理流程
每个 HTTP 请求的转换流程:
```
客户端入站 调用方(协议识别+前缀剥离) 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: │
│ 协议原生格式 │ │ 目标协议格式 │
└──────────────────┘ └──────────────────┘
```
响应方向同理含流式。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<None, String, Array<SystemBlock>>
SystemBlock {
text: String
}
messages: Array<CanonicalMessage>
tools: Option<Array<CanonicalTool>>
tool_choice: Option<ToolChoice>
parameters: RequestParameters
thinking: Option<ThinkingConfig>
stream: Boolean
user_id: Option<String>
output_format: Option<OutputFormat>
parallel_tool_use: Option<Boolean> // true = 允许并行, false = 禁止并行
}
```
### 4.2 RequestParameters
```
RequestParameters {
max_tokens: Option<Integer>
temperature: Option<Float>
top_p: Option<Float>
top_k: Option<Integer>
frequency_penalty: Option<Float>
presence_penalty: Option<Float>
stop_sequences: Option<Array<String>>
}
```
### 4.3 CanonicalMessage / ContentBlock
```
CanonicalMessage {
role: Enum<system, user, assistant, tool>
content: Array<ContentBlock>
}
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<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: ... } // 多模态预留
>
```
### 4.4 CanonicalTool / ToolChoice
```
CanonicalTool {
name: String
description: Option<String>
input_schema: Object
}
ToolChoice = Union<
{type: "auto"},
{type: "none"},
{type: "any"},
{type: "tool", name: String}
>
```
### 4.5 ThinkingConfig
```
ThinkingConfig {
type: Enum<enabled, disabled>
budget_tokens: Option<Integer> // token 级控制
effort: Option<Enum<low, medium, high, xhigh>> // 抽象级别控制
}
```
### 4.6 OutputFormat
```
OutputFormat = Union<
{ type: "json_object" },
{ type: "json_schema", json_schema: { name: String, schema: Object, strict: Option<Boolean> } },
{ type: "text" }
>
```
### 4.7 CanonicalResponse
```
CanonicalResponse {
id: String
model: String
content: Array<ContentBlock>
stop_reason: Option<StopReason> // end_turn | max_tokens | tool_use | stop_sequence | content_filter | refusal
usage: CanonicalUsage
}
CanonicalUsage {
input_tokens: Integer
output_tokens: Integer
cache_read_tokens: Option<Integer>
cache_creation_tokens: Option<Integer>
reasoning_tokens: Option<Integer>
}
```
### 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> }
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<String, Any> // 协议专属配置
}
```
`adapter_config` 由各 Adapter 自行定义所需的 key引擎不感知含义原样透传。各 Adapter 需在协议适配文档中声明自己从 `adapter_config` 读取的 key 列表和默认值。
### 5.2 ProtocolAdapter
每种协议的完整适配器,融入所有接口类型的处理能力。所有 Adapter 通过代码注册,不支持动态增减。新协议可通过空函数(如不支持的扩展层接口直接透传)渐进适配,接口集中定义方便明确所有应实现的内容。
```
interface ProtocolAdapter {
protocolName(): String
protocolVersion(): String
supportsPassthrough(): Boolean // 同协议透传开关,默认 true
// HTTP 级别
buildUrl(nativePath: String, interfaceType: InterfaceType): String // 始终返回有效 URL未知接口返回 nativePath
buildHeaders(provider: TargetProvider): Map<String, String>
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<CanonicalStreamEvent>
flush(): Array<CanonicalStreamEvent>
}
interface StreamEncoder {
encodeEvent(event): Array<RawSSEChunk>
flush(): Array<RawSSEChunk>
}
```
### 5.5 AdapterRegistry
```
interface AdapterRegistry {
register(adapter: ProtocolAdapter): void
get(protocolName: String): ProtocolAdapter
listProtocols(): Array<String>
}
```
---
## 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
interfaceType = detectInterfaceType(nativePath)
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)
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<RawSSEChunk>
flush(): Array<RawSSEChunk>
}
class PassthroughStreamConverter implements StreamConverter {
headers: Map<String, String>
constructor(headers) { this.headers = headers }
processChunk(rawChunk): Array<RawSSEChunk> { return [rawChunk] }
flush(): Array<RawSSEChunk> { 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<Integer>
currentBlockType: Map<Integer, String>
currentBlockId: Map<Integer, String>
utf8Remainder: Option<ByteArray> // UTF-8 跨 chunk 安全
accumulatedUsage: Option<CanonicalUsage>
}
```
**协议特有状态**:某些协议的 SSE 格式需要额外状态(如工具调用参数累积、工具调用索引映射等),在各自的协议适配文档中定义。
### 7.3 事件映射概述
StreamDecoder 将协议原生 SSE 事件翻译为 CanonicalStreamEventStreamEncoder 做反向翻译。映射复杂度取决于协议的 SSE 格式:
- **类型化事件协议**(事件有明确的 start/delta/stop 生命周期):与 CanonicalStreamEvent 接近 1:1 映射,状态机最简单
- **Delta 模型协议**(只有增量 delta chunk无显式生命周期需要状态机推断 block 边界,管理工具调用索引和参数累积
- **缓冲策略**:部分协议的 Encoder 需要缓冲 ContentBlockStart 事件,等待首次 ContentBlockDelta 时合并输出
各协议的具体事件映射表详见各自的协议适配文档(附录 E
---
## 8. 扩展点设计
### 8.1 新协议接入
1. 实现 ProtocolAdapterURL 映射 + 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<String, Any>
// ─── URL 路由 ───
// 协议识别见 §2.2;出站: provider.base_url + 目标协议原生路径
// ─── 协议适配器 ───
ProtocolAdapter
.protocolName() / .protocolVersion() / .supportsPassthrough()
.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.1Decoder 来自 provider 协议(解码上游 SSEEncoder 来自 client 协议(编码给客户端)
- `buildHeaders`:每次请求出站时调用,同协议透传也会调用
- `encodeError`:转换失败或 Middleware 中断时调用,使用 client 协议格式编码错误响应
### D.1 协议基本信息
| 项目 | 说明 |
|------|------|
| 协议名称 | 用于 URL 前缀和 Adapter 注册的唯一标识 |
| 协议版本 | 当前适配的 API 版本 |
| Base URL | API 服务地址 |
| 认证方式 | Header 名称和格式 |
### D.2 接口识别
| 项目 | 说明 |
|------|------|
| URL 路径模式 | 列出所有接口的 URL 路径和对应的 InterfaceType |
| 接口能力矩阵 | 每种 InterfaceType 的支持状态(`supportsInterface` |
| URL 映射表 | 每种 InterfaceType 的目标 URL 路径(`buildUrl` |
### 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.3system/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`,确认提取规则 |
| 协议特有字段 | 仅本协议使用的字段列表,以及处理方式(忽略/记录) |
| 协议约束 | 消息顺序要求、角色交替要求、必填字段等 |
#### EncoderCanonical → 协议)
| 项目 | 说明 |
|------|------|
| 模型名称 | 使用 `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 的解码规则 |
| 协议特有内容 | 仅本协议返回的特有字段的处理(忽略/丢弃/记录) |
#### EncoderCanonical → 协议)
| 项目 | 说明 |
|------|------|
| 响应结构 | 如何从 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 截断 |
| 特殊情况 | 工具调用参数乱序、无限空白检测、延迟字段等 |
#### StreamEncoderCanonical 事件 → 协议 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 | [ ] 所有 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 模板编撰适配文档,并将链接添加到上表。