Repository navigation
Cap each AI feature's reply length instead of 32,768 tokens - #182
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… notice on render Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reachedEnable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. Next included review available in 19 minutes. View limit details
Note 🎁 Summarized by CodeRabbit Free
Comment |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Deployment failed for project debatelab-staging with the following error: Learn More: https://vercel.com/adam-gaborjanis-projects?upgradeToPro=build-rate-limit |
|
Deployment failed for project debate-navigator with the following error: Learn More: https://vercel.com/adam-gaborjanis-projects?upgradeToPro=build-rate-limit |
…the cap math An Ask stream that spent its whole cap on reasoning now fails so the client falls back to the structured endpoint, which retries with a larger cap. The Sparring speech and scorecard caps fit a full-length Hungarian reply. The cap formula uses integer percentages, and a table test pins it from outside. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep #182 retry of a reply cut off at its per-feature cap (now with the strict schema) and drop this branch no-retry-on-truncation path, which the per-feature caps superseded.
|
Post-merge follow-up. Staging. The staging sync deployed Recipes (fake mode). I ran them at
Post-merge Codex review of |

Cost fix 3 of 7. Every AI call used to send
max_tokens: 32768, whatever the feature. Each feature now has its own reply cap, kept in one file:supabase/functions/_shared/output-caps.ts.The caps
Reply budgets in tokens:
Four of these differ from the brief, which said roughly 1,000 for Crossfire and Sparring:
max_tokenspasses about 5,100.Why max_tokens is bigger than the reply cap
OpenRouter counts hidden reasoning against
max_tokens. For Claude (the default model) it also sets the thinking budget tomax(max_tokens × share, 1024), where the share is 0.2 at low effort, 0.5 at medium and 0.8 at high. A flatmax_tokens: 1000would leave Claude no room to answer.So
max_tokensis the reply cap plus room for reasoning. That room is the larger of two amounts: Claude's full budget, or a fixed floor of 2,048 / 4,096 / 8,192 tokens for low / medium / high. The floor is for OpenAI, Gemini 3 and DeepSeek, which decide on their own how long to think. Examples:reasoning.max_tokens, and that changes what the effort setting does.Cut-off replies
Before this change nothing checked
finish_reason: "length". A JSON reply cut off at the limit surfaced as "invalid JSON", then went to a repair retry with the same limit, which couldn't help.Now:
aicall retries a cut-off reply once with twice the reply budget. The live draft resets first, and the usage log bills both attempts. A second cut-off fails the request instead of retrying again. Each cut-off logs[AI] Reply hit its <cap> cap (...)with the model and effort, so the numbers can be tuned from logs.ai-streamthe text has already reached the user, so it can't be retried. The server setsreplyTruncated: trueon the output, and the chat shows "This answer hit its length limit and stops early..." under the answer, translated when shown (en, es, hu). When the model spent the whole cap on reasoning and sent no text, the stream fails instead. The client then falls back to the structured endpoint, which retries with the larger cap.Verify tooling
/__truncatehook (fake.truncate(feature)) that cuts the next reply off withfinish_reason: "length".maxTokens.gen-cutoffandask-cutoff.Verification
GitHub Actions doesn't run repo-wide right now: every job fails in 2 to 3 seconds with "The job was not started because recent account payments have failed or your spending limit needs to be increased." Both Vercel previews are on a 24-hour build rate limit. Everything below ran locally, and a local pass is not a CI pass.
78164d2b,npm run verifypassed docs check, lint, typecheck, unit, collaboration, edge and build.test:swtimed out once under load, then passed on rerun. The desktop@smokebrowser set passed: 26 tests, 4 flaky on pages that make no AI calls.f9159f7c, which has the same tree as the tested rebase), edge (362), unit (338), lint and typecheck all passed.dda9bc01the VPS is short on memory, so only the touched files were rechecked:output-caps.test.ts,ai-stream/,ai/stream.test.ts,ai/handler-research.test.tsandai/provider-errors_test.ts(26 tests) passed, and eslint passed on the touched files.verify-debatelabrecipes (gen-cutoff,ask-cutoffand the existing Ask, Generate, Sparring and Crossfire ones) on both viewports, plus a live pass to check real reply lengths against the caps. The shared local Supabase stack is busy with other threads.Reviews
Codex round 1 (
e5b4d986) had two findings, both fixed in78164d2b:Codex round 2 couldn't run: the Codex account hit its usage cap (it resets at 15:05 UTC), and a final round is scheduled after that.
An Opus review of
f9159f7chad four findings:dda9bc01: it now fails over to the structured endpoint. The test is thecut-off-emptyscenario.dda9bc01: the caps are now 2,500 and 3,000.max_tokens, so doubling the reply room means doublingmax_tokens. The budget is a ceiling, not spend, and the retry runs only after a cut-off.dda9bc01:output-caps.test.tspins hard-coded values and checks that every cap leaves its full reply after Claude's budget. It caught a floating-point off-by-one (30,001 instead of 30,000), fixed by using integer percentages.Out of scope
The default model, the background-step model, quotas, prompt trimming, cost logging and the JSON output format are all handled in other threads.
🤖 Generated with Claude Code