Skip to content

Commit 8c8f099

Browse files
authored
Merge pull request #50 from devlux76/copilot/sub-pr-49
Apply review feedback to minimal Cortex query (P0-D)
2 parents 3876d88 + 1640537 commit 8c8f099

4 files changed

Lines changed: 107 additions & 2 deletions

File tree

cortex/Query.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ function dot(a: Float32Array, b: Float32Array): number {
2323
return sum;
2424
}
2525

26+
/**
27+
* Concatenates an array of equal-length vectors into a single flat buffer.
28+
* @param vectors - Must be non-empty; every element must have the same length.
29+
*/
2630
function concatVectors(vectors: Float32Array[]): Float32Array {
2731
const dim = vectors[0].length;
2832
const out = new Float32Array(vectors.length * dim);
@@ -129,6 +133,7 @@ export async function query(
129133
}
130134

131135
const combined = [...hotpathResults, ...coldResults];
136+
combined.sort((a, b) => b.score - a.score);
132137

133138
// Update activity for returned pages
134139
await Promise.all(combined.map(async ({ page }) => {

storage/IndexedDbMetadataStore.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,11 @@ export class IndexedDbMetadataStore implements MetadataStore {
145145
return this._get<Page>(STORE.pages, pageId);
146146
}
147147

148-
/** Returns all pages in the store. Used for warm/cold fallbacks in query. */
148+
/**
149+
* Returns all pages in the store. Used for warm/cold fallbacks in query.
150+
* TODO: Replace with a paginated or indexed scan before production use —
151+
* loading every page into memory is expensive for large corpora.
152+
*/
149153
async getAllPages(): Promise<Page[]> {
150154
return new Promise((resolve, reject) => {
151155
const tx = this.db.transaction(STORE.pages, "readonly");

tests/SalienceEngine.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,9 @@ class MockMetadataStore implements MetadataStore {
104104

105105
// --- Stubs for unused MetadataStore methods ---
106106
async putPage(): Promise<void> { /* stub */ }
107-
async getPage(): Promise<undefined> { return undefined; } async getAllPages(): Promise<any[]> { return []; } async putBook(): Promise<void> { /* stub */ }
107+
async getPage(): Promise<undefined> { return undefined; }
108+
async getAllPages(): Promise<any[]> { return []; }
109+
async putBook(): Promise<void> { /* stub */ }
108110
async getBook(): Promise<undefined> { return undefined; }
109111
async putVolume(): Promise<void> { /* stub */ }
110112
async getVolume(): Promise<undefined> { return undefined; }

tests/cortex/Query.test.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,44 @@ describe("cortex query (minimal)", () => {
6262
(globalThis as any).IDBKeyRange = FakeIDBKeyRange;
6363
});
6464

65+
it("returns empty results for an empty corpus", async () => {
66+
const metadataStore = await IndexedDbMetadataStore.open(freshDbName());
67+
const vectorStore = new MemoryVectorStore();
68+
69+
const backend = new DeterministicDummyEmbeddingBackend({ dimension: 4 });
70+
const vectorBackend = new TestVectorBackend();
71+
72+
const runner = new EmbeddingRunner(async () => ({
73+
backend,
74+
selectedKind: "dummy" as const,
75+
reason: "forced" as const,
76+
supportedKinds: ["dummy" as const],
77+
measurements: [],
78+
}));
79+
80+
const profile: ModelProfile = {
81+
modelId: "test-model",
82+
embeddingDimension: 4,
83+
contextWindowTokens: 64,
84+
truncationTokens: 48,
85+
maxChunkTokens: 5,
86+
source: "metadata",
87+
};
88+
89+
const result = await query("anything", {
90+
modelProfile: profile,
91+
embeddingRunner: runner,
92+
vectorStore,
93+
metadataStore,
94+
vectorBackend,
95+
topK: 5,
96+
});
97+
98+
expect(result.pages).toHaveLength(0);
99+
expect(result.scores).toHaveLength(0);
100+
expect(result.metadata.returned).toBe(0);
101+
});
102+
65103
it("returns the most relevant page and updates activity", async () => {
66104
const metadataStore = await IndexedDbMetadataStore.open(freshDbName());
67105
const vectorStore = new MemoryVectorStore();
@@ -121,4 +159,60 @@ describe("cortex query (minimal)", () => {
121159
expect(activity?.queryHitCount).toBe(1);
122160
expect(activity?.lastQueryAt).toBeDefined();
123161
});
162+
163+
it("returns results in descending score order (relevance)", async () => {
164+
const metadataStore = await IndexedDbMetadataStore.open(freshDbName());
165+
const vectorStore = new MemoryVectorStore();
166+
const keyPair = await generateKeyPair();
167+
168+
const backend = new DeterministicDummyEmbeddingBackend({ dimension: 4 });
169+
const vectorBackend = new TestVectorBackend();
170+
171+
const runner = new EmbeddingRunner(async () => ({
172+
backend,
173+
selectedKind: "dummy" as const,
174+
reason: "forced" as const,
175+
supportedKinds: ["dummy" as const],
176+
measurements: [],
177+
}));
178+
179+
const profile: ModelProfile = {
180+
modelId: "test-model",
181+
embeddingDimension: 4,
182+
contextWindowTokens: 64,
183+
truncationTokens: 48,
184+
maxChunkTokens: 5,
185+
source: "metadata",
186+
};
187+
188+
const text = "One two three four five six seven eight nine ten.";
189+
const ingestResult = await ingestText(text, {
190+
modelProfile: profile,
191+
embeddingRunner: runner,
192+
vectorStore,
193+
metadataStore,
194+
keyPair,
195+
});
196+
197+
expect(ingestResult.pages.length).toBeGreaterThanOrEqual(2);
198+
199+
const targetPage = ingestResult.pages[0];
200+
201+
const result = await query(targetPage.content, {
202+
modelProfile: profile,
203+
embeddingRunner: runner,
204+
vectorStore,
205+
metadataStore,
206+
vectorBackend,
207+
topK: ingestResult.pages.length,
208+
});
209+
210+
// Results must include the page whose content matches the query.
211+
expect(result.pages.map((p) => p.pageId)).toContain(targetPage.pageId);
212+
213+
// Scores must be in non-increasing order.
214+
for (let i = 1; i < result.scores.length; i++) {
215+
expect(result.scores[i]).toBeLessThanOrEqual(result.scores[i - 1]);
216+
}
217+
});
124218
});

0 commit comments

Comments
 (0)