fix: realtime 转写会话按上游 usage 计费(completed 事件) - #6033
Conversation
WalkthroughAdds 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. ChangesRealtime Transcription Usage Billing
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
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
移植自上游 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>
怀疑性重审(对照 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>
There was a problem hiding this comment.
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
📒 Files selected for processing (7)
relay/channel/openai/relay_realtime.gorelay/channel/openai/relay_realtime_test.gorelay/common/realtime_transcription.gorelay/common/realtime_transcription_test.gorelay/common/relay_info.goservice/quota.goservice/quota_realtime_test.go
| func (info *RelayInfo) InitRealtimeTranscriptionState() { | ||
| if info == nil { | ||
| return | ||
| } | ||
| if info.RealtimeTranscription == nil { | ||
| info.RealtimeTranscription = &RealtimeTranscriptionState{} | ||
| } | ||
| } |
There was a problem hiding this comment.
🩺 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{}whenRelayInfois instantiated (or before spawning concurrent goroutines) and remove the lazy initialization checks fromSetRealtimeTranscriptionModelandAddRealtimeTranscriptionQuota. - Alternative: Protect this initialization using an atomic pointer operation (like
atomic.CompareAndSwapPointer) or by adding async.MutextoRelayInfospecifically 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.
📝 变更描述 / Description
OAI Realtime API
conversation.item.input_audio_transcription.completed自带 usage时, 网关仍然使用本地估算影响计费准确度🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
📸 运行证明 / Proof of Work
本地测试 10 秒语音:
tokenwd估算错误
Summary by CodeRabbit
New Features
Bug Fixes