Skip to content

Commit 44e6caf

Browse files
fix(memory): accept local default model path migration (#92954)
* fix(memory): accept local default model path migration Treat the official local default embedding model's hf URI and downloaded GGUF path identities as equivalent so upgraded local memory indexes do not pause solely on path-format changes. * fix(memory): satisfy local identity lint Avoid filtered array tail access in the local model filename helper while preserving the same compatibility behavior. * fix(memory): preserve local embedding identity aliases --------- Co-authored-by: Vincent Koc <[email protected]>
1 parent d1eda0b commit 44e6caf

28 files changed

Lines changed: 1047 additions & 102 deletions

extensions/llama-cpp/index.test.ts

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os from "node:os";
2+
import path from "node:path";
13
import {
24
createPluginRegistryFixture,
35
registerVirtualTestPlugin,
@@ -21,7 +23,9 @@ import llamaCppPlugin from "./index.js";
2123
import {
2224
DEFAULT_LLAMA_CPP_EMBEDDING_MODEL,
2325
createLlamaCppEmbeddingProvider,
26+
createLlamaCppMemoryEmbeddingProvider,
2427
formatLlamaCppSetupError,
28+
llamaCppEmbeddingProviderAdapter,
2529
} from "./src/embedding-provider.js";
2630

2731
afterEach(() => {
@@ -106,6 +110,285 @@ describe("llama.cpp provider plugin", () => {
106110
});
107111
});
108112

113+
it("keeps the default model identity when configured with its exact cache artifact path", async () => {
114+
const modelPath = path.join(
115+
os.homedir(),
116+
".node-llama-cpp",
117+
"models",
118+
"hf_ggml-org_embeddinggemma-300m-qat-Q8_0.gguf",
119+
);
120+
memoryHostEmbeddingMocks.createLocalEmbeddingProvider.mockResolvedValue({
121+
id: "local",
122+
model: modelPath,
123+
embedQuery: vi.fn(),
124+
embedBatch: vi.fn(),
125+
});
126+
127+
const result = await createLlamaCppMemoryEmbeddingProvider(
128+
{
129+
config: {},
130+
provider: "local",
131+
fallback: "none",
132+
model: modelPath,
133+
local: { modelPath },
134+
},
135+
{ nodeLlamaCppImportUrl: "file:///plugin/node-llama-cpp.js" },
136+
);
137+
138+
expect(result.provider?.model).toBe(DEFAULT_LLAMA_CPP_EMBEDDING_MODEL);
139+
expect(result.runtime?.cacheKeyData).toEqual({
140+
provider: "local",
141+
model: DEFAULT_LLAMA_CPP_EMBEDDING_MODEL,
142+
});
143+
expect(result.runtime?.indexIdentityAliases).toEqual([
144+
{
145+
model: modelPath,
146+
cacheKeyData: {
147+
provider: "local",
148+
model: modelPath,
149+
},
150+
},
151+
{
152+
model: "hf_ggml-org_embeddinggemma-300m-qat-Q8_0.gguf",
153+
cacheKeyData: {
154+
provider: "local",
155+
model: "hf_ggml-org_embeddinggemma-300m-qat-Q8_0.gguf",
156+
},
157+
},
158+
]);
159+
expect(
160+
llamaCppEmbeddingProviderAdapter.resolveIndexIdentity?.({
161+
config: {},
162+
provider: "local",
163+
model: modelPath,
164+
local: { modelPath },
165+
}),
166+
).toEqual({
167+
model: DEFAULT_LLAMA_CPP_EMBEDDING_MODEL,
168+
cacheKeyData: {
169+
provider: "local",
170+
model: DEFAULT_LLAMA_CPP_EMBEDDING_MODEL,
171+
},
172+
aliases: [
173+
{
174+
model: modelPath,
175+
cacheKeyData: {
176+
provider: "local",
177+
model: modelPath,
178+
},
179+
},
180+
{
181+
model: "hf_ggml-org_embeddinggemma-300m-qat-Q8_0.gguf",
182+
cacheKeyData: {
183+
provider: "local",
184+
model: "hf_ggml-org_embeddinggemma-300m-qat-Q8_0.gguf",
185+
},
186+
},
187+
],
188+
});
189+
expect(memoryHostEmbeddingMocks.createLocalEmbeddingProvider).toHaveBeenCalledWith(
190+
expect.objectContaining({
191+
model: modelPath,
192+
local: { modelPath },
193+
}),
194+
{
195+
nodeLlamaCppImportUrl: "file:///plugin/node-llama-cpp.js",
196+
},
197+
);
198+
});
199+
200+
it("keeps an arbitrary same-basename model path as a distinct identity", async () => {
201+
const modelPath = path.join(
202+
os.tmpdir(),
203+
"custom-models",
204+
DEFAULT_LLAMA_CPP_EMBEDDING_MODEL.split("/").at(-1)!,
205+
);
206+
memoryHostEmbeddingMocks.createLocalEmbeddingProvider.mockResolvedValue({
207+
id: "local",
208+
model: modelPath,
209+
embedQuery: vi.fn(),
210+
embedBatch: vi.fn(),
211+
});
212+
213+
const result = await createLlamaCppMemoryEmbeddingProvider(
214+
{
215+
config: {},
216+
provider: "local",
217+
fallback: "none",
218+
model: modelPath,
219+
local: { modelPath },
220+
},
221+
{ nodeLlamaCppImportUrl: "file:///plugin/node-llama-cpp.js" },
222+
);
223+
224+
expect(result.provider?.model).toBe(modelPath);
225+
expect(result.runtime?.cacheKeyData).toEqual({
226+
provider: "local",
227+
model: modelPath,
228+
});
229+
expect(result.runtime).not.toHaveProperty("indexIdentityAliases");
230+
});
231+
232+
it("keeps a bare same-basename file in the default cache as a distinct identity", async () => {
233+
const modelPath = path.join(
234+
os.homedir(),
235+
".node-llama-cpp",
236+
"models",
237+
DEFAULT_LLAMA_CPP_EMBEDDING_MODEL.split("/").at(-1)!,
238+
);
239+
memoryHostEmbeddingMocks.createLocalEmbeddingProvider.mockResolvedValue({
240+
id: "local",
241+
model: modelPath,
242+
embedQuery: vi.fn(),
243+
embedBatch: vi.fn(),
244+
});
245+
246+
const result = await createLlamaCppMemoryEmbeddingProvider(
247+
{
248+
config: {},
249+
provider: "local",
250+
fallback: "none",
251+
model: modelPath,
252+
local: { modelPath },
253+
},
254+
{ nodeLlamaCppImportUrl: "file:///plugin/node-llama-cpp.js" },
255+
);
256+
257+
expect(result.provider?.model).toBe(modelPath);
258+
expect(result.runtime).not.toHaveProperty("indexIdentityAliases");
259+
});
260+
261+
it("keeps the default model identity with a custom cache directory", async () => {
262+
const modelCacheDir = path.join(os.tmpdir(), "llama-cpp-model-cache");
263+
const modelPath = path.join(modelCacheDir, "hf_ggml-org_embeddinggemma-300m-qat-Q8_0.gguf");
264+
memoryHostEmbeddingMocks.createLocalEmbeddingProvider.mockResolvedValue({
265+
id: "local",
266+
model: modelPath,
267+
embedQuery: vi.fn(),
268+
embedBatch: vi.fn(),
269+
});
270+
271+
const result = await createLlamaCppMemoryEmbeddingProvider(
272+
{
273+
config: {},
274+
provider: "local",
275+
fallback: "none",
276+
model: DEFAULT_LLAMA_CPP_EMBEDDING_MODEL,
277+
local: { modelPath: DEFAULT_LLAMA_CPP_EMBEDDING_MODEL, modelCacheDir },
278+
},
279+
{ nodeLlamaCppImportUrl: "file:///plugin/node-llama-cpp.js" },
280+
);
281+
282+
expect(result.provider?.model).toBe(DEFAULT_LLAMA_CPP_EMBEDDING_MODEL);
283+
expect(result.runtime?.cacheKeyData).toEqual({
284+
provider: "local",
285+
model: DEFAULT_LLAMA_CPP_EMBEDDING_MODEL,
286+
});
287+
expect(result.runtime?.indexIdentityAliases).toEqual([
288+
{
289+
model: modelPath,
290+
cacheKeyData: {
291+
provider: "local",
292+
model: modelPath,
293+
},
294+
},
295+
{
296+
model: "hf_ggml-org_embeddinggemma-300m-qat-Q8_0.gguf",
297+
cacheKeyData: {
298+
provider: "local",
299+
model: "hf_ggml-org_embeddinggemma-300m-qat-Q8_0.gguf",
300+
},
301+
},
302+
]);
303+
});
304+
305+
it.each([
306+
{
307+
direction: "default URI to exact relative cache artifact",
308+
modelPath: DEFAULT_LLAMA_CPP_EMBEDDING_MODEL,
309+
},
310+
{
311+
direction: "exact relative cache artifact to default URI",
312+
modelPath: "hf_ggml-org_embeddinggemma-300m-qat-Q8_0.gguf",
313+
},
314+
])("keeps $direction compatible", ({ modelPath }) => {
315+
const modelCacheDir = path.join(os.tmpdir(), "llama-cpp-relative-model-cache");
316+
const relativeModelPath = "hf_ggml-org_embeddinggemma-300m-qat-Q8_0.gguf";
317+
const resolvedModelPath = path.join(modelCacheDir, relativeModelPath);
318+
319+
expect(
320+
llamaCppEmbeddingProviderAdapter.resolveIndexIdentity?.({
321+
config: {},
322+
provider: "local",
323+
model: modelPath,
324+
local: { modelPath, modelCacheDir },
325+
}),
326+
).toEqual({
327+
model: DEFAULT_LLAMA_CPP_EMBEDDING_MODEL,
328+
cacheKeyData: {
329+
provider: "local",
330+
model: DEFAULT_LLAMA_CPP_EMBEDDING_MODEL,
331+
},
332+
aliases: [
333+
{
334+
model: resolvedModelPath,
335+
cacheKeyData: {
336+
provider: "local",
337+
model: resolvedModelPath,
338+
},
339+
},
340+
{
341+
model: relativeModelPath,
342+
cacheKeyData: {
343+
provider: "local",
344+
model: relativeModelPath,
345+
},
346+
},
347+
],
348+
});
349+
});
350+
351+
it("keeps the default model identity for its exact relative cache artifact", async () => {
352+
const modelCacheDir = path.join(os.tmpdir(), "llama-cpp-relative-model-cache");
353+
const modelPath = "hf_ggml-org_embeddinggemma-300m-qat-Q8_0.gguf";
354+
const resolvedModelPath = path.join(modelCacheDir, modelPath);
355+
memoryHostEmbeddingMocks.createLocalEmbeddingProvider.mockResolvedValue({
356+
id: "local",
357+
model: modelPath,
358+
embedQuery: vi.fn(),
359+
embedBatch: vi.fn(),
360+
});
361+
362+
const result = await createLlamaCppMemoryEmbeddingProvider(
363+
{
364+
config: {},
365+
provider: "local",
366+
fallback: "none",
367+
model: modelPath,
368+
local: { modelPath, modelCacheDir },
369+
},
370+
{ nodeLlamaCppImportUrl: "file:///plugin/node-llama-cpp.js" },
371+
);
372+
373+
expect(result.provider?.model).toBe(DEFAULT_LLAMA_CPP_EMBEDDING_MODEL);
374+
expect(result.runtime?.indexIdentityAliases).toEqual([
375+
{
376+
model: resolvedModelPath,
377+
cacheKeyData: {
378+
provider: "local",
379+
model: resolvedModelPath,
380+
},
381+
},
382+
{
383+
model: modelPath,
384+
cacheKeyData: {
385+
provider: "local",
386+
model: modelPath,
387+
},
388+
},
389+
]);
390+
});
391+
109392
it("formats missing runtime errors with the plugin install command", () => {
110393
const err = Object.assign(new Error("Cannot find package 'node-llama-cpp'"), {
111394
code: "ERR_MODULE_NOT_FOUND",

0 commit comments

Comments
 (0)