1
0

fix: 修正 conversion 代理路径和错误边界

This commit is contained in:
2026-04-25 23:12:54 +08:00
parent f5c82b6980
commit 2c043c6cf7
25 changed files with 2020 additions and 214 deletions

View File

@@ -0,0 +1,100 @@
## Context
当前后端代理入口采用 `/{protocol}/*path``ProxyHandler` 将剥离协议前缀后的 path 作为 `nativePath` 传入 adapter。这个模型本身是合理的OpenAI adapter 应接收 `/chat/completions`Anthropic adapter 应接收 `/v1/messages`。需要修正的是文档中曾把版本路径抽象为网关统一规则,但实际上版本路径是否存在属于协议原生路径的一部分,应由协议 adapter 识别和映射。
流式链路当前由 `ProviderClient.SendStream``\n\n` 拆分 SSE 事件,并把 `data: [DONE]` 转成 Done 信号。这个设计适合跨协议 decoder但不适合同协议 raw passthrough透传路径会丢失 SSE frame 结尾空行Smart Passthrough 也无法对 `data: {...}` 中的 JSON 顶层 model 做可靠改写。
错误处理目前混合了协议错误、应用错误和上游错误。新的产品决策是:网关层错误使用应用统一 `{error, code}` 格式;只要上游已经返回 HTTP 响应,即使是非 2xx也保持透明代理语义直接透传上游 status、headers、body。
## Goals / Non-Goals
**Goals:**
- 明确网关只剥离协议前缀,不对剩余 nativePath 做版本号归一化。
- 明确不同协议的 nativePath 形态由协议自身决定OpenAI 为 `/chat/completions`Anthropic 为 `/v1/messages`
- 保持现有 `base_url` 约定OpenAI 配置到版本路径一级Anthropic 配置到域名级。
- 由 adapter 负责识别协议原生 nativePath并通过 BuildUrl 产出真实上游路径。
- 同协议无 model 改写的流式请求保持 raw SSE 字节透传。
- 同协议需要 model 改写的流式请求按 SSE frame 解析,只改写 `data` JSON 中的 model 字段,再重建 SSE frame。
- 网关层错误统一使用应用错误格式;上游非 2xx 响应透明透传。
- 只有 adapter 明确适配的接口才提取 model未知接口不做通用 model 猜测。
- 跨协议遇到多模态占位内容时明确返回不支持错误,避免静默丢内容。
**Non-Goals:**
- 不统一不同协议是否使用 `/v1` 版本路径。
- 不调整 OpenAI/Anthropic `base_url` 配置约定。
- 不调整统计口径和统计数据模型。
- 不实现多模态正式转换。
- 不引入新依赖。
## Decisions
### Decision 1: nativePath 保持协议原生路径
外部请求 `/{protocol}/{path}` 只剥离协议前缀,剩余 path 原样作为 client protocol 的 nativePath。OpenAI 请求 `/openai/chat/completions` 进入 OpenAI adapter 时为 `/chat/completions`Anthropic 请求 `/anthropic/v1/messages` 进入 Anthropic adapter 时为 `/v1/messages`
替代方案是由网关统一添加或移除 `/v1`。该方案会把版本路径从协议知识错误提升为网关通用规则,导致 Anthropic 和 OpenAI 的 base_url 约定被混淆。
### Decision 2: 对外路径由协议 adapter 的 nativePath 决定
网关不提供跨协议统一路径规范。OpenAI 对外路径不带 `/v1`,因为 OpenAI provider 的 `base_url` 已配置到版本路径一级Anthropic 对外路径保留 `/v1`,因为 Anthropic 协议原生路径包含版本段且 `base_url` 配置到域名级。
替代方案是把所有协议对外路径都改成不带 `/v1` 或都带 `/v1`。该方案不符合各协议原生路径,也会让 adapter 的 `DetectInterfaceType` 失去协议边界。
### Decision 3: 上游路径由 adapter.BuildUrl 产生
无论同协议透传、Smart Passthrough 还是跨协议转换,出站 URL 都使用 `provider.BaseURL + providerAdapter.BuildUrl(nativePath, interfaceType)`。OpenAI adapter 对 `/chat/completions` 返回 `/chat/completions`,配合 `base_url=http://xxx.com/v1` 得到 `http://xxx.com/v1/chat/completions`。Anthropic adapter 对 `/v1/messages` 返回 `/v1/messages`,配合 `base_url=http://xxx.com` 得到 `http://xxx.com/v1/messages`
替代方案是在 engine 中直接拼接 `provider.BaseURL + nativePath`。该方案当前对部分协议可工作,但绕过了 adapter 的 URL 映射职责,不利于后续协议扩展和特殊路径映射。
### Decision 4: 同协议流式分 raw passthrough 和 smart passthrough 两条路径
当 clientProtocol 等于 providerProtocol 且不需要响应 model 改写时handler/provider client 应直接将上游响应 body 的 SSE 字节写回客户端,不进入 `StreamConverter` 事件拆分链路。
当需要响应 model 改写时,保留 SSE frame 边界,解析每个 SSE frame 的 `data` 行:`[DONE]` 原样输出JSON payload 调用 adapter 的响应 model 改写逻辑后重新写为 SSE frame解析失败则按宽容策略输出原 frame。
替代方案是继续让 `ProviderClient.SendStream` 只暴露去掉 `\n\n` 的 rawEvent。该方案无法满足 raw passthrough 的字节语义,也无法可靠输出 `[DONE]`
### Decision 5: 网关层错误与上游错误分离
网关层错误包括路由失败、请求 JSON 错误、转换失败、上游连接失败、跨协议暂不支持能力等,统一返回 `{error, code}`。上游错误指已经收到上游 HTTP 响应的情况,非 2xx 响应不进入 conversion直接透传 status、过滤后的 headers 和 body。
替代方案是把所有代理错误都编码为客户端协议错误格式。该方案会隐藏上游原始错误,也与管理接口统一错误格式不一致。
### Decision 6: adapter 明确声明可提取 model 的接口边界
Chat、Embeddings、Rerank 等已适配接口由 adapter 的 `ExtractModelName` 明确解析 model。未知接口即使 body 中存在顶层 `model`,也不做假设性提取,按无 model 透传处理。
替代方案是在 handler 中统一尝试解析顶层 model。该方案会误判未来协议特有接口的模型字段语义破坏 adapter 对协议知识的封装。
### Decision 7: 多模态占位保留但跨协议拒绝
Canonical 中现有 image/audio/video/file 占位保留,后续多模态实现可继续扩展。同协议 Smart Passthrough 保留请求 JSON 语义,不检查多模态。跨协议完整转换检测到 image/audio/video/file 时返回 `UNSUPPORTED_MULTIMODAL` 网关错误。
替代方案是继续编码空占位或静默丢弃。该方案会制造数据丢失,调用方难以诊断。
## Risks / Trade-offs
- [Risk] 对外路径由协议决定,调用方需要分别记住 OpenAI 和 Anthropic 的路径形态。→ Mitigation: README、backend README 和设计文档明确列出每个协议的路径和 base_url 约定。
- [Risk] raw stream passthrough 可能需要调整 ProviderClient 接口,影响现有测试。→ Mitigation: 将非流式、跨协议流式、同协议 raw 流式分别建模,并补充 E2E 测试。
- [Risk] 上游错误透传可能让 OpenAI 客户端看到 Anthropic 错误体。→ Mitigation: 文档明确透明代理边界;只有网关自身错误保证统一格式。
- [Risk] SSE frame 级 model 改写需要处理 CRLF、多 data 行和 `[DONE]`。→ Mitigation: 实现轻量 SSE frame parser覆盖 LF/CRLF、多行 data、解析失败回退原 frame 的测试。
- [Risk] 跨协议多模态拒绝会短期限制能力。→ Mitigation: 保留 Canonical 占位和同协议透传,后续多模态 change 可在此基础上扩展。
## Migration Plan
1. 更新 OpenSpec 和设计文档明确协议原生路径、base_url、错误和流式边界。
2. 校正 adapter 路径识别和 `BuildUrl` 映射测试,确保 OpenAI 接收 `/chat/completions`、Anthropic 接收 `/v1/messages`
3. 修改 `ConversionEngine` 同协议 URL 构建,统一使用 `BuildUrl`,避免核心逻辑绕过 adapter。
4. 调整 ProviderClient/ProxyHandler 流式链路,支持 raw passthrough、SSE frame 级 Smart Passthrough 和非 2xx 透传。
5. 调整网关层错误输出,非 2xx 上游响应绕过 conversion。
6. 补齐单测、集成测试和 E2E 测试。
7. 更新 README、backend README 和 `docs/conversion_design.md`
由于应用尚未上线,不提供额外路径别名兼容和数据迁移。回滚策略是恢复旧流式 provider client 和错误处理行为。
## Open Questions
无。