主要更新内容: - 新增三车道数据流模型(透传/智能透传/完整转换) - 补充 ProtocolAdapter 智能透传相关 4 个方法 - 更新 InterfaceType 枚举(移除 AUDIO/IMAGES,增加 PASSTHROUGH) - 新增 HTTPRequestSpec/HTTPResponseSpec 类型定义 - 更新引擎方法签名(增加 modelOverride 和 interfaceType 参数) - 明确接口类型分发策略和中间件应用范围 - 新增三种流式转换器变体 - 重写错误处理策略为分层宽容策略 - 标记多模态和扩展点为 Deferred - 更新附录 B 接口速查和附录 D 协议适配清单
1434 lines
66 KiB
Markdown
1434 lines
66 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 请求处理流程
|
||
|
||
#### 2.3.1 三车道数据流模型
|
||
|
||
引擎根据协议匹配情况和参数条件,选择三条数据流通道之一:
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────────────────────┐
|
||
│ 数据流三车道模型 │
|
||
├────────────────┬─────────────────────────┬────────────────────────────────────────┤
|
||
│ 透传车道 │ 智能透传车道 │ 完整转换车道 │
|
||
├────────────────┼─────────────────────────┼────────────────────────────────────────┤
|
||
│ 触发条件 │ 同协议 │ 同协议 + 接口∈{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字段改写) │ 高(完整序列化) │
|
||
└────────────────┴─────────────────────────┴────────────────────────────────────────┘
|
||
```
|
||
|
||
**智能透传的设计动机**:同协议场景下,若仅需改写 `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 原样转发。
|
||
|
||
---
|
||
|
||
## 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: ... } // 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
|
||
|
||
```
|
||
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): (body, statusCode)
|
||
|
||
// 扩展层
|
||
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
|
||
|
||
// 智能透传支持
|
||
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, PASSTHROUGH
|
||
>
|
||
```
|
||
|
||
| 类型 | 说明 |
|
||
|------|------|
|
||
| CHAT | 核心对话接口(各协议的 Chat/Messages 接口) |
|
||
| MODELS | 模型列表接口 |
|
||
| MODEL_INFO | 模型详情接口 |
|
||
| EMBEDDINGS | 向量嵌入接口 |
|
||
| RERANK | 重排序接口 |
|
||
| PASSTHROUGH | 未知接口,透传处理 |
|
||
|
||
### 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)。
|
||
|
||
#### 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): HTTPRequestSpec
|
||
convertHttpResponse(response, clientProtocol, providerProtocol, interfaceType, modelOverride): HTTPResponseSpec
|
||
createStreamConverter(clientProtocol, providerProtocol, modelOverride, interfaceType): StreamConverter
|
||
|
||
// 辅助方法
|
||
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:
|
||
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 // 透传层:原样转发
|
||
```
|
||
|
||
#### 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 {
|
||
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)
|
||
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():
|
||
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 进行变换。
|
||
|
||
```
|
||
interface ConversionMiddleware {
|
||
intercept(canonical, clientProtocol, providerProtocol, context): canonical | error
|
||
interceptStreamEvent(event, clientProtocol, providerProtocol, context): event | error
|
||
}
|
||
|
||
ConversionContext {
|
||
conversionId: String // 唯一转换 ID(UUID)
|
||
interfaceType: InterfaceType
|
||
timestamp: DateTime
|
||
metadata: Map<String, Any>
|
||
}
|
||
```
|
||
|
||
#### 6.4.1 中间件执行规则
|
||
|
||
- `intercept` 返回修改后的 canonical,或返回 ConversionError 以**中断转换**
|
||
- `interceptStreamEvent` 返回修改后的 event,或返回 error
|
||
- 多个 Middleware 按注册顺序链式执行,任一中断则后续不再执行
|
||
|
||
#### 6.4.2 错误处理差异
|
||
|
||
| 场景 | 错误处理策略 |
|
||
|------|-------------|
|
||
| 请求中间件 `intercept` 返回 error | **严格模式**:中断整个转换,返回错误 |
|
||
| 流式中间件 `interceptStreamEvent` 返回 error | **宽容模式**:跳过该事件,继续处理后续事件 |
|
||
|
||
**设计动机**:流式场景下,单个事件的错误不应中断整个流。请求场景下,错误请求应被明确拒绝。
|
||
|
||
### 6.5 使用示例
|
||
|
||
```
|
||
engine = new ConversionEngine()
|
||
engine.registerAdapter(new OpenAIAdapter())
|
||
engine.registerAdapter(new AnthropicAdapter())
|
||
|
||
// 场景1: 跨协议 Chat 转换
|
||
// 入站: /openai/v1/chat/completions
|
||
provider = TargetProvider {
|
||
base_url: "https://api.anthropic.com",
|
||
api_key: "xxx",
|
||
model_name: "claude-3-opus",
|
||
adapter_config: { anthropic_version: "2023-06-01" }
|
||
}
|
||
out = engine.convertHttpRequest(inRequest, "openai", "anthropic", provider)
|
||
// 出站: /v1/messages + Anthropic headers + 转换后的 body
|
||
|
||
// 场景2: /models 跨协议
|
||
out = engine.convertHttpRequest(inRequest, "openai", "anthropic", provider)
|
||
// URL: /v1/models, headers 按 Anthropic 格式重建
|
||
|
||
// 场景3: 同协议透传
|
||
out = engine.convertHttpRequest(inRequest, "openai", "openai", provider)
|
||
// client == provider → 透传车道:重建 headers 后原样转发
|
||
|
||
// 场景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 字段
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 流式转换架构
|
||
|
||
### 7.1 转换管道
|
||
|
||
```
|
||
上游 SSE 流
|
||
│
|
||
├─ 同协议 + 无 modelOverride: PassthroughStreamConverter
|
||
│ chunk → [chunk]
|
||
│
|
||
├─ 同协议 + 有 modelOverride: SmartPassthroughStreamConverter
|
||
│ chunk → [rewriteResponseModelName(chunk)]
|
||
│
|
||
└─ 不同协议: CanonicalStreamConverter
|
||
StreamDecoder StreamEncoder
|
||
┌───────────┐ ┌───────────┐
|
||
│ SSE Parser│ │SSE Writer │
|
||
└─────┬─────┘ └─────▲─────┘
|
||
│ │
|
||
┌─────▼─────┐ CanonicalEvent[] ┌─────┴─────┐
|
||
│ Event │──────────────────────▶│ Event │
|
||
│ Translator │ ┌──────────┐ │ Translator │
|
||
│ (状态机) │ │Middleware│ │ │
|
||
└───────────┘ │(宽容模式) │ └───────────┘
|
||
└──────────┘
|
||
│
|
||
┌─────▼─────┐
|
||
│modelOverride│
|
||
│(覆写 model) │
|
||
└───────────┘
|
||
```
|
||
|
||
**流式中间件错误处理**:`interceptStreamEvent` 返回 error 时,跳过该事件继续处理后续事件(宽容模式),而非中断整个流。
|
||
|
||
### 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 多模态扩展
|
||
|
||
**状态**:Deferred(未实现)
|
||
|
||
Canonical Model 已预留 ImageBlock / AudioBlock / VideoBlock / FileBlock。实现路径:
|
||
1. 在各 ProtocolAdapter 中实现多模态块的编解码
|
||
2. 在 StreamDecoder/StreamEncoder 中处理多模态增量数据
|
||
|
||
### 8.3 有状态特性扩展
|
||
|
||
**状态**:Deferred(未实现)
|
||
|
||
```
|
||
interface StatefulMiddleware extends ConversionMiddleware {
|
||
stateStore: SessionStateStore
|
||
}
|
||
```
|
||
|
||
适用场景:跨轮次保留协议特有的状态(如 thinking signature)。
|
||
|
||
### 8.4 特性降级策略
|
||
|
||
当 Canonical 公共字段在目标协议中无直接等价物时:
|
||
- 有语义等价物 → 自动映射
|
||
- 无等价物 → 丢弃,日志 warn
|
||
- 有替代方案 → 降级策略处理(如注入合成工具实现 JSON 模式)
|
||
|
||
各协议的具体降级规则详见各自的协议适配文档(附录 E)。
|
||
|
||
### 8.5 自定义接口支持
|
||
|
||
**状态**:Deferred(未实现)
|
||
|
||
```
|
||
interface CustomInterfaceHandler {
|
||
interfaceType(): InterfaceType
|
||
matchUrl(url): Boolean
|
||
convertRequest(client, provider, raw): raw
|
||
convertResponse(client, provider, raw): raw
|
||
}
|
||
engine.registerCustomHandler(handler)
|
||
```
|
||
|
||
当前实现中,未知接口直接走 PASSTHROUGH 透传。
|
||
|
||
---
|
||
|
||
## 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 错误处理策略
|
||
|
||
引擎采用**分层宽容策略**,根据接口层级和场景选择不同的错误处理方式:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────┐
|
||
│ 错误处理分层策略 │
|
||
├─────────────────┬───────────────────────────────────────────────────┤
|
||
│ 核心层(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`):
|
||
|
||
| 策略 | 适用场景 | 实现 |
|
||
|------|---------|------|
|
||
| 透传 | 上游可能有自己的实现 | URL+Header 适配后 Body 原样转发 |
|
||
| 返回空响应 | 不影响核心功能 | 返回空列表 `{data: []}` |
|
||
| 返回错误 | 客户端明确需要此功能 | 返回 501 或协议格式错误 |
|
||
|
||
具体策略由 `supportsInterface` 返回值决定:返回 false 时引擎直接透传 body。
|
||
|
||
### 9.3 错误响应格式
|
||
|
||
转换失败时,错误响应用**客户端协议(client protocol)**的格式编码。由 `clientAdapter.encodeError(error)` 完成。各协议的错误响应 JSON 结构和 HTTP 状态码映射详见各自的协议适配文档(附录 E)。
|
||
|
||
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:模块依赖
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────┐
|
||
│ ConversionEngine │
|
||
│ 门面:HTTP 转换 / 透传判断 / 流式转换 │
|
||
│ 无状态;协议识别见 §2.2 │
|
||
├──────────────────────────────────────────────────┤
|
||
│ HTTPRequestSpec / HTTPResponseSpec │
|
||
│ url, method, headers, body / statusCode, ... │
|
||
├──────────────────────────────────────────────────┤
|
||
│ TargetProvider │
|
||
│ base_url / api_key / model_name / adapter_config │
|
||
├──────────────────┬───────────────────────────────┤
|
||
│ AdapterRegistry │ MiddlewareChain │
|
||
├──────────────────┴───────────────────────────────┤
|
||
│ StreamConverter: Passthrough | SmartPassthrough | Canonical │
|
||
├──────────────────────────────────────────────────┤
|
||
│ ProtocolAdapter: 各协议实现 │
|
||
│ · buildHeaders(provider) · URL 映射 │
|
||
│ · Chat/Models/ModelInfo/Embeddings/Rerank/... 编解码 │
|
||
│ · encodeError · StreamDecoder / StreamEncoder │
|
||
│ · rewriteRequestModelName / rewriteResponseModelName (智能透传) │
|
||
├──────────────────────────────────────────────────┤
|
||
│ Canonical Model (Core + Extended) │
|
||
├──────────────────────────────────────────────────┤
|
||
│ Error Handling (分层宽容策略) │
|
||
├──────────────────────────────────────────────────┤
|
||
│ Utility: UTF-8 Buffer / SSE Parser / Detector │
|
||
└──────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 附录 B:接口速查
|
||
|
||
```
|
||
// ─── 核心入口 ───
|
||
ConversionEngine
|
||
.registerAdapter(adapter): void
|
||
.use(middleware): void
|
||
.getRegistry(): AdapterRegistry
|
||
.isPassthrough(clientProtocol, providerProtocol): Boolean
|
||
.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, api_key, model_name, adapter_config }
|
||
|
||
// ─── 接口类型 ───
|
||
InterfaceType = CHAT | MODELS | MODEL_INFO | EMBEDDINGS | RERANK | PASSTHROUGH
|
||
|
||
// ─── 协议适配器 ───
|
||
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): (body, statusCode)
|
||
// 扩展层
|
||
.decodeModelsResponse / .encodeModelsResponse
|
||
.decodeModelInfoResponse / .encodeModelInfoResponse
|
||
.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]
|
||
├─ SmartPassthroughStreamConverter [raw] → [rewrite(raw)]
|
||
└─ CanonicalStreamConverter decode → middleware → modelOverride → encode
|
||
|
||
// ─── 中间件 ───
|
||
ConversionMiddleware
|
||
.intercept(req, clientProtocol, providerProtocol, ctx): (req, error)
|
||
.interceptStreamEvent(event, clientProtocol, providerProtocol, ctx): (event, error)
|
||
|
||
ConversionContext { conversionId, interfaceType, timestamp, metadata }
|
||
```
|
||
|
||
---
|
||
|
||
## 附录 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` 已实现 |
|
||
| 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 |
|
||
|
||
---
|
||
|
||
## 附录 E:协议适配文档索引
|
||
|
||
| 协议 | 适配文档 |
|
||
|------|---------|
|
||
| OpenAI | [conversion_openai.md](./conversion_openai.md) |
|
||
| Anthropic | [conversion_anthropic.md](./conversion_anthropic.md) |
|
||
|
||
新增协议时,按附录 D 模板编撰适配文档,并将链接添加到上表。
|