fix: 修复 Gemini 和 OpenAI Chat Completions 流式响应的 usage 解析问题#153
fix: 修复 Gemini 和 OpenAI Chat Completions 流式响应的 usage 解析问题#153ding113 merged 9 commits intoding113:devfrom
Conversation
feat(errors): 扩展不可重试的客户端错误定义和模式
## 问题描述
当用户使用 Claude CLI 请求 /v1/messages 端点(Claude Messages API 格式),
但请求的模型不是 claude-* 开头(如 gemini-3-pro-preview)时,系统会错误
地选择 provider_type: "codex" 的供应商,导致请求格式与供应商类型不匹配。
根本原因:系统根据**模型名称**(是否以 `claude-` 开头)判断目标供应商类型,
而不是根据**请求格式**(session.originalFormat)。
## 修复方案
### 1. 新增格式兼容性检查函数
- 新增 `checkFormatProviderTypeCompatibility()` 辅助函数
- 根据 ClientFormat 和 ProviderType 判断兼容性
- 映射关系:
* claude → claude | claude-auth
* response → codex
* openai → openai-compatible
* gemini → gemini
* gemini-cli → gemini-cli
### 2. 修改供应商筛选逻辑
- 在模型匹配检查**之前**增加格式类型匹配检查(Step 1b)
- 只选择与请求格式兼容的供应商类型
- 向后兼容:如果 session.originalFormat 未设置,跳过此检查
### 3. 优化 targetType 计算
- 将 decisionContext.targetType 的判断从基于模型名称改为基于 session.originalFormat
- 修复:不再使用 `requestedModel.startsWith("claude-")` 判断
### 4. 增加过滤原因
- 新增 `format_type_mismatch` 过滤原因
- 记录格式不兼容的供应商及详细原因
### 5. 扩展类型定义
- 扩展 decisionContext.targetType 支持所有供应商类型
- 更新过滤原因枚举
## 影响范围
- ✅ 修复了 Claude 格式请求非 Claude 模型时的格式错配问题
- ✅ 支持 claude 类型供应商通过 allowedModels 或 model_redirects 处理非 Claude 模型
- ✅ 向后兼容:不影响现有的正常请求
## 测试验证
- ✅ TypeScript 类型检查通过
- ✅ 代码格式化通过
- ✅ 数据库查询验证:ccr 供应商(provider_type: "claude")成功处理 gemini-3-pro-preview 请求
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## 问题描述
Gemini 流式响应 (`:streamGenerateContent`) 和 OpenAI Chat Completions
流式响应返回的 SSE 格式只有 `data:` 行,没有 `event:` 前缀,导致
`parseSSEData()` 函数无法解析 `usageMetadata`/`usage` 字段,从而无法计费。
**影响范围**:
- ❌ Gemini API (`/v1beta/models/xxx:streamGenerateContent`) - 无计费
- ❌ OpenAI Chat Completions (`/v1/chat/completions`) 流式 - usage 可能丢失
- ✅ Claude Messages API (`/v1/messages`) - 正常(有 `event:` 前缀)
- ✅ OpenAI Response API (`/v1/responses`) - 正常(有 `event:` 前缀)
## 根本原因
1. **SSE 格式差异**:
- Claude/Response API: `event: xxx\ndata: {...}` (标准 SSE)
- Gemini/Chat Completions: `data: {...}` (简化格式,无 event 行)
2. **原有逻辑缺陷**:
- `parseSSEData()` 要求必须有 `eventName`,否则直接跳过
- `response-handler.ts` 只在检测到 `includes("event:")` 时才解析 SSE
- 导致 Gemini 和 Chat Completions 的流式响应无法提取 usage
## 修复方案
### 1. `src/lib/utils/sse.ts`
- 移除 `!eventName` 的检查条件
- 使用默认事件名 `"message"` 替代空 eventName(符合 SSE 规范)
- 支持没有 `event:` 前缀的纯 `data:` 格式
### 2. `src/app/v1/_lib/proxy/response-handler.ts`
- 将触发条件从 `includes("event:")` 改为 `includes("data:")`
- 扩大 SSE 解析范围,支持所有格式
## 技术细节
**SSE 规范兼容性**:
- SSE 标准允许省略 `event:` 行,默认事件名为 `"message"`
- 修改后的代码符合 W3C SSE 规范
**向后兼容性**:
- ✅ Claude Messages API (有 event:) - 继续正常工作
- ✅ OpenAI Response API (有 event:) - 继续正常工作
- ✅ Gemini API (无 event:) - 修复成功,现在能计费
- ✅ OpenAI Chat Completions (无 event:) - 改进 usage 提取
**风险评估** (Codex Review):
- ✅ 无向后兼容性问题
- ✅ 误匹配风险很小(后续有类型检查)
- ✅ 性能影响可忽略
- ✅ 边界情况处理正确
## 测试验证
- ✅ TypeScript 编译通过
- ✅ 代码格式化完成
- ✅ Gemini 流式响应计费验证通过
- ✅ Codex 代码审查通过
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Summary of ChangesHello @sususu98, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 此拉取请求主要旨在解决 Gemini 和 OpenAI Chat Completions 流式响应中 Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
本次 PR 很好地解决了 Gemini 和 OpenAI 流式响应中因缺少 event: 前缀导致的 usage 解析失败问题。修改 sse.ts 以兼容无 event: 的 SSE 格式,并调整 response-handler.ts 的触发条件,从根源上修复了计费统计的 bug,并且符合 SSE 规范。
此外,相关的 provider-selector.ts 和类型定义文件的修改,虽然超出了 bug 修复的范畴,但很好地增强了系统的可扩展性,为支持更多类型的供应商(如 Gemini)打下了坚实的基础。这些架构上的改进是值得称赞的。
代码整体质量很高,考虑了向后兼容和各种边界情况。我只有一个关于代码简化的建议。
问题描述
Gemini 流式响应 (
:streamGenerateContent) 和 OpenAI Chat Completions 流式响应返回的 SSE 格式只有data:行,没有event:前缀,导致parseSSEData()函数无法解析usageMetadata/usage字段,从而无法计费。影响范围
/v1beta/models/xxx:streamGenerateContent) - 无计费/v1/chat/completions) 流式 - usage 可能丢失/v1/messages) - 正常(有event:前缀)/v1/responses) - 正常(有event:前缀)根本原因
SSE 格式差异:
event: xxx\ndata: {...}(标准 SSE 格式)data: {...}(简化格式,无 event 行)原有逻辑缺陷:
parseSSEData()要求必须有eventName,否则直接跳过解析response-handler.ts只在检测到includes("event:")时才解析 SSE修复方案
1.
src/lib/utils/sse.tsconst flushEvent = () => { - if (!eventName || dataLines.length === 0) { + // 修改:支持没有 event: 前缀的纯 data: 格式(Gemini 流式响应) + // 如果没有 eventName,使用默认值 "message" + if (dataLines.length === 0) { return; } try { const data = JSON.parse(dataStr); - events.push({ event: eventName, data }); + events.push({ event: eventName || "message", data }); } catch { - events.push({ event: eventName, data: dataStr }); + events.push({ event: eventName || "message", data: dataStr }); } };改动说明:
!eventName的检查条件"message"替代空 eventName(符合 SSE 规范)event:前缀的纯data:格式2.
src/app/v1/_lib/proxy/response-handler.ts改动说明:
includes("event:")改为includes("data:")技术细节
SSE 规范兼容性
根据 W3C SSE 规范:
event:行"message"向后兼容性
✅ 完全兼容,所有现有格式继续正常工作:
event: xxx\ndata: {...}event: xxx\ndata: {...}data: {...}data: {...}风险评估 (Codex Review)
已通过 Codex MCP 代码审查,评估结果:
typeof event.data === "object"类型检查data: [DONE]等文本会被正确忽略详细 Review 结果:
测试验证
bun run typecheck)bun run format)生产环境验证
部署后测试结果:
Checklist
🤖 Generated with Claude Code