diff --git a/extensions/memory-lancedb/clawdbot.plugin.json b/extensions/memory-lancedb/clawdbot.plugin.json index 94cd679ca..9174341ec 100644 --- a/extensions/memory-lancedb/clawdbot.plugin.json +++ b/extensions/memory-lancedb/clawdbot.plugin.json @@ -6,12 +6,24 @@ "label": "OpenAI API Key", "sensitive": true, "placeholder": "sk-proj-...", - "help": "API key for OpenAI embeddings (or use ${OPENAI_API_KEY})" + "help": "API key for embeddings (use 'not-needed' for local servers)" }, "embedding.model": { "label": "Embedding Model", "placeholder": "text-embedding-3-small", - "help": "OpenAI embedding model to use" + "help": "Embedding model name" + }, + "embedding.baseUrl": { + "label": "Custom Endpoint URL", + "placeholder": "http://localhost:8080/v1", + "help": "Custom OpenAI-compatible embedding endpoint (for local/self-hosted servers)", + "advanced": true + }, + "embedding.dimensions": { + "label": "Vector Dimensions", + "placeholder": "1536", + "help": "Override vector dimensions (required for custom models not in built-in list)", + "advanced": true }, "dbPath": { "label": "Database Path", @@ -39,11 +51,15 @@ "type": "string" }, "model": { + "type": "string" + }, + "baseUrl": { "type": "string", - "enum": [ - "text-embedding-3-small", - "text-embedding-3-large" - ] + "description": "Custom OpenAI-compatible endpoint URL" + }, + "dimensions": { + "type": "number", + "description": "Override vector dimensions for custom models" } }, "required": [ diff --git a/extensions/memory-lancedb/config.ts b/extensions/memory-lancedb/config.ts index c0382392f..9fbe4f9ba 100644 --- a/extensions/memory-lancedb/config.ts +++ b/extensions/memory-lancedb/config.ts @@ -7,6 +7,8 @@ export type MemoryConfig = { provider: "openai"; model?: string; apiKey: string; + baseUrl?: string; // Custom endpoint URL (for local/self-hosted embeddings) + dimensions?: number; // Override vector dimensions }; dbPath?: string; autoCapture?: boolean; @@ -34,10 +36,14 @@ function assertAllowedKeys( throw new Error(`${label} has unknown keys: ${unknown.join(", ")}`); } -export function vectorDimsForModel(model: string): number { +export function vectorDimsForModel(model: string, customDims?: number): number { + // Custom dimensions override built-in model lookup + if (customDims) return customDims; const dims = EMBEDDING_DIMENSIONS[model]; if (!dims) { - throw new Error(`Unsupported embedding model: ${model}`); + throw new Error( + `Unsupported embedding model: ${model}. Specify dimensions manually via embedding.dimensions.` + ); } return dims; } @@ -54,7 +60,10 @@ function resolveEnvVars(value: string): string { function resolveEmbeddingModel(embedding: Record): string { const model = typeof embedding.model === "string" ? embedding.model : DEFAULT_MODEL; - vectorDimsForModel(model); + // Skip dimension validation if custom dimensions provided + if (typeof embedding.dimensions !== "number") { + vectorDimsForModel(model); + } return model; } @@ -70,15 +79,19 @@ export const memoryConfigSchema = { if (!embedding || typeof embedding.apiKey !== "string") { throw new Error("embedding.apiKey is required"); } - assertAllowedKeys(embedding, ["apiKey", "model"], "embedding config"); + assertAllowedKeys(embedding, ["apiKey", "model", "baseUrl", "dimensions"], "embedding config"); const model = resolveEmbeddingModel(embedding); + const baseUrl = typeof embedding.baseUrl === "string" ? embedding.baseUrl : undefined; + const dimensions = typeof embedding.dimensions === "number" ? embedding.dimensions : undefined; return { embedding: { provider: "openai", model, apiKey: resolveEnvVars(embedding.apiKey), + baseUrl, + dimensions, }, dbPath: typeof cfg.dbPath === "string" ? cfg.dbPath : DEFAULT_DB_PATH, autoCapture: cfg.autoCapture !== false, @@ -90,12 +103,24 @@ export const memoryConfigSchema = { label: "OpenAI API Key", sensitive: true, placeholder: "sk-proj-...", - help: "API key for OpenAI embeddings (or use ${OPENAI_API_KEY})", + help: "API key for embeddings (use 'not-needed' for local servers)", }, "embedding.model": { label: "Embedding Model", placeholder: DEFAULT_MODEL, - help: "OpenAI embedding model to use", + help: "Embedding model name", + }, + "embedding.baseUrl": { + label: "Custom Endpoint URL", + placeholder: "http://localhost:8080/v1", + help: "Custom OpenAI-compatible embedding endpoint (for local/self-hosted servers)", + advanced: true, + }, + "embedding.dimensions": { + label: "Vector Dimensions", + placeholder: "1536", + help: "Override vector dimensions (required for custom models not in built-in list)", + advanced: true, }, dbPath: { label: "Database Path", diff --git a/extensions/memory-lancedb/index.ts b/extensions/memory-lancedb/index.ts index e7daab6f5..9add4fa50 100644 --- a/extensions/memory-lancedb/index.ts +++ b/extensions/memory-lancedb/index.ts @@ -156,8 +156,9 @@ class Embeddings { constructor( apiKey: string, private model: string, + baseUrl?: string, ) { - this.client = new OpenAI({ apiKey }); + this.client = new OpenAI({ apiKey, baseURL: baseUrl }); } async embed(text: string): Promise { @@ -223,9 +224,16 @@ const memoryPlugin = { register(api: MoltbotPluginApi) { const cfg = memoryConfigSchema.parse(api.pluginConfig); const resolvedDbPath = api.resolvePath(cfg.dbPath!); - const vectorDim = vectorDimsForModel(cfg.embedding.model ?? "text-embedding-3-small"); + const vectorDim = vectorDimsForModel( + cfg.embedding.model ?? "text-embedding-3-small", + cfg.embedding.dimensions, + ); const db = new MemoryDB(resolvedDbPath, vectorDim); - const embeddings = new Embeddings(cfg.embedding.apiKey, cfg.embedding.model!); + const embeddings = new Embeddings( + cfg.embedding.apiKey, + cfg.embedding.model!, + cfg.embedding.baseUrl, + ); api.logger.info( `memory-lancedb: plugin registered (db: ${resolvedDbPath}, lazy init)`,