Skip to content

fix: openai 音频模型流模式未正确计费 - #2160

Merged
Calcium-Ion merged 1 commit into
mainfrom
fix-openai-audio-stream-billing
Nov 3, 2025
Merged

fix: openai 音频模型流模式未正确计费#2160
Calcium-Ion merged 1 commit into
mainfrom
fix-openai-audio-stream-billing

Conversation

@creamlike1024

@creamlike1024 creamlike1024 commented Nov 3, 2025

Copy link
Copy Markdown
Collaborator

fix #2108, fix #2110
使用 gpt-4o-audio-preview 测试发现 openai 音频模型流模式下包含有效 usage 的响应为倒数第二个
image

image

Summary by CodeRabbit

  • Bug Fixes
    • Improved usage data extraction for audio models during streaming operations.

@coderabbitai

coderabbitai Bot commented Nov 3, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds audio-model handling to OpenAI stream flow by introducing secondLastStreamData to capture intermediate stream data and isAudioModel flag to conditionally extract usage information from the preceding data item during streaming operations.

Changes

Cohort / File(s) Summary
Audio Model Streaming Logic
relay/channel/openai/relay-openai.go
Introduces secondLastStreamData variable and isAudioModel flag to handle usage extraction for audio models. During streaming, the previous data item is stored when streaming data arrives for audio models. After streaming completes, usage is unmarshalled from secondLastStreamData for audio models, with debug logging for tracing.

Sequence Diagram(s)

sequenceDiagram
    participant Upstream
    participant Relay as relay-openai.go
    participant Store as secondLastStreamData
    participant Response

    Note over Relay: isAudioModel = contains("audio")
    
    Upstream->>Relay: Stream data (item N)
    alt is Audio Model
        Relay->>Store: Save previous data
    end
    Relay->>Response: Process current data
    
    Upstream->>Relay: Stream complete
    alt is Audio Model & secondLastStreamData exists
        rect rgb(220, 240, 255)
            Note over Relay: Extract usage from secondLastStreamData
            Relay->>Relay: Unmarshal usage
            Relay->>Response: Set usage & containStreamUsage
        end
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

  • Focused change in a single file with straightforward conditional logic for audio models
  • Requires understanding the existing streaming flow and how secondLastStreamData fits into the usage extraction mechanism
  • Verify correct identification of audio models via model name string matching
  • Validate that the unmarshal operation on secondLastStreamData handles edge cases safely

Possibly related PRs

  • feat: use audio token usage if return #1721: Modifies audio model usage extraction in the same file; compares alternative approaches to handling usage in OpenAI audio streams (streamed usage via secondLastStreamData vs. upstream JSON usage in STT handler).

Poem

🐇 A rabbit's ode to audio streams

Two streams merge where echoes gleam,
secondLastStreamData threads a dream,
Audio models sing their song,
Usage flows where it belongs,
Hop, hop—through the streaming way! 🎧

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title is in Chinese and translates to 'fix: openai audio model stream mode not charging correctly', which directly relates to the main change in the pull request. The changeset specifically addresses audio model handling in the OpenAI stream flow by introducing logic to extract usage data from the penultimate (second-to-last) stream item for audio models, which aligns precisely with the title's focus on fixing billing/charging issues for audio models in streaming mode.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-openai-audio-stream-billing

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 and usage tips.

@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

🧹 Nitpick comments (1)
relay/channel/openai/relay-openai.go (1)

127-128: Consider more robust audio model detection.

The current substring matching could match models with "audio" in their name that don't exhibit this streaming behavior. While this works for known models like gpt-4o-audio-preview, consider maintaining an explicit list of audio models or using a more specific pattern if this causes issues in the future.

Example with explicit list:

