Skip to content

fix: realtime 转写会话按上游 usage 计费(completed 事件) - #6033

Open
feitianbubu wants to merge 2 commits into
QuantumNous:mainfrom
feitianbubu:pr/bb1132fc3
Open

fix: realtime 转写会话按上游 usage 计费(completed 事件)#6033
feitianbubu wants to merge 2 commits into
QuantumNous:mainfrom
feitianbubu:pr/bb1132fc3

Conversation

@feitianbubu

@feitianbubu feitianbubu commented Jul 9, 2026

Copy link
Copy Markdown
Member

📝 变更描述 / Description

OAI Realtime API conversation.item.input_audio_transcription.completed 自带 usage时, 网关仍然使用本地估算影响计费准确度

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix)
  • ✨ 新功能 (New feature)
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 Issues 与 PRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 `Bug fix`,我已提交或关联对应 Issue。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

本地测试 10 秒语音:

  • 修复前:本地估算;
image

tokenwd估算错误

  • 修复后, 以上游usage为准:
image 计费准确, 符合官方10token/秒 的估算

Summary by CodeRabbit

  • New Features

    • Added accurate billing for realtime audio transcription usage.
    • Realtime transcription usage is tracked separately from primary model usage.
    • Added support for transcription usage data in completed realtime events.
    • Improved quota and usage settlement for sessions containing transcription-only activity.
  • Bug Fixes

    • Prevented transcription usage from being double-counted.
    • Ensured transcription usage contributes to statistics without inflating settlement totals.
    • Added quota saturation handling for realtime transcription billing.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds official top-level realtime transcription usage data, thread-safe transcription billing state, direct transcription quota consumption, and websocket settlement logic that keeps transcription usage out of main settlement while retaining it in statistics.

Changes

Realtime Transcription Usage Billing

Layer / File(s) Summary
Usage contract and billing state
dto/realtime.go, relay/common/realtime_transcription.go, relay/common/relay_info.go, relay/common/*_test.go
Adds the transcription completion event constant, top-level event usage, and synchronized model/quota state with saturation tests.
OpenAI realtime processing
relay/channel/openai/relay_realtime.go, relay/channel/openai/relay_realtime_test.go
Captures transcription models, aggregates and normalizes official usage, bills completed events immediately, and tests model selection, billability, and non-mutation behavior.
Websocket quota settlement
service/quota.go, service/quota_realtime_test.go
Returns quota ratio snapshots, records transcription consumption, and separates transcription statistics from main settlement quota with overflow handling tests.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant OpenAIRealtime
  participant OpenaiRealtimeHandler
  participant RelayInfo
  participant PreWssConsumeQuota
  participant ConsumeLog
  OpenAIRealtime->>OpenaiRealtimeHandler: transcription completed event with Usage
  OpenaiRealtimeHandler->>RelayInfo: read transcription model
  OpenaiRealtimeHandler->>PreWssConsumeQuota: pre-consume normalized usage
  PreWssConsumeQuota-->>OpenaiRealtimeHandler: WssQuotaResult
  OpenaiRealtimeHandler->>RelayInfo: add transcription quota
  OpenaiRealtimeHandler->>ConsumeLog: record transcription consumption
Loading

Possibly related PRs

Suggested reviewers: calcium-ion

Poem

A rabbit hears the usage chime,
Counts each token right on time.
Quotas hop from stream to log,
Statistics join the fog.
No double bill, the ledger’s bright—
Carrots settled just right! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: billing realtime transcription completed events using upstream usage instead of local estimates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

JacksonsY added a commit to JacksonsY/new-api that referenced this pull request Jul 19, 2026
移植自上游 PR QuantumNous#6033。GA 版 input_audio_transcription 会话没有
response.done 事件,转写模型的官方 usage 附在
conversation.item.input_audio_transcription.completed 事件上,
本地 handler 不处理该事件导致漏计费。补:dto 加常量与
RealtimeEvent.Usage 字段;handler 加 completed 分支,与 response.done
同口径累加官方 usage 并预扣,明细缺失时输出全按文本补记
(计费公式只认明细字段);whisper 按时长返回(token 全 0)不走此
分支,仍用本地估算兜底;官方 usage 入账后清本地估算防重复。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
JacksonsY added a commit to JacksonsY/new-api that referenced this pull request Jul 19, 2026
怀疑性重审(对照 OpenAI 官方 SDK realtime 类型)发现 a86dea5 引入
P1 过度计费:纯 GA 转写会话(intent=transcription)没有 response.done,
localUsage 无任何清空点,每条 input_audio_buffer.append 按估算累积
(≈27.8 token/秒),会话结束时(:241-243)整段再扣一次——与
completed 事件的官方 usage 双重计费,合计可达官方数倍。

a86dea5 当初为堵"混合会话漏计对话输入"改成不清 localUsage,但该
漏计窗口极小(混合会话每个 response.done 本就结算并清 localUsage),
用系统性多收换微小少算是坏交易。恢复上游 PR QuantumNous#6033 的"两个都清":
官方 usage 权威、本地估算作废。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@relay/common/realtime_transcription.go`:
- Around line 20-27: Eliminate the unsynchronized lazy initialization in
RelayInfo.InitRealtimeTranscriptionState by eagerly assigning a
RealtimeTranscriptionState when RelayInfo is created or before concurrent
goroutines start. Then remove redundant nil-check initialization from
SetRealtimeTranscriptionModel and AddRealtimeTranscriptionQuota, ensuring all
concurrent callers use the single initialized state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ae31ec08-89ff-4036-8151-5a2c95107d47

📥 Commits

Reviewing files that changed from the base of the PR and between dc1d814 and 9b835ac.

📒 Files selected for processing (7)
  • relay/channel/openai/relay_realtime.go
  • relay/channel/openai/relay_realtime_test.go
  • relay/common/realtime_transcription.go
  • relay/common/realtime_transcription_test.go
  • relay/common/relay_info.go
  • service/quota.go
  • service/quota_realtime_test.go

Comment on lines +20 to +27
func (info *RelayInfo) InitRealtimeTranscriptionState() {
if info == nil {
return
}
if info.RealtimeTranscription == nil {
info.RealtimeTranscription = &RealtimeTranscriptionState{}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Data race on lazy initialization of RealtimeTranscription.

Because RealtimeTranscriptionState contains a mutex, it expects concurrent updates (e.g., from upstream and client websocket goroutines). However, this lazy initialization performs a check-then-act on info.RealtimeTranscription without synchronization. Concurrent calls will cause a TOCTOU data race, leading to multiple allocations, lost state updates, and corrupted mutexes.

Please consider one of these fixes:

  • Preferred: Eagerly initialize info.RealtimeTranscription = &RealtimeTranscriptionState{} when RelayInfo is instantiated (or before spawning concurrent goroutines) and remove the lazy initialization checks from SetRealtimeTranscriptionModel and AddRealtimeTranscriptionQuota.
  • Alternative: Protect this initialization using an atomic pointer operation (like atomic.CompareAndSwapPointer) or by adding a sync.Mutex to RelayInfo specifically for pointer initializations.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@relay/common/realtime_transcription.go` around lines 20 - 27, Eliminate the
unsynchronized lazy initialization in RelayInfo.InitRealtimeTranscriptionState
by eagerly assigning a RealtimeTranscriptionState when RelayInfo is created or
before concurrent goroutines start. Then remove redundant nil-check
initialization from SetRealtimeTranscriptionModel and
AddRealtimeTranscriptionQuota, ensuring all concurrent callers use the single
initialized state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants