47 KiB
LLM API Protocol Conversion Layer — Architecture Design
语言无关的 HTTP 层 SDK。以 Hub-and-Spoke 架构实现多协议间的双向转换,覆盖完整 HTTP 接口体系,同协议自动透传,为新协议和多模态预留扩展点。
目录
- 设计目标与约束
- 架构总览
- 接口体系分层
- Canonical Model(规范模型)
- Protocol Adapter 接口
- Conversion Engine(转换引擎)
- 流式转换架构
- 扩展点设计
- 错误处理
- 附录 A:模块依赖
- 附录 B:接口速查
- 附录 C:字段晋升规范
- 附录 D:协议适配清单模板
- 附录 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 protocol(URL 前缀 / 配置映射 / 任意方式) │ │
│ │ Step 2: 剥离前缀 → 得到 nativePath │ │
│ │ Step 3: 确定 provider protocol(配置决定) │ │
│ └─────────────────────────────┬───────────────────────────────────┘ │
└───────────────────────────────┬─────────────────────────────────────────┘
│ nativePath + clientProtocol + providerProtocol
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Conversion Engine (SDK) │
│ │
│ ┌────────────┼────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌─────────┐ ┌──────────┐ │
│ │ Passthrough │ │核心接口层│ │扩展接口层 │ │
│ │ 同协议直通 │ │ Chat │ │ Models │ │
│ │ 或未知接口 │ │ 流式 │ │ Embed │ │
│ │ 直接转发 │ │ Tool │ │ Rerank │ │
│ └──────────────┘ │ Thinking│ └──────────┘ │
│ └────┬────┘ │
│ ┌──────────▼───────────────┐ │
│ │ ProtocolAdapter 层 │ │
│ │ ┌────────┐ ┌─────────┐ │ │
│ │ │Protocol│ │Protocol │ │ │
│ │ │ A │ │ B │ │ │
│ │ └────────┘ └─────────┘ │ │
│ └──────────────┬────────────┘ │
│ ┌──────────────▼────────────┐ │
│ │ Canonical Model │ │
│ └───────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
2.2 URL 路由规则
调用方负责识别协议并剥离前缀,将 nativePath、clientProtocol、providerProtocol 传入引擎。调用方可自行决定识别方式(URL 前缀、配置映射等)。
入站 URL 调用方剥离前缀后 引擎出站
──────────────────────────────────────────────────────────────────────────────
/<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 事件翻译为 CanonicalStreamEvent,StreamEncoder 做反向翻译。映射复杂度取决于协议的 SSE 格式:
- 类型化事件协议(事件有明确的 start/delta/stop 生命周期):与 CanonicalStreamEvent 接近 1:1 映射,状态机最简单
- Delta 模型协议(只有增量 delta chunk,无显式生命周期):需要状态机推断 block 边界,管理工具调用索引和参数累积
- 缓冲策略:部分协议的 Encoder 需要缓冲 ContentBlockStart 事件,等待首次 ContentBlockDelta 时合并输出
各协议的具体事件映射表详见各自的协议适配文档(附录 E)。
8. 扩展点设计
8.1 新协议接入
- 实现 ProtocolAdapter(URL 映射 + Header 映射 + 各接口编解码)
- 注册到 AdapterRegistry
- 完成
按附录 D 的清单模板逐项确认,所有项目确认后即可与引擎对接。
8.2 多模态扩展
Canonical Model 已预留 ImageBlock / AudioBlock / VideoBlock / FileBlock。实现路径:
- 在各 ProtocolAdapter 中实现多模态块的编解码
- 在 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 时引擎直接透传 bodycreateStreamDecoder/createStreamEncoder:引擎在createStreamConverter中调用(§6.1),Decoder 来自 provider 协议(解码上游 SSE),Encoder 来自 client 协议(编码给客户端)buildHeaders:每次请求出站时调用,同协议透传也会调用encodeError:转换失败或 Middleware 中断时调用,使用 client 协议格式编码错误响应
D.1 协议基本信息
| 项目 | 说明 |
|---|---|
| 协议名称 | 用于 URL 前缀和 Adapter 注册的唯一标识 |
| 协议版本 | 当前适配的 API 版本 |
| Base URL | API 服务地址 |
| 认证方式 | Header 名称和格式 |
D.2 接口识别
| 项目 | 说明 |
|---|---|
| URL 路径模式 | 列出所有接口的 URL 路径和对应的 InterfaceType |
| 接口能力矩阵 | 每种 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.3:system/user/assistant/tool)的映射 |
| 内容块 | 逐类型对照 §4.3 ContentBlock 联合体,确认每种类型的解码规则 |
| 工具定义 | 对照 §4.4 CanonicalTool,确认字段名映射 |
| 工具选择 | 对照 §4.4 ToolChoice 联合体,确认各变体的映射规则 |
| 参数映射 | 对照 §4.2 RequestParameters,逐字段确认映射和类型转换 |
| 公共字段 | 逐字段对照 §4.1 中 user_id、output_format、parallel_tool_use、thinking,确认提取规则 |
| 协议特有字段 | 仅本协议使用的字段列表,以及处理方式(忽略/记录) |
| 协议约束 | 消息顺序要求、角色交替要求、必填字段等 |
Encoder(Canonical → 协议)
| 项目 | 说明 |
|---|---|
| 模型名称 | 使用 provider.model_name 覆盖 canonical.model |
| 系统消息注入 | 如何将 canonical.system 编码为协议格式 |
| 消息编码 | 各角色的编码规则(特别注意 role 映射、content 结构差异) |
| 角色约束处理 | 是否需要 enforceAlternation?具体策略? |
| 工具编码 | 对照 §4.4 确认 tools/tool_choice 的编码规则 |
| 参数编码 | 对照 §4.2 确认参数映射 |
| 公共字段编码 | 逐字段确认注入规则 |
| 降级处理 | 对照 §8.4 三级降级策略(自动映射/丢弃/替代方案),确认每个不支持字段的处理方式 |
D.5 核心层 — Chat 响应编解码
逐字段对照 §4.7 CanonicalResponse 确认映射关系。
Decoder(协议 → Canonical)
| 项目 | 说明 |
|---|---|
| 响应结构 | 协议响应的顶层结构解析 |
| 内容块解码 | 逐类型对照 §4.3 ContentBlock 联合体,确认解码规则 |
| 停止原因 | 协议原生值与 §4.7 StopReason 枚举的映射表 |
| Token 用量 | 对照 §4.7 CanonicalUsage,逐字段确认映射和命名差异 |
| 推理内容 | ThinkingBlock 的解码规则 |
| 协议特有内容 | 仅本协议返回的特有字段的处理(忽略/丢弃/记录) |
Encoder(Canonical → 协议)
| 项目 | 说明 |
|---|---|
| 响应结构 | 如何从 CanonicalResponse 构建协议响应的顶层结构 |
| 内容块编码 | 各 ContentBlock 类型到协议格式的编码规则 |
| 停止原因 | §4.7 StopReason 到协议原生值的映射表 |
| Token 用量 | CanonicalUsage 到协议 usage 字段的编码规则 |
| 推理内容 | ThinkingBlock 的编码规则 |
| 降级处理 | Canonical 中协议不支持的 stop_reason 等值的降级映射 |
D.6 核心层 — 流式编解码
对照 §4.8 CanonicalStreamEvent 和 §7.2 StreamDecoderState 确认映射和状态设计。
StreamDecoder(协议 SSE → Canonical 事件)
| 项目 | 说明 |
|---|---|
| SSE 格式 | 协议的 SSE 事件格式(named events vs delta chunks) |
| 事件映射表 | 每种协议 SSE 事件 → §4.8 CanonicalStreamEvent 的映射规则 |
| 状态机设计 | 在 §7.2 通用状态基础上,声明本协议需要的额外状态 |
| UTF-8 安全 | 是否需要处理跨 chunk 的 UTF-8 截断 |
| 特殊情况 | 工具调用参数乱序、无限空白检测、延迟字段等 |
StreamEncoder(Canonical 事件 → 协议 SSE)
| 项目 | 说明 |
|---|---|
| 事件映射表 | §4.8 每个 CanonicalStreamEvent → 协议 SSE chunk 的映射规则 |
| 缓冲策略 | 哪些事件需要缓冲、何时输出 |
| SSE 格式 | event type / data 字段的编码方式 |
| 结束标记 | 如何输出流结束信号(如 [DONE]) |
D.7 扩展层接口
对每种扩展层接口逐项确认。当前扩展层 InterfaceType 见 §5.3 枚举(MODELS、MODEL_INFO、EMBEDDINGS、RERANK),随协议扩展可能增加。
| 项目 | 说明 |
|---|---|
| 接口是否存在 | 该协议是否原生支持此接口 |
| URL 路径 | 接口的 URL 路径 |
| 请求格式 | 对照 §4.9 扩展层 Canonical Models,确认请求体映射 |
| 响应格式 | 对照 §4.9 扩展层 Canonical Models,确认响应体映射 |
| 不支持时策略 | 透传 / 返回空响应 / 返回错误 |
D.8 错误编码
| 项目 | 说明 |
|---|---|
| 错误响应格式 | 协议的错误响应 JSON 结构 |
| encodeError | §9.1 ConversionError → 协议错误格式的编码规则 |
| HTTP 状态码 | 协议常用的错误状态码映射 |
D.9 自检清单
| 章节 | 检查项 |
|---|---|
| D.2 | [ ] 所有 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 |
| Anthropic | conversion_anthropic.md |
新增协议时,按附录 D 模板编撰适配文档,并将链接添加到上表。