This commit is contained in:
Mike Nott 2026-01-29 16:21:02 -05:00 committed by GitHub
commit f4d49eb6f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 64 additions and 15 deletions

View File

@ -6,12 +6,24 @@
"label": "OpenAI API Key", "label": "OpenAI API Key",
"sensitive": true, "sensitive": true,
"placeholder": "sk-proj-...", "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": { "embedding.model": {
"label": "Embedding Model", "label": "Embedding Model",
"placeholder": "text-embedding-3-small", "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": { "dbPath": {
"label": "Database Path", "label": "Database Path",
@ -39,11 +51,15 @@
"type": "string" "type": "string"
}, },
"model": { "model": {
"type": "string"
},
"baseUrl": {
"type": "string", "type": "string",
"enum": [ "description": "Custom OpenAI-compatible endpoint URL"
"text-embedding-3-small", },
"text-embedding-3-large" "dimensions": {
] "type": "number",
"description": "Override vector dimensions for custom models"
} }
}, },
"required": [ "required": [

View File

@ -7,6 +7,8 @@ export type MemoryConfig = {
provider: "openai"; provider: "openai";
model?: string; model?: string;
apiKey: string; apiKey: string;
baseUrl?: string; // Custom endpoint URL (for local/self-hosted embeddings)
dimensions?: number; // Override vector dimensions
}; };
dbPath?: string; dbPath?: string;
autoCapture?: boolean; autoCapture?: boolean;
@ -34,10 +36,14 @@ function assertAllowedKeys(
throw new Error(`${label} has unknown keys: ${unknown.join(", ")}`); 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]; const dims = EMBEDDING_DIMENSIONS[model];
if (!dims) { if (!dims) {
throw new Error(`Unsupported embedding model: ${model}`); throw new Error(
`Unsupported embedding model: ${model}. Specify dimensions manually via embedding.dimensions.`
);
} }
return dims; return dims;
} }
@ -54,7 +60,10 @@ function resolveEnvVars(value: string): string {
function resolveEmbeddingModel(embedding: Record<string, unknown>): string { function resolveEmbeddingModel(embedding: Record<string, unknown>): string {
const model = typeof embedding.model === "string" ? embedding.model : DEFAULT_MODEL; 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; return model;
} }
@ -70,15 +79,19 @@ export const memoryConfigSchema = {
if (!embedding || typeof embedding.apiKey !== "string") { if (!embedding || typeof embedding.apiKey !== "string") {
throw new Error("embedding.apiKey is required"); 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 model = resolveEmbeddingModel(embedding);
const baseUrl = typeof embedding.baseUrl === "string" ? embedding.baseUrl : undefined;
const dimensions = typeof embedding.dimensions === "number" ? embedding.dimensions : undefined;
return { return {
embedding: { embedding: {
provider: "openai", provider: "openai",
model, model,
apiKey: resolveEnvVars(embedding.apiKey), apiKey: resolveEnvVars(embedding.apiKey),
baseUrl,
dimensions,
}, },
dbPath: typeof cfg.dbPath === "string" ? cfg.dbPath : DEFAULT_DB_PATH, dbPath: typeof cfg.dbPath === "string" ? cfg.dbPath : DEFAULT_DB_PATH,
autoCapture: cfg.autoCapture !== false, autoCapture: cfg.autoCapture !== false,
@ -90,12 +103,24 @@ export const memoryConfigSchema = {
label: "OpenAI API Key", label: "OpenAI API Key",
sensitive: true, sensitive: true,
placeholder: "sk-proj-...", 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": { "embedding.model": {
label: "Embedding Model", label: "Embedding Model",
placeholder: DEFAULT_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: { dbPath: {
label: "Database Path", label: "Database Path",

View File

@ -156,8 +156,9 @@ class Embeddings {
constructor( constructor(
apiKey: string, apiKey: string,
private model: string, private model: string,
baseUrl?: string,
) { ) {
this.client = new OpenAI({ apiKey }); this.client = new OpenAI({ apiKey, baseURL: baseUrl });
} }
async embed(text: string): Promise<number[]> { async embed(text: string): Promise<number[]> {
@ -223,9 +224,16 @@ const memoryPlugin = {
register(api: MoltbotPluginApi) { register(api: MoltbotPluginApi) {
const cfg = memoryConfigSchema.parse(api.pluginConfig); const cfg = memoryConfigSchema.parse(api.pluginConfig);
const resolvedDbPath = api.resolvePath(cfg.dbPath!); 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 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( api.logger.info(
`memory-lancedb: plugin registered (db: ${resolvedDbPath}, lazy init)`, `memory-lancedb: plugin registered (db: ${resolvedDbPath}, lazy init)`,