tools: add memory_query for structured graph queries
Exposes the full query language as a tool: filtering, sorting, field
selection, neighbor walks. Examples:
degree > 10 | sort weight | limit 5
neighbors('identity') | select strength
key ~ 'journal.*' | count
Also added query_to_string() in the parser so queries return strings
instead of printing to stdout. Updated memory-instructions-core to
list all current tools (added memory_query and journal, removed
CLI commands section and nonexistent memory_search_content).
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
9a09a665fb
commit
a865285313
2 changed files with 57 additions and 0 deletions
|
|
@ -38,6 +38,11 @@ pub fn definitions() -> Vec<ToolDef> {
|
||||||
ToolDef::new("memory_supersede",
|
ToolDef::new("memory_supersede",
|
||||||
"Mark a node as superseded by another (sets weight to 0.01).",
|
"Mark a node as superseded by another (sets weight to 0.01).",
|
||||||
json!({"type":"object","properties":{"old_key":{"type":"string"},"new_key":{"type":"string"},"reason":{"type":"string"}},"required":["old_key","new_key"]})),
|
json!({"type":"object","properties":{"old_key":{"type":"string"},"new_key":{"type":"string"},"reason":{"type":"string"}},"required":["old_key","new_key"]})),
|
||||||
|
ToolDef::new("memory_query",
|
||||||
|
"Run a structured query against the memory graph. Supports filtering, \
|
||||||
|
sorting, field selection. Examples: \"degree > 10 | sort weight | limit 5\", \
|
||||||
|
\"neighbors('identity') | select strength\", \"key ~ 'journal.*' | count\"",
|
||||||
|
json!({"type":"object","properties":{"query":{"type":"string","description":"Query expression"}},"required":["query"]})),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,6 +107,13 @@ pub fn dispatch(name: &str, args: &serde_json::Value, provenance: Option<&str>)
|
||||||
store.save().map_err(|e| anyhow::anyhow!("{}", e))?;
|
store.save().map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||||
Ok(format!("superseded {} → {} ({})", old_key, new_key, reason))
|
Ok(format!("superseded {} → {} ({})", old_key, new_key, reason))
|
||||||
}
|
}
|
||||||
|
"memory_query" => {
|
||||||
|
let query = get_str(args, "query")?;
|
||||||
|
let store = Store::load().map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||||
|
let graph = store.build_graph();
|
||||||
|
crate::query_parser::query_to_string(&store, &graph, query)
|
||||||
|
.map_err(|e| anyhow::anyhow!("{}", e))
|
||||||
|
}
|
||||||
_ => anyhow::bail!("Unknown memory tool: {}", name),
|
_ => anyhow::bail!("Unknown memory tool: {}", name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -520,6 +520,51 @@ pub fn run_query(store: &Store, graph: &Graph, query_str: &str) -> Result<(), St
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run a query and return the output as a string (for tool calls).
|
||||||
|
pub fn query_to_string(store: &Store, graph: &Graph, query_str: &str) -> Result<String, String> {
|
||||||
|
let q = query_parser::query(query_str)
|
||||||
|
.map_err(|e| format!("Parse error: {}", e))?;
|
||||||
|
|
||||||
|
let results = execute_parsed(store, graph, &q)?;
|
||||||
|
|
||||||
|
if q.stages.iter().any(|s| matches!(s, Stage::Count)) {
|
||||||
|
return Ok(results.len().to_string());
|
||||||
|
}
|
||||||
|
if results.is_empty() {
|
||||||
|
return Ok("no results".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let fields: Option<&Vec<String>> = q.stages.iter().find_map(|s| match s {
|
||||||
|
Stage::Select(f) => Some(f),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut out = String::new();
|
||||||
|
if let Some(fields) = fields {
|
||||||
|
let mut header = vec!["key".to_string()];
|
||||||
|
header.extend(fields.iter().cloned());
|
||||||
|
out.push_str(&header.join("\t"));
|
||||||
|
out.push('\n');
|
||||||
|
for r in &results {
|
||||||
|
let mut row = vec![r.key.clone()];
|
||||||
|
for f in fields {
|
||||||
|
row.push(match r.fields.get(f) {
|
||||||
|
Some(v) => format_value(v),
|
||||||
|
None => "-".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
out.push_str(&row.join("\t"));
|
||||||
|
out.push('\n');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for r in &results {
|
||||||
|
out.push_str(&r.key);
|
||||||
|
out.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
// -- Connectivity analysis --
|
// -- Connectivity analysis --
|
||||||
|
|
||||||
/// BFS shortest path between two nodes, max_hops limit.
|
/// BFS shortest path between two nodes, max_hops limit.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue