perf(mcp): Replace redundant LLM usages with deterministic logic

- Refactored memory_consolidation_worker to use purely deterministic string normalization ((N)$ regex stripping & lowercase) instead of passing graph entities to Ollama for duplication detection.
- Removed redundant Ollama architectural summarization from log_code_change tool since the agent already provides a descriptive summary.
- Removed redundant Ollama fix summarization from log_error_fix tool since the agent already provides a solution string.
This commit is contained in:
Riz Ashraf committed 2026-10-10 10:58:50 +01:00
1 parent 26eaf03b6f
commit e006ef1220
4 files changed
+77 -90

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
use schemars::schema_for;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(JsonSchema, Deserialize, Serialize)]
#[serde(tag = "action", rename_all = "snake_case")]
pub enum TasksToolEnum {
/// Create task.
Add {
/// Task title.
title: String,
/// Task description.
description: Option<String>,
},
/// Delete task.
Delete {
/// Task ID.
id: String,
}
}
fn main() {
let schema = schema_for!(TasksToolEnum);
println!("{}", serde_json::to_string_pretty(&schema).unwrap());
}
+6
View File
@@ -0,0 +1,6 @@
use mcp_memory_server::tools::{DecisionsTool, TechDebtTool, ManageCheckpointTool, HypothesesTool, AgentSignalsTool};
use schemars::schema_for;
fn main() {
println!("{}", serde_json::to_string_pretty(&schema_for!(AgentSignalsTool)).unwrap());
}
+1 -30
View File
@@ -24,24 +24,7 @@ impl McpTool for LogErrorFixHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> { async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: LogErrorFixTool = serde_json::from_value(args).map_err(|e| e.to_string())?; let req: LogErrorFixTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
let text_to_embed = format!("Signature: {}\nSolution: {}", req.signature, req.solution); let text_to_embed = format!("Signature: {}\nSolution: {}", req.signature, req.solution);
let mut solution = req.solution; let solution = req.solution;
if state.ollama.is_available().await {
let prompt = format!(
"Analyze this error signature and solution. Output 1 sentence summarizing the root cause and fix:\nSignature: {}\nSolution: {}",
req.signature, solution
);
if let Ok(summary) = state
.ollama
.generate(&prompt, Some(&state.ollama.reasoning_model), None, None)
.await
{
let clean = summary.trim();
if !clean.is_empty() {
solution = format!("{} (AI Analysis: {})", solution, clean);
}
}
}
let embedding = crate::embedding::generate_embedding_async(text_to_embed) let embedding = crate::embedding::generate_embedding_async(text_to_embed)
.await .await
@@ -204,18 +187,6 @@ impl McpTool for LogCodeChangeHandler {
description = format!("{} [Symbols: {}]", description, symbols.join(", ")); description = format!("{} [Symbols: {}]", description, symbols.join(", "));
} }
if state.ollama.is_available().await {
let prompt = format!(
"Summarize in 1 concise sentence the architectural impact of changing file '{}': {}",
req.file_path, description
);
if let Ok(summary) = state.ollama.generate(&prompt, None, None, None).await {
let clean = summary.trim();
if !clean.is_empty() {
description = format!("{} (AI Summary: {})", description, clean);
}
}
}
let change_kind = match req let change_kind = match req
.change_kind .change_kind
.as_deref() .as_deref()
+45 -60
View File
@@ -285,76 +285,61 @@ pub async fn memory_consolidation_worker(state: Arc<MemoryState>) {
_ = interval.tick() => {}, _ = interval.tick() => {},
} }
let entities: Vec<_> = state.graph.read_with(|g| { let entities: Vec<String> = state.graph.read_with(|g| {
g.entities g.entities.keys().cloned().collect()
.values()
.map(|e| (e.name.clone(), e.entity_type.clone()))
.collect()
}); });
if entities.len() < 2 { if entities.len() < 2 {
continue; continue;
} }
let mut entity_summaries = String::new(); let mut duplicates = None;
for (name, e_type) in entities.iter().take(50) { let mut seen = std::collections::HashMap::new();
entity_summaries.push_str(&format!("- [{}] {}\n", e_type, name)); for name in &entities {
} let normalized: String = name
.chars()
.filter(|c| c.is_alphanumeric())
.flat_map(|c| c.to_lowercase())
.collect();
// Skip empty normalized names
if normalized.is_empty() {
continue;
}
let prompt = format!( if let Some(existing) = seen.insert(normalized, name.clone()) {
"Analyze the following list of entities and identify exactly TWO that represent the exact same concept or item but have slightly different names (e.g. 'auth_service' and 'AuthService'). Return ONLY a valid JSON array containing exactly two strings: the two names to merge. If no obvious duplicates exist, return an empty array []. Do not output any markdown formatting or extra text.\n\nEntities:\n{}", if existing != *name {
entity_summaries duplicates = Some((existing, name.clone()));
); break;
if let Ok(response) = state
.ollama
.generate(
&prompt,
None,
Some("You are a helpful JSON-only data deduplication assistant. Output only JSON."),
Some("json"),
)
.await
{
let cleaned = response
.trim()
.trim_start_matches("```json")
.trim_start_matches("```")
.trim_end_matches("```")
.trim();
if let Ok(duplicates) = serde_json::from_str::<Vec<String>>(cleaned)
&& duplicates.len() == 2
{
let e1_name = &duplicates[0];
let e2_name = &duplicates[1];
if e1_name != e2_name {
tracing::info!(
"Memory Consolidation Daemon: Merging '{}' into '{}'",
e2_name,
e1_name
);
state.modify_graph(|g| {
if let Some(mut e2) = g.entities.remove(e2_name) {
if let Some(e1) = g.entities.get_mut(e1_name) {
e1.observations.append(&mut e2.observations);
} else {
g.entities.insert(e2_name.clone(), e2);
}
}
for rel in g.relations.iter_mut() {
if rel.from == *e2_name {
rel.from = e1_name.clone();
}
if rel.to == *e2_name {
rel.to = e1_name.clone();
}
}
});
} }
} }
} }
if let Some((e1_name, e2_name)) = duplicates {
tracing::info!(
"Memory Consolidation Daemon: Merging '{}' into '{}'",
e2_name,
e1_name
);
state.modify_graph(|g| {
if let Some(mut e2) = g.entities.remove(&e2_name) {
if let Some(e1) = g.entities.get_mut(&e1_name) {
e1.observations.append(&mut e2.observations);
} else {
g.entities.insert(e2_name.clone(), e2);
}
}
for rel in g.relations.iter_mut() {
if rel.from == e2_name {
rel.from = e1_name.clone();
}
if rel.to == e2_name {
rel.to = e1_name.clone();
}
}
});
}
} }
} }