Files
oh-my-pi/packages/mnemopi/test/embedding-failure-logging.test.ts
T
can1357 3e04b81c1b fix(mnemopi): surfaced silent embedding pipeline failures with structured logs
- Replaced the bare `catch {}` blocks in `runEmbedding()` (`beam/helpers.ts`), `getLocalModel()`, and the local-model path of `embed()` (`embeddings.ts`) with structured `logger.debug` entries carrying the error plus per-site context (item count, model name); failure semantics stay best-effort.
- Threaded `MnemopiOptions.debug` through `resolveRuntimeOptions()` in `memory.ts` so `mnemopiDebugEnabled()` escalates these logs to `warn` when `mnemopi.debug` is set.
- Passed `mnemopi.debug` from coding-agent settings into provider options (`debug` added to the `MnemopiProviderOptions` pick in `src/mnemopi/config.ts`).
- Added `embedding-failure-logging.test.ts` asserting the logged context on a real local-model load failure and the debug→warn escalation; the changelog entry landed with the previous commit (same contiguous run).

Fixes #2322: Mnemopi: runEmbedding() and getLocalModel() silently swallow errors — mnemopi.debug produces no diagnostic output
2026-06-12 04:01:47 +02:00

110 lines
3.2 KiB
TypeScript

import { afterEach, describe, expect, it, spyOn } from "bun:test";
import { logger } from "@oh-my-pi/pi-utils";
import "./setup";
import {
embed,
resetEmbeddingProviderForTests,
setLocalModelInitializerForTests,
} from "@oh-my-pi/pi-mnemopi/core/embeddings";
import { withMnemopiRuntimeOptions } from "@oh-my-pi/pi-mnemopi/core/runtime-options";
const ENV_KEYS = [
"NODE_ENV",
"BUN_ENV",
"MNEMOPI_NO_EMBEDDINGS",
"MNEMOPI_EMBEDDING_MODEL",
"MNEMOPI_EMBEDDING_API_URL",
"MNEMOPI_EMBEDDING_API_KEY",
"OPENROUTER_BASE_URL",
"OPENROUTER_API_KEY",
"OPENAI_API_KEY",
] as const;
type EnvKey = (typeof ENV_KEYS)[number];
/** Force the local-fastembed path: not a test runtime, local model, no API config. */
async function withLocalModelEnv<T>(fn: () => Promise<T>): Promise<T> {
const snapshot: Partial<Record<EnvKey, string>> = {};
for (const key of ENV_KEYS) {
const value = process.env[key];
if (value !== undefined) snapshot[key] = value;
delete process.env[key];
}
process.env.MNEMOPI_EMBEDDING_MODEL = "BAAI/bge-small-en-v1.5";
resetEmbeddingProviderForTests();
try {
return await fn();
} finally {
for (const key of ENV_KEYS) {
const value = snapshot[key];
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
}
resetEmbeddingProviderForTests();
}
}
afterEach(() => {
resetEmbeddingProviderForTests();
});
describe("embedding failure logging (#2322)", () => {
it("logs local model load failures at debug level with model context", async () => {
const debugSpy = spyOn(logger, "debug").mockImplementation(() => {});
const warnSpy = spyOn(logger, "warn").mockImplementation(() => {});
try {
await withLocalModelEnv(async () => {
setLocalModelInitializerForTests(async () => {
throw new Error("onnx init blew up");
});
expect(await embed(["hello"])).toBeNull();
expect(debugSpy).toHaveBeenCalledWith(
"mnemopi: local embedding model failed to load",
expect.objectContaining({
model: expect.any(String),
error: expect.stringContaining("onnx init blew up"),
}),
);
expect(warnSpy).not.toHaveBeenCalledWith(
"mnemopi: local embedding model failed to load",
expect.anything(),
);
});
} finally {
debugSpy.mockRestore();
warnSpy.mockRestore();
}
});
it("escalates the same failure to warn when runtime debug is enabled", async () => {
const debugSpy = spyOn(logger, "debug").mockImplementation(() => {});
const warnSpy = spyOn(logger, "warn").mockImplementation(() => {});
try {
await withLocalModelEnv(async () => {
setLocalModelInitializerForTests(async () => {
throw new Error("onnx init blew up again");
});
expect(await withMnemopiRuntimeOptions({ debug: true }, () => embed(["hello"]))).toBeNull();
expect(warnSpy).toHaveBeenCalledWith(
"mnemopi: local embedding model failed to load",
expect.objectContaining({ error: expect.stringContaining("onnx init blew up again") }),
);
expect(debugSpy).not.toHaveBeenCalledWith(
"mnemopi: local embedding model failed to load",
expect.anything(),
);
});
} finally {
debugSpy.mockRestore();
warnSpy.mockRestore();
}
});
});