-	// 检查是否为音频模型
-	isAudioModel := strings.Contains(strings.ToLower(model), "audio")
+	// 检查是否为音频模型
+	audioModels := []string{"gpt-4o-audio-preview", "gpt-4o-realtime-preview"}
+	isAudioModel := false
+	modelLower := strings.ToLower(model)
+	for _, am := range audioModels {
+		if strings.Contains(modelLower, am) {
+			isAudioModel = true
+			break
+		}
+	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 00782aa and ee4c046.

📒 Files selected for processing (1)
  • relay/channel/openai/relay-openai.go (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-21T06:31:11.073Z
Learnt from: jiajunly
Repo: QuantumNous/new-api PR: 1629
File: relay/channel/openai/relay-openai.go:170-174
Timestamp: 2025-08-21T06:31:11.073Z
Learning: In relay/channel/openai/relay-openai.go, the streaming logic for the AddThinkFirst feature is designed so that only the first chunk of a stream gets the "<think>\n" prefix. The final flush in the streaming handler intentionally uses addThink=false because the last chunk should never receive the prefix, even in single-chunk streams where the prefix would have been applied during normal processing.

Applied to files:

  • relay/channel/openai/relay-openai.go
🧬 Code graph analysis (1)
relay/channel/openai/relay-openai.go (4)
dto/openai_response.go (1)
  • Usage (222-235)
service/usage_helpr.go (1)
  • ValidUsage (28-30)
common/constants.go (1)
  • DebugEnabled (71-71)
logger/logger.go (1)
  • LogDebug (69-74)
🔇 Additional comments (2)
relay/channel/openai/relay-openai.go (2)

125-125: LGTM!

The variable declaration is clear and the comment explains its purpose for audio model billing.


138-141: LGTM!

The logic correctly captures the penultimate stream data by saving lastStreamData before it gets updated. This ensures that after streaming completes, secondLastStreamData contains the second-to-last event where usage information resides for audio models.

Comment on lines +149 to +165
// 对音频模型,从倒数第二个stream data中提取usage信息
if isAudioModel && secondLastStreamData != "" {
var streamResp struct {
Usage *dto.Usage `json:"usage"`
}
err := json.Unmarshal([]byte(secondLastStreamData), &streamResp)
if err == nil && streamResp.Usage != nil && service.ValidUsage(streamResp.Usage) {
usage = streamResp.Usage
containStreamUsage = true

if common.DebugEnabled {
logger.LogDebug(c, fmt.Sprintf("Audio model usage extracted from second last SSE: PromptTokens=%d, CompletionTokens=%d, TotalTokens=%d, InputTokens=%d, OutputTokens=%d",
usage.PromptTokens, usage.CompletionTokens, usage.TotalTokens,
usage.InputTokens, usage.OutputTokens))
}
}
}

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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify that handleLastResponse doesn't overwrite audio model usage.

The usage extraction logic is correct and properly validates the usage before setting containStreamUsage = true. However, handleLastResponse is called after this block (line 169), which could potentially modify usage or reset containStreamUsage, thereby overwriting the audio model usage you just extracted.

Run the following script to examine handleLastResponse and verify it won't overwrite the audio model usage:


🏁 Script executed:

#!/bin/bash
# Description: Find and examine handleLastResponse function to verify it doesn't unconditionally overwrite usage or containStreamUsage

# Find the handleLastResponse function definition
ast-grep --pattern 'func handleLastResponse($$$) $$$'

# Also search for any assignments to containStreamUsage within handleLastResponse
rg -n -A 10 -B 2 'func handleLastResponse' 

Length of output: 2840


handleLastResponse will overwrite audio model usage extracted from secondLastStreamData if the last stream also contains valid usage.

The audio model usage extraction at lines 149-165 is undermined because handleLastResponse (called at line 169) unconditionally overwrites usage and containStreamUsage when lastStreamData contains valid usage. This causes the second-to-last SSE extraction to be discarded if the final SSE response has usage data, defeating the purpose of the audio model special handling.

To fix this, handleLastResponse should only update usage for audio models if containStreamUsage is not already set (to preserve the secondLastStreamData extraction), or explicitly document that final response usage takes precedence for all models.

🤖 Prompt for AI Agents
In relay/channel/openai/relay-openai.go around lines 149-165, the code extracts
usage from secondLastStreamData for audio models but handleLastResponse (invoked
at line 169) later unconditionally overwrites usage; update the logic so
handleLastResponse does not replace usage for audio models when
containStreamUsage is already true (i.e., if isAudioModel && containStreamUsage
then skip updating usage), or alternatively add a parameter/flag to
handleLastResponse indicating stream-derived usage should be preserved and honor
that flag to avoid overwriting the extraction from secondLastStreamData.

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个usage,不管是openai还是azure

2 participants