mirror of
https://github.com/HeyPuter/puter.git
synced 2026-07-08 08:12:15 +00:00
make max tokens less lenient (#3240)
This commit is contained in:
committed by
GitHub
parent
ee37be16f0
commit
7ed87f760b
@@ -547,6 +547,46 @@ describe('ChatCompletionDriver.complete credit gate and max_tokens cap', () => {
|
||||
expect(passed.max_tokens!).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('throws 402 instead of leaving `max_tokens` unset when credits cannot afford one output token', async () => {
|
||||
// Regression: previously a sub-1 cap set max_tokens to `undefined`,
|
||||
// which let the provider run to the model's full output limit and
|
||||
// overdraw the account. It must reject instead.
|
||||
vi.spyOn(FakeChatProvider.prototype, 'models').mockResolvedValueOnce([
|
||||
{
|
||||
id: 'capme',
|
||||
aliases: [],
|
||||
costs_currency: 'usd-cents',
|
||||
costs: { input_tokens: 1000, output_tokens: 2000 },
|
||||
max_tokens: 8192,
|
||||
},
|
||||
]);
|
||||
const d = await makeDriver();
|
||||
|
||||
// Pass the cheap pre-flight gate but leave a balance too small to
|
||||
// afford a single 2000-microcent output token.
|
||||
vi.spyOn(server.services.metering, 'hasEnoughCredits').mockResolvedValue(
|
||||
true,
|
||||
);
|
||||
vi.spyOn(server.services.metering, 'getRemainingUsage').mockResolvedValue(
|
||||
100,
|
||||
);
|
||||
|
||||
const completeSpy = vi.spyOn(FakeChatProvider.prototype, 'complete');
|
||||
|
||||
await expect(
|
||||
withTestActor(() =>
|
||||
d.complete({
|
||||
model: 'capme',
|
||||
messages: [{ role: 'user', content: 'hi' }],
|
||||
}),
|
||||
),
|
||||
).rejects.toMatchObject({
|
||||
statusCode: 402,
|
||||
legacyCode: 'insufficient_funds',
|
||||
});
|
||||
expect(completeSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects subscriber-only models for the default free subscription', async () => {
|
||||
vi.spyOn(FakeChatProvider.prototype, 'models').mockResolvedValueOnce([
|
||||
{
|
||||
|
||||
@@ -400,16 +400,25 @@ export class ChatCompletionDriver extends PuterDriver {
|
||||
remainingCredits - approximateInputCost;
|
||||
const maxAllowedOutputTokens =
|
||||
maxAllowedOutputUcents / outputTokenCost;
|
||||
if (maxAllowedOutputTokens) {
|
||||
const cap = Math.floor(
|
||||
Math.min(
|
||||
args.max_tokens ?? Number.POSITIVE_INFINITY,
|
||||
maxAllowedOutputTokens,
|
||||
model.max_tokens - approximateTokenCount,
|
||||
),
|
||||
);
|
||||
args.max_tokens = cap < 1 ? undefined : cap;
|
||||
const cap = Math.floor(
|
||||
Math.min(
|
||||
args.max_tokens ?? Number.POSITIVE_INFINITY,
|
||||
maxAllowedOutputTokens,
|
||||
model.max_tokens - approximateTokenCount,
|
||||
),
|
||||
);
|
||||
// `cap` is the credit-bounded ceiling on output tokens. When
|
||||
// it drops below 1 the user can't afford even a single output
|
||||
// token, so reject the request. Crucially we must NOT leave
|
||||
// `max_tokens` unset here: an undefined max_tokens lets the
|
||||
// provider run to the model's full output limit (e.g. 128k for
|
||||
// Claude), billing far past the user's remaining balance.
|
||||
if (cap < 1) {
|
||||
throw new HttpError(402, 'No usage left for request.', {
|
||||
legacyCode: 'insufficient_funds',
|
||||
});
|
||||
}
|
||||
args.max_tokens = cap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user