fix(edge): meter and size-cap knowledge embeddings in embed-chunks and search-knowledge
This commit is contained in:
parent
b35676c75c
commit
1afaea7214
12 changed files with 1111 additions and 270 deletions
85
server/supabase/functions/_shared/embedding-quota.test.ts
Normal file
85
server/supabase/functions/_shared/embedding-quota.test.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import {
|
||||
EMBEDDING_LIMITS,
|
||||
EMBEDDING_QUOTA,
|
||||
embeddingUnits,
|
||||
firstOversizedChunk,
|
||||
planEmbeddingBatches,
|
||||
refundEmbeddingUnits,
|
||||
reserveEmbeddingUnits,
|
||||
usageWindowStart,
|
||||
} from './embedding-quota.ts'
|
||||
import { MemoryUsageStore } from './embedding-quota.fake.ts'
|
||||
|
||||
function assert(condition: boolean, message: string): asserts condition {
|
||||
if (!condition) throw new Error(message)
|
||||
}
|
||||
|
||||
const NOW = new Date('2026-09-28T12:00:00Z')
|
||||
|
||||
Deno.test('units count every started 1,000 characters, at least one per input', () => {
|
||||
assert(embeddingUnits(['a']) === 1, 'short input')
|
||||
assert(embeddingUnits(['x'.repeat(1_000), 'x'.repeat(1_001)]) === 3, 'boundary rounding')
|
||||
})
|
||||
|
||||
Deno.test('oversized chunks are found before any spend', () => {
|
||||
const ok = { content: 'x'.repeat(EMBEDDING_LIMITS.maxChunkChars) }
|
||||
const big = { content: 'x'.repeat(EMBEDDING_LIMITS.maxChunkChars + 1) }
|
||||
assert(firstOversizedChunk([ok, ok]) === -1, 'at-limit chunk rejected')
|
||||
assert(firstOversizedChunk([ok, big]) === 1, 'oversized chunk missed')
|
||||
})
|
||||
|
||||
Deno.test('batches stay under the input and character caps', () => {
|
||||
const small = Array.from({ length: 250 }, (_, i) => ({ content: `c${i}` }))
|
||||
assert(planEmbeddingBatches(small).map((b) => b.length).join(',') === '100,100,50', 'input cap')
|
||||
const large = Array.from({ length: 30 }, () => ({ content: 'x'.repeat(8_000) }))
|
||||
const batches = planEmbeddingBatches(large)
|
||||
assert(batches.every((b) => b.reduce((s, c) => s + c.content.length, 0) <= EMBEDDING_LIMITS.maxBatchChars), 'char cap')
|
||||
assert(batches.flat().length === 30, 'chunks lost')
|
||||
})
|
||||
|
||||
Deno.test('weekly window covers the six days before today', () => {
|
||||
assert(usageWindowStart('weekly', NOW) === '2026-09-22', 'weekly start')
|
||||
assert(usageWindowStart('daily', NOW) === null, 'daily has no earlier window')
|
||||
})
|
||||
|
||||
Deno.test('free tier is capped weekly and a denied reservation is rolled back', async () => {
|
||||
const store = new MemoryUsageStore('free', '2026-09-28')
|
||||
store.rows.set('2026-09-25', EMBEDDING_QUOTA.free.limit - 10)
|
||||
const denied = await reserveEmbeddingUnits(store, 'u1', 11, NOW)
|
||||
assert(!denied.allowed, 'over-limit reservation was allowed')
|
||||
assert((store.rows.get('2026-09-28') ?? 0) === 0, 'denied units were kept')
|
||||
const allowed = await reserveEmbeddingUnits(store, 'u1', 10, NOW)
|
||||
assert(allowed.allowed && allowed.current === EMBEDDING_QUOTA.free.limit, 'exact fit denied')
|
||||
})
|
||||
|
||||
Deno.test('usage older than the window does not count', async () => {
|
||||
const store = new MemoryUsageStore('free', '2026-09-28')
|
||||
store.rows.set('2026-09-21', EMBEDDING_QUOTA.free.limit)
|
||||
assert((await reserveEmbeddingUnits(store, 'u1', 5, NOW)).allowed, 'stale usage counted')
|
||||
})
|
||||
|
||||
Deno.test('enterprise is unlimited but still counted', async () => {
|
||||
const store = new MemoryUsageStore('enterprise', '2026-09-28')
|
||||
const result = await reserveEmbeddingUnits(store, 'u1', 1_000_000, NOW)
|
||||
assert(result.allowed && store.rows.get('2026-09-28') === 1_000_000, 'unlimited tier not counted')
|
||||
})
|
||||
|
||||
Deno.test('refunds are capped by the reservation and skipped after the day rolls over', async () => {
|
||||
const store = new MemoryUsageStore('pro', '2026-09-28')
|
||||
const reservation = await reserveEmbeddingUnits(store, 'u1', 40, NOW)
|
||||
await refundEmbeddingUnits(store, 'u1', reservation, 1_000, NOW)
|
||||
assert(store.rows.get('2026-09-28') === 0, 'refund exceeded the reserved units')
|
||||
|
||||
const again = await reserveEmbeddingUnits(store, 'u1', 40, NOW)
|
||||
store.today = '2026-09-29'
|
||||
await refundEmbeddingUnits(store, 'u1', again, 40, new Date('2026-09-29T00:00:01Z'))
|
||||
assert(!store.rows.has('2026-09-29'), 'next-day refund credited the new day')
|
||||
})
|
||||
|
||||
Deno.test('a store failure during refund is swallowed', async () => {
|
||||
const store = new MemoryUsageStore('pro', '2026-09-28')
|
||||
const reservation = await reserveEmbeddingUnits(store, 'u1', 3, NOW)
|
||||
store.failIncrement = true
|
||||
await refundEmbeddingUnits(store, 'u1', reservation, 3, NOW)
|
||||
assert(store.rows.get('2026-09-28') === 3, 'usage changed')
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue