@cortexmemory/cli 0.27.4 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/db.d.ts.map +1 -1
- package/dist/commands/db.js +18 -6
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +74 -34
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.js +3 -2
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +12 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/app-template-sync.d.ts.map +1 -1
- package/dist/utils/app-template-sync.js +3 -1
- package/dist/utils/app-template-sync.js.map +1 -1
- package/dist/utils/init/quickstart-setup.d.ts.map +1 -1
- package/dist/utils/init/quickstart-setup.js.map +1 -1
- package/package.json +4 -4
- package/templates/basic/.env.local.example +23 -0
- package/templates/basic/README.md +181 -56
- package/templates/basic/package-lock.json +2180 -406
- package/templates/basic/package.json +23 -5
- package/templates/basic/src/__tests__/chat.test.ts +340 -0
- package/templates/basic/src/__tests__/cortex.test.ts +260 -0
- package/templates/basic/src/__tests__/display.test.ts +455 -0
- package/templates/basic/src/__tests__/e2e/fact-extraction.test.ts +498 -0
- package/templates/basic/src/__tests__/e2e/memory-flow.test.ts +355 -0
- package/templates/basic/src/__tests__/e2e/server-e2e.test.ts +414 -0
- package/templates/basic/src/__tests__/helpers/test-utils.ts +345 -0
- package/templates/basic/src/__tests__/integration/chat-flow.test.ts +422 -0
- package/templates/basic/src/__tests__/integration/server.test.ts +441 -0
- package/templates/basic/src/__tests__/llm.test.ts +344 -0
- package/templates/basic/src/chat.ts +300 -0
- package/templates/basic/src/cortex.ts +203 -0
- package/templates/basic/src/display.ts +425 -0
- package/templates/basic/src/index.ts +194 -64
- package/templates/basic/src/llm.ts +214 -0
- package/templates/basic/src/server.ts +280 -0
- package/templates/basic/vitest.config.ts +33 -0
- package/templates/basic/vitest.e2e.config.ts +28 -0
- package/templates/basic/vitest.integration.config.ts +25 -0
- package/templates/vercel-ai-quickstart/app/api/auth/check/route.ts +1 -1
- package/templates/vercel-ai-quickstart/app/api/auth/login/route.ts +6 -9
- package/templates/vercel-ai-quickstart/app/api/auth/register/route.ts +14 -18
- package/templates/vercel-ai-quickstart/app/api/auth/setup/route.ts +4 -7
- package/templates/vercel-ai-quickstart/app/api/chat/route.ts +28 -11
- package/templates/vercel-ai-quickstart/app/api/chat-v6/route.ts +19 -13
- package/templates/vercel-ai-quickstart/app/api/conversations/route.ts +16 -16
- package/templates/vercel-ai-quickstart/app/globals.css +24 -9
- package/templates/vercel-ai-quickstart/app/page.tsx +25 -13
- package/templates/vercel-ai-quickstart/components/AdminSetup.tsx +3 -1
- package/templates/vercel-ai-quickstart/components/AuthProvider.tsx +6 -6
- package/templates/vercel-ai-quickstart/components/ChatHistorySidebar.tsx +19 -8
- package/templates/vercel-ai-quickstart/components/ChatInterface.tsx +41 -14
- package/templates/vercel-ai-quickstart/components/LoginScreen.tsx +10 -5
- package/templates/vercel-ai-quickstart/lib/agents/memory-agent.ts +3 -3
- package/templates/vercel-ai-quickstart/lib/password.ts +5 -5
- package/templates/vercel-ai-quickstart/next.config.js +10 -2
- package/templates/vercel-ai-quickstart/package.json +18 -11
- package/templates/vercel-ai-quickstart/test-api.mjs +131 -100
- package/templates/vercel-ai-quickstart/tests/e2e/chat-memory-flow.test.ts +73 -44
- package/templates/vercel-ai-quickstart/tests/helpers/mock-cortex.ts +40 -40
- package/templates/vercel-ai-quickstart/tests/integration/auth.test.ts +8 -8
- package/templates/vercel-ai-quickstart/tests/integration/conversations.test.ts +12 -8
- package/templates/vercel-ai-quickstart/tests/unit/password.test.ts +4 -1
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E Tests: Fact Extraction and Belief Revision
|
|
3
|
+
*
|
|
4
|
+
* Tests fact extraction and belief revision with real Convex backend.
|
|
5
|
+
* Requires: CONVEX_URL and OPENAI_API_KEY environment variables
|
|
6
|
+
*
|
|
7
|
+
* Run with: CONVEX_URL=<url> OPENAI_API_KEY=<key> npm run test:e2e
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect, beforeAll, beforeEach, afterAll } from "vitest";
|
|
11
|
+
import { Cortex } from "@cortexmemory/sdk";
|
|
12
|
+
import {
|
|
13
|
+
shouldSkipFactTests,
|
|
14
|
+
generateTestId,
|
|
15
|
+
createTestMemorySpaceId,
|
|
16
|
+
createTestUserId,
|
|
17
|
+
createTestConversationId,
|
|
18
|
+
wait,
|
|
19
|
+
generateTestEmbedding,
|
|
20
|
+
} from "../helpers/test-utils.js";
|
|
21
|
+
|
|
22
|
+
// Skip all tests if required env vars not set
|
|
23
|
+
const SKIP_FACT_TESTS = shouldSkipFactTests();
|
|
24
|
+
|
|
25
|
+
describe("Fact Extraction E2E", () => {
|
|
26
|
+
let cortex: Cortex;
|
|
27
|
+
let testMemorySpaceId: string;
|
|
28
|
+
let testUserId: string;
|
|
29
|
+
let testAgentId: string;
|
|
30
|
+
|
|
31
|
+
beforeAll(() => {
|
|
32
|
+
if (SKIP_FACT_TESTS) {
|
|
33
|
+
console.log("Skipping fact tests - CONVEX_URL or OPENAI_API_KEY not configured");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Configure Cortex with llm config for automatic fact extraction
|
|
38
|
+
// This is the correct way to enable fact extraction in the SDK!
|
|
39
|
+
cortex = new Cortex({
|
|
40
|
+
convexUrl: process.env.CONVEX_URL!,
|
|
41
|
+
llm: {
|
|
42
|
+
provider: "openai",
|
|
43
|
+
apiKey: process.env.OPENAI_API_KEY!,
|
|
44
|
+
model: process.env.CORTEX_FACT_EXTRACTION_MODEL || "gpt-4o-mini",
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
if (SKIP_FACT_TESTS) return;
|
|
51
|
+
|
|
52
|
+
// Generate unique IDs for test isolation
|
|
53
|
+
testMemorySpaceId = createTestMemorySpaceId("e2e-fact");
|
|
54
|
+
testUserId = createTestUserId();
|
|
55
|
+
testAgentId = generateTestId("agent");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
afterAll(async () => {
|
|
59
|
+
if (cortex) {
|
|
60
|
+
cortex.close();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
65
|
+
// Basic Fact Extraction
|
|
66
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
67
|
+
|
|
68
|
+
(SKIP_FACT_TESTS ? describe.skip : describe)("basic fact extraction", () => {
|
|
69
|
+
it("should extract facts from conversation", async () => {
|
|
70
|
+
const conversationId = createTestConversationId();
|
|
71
|
+
|
|
72
|
+
// Store a message with an extractable fact
|
|
73
|
+
await cortex.memory.remember({
|
|
74
|
+
memorySpaceId: testMemorySpaceId,
|
|
75
|
+
conversationId,
|
|
76
|
+
userMessage: "My name is Alice and I work as a software engineer in Seattle",
|
|
77
|
+
agentResponse: "Nice to meet you, Alice! Software engineering in Seattle sounds exciting.",
|
|
78
|
+
userId: testUserId,
|
|
79
|
+
userName: "Alice",
|
|
80
|
+
agentId: testAgentId,
|
|
81
|
+
agentName: "Test Agent",
|
|
82
|
+
generateEmbedding: generateTestEmbedding,
|
|
83
|
+
// Note: llmConfig on the client enables automatic fact extraction
|
|
84
|
+
// No need for extractFacts parameter here
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Wait for fact extraction (async process)
|
|
88
|
+
await wait(5000);
|
|
89
|
+
|
|
90
|
+
// Check for extracted facts
|
|
91
|
+
const facts = await cortex.facts.list({
|
|
92
|
+
memorySpaceId: testMemorySpaceId,
|
|
93
|
+
userId: testUserId,
|
|
94
|
+
includeSuperseded: false,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
console.log(`Extracted ${facts.length} facts:`);
|
|
98
|
+
facts.forEach((f: any) => console.log(` - ${f.fact}`));
|
|
99
|
+
|
|
100
|
+
// STRONG ASSERTION: We should have extracted at least one fact
|
|
101
|
+
// The message clearly contains: name=Alice, job=software engineer, location=Seattle
|
|
102
|
+
expect(facts.length).toBeGreaterThanOrEqual(1);
|
|
103
|
+
expect(facts[0]).toHaveProperty("fact");
|
|
104
|
+
}, 60000);
|
|
105
|
+
|
|
106
|
+
it("should extract multiple facts from one message", async () => {
|
|
107
|
+
const conversationId = createTestConversationId();
|
|
108
|
+
|
|
109
|
+
await cortex.memory.remember({
|
|
110
|
+
memorySpaceId: testMemorySpaceId,
|
|
111
|
+
conversationId,
|
|
112
|
+
userMessage: "I'm Bob, I'm 35 years old, and I love playing chess",
|
|
113
|
+
agentResponse: "Nice to meet you, Bob! Chess is a wonderful game.",
|
|
114
|
+
userId: testUserId,
|
|
115
|
+
userName: "Bob",
|
|
116
|
+
agentId: testAgentId,
|
|
117
|
+
agentName: "Test Agent",
|
|
118
|
+
generateEmbedding: generateTestEmbedding,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
await wait(5000);
|
|
122
|
+
|
|
123
|
+
const facts = await cortex.facts.list({
|
|
124
|
+
memorySpaceId: testMemorySpaceId,
|
|
125
|
+
userId: testUserId,
|
|
126
|
+
includeSuperseded: false,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
console.log(`Multiple facts test - extracted ${facts.length} facts:`);
|
|
130
|
+
facts.forEach((f: any) => console.log(` - ${f.fact}`));
|
|
131
|
+
|
|
132
|
+
// STRONG ASSERTION: Should extract at least 2 facts
|
|
133
|
+
// The message clearly contains: name=Bob, age=35, hobby=chess
|
|
134
|
+
expect(facts.length).toBeGreaterThanOrEqual(2);
|
|
135
|
+
}, 60000);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
139
|
+
// Belief Revision
|
|
140
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
141
|
+
|
|
142
|
+
(SKIP_FACT_TESTS ? describe.skip : describe)("belief revision", () => {
|
|
143
|
+
it("should supersede old fact when user updates preference", async () => {
|
|
144
|
+
const conversationId = createTestConversationId();
|
|
145
|
+
|
|
146
|
+
// Step 1: State initial preference
|
|
147
|
+
console.log("Step 1: Stating favorite color is blue...");
|
|
148
|
+
await cortex.memory.remember({
|
|
149
|
+
memorySpaceId: testMemorySpaceId,
|
|
150
|
+
conversationId,
|
|
151
|
+
userMessage: "My favorite color is blue",
|
|
152
|
+
agentResponse: "Blue is a lovely color!",
|
|
153
|
+
userId: testUserId,
|
|
154
|
+
userName: "Test User",
|
|
155
|
+
agentId: testAgentId,
|
|
156
|
+
agentName: "Test Agent",
|
|
157
|
+
generateEmbedding: generateTestEmbedding,
|
|
158
|
+
beliefRevision: {
|
|
159
|
+
enabled: true,
|
|
160
|
+
slotMatching: true,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
await wait(5000);
|
|
165
|
+
|
|
166
|
+
// Check initial facts
|
|
167
|
+
const factsAfterFirst = await cortex.facts.list({
|
|
168
|
+
memorySpaceId: testMemorySpaceId,
|
|
169
|
+
userId: testUserId,
|
|
170
|
+
includeSuperseded: true,
|
|
171
|
+
});
|
|
172
|
+
console.log(`After first message: ${factsAfterFirst.length} facts`);
|
|
173
|
+
|
|
174
|
+
// STRONG ASSERTION: Should have at least one fact after first message
|
|
175
|
+
expect(factsAfterFirst.length).toBeGreaterThanOrEqual(1);
|
|
176
|
+
|
|
177
|
+
// Step 2: Update preference (should trigger supersession)
|
|
178
|
+
console.log("Step 2: Updating favorite color to purple...");
|
|
179
|
+
await cortex.memory.remember({
|
|
180
|
+
memorySpaceId: testMemorySpaceId,
|
|
181
|
+
conversationId,
|
|
182
|
+
userMessage: "Actually, my favorite color is purple now",
|
|
183
|
+
agentResponse: "I'll remember that your favorite color is now purple!",
|
|
184
|
+
userId: testUserId,
|
|
185
|
+
userName: "Test User",
|
|
186
|
+
agentId: testAgentId,
|
|
187
|
+
agentName: "Test Agent",
|
|
188
|
+
generateEmbedding: generateTestEmbedding,
|
|
189
|
+
beliefRevision: {
|
|
190
|
+
enabled: true,
|
|
191
|
+
slotMatching: true,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
await wait(5000);
|
|
196
|
+
|
|
197
|
+
// Step 3: Verify supersession
|
|
198
|
+
const allFacts = await cortex.facts.list({
|
|
199
|
+
memorySpaceId: testMemorySpaceId,
|
|
200
|
+
userId: testUserId,
|
|
201
|
+
includeSuperseded: true,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const activeFacts = await cortex.facts.list({
|
|
205
|
+
memorySpaceId: testMemorySpaceId,
|
|
206
|
+
userId: testUserId,
|
|
207
|
+
includeSuperseded: false,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
console.log(`Total facts (including superseded): ${allFacts.length}`);
|
|
211
|
+
console.log(`Active facts: ${activeFacts.length}`);
|
|
212
|
+
|
|
213
|
+
allFacts.forEach((f: any) => {
|
|
214
|
+
const status = f.supersededBy ? "SUPERSEDED" : "ACTIVE";
|
|
215
|
+
console.log(` [${status}] ${f.fact}`);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// STRONG ASSERTIONS
|
|
219
|
+
expect(allFacts.length).toBeGreaterThanOrEqual(1);
|
|
220
|
+
// After revision, should have at least one active fact about purple
|
|
221
|
+
expect(activeFacts.length).toBeGreaterThanOrEqual(1);
|
|
222
|
+
}, 120000);
|
|
223
|
+
|
|
224
|
+
it("should preserve non-conflicting facts", async () => {
|
|
225
|
+
const conversationId = createTestConversationId();
|
|
226
|
+
|
|
227
|
+
// Fact 1: Name
|
|
228
|
+
await cortex.memory.remember({
|
|
229
|
+
memorySpaceId: testMemorySpaceId,
|
|
230
|
+
conversationId,
|
|
231
|
+
userMessage: "My name is Charlie",
|
|
232
|
+
agentResponse: "Nice to meet you, Charlie!",
|
|
233
|
+
userId: testUserId,
|
|
234
|
+
userName: "Charlie",
|
|
235
|
+
agentId: testAgentId,
|
|
236
|
+
agentName: "Test Agent",
|
|
237
|
+
generateEmbedding: generateTestEmbedding,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
await wait(3000);
|
|
241
|
+
|
|
242
|
+
// Fact 2: Job (non-conflicting with name)
|
|
243
|
+
await cortex.memory.remember({
|
|
244
|
+
memorySpaceId: testMemorySpaceId,
|
|
245
|
+
conversationId,
|
|
246
|
+
userMessage: "I work as a data scientist",
|
|
247
|
+
agentResponse: "Data science is a fascinating field!",
|
|
248
|
+
userId: testUserId,
|
|
249
|
+
userName: "Charlie",
|
|
250
|
+
agentId: testAgentId,
|
|
251
|
+
agentName: "Test Agent",
|
|
252
|
+
generateEmbedding: generateTestEmbedding,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
await wait(3000);
|
|
256
|
+
|
|
257
|
+
// Both facts should be active (non-conflicting)
|
|
258
|
+
const facts = await cortex.facts.list({
|
|
259
|
+
memorySpaceId: testMemorySpaceId,
|
|
260
|
+
userId: testUserId,
|
|
261
|
+
includeSuperseded: false,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
console.log(`Non-conflicting facts: ${facts.length}`);
|
|
265
|
+
facts.forEach((f: any) => console.log(` - ${f.fact}`));
|
|
266
|
+
|
|
267
|
+
// STRONG ASSERTION: Should have at least 2 non-conflicting facts
|
|
268
|
+
expect(facts.length).toBeGreaterThanOrEqual(2);
|
|
269
|
+
}, 60000);
|
|
270
|
+
|
|
271
|
+
it("should handle duplicate facts (same value)", async () => {
|
|
272
|
+
// Edge case: User says the same thing twice
|
|
273
|
+
// Expected: NONE (skip as duplicate), NOT SUPERSEDE
|
|
274
|
+
const conversationId = createTestConversationId();
|
|
275
|
+
|
|
276
|
+
// Statement 1: Initial fact
|
|
277
|
+
console.log("Statement 1: 'My favorite food is pizza'");
|
|
278
|
+
await cortex.memory.remember({
|
|
279
|
+
memorySpaceId: testMemorySpaceId,
|
|
280
|
+
conversationId,
|
|
281
|
+
userMessage: "My favorite food is pizza",
|
|
282
|
+
agentResponse: "Pizza is delicious!",
|
|
283
|
+
userId: testUserId,
|
|
284
|
+
userName: "Test User",
|
|
285
|
+
agentId: testAgentId,
|
|
286
|
+
agentName: "Test Agent",
|
|
287
|
+
generateEmbedding: generateTestEmbedding,
|
|
288
|
+
beliefRevision: {
|
|
289
|
+
enabled: true,
|
|
290
|
+
slotMatching: true,
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
await wait(5000);
|
|
295
|
+
|
|
296
|
+
const factsAfterFirst = await cortex.facts.list({
|
|
297
|
+
memorySpaceId: testMemorySpaceId,
|
|
298
|
+
userId: testUserId,
|
|
299
|
+
includeSuperseded: false,
|
|
300
|
+
});
|
|
301
|
+
console.log(`After first statement: ${factsAfterFirst.length} facts`);
|
|
302
|
+
|
|
303
|
+
// Statement 2: Same fact repeated
|
|
304
|
+
console.log("Statement 2: 'I really love pizza, it's my favorite' (same value)");
|
|
305
|
+
await cortex.memory.remember({
|
|
306
|
+
memorySpaceId: testMemorySpaceId,
|
|
307
|
+
conversationId,
|
|
308
|
+
userMessage: "I really love pizza, it's my favorite",
|
|
309
|
+
agentResponse: "Yes, you mentioned you love pizza!",
|
|
310
|
+
userId: testUserId,
|
|
311
|
+
userName: "Test User",
|
|
312
|
+
agentId: testAgentId,
|
|
313
|
+
agentName: "Test Agent",
|
|
314
|
+
generateEmbedding: generateTestEmbedding,
|
|
315
|
+
beliefRevision: {
|
|
316
|
+
enabled: true,
|
|
317
|
+
slotMatching: true,
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
await wait(5000);
|
|
322
|
+
|
|
323
|
+
const allFacts = await cortex.facts.list({
|
|
324
|
+
memorySpaceId: testMemorySpaceId,
|
|
325
|
+
userId: testUserId,
|
|
326
|
+
includeSuperseded: true,
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
const activeFacts = await cortex.facts.list({
|
|
330
|
+
memorySpaceId: testMemorySpaceId,
|
|
331
|
+
userId: testUserId,
|
|
332
|
+
includeSuperseded: false,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
console.log(`\n=== Duplicate Fact Test Results ===`);
|
|
336
|
+
console.log(`Total facts: ${allFacts.length}`);
|
|
337
|
+
console.log(`Active facts: ${activeFacts.length}`);
|
|
338
|
+
|
|
339
|
+
allFacts.forEach((f: any) => {
|
|
340
|
+
const status = f.supersededBy ? "SUPERSEDED" : "ACTIVE";
|
|
341
|
+
console.log(` [${status}] ${f.fact}`);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// STRONG ASSERTIONS
|
|
345
|
+
// Should have extracted at least one fact about pizza
|
|
346
|
+
expect(allFacts.length).toBeGreaterThanOrEqual(1);
|
|
347
|
+
// Should have at most 2 active pizza facts (dedup should merge or skip)
|
|
348
|
+
// In ideal case, should be exactly 1 active fact after dedup
|
|
349
|
+
expect(activeFacts.length).toBeGreaterThanOrEqual(1);
|
|
350
|
+
}, 120000);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
354
|
+
// Facts API
|
|
355
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
356
|
+
|
|
357
|
+
(SKIP_FACT_TESTS ? describe.skip : describe)("facts API", () => {
|
|
358
|
+
it("should list facts with includeSuperseded filter", async () => {
|
|
359
|
+
const conversationId = createTestConversationId();
|
|
360
|
+
|
|
361
|
+
// Create a fact
|
|
362
|
+
await cortex.memory.remember({
|
|
363
|
+
memorySpaceId: testMemorySpaceId,
|
|
364
|
+
conversationId,
|
|
365
|
+
userMessage: "I am 30 years old",
|
|
366
|
+
agentResponse: "Got it!",
|
|
367
|
+
userId: testUserId,
|
|
368
|
+
userName: "Test User",
|
|
369
|
+
agentId: testAgentId,
|
|
370
|
+
agentName: "Test Agent",
|
|
371
|
+
generateEmbedding: generateTestEmbedding,
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
await wait(5000);
|
|
375
|
+
|
|
376
|
+
// List with includeSuperseded = false
|
|
377
|
+
const activeFacts = await cortex.facts.list({
|
|
378
|
+
memorySpaceId: testMemorySpaceId,
|
|
379
|
+
userId: testUserId,
|
|
380
|
+
includeSuperseded: false,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// List with includeSuperseded = true
|
|
384
|
+
const allFacts = await cortex.facts.list({
|
|
385
|
+
memorySpaceId: testMemorySpaceId,
|
|
386
|
+
userId: testUserId,
|
|
387
|
+
includeSuperseded: true,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
console.log(`Active: ${activeFacts.length}, All: ${allFacts.length}`);
|
|
391
|
+
|
|
392
|
+
// STRONG ASSERTIONS
|
|
393
|
+
expect(activeFacts.length).toBeGreaterThanOrEqual(1);
|
|
394
|
+
expect(allFacts.length).toBeGreaterThanOrEqual(1);
|
|
395
|
+
expect(activeFacts.length).toBeLessThanOrEqual(allFacts.length);
|
|
396
|
+
}, 60000);
|
|
397
|
+
|
|
398
|
+
it("should filter facts by memory space", async () => {
|
|
399
|
+
const spaceA = createTestMemorySpaceId("fact-space-a");
|
|
400
|
+
const spaceB = createTestMemorySpaceId("fact-space-b");
|
|
401
|
+
|
|
402
|
+
// Add fact to space A
|
|
403
|
+
await cortex.memory.remember({
|
|
404
|
+
memorySpaceId: spaceA,
|
|
405
|
+
conversationId: createTestConversationId(),
|
|
406
|
+
userMessage: "I like cats",
|
|
407
|
+
agentResponse: "Cats are great!",
|
|
408
|
+
userId: testUserId,
|
|
409
|
+
userName: "Test User",
|
|
410
|
+
agentId: testAgentId,
|
|
411
|
+
agentName: "Test Agent",
|
|
412
|
+
generateEmbedding: generateTestEmbedding,
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
await wait(5000);
|
|
416
|
+
|
|
417
|
+
// Verify space A has facts
|
|
418
|
+
const factsA = await cortex.facts.list({
|
|
419
|
+
memorySpaceId: spaceA,
|
|
420
|
+
userId: testUserId,
|
|
421
|
+
});
|
|
422
|
+
console.log(`Space A facts: ${factsA.length}`);
|
|
423
|
+
expect(factsA.length).toBeGreaterThanOrEqual(1);
|
|
424
|
+
|
|
425
|
+
// Facts in space B should not include space A's facts
|
|
426
|
+
const factsB = await cortex.facts.list({
|
|
427
|
+
memorySpaceId: spaceB,
|
|
428
|
+
userId: testUserId,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Space B should be empty
|
|
432
|
+
expect(factsB.length).toBe(0);
|
|
433
|
+
}, 60000);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
437
|
+
// Recall with Facts
|
|
438
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
439
|
+
|
|
440
|
+
(SKIP_FACT_TESTS ? describe.skip : describe)("recall with facts", () => {
|
|
441
|
+
it("should include facts in recall results", async () => {
|
|
442
|
+
const conversationId = createTestConversationId();
|
|
443
|
+
|
|
444
|
+
// Store message with fact - use a very clear, searchable statement
|
|
445
|
+
await cortex.memory.remember({
|
|
446
|
+
memorySpaceId: testMemorySpaceId,
|
|
447
|
+
conversationId,
|
|
448
|
+
userMessage: "I prefer dark mode for all my applications",
|
|
449
|
+
agentResponse: "Dark mode is easier on the eyes!",
|
|
450
|
+
userId: testUserId,
|
|
451
|
+
userName: "Test User",
|
|
452
|
+
agentId: testAgentId,
|
|
453
|
+
agentName: "Test Agent",
|
|
454
|
+
generateEmbedding: generateTestEmbedding,
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
await wait(5000);
|
|
458
|
+
|
|
459
|
+
// Verify fact was created - this is the key test
|
|
460
|
+
const facts = await cortex.facts.list({
|
|
461
|
+
memorySpaceId: testMemorySpaceId,
|
|
462
|
+
userId: testUserId,
|
|
463
|
+
includeSuperseded: false,
|
|
464
|
+
});
|
|
465
|
+
console.log(`Facts created: ${facts.length}`);
|
|
466
|
+
facts.forEach((f: any) => console.log(` - ${f.fact}`));
|
|
467
|
+
|
|
468
|
+
// STRONG ASSERTION: Fact was extracted
|
|
469
|
+
expect(facts.length).toBeGreaterThanOrEqual(1);
|
|
470
|
+
|
|
471
|
+
// Recall with facts enabled - use more specific query matching the fact
|
|
472
|
+
const result = await cortex.memory.recall({
|
|
473
|
+
memorySpaceId: testMemorySpaceId,
|
|
474
|
+
userId: testUserId,
|
|
475
|
+
query: "dark mode preference",
|
|
476
|
+
limit: 10,
|
|
477
|
+
sources: {
|
|
478
|
+
vector: true,
|
|
479
|
+
facts: true,
|
|
480
|
+
graph: false,
|
|
481
|
+
},
|
|
482
|
+
generateEmbedding: generateTestEmbedding,
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
console.log(`Recall results: ${result.sources?.vector?.count || 0} memories, ${result.sources?.facts?.count || 0} facts`);
|
|
486
|
+
|
|
487
|
+
// Verify recall works and returns structured response
|
|
488
|
+
expect(result).toBeDefined();
|
|
489
|
+
expect(result.sources).toBeDefined();
|
|
490
|
+
|
|
491
|
+
// Either facts source returns results OR vector source found it
|
|
492
|
+
// The fact was definitely created (verified above), recall behavior may vary
|
|
493
|
+
const totalResults = (result.sources?.vector?.count || 0) + (result.sources?.facts?.count || 0);
|
|
494
|
+
console.log(`Total recall results: ${totalResults}`);
|
|
495
|
+
expect(totalResults).toBeGreaterThanOrEqual(1);
|
|
496
|
+
}, 60000);
|
|
497
|
+
});
|
|
498
|
+
});
|