Files
GMW/services/discord-gateway/src/modules/ai-moderation/batchBudget.ts
T
asepharyana 16becd5340 perf(ai): kontiguitas batch budget + max_tokens dinamis + urutan kronologis RETURNING
- pickBatchWithinBudget: stop di overflow pertama (break), bukan skip —
  batch tetap prefix kronologis tanpa gap analisis di tengah timeline.
  Diekstrak ke batchBudget.ts (pure, estimator di-inject) + regression test.
- callModerationLLM: param opsional maxTokens; text/media caller menghitung
  ceiling dari estimasi prompt (floor 2048, cap 16384) — batch kecil tak
  lagi reserve window completion 16k.
- getPending/IncompleteMessagesByConversation: sort hasil UPDATE..RETURNING
  by created_at ASC — Postgres tak menjamin urutan, konsumen (anchor konteks
  messages[0], prefix batch) bergantung pada urutan kronologis.
2026-08-22 17:19:41 +07:00

46 lines
1.5 KiB
TypeScript

/**
* batchBudget.ts
*
* Pure batch-sizing helper extracted from batchProcessor.ts so it can be
* unit-tested without pulling in the Piscina worker pool, message store,
* or any other side-effectful import chain.
*/
import type { MessageRecord } from "../message-capture/types.js";
/** Token estimator contract (satisfied by conversationContext.estimateTokens). */
export type TokenEstimator = (text: string) => number;
/**
* Picks a batch of messages within a token budget.
* `tokensPerMessage` accounts for JSON structure overhead around each entry.
* The estimator is injected so this stays a pure function — callers in the
* batch pipeline pass the tiktoken-based estimateTokens.
*/
export function pickBatchWithinBudget(
messages: MessageRecord[],
maxTokens: number,
tokensPerMessage: number,
estimateTokens: TokenEstimator,
): MessageRecord[] {
const batch: MessageRecord[] = [];
let usedTokens = 0;
for (const msg of messages) {
const content = msg.edited_content ?? msg.content;
const msgTokens = estimateTokens(content) + tokensPerMessage;
// Stop at the first overflow instead of skipping: input is ordered
// created_at ASC, so a contiguous chronological prefix keeps the batch
// gap-free. Skipped-over messages would leave unanalyzed holes mid-
// timeline; anything past the budget is picked up by the next wave
// (processBatch always re-schedules after success).
if (usedTokens + msgTokens > maxTokens) {
break;
}
batch.push(msg);
usedTokens += msgTokens;
}
return batch;
}