/** * provider.ts - Embedding provider abstraction * * Defines the EmbeddingProvider interface that allows qmd to use either: * - LocalLlamaCppProvider (legacy, GGUF via node-llama-cpp) * - OpenAIEmbeddingsProvider (HTTP, OpenAI-compatible endpoint like ai.mm.mk) * * The factory in `./factory.ts` selects an implementation based on env vars, * a CLI flag, or `~/.config/qmd/config.json`. */ /** * Single embedding result */ export type ProviderEmbedding = { embedding: number[]; /** Model identifier used to produce this embedding (matches content_vectors.model in DB) */ model: string; }; /** * Supported provider kinds */ export type ProviderKind = "local" | "openai"; /** * Healthcheck result for provider startup verification */ export type ProviderHealth = { ok: boolean; /** Model identifier reported by the provider */ model: string; /** Embedding dimensions (e.g. 768 for embeddinggemma-300M) */ dimensions?: number; /** Detail message (error reason on failure, status on success) */ detail?: string; }; /** * Per-call options for provider embedding */ export type ProviderEmbedOptions = { /** Optional model id override (rare; usually provider has a fixed model) */ model?: string; /** Abort signal for cancellation / timeout */ signal?: AbortSignal; }; /** * Provider interface — both LocalLlamaCppProvider and OpenAIEmbeddingsProvider implement this. * * Implementations MUST: * - Return `null` (not throw) for individual texts that fail to embed; * the caller will count it as an error and continue. * - Honor `options.signal` for cancellation. * - Be safe to call concurrently for `embedBatch`. */ export interface EmbeddingProvider { /** Provider kind tag — useful for logging and factory introspection */ readonly kind: ProviderKind; /** * Stable model identifier reported to the caller. * * MUST match what's stored in `content_vectors.model` for the existing * index — otherwise the model-id guard refuses to embed. */ getModelId(): string; /** * Embedding vector dimensions. May return `undefined` before the first call * (some providers probe lazily). Once known, MUST stay stable. */ getDimensions(): number | undefined; /** * Healthcheck — verifies the provider is reachable and the model is loaded. * Should NOT throw — return `{ ok: false, detail: ... }` on failure. * * For HTTP providers: ping `/health` endpoint. * For local provider: ensure model loads. */ healthcheck(signal?: AbortSignal): Promise; /** * Embed a single text. Returns `null` on per-call failure. */ embed(text: string, options?: ProviderEmbedOptions): Promise; /** * Embed multiple texts in a batch (more efficient than calling `embed` N times). * * Output array length MUST equal input array length. Failed entries are `null`. * Implementations are responsible for chunking large batches per their * upstream limits (e.g. OpenAI provider chunks to 64). */ embedBatch(texts: string[], options?: ProviderEmbedOptions): Promise<(ProviderEmbedding | null)[]>; /** Release any held resources (HTTP keep-alive sockets, model handles, …) */ dispose(): Promise; } /** * Error thrown when the provider's reported model id does not match the * model id baked into existing `content_vectors` rows. Forces user to * re-embed (`qmd embed -f`) or pin the matching model id. */ export declare class ModelMismatchError extends Error { readonly providerModel: string; readonly existingModels: string[]; constructor(providerModel: string, existingModels: string[]); } /** * Verify that the provider's model id is compatible with the existing * `content_vectors` entries. Pass-through (no-op) if the table is empty * (fresh DB) or if the model id appears in the distinct set. * * Caller passes `existingModels` (typically result of * `SELECT DISTINCT model FROM content_vectors`). */ export declare function assertModelCompatible(providerModel: string, existingModels: string[]): void;