diff --git a/bun.lock b/bun.lock index f458673dc..3784450ab 100644 --- a/bun.lock +++ b/bun.lock @@ -103,6 +103,7 @@ "dependencies": { "@oh-my-pi/pi-ai": "catalog:", "fastembed": "catalog:", + "onnxruntime-node": "catalog:", }, "devDependencies": { "@types/bun": "catalog:", @@ -278,6 +279,7 @@ "lucide-react": "^1.16.0", "marked": "^18.0.4", "markit-ai": "0.5.3", + "onnxruntime-node": "1.24.3", "openai": "^6.39.0", "partial-json": "^0.1.7", "postcss": "^8.5.15", diff --git a/package.json b/package.json index 9200b4b6b..eab5c4938 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "lucide-react": "^1.16.0", "marked": "^18.0.4", "markit-ai": "0.5.3", + "onnxruntime-node": "1.24.3", "openai": "^6.39.0", "partial-json": "^0.1.7", "postcss": "^8.5.15", diff --git a/packages/mnemosyne/CHANGELOG.md b/packages/mnemosyne/CHANGELOG.md index c1abee285..8f7699f68 100644 --- a/packages/mnemosyne/CHANGELOG.md +++ b/packages/mnemosyne/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixed +- Fixed Windows startup crashes by keeping fastembed's older ONNX Runtime binding lazy until local embeddings are used. - Fixed a segfault at startup from eagerly loading fastembed: importing the embeddings module pulled in `fastembed`, which eagerly loads the `onnxruntime-node` native addon. The import is now deferred until a local fastembed model is actually initialized, so API-model, disabled-embeddings, and test runtimes never load the native addon. ## [15.6.0] - 2026-05-30 diff --git a/packages/mnemosyne/package.json b/packages/mnemosyne/package.json index dd1f84a93..8603041c6 100644 --- a/packages/mnemosyne/package.json +++ b/packages/mnemosyne/package.json @@ -40,7 +40,8 @@ }, "dependencies": { "@oh-my-pi/pi-ai": "catalog:", - "fastembed": "catalog:" + "fastembed": "catalog:", + "onnxruntime-node": "catalog:" }, "devDependencies": { "@types/bun": "catalog:" diff --git a/packages/mnemosyne/src/core/embeddings.ts b/packages/mnemosyne/src/core/embeddings.ts index f393c9415..759e56697 100644 --- a/packages/mnemosyne/src/core/embeddings.ts +++ b/packages/mnemosyne/src/core/embeddings.ts @@ -1,5 +1,6 @@ import { mkdirSync } from "node:fs"; -import type { EmbeddingModel } from "fastembed"; +import { createRequire } from "node:module"; +import type { EmbeddingModel, FlagEmbedding } from "fastembed"; import { getMnemosyneRuntimeOptions, resolveEmbeddingProvider } from "./runtime-options"; export type Vector = number[]; @@ -24,8 +25,14 @@ type LocalModelInitOptions = { }; type LocalModelInitializer = (options: LocalModelInitOptions) => Promise; +interface FastembedRuntime { + EmbeddingModel: typeof EmbeddingModel; + FlagEmbedding: typeof FlagEmbedding; +} + const FASTEMBED_CACHE_DIR = `${process.env.HOME ?? ""}/.hermes/cache/fastembed`; const QUERY_CACHE_MAX = 512; +const sourceRequire = createRequire(import.meta.url); let providerOverride: EmbeddingProvider | null = null; let localModelPromise: Promise | null = null; @@ -33,12 +40,14 @@ let localModelInitializer: LocalModelInitializer = defaultLocalModelInitializer; let apiCallCount = 0; const queryCache = new Map(); -async function defaultLocalModelInitializer(options: LocalModelInitOptions): Promise { - // Lazily pull in fastembed here — its module eagerly imports `onnxruntime-node`, - // whose native addon load segfaults in some runtimes. Deferring to the actual - // model init keeps API-model, disabled, and test runtimes from ever loading it. - const { FlagEmbedding } = await import("fastembed"); - return FlagEmbedding.init(options) as Promise; +function loadFastembedRuntime(): FastembedRuntime { + // Preload ORT 1.24 before fastembed's ORT 1.21 binding to avoid Windows DLL reuse crashes. + sourceRequire("onnxruntime-node"); + return sourceRequire("fastembed") as FastembedRuntime; +} + +function defaultLocalModelInitializer(options: LocalModelInitOptions): Promise { + return loadFastembedRuntime().FlagEmbedding.init(options); } function activeEmbeddingOptions() {