Skip to content

修复异步视频输出 Token 计费 - #6173

Closed
zuiho-kai wants to merge 5 commits into
QuantumNous:mainfrom
zuiho-kai:agent/fix-video-output-token-billing
Closed

修复异步视频输出 Token 计费#6173
zuiho-kai wants to merge 5 commits into
QuantumNous:mainfrom
zuiho-kai:agent/fix-video-output-token-billing

Conversation

@zuiho-kai

@zuiho-kai zuiho-kai commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

本变更由 AI 辅助实现,提交者已结合实际异步视频 usage 与计费日志核对根因、结算公式和变更范围。

📝 变更描述 / Description

异步视频任务完成时,原逻辑只按 total_tokens * ModelRatio 重新结算。虽然任务适配器已解析 completion_tokens,结算层没有使用该字段,也没有应用 CompletionRatio,导致输出 Token 按输入价格计费。

本 PR 做了三点调整:

  • total_tokens - completion_tokens 计算输入 Token,输出 Token 应用 CompletionRatio
  • 上游未返回 completion_tokens 时继续按 total_tokens 使用原有计费逻辑;
  • 在任务提交时保存模型倍率、输出倍率和分组倍率快照,完成结算使用快照,避免任务运行期间修改配置影响既有任务。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

  • 人工确认: 我已核对生产计费日志、上游 usage 和最终公式,并确认描述与改动一致。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 已提交并关联 Issue 异步视频按 Token 结算时忽略输出倍率 #6172,Issue 中包含实际 usage、现行公式和复现步骤。
  • 变更理解: 已确认任务提交、计费快照、轮询解析、差额结算和历史回退的完整数据流。
  • 范围聚焦: 仅修改异步任务按 Token 计费及其回归测试。
  • 本地验证: 受本地 Windows 资源限制,按照项目部署约定在 GitHub Actions 运行目标 Go 测试并通过。
  • 安全合规: 代码和测试中不包含真实模型密钥、用户信息或其他敏感凭据。

📸 运行证明 / Proof of Work

GitHub Actions 已执行:

go test ./controller ./service ./relay/helper

目标测试任务通过,覆盖以下行为:

  • 按 Token 计费的异步任务保留提交预扣,完成后按实际用量补扣或退款;
  • 输入、输出 Token 分开计费并应用输出倍率;
  • 结算使用任务提交时的倍率快照;
  • 缺少 completion_tokens 时回退到原有 total_tokens 计费;
  • 按次价格任务仍跳过 Token 差额结算。

测试记录:https://github.com/zuiho-kai/new-api/actions/runs/29258682684/job/86845761821

Summary by CodeRabbit

  • New Features
    • Added optional completion ratio to task billing context and surfaced it in billing “Other” details when available.
    • Updated per-call pricing to include completion ratio data in pricing calculations.
  • Bug Fixes
    • Improved quota accuracy for completion-ratio–based billing and completion-token–aware quota recalculation.
    • Adjusted completion billing fallback to use both total and completion tokens when present.
  • Tests
    • Added unit tests covering completion-ratio inclusion, token billing semantics, and fallback behavior.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The change preserves CompletionRatio in task billing snapshots and applies it when recalculating asynchronous token-based quotas. Completion and total tokens are passed through polling settlement, with fallback behavior when completion tokens are unavailable.

Changes

Token billing settlement

Layer / File(s) Summary
Capture completion ratio in billing snapshots
model/task.go, relay/helper/price.go, relay/helper/price_test.go, controller/relay.go
Per-call pricing returns CompletionRatio, relay task creation stores it as an optional billing snapshot field, and unit coverage verifies the pricing data.
Apply completion-token billing during settlement
service/task_polling.go, service/task_billing.go, service/task_billing_test.go
Completion settlement passes both token counts, recalculates input and completion charges using the snapshot ratio, records the ratio in billing details, and tests snapshot and fallback behavior.

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

Possibly related PRs

  • QuantumNous/new-api#1281: Both modify the per-call pricing path in relay/helper/price.go and quota computation related to model pricing.

Suggested reviewers: calcium-ion

Sequence Diagram(s)

sequenceDiagram
  participant RelayTask
  participant ModelPriceHelperPerCall
  participant settleTaskBillingOnComplete
  participant RecalculateTaskQuotaByTokens
  participant RecalculateTaskQuota
  RelayTask->>ModelPriceHelperPerCall: obtain PriceData
  ModelPriceHelperPerCall-->>RelayTask: return ModelRatio and CompletionRatio
  RelayTask->>settleTaskBillingOnComplete: persist billing snapshot
  settleTaskBillingOnComplete->>RecalculateTaskQuotaByTokens: pass TotalTokens and CompletionTokens
  RecalculateTaskQuotaByTokens->>RecalculateTaskQuota: settle calculated quota
Loading

Poem

I’m a rabbit with ratios to spare,
Counting input and output with care.
Snapshots stay bright,
Tokens bill right,
And quotas hop neatly from there.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% 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
Linked Issues check ✅ Passed The changes add completion-token-aware billing, snapshot the completion ratio, and fall back to total_tokens when completion_tokens is absent, matching #6172.
Out of Scope Changes check ✅ Passed The diff stays focused on async video token billing, related snapshot storage, quota recalculation, polling, and regression tests.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title is concise and accurately reflects the main change: fixing async video token billing, especially output-token handling.
✨ 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.

