fix(memory-ruvector): register ruvector_learn tool and add link CLI
Documentation validation fixes: - Register ruvector_learn tool in local mode (was defined but not registered) - Add 'clawdbot ruvector link' CLI command (was documented but missing) - Remove non-existent graph config section from docs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a801c7e721
commit
ac20e30817
@ -269,39 +269,11 @@ clawdbot ruvector sona-stats
|
||||
|
||||
Query message relationships using Neo4j-compatible Cypher syntax. This enables finding conversation threads, reply chains, and topic relationships.
|
||||
|
||||
### Configuration
|
||||
|
||||
```json5
|
||||
{
|
||||
plugins: {
|
||||
entries: {
|
||||
"memory-ruvector": {
|
||||
enabled: true,
|
||||
config: {
|
||||
embedding: {
|
||||
provider: "openai",
|
||||
apiKey: "${OPENAI_API_KEY}"
|
||||
},
|
||||
graph: {
|
||||
enabled: true, // Enable graph features
|
||||
autoLink: true, // Auto-create edges for replies/threads
|
||||
maxDepth: 5 // Maximum traversal depth
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Graph features are automatically available when the ruvector library is built with graph extension support. No additional configuration is needed.
|
||||
|
||||
### Linking messages
|
||||
|
||||
**Automatic linking** (when `autoLink: true`):
|
||||
- Messages in the same conversation are linked with `IN_CONVERSATION`
|
||||
- Reply messages are linked with `REPLIED_BY`
|
||||
- Messages from the same user are linked with `FROM_USER`
|
||||
|
||||
**Manual linking** via the `ruvector_graph` tool:
|
||||
**Manual linking** via the `ruvector_graph` tool or CLI:
|
||||
|
||||
```json5
|
||||
{
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
createRuvectorFeedbackTool,
|
||||
createRuvectorGraphTool,
|
||||
createRuvectorRecallTool,
|
||||
createRuvectorLearnTool,
|
||||
} from "./tool.js";
|
||||
import { ruvectorConfigSchema, type RuvectorConfig } from "./config.js";
|
||||
import { createDatabase } from "./db.js";
|
||||
@ -682,6 +683,123 @@ function registerLocalMode(api: ClawdbotPluginApi, config: RuvectorConfig): void
|
||||
{ name: "ruvector_recall", optional: true },
|
||||
);
|
||||
|
||||
// ruvector_learn tool (manual knowledge injection)
|
||||
api.registerTool(
|
||||
{
|
||||
name: "ruvector_learn",
|
||||
label: "Manual Knowledge Learning",
|
||||
description:
|
||||
"Explicitly learn and index new knowledge with optional graph relationships. " +
|
||||
"Use this to inject important facts, decisions, or preferences into the memory system.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
content: { type: "string", description: "The content/knowledge to learn" },
|
||||
category: {
|
||||
type: "string",
|
||||
enum: ["preference", "fact", "decision", "entity", "other"],
|
||||
description: "Category for the knowledge (default: fact)",
|
||||
},
|
||||
importance: {
|
||||
type: "number",
|
||||
description: "Importance score from 0-1 (default: 0.5)",
|
||||
},
|
||||
relationships: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description: "Related document IDs to link to",
|
||||
},
|
||||
},
|
||||
required: ["content"],
|
||||
},
|
||||
async execute(_toolCallId, params, ctx) {
|
||||
const {
|
||||
content,
|
||||
category = "fact",
|
||||
importance = 0.5,
|
||||
relationships = [],
|
||||
} = params as {
|
||||
content: string;
|
||||
category?: string;
|
||||
importance?: number;
|
||||
relationships?: string[];
|
||||
};
|
||||
|
||||
try {
|
||||
const vector = await embeddings.embed(content);
|
||||
|
||||
// Check for duplicates
|
||||
const existing = await db.search(vector, { limit: 1, minScore: 0.95 });
|
||||
if (existing.length > 0) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Similar knowledge already exists: "${existing[0].document.content.slice(0, 100)}..."`,
|
||||
},
|
||||
],
|
||||
details: { indexed: false, duplicate: true, existingId: existing[0].document.id },
|
||||
};
|
||||
}
|
||||
|
||||
const validCategories = ["preference", "fact", "decision", "entity", "other"];
|
||||
const validCategory = validCategories.includes(category) ? category : "fact";
|
||||
const clampedImportance = Math.max(0, Math.min(1, importance));
|
||||
|
||||
const id = await db.insert({
|
||||
content,
|
||||
vector,
|
||||
direction: "outbound",
|
||||
channel: "ruvector_learn",
|
||||
sessionKey: ctx?.sessionKey,
|
||||
agentId: ctx?.agentId,
|
||||
timestamp: Date.now(),
|
||||
category: validCategory,
|
||||
importance: clampedImportance,
|
||||
});
|
||||
|
||||
// Create graph edges if relationships provided
|
||||
let edgesCreated = 0;
|
||||
const hasGraphSupport =
|
||||
"linkMessages" in db &&
|
||||
typeof (db as Record<string, unknown>).linkMessages === "function";
|
||||
|
||||
if (hasGraphSupport && relationships.length > 0) {
|
||||
const graphDb = db as typeof db & {
|
||||
linkMessages: (source: string, target: string, rel: string) => Promise<boolean>;
|
||||
};
|
||||
for (const targetId of relationships) {
|
||||
try {
|
||||
const created = await graphDb.linkMessages(id, targetId, "RELATED_TO");
|
||||
if (created) edgesCreated++;
|
||||
} catch {
|
||||
// Skip failed edges
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Learned: "${content.slice(0, 100)}${content.length > 100 ? "..." : ""}" [${validCategory}] with ${edgesCreated} relationship(s)`,
|
||||
},
|
||||
],
|
||||
details: { indexed: true, id, category: validCategory, importance: clampedImportance, edges: edgesCreated },
|
||||
};
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
api.logger.warn(`ruvector_learn: learning failed: ${message}`);
|
||||
return {
|
||||
content: [{ type: "text", text: `Learning failed: ${message}` }],
|
||||
details: { indexed: false, error: message },
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
{ name: "ruvector_learn", optional: true },
|
||||
);
|
||||
|
||||
// =========================================================================
|
||||
// Register CLI Commands
|
||||
// =========================================================================
|
||||
@ -824,6 +942,38 @@ function registerLocalMode(api: ClawdbotPluginApi, config: RuvectorConfig): void
|
||||
}
|
||||
});
|
||||
|
||||
// GNN link command
|
||||
rv.command("link")
|
||||
.description("Create a relationship between two messages")
|
||||
.argument("<sourceId>", "Source message ID")
|
||||
.argument("<targetId>", "Target message ID")
|
||||
.option("--relationship <rel>", "Relationship type", "RELATES_TO")
|
||||
.action(async (sourceId, targetId, opts) => {
|
||||
const hasGraphSupport = "linkMessages" in db && typeof (db as Record<string, unknown>).linkMessages === "function";
|
||||
|
||||
if (!hasGraphSupport) {
|
||||
console.log("GNN graph features not available.");
|
||||
console.log("Requires ruvector with graph extension enabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
const relationship = opts.relationship || "RELATES_TO";
|
||||
const graphDb = db as typeof db & { linkMessages: (source: string, target: string, rel: string) => Promise<boolean> };
|
||||
|
||||
try {
|
||||
const created = await graphDb.linkMessages(sourceId, targetId, relationship);
|
||||
if (created) {
|
||||
console.log(`Created link: ${sourceId} -[${relationship}]-> ${targetId}`);
|
||||
} else {
|
||||
console.log(`Link already exists or could not be created.`);
|
||||
}
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
console.error(`Failed to create link: ${message}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
});
|
||||
|
||||
// Pattern export command (P3 Advanced Features)
|
||||
rv.command("export-patterns")
|
||||
.description("Export learned patterns to a JSON file")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user