| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- /**
- * local.ts - Local llama.cpp adapter implementing EmbeddingProvider.
- *
- * Wraps an existing `LlamaCpp` instance so the legacy GGUF path looks like
- * any other EmbeddingProvider to upstream callers. Used as the default and
- * as the fallback target when `OpenAIEmbeddingsProvider` trips its breaker.
- */
- import { getDefaultLlamaCpp, } from "../llm.js";
- export class LocalLlamaCppProvider {
- kind = "local";
- llm;
- modelId;
- dimensions = undefined;
- lastError = undefined;
- constructor(config = {}) {
- this.llm = config.llm ?? getDefaultLlamaCpp();
- this.modelId = config.modelId ?? "embeddinggemma";
- }
- getModelId() {
- return this.modelId;
- }
- getDimensions() {
- return this.dimensions;
- }
- /**
- * Most recent thrown error from `llm.embed` / `llm.embedBatch`. Returns
- * `undefined` after a successful call or before the first call. See
- * `EmbeddingProvider.getLastError`.
- */
- getLastError() {
- return this.lastError;
- }
- async healthcheck(_signal) {
- // For the local provider, "healthy" means the embed model loads.
- // We probe with a single embed call.
- try {
- const result = await this.llm.embed("healthcheck", { model: this.modelId });
- if (!result) {
- return {
- ok: false,
- model: this.modelId,
- detail: "embed probe returned null",
- };
- }
- this.dimensions = result.embedding.length;
- return {
- ok: true,
- model: this.modelId,
- dimensions: this.dimensions,
- detail: `local llama.cpp ready, ${this.dimensions}-d`,
- };
- }
- catch (err) {
- return {
- ok: false,
- model: this.modelId,
- detail: err instanceof Error ? err.message : String(err),
- };
- }
- }
- async embed(text, options = {}) {
- if (options.signal?.aborted) {
- this.lastError = `aborted by caller${options.signal.reason ? `: ${String(options.signal.reason)}` : ""}`;
- return null;
- }
- let result;
- try {
- result = await this.llm.embed(text, { model: options.model ?? this.modelId });
- }
- catch (err) {
- this.lastError = `provider=local error="${err instanceof Error ? err.message : String(err)}"`;
- return null;
- }
- if (!result) {
- this.lastError = `provider=local error="llm.embed returned null/undefined"`;
- return null;
- }
- if (this.dimensions === undefined) {
- this.dimensions = result.embedding.length;
- }
- this.lastError = undefined;
- return {
- embedding: result.embedding,
- model: this.modelId,
- };
- }
- async embedBatch(texts, options = {}) {
- if (texts.length === 0)
- return [];
- if (options.signal?.aborted) {
- this.lastError = `aborted by caller${options.signal.reason ? `: ${String(options.signal.reason)}` : ""}`;
- return texts.map(() => null);
- }
- let raw;
- try {
- raw = await this.llm.embedBatch(texts, {
- model: options.model ?? this.modelId,
- });
- }
- catch (err) {
- this.lastError = `provider=local error="${err instanceof Error ? err.message : String(err)}"`;
- return texts.map(() => null);
- }
- const out = raw.map((r) => {
- if (!r)
- return null;
- if (this.dimensions === undefined && r.embedding.length > 0) {
- this.dimensions = r.embedding.length;
- }
- return {
- embedding: r.embedding,
- model: this.modelId,
- };
- });
- if (out.every((r) => r !== null)) {
- this.lastError = undefined;
- }
- else if (out.some((r) => r === null)) {
- this.lastError = `provider=local error="llm.embedBatch returned null entries (${out.filter((r) => r === null).length}/${out.length})"`;
- }
- return out;
- }
- async dispose() {
- // We do NOT dispose the underlying LlamaCpp here because the singleton
- // is shared with rerank/generate/expansion paths. Disposal is handled
- // by the existing `disposeDefaultLlamaCpp()` global hook.
- }
- }
|