@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.

🧹 Nitpick comments (1)
relay/helper/price_test.go (1)

301-304: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use assert for non-fatal value checks.

Per repo test guideline, setup/fatal checks should use require, but pure value assertions should use assert so all checks run and report together.

As per coding guidelines: "New or substantially rewritten Go backend tests must use require for setup and fatal assertions and assert for non-fatal value checks."

♻️ Proposed fix
-	require.NoError(t, err)
-	require.False(t, priceData.UsePrice)
-	require.Equal(t, 0.5, priceData.ModelRatio)
-	require.Equal(t, 4.0, priceData.CompletionRatio)
+	require.NoError(t, err)
+	assert.False(t, priceData.UsePrice)
+	assert.Equal(t, 0.5, priceData.ModelRatio)
+	assert.Equal(t, 4.0, priceData.CompletionRatio)
🤖 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/helper/price_test.go` around lines 301 - 304, Update the value checks
for priceData.UsePrice, priceData.ModelRatio, and priceData.CompletionRatio in
this test to use assert instead of require, while keeping require.NoError for
the setup/error check.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@relay/helper/price_test.go`:
- Around line 301-304: Update the value checks for priceData.UsePrice,
priceData.ModelRatio, and priceData.CompletionRatio in this test to use assert
instead of require, while keeping require.NoError for the setup/error check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cfb400a9-7659-4a4c-ba23-ba9ae54d8b5e

📥 Commits

Reviewing files that changed from the base of the PR and between 7c28993 and 20b22c1.

📒 Files selected for processing (7)
  • controller/relay.go
  • model/task.go
  • relay/helper/price.go
  • relay/helper/price_test.go
  • service/task_billing.go
  • service/task_billing_test.go
  • service/task_polling.go

@zuiho-kai
zuiho-kai marked this pull request as ready for review July 13, 2026 14:19

@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.

🧹 Nitpick comments (1)
relay/helper/price.go (1)

230-239: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant quota = 0 inside the free-model check.

quota is already unconditionally set to 0 on line 232, so the quota = 0 on line 236 is a no-op. Only freeModel = true carries new information. Consider removing the redundant assignment for clarity.

♻️ Suggested cleanup
 	} else {
 		// 按 Token 计费的异步任务只能在上游返回 usage 后确定费用,提交时不预扣。
 		// 任务完成后由 RecalculateTaskQuotaByTokens 按输入、输出 Token 差额结算。
 		quota = 0
 		modelPrice = -1
 		if !operation_setting.GetQuotaSetting().EnableFreeModelPreConsume {
 			if groupRatioInfo.GroupRatio == 0 || modelRatio == 0 {
-				quota = 0
 				freeModel = true
 			}
 		}
 	}
🤖 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/helper/price.go` around lines 230 - 239, Remove the redundant quota = 0
assignment inside the free-model condition surrounding groupRatioInfo.GroupRatio
and modelRatio. Keep the unconditional quota reset and preserve freeModel = true
behavior.
🤖 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.

Nitpick comments:
In `@relay/helper/price.go`:
- Around line 230-239: Remove the redundant quota = 0 assignment inside the
free-model condition surrounding groupRatioInfo.GroupRatio and modelRatio. Keep
the unconditional quota reset and preserve freeModel = true behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f5b9e33c-b453-47ba-8ef0-9722797303c8

📥 Commits

Reviewing files that changed from the base of the PR and between 20b22c1 and 38b6654.

📒 Files selected for processing (2)
  • relay/helper/price.go
  • relay/helper/price_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • relay/helper/price_test.go

JacksonsY added a commit to JacksonsY/new-api that referenced this pull request Jul 19, 2026
移植上游 PR QuantumNous#6173(异步视频输出 Token 计费)与 QuantumNous#5831(计量外露)。

QuantumNous#6173:RecalculateTaskQuotaByTokens 此前把 totalTokens 整体按模型倍率
计价,输出 token 未乘补全倍率。现新增 completionTokens 入参,按
input + completion×completionRatio 计价;倍率优先取任务提交时的
BillingContext 快照而非当前配置,避免中途改价影响在途任务。
TaskBillingContext 新增 CompletionRatio(指针,兼容未保存该字段的历史
任务);未配置补全倍率的模型默认取 1,与旧行为等价。

QuantumNous#5831:任务计量经 X-New-Api-Usage 响应头透传给下游(中转套娃场景),
body 保持纯 OpenAI 结构;ToOpenAIVideo 带出按秒计费的时长。

一处加固:OtherRatios["seconds"] 读自数据库,历史记录可能写于尚无时长
上界校验的版本,转 int 前先钳到 MaxTaskDurationSeconds——超出 int
范围的 float64 转换结果是未定义的。

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

Copy link
Copy Markdown
Member

异步任务已经重构,辛苦重新看一下

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.

异步视频按 Token 结算时忽略输出倍率

2 participants