From d7de7a08fa3b517f1d5cae39adbbd5c4bc79f069 Mon Sep 17 00:00:00 2001 From: Alejo Castillo Date: Mon, 13 Jul 2026 10:34:38 -0300 Subject: [PATCH] fix: avoid undefined metadata when retrieval finds no context Empty retrieval used to leak undefined metadata entries (topK=5). Return [] when no context and coerce missing metadata to {}. Closes #49 Co-authored-by: Cursor --- src/context-service/index.ts | 3 +- src/database.retrieve.test.ts | 63 +++++++++++++++++++++++++++++++++++ src/database.ts | 15 ++++----- 3 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 src/database.retrieve.test.ts diff --git a/src/context-service/index.ts b/src/context-service/index.ts index ba98612..6afe7fd 100644 --- a/src/context-service/index.ts +++ b/src/context-service/index.ts @@ -104,7 +104,8 @@ export class ContextService { run_type: "tool", } )(context), - metadata: context.map(({ metadata }) => metadata) as TMetadata[], + metadata: + context.length > 0 ? context.map(({ metadata }) => (metadata ?? {}) as TMetadata) : [], rawContext: context, }; }, diff --git a/src/database.retrieve.test.ts b/src/database.retrieve.test.ts new file mode 100644 index 0000000..63a4d10 --- /dev/null +++ b/src/database.retrieve.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, mock, test } from "bun:test"; +import { Database } from "./database"; + +describe("Database.retrieve — empty / missing metadata (#49)", () => { + test("returns [] when every result has undefined data", async () => { + const query = mock(() => + Promise.resolve([ + { id: "1", score: 0.9, data: undefined, metadata: undefined }, + { id: "2", score: 0.8, data: undefined, metadata: undefined }, + { id: "3", score: 0.7, data: undefined, metadata: undefined }, + { id: "4", score: 0.6, data: undefined, metadata: undefined }, + { id: "5", score: 0.5, data: undefined, metadata: undefined }, + ]) + ); + + const database = new Database({ query } as never); + const result = await database.retrieve({ + question: "unrelated question", + topK: 5, + similarityThreshold: 0.5, + namespace: "", + }); + + expect(result).toEqual([]); + expect(query).toHaveBeenCalled(); + }); + + test("returns [] when the vector index yields no rows", async () => { + const query = mock(() => Promise.resolve([])); + const database = new Database({ query } as never); + + const result = await database.retrieve({ + question: "anything", + topK: 5, + similarityThreshold: 0.5, + namespace: "", + }); + + expect(result).toEqual([]); + }); + + test("coerces missing metadata to {} instead of undefined", async () => { + const query = mock(() => + Promise.resolve([ + { id: "1", score: 0.95, data: "Tokyo is the capital of Japan.", metadata: undefined }, + { id: "2", score: 0.9, data: "Kyoto was the former capital.", metadata: { unit: "A" } }, + ]) + ); + + const database = new Database({ query } as never); + const result = await database.retrieve<{ unit?: string }>({ + question: "capital of Japan", + topK: 5, + similarityThreshold: 0.5, + namespace: "", + }); + + expect(result.map(({ id, metadata }) => ({ id, metadata }))).toEqual([ + { id: "1", metadata: {} }, + { id: "2", metadata: { unit: "A" } }, + ]); + }); +}); diff --git a/src/database.ts b/src/database.ts index 8b6c822..075240f 100644 --- a/src/database.ts +++ b/src/database.ts @@ -127,14 +127,9 @@ export class Database { if (allValuesUndefined) { console.error("There is no answer for this question in the provided context."); - - return [ - { - data: "There is no answer for this question in the provided context.", - id: "error", - metadata: {} as TMetadata, - }, - ]; + // Return an empty list so callers do not surface a fixed-size array of + // undefined metadata (default topK is 5) when nothing relevant is found. + return []; } const facts = result @@ -142,7 +137,9 @@ export class Database { .map((embedding) => ({ data: embedding.data ?? "", id: embedding.id.toString(), - metadata: embedding.metadata as TMetadata, + // Vectors without stored metadata previously leaked `undefined` entries + // into `chat()` responses (see #49). + metadata: (embedding.metadata ?? {}) as TMetadata, })); return facts;