pricing: budget maxPayment generously to avoid PaymentTooHigh reverts
The previous formula computed maxPayment from the estimated input length plus offering.maxContextTokens — but the on-chain offering for some provider models has maxContextTokens=0, so output cost was 0 and the posted maxPayment only covered the (under-estimated) input side. The provider's claimJob then reverted with PaymentTooHigh, leaving the gateway hung waiting for a PSS job_deliver that never came. Match the t4t/container homelab gateway: assume both prompt and completion can be (max_tokens + 1M-token headroom), budget at the full split rate, let the JobEscrow contract refund the unused portion. The provider's actual claim is always smaller, so PaymentTooHigh can't fire. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
3618f5baf6
commit
9503b19378
1 file changed
+18
-16
+18
-16
@@ -4,6 +4,19 @@ import { env } from '../env';
|
|||||||
const BPS_DIVISOR = 10_000n;
|
const BPS_DIVISOR = 10_000n;
|
||||||
const TOKENS_PER_MILLION = 1_000_000n;
|
const TOKENS_PER_MILLION = 1_000_000n;
|
||||||
|
|
||||||
|
// Conservative ceiling matches the t4t/container homelab gateway. We assume
|
||||||
|
// prompt and completion could each be `max_tokens + headroom` and budget both
|
||||||
|
// sides at the model's full split rate. The provider always claims the actual
|
||||||
|
// (smaller) amount; the JobEscrow contract refunds the remainder to the client.
|
||||||
|
//
|
||||||
|
// Why so generous: providers may apply chat templates that inflate the prompt
|
||||||
|
// token count well beyond the user-visible messages, and the on-chain
|
||||||
|
// `maxContextTokens` is sometimes unset (== 0) — in which case the previous
|
||||||
|
// per-actual-input formula posted a too-low maxPayment and the provider's
|
||||||
|
// claimJob reverted with PaymentTooHigh.
|
||||||
|
const DEFAULT_MAX_TOKENS = 1024n;
|
||||||
|
const HEADROOM_TOKENS = 1_000_000n;
|
||||||
|
|
||||||
export interface ChatRequest {
|
export interface ChatRequest {
|
||||||
model: string;
|
model: string;
|
||||||
max_tokens?: number;
|
max_tokens?: number;
|
||||||
@@ -23,26 +36,15 @@ export function estimateMaxXbzz(
|
|||||||
req: ChatRequest,
|
req: ChatRequest,
|
||||||
offering: Pick<ModelOffering, 'inputPricePerMillionTokens' | 'outputPricePerMillionTokens' | 'maxContextTokens'>,
|
offering: Pick<ModelOffering, 'inputPricePerMillionTokens' | 'outputPricePerMillionTokens' | 'maxContextTokens'>,
|
||||||
): { rawWei: bigint; withMarkupWei: bigint } {
|
): { rawWei: bigint; withMarkupWei: bigint } {
|
||||||
const maxInputTokens = estimateInputTokens(req);
|
const maxTokens = BigInt(req.max_tokens ?? DEFAULT_MAX_TOKENS);
|
||||||
const maxOutputTokens = BigInt(req.max_tokens ?? Number(offering.maxContextTokens));
|
const budget = maxTokens + HEADROOM_TOKENS;
|
||||||
|
const inPay = BigInt(offering.inputPricePerMillionTokens) * budget;
|
||||||
const inputCost = (BigInt(maxInputTokens) * BigInt(offering.inputPricePerMillionTokens)) / TOKENS_PER_MILLION;
|
const outPay = BigInt(offering.outputPricePerMillionTokens) * budget;
|
||||||
const outputCost = (maxOutputTokens * BigInt(offering.outputPricePerMillionTokens)) / TOKENS_PER_MILLION;
|
const rawWei = (inPay + outPay) / TOKENS_PER_MILLION;
|
||||||
const rawWei = inputCost + outputCost;
|
|
||||||
const withMarkupWei = (rawWei * (BPS_DIVISOR + BigInt(env.MARKUP_BPS))) / BPS_DIVISOR;
|
const withMarkupWei = (rawWei * (BPS_DIVISOR + BigInt(env.MARKUP_BPS))) / BPS_DIVISOR;
|
||||||
return { rawWei, withMarkupWei };
|
return { rawWei, withMarkupWei };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Approximate input-token count from messages. Uses a 4-char-per-token
|
|
||||||
* heuristic — fine for an upper-bound estimate; the actual cost is recorded
|
|
||||||
* later from the JobClaimed event.
|
|
||||||
*/
|
|
||||||
function estimateInputTokens(req: ChatRequest): number {
|
|
||||||
const totalChars = (req.messages ?? []).reduce((acc, m) => acc + (m.content?.length ?? 0), 0);
|
|
||||||
return Math.ceil(totalChars / 4) + 16; // +16 for system / role overhead
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Apply the same markup function used at reservation time to a settled cost. */
|
/** Apply the same markup function used at reservation time to a settled cost. */
|
||||||
export function applyMarkup(rawWei: bigint): bigint {
|
export function applyMarkup(rawWei: bigint): bigint {
|
||||||
return (rawWei * (BPS_DIVISOR + BigInt(env.MARKUP_BPS))) / BPS_DIVISOR;
|
return (rawWei * (BPS_DIVISOR + BigInt(env.MARKUP_BPS))) / BPS_DIVISOR;
|
||||||
|
|||||||
Reference in new issue
Block a user