1084 lines
48 KiB
Markdown
1084 lines
48 KiB
Markdown
# 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 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 级别
|
||
detectInterfaceType(nativePath: String): InterfaceType // 根据协议的 URL 路径识别接口类型
|
||
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
|
||
|
||
if isPassthrough(clientProtocol, providerProtocol):
|
||
providerAdapter = registry.get(providerProtocol)
|
||
return {url: provider.base_url + nativePath, method: request.method,
|
||
headers: providerAdapter.buildHeaders(provider), body: request.body}
|
||
|
||
clientAdapter = registry.get(clientProtocol)
|
||
providerAdapter = registry.get(providerProtocol)
|
||
|
||
// 使用 clientAdapter 识别接口类型
|
||
interfaceType = clientAdapter.detectInterfaceType(nativePath)
|
||
|
||
providerUrl = providerAdapter.buildUrl(nativePath, interfaceType)
|
||
providerHeaders = providerAdapter.buildHeaders(provider)
|
||
providerBody = convertBody(interfaceType, clientAdapter, providerAdapter, provider, request.body)
|
||
|
||
return {url: provider.base_url + providerUrl, method: request.method,
|
||
headers: providerHeaders, body: providerBody}
|
||
}
|
||
|
||
// 非流式响应转换
|
||
convertHttpResponse(response, clientProtocol, providerProtocol, interfaceType): HttpResponse {
|
||
if isPassthrough(clientProtocol, providerProtocol): return response
|
||
|
||
clientAdapter = registry.get(clientProtocol)
|
||
providerAdapter = registry.get(providerProtocol)
|
||
providerBody = convertResponseBody(interfaceType, clientAdapter, providerAdapter, response.body)
|
||
|
||
return {status: response.status, headers: response.headers, body: providerBody}
|
||
}
|
||
|
||
// 流式转换:从 provider 协议解码,编码为 client 协议
|
||
createStreamConverter(clientProtocol, providerProtocol, provider): StreamConverter {
|
||
if isPassthrough(clientProtocol, providerProtocol):
|
||
providerAdapter = registry.get(providerProtocol)
|
||
return new PassthroughStreamConverter(providerAdapter.buildHeaders(provider))
|
||
|
||
providerAdapter = registry.get(providerProtocol)
|
||
clientAdapter = registry.get(clientProtocol)
|
||
return new CanonicalStreamConverter(
|
||
providerAdapter.createStreamDecoder(), clientAdapter.createStreamEncoder(), middlewareChain)
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.2 Body 转换分发
|
||
|
||
```
|
||
function convertBody(interfaceType, clientAdapter, providerAdapter, provider, body):
|
||
switch interfaceType:
|
||
case CHAT:
|
||
canonical = clientAdapter.decodeRequest(body)
|
||
canonical = middlewareChain.apply(canonical)
|
||
return providerAdapter.encodeRequest(canonical, provider)
|
||
case MODELS:
|
||
return body // GET 请求,无 body
|
||
case MODEL_INFO:
|
||
return body // GET 请求,无 body
|
||
case EMBEDDINGS:
|
||
if !clientAdapter.supportsInterface(EMBEDDINGS)
|
||
|| !providerAdapter.supportsInterface(EMBEDDINGS):
|
||
return body // 尽力转换:不支持则透传
|
||
return providerAdapter.encodeEmbeddingRequest(
|
||
clientAdapter.decodeEmbeddingRequest(body), provider)
|
||
case RERANK:
|
||
// 同 EMBEDDINGS 模式
|
||
default:
|
||
return body // 透传层:原样转发
|
||
|
||
function convertResponseBody(interfaceType, clientAdapter, providerAdapter, body):
|
||
// 结构与 convertBody 对称,CHAT 走 Canonical 深度转换,扩展层走轻量映射,默认透传
|
||
// 各接口的具体响应转换逻辑详见各协议适配文档(附录 E)
|
||
```
|
||
|
||
### 6.3 StreamConverter
|
||
|
||
```
|
||
interface StreamConverter {
|
||
processChunk(rawChunk): Array<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 新协议接入
|
||
|
||
1. 实现 ProtocolAdapter(URL 映射 + Header 映射 + 各接口编解码)
|
||
2. 注册到 AdapterRegistry
|
||
3. 完成
|
||
|
||
按附录 D 的清单模板逐项确认,所有项目确认后即可与引擎对接。
|
||
|
||
### 8.2 多模态扩展
|
||
|
||
Canonical Model 已预留 ImageBlock / AudioBlock / VideoBlock / FileBlock。实现路径:
|
||
1. 在各 ProtocolAdapter 中实现多模态块的编解码
|
||
2. 在 StreamDecoder/StreamEncoder 中处理多模态增量数据
|
||
|
||
### 8.3 有状态特性扩展
|
||
|
||
```
|
||
interface StatefulMiddleware extends ConversionMiddleware {
|
||
stateStore: SessionStateStore
|
||
}
|
||
```
|
||
|
||
适用场景:跨轮次保留协议特有的状态(如 thinking signature)。
|
||
|
||
### 8.4 特性降级策略
|
||
|
||
当 Canonical 公共字段在目标协议中无直接等价物时:
|
||
- 有语义等价物 → 自动映射
|
||
- 无等价物 → 丢弃,日志 warn
|
||
- 有替代方案 → 降级策略处理(如注入合成工具实现 JSON 模式)
|
||
|
||
各协议的具体降级规则详见各自的协议适配文档(附录 E)。
|
||
|
||
### 8.5 自定义接口支持
|
||
|
||
```
|
||
interface CustomInterfaceHandler {
|
||
interfaceType(): InterfaceType
|
||
matchUrl(url): Boolean
|
||
convertRequest(client, provider, raw): raw
|
||
convertResponse(client, provider, raw): raw
|
||
}
|
||
engine.registerCustomHandler(handler)
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 错误处理
|
||
|
||
### 9.1 错误分类
|
||
|
||
```
|
||
ConversionError { code: ErrorCode, message, clientProtocol?, providerProtocol?,
|
||
interfaceType?, details?, cause? }
|
||
|
||
ErrorCode = Enum<
|
||
INVALID_INPUT, // 请求格式不符合协议规范
|
||
MISSING_REQUIRED_FIELD, // 缺少必填字段
|
||
INCOMPATIBLE_FEATURE, // 特性在目标协议不可用
|
||
FIELD_MAPPING_FAILURE, // 字段映射逻辑错误
|
||
TOOL_CALL_PARSE_ERROR, // 工具调用参数解析失败
|
||
JSON_PARSE_ERROR, // JSON 解析失败
|
||
STREAM_STATE_ERROR, // 流式状态机异常
|
||
UTF8_DECODE_ERROR, // UTF-8 解码错误
|
||
PROTOCOL_CONSTRAINT_VIOLATION, // 违反协议约束
|
||
ENCODING_FAILURE, // 编码失败
|
||
INTERFACE_NOT_SUPPORTED // 目标协议不支持此接口
|
||
>
|
||
```
|
||
|
||
### 9.2 错误处理策略
|
||
|
||
```
|
||
ErrorHandler { mode: "strict" | "lenient" }
|
||
|
||
strict: 任何错误抛出异常
|
||
lenient: 尽力继续
|
||
INCOMPATIBLE_FEATURE → 降级继续
|
||
INTERFACE_NOT_SUPPORTED → 透传或返回空响应
|
||
TOOL_CALL_PARSE_ERROR → 保留原始内容继续
|
||
PROTOCOL_CONSTRAINT_VIOLATION → 自动修复
|
||
```
|
||
|
||
**不支持接口的处理**(`INTERFACE_NOT_SUPPORTED`):
|
||
|
||
| 策略 | 适用场景 | 实现 |
|
||
|------|---------|------|
|
||
| 透传 | 上游可能有自己的实现 | URL+Header 适配后 Body 原样转发 |
|
||
| 返回空响应 | 不影响核心功能 | 返回空列表 `{data: []}` |
|
||
| 返回错误 | 客户端明确需要此功能 | 返回 501 或协议格式错误 |
|
||
|
||
具体策略通过配置或 Middleware 决定。
|
||
|
||
### 9.3 错误响应格式
|
||
|
||
转换失败时,错误响应用**客户端协议(client protocol)**的格式编码。由 `clientAdapter.encodeError(error)` 完成。各协议的错误响应 JSON 结构和 HTTP 状态码映射详见各自的协议适配文档(附录 E)。
|
||
|
||
Middleware 中断转换时同理,引擎调用 clientAdapter.encodeError 将 ConversionError 编码为客户端可理解的格式。
|
||
|
||
---
|
||
|
||
## 附录 A:模块依赖
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────┐
|
||
│ ConversionEngine │
|
||
│ 门面:HTTP 转换 / 透传判断 / 流式转换 │
|
||
│ 无状态;协议识别见 §2.2 │
|
||
├──────────────────────────────────────────────────┤
|
||
│ TargetProvider │
|
||
│ base_url / api_key / model_name / adapter_config │
|
||
├──────────────────┬───────────────────────────────┤
|
||
│ AdapterRegistry │ MiddlewareChain │
|
||
├──────────────────┴───────────────────────────────┤
|
||
│ StreamConverter: Passthrough | Canonical │
|
||
├──────────────────────────────────────────────────┤
|
||
│ ProtocolAdapter: 各协议实现 │
|
||
│ · buildHeaders(provider) · URL 映射 │
|
||
│ · Chat/Models/ModelInfo/Embeddings/Rerank/... 编解码 │
|
||
│ · encodeError · StreamDecoder / StreamEncoder │
|
||
├──────────────────────────────────────────────────┤
|
||
│ Canonical Model (Core + Extended) │
|
||
├──────────────────────────────────────────────────┤
|
||
│ Error Handling │
|
||
├──────────────────────────────────────────────────┤
|
||
│ Utility: UTF-8 Buffer / SSE Parser / Detector │
|
||
└──────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 附录 B:接口速查
|
||
|
||
```
|
||
// ─── 核心入口 ───
|
||
ConversionEngine
|
||
.registerAdapter(adapter)
|
||
.use(middleware)
|
||
.isPassthrough(clientProtocol, providerProtocol): Boolean
|
||
.convertHttpRequest(request, clientProtocol, providerProtocol, provider): HttpRequest
|
||
.convertHttpResponse(response, clientProtocol, providerProtocol, interfaceType): HttpResponse
|
||
.createStreamConverter(clientProtocol, providerProtocol, provider): StreamConverter
|
||
|
||
// ─── 目标上游信息 ───
|
||
TargetProvider
|
||
.base_url: String
|
||
.api_key: String
|
||
.model_name: String
|
||
.adapter_config: Map<String, Any>
|
||
|
||
// ─── URL 路由 ───
|
||
// 协议识别见 §2.2;出站: provider.base_url + 目标协议原生路径
|
||
|
||
// ─── 协议适配器 ───
|
||
ProtocolAdapter
|
||
.protocolName() / .protocolVersion() / .supportsPassthrough()
|
||
.detectInterfaceType(nativePath) / .buildUrl(nativePath, type) / .buildHeaders(provider) / .supportsInterface(type)
|
||
.decodeRequest(raw) / .encodeRequest(canonical, provider)
|
||
.decodeResponse(raw) / .encodeResponse(canonical)
|
||
.createStreamDecoder() / .createStreamEncoder()
|
||
.encodeError(error): RawResponse
|
||
.decodeModelsResponse / .encodeModelsResponse
|
||
.decodeModelInfoResponse / .encodeModelInfoResponse
|
||
.decodeEmbeddingRequest / .encodeEmbeddingRequest(canonical, provider) / ...Response
|
||
.decodeRerankRequest / .encodeRerankRequest(canonical, provider) / ...Response
|
||
|
||
// ─── 流式处理 ───
|
||
StreamConverter: .processChunk(raw) / .flush()
|
||
├─ PassthroughStreamConverter [raw] → [raw](用 provider 重建 Headers)
|
||
└─ CanonicalStreamConverter decode → middleware → encode
|
||
|
||
// ─── 接口类型 ───
|
||
InterfaceType = CHAT | MODELS | MODEL_INFO | EMBEDDINGS | RERANK | AUDIO | IMAGES
|
||
```
|
||
|
||
---
|
||
|
||
## 附录 C:字段晋升规范
|
||
|
||
### 原则
|
||
|
||
Canonical Model 是**活的公共契约**,不是固定不变的。其字段集反映的是**当前已适配协议的公共语义**,随协议扩展而演进。
|
||
|
||
### 字段分类
|
||
|
||
| 分类 | 判定条件 | 处理方式 |
|
||
|------|---------|---------|
|
||
| **公共字段** | ≥2 个协议表达相同含义 | 升级为 Canonical Model 的正式字段 |
|
||
| **协议特有字段** | 仅 1 个协议使用 | 不在中间层传播;同协议透传时自然保留;跨协议时丢弃 |
|
||
| **未知字段** | 新协议带来的新概念 | 先按特有字段处理;当第二个协议也出现时晋升 |
|
||
|
||
### 晋升流程
|
||
|
||
```
|
||
1. 发现:适配新协议时,识别出无法映射到现有 Canonical 字段的语义
|
||
2. 判定:该语义是否已被 ≥1 个现有协议所表达?
|
||
- 是 → 晋升为 Canonical 公共字段
|
||
- 否 → 暂不纳入,记录在适配清单中
|
||
3. 设计:确定字段名(协议中立)、类型、在 Canonical 中的位置
|
||
4. 更新:修改 Canonical Model 定义,所有现有 Adapter 的 Decoder/Encoder 同步更新
|
||
5. 文档:更新本节说明
|
||
```
|
||
|
||
### 晋升示例
|
||
|
||
| 字段 | 晋升原因 |
|
||
|------|---------|
|
||
| `user_id` | 多协议均支持用户标识,但字段名和嵌套位置不同 |
|
||
| `output_format` | 多协议均支持控制输出格式,但字段名不同 |
|
||
| `parallel_tool_use` | 多协议均支持并行工具调用控制,但语义方向(允许/禁止)不同 |
|
||
| `top_k` | 多协议(Gemini、OpenAI 兼容厂商、Ollama 等)均支持,但字段名和嵌套位置不同 |
|
||
| `frequency_penalty` / `presence_penalty` | OpenAI 生态广泛使用,多协议均支持类似概念 |
|
||
| `stop_reason: refusal` | OpenAI 和 Gemini 均有安全拒绝/内容过滤场景,语义一致 |
|
||
|
||
### 降级规则
|
||
|
||
当公共字段的目标协议不支持时:
|
||
- 有语义等价物 → 自动映射
|
||
- 无等价物 → 丢弃,日志 warn
|
||
- 有替代方案 → 降级策略处理
|
||
|
||
---
|
||
|
||
## 附录 D:协议适配清单模板
|
||
|
||
适配新协议时,按以下清单逐项确认。所有项目确认后即可与引擎对接。
|
||
|
||
**方法调用时机**:理解各方法何时被引擎调用,有助于正确实现:
|
||
- `decodeRequest` / `encodeRequest`:引擎在 `convertBody(CHAT)` 中调用(§6.2),对每个 Chat 请求执行 decode → middleware → encode 管道
|
||
- `decodeResponse` / `encodeResponse`:引擎在 `convertResponseBody(CHAT)` 中调用,对每个 Chat 响应执行反向管道
|
||
- `decodeXxxRequest` / `encodeXxxRequest`:扩展层接口仅在 `supportsInterface` 返回 true 时被调用(§6.2 convertBody 分支);返回 false 时引擎直接透传 body
|
||
- `createStreamDecoder` / `createStreamEncoder`:引擎在 `createStreamConverter` 中调用(§6.1),Decoder 来自 provider 协议(解码上游 SSE),Encoder 来自 client 协议(编码给客户端)
|
||
- `buildHeaders`:每次请求出站时调用,同协议透传也会调用
|
||
- `encodeError`:转换失败或 Middleware 中断时调用,使用 client 协议格式编码错误响应
|
||
|
||
### D.1 协议基本信息
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 协议名称 | 用于 URL 前缀和 Adapter 注册的唯一标识 |
|
||
| 协议版本 | 当前适配的 API 版本 |
|
||
| Base URL | API 服务地址 |
|
||
| 认证方式 | Header 名称和格式 |
|
||
|
||
### D.2 接口识别
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| URL 路径模式 | 列出所有接口的 URL 路径和对应的 InterfaceType,由 `detectInterfaceType` 实现 |
|
||
| 接口能力矩阵 | 每种 InterfaceType 的支持状态(`supportsInterface`) |
|
||
| URL 映射表 | 每种 InterfaceType 的目标 URL 路径(`buildUrl`) |
|
||
|
||
**重要**:`detectInterfaceType` 由各协议 Adapter 实现,因为不同协议有不同的 URL 路径约定。例如:
|
||
- OpenAI: `/v1/chat/completions` → CHAT
|
||
- Anthropic: `/v1/messages` → CHAT
|
||
|
||
### D.3 请求头构建
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 认证头 | 如何从 `provider.api_key` 构建认证 Header |
|
||
| 必需 Header | 协议要求的固定 Header(如 Content-Type) |
|
||
| 可选 Header | 根据功能动态添加的 Header |
|
||
| adapter_config 契约 | 定义本 Adapter 从 `provider.adapter_config` 读取的 key 列表和默认值 |
|
||
|
||
### D.4 核心层 — Chat 请求编解码
|
||
|
||
逐字段对照 §4.1 CanonicalRequest 和 §4.2 RequestParameters 确认映射关系。
|
||
|
||
#### Decoder(协议 → Canonical)
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 系统消息 | 如何提取为 `canonical.system`(顶层字段 / messages 中 / 不支持) |
|
||
| 消息角色 | 协议角色与 Canonical 角色(§4.3:system/user/assistant/tool)的映射 |
|
||
| 内容块 | 逐类型对照 §4.3 ContentBlock 联合体,确认每种类型的解码规则 |
|
||
| 工具定义 | 对照 §4.4 CanonicalTool,确认字段名映射 |
|
||
| 工具选择 | 对照 §4.4 ToolChoice 联合体,确认各变体的映射规则 |
|
||
| 参数映射 | 对照 §4.2 RequestParameters,逐字段确认映射和类型转换 |
|
||
| 公共字段 | 逐字段对照 §4.1 中 `user_id`、`output_format`、`parallel_tool_use`、`thinking`,确认提取规则 |
|
||
| 协议特有字段 | 仅本协议使用的字段列表,以及处理方式(忽略/记录) |
|
||
| 协议约束 | 消息顺序要求、角色交替要求、必填字段等 |
|
||
|
||
#### Encoder(Canonical → 协议)
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 模型名称 | 使用 `provider.model_name` 覆盖 `canonical.model` |
|
||
| 系统消息注入 | 如何将 `canonical.system` 编码为协议格式 |
|
||
| 消息编码 | 各角色的编码规则(特别注意 role 映射、content 结构差异) |
|
||
| 角色约束处理 | 是否需要 enforceAlternation?具体策略? |
|
||
| 工具编码 | 对照 §4.4 确认 tools/tool_choice 的编码规则 |
|
||
| 参数编码 | 对照 §4.2 确认参数映射 |
|
||
| 公共字段编码 | 逐字段确认注入规则 |
|
||
| 降级处理 | 对照 §8.4 三级降级策略(自动映射/丢弃/替代方案),确认每个不支持字段的处理方式 |
|
||
|
||
### D.5 核心层 — Chat 响应编解码
|
||
|
||
逐字段对照 §4.7 CanonicalResponse 确认映射关系。
|
||
|
||
#### Decoder(协议 → Canonical)
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 响应结构 | 协议响应的顶层结构解析 |
|
||
| 内容块解码 | 逐类型对照 §4.3 ContentBlock 联合体,确认解码规则 |
|
||
| 停止原因 | 协议原生值与 §4.7 StopReason 枚举的映射表 |
|
||
| Token 用量 | 对照 §4.7 CanonicalUsage,逐字段确认映射和命名差异 |
|
||
| 推理内容 | ThinkingBlock 的解码规则 |
|
||
| 协议特有内容 | 仅本协议返回的特有字段的处理(忽略/丢弃/记录) |
|
||
|
||
#### Encoder(Canonical → 协议)
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 响应结构 | 如何从 CanonicalResponse 构建协议响应的顶层结构 |
|
||
| 内容块编码 | 各 ContentBlock 类型到协议格式的编码规则 |
|
||
| 停止原因 | §4.7 StopReason 到协议原生值的映射表 |
|
||
| Token 用量 | CanonicalUsage 到协议 usage 字段的编码规则 |
|
||
| 推理内容 | ThinkingBlock 的编码规则 |
|
||
| 降级处理 | Canonical 中协议不支持的 stop_reason 等值的降级映射 |
|
||
|
||
### D.6 核心层 — 流式编解码
|
||
|
||
对照 §4.8 CanonicalStreamEvent 和 §7.2 StreamDecoderState 确认映射和状态设计。
|
||
|
||
#### StreamDecoder(协议 SSE → Canonical 事件)
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| SSE 格式 | 协议的 SSE 事件格式(named events vs delta chunks) |
|
||
| 事件映射表 | 每种协议 SSE 事件 → §4.8 CanonicalStreamEvent 的映射规则 |
|
||
| 状态机设计 | 在 §7.2 通用状态基础上,声明本协议需要的额外状态 |
|
||
| UTF-8 安全 | 是否需要处理跨 chunk 的 UTF-8 截断 |
|
||
| 特殊情况 | 工具调用参数乱序、无限空白检测、延迟字段等 |
|
||
|
||
#### StreamEncoder(Canonical 事件 → 协议 SSE)
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 事件映射表 | §4.8 每个 CanonicalStreamEvent → 协议 SSE chunk 的映射规则 |
|
||
| 缓冲策略 | 哪些事件需要缓冲、何时输出 |
|
||
| SSE 格式 | event type / data 字段的编码方式 |
|
||
| 结束标记 | 如何输出流结束信号(如 `[DONE]`) |
|
||
|
||
### D.7 扩展层接口
|
||
|
||
对每种扩展层接口逐项确认。当前扩展层 InterfaceType 见 §5.3 枚举(MODELS、MODEL_INFO、EMBEDDINGS、RERANK),随协议扩展可能增加。
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口是否存在 | 该协议是否原生支持此接口 |
|
||
| URL 路径 | 接口的 URL 路径 |
|
||
| 请求格式 | 对照 §4.9 扩展层 Canonical Models,确认请求体映射 |
|
||
| 响应格式 | 对照 §4.9 扩展层 Canonical Models,确认响应体映射 |
|
||
| 不支持时策略 | 透传 / 返回空响应 / 返回错误 |
|
||
|
||
### D.8 错误编码
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 错误响应格式 | 协议的错误响应 JSON 结构 |
|
||
| encodeError | §9.1 ConversionError → 协议错误格式的编码规则 |
|
||
| HTTP 状态码 | 协议常用的错误状态码映射 |
|
||
|
||
### D.9 自检清单
|
||
|
||
| 章节 | 检查项 |
|
||
|------|--------|
|
||
| D.2 | [ ] `detectInterfaceType(nativePath)` 已实现,所有已知路径已覆盖 |
|
||
| D.2 | [ ] 所有 InterfaceType 的 `supportsInterface` 返回值已确定 |
|
||
| D.2 | [ ] 所有 InterfaceType 的 `buildUrl` 映射已确定 |
|
||
| D.3 | [ ] `buildHeaders(provider)` 已实现,adapter_config 契约已文档化 |
|
||
| D.4 | [ ] Chat 请求的 Decoder 和 Encoder 已实现(逐字段对照 §4.1/§4.2) |
|
||
| D.4 | [ ] 角色映射和消息顺序约束已处理 |
|
||
| D.4 | [ ] 工具调用(tool_use / tool_result)的编解码已处理 |
|
||
| D.4 | [ ] 协议特有字段已识别并确定处理方式(忽略/降级) |
|
||
| D.5 | [ ] Chat 响应的 Decoder 和 Encoder 已实现(逐字段对照 §4.7) |
|
||
| D.5 | [ ] stop_reason 映射表已确认 |
|
||
| D.5 | [ ] usage 字段映射已确认 |
|
||
| D.6 | [ ] 流式 StreamDecoder 和 StreamEncoder 已实现(对照 §4.8) |
|
||
| D.7 | [ ] 扩展层接口的编解码已实现(支持的接口) |
|
||
| D.8 | [ ] `encodeError` 已实现 |
|
||
|
||
---
|
||
|
||
## 附录 E:协议适配文档索引
|
||
|
||
| 协议 | 适配文档 |
|
||
|------|---------|
|
||
| OpenAI | [conversion_openai.md](./conversion_openai.md) |
|
||
| Anthropic | [conversion_anthropic.md](./conversion_anthropic.md) |
|
||
|
||
新增协议时,按附录 D 模板编撰适配文档,并将链接添加到上表。
|