akm-cli 0.9.15-beta.2 → 0.9.15-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +116 -3
- package/dist/cli.js +3 -3
- package/dist/commands/health/checks.js +13 -9
- package/dist/commands/health/scheduler-binary.js +120 -0
- package/dist/commands/health.js +9 -0
- package/dist/commands/improve/improve-cli.js +107 -6
- package/dist/commands/improve/improve.js +47 -3
- package/dist/commands/improve/reflect.js +159 -64
- package/dist/core/config/schema/embedding.js +2 -3
- package/dist/core/errors.js +2 -0
- package/dist/core/maintenance-barrier.js +35 -6
- package/dist/indexer/indexer.js +44 -3
- package/dist/indexer/materialize-embeddings.js +51 -9
- package/dist/llm/client.js +18 -0
- package/dist/llm/embedders/remote.js +106 -8
- package/dist/llm/usage-persist.js +7 -1
- package/dist/scripts/akm-migrate-node.js +77 -17
- package/dist/scripts/akm-migrate.js +77 -17
- package/docs/migration/release-notes/0.9.15.md +83 -0
- package/docs/reference/cli.md +60 -6
- package/docs/reference/configuration.md +55 -5
- package/package.json +1 -1
- package/schemas/akm-config.json +0 -12
|
@@ -30,10 +30,17 @@ export const DEFAULT_REMOTE_BATCH_SIZE = 100;
|
|
|
30
30
|
* a batch of 100 small docs (~400 KB, ~100K tokens) took 14.8s against a
|
|
31
31
|
* healthy local endpoint — half the 30s request timeout — and a single
|
|
32
32
|
* 128 KB (~24K token) document alone was rejected by the endpoint as
|
|
33
|
-
* exceeding its context size.
|
|
34
|
-
*
|
|
33
|
+
* exceeding its context size.
|
|
34
|
+
*
|
|
35
|
+
* Lowered from 8000 to 6000 (#954, field report on beta.1): the 4-chars-
|
|
36
|
+
* per-token estimator undercounts dense technical text by 7-55%, so 8000
|
|
37
|
+
* against an 8192-token llama.cpp embedder regularly landed real requests
|
|
38
|
+
* over the endpoint's context window. 6000 is the value the field confirmed
|
|
39
|
+
* stops that steady trickle of rejections; `embedBatch`'s run-scoped
|
|
40
|
+
* adaptive budget below still shrinks further, for an endpoint where even
|
|
41
|
+
* this is not enough.
|
|
35
42
|
*/
|
|
36
|
-
export const DEFAULT_TOKEN_BUDGET =
|
|
43
|
+
export const DEFAULT_TOKEN_BUDGET = 6000;
|
|
37
44
|
/** Cheap token estimator: 4 chars ≈ 1 token. Used in verbose logging and error messages. */
|
|
38
45
|
export function estimateTokenCount(text) {
|
|
39
46
|
return Math.round(text.length / 4);
|
|
@@ -73,7 +80,7 @@ export function capEmbeddingText(text, maxTokens) {
|
|
|
73
80
|
/**
|
|
74
81
|
* Default per-request timeout when `embedding.timeoutMs` is unset (#954).
|
|
75
82
|
* The prior fixed 30s cut off exactly the field-report case: a
|
|
76
|
-
* local model server on
|
|
83
|
+
* local model server on a full-budget (`DEFAULT_TOKEN_BUDGET`) batch
|
|
77
84
|
* legitimately takes longer than that, and the timeout fired mid-response
|
|
78
85
|
* with no retry — every batch it hit was silently dropped for the rest of
|
|
79
86
|
* an hours-long run. 120s comfortably covers a slow local batch while still
|
|
@@ -235,6 +242,21 @@ export function buildTokenBoundedBatches(texts, tokenBudget, maxCount) {
|
|
|
235
242
|
flush();
|
|
236
243
|
return batches;
|
|
237
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Shrink factor applied to the effective request budget on the first
|
|
247
|
+
* context-size rejection of an `embedBatch` run (#954, field report on
|
|
248
|
+
* beta.1): one 25% cut absorbs the estimator's measured undercount without
|
|
249
|
+
* repeatedly re-shrinking mid-run — see the "shrink at most once" rule on
|
|
250
|
+
* {@link RemoteEmbedder.embedBatch}.
|
|
251
|
+
*/
|
|
252
|
+
const ADAPTIVE_BUDGET_SHRINK_FACTOR = 0.75;
|
|
253
|
+
/**
|
|
254
|
+
* Floor on the adaptive-budget shrink above, as a multiple of
|
|
255
|
+
* `maxInputTokens` (#954): a request budget below twice the per-document cap
|
|
256
|
+
* could no longer batch more than one document per request, defeating the
|
|
257
|
+
* point of batching at all.
|
|
258
|
+
*/
|
|
259
|
+
const ADAPTIVE_BUDGET_FLOOR_MULTIPLIER = 2;
|
|
238
260
|
export class RemoteEmbedder {
|
|
239
261
|
config;
|
|
240
262
|
endpoint;
|
|
@@ -339,6 +361,18 @@ export class RemoteEmbedder {
|
|
|
339
361
|
* {@link scaleEmbeddingTimeoutMs}, so a dead server is detected in seconds
|
|
340
362
|
* on a small batch rather than always waiting out the full configured
|
|
341
363
|
* `embedding.timeoutMs`.
|
|
364
|
+
*
|
|
365
|
+
* Run-scoped adaptive budget (#954, field report on beta.1): the FIRST
|
|
366
|
+
* context-size rejection of the run shrinks the effective request budget
|
|
367
|
+
* by {@link ADAPTIVE_BUDGET_SHRINK_FACTOR} (floored at
|
|
368
|
+
* {@link ADAPTIVE_BUDGET_FLOOR_MULTIPLIER} times `maxInputTokens`) for
|
|
369
|
+
* every batch not yet dispatched — the still-planned tail of `texts` is
|
|
370
|
+
* re-batched with `buildTokenBoundedBatches` at the smaller budget, and a
|
|
371
|
+
* `budget-lowered` `onBatch` event reports it once. This never touches the
|
|
372
|
+
* split-and-retry of the rejected batch itself (above), and never fires a
|
|
373
|
+
* second time in the same run even if a later batch is also rejected — a
|
|
374
|
+
* static configured budget that is simply too big for the endpoint should
|
|
375
|
+
* self-correct once, not ratchet down forever.
|
|
342
376
|
*/
|
|
343
377
|
async embedBatch(texts, signal, onSkip, onBatch) {
|
|
344
378
|
if (texts.length === 0)
|
|
@@ -351,10 +385,59 @@ export class RemoteEmbedder {
|
|
|
351
385
|
// request budget too, so a config author setting it for one purpose
|
|
352
386
|
// silently changed the other. `maxTokens` is the sole knob for the
|
|
353
387
|
// request budget now; unset falls back to DEFAULT_TOKEN_BUDGET.
|
|
354
|
-
|
|
388
|
+
//
|
|
389
|
+
// `effectiveTokenBudget` (#954) starts at the configured/default value
|
|
390
|
+
// and MAY shrink once, on the run's first context-size rejection — see
|
|
391
|
+
// `maybeShrinkBudget` below. `textBatches` is mutated in place (spliced)
|
|
392
|
+
// by that shrink rather than reassigned, so the in-flight
|
|
393
|
+
// `concurrentMap` pool below (which reads this same array by reference)
|
|
394
|
+
// picks up the re-planned tail without restarting.
|
|
395
|
+
let effectiveTokenBudget = this.config.maxTokens ?? DEFAULT_TOKEN_BUDGET;
|
|
355
396
|
const maxCount = this.config.batchSize ?? DEFAULT_REMOTE_BATCH_SIZE;
|
|
356
|
-
const
|
|
397
|
+
const maxInputTokens = this.config.maxInputTokens ?? DEFAULT_MAX_INPUT_TOKENS;
|
|
398
|
+
const textBatches = buildTokenBoundedBatches(texts, effectiveTokenBudget, maxCount);
|
|
357
399
|
const configuredTimeoutMs = resolveEmbeddingTimeoutMs(this.config);
|
|
400
|
+
// How many of `textBatches` concurrentMap has already claimed (its own
|
|
401
|
+
// `nextIndex`, mirrored here so a budget shrink knows where the
|
|
402
|
+
// not-yet-dispatched tail begins). Assigned, not incremented, at the top
|
|
403
|
+
// of `runProviderBatch` — batches are claimed in strictly increasing
|
|
404
|
+
// order, so the highest `batchIndex` seen so far IS the claimed count.
|
|
405
|
+
let dispatchedBatchCount = 0;
|
|
406
|
+
// Set once the run's first context-size rejection has shrunk the budget
|
|
407
|
+
// (#954) — guards `maybeShrinkBudget` so it never fires twice.
|
|
408
|
+
let budgetShrunk = false;
|
|
409
|
+
// On the FIRST context-size rejection of this `embedBatch` call, shrink
|
|
410
|
+
// `effectiveTokenBudget` and re-plan every batch `concurrentMap` has not
|
|
411
|
+
// yet claimed from the smaller budget. Never touches `rejectedIndices`
|
|
412
|
+
// itself — the caller's own split-and-retry handles that batch — and is
|
|
413
|
+
// a no-op after the first call (`budgetShrunk`).
|
|
414
|
+
const maybeShrinkBudget = (rejectedIndices, rejectedBatchIndex, rejectedRequestTokens) => {
|
|
415
|
+
if (budgetShrunk)
|
|
416
|
+
return;
|
|
417
|
+
budgetShrunk = true;
|
|
418
|
+
const floor = ADAPTIVE_BUDGET_FLOOR_MULTIPLIER * maxInputTokens;
|
|
419
|
+
effectiveTokenBudget = Math.max(Math.round(effectiveTokenBudget * ADAPTIVE_BUDGET_SHRINK_FACTOR), floor);
|
|
420
|
+
const notYetDispatched = textBatches.slice(dispatchedBatchCount);
|
|
421
|
+
const remainingIndices = notYetDispatched.flatMap((batch) => batch.indices);
|
|
422
|
+
if (remainingIndices.length > 0) {
|
|
423
|
+
const remainingTexts = remainingIndices.map((i) => texts[i]);
|
|
424
|
+
const replanned = buildTokenBoundedBatches(remainingTexts, effectiveTokenBudget, maxCount).map((batch) => ({
|
|
425
|
+
indices: batch.indices.map((localIndex) => remainingIndices[localIndex]),
|
|
426
|
+
oversized: batch.oversized,
|
|
427
|
+
}));
|
|
428
|
+
textBatches.splice(dispatchedBatchCount, textBatches.length - dispatchedBatchCount, ...replanned);
|
|
429
|
+
}
|
|
430
|
+
warnVerbose(`[embed] provider rejected a ${rejectedRequestTokens}-token request as over its context; request budget lowered to ${effectiveTokenBudget.toLocaleString()} for the rest of this run`);
|
|
431
|
+
commitBatch(rejectedIndices, rejectedIndices.map(() => undefined), undefined, {
|
|
432
|
+
batchIndex: rejectedBatchIndex,
|
|
433
|
+
batchCount: textBatches.length,
|
|
434
|
+
docCount: rejectedIndices.length,
|
|
435
|
+
requestTokens: rejectedRequestTokens,
|
|
436
|
+
elapsedMs: 0,
|
|
437
|
+
outcome: "budget-lowered",
|
|
438
|
+
reason: `provider rejected ${rejectedRequestTokens.toLocaleString()} tokens as over its context; request budget lowered to ${effectiveTokenBudget.toLocaleString()} for the rest of this run`,
|
|
439
|
+
});
|
|
440
|
+
};
|
|
358
441
|
// Stops the pool from claiming any FURTHER provider batch once the
|
|
359
442
|
// caller's onBatch has failed once (the materializer's transaction
|
|
360
443
|
// failed, so a subsequent commit would just fail again) — dispatching
|
|
@@ -437,7 +520,7 @@ export class RemoteEmbedder {
|
|
|
437
520
|
return;
|
|
438
521
|
const batch = indices.map((i) => texts[i]);
|
|
439
522
|
const requestTokens = batch.reduce((sum, text) => sum + estimateTokenCount(text), 0);
|
|
440
|
-
const requestTimeoutMs = scaleEmbeddingTimeoutMs(configuredTimeoutMs, requestTokens,
|
|
523
|
+
const requestTimeoutMs = scaleEmbeddingTimeoutMs(configuredTimeoutMs, requestTokens, effectiveTokenBudget);
|
|
441
524
|
const requestStart = Date.now();
|
|
442
525
|
let batchEmbeddings;
|
|
443
526
|
let responseModel;
|
|
@@ -457,6 +540,15 @@ export class RemoteEmbedder {
|
|
|
457
540
|
// failed" condition, it means stop entirely.
|
|
458
541
|
if (signal?.aborted)
|
|
459
542
|
throw err;
|
|
543
|
+
if (err instanceof ContextExceededError) {
|
|
544
|
+
// #954: the run's FIRST context-size rejection (any size) shrinks
|
|
545
|
+
// the budget for everything not yet dispatched; a no-op after the
|
|
546
|
+
// first call. Deliberately BEFORE the split below — it must fire
|
|
547
|
+
// for a single-document rejection too (which never reaches the
|
|
548
|
+
// `indices.length > 1` split branch), and it never touches this
|
|
549
|
+
// batch's own split-and-retry.
|
|
550
|
+
maybeShrinkBudget(indices, batchIndex, requestTokens);
|
|
551
|
+
}
|
|
460
552
|
if (err instanceof ContextExceededError && indices.length > 1) {
|
|
461
553
|
const mid = Math.ceil(indices.length / 2);
|
|
462
554
|
await requestAndCommit(indices.slice(0, mid), batchIndex, false, timeoutAttempt);
|
|
@@ -554,13 +646,19 @@ export class RemoteEmbedder {
|
|
|
554
646
|
});
|
|
555
647
|
};
|
|
556
648
|
const runProviderBatch = async (textBatch, batchIndex) => {
|
|
649
|
+
// Claimed in strictly increasing order by `concurrentMap` below, so
|
|
650
|
+
// the highest `batchIndex` seen so far is exactly how many batches it
|
|
651
|
+
// has claimed (#954) — see `maybeShrinkBudget`'s doc comment above.
|
|
652
|
+
// Assigned synchronously at entry, before any `await`, so this always
|
|
653
|
+
// matches `concurrentMap`'s own `nextIndex` at the moment of claim.
|
|
654
|
+
dispatchedBatchCount = batchIndex;
|
|
557
655
|
if (textBatch.oversized) {
|
|
558
656
|
const idx = textBatch.indices[0];
|
|
559
657
|
const estTokens = estimateTokenCount(texts[idx]);
|
|
560
658
|
onSkip?.({
|
|
561
659
|
index: idx,
|
|
562
660
|
reason: "context-window-exceeded",
|
|
563
|
-
message: `Document estimated at ${estTokens} tokens exceeds the ${
|
|
661
|
+
message: `Document estimated at ${estTokens} tokens exceeds the ${effectiveTokenBudget}-token embedding budget; skipped.`,
|
|
564
662
|
batchStart: true,
|
|
565
663
|
batchSize: 1,
|
|
566
664
|
});
|
|
@@ -67,12 +67,18 @@ function toEventMetadata(record) {
|
|
|
67
67
|
* opened for its other events. A getter is resolved for every append so a
|
|
68
68
|
* caller can replace its context binding without replacing this owning sink.
|
|
69
69
|
* When omitted, `appendEvent` falls back to its default open-insert-close path.
|
|
70
|
+
*
|
|
71
|
+
* `onRecord`, when supplied, runs synchronously for every terminal record
|
|
72
|
+
* BEFORE persistence — improve's first-engine-response heartbeat (#957) uses
|
|
73
|
+
* it to know the run is no longer silent, without this module taking on any
|
|
74
|
+
* dependency of its own on improve's lifecycle.
|
|
70
75
|
*/
|
|
71
|
-
export function installLlmUsagePersistence(ctx) {
|
|
76
|
+
export function installLlmUsagePersistence(ctx, onRecord) {
|
|
72
77
|
let expectedTerminalRecords = 0;
|
|
73
78
|
let disposed = false;
|
|
74
79
|
setLlmUsageSink((record) => {
|
|
75
80
|
expectedTerminalRecords += 1;
|
|
81
|
+
onRecord?.(record);
|
|
76
82
|
appendEvent({ eventType: LLM_USAGE_EVENT, metadata: toEventMetadata(record) }, typeof ctx === "function" ? ctx() : ctx);
|
|
77
83
|
});
|
|
78
84
|
return () => {
|
|
@@ -7070,7 +7070,9 @@ var init_errors = __esm(() => {
|
|
|
7070
7070
|
};
|
|
7071
7071
|
TRANSIENT_HINTS = {
|
|
7072
7072
|
RUN_LEASE_HELD: "Wait for the named engine invocation to finish or for the lease to expire, then retry. `akm workflow status <id>` shows the current lease.",
|
|
7073
|
-
STATE_DB_CONTENDED: "Another akm process is writing state.db right now. Wait a few seconds and retry; commands that support --skip-if-locked can skip instead of failing."
|
|
7073
|
+
STATE_DB_CONTENDED: "Another akm process is writing state.db right now. Wait a few seconds and retry; commands that support --skip-if-locked can skip instead of failing.",
|
|
7074
|
+
INDEX_DB_CONTENDED: "Another akm process is writing index.db; retry shortly, or pass --skip-if-locked on scheduled runs.",
|
|
7075
|
+
MAINTENANCE_BARRIER_BUSY: "Another akm process is registering a lock or lease right now. Retry shortly, or pass --skip-if-locked on scheduled index/improve/workflow runs."
|
|
7074
7076
|
};
|
|
7075
7077
|
NOT_FOUND_HINTS = {
|
|
7076
7078
|
ASSET_NOT_FOUND: "Run `akm search <query>` or `akm index` to refresh the index.",
|
|
@@ -75920,7 +75922,6 @@ var EmbeddingConnectionConfigSchema = exports_external.object({
|
|
|
75920
75922
|
maxInputTokens: positiveInt.optional(),
|
|
75921
75923
|
maxTokens: positiveInt.optional(),
|
|
75922
75924
|
batchSize: positiveInt.optional(),
|
|
75923
|
-
chunkSize: positiveInt.optional(),
|
|
75924
75925
|
contextLength: positiveInt.optional(),
|
|
75925
75926
|
ollamaOptions: EmbeddingOllamaOptionsSchema.optional(),
|
|
75926
75927
|
timeoutMs: positiveInt.optional(),
|
|
@@ -77923,10 +77924,13 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
77923
77924
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
77924
77925
|
import fs7 from "node:fs";
|
|
77925
77926
|
import path12 from "node:path";
|
|
77927
|
+
init_common();
|
|
77926
77928
|
init_errors();
|
|
77927
77929
|
init_paths();
|
|
77928
77930
|
var heldBarrierContext = new AsyncLocalStorage;
|
|
77929
77931
|
var MAINTENANCE_BARRIER_STALE_AFTER_MS = 5 * 60 * 1000;
|
|
77932
|
+
var MAINTENANCE_BARRIER_BUSY_RETRY_BOUND_MS = 1500;
|
|
77933
|
+
var busyRetryBoundMsForTests;
|
|
77930
77934
|
function tryAcquireMaintenanceBarrier() {
|
|
77931
77935
|
const lockPath = getMaintenanceBarrierPath();
|
|
77932
77936
|
fs7.mkdirSync(path12.dirname(lockPath), { recursive: true });
|
|
@@ -77942,10 +77946,18 @@ function tryAcquireMaintenanceBarrier() {
|
|
|
77942
77946
|
return;
|
|
77943
77947
|
}
|
|
77944
77948
|
function acquireMaintenanceBarrier() {
|
|
77945
|
-
const
|
|
77946
|
-
|
|
77947
|
-
|
|
77948
|
-
|
|
77949
|
+
const boundMs = busyRetryBoundMsForTests ?? MAINTENANCE_BARRIER_BUSY_RETRY_BOUND_MS;
|
|
77950
|
+
const deadline = Date.now() + boundMs;
|
|
77951
|
+
for (let attempt = 0;; attempt += 1) {
|
|
77952
|
+
const release = tryAcquireMaintenanceBarrier();
|
|
77953
|
+
if (release)
|
|
77954
|
+
return release;
|
|
77955
|
+
const remainingMs = deadline - Date.now();
|
|
77956
|
+
if (remainingMs <= 0)
|
|
77957
|
+
break;
|
|
77958
|
+
sleepSync(Math.min(backoffDelay(attempt), remainingMs));
|
|
77959
|
+
}
|
|
77960
|
+
throw new TransientError(`AKM maintenance is in progress (barrier ${getMaintenanceBarrierPath()}); retry shortly. ` + `A sentinel older than ${MAINTENANCE_BARRIER_STALE_AFTER_MS / 60000} minute(s) is reclaimed automatically on the next attempt.`, "MAINTENANCE_BARRIER_BUSY");
|
|
77949
77961
|
}
|
|
77950
77962
|
function withMaintenanceStartBarrier(run) {
|
|
77951
77963
|
if (heldBarrierContext.getStore()?.active)
|
|
@@ -96987,7 +96999,7 @@ function defaultConcurrencyForEndpoint(endpoint) {
|
|
|
96987
96999
|
// src/llm/embedders/remote.ts
|
|
96988
97000
|
init_warn();
|
|
96989
97001
|
var DEFAULT_REMOTE_BATCH_SIZE = 100;
|
|
96990
|
-
var DEFAULT_TOKEN_BUDGET =
|
|
97002
|
+
var DEFAULT_TOKEN_BUDGET = 6000;
|
|
96991
97003
|
function estimateTokenCount(text) {
|
|
96992
97004
|
return Math.round(text.length / 4);
|
|
96993
97005
|
}
|
|
@@ -97069,6 +97081,8 @@ function buildTokenBoundedBatches(texts, tokenBudget, maxCount) {
|
|
|
97069
97081
|
flush();
|
|
97070
97082
|
return batches;
|
|
97071
97083
|
}
|
|
97084
|
+
var ADAPTIVE_BUDGET_SHRINK_FACTOR = 0.75;
|
|
97085
|
+
var ADAPTIVE_BUDGET_FLOOR_MULTIPLIER = 2;
|
|
97072
97086
|
|
|
97073
97087
|
class RemoteEmbedder {
|
|
97074
97088
|
config;
|
|
@@ -97121,10 +97135,42 @@ class RemoteEmbedder {
|
|
|
97121
97135
|
const results = new Array(texts.length).fill(undefined);
|
|
97122
97136
|
const headers = this.buildHeaders();
|
|
97123
97137
|
const ollamaOpts = resolveOllamaOptions(this.config);
|
|
97124
|
-
|
|
97138
|
+
let effectiveTokenBudget = this.config.maxTokens ?? DEFAULT_TOKEN_BUDGET;
|
|
97125
97139
|
const maxCount = this.config.batchSize ?? DEFAULT_REMOTE_BATCH_SIZE;
|
|
97126
|
-
const
|
|
97140
|
+
const maxInputTokens = this.config.maxInputTokens ?? DEFAULT_MAX_INPUT_TOKENS;
|
|
97141
|
+
const textBatches = buildTokenBoundedBatches(texts, effectiveTokenBudget, maxCount);
|
|
97127
97142
|
const configuredTimeoutMs = resolveEmbeddingTimeoutMs(this.config);
|
|
97143
|
+
let dispatchedBatchCount = 0;
|
|
97144
|
+
let budgetShrunk = false;
|
|
97145
|
+
const maybeShrinkBudget = (rejectedIndices, rejectedBatchIndex, rejectedRequestTokens) => {
|
|
97146
|
+
if (budgetShrunk)
|
|
97147
|
+
return;
|
|
97148
|
+
budgetShrunk = true;
|
|
97149
|
+
const floor2 = ADAPTIVE_BUDGET_FLOOR_MULTIPLIER * maxInputTokens;
|
|
97150
|
+
effectiveTokenBudget = Math.max(Math.round(effectiveTokenBudget * ADAPTIVE_BUDGET_SHRINK_FACTOR), floor2);
|
|
97151
|
+
const notYetDispatched = textBatches.slice(dispatchedBatchCount);
|
|
97152
|
+
const remainingIndices = notYetDispatched.flatMap((batch) => batch.indices);
|
|
97153
|
+
if (remainingIndices.length > 0) {
|
|
97154
|
+
const remainingTexts = remainingIndices.map((i) => texts[i]);
|
|
97155
|
+
const replanned = buildTokenBoundedBatches(remainingTexts, effectiveTokenBudget, maxCount).map((batch) => ({
|
|
97156
|
+
indices: batch.indices.map((localIndex) => remainingIndices[localIndex]),
|
|
97157
|
+
oversized: batch.oversized
|
|
97158
|
+
}));
|
|
97159
|
+
textBatches.splice(dispatchedBatchCount, textBatches.length - dispatchedBatchCount, ...replanned);
|
|
97160
|
+
}
|
|
97161
|
+
warnVerbose(`[embed] provider rejected a ${rejectedRequestTokens}-token request as over its context; request budget lowered to ${effectiveTokenBudget.toLocaleString()} for the rest of this run`);
|
|
97162
|
+
commitBatch(rejectedIndices, rejectedIndices.map(() => {
|
|
97163
|
+
return;
|
|
97164
|
+
}), undefined, {
|
|
97165
|
+
batchIndex: rejectedBatchIndex,
|
|
97166
|
+
batchCount: textBatches.length,
|
|
97167
|
+
docCount: rejectedIndices.length,
|
|
97168
|
+
requestTokens: rejectedRequestTokens,
|
|
97169
|
+
elapsedMs: 0,
|
|
97170
|
+
outcome: "budget-lowered",
|
|
97171
|
+
reason: `provider rejected ${rejectedRequestTokens.toLocaleString()} tokens as over its context; request budget lowered to ${effectiveTokenBudget.toLocaleString()} for the rest of this run`
|
|
97172
|
+
});
|
|
97173
|
+
};
|
|
97128
97174
|
const dispatchAbort = new AbortController;
|
|
97129
97175
|
const stopDispatch = (reason) => {
|
|
97130
97176
|
if (!dispatchAbort.signal.aborted)
|
|
@@ -97157,7 +97203,7 @@ class RemoteEmbedder {
|
|
|
97157
97203
|
return;
|
|
97158
97204
|
const batch = indices.map((i) => texts[i]);
|
|
97159
97205
|
const requestTokens = batch.reduce((sum, text) => sum + estimateTokenCount(text), 0);
|
|
97160
|
-
const requestTimeoutMs = scaleEmbeddingTimeoutMs(configuredTimeoutMs, requestTokens,
|
|
97206
|
+
const requestTimeoutMs = scaleEmbeddingTimeoutMs(configuredTimeoutMs, requestTokens, effectiveTokenBudget);
|
|
97161
97207
|
const requestStart = Date.now();
|
|
97162
97208
|
let batchEmbeddings;
|
|
97163
97209
|
let responseModel;
|
|
@@ -97174,6 +97220,9 @@ class RemoteEmbedder {
|
|
|
97174
97220
|
} catch (err) {
|
|
97175
97221
|
if (signal?.aborted)
|
|
97176
97222
|
throw err;
|
|
97223
|
+
if (err instanceof ContextExceededError) {
|
|
97224
|
+
maybeShrinkBudget(indices, batchIndex, requestTokens);
|
|
97225
|
+
}
|
|
97177
97226
|
if (err instanceof ContextExceededError && indices.length > 1) {
|
|
97178
97227
|
const mid = Math.ceil(indices.length / 2);
|
|
97179
97228
|
await requestAndCommit(indices.slice(0, mid), batchIndex, false, timeoutAttempt);
|
|
@@ -97239,13 +97288,14 @@ class RemoteEmbedder {
|
|
|
97239
97288
|
});
|
|
97240
97289
|
};
|
|
97241
97290
|
const runProviderBatch = async (textBatch, batchIndex) => {
|
|
97291
|
+
dispatchedBatchCount = batchIndex;
|
|
97242
97292
|
if (textBatch.oversized) {
|
|
97243
97293
|
const idx = textBatch.indices[0];
|
|
97244
97294
|
const estTokens = estimateTokenCount(texts[idx]);
|
|
97245
97295
|
onSkip?.({
|
|
97246
97296
|
index: idx,
|
|
97247
97297
|
reason: "context-window-exceeded",
|
|
97248
|
-
message: `Document estimated at ${estTokens} tokens exceeds the ${
|
|
97298
|
+
message: `Document estimated at ${estTokens} tokens exceeds the ${effectiveTokenBudget}-token embedding budget; skipped.`,
|
|
97249
97299
|
batchStart: true,
|
|
97250
97300
|
batchSize: 1
|
|
97251
97301
|
});
|
|
@@ -97469,7 +97519,7 @@ function deriveObservedEmbeddingIdentity(embedding, observedModel, observedVecto
|
|
|
97469
97519
|
}
|
|
97470
97520
|
return `local:${embedding?.localModel ?? DEFAULT_LOCAL_MODEL}|${observedVectorLen}`;
|
|
97471
97521
|
}
|
|
97472
|
-
async function runEmbeddingCanary(db, config, signal) {
|
|
97522
|
+
async function runEmbeddingCanary(db, config, signal, maxInputTokens) {
|
|
97473
97523
|
const samples = sampleEmbeddedEntriesForCanary(db, CANARY_SAMPLE_SIZE);
|
|
97474
97524
|
if (samples.length === 0) {
|
|
97475
97525
|
return { outcome: "keep", verified: false, viaIdentityMatch: false };
|
|
@@ -97478,7 +97528,7 @@ async function runEmbeddingCanary(db, config, signal) {
|
|
|
97478
97528
|
const skips = [];
|
|
97479
97529
|
let canaryVectors;
|
|
97480
97530
|
try {
|
|
97481
|
-
canaryVectors = await embedBatch(samples.map((sample) => sample.searchText), config.embedding, signal, (skip) => skips.push(skip), (_indices, _embeddings, model) => {
|
|
97531
|
+
canaryVectors = await embedBatch(samples.map((sample) => capEmbeddingText(sample.searchText, maxInputTokens).text), config.embedding, signal, (skip) => skips.push(skip), (_indices, _embeddings, model) => {
|
|
97482
97532
|
if (model)
|
|
97483
97533
|
observedModel = model;
|
|
97484
97534
|
});
|
|
@@ -97548,6 +97598,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97548
97598
|
const storedFingerprint = getMeta(db, "embeddingFingerprint");
|
|
97549
97599
|
let targetEntryIds = entryIds;
|
|
97550
97600
|
let rebuildReason;
|
|
97601
|
+
const maxInputTokens = config.embedding?.maxInputTokens ?? DEFAULT_MAX_INPUT_TOKENS;
|
|
97551
97602
|
if (opts?.forceReembed) {
|
|
97552
97603
|
db.transaction(() => {
|
|
97553
97604
|
purgeEmbeddings(db, { dropVecTable: true });
|
|
@@ -97559,7 +97610,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97559
97610
|
targetEntryIds = undefined;
|
|
97560
97611
|
rebuildReason = "forced by --reembed";
|
|
97561
97612
|
} else if (storedFingerprint && storedFingerprint !== currentFingerprint) {
|
|
97562
|
-
const decision = await runEmbeddingCanary(db, config, signal);
|
|
97613
|
+
const decision = await runEmbeddingCanary(db, config, signal, maxInputTokens);
|
|
97563
97614
|
if (decision.outcome === "unverifiable") {
|
|
97564
97615
|
warn(`[embed] ${decision.message}`);
|
|
97565
97616
|
onProgress({ phase: "embeddings", message: decision.message });
|
|
@@ -97623,7 +97674,6 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97623
97674
|
purgeEmbeddingSalvage(db);
|
|
97624
97675
|
return reusedCount > 0 ? { success: true, vecInsertFailures: vecFailedCount } : { success: true };
|
|
97625
97676
|
}
|
|
97626
|
-
const maxInputTokens = config.embedding?.maxInputTokens ?? DEFAULT_MAX_INPUT_TOKENS;
|
|
97627
97677
|
let truncatedCount = 0;
|
|
97628
97678
|
const texts = [];
|
|
97629
97679
|
const pendingEntries = [];
|
|
@@ -97718,6 +97768,15 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97718
97768
|
}
|
|
97719
97769
|
return;
|
|
97720
97770
|
}
|
|
97771
|
+
if (outcome?.outcome === "budget-lowered") {
|
|
97772
|
+
if (reportPerBatchLine) {
|
|
97773
|
+
onProgress({
|
|
97774
|
+
phase: "embeddings",
|
|
97775
|
+
message: `[embed] batch ${outcome.batchIndex}/${outcome.batchCount}: ${outcome.docCount} docs, ${outcome.requestTokens.toLocaleString()} tokens → ${outcome.reason}`
|
|
97776
|
+
});
|
|
97777
|
+
}
|
|
97778
|
+
return;
|
|
97779
|
+
}
|
|
97721
97780
|
if (model)
|
|
97722
97781
|
observedModel = model;
|
|
97723
97782
|
if (batchEmbeddings.some((embedding) => embedding !== undefined)) {
|
|
@@ -97726,7 +97785,8 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97726
97785
|
}
|
|
97727
97786
|
db.transaction(() => {
|
|
97728
97787
|
for (let k2 = 0;k2 < indices.length; k2++) {
|
|
97729
|
-
const
|
|
97788
|
+
const index = indices[k2];
|
|
97789
|
+
const entry = pendingEntries[index];
|
|
97730
97790
|
if (!entry)
|
|
97731
97791
|
continue;
|
|
97732
97792
|
const embedding = batchEmbeddings[k2];
|
|
@@ -97739,7 +97799,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97739
97799
|
const result = upsertEmbedding(db, entry.id, embedding);
|
|
97740
97800
|
if (result.stored) {
|
|
97741
97801
|
storedCount++;
|
|
97742
|
-
storedTokens += estimateTokenCount(
|
|
97802
|
+
storedTokens += estimateTokenCount(texts[index]);
|
|
97743
97803
|
} else {
|
|
97744
97804
|
skippedCount++;
|
|
97745
97805
|
}
|
|
@@ -7069,7 +7069,9 @@ var init_errors = __esm(() => {
|
|
|
7069
7069
|
};
|
|
7070
7070
|
TRANSIENT_HINTS = {
|
|
7071
7071
|
RUN_LEASE_HELD: "Wait for the named engine invocation to finish or for the lease to expire, then retry. `akm workflow status <id>` shows the current lease.",
|
|
7072
|
-
STATE_DB_CONTENDED: "Another akm process is writing state.db right now. Wait a few seconds and retry; commands that support --skip-if-locked can skip instead of failing."
|
|
7072
|
+
STATE_DB_CONTENDED: "Another akm process is writing state.db right now. Wait a few seconds and retry; commands that support --skip-if-locked can skip instead of failing.",
|
|
7073
|
+
INDEX_DB_CONTENDED: "Another akm process is writing index.db; retry shortly, or pass --skip-if-locked on scheduled runs.",
|
|
7074
|
+
MAINTENANCE_BARRIER_BUSY: "Another akm process is registering a lock or lease right now. Retry shortly, or pass --skip-if-locked on scheduled index/improve/workflow runs."
|
|
7073
7075
|
};
|
|
7074
7076
|
NOT_FOUND_HINTS = {
|
|
7075
7077
|
ASSET_NOT_FOUND: "Run `akm search <query>` or `akm index` to refresh the index.",
|
|
@@ -75248,7 +75250,6 @@ var EmbeddingConnectionConfigSchema = exports_external.object({
|
|
|
75248
75250
|
maxInputTokens: positiveInt.optional(),
|
|
75249
75251
|
maxTokens: positiveInt.optional(),
|
|
75250
75252
|
batchSize: positiveInt.optional(),
|
|
75251
|
-
chunkSize: positiveInt.optional(),
|
|
75252
75253
|
contextLength: positiveInt.optional(),
|
|
75253
75254
|
ollamaOptions: EmbeddingOllamaOptionsSchema.optional(),
|
|
75254
75255
|
timeoutMs: positiveInt.optional(),
|
|
@@ -77251,10 +77252,13 @@ import { AsyncLocalStorage } from "async_hooks";
|
|
|
77251
77252
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
77252
77253
|
import fs7 from "fs";
|
|
77253
77254
|
import path12 from "path";
|
|
77255
|
+
init_common();
|
|
77254
77256
|
init_errors();
|
|
77255
77257
|
init_paths();
|
|
77256
77258
|
var heldBarrierContext = new AsyncLocalStorage;
|
|
77257
77259
|
var MAINTENANCE_BARRIER_STALE_AFTER_MS = 5 * 60 * 1000;
|
|
77260
|
+
var MAINTENANCE_BARRIER_BUSY_RETRY_BOUND_MS = 1500;
|
|
77261
|
+
var busyRetryBoundMsForTests;
|
|
77258
77262
|
function tryAcquireMaintenanceBarrier() {
|
|
77259
77263
|
const lockPath = getMaintenanceBarrierPath();
|
|
77260
77264
|
fs7.mkdirSync(path12.dirname(lockPath), { recursive: true });
|
|
@@ -77270,10 +77274,18 @@ function tryAcquireMaintenanceBarrier() {
|
|
|
77270
77274
|
return;
|
|
77271
77275
|
}
|
|
77272
77276
|
function acquireMaintenanceBarrier() {
|
|
77273
|
-
const
|
|
77274
|
-
|
|
77275
|
-
|
|
77276
|
-
|
|
77277
|
+
const boundMs = busyRetryBoundMsForTests ?? MAINTENANCE_BARRIER_BUSY_RETRY_BOUND_MS;
|
|
77278
|
+
const deadline = Date.now() + boundMs;
|
|
77279
|
+
for (let attempt = 0;; attempt += 1) {
|
|
77280
|
+
const release = tryAcquireMaintenanceBarrier();
|
|
77281
|
+
if (release)
|
|
77282
|
+
return release;
|
|
77283
|
+
const remainingMs = deadline - Date.now();
|
|
77284
|
+
if (remainingMs <= 0)
|
|
77285
|
+
break;
|
|
77286
|
+
sleepSync(Math.min(backoffDelay(attempt), remainingMs));
|
|
77287
|
+
}
|
|
77288
|
+
throw new TransientError(`AKM maintenance is in progress (barrier ${getMaintenanceBarrierPath()}); retry shortly. ` + `A sentinel older than ${MAINTENANCE_BARRIER_STALE_AFTER_MS / 60000} minute(s) is reclaimed automatically on the next attempt.`, "MAINTENANCE_BARRIER_BUSY");
|
|
77277
77289
|
}
|
|
77278
77290
|
function withMaintenanceStartBarrier(run) {
|
|
77279
77291
|
if (heldBarrierContext.getStore()?.active)
|
|
@@ -96943,7 +96955,7 @@ function defaultConcurrencyForEndpoint(endpoint) {
|
|
|
96943
96955
|
// src/llm/embedders/remote.ts
|
|
96944
96956
|
init_warn();
|
|
96945
96957
|
var DEFAULT_REMOTE_BATCH_SIZE = 100;
|
|
96946
|
-
var DEFAULT_TOKEN_BUDGET =
|
|
96958
|
+
var DEFAULT_TOKEN_BUDGET = 6000;
|
|
96947
96959
|
function estimateTokenCount(text) {
|
|
96948
96960
|
return Math.round(text.length / 4);
|
|
96949
96961
|
}
|
|
@@ -97025,6 +97037,8 @@ function buildTokenBoundedBatches(texts, tokenBudget, maxCount) {
|
|
|
97025
97037
|
flush();
|
|
97026
97038
|
return batches;
|
|
97027
97039
|
}
|
|
97040
|
+
var ADAPTIVE_BUDGET_SHRINK_FACTOR = 0.75;
|
|
97041
|
+
var ADAPTIVE_BUDGET_FLOOR_MULTIPLIER = 2;
|
|
97028
97042
|
|
|
97029
97043
|
class RemoteEmbedder {
|
|
97030
97044
|
config;
|
|
@@ -97077,10 +97091,42 @@ class RemoteEmbedder {
|
|
|
97077
97091
|
const results = new Array(texts.length).fill(undefined);
|
|
97078
97092
|
const headers = this.buildHeaders();
|
|
97079
97093
|
const ollamaOpts = resolveOllamaOptions(this.config);
|
|
97080
|
-
|
|
97094
|
+
let effectiveTokenBudget = this.config.maxTokens ?? DEFAULT_TOKEN_BUDGET;
|
|
97081
97095
|
const maxCount = this.config.batchSize ?? DEFAULT_REMOTE_BATCH_SIZE;
|
|
97082
|
-
const
|
|
97096
|
+
const maxInputTokens = this.config.maxInputTokens ?? DEFAULT_MAX_INPUT_TOKENS;
|
|
97097
|
+
const textBatches = buildTokenBoundedBatches(texts, effectiveTokenBudget, maxCount);
|
|
97083
97098
|
const configuredTimeoutMs = resolveEmbeddingTimeoutMs(this.config);
|
|
97099
|
+
let dispatchedBatchCount = 0;
|
|
97100
|
+
let budgetShrunk = false;
|
|
97101
|
+
const maybeShrinkBudget = (rejectedIndices, rejectedBatchIndex, rejectedRequestTokens) => {
|
|
97102
|
+
if (budgetShrunk)
|
|
97103
|
+
return;
|
|
97104
|
+
budgetShrunk = true;
|
|
97105
|
+
const floor2 = ADAPTIVE_BUDGET_FLOOR_MULTIPLIER * maxInputTokens;
|
|
97106
|
+
effectiveTokenBudget = Math.max(Math.round(effectiveTokenBudget * ADAPTIVE_BUDGET_SHRINK_FACTOR), floor2);
|
|
97107
|
+
const notYetDispatched = textBatches.slice(dispatchedBatchCount);
|
|
97108
|
+
const remainingIndices = notYetDispatched.flatMap((batch) => batch.indices);
|
|
97109
|
+
if (remainingIndices.length > 0) {
|
|
97110
|
+
const remainingTexts = remainingIndices.map((i) => texts[i]);
|
|
97111
|
+
const replanned = buildTokenBoundedBatches(remainingTexts, effectiveTokenBudget, maxCount).map((batch) => ({
|
|
97112
|
+
indices: batch.indices.map((localIndex) => remainingIndices[localIndex]),
|
|
97113
|
+
oversized: batch.oversized
|
|
97114
|
+
}));
|
|
97115
|
+
textBatches.splice(dispatchedBatchCount, textBatches.length - dispatchedBatchCount, ...replanned);
|
|
97116
|
+
}
|
|
97117
|
+
warnVerbose(`[embed] provider rejected a ${rejectedRequestTokens}-token request as over its context; request budget lowered to ${effectiveTokenBudget.toLocaleString()} for the rest of this run`);
|
|
97118
|
+
commitBatch(rejectedIndices, rejectedIndices.map(() => {
|
|
97119
|
+
return;
|
|
97120
|
+
}), undefined, {
|
|
97121
|
+
batchIndex: rejectedBatchIndex,
|
|
97122
|
+
batchCount: textBatches.length,
|
|
97123
|
+
docCount: rejectedIndices.length,
|
|
97124
|
+
requestTokens: rejectedRequestTokens,
|
|
97125
|
+
elapsedMs: 0,
|
|
97126
|
+
outcome: "budget-lowered",
|
|
97127
|
+
reason: `provider rejected ${rejectedRequestTokens.toLocaleString()} tokens as over its context; request budget lowered to ${effectiveTokenBudget.toLocaleString()} for the rest of this run`
|
|
97128
|
+
});
|
|
97129
|
+
};
|
|
97084
97130
|
const dispatchAbort = new AbortController;
|
|
97085
97131
|
const stopDispatch = (reason) => {
|
|
97086
97132
|
if (!dispatchAbort.signal.aborted)
|
|
@@ -97113,7 +97159,7 @@ class RemoteEmbedder {
|
|
|
97113
97159
|
return;
|
|
97114
97160
|
const batch = indices.map((i) => texts[i]);
|
|
97115
97161
|
const requestTokens = batch.reduce((sum, text) => sum + estimateTokenCount(text), 0);
|
|
97116
|
-
const requestTimeoutMs = scaleEmbeddingTimeoutMs(configuredTimeoutMs, requestTokens,
|
|
97162
|
+
const requestTimeoutMs = scaleEmbeddingTimeoutMs(configuredTimeoutMs, requestTokens, effectiveTokenBudget);
|
|
97117
97163
|
const requestStart = Date.now();
|
|
97118
97164
|
let batchEmbeddings;
|
|
97119
97165
|
let responseModel;
|
|
@@ -97130,6 +97176,9 @@ class RemoteEmbedder {
|
|
|
97130
97176
|
} catch (err) {
|
|
97131
97177
|
if (signal?.aborted)
|
|
97132
97178
|
throw err;
|
|
97179
|
+
if (err instanceof ContextExceededError) {
|
|
97180
|
+
maybeShrinkBudget(indices, batchIndex, requestTokens);
|
|
97181
|
+
}
|
|
97133
97182
|
if (err instanceof ContextExceededError && indices.length > 1) {
|
|
97134
97183
|
const mid = Math.ceil(indices.length / 2);
|
|
97135
97184
|
await requestAndCommit(indices.slice(0, mid), batchIndex, false, timeoutAttempt);
|
|
@@ -97195,13 +97244,14 @@ class RemoteEmbedder {
|
|
|
97195
97244
|
});
|
|
97196
97245
|
};
|
|
97197
97246
|
const runProviderBatch = async (textBatch, batchIndex) => {
|
|
97247
|
+
dispatchedBatchCount = batchIndex;
|
|
97198
97248
|
if (textBatch.oversized) {
|
|
97199
97249
|
const idx = textBatch.indices[0];
|
|
97200
97250
|
const estTokens = estimateTokenCount(texts[idx]);
|
|
97201
97251
|
onSkip?.({
|
|
97202
97252
|
index: idx,
|
|
97203
97253
|
reason: "context-window-exceeded",
|
|
97204
|
-
message: `Document estimated at ${estTokens} tokens exceeds the ${
|
|
97254
|
+
message: `Document estimated at ${estTokens} tokens exceeds the ${effectiveTokenBudget}-token embedding budget; skipped.`,
|
|
97205
97255
|
batchStart: true,
|
|
97206
97256
|
batchSize: 1
|
|
97207
97257
|
});
|
|
@@ -97425,7 +97475,7 @@ function deriveObservedEmbeddingIdentity(embedding, observedModel, observedVecto
|
|
|
97425
97475
|
}
|
|
97426
97476
|
return `local:${embedding?.localModel ?? DEFAULT_LOCAL_MODEL}|${observedVectorLen}`;
|
|
97427
97477
|
}
|
|
97428
|
-
async function runEmbeddingCanary(db, config, signal) {
|
|
97478
|
+
async function runEmbeddingCanary(db, config, signal, maxInputTokens) {
|
|
97429
97479
|
const samples = sampleEmbeddedEntriesForCanary(db, CANARY_SAMPLE_SIZE);
|
|
97430
97480
|
if (samples.length === 0) {
|
|
97431
97481
|
return { outcome: "keep", verified: false, viaIdentityMatch: false };
|
|
@@ -97434,7 +97484,7 @@ async function runEmbeddingCanary(db, config, signal) {
|
|
|
97434
97484
|
const skips = [];
|
|
97435
97485
|
let canaryVectors;
|
|
97436
97486
|
try {
|
|
97437
|
-
canaryVectors = await embedBatch(samples.map((sample) => sample.searchText), config.embedding, signal, (skip) => skips.push(skip), (_indices, _embeddings, model) => {
|
|
97487
|
+
canaryVectors = await embedBatch(samples.map((sample) => capEmbeddingText(sample.searchText, maxInputTokens).text), config.embedding, signal, (skip) => skips.push(skip), (_indices, _embeddings, model) => {
|
|
97438
97488
|
if (model)
|
|
97439
97489
|
observedModel = model;
|
|
97440
97490
|
});
|
|
@@ -97504,6 +97554,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97504
97554
|
const storedFingerprint = getMeta(db, "embeddingFingerprint");
|
|
97505
97555
|
let targetEntryIds = entryIds;
|
|
97506
97556
|
let rebuildReason;
|
|
97557
|
+
const maxInputTokens = config.embedding?.maxInputTokens ?? DEFAULT_MAX_INPUT_TOKENS;
|
|
97507
97558
|
if (opts?.forceReembed) {
|
|
97508
97559
|
db.transaction(() => {
|
|
97509
97560
|
purgeEmbeddings(db, { dropVecTable: true });
|
|
@@ -97515,7 +97566,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97515
97566
|
targetEntryIds = undefined;
|
|
97516
97567
|
rebuildReason = "forced by --reembed";
|
|
97517
97568
|
} else if (storedFingerprint && storedFingerprint !== currentFingerprint) {
|
|
97518
|
-
const decision = await runEmbeddingCanary(db, config, signal);
|
|
97569
|
+
const decision = await runEmbeddingCanary(db, config, signal, maxInputTokens);
|
|
97519
97570
|
if (decision.outcome === "unverifiable") {
|
|
97520
97571
|
warn(`[embed] ${decision.message}`);
|
|
97521
97572
|
onProgress({ phase: "embeddings", message: decision.message });
|
|
@@ -97579,7 +97630,6 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97579
97630
|
purgeEmbeddingSalvage(db);
|
|
97580
97631
|
return reusedCount > 0 ? { success: true, vecInsertFailures: vecFailedCount } : { success: true };
|
|
97581
97632
|
}
|
|
97582
|
-
const maxInputTokens = config.embedding?.maxInputTokens ?? DEFAULT_MAX_INPUT_TOKENS;
|
|
97583
97633
|
let truncatedCount = 0;
|
|
97584
97634
|
const texts = [];
|
|
97585
97635
|
const pendingEntries = [];
|
|
@@ -97674,6 +97724,15 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97674
97724
|
}
|
|
97675
97725
|
return;
|
|
97676
97726
|
}
|
|
97727
|
+
if (outcome?.outcome === "budget-lowered") {
|
|
97728
|
+
if (reportPerBatchLine) {
|
|
97729
|
+
onProgress({
|
|
97730
|
+
phase: "embeddings",
|
|
97731
|
+
message: `[embed] batch ${outcome.batchIndex}/${outcome.batchCount}: ${outcome.docCount} docs, ${outcome.requestTokens.toLocaleString()} tokens \u2192 ${outcome.reason}`
|
|
97732
|
+
});
|
|
97733
|
+
}
|
|
97734
|
+
return;
|
|
97735
|
+
}
|
|
97677
97736
|
if (model)
|
|
97678
97737
|
observedModel = model;
|
|
97679
97738
|
if (batchEmbeddings.some((embedding) => embedding !== undefined)) {
|
|
@@ -97682,7 +97741,8 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97682
97741
|
}
|
|
97683
97742
|
db.transaction(() => {
|
|
97684
97743
|
for (let k2 = 0;k2 < indices.length; k2++) {
|
|
97685
|
-
const
|
|
97744
|
+
const index = indices[k2];
|
|
97745
|
+
const entry = pendingEntries[index];
|
|
97686
97746
|
if (!entry)
|
|
97687
97747
|
continue;
|
|
97688
97748
|
const embedding = batchEmbeddings[k2];
|
|
@@ -97695,7 +97755,7 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds,
|
|
|
97695
97755
|
const result = upsertEmbedding(db, entry.id, embedding);
|
|
97696
97756
|
if (result.stored) {
|
|
97697
97757
|
storedCount++;
|
|
97698
|
-
storedTokens += estimateTokenCount(
|
|
97758
|
+
storedTokens += estimateTokenCount(texts[index]);
|
|
97699
97759
|
} else {
|
|
97700
97760
|
skippedCount++;
|
|
97701
97761
|
}
|