@cogitator-ai/memory 0.10.0 → 0.11.1
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/README.md +212 -89
- package/dist/adapters/index.d.ts +10 -2
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/mongodb.d.ts +6 -0
- package/dist/adapters/mongodb.d.ts.map +1 -1
- package/dist/adapters/mongodb.js +60 -37
- package/dist/adapters/mongodb.js.map +1 -1
- package/dist/adapters/pgvector.d.ts +46 -0
- package/dist/adapters/pgvector.d.ts.map +1 -0
- package/dist/adapters/pgvector.js +55 -0
- package/dist/adapters/pgvector.js.map +1 -0
- package/dist/adapters/postgres.d.ts +4 -0
- package/dist/adapters/postgres.d.ts.map +1 -1
- package/dist/adapters/postgres.js +149 -68
- package/dist/adapters/postgres.js.map +1 -1
- package/dist/adapters/qdrant.d.ts +1 -0
- package/dist/adapters/qdrant.d.ts.map +1 -1
- package/dist/adapters/qdrant.js +49 -42
- package/dist/adapters/qdrant.js.map +1 -1
- package/dist/adapters/sqlite-driver.d.ts +11 -0
- package/dist/adapters/sqlite-driver.d.ts.map +1 -0
- package/dist/adapters/sqlite-driver.js +2 -0
- package/dist/adapters/sqlite-driver.js.map +1 -0
- package/dist/adapters/sqlite.d.ts +1 -0
- package/dist/adapters/sqlite.d.ts.map +1 -1
- package/dist/adapters/sqlite.js +18 -18
- package/dist/adapters/sqlite.js.map +1 -1
- package/dist/compaction.d.ts +14 -1
- package/dist/compaction.d.ts.map +1 -1
- package/dist/compaction.js +4 -1
- package/dist/compaction.js.map +1 -1
- package/dist/context-builder.d.ts +5 -0
- package/dist/context-builder.d.ts.map +1 -1
- package/dist/context-builder.js +10 -11
- package/dist/context-builder.js.map +1 -1
- package/dist/core-facts.d.ts +5 -0
- package/dist/core-facts.d.ts.map +1 -1
- package/dist/core-facts.js +1 -2
- package/dist/core-facts.js.map +1 -1
- package/dist/embedding/openai.d.ts +5 -0
- package/dist/embedding/openai.d.ts.map +1 -1
- package/dist/embedding/openai.js +24 -4
- package/dist/embedding/openai.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/knowledge-graph/entity-extractor.d.ts +24 -11
- package/dist/knowledge-graph/entity-extractor.d.ts.map +1 -1
- package/dist/knowledge-graph/entity-extractor.js +15 -3
- package/dist/knowledge-graph/entity-extractor.js.map +1 -1
- package/dist/knowledge-graph/graph-adapter.d.ts +1 -0
- package/dist/knowledge-graph/graph-adapter.d.ts.map +1 -1
- package/dist/knowledge-graph/graph-adapter.js +477 -453
- package/dist/knowledge-graph/graph-adapter.js.map +1 -1
- package/dist/knowledge-graph/sqlite-graph-adapter.d.ts +1 -0
- package/dist/knowledge-graph/sqlite-graph-adapter.d.ts.map +1 -1
- package/dist/knowledge-graph/sqlite-graph-adapter.js +461 -439
- package/dist/knowledge-graph/sqlite-graph-adapter.js.map +1 -1
- package/dist/schema.d.ts +4 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2 -0
- package/dist/schema.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -15,6 +15,8 @@ pnpm add mongodb # For MongoDB adapter
|
|
|
15
15
|
pnpm add @qdrant/js-client-rest # For Qdrant embedding adapter
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
With `@cogitator-ai/core` you usually configure memory on the runtime (`new Cogitator({ memory: { adapter: 'postgres', postgres: { ... } } })`) and read it with `await cog.getMemory()`; the adapters below are for direct use. Website docs: [Memory](https://cogitator.app/docs/memory), [Adapters](https://cogitator.app/docs/memory/adapters), [Embeddings](https://cogitator.app/docs/memory/embeddings), [Hybrid Search](https://cogitator.app/docs/memory/hybrid-search), [Knowledge Graphs](https://cogitator.app/docs/memory/knowledge-graphs).
|
|
19
|
+
|
|
18
20
|
## Features
|
|
19
21
|
|
|
20
22
|
- **Multiple Adapters** - In-memory, Redis (standalone + cluster), PostgreSQL with pgvector, SQLite, MongoDB, Qdrant
|
|
@@ -24,10 +26,10 @@ pnpm add @qdrant/js-client-rest # For Qdrant embedding adapter
|
|
|
24
26
|
- **Context Builder** - Token-aware context with `recent`, `relevant` (embedding-ranked) and `hybrid` strategies
|
|
25
27
|
- **Scoped Semantic Memory** - Embeddings tagged with `metadata.agentId` / `metadata.threadId` never leak to other agents
|
|
26
28
|
- **Sessions & Compaction** - `SessionManager` for channel sessions, `CompactionService` for summarizing long histories
|
|
27
|
-
- **Embedding Services** - OpenAI and
|
|
29
|
+
- **Embedding Services** - OpenAI, Ollama and Google embedding integration
|
|
28
30
|
- **Semantic Search** - Vector similarity search with pgvector
|
|
29
31
|
- **Hybrid Search** - BM25 + Vector with Reciprocal Rank Fusion
|
|
30
|
-
- **Facts Storage** - Store and retrieve agent knowledge
|
|
32
|
+
- **Facts Storage** - Store and retrieve agent knowledge (`FactAdapter` on PostgreSQL, `CoreFactsStore` on SQLite)
|
|
31
33
|
- **Knowledge Graph** - Entity-relationship memory with multi-hop traversal
|
|
32
34
|
- **Zod Schemas** - Type-safe configuration validation
|
|
33
35
|
|
|
@@ -46,11 +48,13 @@ const thread = unwrap(await memory.createThread('agent-1', { topic: 'greeting' }
|
|
|
46
48
|
await memory.addEntry({
|
|
47
49
|
threadId: thread.id,
|
|
48
50
|
message: { role: 'user', content: 'Hello!' },
|
|
51
|
+
tokenCount: 2,
|
|
49
52
|
});
|
|
50
53
|
|
|
51
54
|
await memory.addEntry({
|
|
52
55
|
threadId: thread.id,
|
|
53
56
|
message: { role: 'assistant', content: 'Hi there!' },
|
|
57
|
+
tokenCount: 3,
|
|
54
58
|
});
|
|
55
59
|
|
|
56
60
|
const builder = new ContextBuilder(
|
|
@@ -67,7 +71,16 @@ const context = await builder.build({
|
|
|
67
71
|
console.log(context.messages);
|
|
68
72
|
```
|
|
69
73
|
|
|
70
|
-
Adapter calls return a `MemoryResult` — `{ success: true, data }` or `{ success: false, error }` — instead of throwing. `unwrap(result)` returns the data or throws an `Error` with the adapter's message; check `result.success` yourself where a failure is expected
|
|
74
|
+
Adapter calls return a `MemoryResult` — `{ success: true, data }` or `{ success: false, error }` — instead of throwing. `unwrap(result)` returns the data or throws an `Error` with the adapter's message; check `result.success` yourself where a failure is expected:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
const result = await memory.getThread('thread_123');
|
|
78
|
+
if (!result.success) {
|
|
79
|
+
console.error('Memory unavailable:', result.error);
|
|
80
|
+
} else if (result.data) {
|
|
81
|
+
console.log(result.data.metadata);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
71
84
|
|
|
72
85
|
---
|
|
73
86
|
|
|
@@ -124,6 +137,8 @@ const memory = new PostgresAdapter({
|
|
|
124
137
|
await memory.connect();
|
|
125
138
|
```
|
|
126
139
|
|
|
140
|
+
On `connect()` the adapter creates its tables and an HNSW cosine index on the embeddings, which works from the first row on (no training data needed). An `ivfflat` index left by an earlier version is replaced on the first connect after upgrading, see the [adapters docs](https://cogitator.app/docs/memory/adapters) for building the new index ahead of time on a large table. `search()` raises `hnsw.ef_search` to the requested limit and, on pgvector 0.8+, uses iterative scans, so large limits and filters still get every matching row. Messages, tool calls, tool results and metadata are stored as `jsonb`.
|
|
141
|
+
|
|
127
142
|
### SQLite Adapter
|
|
128
143
|
|
|
129
144
|
```typescript
|
|
@@ -131,9 +146,11 @@ import { SQLiteAdapter } from '@cogitator-ai/memory';
|
|
|
131
146
|
|
|
132
147
|
const memory = new SQLiteAdapter({
|
|
133
148
|
provider: 'sqlite',
|
|
134
|
-
path: './cogitator.db',
|
|
149
|
+
path: './cogitator.db', // ':memory:' for an in-memory database
|
|
135
150
|
walMode: true,
|
|
136
151
|
});
|
|
152
|
+
|
|
153
|
+
await memory.connect();
|
|
137
154
|
```
|
|
138
155
|
|
|
139
156
|
### MongoDB Adapter
|
|
@@ -146,8 +163,12 @@ const memory = new MongoDBAdapter({
|
|
|
146
163
|
uri: 'mongodb://localhost:27017',
|
|
147
164
|
database: 'cogitator',
|
|
148
165
|
});
|
|
166
|
+
|
|
167
|
+
await memory.connect();
|
|
149
168
|
```
|
|
150
169
|
|
|
170
|
+
Threads and entries live in the `<collectionPrefix>threads` and `<collectionPrefix>entries` collections (prefix `memory_` by default), indexed on `agentId` and `(threadId, createdAt)`. Fields left `undefined` are omitted from the stored documents, not stored as `null`. A failed `connect()` leaves the adapter disconnected, so a later `connect()` retries.
|
|
171
|
+
|
|
151
172
|
### Qdrant Adapter (Embedding)
|
|
152
173
|
|
|
153
174
|
```typescript
|
|
@@ -159,8 +180,12 @@ const embeddings = new QdrantAdapter({
|
|
|
159
180
|
collection: 'cogitator',
|
|
160
181
|
dimensions: 1536,
|
|
161
182
|
});
|
|
183
|
+
|
|
184
|
+
await embeddings.connect();
|
|
162
185
|
```
|
|
163
186
|
|
|
187
|
+
`QdrantAdapter` is an `EmbeddingAdapter` (store and search vectors), not a thread store; pair it with one of the adapters above. Points are stored under a deterministic UUID derived from the embedding id (Qdrant accepts only integer and UUID point ids); the `emb_…` id stays in `payload.embeddingId` and is what `search` returns.
|
|
188
|
+
|
|
164
189
|
### Factory Function
|
|
165
190
|
|
|
166
191
|
```typescript
|
|
@@ -187,8 +212,13 @@ const qdrant = await createEmbeddingAdapter({
|
|
|
187
212
|
provider: 'qdrant',
|
|
188
213
|
dimensions: 1536,
|
|
189
214
|
});
|
|
215
|
+
|
|
216
|
+
await postgres.connect(); // the factories create adapters; connect them before use
|
|
217
|
+
await qdrant.connect(); // typed as ConnectableEmbeddingAdapter, no cast needed
|
|
190
218
|
```
|
|
191
219
|
|
|
220
|
+
`createMemoryAdapter` loads the adapter's driver lazily, so only the drivers you use need to be installed.
|
|
221
|
+
|
|
192
222
|
---
|
|
193
223
|
|
|
194
224
|
## MemoryAdapter Interface
|
|
@@ -220,13 +250,14 @@ interface MemoryAdapter {
|
|
|
220
250
|
`createThread(agentId, metadata, threadId)` with an existing `threadId` is an upsert: entries and `createdAt` are kept and only the metadata is updated, so it is safe to call on every run.
|
|
221
251
|
|
|
222
252
|
```typescript
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
253
|
+
const thread = unwrap(
|
|
254
|
+
await memory.createThread('agent-1', {
|
|
255
|
+
topic: 'support',
|
|
256
|
+
user: 'user-123',
|
|
257
|
+
})
|
|
258
|
+
);
|
|
228
259
|
|
|
229
|
-
const
|
|
260
|
+
const found = unwrap(await memory.getThread(thread.id)); // Thread | null
|
|
230
261
|
|
|
231
262
|
await memory.updateThread(thread.id, {
|
|
232
263
|
resolved: true,
|
|
@@ -238,21 +269,26 @@ await memory.deleteThread(thread.id);
|
|
|
238
269
|
### Entry Operations
|
|
239
270
|
|
|
240
271
|
```typescript
|
|
241
|
-
const entry =
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
272
|
+
const entry = unwrap(
|
|
273
|
+
await memory.addEntry({
|
|
274
|
+
threadId: thread.id,
|
|
275
|
+
message: { role: 'user', content: 'Hello' },
|
|
276
|
+
tokenCount: 10,
|
|
277
|
+
})
|
|
278
|
+
);
|
|
246
279
|
|
|
247
|
-
const entries =
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
280
|
+
const entries = unwrap(
|
|
281
|
+
await memory.getEntries({
|
|
282
|
+
threadId: thread.id,
|
|
283
|
+
limit: 50, // the most recent 50, oldest first
|
|
284
|
+
includeToolCalls: true,
|
|
285
|
+
// before / after: Date bounds
|
|
286
|
+
})
|
|
287
|
+
);
|
|
252
288
|
|
|
253
|
-
const single = await memory.getEntry(entry.
|
|
289
|
+
const single = unwrap(await memory.getEntry(entry.id));
|
|
254
290
|
|
|
255
|
-
await memory.deleteEntry(entry.
|
|
291
|
+
await memory.deleteEntry(entry.id);
|
|
256
292
|
|
|
257
293
|
await memory.clearThread(thread.id);
|
|
258
294
|
```
|
|
@@ -273,12 +309,14 @@ interface ContextBuilderConfig {
|
|
|
273
309
|
includeSystemPrompt?: boolean;
|
|
274
310
|
includeFacts?: boolean;
|
|
275
311
|
includeSemanticContext?: boolean;
|
|
312
|
+
includeGraphContext?: boolean;
|
|
313
|
+
graphContextOptions?: GraphContextOptions;
|
|
276
314
|
}
|
|
277
315
|
```
|
|
278
316
|
|
|
279
317
|
Strategies:
|
|
280
318
|
|
|
281
|
-
- `recent` - newest messages that fit into the token budget
|
|
319
|
+
- `recent` - the newest messages that fit into the token budget, as one unbroken run: the window stops at the first message that does not fit, so a reply never appears without the message it answers
|
|
282
320
|
- `relevant` - messages ranked by embedding similarity to `currentInput` (requires `embeddingService`; falls back to `recent` without input), returned in chronological order
|
|
283
321
|
- `hybrid` - always keeps the latest messages and fills the remaining budget with the most relevant older ones
|
|
284
322
|
|
|
@@ -346,6 +384,7 @@ interface BuiltContext {
|
|
|
346
384
|
messages: Message[];
|
|
347
385
|
facts: Fact[];
|
|
348
386
|
semanticResults: (Embedding & { score: number })[];
|
|
387
|
+
graphContext?: GraphContext; // with includeGraphContext and a graph adapter
|
|
349
388
|
tokenCount: number;
|
|
350
389
|
truncated: boolean;
|
|
351
390
|
metadata: {
|
|
@@ -364,12 +403,26 @@ interface BuiltContext {
|
|
|
364
403
|
`SessionManager` maps channel conversations (user + channel + agent) onto memory threads. `CompactionService` replaces old history with an LLM summary placed before the most recent messages.
|
|
365
404
|
|
|
366
405
|
```typescript
|
|
406
|
+
import { createLLMBackend } from '@cogitator-ai/core';
|
|
367
407
|
import { InMemoryAdapter, SessionManager, CompactionService } from '@cogitator-ai/memory';
|
|
368
408
|
|
|
369
409
|
const memory = new InMemoryAdapter();
|
|
410
|
+
const llm = createLLMBackend('google', {
|
|
411
|
+
providers: { google: { apiKey: process.env.GOOGLE_API_KEY! } },
|
|
412
|
+
});
|
|
413
|
+
|
|
370
414
|
const compaction = new CompactionService({
|
|
371
415
|
adapter: memory,
|
|
372
|
-
summarize: async (messages
|
|
416
|
+
summarize: async (messages, { model, prompt } = {}) => {
|
|
417
|
+
const response = await llm.chat({
|
|
418
|
+
model: model ?? 'gemini-3.5-flash-lite',
|
|
419
|
+
messages: [
|
|
420
|
+
{ role: 'system', content: prompt ?? 'Summarize this conversation in a few sentences.' },
|
|
421
|
+
...messages,
|
|
422
|
+
],
|
|
423
|
+
});
|
|
424
|
+
return response.content;
|
|
425
|
+
},
|
|
373
426
|
});
|
|
374
427
|
const sessions = new SessionManager(memory, { compaction });
|
|
375
428
|
|
|
@@ -383,10 +436,36 @@ const session = await sessions.getOrCreate({
|
|
|
383
436
|
const all = await sessions.list();
|
|
384
437
|
const mine = await sessions.list({ userId: 'user-1', status: 'active', limit: 20 });
|
|
385
438
|
|
|
386
|
-
await sessions.compact(session.id, {
|
|
439
|
+
await sessions.compact(session.id, {
|
|
440
|
+
strategy: 'summary',
|
|
441
|
+
threshold: 8000,
|
|
442
|
+
keepRecent: 10,
|
|
443
|
+
summaryModel: 'gemini-3.5-flash',
|
|
444
|
+
summaryPrompt: 'Keep names, decisions and open questions.',
|
|
445
|
+
});
|
|
387
446
|
```
|
|
388
447
|
|
|
389
|
-
`list()` works with or without a `userId` filter (sessions are tracked in an index thread). `compact()` requires the `compaction` option; without it use `CompactionService` directly.
|
|
448
|
+
`list()` works with or without a `userId` filter (sessions are tracked in an index thread). `compact()` requires the `compaction` option; without it use `CompactionService` directly. The summarizer (`SummarizeFn`) is called as `summarize(messages, options)`, where `options` is a `SummarizeOptions` `{ model?, prompt? }` filled from the `summaryModel` / `summaryPrompt` of the compaction config.
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## Core Facts
|
|
453
|
+
|
|
454
|
+
`CoreFactsStore` keeps a small set of key/value facts about the user in SQLite (with history), formatted for a system prompt:
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
import { CoreFactsStore } from '@cogitator-ai/memory';
|
|
458
|
+
|
|
459
|
+
const facts = new CoreFactsStore({ path: './facts.db' }); // or { db } to share a better-sqlite3 database
|
|
460
|
+
await facts.initialize();
|
|
461
|
+
|
|
462
|
+
await facts.set('name', 'Alice');
|
|
463
|
+
await facts.get('name'); // 'Alice'
|
|
464
|
+
await facts.getAll(); // { name: 'Alice' }
|
|
465
|
+
await facts.getHistory('name'); // [{ value, setAt }]
|
|
466
|
+
const prompt = await facts.formatForPrompt();
|
|
467
|
+
await facts.close();
|
|
468
|
+
```
|
|
390
469
|
|
|
391
470
|
---
|
|
392
471
|
|
|
@@ -425,10 +504,12 @@ import { OpenAIEmbeddingService, createEmbeddingService } from '@cogitator-ai/me
|
|
|
425
504
|
|
|
426
505
|
const embeddings = new OpenAIEmbeddingService({
|
|
427
506
|
apiKey: process.env.OPENAI_API_KEY!,
|
|
428
|
-
model: 'text-embedding-3-small',
|
|
507
|
+
model: 'text-embedding-3-small', // default
|
|
508
|
+
dimensions: 512, // optional, sent to text-embedding-3 models (gateway ids like openai/text-embedding-3-small too)
|
|
429
509
|
});
|
|
430
510
|
|
|
431
|
-
|
|
511
|
+
// or through the factory
|
|
512
|
+
const viaFactory = createEmbeddingService({
|
|
432
513
|
provider: 'openai',
|
|
433
514
|
apiKey: process.env.OPENAI_API_KEY!,
|
|
434
515
|
});
|
|
@@ -438,6 +519,8 @@ const vector = await embeddings.embed('Hello, world!');
|
|
|
438
519
|
const vectors = await embeddings.embedBatch(['Hello', 'World']);
|
|
439
520
|
```
|
|
440
521
|
|
|
522
|
+
For other models `dimensions` declares the size they return. When a response holds vectors of another size than the configured `dimensions`, `embed()` and `embedBatch()` throw instead of handing them to a store sized for it.
|
|
523
|
+
|
|
441
524
|
### Ollama Embeddings
|
|
442
525
|
|
|
443
526
|
```typescript
|
|
@@ -448,7 +531,7 @@ const embeddings = new OllamaEmbeddingService({
|
|
|
448
531
|
baseUrl: 'http://localhost:11434',
|
|
449
532
|
});
|
|
450
533
|
|
|
451
|
-
const
|
|
534
|
+
const viaFactory = createEmbeddingService({
|
|
452
535
|
provider: 'ollama',
|
|
453
536
|
model: 'nomic-embed-text',
|
|
454
537
|
});
|
|
@@ -467,7 +550,7 @@ const embeddings = new GoogleEmbeddingService({
|
|
|
467
550
|
dimensions: 768,
|
|
468
551
|
});
|
|
469
552
|
|
|
470
|
-
const
|
|
553
|
+
const viaFactory = createEmbeddingService({
|
|
471
554
|
provider: 'google',
|
|
472
555
|
apiKey: process.env.GOOGLE_API_KEY!,
|
|
473
556
|
});
|
|
@@ -530,15 +613,15 @@ const hybridResults = await search.search({
|
|
|
530
613
|
});
|
|
531
614
|
|
|
532
615
|
// Results include both scores
|
|
533
|
-
|
|
616
|
+
for (const result of unwrap(hybridResults)) {
|
|
534
617
|
console.log(`${result.content} — score: ${result.score}`);
|
|
535
618
|
console.log(` vector: ${result.vectorScore}, keyword: ${result.keywordScore}`);
|
|
536
|
-
}
|
|
619
|
+
}
|
|
537
620
|
```
|
|
538
621
|
|
|
539
622
|
### Document Indexing
|
|
540
623
|
|
|
541
|
-
|
|
624
|
+
With a `keywordAdapter`, keyword search runs on that adapter's own index (embeddings added to `InMemoryEmbeddingAdapter` or `PostgresAdapter` are searchable right away). Without one, `HybridSearch` keeps a local BM25 index that you fill yourself (it ignores `filter`):
|
|
542
625
|
|
|
543
626
|
```typescript
|
|
544
627
|
// Add documents to BM25 index
|
|
@@ -653,12 +736,12 @@ import type {
|
|
|
653
736
|
### Conversation History
|
|
654
737
|
|
|
655
738
|
```typescript
|
|
656
|
-
import { InMemoryAdapter } from '@cogitator-ai/memory';
|
|
739
|
+
import { InMemoryAdapter, countMessageTokens, unwrap } from '@cogitator-ai/memory';
|
|
657
740
|
|
|
658
741
|
const memory = new InMemoryAdapter();
|
|
659
742
|
await memory.connect();
|
|
660
743
|
|
|
661
|
-
const thread = await memory.createThread('chatbot');
|
|
744
|
+
const thread = unwrap(await memory.createThread('chatbot'));
|
|
662
745
|
|
|
663
746
|
const messages = [
|
|
664
747
|
{ role: 'user' as const, content: 'What is AI?' },
|
|
@@ -668,15 +751,18 @@ const messages = [
|
|
|
668
751
|
|
|
669
752
|
for (const msg of messages) {
|
|
670
753
|
await memory.addEntry({
|
|
671
|
-
threadId: thread.
|
|
754
|
+
threadId: thread.id,
|
|
672
755
|
message: msg,
|
|
756
|
+
tokenCount: countMessageTokens(msg),
|
|
673
757
|
});
|
|
674
758
|
}
|
|
675
759
|
|
|
676
|
-
const history =
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
760
|
+
const history = unwrap(
|
|
761
|
+
await memory.getEntries({
|
|
762
|
+
threadId: thread.id,
|
|
763
|
+
limit: 10,
|
|
764
|
+
})
|
|
765
|
+
);
|
|
680
766
|
```
|
|
681
767
|
|
|
682
768
|
### Token-Limited Context
|
|
@@ -708,7 +794,7 @@ console.log(
|
|
|
708
794
|
### Semantic Memory with PostgreSQL
|
|
709
795
|
|
|
710
796
|
```typescript
|
|
711
|
-
import { PostgresAdapter, OpenAIEmbeddingService } from '@cogitator-ai/memory';
|
|
797
|
+
import { PostgresAdapter, OpenAIEmbeddingService, unwrap } from '@cogitator-ai/memory';
|
|
712
798
|
|
|
713
799
|
const memory = new PostgresAdapter({
|
|
714
800
|
provider: 'postgres',
|
|
@@ -722,7 +808,22 @@ const embeddings = new OpenAIEmbeddingService({
|
|
|
722
808
|
await memory.connect();
|
|
723
809
|
|
|
724
810
|
const content = 'Machine learning is a subset of AI...';
|
|
725
|
-
|
|
811
|
+
await memory.addEmbedding({
|
|
812
|
+
sourceId: 'doc-1',
|
|
813
|
+
sourceType: 'document',
|
|
814
|
+
content,
|
|
815
|
+
vector: await embeddings.embed(content),
|
|
816
|
+
metadata: { agentId: 'agent-1' },
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
const matches = unwrap(
|
|
820
|
+
await memory.search({
|
|
821
|
+
vector: await embeddings.embed('What is ML?'),
|
|
822
|
+
limit: 5,
|
|
823
|
+
threshold: 0.7,
|
|
824
|
+
filter: { agentId: 'agent-1' },
|
|
825
|
+
})
|
|
826
|
+
);
|
|
726
827
|
```
|
|
727
828
|
|
|
728
829
|
---
|
|
@@ -733,32 +834,39 @@ Entity-relationship memory with multi-hop traversal and semantic reasoning.
|
|
|
733
834
|
|
|
734
835
|
### PostgresGraphAdapter
|
|
735
836
|
|
|
736
|
-
|
|
737
|
-
import { PostgresGraphAdapter } from '@cogitator-ai/memory';
|
|
837
|
+
Graph adapters return `MemoryResult`s like the memory adapters. `PostgresGraphAdapter` takes an existing `pg` pool and creates its tables on first use; `SQLiteGraphAdapter` takes `{ path, walMode? }`.
|
|
738
838
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
839
|
+
```typescript
|
|
840
|
+
import { PostgresGraphAdapter, unwrap } from '@cogitator-ai/memory';
|
|
841
|
+
import pg from 'pg';
|
|
742
842
|
|
|
743
|
-
|
|
843
|
+
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
|
|
844
|
+
const graph = new PostgresGraphAdapter({ pool, vectorDimensions: 1536 }); // default 768
|
|
744
845
|
|
|
745
|
-
const person =
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
846
|
+
const person = unwrap(
|
|
847
|
+
await graph.addNode({
|
|
848
|
+
agentId: 'agent-1',
|
|
849
|
+
type: 'person',
|
|
850
|
+
name: 'Alice',
|
|
851
|
+
aliases: [],
|
|
852
|
+
description: 'Software engineer',
|
|
853
|
+
properties: { role: 'developer' },
|
|
854
|
+
confidence: 1.0,
|
|
855
|
+
source: 'user',
|
|
856
|
+
})
|
|
857
|
+
);
|
|
754
858
|
|
|
755
|
-
const company =
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
}
|
|
859
|
+
const company = unwrap(
|
|
860
|
+
await graph.addNode({
|
|
861
|
+
agentId: 'agent-1',
|
|
862
|
+
type: 'organization',
|
|
863
|
+
name: 'TechCorp',
|
|
864
|
+
aliases: [],
|
|
865
|
+
properties: {},
|
|
866
|
+
confidence: 1.0,
|
|
867
|
+
source: 'extracted',
|
|
868
|
+
})
|
|
869
|
+
);
|
|
762
870
|
|
|
763
871
|
await graph.addEdge({
|
|
764
872
|
agentId: 'agent-1',
|
|
@@ -766,6 +874,8 @@ await graph.addEdge({
|
|
|
766
874
|
targetNodeId: company.id,
|
|
767
875
|
type: 'works_at',
|
|
768
876
|
weight: 1.0,
|
|
877
|
+
bidirectional: false,
|
|
878
|
+
properties: {},
|
|
769
879
|
confidence: 0.95,
|
|
770
880
|
source: 'extracted',
|
|
771
881
|
});
|
|
@@ -774,12 +884,15 @@ await graph.addEdge({
|
|
|
774
884
|
### Multi-hop Traversal
|
|
775
885
|
|
|
776
886
|
```typescript
|
|
777
|
-
const result =
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
887
|
+
const result = unwrap(
|
|
888
|
+
await graph.traverse({
|
|
889
|
+
agentId: 'agent-1',
|
|
890
|
+
startNodeId: person.id,
|
|
891
|
+
maxDepth: 3,
|
|
892
|
+
direction: 'outgoing', // 'outgoing' | 'incoming' | 'both'
|
|
893
|
+
edgeTypes: ['works_at', 'knows', 'located_in'],
|
|
894
|
+
})
|
|
895
|
+
);
|
|
783
896
|
|
|
784
897
|
console.log('Visited nodes:', result.visitedNodes);
|
|
785
898
|
console.log('Paths found:', result.paths);
|
|
@@ -788,7 +901,7 @@ console.log('Paths found:', result.paths);
|
|
|
788
901
|
### Shortest Path
|
|
789
902
|
|
|
790
903
|
```typescript
|
|
791
|
-
const path = await graph.findShortestPath(
|
|
904
|
+
const path = unwrap(await graph.findShortestPath('agent-1', person.id, company.id, 5));
|
|
792
905
|
if (path) {
|
|
793
906
|
console.log('Path:', path.nodes.map((n) => n.name).join(' -> '));
|
|
794
907
|
console.log('Total weight:', path.totalWeight);
|
|
@@ -798,12 +911,14 @@ if (path) {
|
|
|
798
911
|
### Semantic Node Search
|
|
799
912
|
|
|
800
913
|
```typescript
|
|
801
|
-
const similar =
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
914
|
+
const similar = unwrap(
|
|
915
|
+
await graph.searchNodesSemantic({
|
|
916
|
+
agentId: 'agent-1',
|
|
917
|
+
vector: await embeddingService.embed('machine learning engineer'), // a vector is required
|
|
918
|
+
limit: 10,
|
|
919
|
+
threshold: 0.7,
|
|
920
|
+
})
|
|
921
|
+
); // nodes with a `score`
|
|
807
922
|
```
|
|
808
923
|
|
|
809
924
|
### LLM Entity Extraction
|
|
@@ -811,32 +926,39 @@ const similar = await graph.searchNodesSemantic({
|
|
|
811
926
|
```typescript
|
|
812
927
|
import { LLMEntityExtractor } from '@cogitator-ai/memory';
|
|
813
928
|
|
|
814
|
-
const
|
|
929
|
+
const backend = cog.getLLMBackend('openai/gpt-5.5');
|
|
930
|
+
|
|
931
|
+
const extractor = new LLMEntityExtractor(backend, {
|
|
932
|
+
model: 'gpt-5.5', // required with an LLMBackend
|
|
815
933
|
minConfidence: 0.7,
|
|
816
934
|
maxEntitiesPerText: 20,
|
|
935
|
+
maxRelationsPerText: 30,
|
|
817
936
|
});
|
|
818
937
|
|
|
819
938
|
const result = await extractor.extract(
|
|
820
|
-
'Alice works at TechCorp in San Francisco. She knows Bob from the AI conference.'
|
|
939
|
+
'Alice works at TechCorp in San Francisco. She knows Bob from the AI conference.',
|
|
940
|
+
{ agentId: 'agent-1' }
|
|
821
941
|
);
|
|
822
942
|
|
|
823
943
|
console.log('Entities:', result.entities);
|
|
824
944
|
console.log('Relations:', result.relations);
|
|
825
945
|
```
|
|
826
946
|
|
|
947
|
+
Any object with a `chat({ model?, messages, responseFormat })` method resolving to `{ content }` (`LLMBackendMinimal`) also works as the first argument; there `model` is optional and, when set, is passed through in each request.
|
|
948
|
+
|
|
827
949
|
### Graph Inference Engine
|
|
828
950
|
|
|
829
951
|
```typescript
|
|
830
952
|
import { GraphInferenceEngine } from '@cogitator-ai/memory';
|
|
831
953
|
|
|
832
|
-
const engine = new GraphInferenceEngine(graph
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
954
|
+
const engine = new GraphInferenceEngine(graph); // built-in rules; pass false to start without them
|
|
955
|
+
|
|
956
|
+
const inferred = await engine.infer('agent-1', { minConfidence: 0.5, maxInferences: 100 });
|
|
957
|
+
for (const edge of inferred) {
|
|
958
|
+
console.log(edge.type, edge.sourceNodeId, '->', edge.targetNodeId, 'via rule', edge.ruleId);
|
|
959
|
+
}
|
|
836
960
|
|
|
837
|
-
|
|
838
|
-
console.log('Inferred edges:', inferred.edges);
|
|
839
|
-
console.log('Rules applied:', inferred.rulesApplied);
|
|
961
|
+
await engine.materialize(inferred); // store them as edges
|
|
840
962
|
```
|
|
841
963
|
|
|
842
964
|
### Graph Context Builder
|
|
@@ -850,13 +972,14 @@ const contextBuilder = new GraphContextBuilder(graph, embeddingService, {
|
|
|
850
972
|
includeInferred: true,
|
|
851
973
|
});
|
|
852
974
|
|
|
853
|
-
const context = await contextBuilder.buildContext({
|
|
854
|
-
|
|
855
|
-
|
|
975
|
+
const context = await contextBuilder.buildContext('agent-1', 'Tell me about Alice and her work', {
|
|
976
|
+
maxDepth: 2,
|
|
977
|
+
userId: 'user-1', // leave out other users' nodes (metadata.userId)
|
|
856
978
|
});
|
|
857
979
|
|
|
858
980
|
console.log('Relevant nodes:', context.nodes);
|
|
859
981
|
console.log('Relationships:', context.edges);
|
|
982
|
+
console.log(context.formattedContext); // ready for a system prompt
|
|
860
983
|
```
|
|
861
984
|
|
|
862
985
|
---
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Memory adapter exports and factory
|
|
3
3
|
*/
|
|
4
|
-
import type { MemoryAdapter, EmbeddingAdapter, InMemoryAdapterConfig, RedisAdapterConfig, PostgresAdapterConfig, SQLiteAdapterConfig, MongoDBAdapterConfig, QdrantAdapterConfig } from '@cogitator-ai/types';
|
|
4
|
+
import type { MemoryAdapter, MemoryResult, EmbeddingAdapter, InMemoryAdapterConfig, RedisAdapterConfig, PostgresAdapterConfig, SQLiteAdapterConfig, MongoDBAdapterConfig, QdrantAdapterConfig } from '@cogitator-ai/types';
|
|
5
5
|
export { BaseMemoryAdapter } from './base.js';
|
|
6
6
|
export { InMemoryAdapter } from './memory.js';
|
|
7
7
|
export type MemoryAdapterConfigUnion = InMemoryAdapterConfig | RedisAdapterConfig | PostgresAdapterConfig | SQLiteAdapterConfig | MongoDBAdapterConfig;
|
|
8
8
|
export type EmbeddingAdapterConfigUnion = QdrantAdapterConfig;
|
|
9
|
+
/**
|
|
10
|
+
* An embedding store that talks to a server, as `createEmbeddingAdapter` returns it: call
|
|
11
|
+
* `connect()` before the first operation and `disconnect()` when done.
|
|
12
|
+
*/
|
|
13
|
+
export interface ConnectableEmbeddingAdapter extends EmbeddingAdapter {
|
|
14
|
+
connect(): Promise<MemoryResult<void>>;
|
|
15
|
+
disconnect(): Promise<MemoryResult<void>>;
|
|
16
|
+
}
|
|
9
17
|
export declare function createMemoryAdapter(config: MemoryAdapterConfigUnion): Promise<MemoryAdapter>;
|
|
10
|
-
export declare function createEmbeddingAdapter(config: EmbeddingAdapterConfigUnion): Promise<
|
|
18
|
+
export declare function createEmbeddingAdapter(config: EmbeddingAdapterConfigUnion): Promise<ConnectableEmbeddingAdapter>;
|
|
11
19
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C,MAAM,MAAM,wBAAwB,GAChC,qBAAqB,GACrB,kBAAkB,GAClB,qBAAqB,GACrB,mBAAmB,GACnB,oBAAoB,CAAC;AAEzB,MAAM,MAAM,2BAA2B,GAAG,mBAAmB,CAAC;AAE9D,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,aAAa,CAAC,CAgCxB;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C,MAAM,MAAM,wBAAwB,GAChC,qBAAqB,GACrB,kBAAkB,GAClB,qBAAqB,GACrB,mBAAmB,GACnB,oBAAoB,CAAC;AAEzB,MAAM,MAAM,2BAA2B,GAAG,mBAAmB,CAAC;AAE9D;;;GAGG;AACH,MAAM,WAAW,2BAA4B,SAAQ,gBAAgB;IACnE,OAAO,IAAI,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IACvC,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;CAC3C;AAED,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,aAAa,CAAC,CAgCxB;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,2BAA2B,CAAC,CAOtC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAaH,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAoB3C,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAgC;IAEhC,QAAQ,MAAM,CAAC,QAAQ,EAAE,CAAC;QACxB,KAAK,QAAQ;YACX,OAAO,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC;QAErC,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;YACjD,OAAO,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;QAClC,CAAC;QAED,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;YACvD,OAAO,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC;QACrC,CAAC;QAED,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;YACnD,OAAO,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC;QAED,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;YACrD,OAAO,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC;QACpC,CAAC;QAED,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,UAAU,GAAU,MAAM,CAAC;YACjC,MAAM,IAAI,KAAK,CACb,4BAA6B,UAAuC,CAAC,QAAQ,EAAE,CAChF,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,MAAmC;IAEnC,QAAQ,MAAM,CAAC,QAAQ,EAAE,CAAC;QACxB,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;YACnD,OAAO,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -4,13 +4,19 @@ export declare class MongoDBAdapter extends BaseMemoryAdapter {
|
|
|
4
4
|
readonly provider: MemoryProvider;
|
|
5
5
|
private client;
|
|
6
6
|
private db;
|
|
7
|
+
private connecting;
|
|
7
8
|
private uri;
|
|
8
9
|
private database;
|
|
9
10
|
private prefix;
|
|
10
11
|
constructor(config: MongoDBAdapterConfig);
|
|
11
12
|
private get threads();
|
|
12
13
|
private get entries();
|
|
14
|
+
/**
|
|
15
|
+
* Opens the client and builds the indexes. Concurrent calls share one attempt, and a failed
|
|
16
|
+
* attempt closes its client and leaves the adapter disconnected, so the next call tries again.
|
|
17
|
+
*/
|
|
13
18
|
connect(): Promise<MemoryResult<void>>;
|
|
19
|
+
private open;
|
|
14
20
|
disconnect(): Promise<MemoryResult<void>>;
|
|
15
21
|
createThread(agentId: string, metadata?: Record<string, unknown>, threadId?: string): Promise<MemoryResult<Thread>>;
|
|
16
22
|
getThread(threadId: string): Promise<MemoryResult<Thread | null>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mongodb.d.ts","sourceRoot":"","sources":["../../src/adapters/mongodb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EACN,WAAW,EACX,kBAAkB,EAClB,YAAY,
|
|
1
|
+
{"version":3,"file":"mongodb.d.ts","sourceRoot":"","sources":["../../src/adapters/mongodb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EACN,WAAW,EACX,kBAAkB,EAClB,YAAY,EAEZ,oBAAoB,EACpB,cAAc,EAGf,MAAM,qBAAqB,CAAC;AAS7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAyF3C,qBAAa,cAAe,SAAQ,iBAAiB;IACnD,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAa;IAE9C,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,EAAE,CAA8B;IACxC,OAAO,CAAC,UAAU,CAA4C;IAC9D,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,oBAAoB;IAOxC,OAAO,KAAK,OAAO,GAGlB;IAED,OAAO,KAAK,OAAO,GAGlB;IAED;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;YAQ9B,IAAI;IA2BZ,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAgBzC,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACtC,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IAyB1B,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAmBjE,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IAwB1B,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAY3D,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;IA0B1F,UAAU,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC,CAAC;IAyB7E,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAWpE,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAWzD,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;CAUjE"}
|