/** * Embedding engine internals. * * `EmbeddingEngine` owns one GGUF model + llama.cpp context and serializes * embed calls against it. Multiple engines can coexist in one process — one * per registered Harper model entry — so the native addon binding is shared * through a process-resident registry keyed by addon path (dlopen of the same * path returns the same handle; llama.cpp keeps all state in heap objects, so * engines stay independent through the shared native code). */ import { type PoolingName } from './gguf.js'; /** * Per-inputType prompt templates, declared as data on a model entry (built-in * registry or `templates` in engine/config options) instead of detected by * model-name heuristics in code. See issue #4. * * Placeholders are `{name}` tokens: `{text}` is the input text (always * provided); `{task}` comes from the embed call's `task` option, falling back * to `defaults.task`; any other placeholder must have a value in `defaults`. * Literal braces are escaped as `{{` / `}}`. Interpolation is single-pass — * placeholder values are never re-scanned for placeholders. * * A missing template for an inputType means passthrough, and an omitted * `inputType` is ALWAYS passthrough regardless of templates — that is the * compatibility contract that keeps vectors from older versions comparable. */ export interface EmbedTemplates { /** Template applied when `inputType: 'document'`. */ document?: string; /** Template applied when `inputType: 'query'`. */ query?: string; /** Fallback values for non-`{text}` placeholders (e.g. `task`, `title`). */ defaults?: Record; } export interface EngineOptions { /** Absolute path to a .gguf model file. */ modelPath?: string; /** Directory containing (or to download to) model files. */ modelsDir?: string; /** Model name from the built-in registry. */ modelName?: string; /** Token context window size. */ contextSize?: number; /** * Batch processing size. In node-llama-cpp this sets both `n_batch` AND * `n_ubatch` on the llama.cpp context. llama.cpp's encoder asserts * `n_ubatch >= n_tokens` for every input — inputs that tokenize above * `batchSize` trigger `GGML_ASSERT` → `ggml_abort`, killing the host * process. Defaults to `contextSize` so the full context window is * usable out of the box. */ batchSize?: number; /** CPU threads for inference. */ threads?: number; /** Layers to offload to GPU (0 = CPU only). */ gpuLayers?: number; /** Override path to llama-addon.node. */ addonPath?: string; /** * Prompt templates for this model. Overrides the built-in registry entry's * templates; validated at construction (registration time). Without this — * and without registry templates — template-less models fall back to the * legacy nomic name-prefix heuristic. */ templates?: EmbedTemplates; /** * Expected pooling for this model (`'none' | 'mean' | 'cls' | 'last' | * 'rank'`). Verification, not override: the native addon exposes no pooling * option, so the llama.cpp context always uses the model's own * `.pooling_type` metadata. Declaring the expectation makes init fail * loudly when the GGUF omits or contradicts it — the alternative is a * metadata-less conversion silently mean-pooling a last-token model * (issue #12). Omitted = accept whatever the model resolves to. */ pooling?: PoolingName; } export interface EmbedManyOptions { /** * For models that distinguish document-vs-query embeddings. Applies the * model's template for that side (or the legacy nomic prefix for * template-less models). Omitted = passthrough, always. */ inputType?: 'document' | 'query'; /** * Free-text task instruction for models whose templates use `{task}` * (instruct-style embedders). Overrides the entry's `templates.defaults.task`. */ task?: string; /** Best-effort cancellation — checked between inputs, not mid-decode. */ signal?: AbortSignal; } export interface EmbedManyResult { /** One L2-normalized vector per input, in input order. */ vectors: Float32Array[]; /** Total tokens decoded across all inputs (including BOS/EOS). */ tokens: number; } /** * Native embedding context handle. Exported because `decodeAndEmbed` — public * so tests can exercise it against a fake — takes one as a parameter; * `declaration: true` requires every type reachable from an exported * signature to itself be exported. */ export interface LlamaContext { init(): Promise; dispose(): Promise; initBatch(size: number): void; addToBatch(seq: number, pos: number, tokens: Uint32Array, logitIndexes: Uint32Array): void; decodeBatch(): Promise; getEmbedding(tokenCount: number): Float32Array; /** * Evict every KV-cache cell for a sequence (`llama_memory_seq_rm(seq, -1, -1)` * under the hood). Synchronous; throws if the native eviction fails. A no-op * on a sequence with no cached cells (e.g. a freshly created context). */ disposeSequence(seq: number): void; } /** * One GGUF model + llama.cpp embedding context. * * - Construction validates configuration synchronously (missing model source, * unknown model name) so misconfiguration fails at registration, not first use. * - Heavy work (download, addon + model load) happens in `ensureReady()`, which * is lazy, shared across concurrent callers, and retryable after failure. * - `embedMany()` serializes against a per-engine queue — the llama.cpp context * is not safe for concurrent use. */ export declare class EmbeddingEngine { #private; constructor(options: EngineOptions); /** Model name (or model file basename) — used for backend naming and prefix detection. */ get modelIdentity(): string; /** * Kick off (or join) initialization. Lazy and retryable: a failed attempt * resets so the next call tries again (e.g. a transient download failure). */ ensureReady(): Promise; /** * Generate one L2-normalized vector per input, in input order. * * Serialized against the engine's queue; init is awaited lazily on first * call. `opts.signal` is checked between inputs (best-effort — a decode in * flight can't be interrupted). */ embedMany(texts: string[], opts?: EmbedManyOptions): Promise; /** Get the embedding vector dimensionality. */ dimensions(): number; /** * Clean up native resources. * * Drains the embed queue before touching native handles — disposing the * llama.cpp context while a decodeBatch is executing is a use-after-free * that can kill the host process. Embeds accepted before this call complete * (when the engine is initialized); embeds that would need a fresh init * during disposal, and any submitted after, reject with a disposed error. */ dispose(): Promise; } /** * Decode one token sequence against a single-sequence llama.cpp context * (`sequences: 1` — see `#doInit`) and return its L2-normalized embedding. * * Always decodes at `seq=0, pos=0`: there is exactly one KV-cache sequence * slot on the context, reused across every call. `disposeSequence(0)` evicts * whatever cache cells the *previous* call on this context left behind * before writing new cells at pos 0 — without it, a second decode on the * same context sees inconsistent KV-cache/position state at pos 0 and * `llama_decode` hard-aborts the host process (a native `GGML_ASSERT`, not a * catchable JS error). A no-op on a fresh context (issue #8). * * A free function (not a private method) so it can be unit tested against a * fake `LlamaContext` double — the real native context can't be constructed * without a loaded GGUF model. */ export declare function decodeAndEmbed(context: LlamaContext, input: Uint32Array): Promise<{ vector: Float32Array; tokens: number; }>; /** * Resolve the templates an engine will use: explicit `options.templates` wins; * a registry model falls back to its entry's templates; an explicit `modelPath` * with no explicit templates gets none (the legacy name-prefix heuristic still * applies at embed time). Exported for tests — the models-backend production * path constructs via `modelName` and must resolve the registry branch. */ export declare function resolveEngineTemplates(options: EngineOptions): EmbedTemplates | undefined; /** * Validate templates at construction (registration) time, so misconfiguration * fails at Harper boot instead of on the first embed call. Unrecognized * top-level keys are rejected (a typo'd side like `documnet:` would otherwise * silently fall back to unprefixed embeds); every placeholder must be `{text}`, * `{task}` (call-suppliable), or covered by `defaults`; any other `{`/`}` must * be escaped as `{{` / `}}`. */ export declare function validateTemplates(templates: EmbedTemplates): void; /** Single-pass interpolation. `vars` must cover every placeholder (validated at registration for all but a default-less `{task}`). */ export declare function renderTemplate(template: string, vars: Record): string; /** * Download a model from Hugging Face. * * Cross-worker coordination: exclusive creation of `.downloading` elects * one downloader; the rest poll. A lock whose owner died is reclaimed after * `LOCK_STALE_MS`, and a waiter whose winner failed (lock vanished, no final * file) retakes the lock and retries rather than timing out. */ export declare function downloadModel(dir: string, modelName?: string): Promise; //# sourceMappingURL=engine.d.ts.map