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:
1 parent
26eaf03b6f
commit
e006ef1220
4 files changed
+58
-71
No files matched your search
@@ -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());
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
|||||||
+26
-41
@@ -285,58 +285,45 @@ 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
|
if let Some((e1_name, e2_name)) = duplicates {
|
||||||
.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!(
|
tracing::info!(
|
||||||
"Memory Consolidation Daemon: Merging '{}' into '{}'",
|
"Memory Consolidation Daemon: Merging '{}' into '{}'",
|
||||||
e2_name,
|
e2_name,
|
||||||
e1_name
|
e1_name
|
||||||
);
|
);
|
||||||
state.modify_graph(|g| {
|
state.modify_graph(|g| {
|
||||||
if let Some(mut e2) = g.entities.remove(e2_name) {
|
if let Some(mut e2) = g.entities.remove(&e2_name) {
|
||||||
if let Some(e1) = g.entities.get_mut(e1_name) {
|
if let Some(e1) = g.entities.get_mut(&e1_name) {
|
||||||
e1.observations.append(&mut e2.observations);
|
e1.observations.append(&mut e2.observations);
|
||||||
} else {
|
} else {
|
||||||
g.entities.insert(e2_name.clone(), e2);
|
g.entities.insert(e2_name.clone(), e2);
|
||||||
@@ -344,10 +331,10 @@ pub async fn memory_consolidation_worker(state: Arc<MemoryState>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for rel in g.relations.iter_mut() {
|
for rel in g.relations.iter_mut() {
|
||||||
if rel.from == *e2_name {
|
if rel.from == e2_name {
|
||||||
rel.from = e1_name.clone();
|
rel.from = e1_name.clone();
|
||||||
}
|
}
|
||||||
if rel.to == *e2_name {
|
if rel.to == e2_name {
|
||||||
rel.to = e1_name.clone();
|
rel.to = e1_name.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,8 +342,6 @@ pub async fn memory_consolidation_worker(state: Arc<MemoryState>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run_server(state: Arc<MemoryState>) -> Result<(), Box<dyn std::error::Error>> {
|
pub async fn run_server(state: Arc<MemoryState>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let state_for_index = Arc::clone(&state);
|
let state_for_index = Arc::clone(&state);
|
||||||
|
|||||||
Reference in new issue
Block a user