/** * Semantic ranking behind a synchronous engine contract. * * THIS FILE EXISTS BECAUSE I GOT THE ARCHITECTURE WRONG ONCE, and the wrong * answer is worth stating so it is not re-derived. `ranking.ts` shipped with a * claim that a model could not be bundled: the engines are synchronous pure * functions, `onnxruntime-node`'s `session.run()` is async, and the units to * embed -- rows, lines, sentences, declarations -- only exist after an engine * has parsed a block, so they cannot be encoded before it runs. * * The last clause is false, and it was the one holding the conclusion up. * Parsing is cheap and pure. Nothing stops a pre-pass from asking each engine * what units it WOULD rank, embedding all of them in one batch, and handing * the sync engines a cache to read. The async work moves to the request level, * which is exactly where `loadFindings` already does its I/O. * * SO THE SHAPE IS TWO PHASES: * * 1. async, once per request -- `warmEmbeddings` walks the blocks, collects * candidate units from the registered engines, and embeds everything it * has not seen before in a single batched call; * 2. sync, per block, unchanged -- the engines run exactly as they always * have, and the ranker reads vectors out of the cache. * * A UNIT THAT MISSED THE PRE-PASS FALLS BACK TO BM25 rather than being scored * zero. Content can appear that phase one did not see -- an engine's unit * extraction is a best effort, not a contract -- and a unit silently scoring * zero would be dropped as irrelevant when the truth is that nobody asked the * model about it. Lexical is the floor, never nothing. * * NONE OF THIS IS ON BY DEFAULT. BM25 remains the shipped ranker: no Python, * no weights, no RAM floor, deterministic, and cache-stable. This is the door * for someone who wants more and can pay for it. */ import { type Ranker } from './relevance.js'; /** Turns text into vectors. Async, because every real model is. */ export interface SemanticEncoder { /** Vector width. Used to validate what comes back rather than to trust it. */ readonly dimensions: number; /** * Embeds a batch. * * BATCHED, NOT PER-UNIT, because per-call overhead dominates for small * inputs -- a log line is a dozen tokens and a session may hold thousands of * them. Must return one vector per input, in order. */ encode(texts: readonly string[]): Promise; } /** Vectors already computed, keyed by the exact text they came from. */ export interface EmbeddingCache { get(text: string): Float32Array | undefined; has(text: string): boolean; readonly size: number; } interface MutableCache extends EmbeddingCache { set(text: string, vector: Float32Array): void; } /** * How many units one request may embed. * * A bound rather than a guess: a large session can hold tens of thousands of * candidate units, and embedding all of them would cost more time than the * compression saves. The cap is applied AFTER ordering by size, so the units * that survive are the ones whose retention decision matters most. */ export declare const MAX_UNITS_PER_REQUEST = 2048; /** An LRU-free cache: a request's working set is bounded by the cap above. */ export declare function embeddingCache(): EmbeddingCache & MutableCache; /** * A ranker backed by precomputed vectors, with BM25 underneath. * * Synchronous, which is the whole point: it is called from inside the engines * and does nothing but arithmetic over vectors phase one already computed. */ export declare function semanticRanker(query: string | undefined, cache: EmbeddingCache): Ranker; /** * Phase one: embed everything the engines might rank, in one batch. * * `texts` is whatever the caller can collect cheaply -- see `unitsOf` in * `router.ts`, which asks each registered engine. Order is by length * descending before the cap, so when there is more content than budget the * units that survive are the substantial ones rather than whichever happened * to come first. * * FAILS SOFT, ALWAYS. An encoder that throws, hangs past its budget, or * returns the wrong shape leaves the cache as it was, and every engine falls * back to BM25. A model is far likelier to fail than a word count, and the * proxy's rule is that nothing it does may cost the request. */ export declare function warmEmbeddings(encoder: SemanticEncoder, texts: readonly string[], cache: EmbeddingCache & MutableCache, maxUnits?: number): Promise; /** * The units a request could rank, gathered without running any engine. * * Deliberately generic: splitting on lines and sentence boundaries covers what * `log`, `search`, `prose` and `json`-row ranking actually score, and costs a * regex rather than a parse. An engine whose units are stranger than this * simply gets lexical ranking for them, which is the documented floor. */ export declare function candidateUnits(text: string): string[]; /** True when a query has enough content to be worth embedding at all. */ export declare function queryIsUsable(query: string | undefined): boolean; export {}; //# sourceMappingURL=embedding.d.ts.map