cozo-memory 1.0.7 → 1.0.8
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 +38 -10
- package/dist/eval-suite.js +136 -0
- package/dist/hybrid-search.js +99 -0
- package/dist/index.js +267 -43
- package/dist/inference-engine.js +7 -0
- package/dist/make-old.js +58 -0
- package/dist/test-agentic-retrieval.js +28 -0
- package/dist/test-discovery.js +53 -0
- package/dist/test-graphrag.js +35 -0
- package/dist/verify-agentic-logic.js +64 -0
- package/package.json +2 -1
- package/dist/test-mcp-search.js +0 -47
- package/dist/tui-blessed.js +0 -789
- package/dist/tui.py +0 -481
package/dist/index.js
CHANGED
|
@@ -361,6 +361,124 @@ class MemoryServer {
|
|
|
361
361
|
}
|
|
362
362
|
return result.rows.map((r) => ({ entity_id: String(r[0]), community_id: String(r[1]) }));
|
|
363
363
|
}
|
|
364
|
+
async summarizeCommunities(args) {
|
|
365
|
+
await this.initPromise;
|
|
366
|
+
const minSize = Math.max(2, Math.floor(args?.min_community_size ?? 3));
|
|
367
|
+
const model = args?.model ?? "demyagent-4b-i1:Q6_K";
|
|
368
|
+
console.error("[GraphRAG] Recomputing communities before summarization...");
|
|
369
|
+
const commResult = await this.recomputeCommunities();
|
|
370
|
+
if (commResult.length === 0) {
|
|
371
|
+
return { status: "no_data", generated_summaries: 0 };
|
|
372
|
+
}
|
|
373
|
+
// Group entities by community
|
|
374
|
+
const byCommunity = new Map();
|
|
375
|
+
for (const row of commResult) {
|
|
376
|
+
const arr = byCommunity.get(row.community_id) ?? [];
|
|
377
|
+
arr.push(row.entity_id);
|
|
378
|
+
byCommunity.set(row.community_id, arr);
|
|
379
|
+
}
|
|
380
|
+
let generatedSummaries = 0;
|
|
381
|
+
const results = [];
|
|
382
|
+
for (const [communityId, entityIds] of byCommunity.entries()) {
|
|
383
|
+
if (entityIds.length < minSize)
|
|
384
|
+
continue;
|
|
385
|
+
console.error(`[GraphRAG] Summarizing community ${communityId} with ${entityIds.length} members...`);
|
|
386
|
+
// Fetch details for all entities in this community
|
|
387
|
+
const entityInfos = [];
|
|
388
|
+
for (const eId of entityIds) {
|
|
389
|
+
try {
|
|
390
|
+
// get entity
|
|
391
|
+
const entRes = await this.db.run('?[name, type] := *entity{id: $id, name, type, @ "NOW"}', { id: eId });
|
|
392
|
+
if (entRes.rows.length > 0) {
|
|
393
|
+
const name = entRes.rows[0][0];
|
|
394
|
+
const type = entRes.rows[0][1];
|
|
395
|
+
// get top observations for this entity
|
|
396
|
+
const obsRes = await this.db.run('?[text, created_at] := *observation{entity_id: $id, text, created_at, @ "NOW"} :limit 3', { id: eId });
|
|
397
|
+
const obs = obsRes.rows.map((r) => r[0]);
|
|
398
|
+
entityInfos.push(`- ${name} (${type})` + (obs.length > 0 ? `\n Notes: ${obs.join("; ")}` : ""));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch (e) {
|
|
402
|
+
console.warn(`[GraphRAG] Error fetching info for ${eId}: ${e.message}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (entityInfos.length === 0)
|
|
406
|
+
continue;
|
|
407
|
+
const systemPrompt = "You are an expert analyst. Summarize the following community of related entities into a single, cohesive 'Community Report'. Identify the overarching themes, topics, and connections between these entities. Respond ONLY with the finalized summary text.";
|
|
408
|
+
const userPrompt = `Community Size: ${entityIds.length}\n\nMembers & Notes:\n${entityInfos.join("\n")}`;
|
|
409
|
+
let summaryText = "";
|
|
410
|
+
try {
|
|
411
|
+
const ollamaMod = await import("ollama");
|
|
412
|
+
const ollamaClient = ollamaMod?.default ?? ollamaMod;
|
|
413
|
+
const response = await ollamaClient.chat({
|
|
414
|
+
model,
|
|
415
|
+
messages: [
|
|
416
|
+
{ role: "system", content: systemPrompt },
|
|
417
|
+
{ role: "user", content: userPrompt },
|
|
418
|
+
],
|
|
419
|
+
});
|
|
420
|
+
summaryText = response?.message?.content?.trim?.() ?? "";
|
|
421
|
+
}
|
|
422
|
+
catch (e) {
|
|
423
|
+
console.warn(`[GraphRAG] Ollama error for community ${communityId}: ${e.message}`);
|
|
424
|
+
}
|
|
425
|
+
if (!summaryText || summaryText === "") {
|
|
426
|
+
console.warn(`[GraphRAG] Could not generate summary for community ${communityId}, skipping.`);
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
// Create new CommunitySummary entity
|
|
430
|
+
const nowIso = new Date().toISOString();
|
|
431
|
+
const summaryName = `Community Summary ${communityId.slice(0, 8)} (${nowIso.slice(0, 10)})`;
|
|
432
|
+
const sumEntity = await this.createEntity({
|
|
433
|
+
name: summaryName,
|
|
434
|
+
type: "CommunitySummary",
|
|
435
|
+
metadata: {
|
|
436
|
+
graphrag: {
|
|
437
|
+
kind: "community_summary",
|
|
438
|
+
community_id: communityId,
|
|
439
|
+
member_count: entityIds.length,
|
|
440
|
+
member_ids: entityIds,
|
|
441
|
+
model,
|
|
442
|
+
summarized_at: nowIso
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
const summaryEntityId = sumEntity?.id;
|
|
447
|
+
if (summaryEntityId) {
|
|
448
|
+
await this.addObservation({
|
|
449
|
+
entity_id: summaryEntityId,
|
|
450
|
+
text: summaryText,
|
|
451
|
+
metadata: {
|
|
452
|
+
graphrag: {
|
|
453
|
+
kind: "community_summary",
|
|
454
|
+
community_id: communityId,
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
// Link the summary to all members
|
|
459
|
+
for (const memberId of entityIds) {
|
|
460
|
+
await this.createRelation({
|
|
461
|
+
from_id: summaryEntityId,
|
|
462
|
+
to_id: memberId,
|
|
463
|
+
relation_type: "summary_of",
|
|
464
|
+
strength: 1.0,
|
|
465
|
+
metadata: { community_id: communityId }
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
generatedSummaries++;
|
|
469
|
+
results.push({
|
|
470
|
+
community_id: communityId,
|
|
471
|
+
summary_entity_id: summaryEntityId,
|
|
472
|
+
member_count: entityIds.length
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
status: "completed",
|
|
478
|
+
generated_summaries: generatedSummaries,
|
|
479
|
+
results
|
|
480
|
+
};
|
|
481
|
+
}
|
|
364
482
|
async recomputeBetweennessCentrality() {
|
|
365
483
|
await this.initPromise;
|
|
366
484
|
const edgeCheckRes = await this.db.run(`?[from_id] := *relationship{from_id, @ "NOW"} :limit 1`);
|
|
@@ -1482,6 +1600,7 @@ ids[id] <- $ids
|
|
|
1482
1600
|
async reflectMemory(args) {
|
|
1483
1601
|
await this.initPromise;
|
|
1484
1602
|
const model = args.model ?? "demyagent-4b-i1:Q6_K";
|
|
1603
|
+
const mode = args.mode ?? "summary";
|
|
1485
1604
|
const targetEntityId = args.entity_id;
|
|
1486
1605
|
let entitiesToReflect = [];
|
|
1487
1606
|
if (targetEntityId) {
|
|
@@ -1504,47 +1623,114 @@ ids[id] <- $ids
|
|
|
1504
1623
|
}
|
|
1505
1624
|
const results = [];
|
|
1506
1625
|
for (const entity of entitiesToReflect) {
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1626
|
+
if (mode === "summary") {
|
|
1627
|
+
const obsRes = await this.db.run('?[text, ts] := *observation{entity_id: $id, text, created_at, @ "NOW"}, ts = to_int(created_at) :order ts', {
|
|
1628
|
+
id: entity.id,
|
|
1629
|
+
});
|
|
1630
|
+
if (obsRes.rows.length < 2) {
|
|
1631
|
+
results.push({ entity_id: entity.id, status: "skipped", reason: "Too few observations for reflection" });
|
|
1632
|
+
continue;
|
|
1633
|
+
}
|
|
1634
|
+
const observations = obsRes.rows.map((r) => `- [${new Date(Number(r[1]) / 1000).toISOString()}] ${r[0]}`);
|
|
1635
|
+
const systemPrompt = `You are an analytical memory module. Analyze the following observations about an entity.
|
|
1516
1636
|
Look for contradictions, temporal developments, behavioral patterns, or deeper insights.
|
|
1517
1637
|
Formulate a concise reflection (max. 3-4 sentences) that helps the user understand the current state or evolution.
|
|
1518
1638
|
If there are contradictory statements, name them explicitly.
|
|
1519
1639
|
If no special patterns are recognizable, answer with "No new insights".`;
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1640
|
+
const userPrompt = `Entity: ${entity.name} (${entity.type})\n\nObservations:\n${observations.join("\n")}`;
|
|
1641
|
+
let reflectionText;
|
|
1642
|
+
try {
|
|
1643
|
+
const ollamaMod = await import("ollama");
|
|
1644
|
+
const ollamaClient = ollamaMod?.default ?? ollamaMod;
|
|
1645
|
+
const response = await ollamaClient.chat({
|
|
1646
|
+
model,
|
|
1647
|
+
messages: [
|
|
1648
|
+
{ role: "system", content: systemPrompt },
|
|
1649
|
+
{ role: "user", content: userPrompt },
|
|
1650
|
+
],
|
|
1651
|
+
});
|
|
1652
|
+
reflectionText = response?.message?.content?.trim?.() ?? "";
|
|
1653
|
+
}
|
|
1654
|
+
catch (e) {
|
|
1655
|
+
console.error(`[Reflect] Ollama error for ${entity.name}:`, e);
|
|
1656
|
+
reflectionText = "";
|
|
1657
|
+
}
|
|
1658
|
+
if (reflectionText && reflectionText !== "No new insights" && !reflectionText.includes("No new insights")) {
|
|
1659
|
+
await this.addObservation({
|
|
1660
|
+
entity_id: entity.id,
|
|
1661
|
+
text: `Reflexive insight: ${reflectionText}`,
|
|
1662
|
+
metadata: { kind: "reflection", model, generated_at: Date.now() },
|
|
1663
|
+
});
|
|
1664
|
+
results.push({ entity_id: entity.id, status: "reflected", insight: reflectionText });
|
|
1665
|
+
}
|
|
1666
|
+
else {
|
|
1667
|
+
results.push({ entity_id: entity.id, status: "no_insight_found" });
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
else if (mode === "discovery") {
|
|
1671
|
+
// Discovery Mode: Find and validate new relationships
|
|
1672
|
+
const candidates = await this.inferenceEngine.getRefinementCandidates(entity.id);
|
|
1673
|
+
const discoveryResults = [];
|
|
1674
|
+
const suggestions = [];
|
|
1675
|
+
for (const candidate of candidates) {
|
|
1676
|
+
// Check if relationship already exists
|
|
1677
|
+
const existing = await this.db.run(`
|
|
1678
|
+
?[count(from_id)] := *relationship{from_id, to_id, relation_type, @ "NOW"},
|
|
1679
|
+
from_id = $from, to_id = $to, relation_type = $rel
|
|
1680
|
+
`, { from: candidate.from_id, to: candidate.to_id, rel: candidate.relation_type });
|
|
1681
|
+
if (Number(existing.rows[0][0]) > 0)
|
|
1682
|
+
continue;
|
|
1683
|
+
// Load target entity details for context
|
|
1684
|
+
const targetRes = await this.db.run('?[name, type] := *entity{id, name, type, @ "NOW"}, id = $id', { id: candidate.to_id });
|
|
1685
|
+
const targetName = String(targetRes.rows[0][0]);
|
|
1686
|
+
const targetType = String(targetRes.rows[0][1]);
|
|
1687
|
+
const validatePrompt = `You are a Knowledge Graph specialized AI. Evaluate if the following suggested relationship is logically sound based on the reason provided.
|
|
1688
|
+
Source Entity: ${entity.name} (${entity.type})
|
|
1689
|
+
Target Entity: ${targetName} (${targetType})
|
|
1690
|
+
Suggested Relationship: ${candidate.relation_type}
|
|
1691
|
+
Reason: ${candidate.reason}
|
|
1692
|
+
|
|
1693
|
+
Respond with a JSON object:
|
|
1694
|
+
{
|
|
1695
|
+
"confidence": <0.0 to 1.0>,
|
|
1696
|
+
"reasoning": "<short explanation>",
|
|
1697
|
+
"verdict": "create" | "suggest" | "reject"
|
|
1698
|
+
}
|
|
1699
|
+
Assign "create" if confidence > 0.8, "suggest" if confidence > 0.5, else "reject".`;
|
|
1700
|
+
try {
|
|
1701
|
+
const ollamaMod = await import("ollama");
|
|
1702
|
+
const ollamaClient = ollamaMod?.default ?? ollamaMod;
|
|
1703
|
+
const response = await ollamaClient.chat({
|
|
1704
|
+
model,
|
|
1705
|
+
messages: [{ role: "user", content: validatePrompt }],
|
|
1706
|
+
format: "json"
|
|
1707
|
+
});
|
|
1708
|
+
const validation = JSON.parse(response?.message?.content ?? "{}");
|
|
1709
|
+
if (validation.verdict === "create" && validation.confidence > 0.8) {
|
|
1710
|
+
await this.createRelation({
|
|
1711
|
+
from_id: candidate.from_id,
|
|
1712
|
+
to_id: candidate.to_id,
|
|
1713
|
+
relation_type: candidate.relation_type,
|
|
1714
|
+
strength: validation.confidence,
|
|
1715
|
+
metadata: { source: "reflection", reasoning: validation.reasoning, model }
|
|
1716
|
+
});
|
|
1717
|
+
discoveryResults.push({ target: targetName, type: candidate.relation_type, status: "created" });
|
|
1718
|
+
}
|
|
1719
|
+
else if (validation.verdict === "suggest" || validation.confidence > 0.5) {
|
|
1720
|
+
suggestions.push({
|
|
1721
|
+
from_id: candidate.from_id,
|
|
1722
|
+
to_id: candidate.to_id,
|
|
1723
|
+
relation_type: candidate.relation_type,
|
|
1724
|
+
confidence: validation.confidence,
|
|
1725
|
+
reason: validation.reasoning
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
catch (e) {
|
|
1730
|
+
console.error(`[Discovery] Validation failed for ${targetName}:`, e);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
results.push({ entity_id: entity.id, status: "discovery_completed", created: discoveryResults, suggestions });
|
|
1548
1734
|
}
|
|
1549
1735
|
}
|
|
1550
1736
|
return { status: "completed", results };
|
|
@@ -2501,12 +2687,17 @@ Validation: Invalid syntax or missing columns in inference rules will result in
|
|
|
2501
2687
|
max_depth: zod_1.z.number().min(1).max(5).optional().default(3).describe("Maximum walking depth"),
|
|
2502
2688
|
limit: zod_1.z.number().optional().default(5).describe("Number of results"),
|
|
2503
2689
|
}),
|
|
2690
|
+
zod_1.z.object({
|
|
2691
|
+
action: zod_1.z.literal("agentic_search"),
|
|
2692
|
+
query: zod_1.z.string().describe("Context query for agentic routing"),
|
|
2693
|
+
limit: zod_1.z.number().optional().default(10).describe("Maximum number of results"),
|
|
2694
|
+
}),
|
|
2504
2695
|
]);
|
|
2505
2696
|
const QueryMemoryParameters = zod_1.z.object({
|
|
2506
2697
|
action: zod_1.z
|
|
2507
|
-
.enum(["search", "advancedSearch", "context", "entity_details", "history", "graph_rag", "graph_walking"])
|
|
2698
|
+
.enum(["search", "advancedSearch", "context", "entity_details", "history", "graph_rag", "graph_walking", "agentic_search"])
|
|
2508
2699
|
.describe("Action (determines which fields are required)"),
|
|
2509
|
-
query: zod_1.z.string().optional().describe("Required for search/advancedSearch/context/graph_rag/graph_walking"),
|
|
2700
|
+
query: zod_1.z.string().optional().describe("Required for search/advancedSearch/context/graph_rag/graph_walking/agentic_search"),
|
|
2510
2701
|
limit: zod_1.z.number().optional().describe("Only for search/advancedSearch/graph_rag/graph_walking"),
|
|
2511
2702
|
filters: zod_1.z.any().optional().describe("Only for advancedSearch"),
|
|
2512
2703
|
graphConstraints: zod_1.z.any().optional().describe("Only for advancedSearch"),
|
|
@@ -2534,8 +2725,9 @@ Supported actions:
|
|
|
2534
2725
|
- 'history': Retrieve historical evolution of an entity. Params: { entity_id: string }.
|
|
2535
2726
|
- 'graph_rag': Graph-based reasoning (Hybrid RAG). Finds semantic vector seeds first, then expands via graph traversals. Ideal for multi-hop reasoning. Params: { query: string, max_depth?: number, limit?: number }.
|
|
2536
2727
|
- 'graph_walking': Recursive semantic graph search. Starts at vector seeds or an entity and follows relationships to other semantically relevant entities. Params: { query: string, start_entity_id?: string, max_depth?: number, limit?: number }.
|
|
2728
|
+
- 'agentic_search': Auto-Routing Search. Uses local LLM to analyze intent and routes the query automatically to the best strategy (Vector, Graph, or Community Summaries). Params: { query: string, limit?: number }.
|
|
2537
2729
|
|
|
2538
|
-
Notes: 'context' is ideal for exploratory questions. 'search' and 'advancedSearch' are better for targeted fact retrieval.`,
|
|
2730
|
+
Notes: 'agentic_search' is the most powerful and adaptable, 'context' is ideal for exploratory questions. 'search' and 'advancedSearch' are better for targeted fact retrieval.`,
|
|
2539
2731
|
parameters: QueryMemoryParameters,
|
|
2540
2732
|
execute: async (args) => {
|
|
2541
2733
|
await this.initPromise;
|
|
@@ -2689,6 +2881,16 @@ Notes: 'context' is ideal for exploratory questions. 'search' and 'advancedSearc
|
|
|
2689
2881
|
};
|
|
2690
2882
|
return JSON.stringify(context);
|
|
2691
2883
|
}
|
|
2884
|
+
if (input.action === "agentic_search") {
|
|
2885
|
+
if (!input.query || input.query.trim().length === 0) {
|
|
2886
|
+
return JSON.stringify({ error: "Search query must not be empty." });
|
|
2887
|
+
}
|
|
2888
|
+
const results = await this.hybridSearch.agenticRetrieve({
|
|
2889
|
+
query: input.query,
|
|
2890
|
+
limit: input.limit,
|
|
2891
|
+
});
|
|
2892
|
+
return JSON.stringify(results);
|
|
2893
|
+
}
|
|
2692
2894
|
if (input.action === "graph_rag") {
|
|
2693
2895
|
if (!input.query || input.query.trim().length === 0) {
|
|
2694
2896
|
return JSON.stringify({ error: "Search query must not be empty." });
|
|
@@ -3165,15 +3367,21 @@ Supported actions:
|
|
|
3165
3367
|
action: zod_1.z.literal("reflect"),
|
|
3166
3368
|
entity_id: zod_1.z.string().optional().describe("Optional entity ID for targeted reflection"),
|
|
3167
3369
|
model: zod_1.z.string().optional().default("demyagent-4b-i1:Q6_K"),
|
|
3370
|
+
mode: zod_1.z.enum(["summary", "discovery"]).optional().default("summary").describe("Reflection mode: 'summary' for insights, 'discovery' for new links"),
|
|
3168
3371
|
}),
|
|
3169
3372
|
zod_1.z.object({
|
|
3170
3373
|
action: zod_1.z.literal("clear_memory"),
|
|
3171
3374
|
confirm: zod_1.z.boolean().describe("Must be true to confirm deletion"),
|
|
3172
3375
|
}),
|
|
3376
|
+
zod_1.z.object({
|
|
3377
|
+
action: zod_1.z.literal("summarize_communities"),
|
|
3378
|
+
model: zod_1.z.string().optional().default("demyagent-4b-i1:Q6_K"),
|
|
3379
|
+
min_community_size: zod_1.z.number().min(2).max(100).optional().default(3),
|
|
3380
|
+
}),
|
|
3173
3381
|
]);
|
|
3174
3382
|
const ManageSystemParameters = zod_1.z.object({
|
|
3175
3383
|
action: zod_1.z
|
|
3176
|
-
.enum(["health", "metrics", "export_memory", "import_memory", "snapshot_create", "snapshot_list", "snapshot_diff", "cleanup", "reflect", "clear_memory"])
|
|
3384
|
+
.enum(["health", "metrics", "export_memory", "import_memory", "snapshot_create", "snapshot_list", "snapshot_diff", "cleanup", "reflect", "clear_memory", "summarize_communities"])
|
|
3177
3385
|
.describe("Action (determines which fields are required)"),
|
|
3178
3386
|
format: zod_1.z.enum(["json", "markdown", "obsidian"]).optional().describe("Export format (for export_memory)"),
|
|
3179
3387
|
includeMetadata: zod_1.z.boolean().optional().describe("Include metadata (for export_memory)"),
|
|
@@ -3192,8 +3400,10 @@ Supported actions:
|
|
|
3192
3400
|
older_than_days: zod_1.z.number().optional().describe("Optional for cleanup"),
|
|
3193
3401
|
max_observations: zod_1.z.number().optional().describe("Optional for cleanup"),
|
|
3194
3402
|
min_entity_degree: zod_1.z.number().optional().describe("Optional for cleanup"),
|
|
3195
|
-
model: zod_1.z.string().optional().describe("Optional for cleanup/reflect"),
|
|
3403
|
+
model: zod_1.z.string().optional().describe("Optional for cleanup/reflect/summarize_communities"),
|
|
3196
3404
|
entity_id: zod_1.z.string().optional().describe("Optional for reflect"),
|
|
3405
|
+
min_community_size: zod_1.z.number().optional().describe("Optional for summarize_communities"),
|
|
3406
|
+
mode: zod_1.z.enum(["summary", "discovery"]).optional().describe("Optional for reflect"),
|
|
3197
3407
|
});
|
|
3198
3408
|
this.mcp.addTool({
|
|
3199
3409
|
name: "manage_system",
|
|
@@ -3217,7 +3427,8 @@ Supported actions:
|
|
|
3217
3427
|
* With confirm=false: Dry-Run (shows candidates).
|
|
3218
3428
|
* With confirm=true: Merges old/isolated fragments using LLM (Executive Summary) and removes noise.
|
|
3219
3429
|
- 'reflect': Reflection service. Analyzes memory for contradictions and insights. Params: { entity_id?: string, model?: string }.
|
|
3220
|
-
- 'clear_memory': Resets the entire database. Params: { confirm: boolean (must be true) }
|
|
3430
|
+
- 'clear_memory': Resets the entire database. Params: { confirm: boolean (must be true) }.
|
|
3431
|
+
- 'summarize_communities': Hierarchical GraphRAG. Generates summaries for entity clusters. Params: { model?: string, min_community_size?: number }.`,
|
|
3221
3432
|
parameters: ManageSystemParameters,
|
|
3222
3433
|
execute: async (args) => {
|
|
3223
3434
|
await this.initPromise;
|
|
@@ -3410,6 +3621,7 @@ Supported actions:
|
|
|
3410
3621
|
const result = await this.reflectMemory({
|
|
3411
3622
|
entity_id: input.entity_id,
|
|
3412
3623
|
model: input.model,
|
|
3624
|
+
mode: input.mode,
|
|
3413
3625
|
});
|
|
3414
3626
|
return JSON.stringify(result);
|
|
3415
3627
|
}
|
|
@@ -3417,6 +3629,18 @@ Supported actions:
|
|
|
3417
3629
|
return JSON.stringify({ error: error.message || "Error during reflection" });
|
|
3418
3630
|
}
|
|
3419
3631
|
}
|
|
3632
|
+
if (input.action === "summarize_communities") {
|
|
3633
|
+
try {
|
|
3634
|
+
const result = await this.summarizeCommunities({
|
|
3635
|
+
model: input.model,
|
|
3636
|
+
min_community_size: input.min_community_size,
|
|
3637
|
+
});
|
|
3638
|
+
return JSON.stringify(result);
|
|
3639
|
+
}
|
|
3640
|
+
catch (error) {
|
|
3641
|
+
return JSON.stringify({ error: error.message || "Error during summarize_communities" });
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3420
3644
|
if (input.action === "clear_memory") {
|
|
3421
3645
|
if (!input.confirm) {
|
|
3422
3646
|
return JSON.stringify({ error: "Deletion not confirmed. Set 'confirm' to true." });
|
package/dist/inference-engine.js
CHANGED
|
@@ -28,6 +28,13 @@ class InferenceEngine {
|
|
|
28
28
|
results.push(...custom);
|
|
29
29
|
return results;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Aggregates potential links from all strategies for the "discovery" mode of reflection.
|
|
33
|
+
*/
|
|
34
|
+
async getRefinementCandidates(entityId) {
|
|
35
|
+
// This is essentially the same as inferRelations but explicitly for refinement
|
|
36
|
+
return this.inferRelations(entityId);
|
|
37
|
+
}
|
|
31
38
|
async inferImplicitRelations(entityId) {
|
|
32
39
|
const expertise = await this.findTransitiveExpertise(entityId);
|
|
33
40
|
const custom = await this.applyCustomRules(entityId);
|
package/dist/make-old.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("./index");
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
async function run() {
|
|
6
|
+
const server = new index_1.MemoryServer();
|
|
7
|
+
await server.initPromise;
|
|
8
|
+
console.log("Directly inserting old entity and observations via CozoDB...");
|
|
9
|
+
const fortyDaysAgo = Math.floor(Date.now() - 40 * 24 * 60 * 60 * 1000) * 1000;
|
|
10
|
+
const entityId = (0, uuid_1.v4)();
|
|
11
|
+
const name = "Very Old Project";
|
|
12
|
+
const type = "Project";
|
|
13
|
+
const metadata = { purpose: "testing janitor" };
|
|
14
|
+
const zeroVec = new Array(1024).fill(0);
|
|
15
|
+
try {
|
|
16
|
+
await server.db.run(`
|
|
17
|
+
?[id, name, type, embedding, name_embedding, metadata, created_at] <- [[$id, $name, $type, $embedding, $name_embedding, $metadata, [$fortyDaysAgo, true]]]
|
|
18
|
+
:insert entity {id, name, type, embedding, name_embedding, metadata, created_at}
|
|
19
|
+
`, {
|
|
20
|
+
id: entityId,
|
|
21
|
+
name,
|
|
22
|
+
type,
|
|
23
|
+
embedding: zeroVec,
|
|
24
|
+
name_embedding: zeroVec,
|
|
25
|
+
metadata,
|
|
26
|
+
fortyDaysAgo
|
|
27
|
+
});
|
|
28
|
+
console.log("Old entity inserted: " + entityId);
|
|
29
|
+
const obsTexts = [
|
|
30
|
+
"This is a really old architecture note.",
|
|
31
|
+
"We decided to use subversion for version control.",
|
|
32
|
+
"The server is a physical machine in the basement.",
|
|
33
|
+
"We wrote our own ORM from scratch.",
|
|
34
|
+
"Deployment takes 3 days and a lot of manual steps."
|
|
35
|
+
];
|
|
36
|
+
for (const text of obsTexts) {
|
|
37
|
+
const obsId = (0, uuid_1.v4)();
|
|
38
|
+
await server.db.run(`
|
|
39
|
+
?[id, entity_id, text, embedding, metadata, created_at] <- [[$id, $entity_id, $text, $embedding, $metadata, [$fortyDaysAgo, true]]]
|
|
40
|
+
:insert observation {id, entity_id, text, embedding, metadata, created_at}
|
|
41
|
+
`, {
|
|
42
|
+
id: obsId,
|
|
43
|
+
entity_id: entityId,
|
|
44
|
+
text,
|
|
45
|
+
embedding: zeroVec,
|
|
46
|
+
metadata: {},
|
|
47
|
+
fortyDaysAgo
|
|
48
|
+
});
|
|
49
|
+
console.log("Old observation inserted: " + obsId);
|
|
50
|
+
}
|
|
51
|
+
console.log("Successfully inserted old data!");
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
console.error("DB error:", e.message);
|
|
55
|
+
}
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
run().catch(console.error);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("./index");
|
|
4
|
+
async function run() {
|
|
5
|
+
const server = new index_1.MemoryServer();
|
|
6
|
+
await server.initPromise;
|
|
7
|
+
console.log("Testing Agentic Retrieval Routing Logic...");
|
|
8
|
+
// Expose the protected hybridSearch for testing (TypeScript hack)
|
|
9
|
+
const hybridSearch = server.hybridSearch;
|
|
10
|
+
const queries = [
|
|
11
|
+
{ text: "Welches Datenbank-System nutzt das Backend Project B?", expected: ["vector_search", "hybrid"] },
|
|
12
|
+
{ text: "Wer arbeitet alles mit ReactJS oder was nutzt ReactJS?", expected: ["graph_walk", "hybrid"] },
|
|
13
|
+
{ text: "Wie ist der generelle Status aller Frontend-Projekte?", expected: ["community_summary"] }
|
|
14
|
+
];
|
|
15
|
+
for (const q of queries) {
|
|
16
|
+
console.log(`\n\n--- Query: "${q.text}" ---`);
|
|
17
|
+
console.log(`Expected Route: ${q.expected.join(" or ")}`);
|
|
18
|
+
const results = await hybridSearch.agenticRetrieve({ query: q.text, limit: 1 });
|
|
19
|
+
if (results.length > 0) {
|
|
20
|
+
console.log(`-> LLM Routed to: ${results[0].metadata?.agentic_routing}`);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.log(`\nNo results found. LLM might have routed to an empty strategy.`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
run().catch(console.error);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const index_1 = require("../src/index");
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
async function runTest() {
|
|
10
|
+
const testDbPath = path_1.default.resolve(__dirname, "discovery_test.cozo");
|
|
11
|
+
// Cleanup previous test DB
|
|
12
|
+
if (fs_1.default.existsSync(testDbPath + ".db"))
|
|
13
|
+
fs_1.default.unlinkSync(testDbPath + ".db");
|
|
14
|
+
const server = new index_1.MemoryServer(testDbPath);
|
|
15
|
+
await server.initPromise;
|
|
16
|
+
console.log("1. Creating test entities...");
|
|
17
|
+
const aliceRes = await server.createEntity({
|
|
18
|
+
name: "Alice",
|
|
19
|
+
type: "Person",
|
|
20
|
+
metadata: { role: "Developer" }
|
|
21
|
+
});
|
|
22
|
+
const aliceId = aliceRes.id;
|
|
23
|
+
const phoenixRes = await server.createEntity({
|
|
24
|
+
name: "Project Phoenix",
|
|
25
|
+
type: "Project",
|
|
26
|
+
metadata: { status: "active" }
|
|
27
|
+
});
|
|
28
|
+
const phoenixId = phoenixRes.id;
|
|
29
|
+
console.log("2. Adding overlapping observations...");
|
|
30
|
+
await server.addObservation({
|
|
31
|
+
entity_id: aliceId,
|
|
32
|
+
text: "Alice is currently focusing all her time on Project Phoenix."
|
|
33
|
+
});
|
|
34
|
+
console.log("3. Running reflection in 'discovery' mode...");
|
|
35
|
+
// We specify the model to ensure it uses one that supports JSON mode if possible
|
|
36
|
+
const reflectRes = await server.reflectMemory({
|
|
37
|
+
entity_id: aliceId,
|
|
38
|
+
mode: "discovery"
|
|
39
|
+
});
|
|
40
|
+
console.log("Result:", JSON.stringify(reflectRes, null, 2));
|
|
41
|
+
console.log("4. Verifying relationship creation...");
|
|
42
|
+
const relationsRes = await server.db.run(`
|
|
43
|
+
?[to_name, rel_type] := *relationship{from_id, to_id, relation_type: rel_type, @ "NOW"},
|
|
44
|
+
from_id = $alice,
|
|
45
|
+
*entity{id: to_id, name: to_name, @ "NOW"}
|
|
46
|
+
`, { alice: aliceId });
|
|
47
|
+
console.log("Relationships from Alice:", relationsRes.rows);
|
|
48
|
+
// Cleanup
|
|
49
|
+
if (fs_1.default.existsSync(testDbPath + ".db"))
|
|
50
|
+
fs_1.default.unlinkSync(testDbPath + ".db");
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
runTest().catch(console.error);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("./index");
|
|
4
|
+
async function run() {
|
|
5
|
+
const server = new index_1.MemoryServer();
|
|
6
|
+
await server.initPromise;
|
|
7
|
+
console.log("Setting up Test Clusters for GraphRAG Community Summaries...");
|
|
8
|
+
// Cluster 1: Frontend
|
|
9
|
+
const fe1 = await server.createEntity({ name: "ReactJS", type: "technology", metadata: {} });
|
|
10
|
+
const fe2 = await server.createEntity({ name: "Redux", type: "technology", metadata: {} });
|
|
11
|
+
const fe3 = await server.createEntity({ name: "Frontend Project A", type: "project", metadata: {} });
|
|
12
|
+
await server.addObservation({ entity_id: fe1.id, text: "ReactJS is used for building UIs." });
|
|
13
|
+
await server.addObservation({ entity_id: fe2.id, text: "Redux is used for state management in React." });
|
|
14
|
+
await server.addObservation({ entity_id: fe3.id, text: "Project A is a heavy frontend SPA using React and Redux." });
|
|
15
|
+
await server.createRelation({ from_id: fe3.id, to_id: fe1.id, relation_type: "uses", strength: 1.0 });
|
|
16
|
+
await server.createRelation({ from_id: fe3.id, to_id: fe2.id, relation_type: "uses", strength: 1.0 });
|
|
17
|
+
await server.createRelation({ from_id: fe1.id, to_id: fe2.id, relation_type: "integrates_with", strength: 1.0 });
|
|
18
|
+
console.log("Created Frontend Cluster");
|
|
19
|
+
// Cluster 2: Backend
|
|
20
|
+
const be1 = await server.createEntity({ name: "PostgreSQL", type: "database", metadata: {} });
|
|
21
|
+
const be2 = await server.createEntity({ name: "CozoDB", type: "database", metadata: {} });
|
|
22
|
+
const be3 = await server.createEntity({ name: "Backend Project B", type: "project", metadata: {} });
|
|
23
|
+
await server.addObservation({ entity_id: be1.id, text: "PostgreSQL is a robust relational database." });
|
|
24
|
+
await server.addObservation({ entity_id: be2.id, text: "CozoDB is a graph database with Datalog." });
|
|
25
|
+
await server.addObservation({ entity_id: be3.id, text: "Project B relies heavily on complex queries across PG and CozoDB." });
|
|
26
|
+
await server.createRelation({ from_id: be3.id, to_id: be1.id, relation_type: "uses", strength: 1.0 });
|
|
27
|
+
await server.createRelation({ from_id: be3.id, to_id: be2.id, relation_type: "uses", strength: 1.0 });
|
|
28
|
+
await server.createRelation({ from_id: be1.id, to_id: be2.id, relation_type: "migrating_to", strength: 1.0 });
|
|
29
|
+
console.log("Created Backend Cluster");
|
|
30
|
+
console.log("Initiating Community Summarization...");
|
|
31
|
+
const result = await server.summarizeCommunities({ min_community_size: 3 });
|
|
32
|
+
console.log("Community Summarization Result:", JSON.stringify(result, null, 2));
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
run().catch(console.error);
|