fix(ts-sdk): re-raise LLM extraction transport failures instead of returning [] (#6102)

Co-authored-by: Kartik <kartik.labhshetwar@mem0.ai>
This commit is contained in:
Shaurya
2026-07-10 20:46:08 +05:30
committed by GitHub
parent 1d383c4ee2
commit 28e4d819f8
2 changed files with 117 additions and 2 deletions
+13 -2
View File
@@ -79,7 +79,18 @@ import { logger } from "../utils/logger";
import { normalizeExpirationDate, payloadIsExpired } from "../utils/expiration";
import { getOrCreateMem0UserId } from "../../../client/config";
// Entity params that must be passed via filters - check both snake_case and camelCase
export class LLMError extends Error {
readonly cause?: unknown;
constructor(message: string, options: { cause?: unknown } = {}) {
super(message);
this.name = "LLMError";
this.cause = options.cause;
Object.setPrototypeOf(this, new.target.prototype);
}
}
// Entity params that must be passed via filters check both snake_case and camelCase
const ENTITY_PARAMS = [
"user_id",
"agent_id",
@@ -871,7 +882,7 @@ export class Memory {
)) as string;
} catch (e) {
console.error("LLM extraction failed:", e);
return [];
throw new LLMError(`LLM extraction failed: ${e}`, { cause: e });
}
// Parse response
@@ -0,0 +1,104 @@
/**
* Regression test for #6101 / #5903: LLM extraction transport failures
* (rate limits, timeouts, connection errors) must propagate as a typed
* `LLMError`, not be silently swallowed into an empty result. Mirrors the
* Python SDK regression test added in #5878
* (`test_llm_extraction_exception_is_reraised`).
*/
/// <reference types="jest" />
import { Memory, LLMError } from "../src/memory";
import type { SearchResult } from "../src/types";
jest.setTimeout(15000);
// Mock Google modules to prevent @google/genai crash in CI
jest.mock("../src/embeddings/google", () => ({
GoogleEmbedder: jest.fn(),
}));
jest.mock("../src/llms/google", () => ({
GoogleLLM: jest.fn(),
}));
class _ProviderError extends Error {}
jest.mock("../src/llms/openai", () => ({
OpenAILLM: jest.fn().mockImplementation(() => ({
generateResponse: jest
.fn()
.mockRejectedValue(new _ProviderError("429 rate limit")),
})),
}));
const mockEmbedding = new Array(1536).fill(0.1);
jest.mock("../src/embeddings/openai", () => ({
OpenAIEmbedder: jest.fn().mockImplementation(() => ({
embed: jest.fn().mockResolvedValue(mockEmbedding),
embedBatch: jest
.fn()
.mockImplementation((texts: string[]) =>
Promise.resolve(texts.map(() => mockEmbedding)),
),
embeddingDims: 1536,
})),
}));
function createMemory(): Memory {
return new Memory({
version: "v1.1",
embedder: {
provider: "openai",
config: { apiKey: "test-key", model: "text-embedding-3-small" },
},
vectorStore: {
provider: "memory",
config: {
collectionName: `test-llm-error-${Date.now()}`,
dimension: 1536,
dbPath: ":memory:",
},
},
llm: {
provider: "openai",
config: { apiKey: "test-key", model: "gpt-5-mini" },
},
historyDbPath: ":memory:",
});
}
describe("Memory - LLM extraction transport failures", () => {
let memory: Memory;
const userId = `llm_error_test_${Date.now()}`;
beforeAll(async () => {
memory = createMemory();
});
afterAll(async () => {
await memory.reset();
});
test("add() rejects with LLMError instead of returning an empty result", async () => {
await expect(
memory.add("this should trigger a provider failure", { userId }),
).rejects.toBeInstanceOf(LLMError);
});
test("thrown LLMError preserves the original error as its cause", async () => {
let caught: unknown;
try {
const result: SearchResult = await memory.add("trigger failure again", {
userId,
});
// Should never reach here — fail loudly if the call resolves.
throw new Error(
`Expected add() to reject, but it resolved with: ${JSON.stringify(result)}`,
);
} catch (e) {
caught = e;
}
expect(caught).toBeInstanceOf(LLMError);
expect((caught as LLMError).cause).toBeInstanceOf(_ProviderError);
expect((caught as LLMError).message).toContain("429 rate limit");
});
});