feat(embedding,vision): candle embeddings with offline fallback, on-demand clipboard vision capture, and concurrency audit
This commit is contained in:
1 parent
5bd8b1587a
commit
e4a0fe72df
47 files changed
+6292
-3503
No files matched your search
@@ -38,21 +38,7 @@ impl<'a> GraphQueryBuilder<'a> {
|
||||
}
|
||||
|
||||
pub fn find_shortest_path(&self, start: &str, end: &str) -> Option<Vec<String>> {
|
||||
let mut adj: std::collections::HashMap<&str, Vec<(&str, &str, bool)>> =
|
||||
std::collections::HashMap::with_capacity(self.graph.relations.len() * 2);
|
||||
for rel in &self.graph.relations {
|
||||
if let Some(rf) = self.relation_filter {
|
||||
if rel.relation_type != rf {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
adj.entry(rel.from.as_str())
|
||||
.or_default()
|
||||
.push((rel.to.as_str(), rel.relation_type.as_str(), false));
|
||||
adj.entry(rel.to.as_str())
|
||||
.or_default()
|
||||
.push((rel.from.as_str(), rel.relation_type.as_str(), true));
|
||||
}
|
||||
let adj = self.graph.build_adjacency_map(self.relation_filter);
|
||||
|
||||
let mut queue = std::collections::VecDeque::new();
|
||||
let mut visited = std::collections::HashSet::new();
|
||||
@@ -253,6 +239,7 @@ impl McpTool for CreateRelationsHandler {
|
||||
observations: vec!["Auto-created stub entity for relation endpoint".to_string()],
|
||||
namespace: crate::models::default_namespace(),
|
||||
git_branch: None,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
auto_created.push(node_name);
|
||||
@@ -328,7 +315,10 @@ impl McpTool for DeleteEntitiesHandler {
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<DeleteEntitiesTool>("delete_entities", "Execute delete_entities")
|
||||
crate::mcp::tool_def::<DeleteEntitiesTool>(
|
||||
"delete_entities",
|
||||
"Delete specified entities and all their associated relations and observations from the knowledge graph.",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
@@ -378,7 +368,7 @@ impl McpTool for DeleteObservationsHandler {
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<DeleteObservationsTool>(
|
||||
"delete_observations",
|
||||
"Execute delete_observations",
|
||||
"Delete specific observations from existing entities in the knowledge graph without removing the entity itself.",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -422,7 +412,10 @@ impl McpTool for DeleteRelationsHandler {
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<DeleteRelationsTool>("delete_relations", "Execute delete_relations")
|
||||
crate::mcp::tool_def::<DeleteRelationsTool>(
|
||||
"delete_relations",
|
||||
"Delete specific relationships between entities in the knowledge graph by specifying source 'from', target 'to', and 'relation_type'.",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
@@ -462,8 +455,45 @@ impl McpTool for ReadGraphHandler {
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: ReadGraphTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let max_tokens = req.max_tokens;
|
||||
let limit = req.limit;
|
||||
let offset = req.offset.unwrap_or(0);
|
||||
|
||||
let data = state.read_graph(|full| -> crate::error::Result<String> {
|
||||
let mut result_json = if let Some(ns) = req.namespace {
|
||||
let mut result_json = if limit.is_some() || req.offset.is_some() {
|
||||
let filtered_entities: Vec<(&String, &crate::models::Entity)> = full
|
||||
.entities
|
||||
.iter()
|
||||
.filter(|(_, v)| req.namespace.as_ref().map_or(true, |ns| &v.namespace == ns))
|
||||
.collect();
|
||||
|
||||
let total_count = filtered_entities.len();
|
||||
let effective_limit = limit.unwrap_or(50);
|
||||
let paged_entities: std::collections::HashMap<&String, &crate::models::Entity> = filtered_entities
|
||||
.into_iter()
|
||||
.skip(offset)
|
||||
.take(effective_limit)
|
||||
.collect();
|
||||
|
||||
let has_more = offset + paged_entities.len() < total_count;
|
||||
|
||||
let relevant_relations: Vec<&crate::models::Relation> = full
|
||||
.relations
|
||||
.iter()
|
||||
.filter(|r| {
|
||||
let ns_match = req.namespace.as_ref().map_or(true, |ns| &r.namespace == ns);
|
||||
ns_match && (paged_entities.contains_key(&r.from) || paged_entities.contains_key(&r.to))
|
||||
})
|
||||
.collect();
|
||||
|
||||
serde_json::to_string(&serde_json::json!({
|
||||
"entities": paged_entities,
|
||||
"relations": relevant_relations,
|
||||
"total_count": total_count,
|
||||
"offset": offset,
|
||||
"limit": effective_limit,
|
||||
"has_more": has_more,
|
||||
}))?
|
||||
} else if let Some(ns) = req.namespace {
|
||||
let mut filtered = BorrowedGraph::default();
|
||||
for (k, v) in &full.entities {
|
||||
if v.namespace == ns {
|
||||
@@ -485,7 +515,7 @@ impl McpTool for ReadGraphHandler {
|
||||
if result_json.len() > max_chars {
|
||||
let valid_boundary = result_json.floor_char_boundary(max_chars);
|
||||
result_json.truncate(valid_boundary);
|
||||
result_json.push_str("\n... [TRUNCATED_TO_MAX_TOKENS. Use search_nodes or get_subgraph for targeted discovery]");
|
||||
result_json.push_str("\n... [TRUNCATED_TO_MAX_TOKENS. Use search_nodes, pagination (limit, offset), or get_subgraph for targeted discovery]");
|
||||
}
|
||||
}
|
||||
Ok(result_json)
|
||||
@@ -503,7 +533,10 @@ impl McpTool for SearchNodesHandler {
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<SearchNodesTool>("search_nodes", "Execute search_nodes")
|
||||
crate::mcp::tool_def::<SearchNodesTool>(
|
||||
"search_nodes",
|
||||
"Search knowledge graph entities and nodes by name or observation query. Returns matching entity names, types, and observations.",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
@@ -550,7 +583,10 @@ impl McpTool for OpenNodesHandler {
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<OpenNodesTool>("open_nodes", "Execute open_nodes")
|
||||
crate::mcp::tool_def::<OpenNodesTool>(
|
||||
"open_nodes",
|
||||
"Retrieve complete node details, observations, and connected relations for specific entities in the knowledge graph.",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
@@ -588,7 +624,10 @@ impl McpTool for VisualizeGraphHandler {
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<VisualizeGraphTool>("visualize_graph", "Execute visualize_graph")
|
||||
crate::mcp::tool_def::<VisualizeGraphTool>(
|
||||
"visualize_graph",
|
||||
"Generate a visual Mermaid representation of the knowledge graph topology or a filtered query subset.",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
@@ -677,7 +716,10 @@ impl McpTool for CondenseEntityHandler {
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<CondenseEntityTool>("condense_entity", "Execute condense_entity")
|
||||
crate::mcp::tool_def::<CondenseEntityTool>(
|
||||
"condense_entity",
|
||||
"Condense, summarize, and deduplicate redundant observations on an entity to optimize context size while preserving key facts.",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
@@ -757,7 +799,10 @@ impl McpTool for MergeEntitiesHandler {
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<MergeEntitiesTool>("merge_entities", "Execute merge_entities")
|
||||
crate::mcp::tool_def::<MergeEntitiesTool>(
|
||||
"merge_entities",
|
||||
"Merge two duplicate entities into a primary target entity, consolidating all observations and re-pointing relations.",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
@@ -817,7 +862,10 @@ impl McpTool for FindOrphansHandler {
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<FindOrphansTool>("find_orphans", "Execute find_orphans")
|
||||
crate::mcp::tool_def::<FindOrphansTool>(
|
||||
"find_orphans",
|
||||
"Find orphan nodes in the knowledge graph that have no incoming or outgoing relationships.",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
@@ -847,7 +895,10 @@ impl McpTool for GetSubgraphHandler {
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<GetSubgraphTool>("get_subgraph", "Execute get_subgraph")
|
||||
crate::mcp::tool_def::<GetSubgraphTool>(
|
||||
"get_subgraph",
|
||||
"Extract a connected neighborhood subgraph centered around specified seed nodes up to a given hop depth.",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
@@ -1188,6 +1239,7 @@ mod tests {
|
||||
observations: vec![],
|
||||
namespace: "global".to_string(),
|
||||
git_branch: None,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
g.entities.insert(
|
||||
@@ -1198,6 +1250,7 @@ mod tests {
|
||||
observations: vec![],
|
||||
namespace: "global".to_string(),
|
||||
git_branch: None,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -1256,6 +1309,7 @@ mod tests {
|
||||
observations: vec![],
|
||||
namespace: "global".to_string(),
|
||||
git_branch: None,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -1447,6 +1501,14 @@ mod tests {
|
||||
let qsignal_res = qsignal_handler.execute(json!({"sender": "agent1"}), state.clone()).await.unwrap();
|
||||
assert!(qsignal_res.contains("task_completed"));
|
||||
|
||||
let read_paged_handler = ReadGraphHandler;
|
||||
let paged_res = read_paged_handler
|
||||
.execute(json!({"limit": 1, "offset": 0}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
let parsed_paged: serde_json::Value = serde_json::from_str(&paged_res).unwrap();
|
||||
assert_eq!(parsed_paged["total_count"], 2);
|
||||
assert_eq!(parsed_paged["has_more"], true);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user