Skip to content

fix: keep Responses stream pre-consume on missing usage - #5291

Closed
yzhkali wants to merge 1 commit into
QuantumNous:mainfrom
yzhkali:fix/responses-stream-missing-usage-quota-5235
Closed

fix: keep Responses stream pre-consume on missing usage#5291
yzhkali wants to merge 1 commit into
QuantumNous:mainfrom
yzhkali:fix/responses-stream-missing-usage-quota-5235

Conversation

@yzhkali

@yzhkali yzhkali commented Jun 3, 2026

Copy link
Copy Markdown

Summary

When a Responses stream emits billable output but ends without final usage, preserve the already pre-consumed quota only for interrupted streams. Normal completion, empty output, non-Responses streams, and responses with actual usage keep the existing behavior.

The fallback is limited to streaming Responses requests with zero reported tokens and an observed billable output marker. A Billing session's pre-consumed value remains authoritative, including a legitimate zero value. Tiered settlement is skipped when the fallback is used, and the consume log records the reason.

Closes #5235

Tests

  • go test ./service -count=1
  • go test ./relay/channel/openai -run Responses -count=1
  • git diff --check

Summary by CodeRabbit

  • Bug Fixes

    • Improved billing accuracy for streamed responses when final usage data is unavailable.
    • Preserved pre-consumed quota for billable response streams and tool calls when appropriate.
    • Refunded or settled quota correctly for aborted, incomplete, metadata-only, and non-response streams.
    • Ensured actual usage takes precedence whenever it is available.
  • Tests

    • Added coverage for missing usage, stream termination scenarios, tool calls, and quota settlement outcomes.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

Streaming Responses quota fallback

Layer / File(s) Summary
Stream billable-output marker
constant/context_key.go, relay/channel/openai/relay_responses.go
Adds a context key and sets it when Responses streams emit text deltas or built-in tool calls.
Missing-usage fallback decision
service/text_quota.go
Detects eligible billable Responses streams with missing final usage and selects the relay’s pre-consumed quota.
Pre-consumed quota settlement
service/text_quota.go
Settles eligible zero-token summaries with pre-consumed quota and records fallback metadata.
Streaming quota validation
service/text_quota_test.go
Tests stream termination conditions, tool-only output, actual usage precedence, metadata-only streams, and non-Responses streams.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant OaiResponsesStreamHandler
  participant GinContext
  participant PostTextConsumeQuota
  participant SettleBilling

  Client->>OaiResponsesStreamHandler: Receive Responses stream
  OaiResponsesStreamHandler->>GinContext: Mark billable output
  OaiResponsesStreamHandler-->>Client: Send text or tool-call output
  PostTextConsumeQuota->>GinContext: Evaluate stream context and usage
  PostTextConsumeQuota->>SettleBilling: Settle pre-consumed quota when final usage is missing
Loading

Poem

I’m a rabbit with a stream to tend,
Billable crumbs now mark the end.
If usage hides beyond the gate,
Pre-consumed quota will not escape.
Tests thump softly: paths align.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 address issue #5235 by retaining positive pre-consumed quota for billable Responses streams when final usage is missing.
Out of Scope Changes check ✅ Passed The changes are limited to Responses stream billing fallback logic, supporting context, and focused tests related to issue #5235.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: retaining pre-consumed quota for Responses streams when usage is missing.
✨ 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 (2)
service/text_quota.go (1)

412-422: 💤 Low value

Consider using LogWarn for the missing-usage fallback.

The fallback is an expected operational path (client disconnect, upstream interruption), not an error condition. Using LogError at line 418 may inflate error metrics and make genuine errors harder to find. Consider LogWarn to indicate an unusual-but-handled situation.

Suggested change
 		if summary.MissingUsagePreConsumed {
 			extraContent = append(extraContent, "流式响应已开始但缺少最终计费信息,按预扣额度结算")
-			logger.LogError(ctx, fmt.Sprintf("stream usage missing after response chunks, settling pre-consumed quota, userId %d, channelId %d, tokenId %d, model %s, pre-consumed quota %d, received %d, sent %d", relayInfo.UserId, relayInfo.ChannelId, relayInfo.TokenId, summary.ModelName, summary.Quota, relayInfo.ReceivedResponseCount, relayInfo.SendResponseCount))
+			logger.LogWarn(ctx, fmt.Sprintf("stream usage missing after response chunks, settling pre-consumed quota, userId %d, channelId %d, tokenId %d, model %s, pre-consumed quota %d, received %d, sent %d", relayInfo.UserId, relayInfo.ChannelId, relayInfo.TokenId, summary.ModelName, summary.Quota, relayInfo.ReceivedResponseCount, relayInfo.SendResponseCount))
 		}

Also applies to: 455-458

🤖 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 `@service/text_quota.go` around lines 412 - 422, Replace the use of
logger.LogError with logger.LogWarn for the expected-but-handled fallback paths
in text_quota.go: in the branch where summary.TotalTokens == 0 &&
!summary.MissingUsagePreConsumed (the upstream returned no billing info) change
logger.LogError(...) to logger.LogWarn(...), and likewise change the
logger.LogError(...) call inside the summary.MissingUsagePreConsumed branch
(stream usage missing, settling pre-consumed quota) to logger.LogWarn(...); keep
the original log message text and parameters intact so metrics and context are
preserved while avoiding classifying these handled fallbacks as errors.
service/text_quota_test.go (1)

237-258: ⚡ Quick win

Test does not isolate the "normal Done ending" branch.

Unlike the keep-quota tests (e.g., Lines 191-213, 260-282), this case omits common.SetContextKey(ctx, constant.ContextKeyResponsesBillableStreamOutput, true). Since billable output was never marked, the refund here is driven by the missing context key rather than the StreamEndReasonDone ending the test name targets. Set the billable flag so the only blocker is the normal end reason; this guards against a regression where Done is mistakenly treated as interrupted.

♻️ Isolate the end-reason condition
 func TestCalculateTextQuotaSummaryDoesNotKeepPreConsumedQuotaForNormalMissingUsageStream(t *testing.T) {
 	ctx := newTextQuotaTestContext()
+	common.SetContextKey(ctx, constant.ContextKeyResponsesBillableStreamOutput, true)
 
 	streamStatus := relaycommon.NewStreamStatus()
 	streamStatus.SetEndReason(relaycommon.StreamEndReasonDone, nil)
🤖 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 `@service/text_quota_test.go` around lines 237 - 258, The test
TestCalculateTextQuotaSummaryDoesNotKeepPreConsumedQuotaForNormalMissingUsageStream
currently fails to isolate the "normal Done ending" branch because it never
marks responses as billable; update the test to set the billable flag on the
context using common.SetContextKey(ctx,
constant.ContextKeyResponsesBillableStreamOutput, true) before calling
calculateTextQuotaSummary so the only condition being exercised is the
StreamEndReasonDone path (relayInfo.StreamStatus) and not the missing
billable-output context key.
🤖 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 `@service/text_quota_test.go`:
- Around line 237-258: The test
TestCalculateTextQuotaSummaryDoesNotKeepPreConsumedQuotaForNormalMissingUsageStream
currently fails to isolate the "normal Done ending" branch because it never
marks responses as billable; update the test to set the billable flag on the
context using common.SetContextKey(ctx,
constant.ContextKeyResponsesBillableStreamOutput, true) before calling
calculateTextQuotaSummary so the only condition being exercised is the
StreamEndReasonDone path (relayInfo.StreamStatus) and not the missing
billable-output context key.

In `@service/text_quota.go`:
- Around line 412-422: Replace the use of logger.LogError with logger.LogWarn
for the expected-but-handled fallback paths in text_quota.go: in the branch
where summary.TotalTokens == 0 && !summary.MissingUsagePreConsumed (the upstream
returned no billing info) change logger.LogError(...) to logger.LogWarn(...),
and likewise change the logger.LogError(...) call inside the
summary.MissingUsagePreConsumed branch (stream usage missing, settling
pre-consumed quota) to logger.LogWarn(...); keep the original log message text
and parameters intact so metrics and context are preserved while avoiding
classifying these handled fallbacks as errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e40b78d2-cc82-4612-9ea8-de723d101cb0

📥 Commits

Reviewing files that changed from the base of the PR and between b0ac042 and 717da4e.

📒 Files selected for processing (5)
  • constant/context_key.go
  • dto/openai_response.go
  • relay/channel/openai/relay_responses.go
  • service/text_quota.go
  • service/text_quota_test.go

@yzhkali

yzhkali commented Jun 14, 2026

Copy link
Copy Markdown
Author

Addressed CodeRabbit's review suggestions in the latest commit: the handled missing-usage fallback logs now use LogWarn, and the normal Done missing-usage stream test now sets the billable-output context so it isolates the end-reason path.

I also reran the relevant service tests locally:

go test ./service -run 'TestCalculateTextQuotaSummary.*MissingUsage|TestPostTextConsumeQuota'

Result: passed.

@yzhkali

yzhkali commented Jun 17, 2026

Copy link
Copy Markdown
Author

Following up after the latest CodeRabbit pass reported no actionable comments. This PR is scoped to #5235: keep pre-consumed quota only when a Responses stream produced billable output but ended without final usage, with tests around text output, tool-only output, end reasons, and actual usage overriding the fallback.

Checks are green. Happy to adjust the fallback semantics if maintainers prefer a different quota policy.

@yzhkali
yzhkali force-pushed the fix/responses-stream-missing-usage-quota-5235 branch from b2ac781 to d00f9dd Compare August 4, 2026 12:45
@yzhkali

yzhkali commented Aug 8, 2026

Copy link
Copy Markdown
Author

Closing because linked issue #5235 was closed as not planned after the requested reproduction details were not confirmed. The billing behavior in this branch remains unverified on current main, so I do not want to leave the PR open without evidence for the policy change. A new reproducible report can be handled separately.

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.

Streaming Responses requests may refund all pre-consumed quota when final usage is missing after client disconnect

1 participant