From 3b975c60901fbce1924cb46dd782281dcaa19982 Mon Sep 17 00:00:00 2001 From: Philip Mischenko Date: Wed, 13 May 2026 12:02:20 +0100 Subject: [PATCH 1/3] feat(embeddings): support custom OpenAI baseURL for Ollama / OpenAI-compatible providers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an optional `base_url` field under `embedding.openai` in the config schema (and `OPENAI_BASE_URL` env var fallback). When set, it is passed to the OpenAI SDK constructor as `baseURL`, allowing doc2vec to embed documents via any OpenAI-compatible endpoint — Ollama, LM Studio, vLLM, llama.cpp server, internal LLM gateways, etc. Conditional spread keeps the default behaviour identical when no base URL is provided; existing configs continue to hit api.openai.com. The chosen endpoint is now revealed in the startup log line for clearer diagnostics ("Using OpenAI ... via http://localhost:11434/v1"). --- doc2vec.ts | 12 ++++++++---- types.ts | 5 +++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/doc2vec.ts b/doc2vec.ts index 47ab0de..3c306df 100644 --- a/doc2vec.ts +++ b/doc2vec.ts @@ -89,15 +89,19 @@ export class Doc2Vec { } else { const openaiApiKey = embeddingConfig.openai?.api_key || process.env.OPENAI_API_KEY; const openaiModel = embeddingConfig.openai?.model || process.env.OPENAI_MODEL || 'text-embedding-3-large'; - + const openaiBaseURL = embeddingConfig.openai?.base_url || process.env.OPENAI_BASE_URL; + if (!openaiApiKey) { this.logger.error('OpenAI requires api_key to be configured'); process.exit(1); } - - this.openai = new OpenAI({ apiKey: openaiApiKey }); + + this.openai = new OpenAI({ + apiKey: openaiApiKey, + ...(openaiBaseURL && { baseURL: openaiBaseURL }), + }); this.embeddingModel = openaiModel; - this.logger.info(`Using OpenAI with model: ${openaiModel} (${this.embeddingDimension} dimensions)`); + this.logger.info(`Using OpenAI with model: ${openaiModel} (${this.embeddingDimension} dimensions)${openaiBaseURL ? ` via ${openaiBaseURL}` : ''}`); } this.contentProcessor = new ContentProcessor(this.logger); diff --git a/types.ts b/types.ts index 9f1341b..e39a238 100644 --- a/types.ts +++ b/types.ts @@ -98,8 +98,9 @@ export interface EmbeddingConfig { provider: 'openai' | 'azure'; dimension?: number; openai?: { - api_key?: string; // Can also use OPENAI_API_KEY env var - model?: string; // Default: text-embedding-3-large + api_key?: string; // Can also use OPENAI_API_KEY env var + model?: string; // Default: text-embedding-3-large + base_url?: string; // Override OpenAI API base URL — useful for Ollama or other OpenAI-compatible endpoints. Can also use OPENAI_BASE_URL env var. }; azure?: { api_key?: string; // Can also use AZURE_OPENAI_KEY env var From 8471335274c77094047ba7b4f2b6ff031ddf9d7b Mon Sep 17 00:00:00 2001 From: Philip Mischenko Date: Wed, 13 May 2026 12:17:57 +0100 Subject: [PATCH 2/3] fix(docker): rebuild better-sqlite3 native bindings after --ignore-scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `npm install --ignore-scripts` (security best-practice — blocks malicious postinstall scripts) skips node-gyp compilation for native modules. The better-sqlite3 binding is therefore never built unless a prebuilt binary exists for the target arch/Node combination. On linux/arm64 (Apple Silicon, ARM Linux runners) no prebuild ships, so runtime fails with "Could not locate the bindings file" the moment any SQLite source tries to open a database. Adding an explicit `npm rebuild better-sqlite3` after install keeps the security guarantee for unrelated scripts while compiling the one native module the tool actually needs. --- Dockerfile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Dockerfile b/Dockerfile index ffec336..4eb8a3c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,6 +32,12 @@ RUN apt-get update && apt-get install -y \ COPY package*.json ./ RUN npm install --ignore-scripts +# --ignore-scripts skips node-gyp rebuild for native modules +# (security best-practice, blocks malicious postinstall scripts). +# Explicitly rebuild better-sqlite3 so its arm64/amd64 .node binding +# is compiled — otherwise the runtime fails with "Could not locate +# the bindings file" on architectures lacking a prebuild. +RUN npm rebuild better-sqlite3 # Install Chrome via Puppeteer as fallback (system Chromium will be used first) RUN npx puppeteer browsers install chrome || true COPY . . From 28b5c7c2992fc2d46f37c0243b565d5bd6ea55e5 Mon Sep 17 00:00:00 2001 From: Philip Mischenko Date: Wed, 13 May 2026 12:31:13 +0100 Subject: [PATCH 3/3] feat(mcp): support custom OpenAI baseURL in the MCP server too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reader-side MCP server (mcp/src/index.ts) also instantiated the OpenAI client with apiKey only. It now reads OPENAI_BASE_URL from the environment and threads it into the constructor via conditional spread, mirroring the writer-side fix introduced earlier on this branch. This lets users point both halves of the doc2vec pipeline (the writer CLI for building vector DBs and the MCP server for querying them) at any OpenAI-compatible endpoint — Ollama, LM Studio, vLLM, llama.cpp server, internal LLM gateways — through a single env var per process. Default behaviour unchanged when OPENAI_BASE_URL is unset. --- mcp/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mcp/src/index.ts b/mcp/src/index.ts index 7f48875..7273cf2 100644 --- a/mcp/src/index.ts +++ b/mcp/src/index.ts @@ -34,6 +34,7 @@ const embeddingProvider = process.env.EMBEDDING_PROVIDER || 'openai'; // OpenAI configuration const openAIApiKey = process.env.OPENAI_API_KEY; const openAIModel = process.env.OPENAI_MODEL || 'text-embedding-3-large'; +const openAIBaseURL = process.env.OPENAI_BASE_URL; // Optional: override API base URL for Ollama / other OpenAI-compatible endpoints // Azure OpenAI configuration const azureApiKey = process.env.AZURE_OPENAI_KEY; @@ -110,6 +111,7 @@ async function createEmbeddings(text: string): Promise { case 'openai': { const openai = new OpenAI({ apiKey: openAIApiKey, + ...(openAIBaseURL && { baseURL: openAIBaseURL }), }); const response = await openai.embeddings.create({ model: openAIModel,