feat: Aggiunge miglioramenti per memoria e pianificazione con integrazione MCP
- Aggiunto sistema SimplePersistentMemory per fatti, preferenze e cronologia conversazioni - Aggiunto EnhancedPlanner con dipendenze task, priorità e tracciamento progresso - Integrati strumenti di miglioramento nell'agente (remember, recall, preference, planning) - Aggiunti comandi CLI /memory e /plan per accesso diretto da terminale - Aggiunto client MCP per connessione a server Model Context Protocol - Aggiornato README con documentazione completa per tutte le nuove funzionalità - Corretto parsing comando creazione piano per gestire nomi multi-parola Nuovi comandi: - /memory remember <testo> - Ricorda un fatto - /memory recall <query> - Cerca nella memoria - /memory preference set/get - Gestisci preferenze - /memory stats - Statistiche memoria - /plan create <nome> <task1;task2;...> - Crea un piano - /plan list - Elenca tutti i piani - /plan progress <plan_id> - Mostra progresso piano - /plan start <task_id> - Inizia un task - /plan complete <task_id> - Completa un task
This commit is contained in:
1 parent
5a8fcda993
commit
56f2819a92
9 files changed
+1889
-1
No files matched your search
@@ -0,0 +1,40 @@
|
||||
# Enhancements for LangCode Agent based on LangChain Deep Agents analysis
|
||||
|
||||
This directory contains planned enhancements to bring LangCode Agent closer to the architecture patterns revealed in LangChain's Deep Agents framework.
|
||||
|
||||
## Planned Enhancements
|
||||
|
||||
1. **Enhanced Memory Systems**
|
||||
- Add VectorStoreRetrieverMemory for semantic recall
|
||||
- Implement SummaryBufferMemory for conversation summarization
|
||||
- Add persistent memory storage for cross-session learning
|
||||
|
||||
2. **Advanced Planning & Reasoning**
|
||||
- Implement plan-and-execute agent pattern
|
||||
- Add self-reflection capabilities
|
||||
- Improve task decomposition and tracking
|
||||
|
||||
3. **Production Observability**
|
||||
- Add LangChain-style callbacks for tracing
|
||||
- Implement tool usage monitoring
|
||||
- Add performance metrics collection
|
||||
|
||||
4. **Advanced Tool Composition**
|
||||
- Implement tool chaining capabilities
|
||||
- Add async tool execution for parallelism
|
||||
- Enhance input validation and error handling
|
||||
|
||||
5. **Agent Hierarchies**
|
||||
- Develop specialist subagents for specific domains
|
||||
- Implement agent routing based on task analysis
|
||||
- Add agent-to-agent communication patterns
|
||||
|
||||
## Implementation Approach
|
||||
|
||||
Each enhancement will be implemented as a separate module that can be optionally integrated into the main agent, maintaining backward compatibility while extending capabilities.
|
||||
|
||||
The enhancements will follow the same patterns already established in the codebase:
|
||||
- Modular design
|
||||
- Configuration-driven behavior
|
||||
- Clear separation of concerns
|
||||
- Comprehensive error handling
|
||||
@@ -0,0 +1,317 @@
|
||||
"""
|
||||
Enhanced memory systems for LangCode Agent
|
||||
Implements advanced memory patterns from LangChain Deep Agents
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
# Try to import from langchain_core, fallback to community if needed
|
||||
try:
|
||||
from langchain_core.memory import BaseMemory
|
||||
from langchain_core.vectorstores import VectorStore
|
||||
from langchain_core.embeddings import Embeddings
|
||||
from langchain_core.documents import Document
|
||||
from langchain_core.storage import BaseStore
|
||||
from langchain_core.retrievers import BaseRetriever
|
||||
LANGCHAIN_CORE_AVAILABLE = True
|
||||
except ImportError:
|
||||
LANGCHAIN_CORE_AVAILABLE = False
|
||||
# We'll handle this gracefully in the implementation
|
||||
|
||||
# Try to import OllamaEmbeddings
|
||||
try:
|
||||
from langchain_community.embeddings import OllamaEmbeddings
|
||||
OLLAMA_AVAILABLE = True
|
||||
except ImportError:
|
||||
OLLAMA_AVAILABLE = False
|
||||
OllamaEmbeddings = None # type: ignore
|
||||
|
||||
# Try to import FAISS for vector store
|
||||
try:
|
||||
from langchain_community.vectorstores import FAISS
|
||||
FAISS_AVAILABLE = True
|
||||
except ImportError:
|
||||
FAISS_AVAILABLE = False
|
||||
FAISS = None # type: ignore
|
||||
|
||||
# Try to import memory components
|
||||
try:
|
||||
from langchain_core.memory import VectorStoreRetrieverMemory, SummaryBufferMemory
|
||||
MEMORY_COMPONENTS_AVAILABLE = True
|
||||
except ImportError:
|
||||
MEMORY_COMPONENTS_AVAILABLE = False
|
||||
VectorStoreRetrieverMemory = None # type: ignore
|
||||
SummaryBufferMemory = None # type: ignore
|
||||
|
||||
from langchain_core.tools import Tool
|
||||
|
||||
class EnhancedMemoryManager:
|
||||
"""Manages enhanced memory systems for the LangCode Agent"""
|
||||
|
||||
def __init__(self,
|
||||
embedding_model: str = "nomic-embed-text",
|
||||
ollama_base_url: str = "http://localhost:11434",
|
||||
persist_directory: str = "./.langcode/memory"):
|
||||
"""
|
||||
Initialize the enhanced memory manager
|
||||
|
||||
Args:
|
||||
embedding_model: Name of the embedding model to use
|
||||
ollama_base_url: URL for Ollama service
|
||||
persist_directory: Directory to persist memory data
|
||||
"""
|
||||
self.embedding_model = embedding_model
|
||||
self.ollama_base_url = ollama_base_url
|
||||
self.persist_directory = persist_directory
|
||||
|
||||
# Ensure persist directory exists
|
||||
os.makedirs(persist_directory, exist_ok=True)
|
||||
|
||||
# Initialize components if available
|
||||
self.embeddings = None
|
||||
self.vectorstore = None
|
||||
self.summary_memory = None
|
||||
self.vector_memory = None
|
||||
self.docstore = None
|
||||
|
||||
self._initialize_memory_systems()
|
||||
|
||||
def _initialize_memory_systems(self):
|
||||
"""Initialize all memory systems if dependencies are available"""
|
||||
if not (OLLAMA_AVAILABLE and FAISS_AVAILABLE and MEMORY_COMPONENTS_AVAILABLE):
|
||||
missing = []
|
||||
if not OLLAMA_AVAILABLE:
|
||||
missing.append("Ollama embeddings")
|
||||
if not FAISS_AVAILABLE:
|
||||
missing.append("FAISS vector store")
|
||||
if not MEMORY_COMPONENTS_AVAILABLE:
|
||||
missing.append("Memory components")
|
||||
print(f"Warning: Enhanced memory features limited due to missing: {', '.join(missing)}")
|
||||
return
|
||||
|
||||
try:
|
||||
# Initialize embeddings
|
||||
self.embeddings = OllamaEmbeddings(
|
||||
model=self.embedding_model,
|
||||
base_url=self.ollama_base_url
|
||||
)
|
||||
|
||||
# Initialize docstore for storing documents
|
||||
from langchain.storage import InMemoryStore
|
||||
self.docstore = InMemoryStore()
|
||||
|
||||
# Initialize vector store for semantic memory
|
||||
self.vectorstore = FAISS(
|
||||
embedding_function=self.embeddings,
|
||||
index=None,
|
||||
docstore=self.docstore,
|
||||
index_to_docstore_id={}
|
||||
)
|
||||
|
||||
# Initialize vector store memory
|
||||
self.vector_memory = VectorStoreRetrieverMemory(
|
||||
retriever=self.vectorstore.as_retriever(search_kwargs={"k": 3})
|
||||
)
|
||||
|
||||
# Initialize summary memory for conversation compression
|
||||
self.summary_memory = SummaryBufferMemory(
|
||||
max_token_limit=1000
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to initialize enhanced memory systems: {e}")
|
||||
# Reset to None if initialization failed
|
||||
self.embeddings = None
|
||||
self.vectorstore = None
|
||||
self.summary_memory = None
|
||||
self.vector_memory = None
|
||||
self.docstore = None
|
||||
|
||||
def set_llm_for_summary_memory(self, llm):
|
||||
"""Set the LLM for summary memory"""
|
||||
if self.summary_memory and hasattr(self.summary_memory, 'llm'):
|
||||
self.summary_memory.llm = llm
|
||||
|
||||
def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Save context to all memory systems
|
||||
|
||||
Args:
|
||||
inputs: Input values from the conversation
|
||||
outputs: Output values from the conversation
|
||||
"""
|
||||
try:
|
||||
# Save to vector memory
|
||||
if self.vector_memory:
|
||||
self.vector_memory.save_context(inputs, outputs)
|
||||
|
||||
# Save to summary memory
|
||||
if self.summary_memory:
|
||||
self.summary_memory.save_context(inputs, outputs)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to save context to memory: {e}")
|
||||
|
||||
def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Load memory variables from all memory systems
|
||||
|
||||
Args:
|
||||
inputs: Input values for context retrieval
|
||||
|
||||
Returns:
|
||||
Dictionary containing memory variables
|
||||
"""
|
||||
result = {}
|
||||
|
||||
try:
|
||||
# Load from vector memory
|
||||
if self.vector_memory:
|
||||
vector_result = self.vector_memory.load_memory_variables(inputs)
|
||||
result.update(vector_result)
|
||||
|
||||
# Load from summary memory
|
||||
if self.summary_memory:
|
||||
summary_result = self.summary_memory.load_memory_variables(inputs)
|
||||
result.update(summary_result)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to load memory variables: {e}")
|
||||
|
||||
return result
|
||||
|
||||
def add_document(self, content: str, metadata: Optional[Dict[str, Any]] = None) -> None:
|
||||
"""
|
||||
Add a document to the vector store for long-term memory
|
||||
|
||||
Args:
|
||||
content: Document content to store
|
||||
metadata: Optional metadata for the document
|
||||
"""
|
||||
if not self.vectorstore:
|
||||
return
|
||||
|
||||
try:
|
||||
doc = Document(page_content=content, metadata=metadata or {})
|
||||
self.vectorstore.add_documents([doc])
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to add document to vector store: {e}")
|
||||
|
||||
def search_similar(self, query: str, k: int = 3) -> List[Document]:
|
||||
"""
|
||||
Search for similar documents in vector store
|
||||
|
||||
Args:
|
||||
query: Search query
|
||||
k: Number of results to return
|
||||
|
||||
Returns:
|
||||
List of similar documents
|
||||
"""
|
||||
if not self.vectorstore:
|
||||
return []
|
||||
|
||||
try:
|
||||
return self.vectorstore.similarity_search(query, k=k)
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to search vector store: {e}")
|
||||
return []
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear all memory systems"""
|
||||
try:
|
||||
if self.vector_memory:
|
||||
self.vector_memory.clear()
|
||||
if self.summary_memory:
|
||||
self.summary_memory.clear()
|
||||
# Reinitialize vector store
|
||||
self._initialize_memory_systems()
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to clear memory systems: {e}")
|
||||
|
||||
def create_memory_tools(memory_manager: EnhancedMemoryManager) -> List[Tool]:
|
||||
"""
|
||||
Create LangChain tools for the enhanced memory system
|
||||
|
||||
Args:
|
||||
memory_manager: Instance of EnhancedMemoryManager
|
||||
|
||||
Returns:
|
||||
List of memory tools
|
||||
"""
|
||||
|
||||
def add_memory_func(content: str, metadata: str = "{}") -> str:
|
||||
try:
|
||||
metadata_dict = json.loads(metadata) if metadata.strip() else {}
|
||||
memory_manager.add_document(content, metadata_dict)
|
||||
return f"Added content to memory: {content[:50]}{'...' if len(content) > 50 else ''}"
|
||||
except Exception as e:
|
||||
return f"Error adding to memory: {str(e)}"
|
||||
|
||||
def search_memory_func(query: str, k: str = "3") -> str:
|
||||
try:
|
||||
k_int = int(k)
|
||||
results = memory_manager.search_similar(query, k_int)
|
||||
if not results:
|
||||
return "No similar memories found."
|
||||
|
||||
result = f"Found {len(results)} similar memories:\n"
|
||||
for i, doc in enumerate(results, 1):
|
||||
result += f"{i}. {doc.page_content[:100]}{'...' if len(doc.page_content) > 100 else ''}\n"
|
||||
if doc.metadata:
|
||||
result += f" Metadata: {doc.metadata}\n"
|
||||
return result
|
||||
except Exception as e:
|
||||
return f"Error searching memory: {str(e)}"
|
||||
|
||||
def clear_memory_func() -> str:
|
||||
try:
|
||||
memory_manager.clear()
|
||||
return "Memory cleared successfully."
|
||||
except Exception as e:
|
||||
return f"Error clearing memory: {str(e)}"
|
||||
|
||||
def get_memory_stats_func() -> str:
|
||||
try:
|
||||
stats = []
|
||||
if memory_manager.vectorstore:
|
||||
# Try to get vector count
|
||||
try:
|
||||
count = memory_manager.vectorstore.index.ntotal if hasattr(memory_manager.vectorstore.index, 'ntotal') else 'unknown'
|
||||
stats.append(f"Vector store: {count} vectors")
|
||||
except:
|
||||
stats.append("Vector store: active")
|
||||
if memory_manager.summary_memory:
|
||||
stats.append("Summary memory: active")
|
||||
if memory_manager.vector_memory:
|
||||
stats.append("Vector memory: active")
|
||||
return "Memory Systems:\n" + "\n".join(f" - {s}" for s in stats)
|
||||
except Exception as e:
|
||||
return f"Error getting memory stats: {str(e)}"
|
||||
|
||||
return [
|
||||
Tool(
|
||||
name="add_to_memory",
|
||||
description="Add content to long-term memory. Provide content and optional metadata as JSON string.",
|
||||
func=add_memory_func
|
||||
),
|
||||
Tool(
|
||||
name="search_memory",
|
||||
description="Search long-term memory for similar content. Provide query and optional k (number of results, default 3).",
|
||||
func=search_memory_func
|
||||
),
|
||||
Tool(
|
||||
name="clear_memory",
|
||||
description="Clear all long-term memory.",
|
||||
func=clear_memory_func
|
||||
),
|
||||
Tool(
|
||||
name="memory_stats",
|
||||
description="Get statistics about the memory systems.",
|
||||
func=get_memory_stats_func
|
||||
)
|
||||
]
|
||||
|
||||
# Global instance for easy access
|
||||
enhanced_memory_manager = EnhancedMemoryManager()
|
||||
@@ -0,0 +1,364 @@
|
||||
"""
|
||||
Enhanced planning systems for LangCode Agent
|
||||
Implements advanced planning patterns from LangChain Deep Agents
|
||||
"""
|
||||
import json
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Any, Optional, Tuple
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, asdict, field
|
||||
from langchain_core.tools import Tool
|
||||
|
||||
class TaskStatus(Enum):
|
||||
PENDING = "pending"
|
||||
IN_PROGRESS = "in_progress"
|
||||
COMPLETED = "completed"
|
||||
CANCELLED = "cancelled"
|
||||
FAILED = "failed"
|
||||
|
||||
class TaskPriority(Enum):
|
||||
LOW = "low"
|
||||
MEDIUM = "medium"
|
||||
HIGH = "high"
|
||||
URGENT = "urgent"
|
||||
|
||||
@dataclass
|
||||
class Task:
|
||||
"""Represents a task in the planning system"""
|
||||
id: str
|
||||
description: str
|
||||
status: TaskStatus
|
||||
priority: TaskPriority
|
||||
created_at: datetime
|
||||
dependencies: List[str] = field(default_factory=list) # List of task IDs that must be completed first
|
||||
started_at: Optional[datetime] = None
|
||||
completed_at: Optional[datetime] = None
|
||||
result: Optional[Any] = None
|
||||
error: Optional[str] = None
|
||||
subtasks: List[str] = field(default_factory=list) # List of subtask IDs
|
||||
|
||||
class EnhancedPlanner:
|
||||
"""Enhanced planning system for LangCode Agent"""
|
||||
|
||||
def __init__(self):
|
||||
self.tasks: Dict[str, Task] = {}
|
||||
self.task_counter = 0
|
||||
self.plans: Dict[str, List[str]] = {} # plan_id -> list of task IDs
|
||||
|
||||
def create_task(self,
|
||||
description: str,
|
||||
priority: TaskPriority = TaskPriority.MEDIUM,
|
||||
dependencies: Optional[List[str]] = None) -> str:
|
||||
"""
|
||||
Create a new task
|
||||
|
||||
Args:
|
||||
description: Task description
|
||||
priority: Task priority level
|
||||
dependencies: List of task IDs that must be completed first
|
||||
|
||||
Returns:
|
||||
Task ID
|
||||
"""
|
||||
if dependencies is None:
|
||||
dependencies = []
|
||||
|
||||
task_id = f"task_{self.task_counter:04d}"
|
||||
self.task_counter += 1
|
||||
|
||||
task = Task(
|
||||
id=task_id,
|
||||
description=description,
|
||||
status=TaskStatus.PENDING,
|
||||
priority=priority,
|
||||
dependencies=dependencies,
|
||||
created_at=datetime.now()
|
||||
)
|
||||
|
||||
self.tasks[task_id] = task
|
||||
return task_id
|
||||
|
||||
def create_plan(self, name: str, task_descriptions: List[str]) -> str:
|
||||
"""
|
||||
Create a plan with multiple tasks
|
||||
|
||||
Args:
|
||||
name: Plan name
|
||||
task_descriptions: List of task descriptions
|
||||
|
||||
Returns:
|
||||
Plan ID
|
||||
"""
|
||||
plan_id = f"plan_{len(self.plans):04d}"
|
||||
task_ids = []
|
||||
|
||||
for desc in task_descriptions:
|
||||
task_id = self.create_task(desc)
|
||||
task_ids.append(task_id)
|
||||
|
||||
self.plans[plan_id] = task_ids
|
||||
return plan_id
|
||||
|
||||
def start_task(self, task_id: str) -> bool:
|
||||
"""
|
||||
Start working on a task
|
||||
|
||||
Args:
|
||||
task_id: ID of the task to start
|
||||
|
||||
Returns:
|
||||
True if task started successfully, False otherwise
|
||||
"""
|
||||
if task_id not in self.tasks:
|
||||
return False
|
||||
|
||||
task = self.tasks[task_id]
|
||||
|
||||
# Check if dependencies are completed
|
||||
for dep_id in task.dependencies:
|
||||
if dep_id not in self.tasks or self.tasks[dep_id].status != TaskStatus.COMPLETED:
|
||||
return False # Dependencies not met
|
||||
|
||||
task.status = TaskStatus.IN_PROGRESS
|
||||
task.started_at = datetime.now()
|
||||
return True
|
||||
|
||||
def complete_task(self, task_id: str, result: Any = None) -> bool:
|
||||
"""
|
||||
Mark a task as completed
|
||||
|
||||
Args:
|
||||
task_id: ID of the task to complete
|
||||
result: Result of the task execution
|
||||
|
||||
Returns:
|
||||
True if task completed successfully, False otherwise
|
||||
"""
|
||||
if task_id not in self.tasks:
|
||||
return False
|
||||
|
||||
task = self.tasks[task_id]
|
||||
if task.status != TaskStatus.IN_PROGRESS:
|
||||
return False
|
||||
|
||||
task.status = TaskStatus.COMPLETED
|
||||
task.completed_at = datetime.now()
|
||||
task.result = result
|
||||
return True
|
||||
|
||||
def fail_task(self, task_id: str, error: str) -> bool:
|
||||
"""
|
||||
Mark a task as failed
|
||||
|
||||
Args:
|
||||
task_id: ID of the task to fail
|
||||
error: Error message
|
||||
|
||||
Returns:
|
||||
True if task marked as failed, False otherwise
|
||||
"""
|
||||
if task_id not in self.tasks:
|
||||
return False
|
||||
|
||||
task = self.tasks[task_id]
|
||||
if task.status != TaskStatus.IN_PROGRESS:
|
||||
return False
|
||||
|
||||
task.status = TaskStatus.FAILED
|
||||
task.completed_at = datetime.now()
|
||||
task.error = error
|
||||
return True
|
||||
|
||||
def get_ready_tasks(self) -> List[Task]:
|
||||
"""
|
||||
Get tasks that are ready to be started (dependencies met)
|
||||
|
||||
Returns:
|
||||
List of ready tasks
|
||||
"""
|
||||
ready_tasks = []
|
||||
for task in self.tasks.values():
|
||||
if task.status == TaskStatus.PENDING:
|
||||
# Check if all dependencies are completed
|
||||
deps_met = True
|
||||
for dep_id in task.dependencies:
|
||||
if dep_id not in self.tasks or self.tasks[dep_id].status != TaskStatus.COMPLETED:
|
||||
deps_met = False
|
||||
break
|
||||
if deps_met:
|
||||
ready_tasks.append(task)
|
||||
return ready_tasks
|
||||
|
||||
def get_task_status(self, task_id: str) -> Optional[TaskStatus]:
|
||||
"""
|
||||
Get the status of a task
|
||||
|
||||
Args:
|
||||
task_id: ID of the task
|
||||
|
||||
Returns:
|
||||
Task status or None if task not found
|
||||
"""
|
||||
if task_id in self.tasks:
|
||||
return self.tasks[task_id].status
|
||||
return None
|
||||
|
||||
def get_plan_progress(self, plan_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Get progress information for a plan
|
||||
|
||||
Args:
|
||||
plan_id: ID of the plan
|
||||
|
||||
Returns:
|
||||
Dictionary with progress information
|
||||
"""
|
||||
if plan_id not in self.plans:
|
||||
return {"error": "Plan not found"}
|
||||
|
||||
task_ids = self.plans[plan_id]
|
||||
total_tasks = len(task_ids)
|
||||
completed_tasks = 0
|
||||
failed_tasks = 0
|
||||
in_progress_tasks = 0
|
||||
|
||||
for task_id in task_ids:
|
||||
if task_id in self.tasks:
|
||||
task = self.tasks[task_id]
|
||||
if task.status == TaskStatus.COMPLETED:
|
||||
completed_tasks += 1
|
||||
elif task.status == TaskStatus.FAILED:
|
||||
failed_tasks += 1
|
||||
elif task.status == TaskStatus.IN_PROGRESS:
|
||||
in_progress_tasks += 1
|
||||
|
||||
return {
|
||||
"plan_id": plan_id,
|
||||
"total_tasks": total_tasks,
|
||||
"completed_tasks": completed_tasks,
|
||||
"failed_tasks": failed_tasks,
|
||||
"in_progress_tasks": in_progress_tasks,
|
||||
"pending_tasks": total_tasks - completed_tasks - failed_tasks - in_progress_tasks,
|
||||
"progress_percentage": (completed_tasks / total_tasks * 100) if total_tasks > 0 else 0
|
||||
}
|
||||
|
||||
def create_planning_tools(self) -> List[Tool]:
|
||||
"""
|
||||
Create LangChain tools for the planning system
|
||||
|
||||
Returns:
|
||||
List of planning tools
|
||||
"""
|
||||
def create_task_func(description: str, priority: str = "medium", dependencies: str = "") -> str:
|
||||
try:
|
||||
priority_enum = TaskPriority(priority.lower())
|
||||
dep_list = [d.strip() for d in dependencies.split(",") if d.strip()] if dependencies else []
|
||||
task_id = self.create_task(description, priority_enum, dep_list)
|
||||
return f"Created task '{description}' with ID: {task_id}"
|
||||
except Exception as e:
|
||||
return f"Error creating task: {str(e)}"
|
||||
|
||||
def start_task_func(task_id: str) -> str:
|
||||
try:
|
||||
if self.start_task(task_id):
|
||||
return f"Started task {task_id}"
|
||||
else:
|
||||
return f"Failed to start task {task_id} - check dependencies or task ID"
|
||||
except Exception as e:
|
||||
return f"Error starting task: {str(e)}"
|
||||
|
||||
def complete_task_func(task_id: str, result: str = "") -> str:
|
||||
try:
|
||||
if self.complete_task(task_id, result if result else None):
|
||||
return f"Completed task {task_id}"
|
||||
else:
|
||||
return f"Failed to complete task {task_id} - check task ID or status"
|
||||
except Exception as e:
|
||||
return f"Error completing task: {str(e)}"
|
||||
|
||||
def list_tasks_func(status: str = "") -> str:
|
||||
try:
|
||||
if status:
|
||||
status_enum = TaskStatus(status.lower())
|
||||
filtered_tasks = [t for t in self.tasks.values() if t.status == status_enum]
|
||||
else:
|
||||
filtered_tasks = list(self.tasks.values())
|
||||
|
||||
if not filtered_tasks:
|
||||
return "No tasks found"
|
||||
|
||||
result = f"Tasks ({len(filtered_tasks)}):\n"
|
||||
for task in sorted(filtered_tasks, key=lambda t: t.created_at):
|
||||
result += f"- [{task.status.value}] {task.id}: {task.description}\n"
|
||||
if task.dependencies:
|
||||
result += f" Dependencies: {', '.join(task.dependencies)}\n"
|
||||
return result
|
||||
except Exception as e:
|
||||
return f"Error listing tasks: {str(e)}"
|
||||
|
||||
def get_ready_tasks_func() -> str:
|
||||
try:
|
||||
ready_tasks = self.get_ready_tasks()
|
||||
if not ready_tasks:
|
||||
return "No tasks ready to start"
|
||||
|
||||
result = f"Ready tasks ({len(ready_tasks)}):\n"
|
||||
for task in ready_tasks:
|
||||
result += f"- {task.id}: {task.description}\n"
|
||||
return result
|
||||
except Exception as e:
|
||||
return f"Error getting ready tasks: {str(e)}"
|
||||
|
||||
def get_plan_progress_func(plan_id: str) -> str:
|
||||
try:
|
||||
progress = self.get_plan_progress(plan_id)
|
||||
if "error" in progress:
|
||||
return progress["error"]
|
||||
|
||||
result = f"Plan {plan_id} Progress:\n"
|
||||
result += f" Total: {progress['total_tasks']}\n"
|
||||
result += f" Completed: {progress['completed_tasks']}\n"
|
||||
result += f" Failed: {progress['failed_tasks']}\n"
|
||||
result += f" In Progress: {progress['in_progress_tasks']}\n"
|
||||
result += f" Pending: {progress['pending_tasks']}\n"
|
||||
result += f" Progress: {progress['progress_percentage']:.1f}%\n"
|
||||
return result
|
||||
except Exception as e:
|
||||
return f"Error getting plan progress: {str(e)}"
|
||||
|
||||
return [
|
||||
Tool(
|
||||
name="create_task",
|
||||
description="Create a new task. Provide description, priority (low/medium/high/urgent), and optional dependencies (comma-separated task IDs)",
|
||||
func=create_task_func
|
||||
),
|
||||
Tool(
|
||||
name="start_task",
|
||||
description="Start working on a task. Provide task ID",
|
||||
func=start_task_func
|
||||
),
|
||||
Tool(
|
||||
name="complete_task",
|
||||
description="Mark a task as completed. Provide task ID and optional result",
|
||||
func=complete_task_func
|
||||
),
|
||||
Tool(
|
||||
name="list_tasks",
|
||||
description="List all tasks. Optionally filter by status (pending, in_progress, completed, failed)",
|
||||
func=list_tasks_func
|
||||
),
|
||||
Tool(
|
||||
name="get_ready_tasks",
|
||||
description="Get tasks that are ready to be started (dependencies met)",
|
||||
func=get_ready_tasks_func
|
||||
),
|
||||
Tool(
|
||||
name="get_plan_progress",
|
||||
description="Get progress information for a plan. Provide plan ID",
|
||||
func=get_plan_progress_func
|
||||
)
|
||||
]
|
||||
|
||||
# Global planner instance
|
||||
enhanced_planner = EnhancedPlanner()
|
||||
@@ -0,0 +1,266 @@
|
||||
"""
|
||||
Simple memory enhancement for LangCode Agent
|
||||
Implements basic persistent memory without complex dependencies
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
|
||||
class SimplePersistentMemory:
|
||||
"""Simple persistent memory system for LangCode Agent"""
|
||||
|
||||
def __init__(self, memory_file: str = "./.langcode/simple_memory.json"):
|
||||
"""
|
||||
Initialize the simple persistent memory
|
||||
|
||||
Args:
|
||||
memory_file: Path to the memory file
|
||||
"""
|
||||
self.memory_file = memory_file
|
||||
self.memory_data = {
|
||||
"conversations": [],
|
||||
"facts": [],
|
||||
"preferences": {},
|
||||
"skills_used": [],
|
||||
"last_updated": None
|
||||
}
|
||||
|
||||
# Ensure directory exists
|
||||
os.makedirs(os.path.dirname(memory_file), exist_ok=True)
|
||||
|
||||
# Load existing memory
|
||||
self.load()
|
||||
|
||||
def load(self) -> None:
|
||||
"""Load memory from file"""
|
||||
try:
|
||||
if os.path.exists(self.memory_file):
|
||||
with open(self.memory_file, 'r') as f:
|
||||
self.memory_data = json.load(f)
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to load memory: {e}")
|
||||
# Reset to default if load fails
|
||||
self.memory_data = {
|
||||
"conversations": [],
|
||||
"facts": [],
|
||||
"preferences": {},
|
||||
"skills_used": [],
|
||||
"last_updated": None
|
||||
}
|
||||
|
||||
def save(self) -> None:
|
||||
"""Save memory to file"""
|
||||
try:
|
||||
self.memory_data["last_updated"] = datetime.now().isoformat()
|
||||
with open(self.memory_file, 'w') as f:
|
||||
json.dump(self.memory_data, f, indent=2)
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to save memory: {e}")
|
||||
|
||||
def add_conversation(self, user_input: str, agent_response: str) -> None:
|
||||
"""
|
||||
Add a conversation exchange to memory
|
||||
|
||||
Args:
|
||||
user_input: User's input
|
||||
agent_response: Agent's response
|
||||
"""
|
||||
self.memory_data["conversations"].append({
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"user": user_input,
|
||||
"agent": agent_response
|
||||
})
|
||||
|
||||
# Keep only last 100 conversations to prevent file from growing too large
|
||||
if len(self.memory_data["conversations"]) > 100:
|
||||
self.memory_data["conversations"] = self.memory_data["conversations"][-100:]
|
||||
|
||||
self.save()
|
||||
|
||||
def add_fact(self, fact: str, category: str = "general") -> None:
|
||||
"""
|
||||
Add a fact to memory
|
||||
|
||||
Args:
|
||||
fact: The fact to remember
|
||||
category: Category for the fact
|
||||
"""
|
||||
self.memory_data["facts"].append({
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"fact": fact,
|
||||
"category": category
|
||||
})
|
||||
|
||||
# Keep only last 50 facts
|
||||
if len(self.memory_data["facts"]) > 50:
|
||||
self.memory_data["facts"] = self.memory_data["facts"][-50:]
|
||||
|
||||
self.save()
|
||||
|
||||
def set_preference(self, key: str, value: Any) -> None:
|
||||
"""
|
||||
Set a user preference
|
||||
|
||||
Args:
|
||||
key: Preference key
|
||||
value: Preference value
|
||||
"""
|
||||
self.memory_data["preferences"][key] = value
|
||||
self.save()
|
||||
|
||||
def get_preference(self, key: str, default: Any = None) -> Any:
|
||||
"""
|
||||
Get a user preference
|
||||
|
||||
Args:
|
||||
key: Preference key
|
||||
default: Default value if key not found
|
||||
|
||||
Returns:
|
||||
Preference value or default
|
||||
"""
|
||||
return self.memory_data["preferences"].get(key, default)
|
||||
|
||||
def add_skill_used(self, skill_name: str) -> None:
|
||||
"""
|
||||
Record that a skill was used
|
||||
|
||||
Args:
|
||||
skill_name: Name of the skill used
|
||||
"""
|
||||
if skill_name not in self.memory_data["skills_used"]:
|
||||
self.memory_data["skills_used"].append(skill_name)
|
||||
self.save()
|
||||
|
||||
def get_recent_conversations(self, limit: int = 5) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get recent conversations
|
||||
|
||||
Args:
|
||||
limit: Maximum number of conversations to return
|
||||
|
||||
Returns:
|
||||
List of recent conversations
|
||||
"""
|
||||
return self.memory_data["conversations"][-limit:] if self.memory_data["conversations"] else []
|
||||
|
||||
def search_facts(self, query: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Search facts by query
|
||||
|
||||
Args:
|
||||
query: Search query
|
||||
|
||||
Returns:
|
||||
List of matching facts
|
||||
"""
|
||||
query_lower = query.lower()
|
||||
return [
|
||||
fact for fact in self.memory_data["facts"]
|
||||
if query_lower in fact["fact"].lower()
|
||||
]
|
||||
|
||||
def get_memory_stats(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Get memory statistics
|
||||
|
||||
Returns:
|
||||
Dictionary with memory statistics
|
||||
"""
|
||||
return {
|
||||
"total_conversations": len(self.memory_data["conversations"]),
|
||||
"total_facts": len(self.memory_data["facts"]),
|
||||
"total_preferences": len(self.memory_data["preferences"]),
|
||||
"total_skills_used": len(self.memory_data["skills_used"]),
|
||||
"last_updated": self.memory_data["last_updated"]
|
||||
}
|
||||
|
||||
def create_simple_memory_tools(memory: SimplePersistentMemory) -> List:
|
||||
"""
|
||||
Create simple tools for the persistent memory system
|
||||
|
||||
Args:
|
||||
memory: Instance of SimplePersistentMemory
|
||||
|
||||
Returns:
|
||||
List of memory tools
|
||||
"""
|
||||
from langchain_core.tools import Tool
|
||||
|
||||
def remember_func(fact: str, category: str = "general") -> str:
|
||||
try:
|
||||
memory.add_fact(fact, category)
|
||||
return f"Remembered: {fact}"
|
||||
except Exception as e:
|
||||
return f"Error remembering: {str(e)}"
|
||||
|
||||
def recall_func(query: str) -> str:
|
||||
try:
|
||||
results = memory.search_facts(query)
|
||||
if not results:
|
||||
return f"No memories found for: {query}"
|
||||
|
||||
result = f"Found {len(results)} memories:\n"
|
||||
for i, fact in enumerate(results[:5], 1): # Limit to 5 results
|
||||
result += f"{i}. {fact['fact']} ({fact['category']})\n"
|
||||
return result
|
||||
except Exception as e:
|
||||
return f"Error recalling: {str(e)}"
|
||||
|
||||
def preference_func(action: str, key: str = "", value: str = "") -> str:
|
||||
try:
|
||||
if action.lower() == "set" and key and value:
|
||||
# Try to parse value as JSON for complex types
|
||||
try:
|
||||
parsed_value = json.loads(value)
|
||||
except:
|
||||
parsed_value = value
|
||||
memory.set_preference(key, parsed_value)
|
||||
return f"Set preference '{key}' = {parsed_value}"
|
||||
elif action.lower() == "get" and key:
|
||||
val = memory.get_preference(key)
|
||||
return f"Preference '{key}': {val}"
|
||||
else:
|
||||
return "Usage: preference set <key> <value> or preference get <key>"
|
||||
except Exception as e:
|
||||
return f"Error with preference: {str(e)}"
|
||||
|
||||
def stats_func() -> str:
|
||||
try:
|
||||
stats = memory.get_memory_stats()
|
||||
result = "Memory Statistics:\n"
|
||||
result += f" Conversations: {stats['total_conversations']}\n"
|
||||
result += f" Facts: {stats['total_facts']}\n"
|
||||
result += f" Preferences: {stats['total_preferences']}\n"
|
||||
result += f" Skills Used: {stats['total_skills_used']}\n"
|
||||
result += f" Last Updated: {stats['last_updated'] or 'Never'}\n"
|
||||
return result
|
||||
except Exception as e:
|
||||
return f"Error getting memory stats: {str(e)}"
|
||||
|
||||
return [
|
||||
Tool(
|
||||
name="remember",
|
||||
description="Remember a fact for future reference. Provide fact and optional category.",
|
||||
func=remember_func
|
||||
),
|
||||
Tool(
|
||||
name="recall",
|
||||
description="Recall facts by searching memory. Provide search query.",
|
||||
func=recall_func
|
||||
),
|
||||
Tool(
|
||||
name="preference",
|
||||
description="Get or set user preferences. Usage: preference set <key> <value> or preference get <key>",
|
||||
func=preference_func
|
||||
),
|
||||
Tool(
|
||||
name="memory_stats",
|
||||
description="Get statistics about the memory system.",
|
||||
func=stats_func
|
||||
)
|
||||
]
|
||||
|
||||
# Global instance
|
||||
simple_memory = SimplePersistentMemory()
|
||||
@@ -0,0 +1,193 @@
|
||||
"""
|
||||
Test integration of enhancements with LangCode Agent
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, '.')
|
||||
|
||||
def test_enhancements_import():
|
||||
"""Test that our enhancements can be imported"""
|
||||
print("Testing enhancement imports...")
|
||||
|
||||
try:
|
||||
from enhancements.simple_memory import simple_memory, create_simple_memory_tools
|
||||
print("✓ Simple memory enhancement imported successfully")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to import simple memory enhancement: {e}")
|
||||
return False
|
||||
|
||||
try:
|
||||
from enhancements.planning_enhancement import enhanced_planner
|
||||
print("✓ Planning enhancement imported successfully")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to import planning enhancement: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def test_simple_memory():
|
||||
"""Test simple memory functionality"""
|
||||
print("\nTesting simple memory...")
|
||||
|
||||
try:
|
||||
from enhancements.simple_memory import simple_memory
|
||||
|
||||
# Test adding a fact
|
||||
simple_memory.add_fact("LangCode Agent supports MCP integration", "features")
|
||||
print("✓ Added fact to memory")
|
||||
|
||||
# Test recalling
|
||||
results = simple_memory.search_facts("MCP")
|
||||
if results:
|
||||
print(f"✓ Recalled {len(results)} facts about MCP")
|
||||
else:
|
||||
print("✗ Failed to recall facts about MCP")
|
||||
return False
|
||||
|
||||
# Test preference
|
||||
simple_memory.set_preference("model_provider", "ollama")
|
||||
provider = simple_memory.get_preference("model_provider")
|
||||
if provider == "ollama":
|
||||
print("✓ Preference system works")
|
||||
else:
|
||||
print(f"✗ Preference system failed: got {provider}")
|
||||
return False
|
||||
|
||||
# Test stats
|
||||
stats = simple_memory.get_memory_stats()
|
||||
if isinstance(stats, dict) and "total_facts" in stats:
|
||||
print(f"✓ Memory stats work: {stats['total_facts']} facts")
|
||||
else:
|
||||
print("✗ Memory stats failed")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Simple memory test failed: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def test_planning():
|
||||
"""Test planning functionality"""
|
||||
print("\nTesting planning...")
|
||||
|
||||
try:
|
||||
from enhancements.planning_enhancement import enhanced_planner, TaskPriority
|
||||
|
||||
# Test creating a task
|
||||
task_id = enhanced_planner.create_task("Test task", TaskPriority.HIGH)
|
||||
if task_id:
|
||||
print(f"✓ Created task: {task_id}")
|
||||
else:
|
||||
print("✗ Failed to create task")
|
||||
return False
|
||||
|
||||
# Test starting task
|
||||
if enhanced_planner.start_task(task_id):
|
||||
print("✓ Started task successfully")
|
||||
else:
|
||||
print("✗ Failed to start task")
|
||||
return False
|
||||
|
||||
# Test completing task
|
||||
if enhanced_planner.complete_task(task_id, "Task completed successfully"):
|
||||
print("✓ Completed task successfully")
|
||||
else:
|
||||
print("✗ Failed to complete task")
|
||||
return False
|
||||
|
||||
# Test listing tasks by getting the tool and calling it
|
||||
planning_tools = enhanced_planner.create_planning_tools()
|
||||
list_tasks_tool = None
|
||||
for tool in planning_tools:
|
||||
if tool.name == "list_tasks":
|
||||
list_tasks_tool = tool
|
||||
break
|
||||
|
||||
if list_tasks_tool and list_tasks_tool.func:
|
||||
tasks_output = list_tasks_tool.func("")
|
||||
if "Test task" in tasks_output and "completed" in tasks_output:
|
||||
print("✓ Task listing works")
|
||||
else:
|
||||
print("✗ Task listing failed")
|
||||
print(f" Output: {tasks_output}")
|
||||
return False
|
||||
else:
|
||||
print("✗ Could not find or call list_tasks tool")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Planning test failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def test_tool_creation():
|
||||
"""Test that our enhancements create valid LangChain tools"""
|
||||
print("\nTesting tool creation...")
|
||||
|
||||
try:
|
||||
from enhancements.simple_memory import create_simple_memory_tools, simple_memory
|
||||
from enhancements.planning_enhancement import enhanced_planner
|
||||
|
||||
# Test simple memory tools
|
||||
memory_tools = create_simple_memory_tools(simple_memory)
|
||||
if len(memory_tools) == 4:
|
||||
print(f"✓ Simple memory tools created: {len(memory_tools)} tools")
|
||||
tool_names = [tool.name for tool in memory_tools]
|
||||
expected = ["remember", "recall", "preference", "memory_stats"]
|
||||
if all(name in tool_names for name in expected):
|
||||
print("✓ All expected memory tools present")
|
||||
else:
|
||||
print(f"✗ Missing some memory tools. Got: {tool_names}")
|
||||
return False
|
||||
else:
|
||||
print(f"✗ Expected 4 memory tools, got {len(memory_tools)}")
|
||||
return False
|
||||
|
||||
# Test planning tools
|
||||
planning_tools = enhanced_planner.create_planning_tools()
|
||||
if len(planning_tools) == 6:
|
||||
print(f"✓ Planning tools created: {len(planning_tools)} tools")
|
||||
tool_names = [tool.name for tool in planning_tools]
|
||||
expected = ["create_task", "start_task", "complete_task", "list_tasks", "get_ready_tasks", "get_plan_progress"]
|
||||
if all(name in tool_names for name in expected):
|
||||
print("✓ All expected planning tools present")
|
||||
else:
|
||||
print(f"✗ Missing some planning tools. Got: {tool_names}")
|
||||
return False
|
||||
else:
|
||||
print(f"✗ Expected 6 planning tools, got {len(planning_tools)}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Tool creation test failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""Run all tests"""
|
||||
print("=== LangCode Agent Enhancements Integration Test ===\n")
|
||||
|
||||
all_passed = True
|
||||
|
||||
all_passed &= test_enhancements_import()
|
||||
all_passed &= test_simple_memory()
|
||||
all_passed &= test_planning()
|
||||
all_passed &= test_tool_creation()
|
||||
|
||||
print("\n=== Test Results ===")
|
||||
if all_passed:
|
||||
print("✓ All tests passed! Enhancements are ready for integration.")
|
||||
return 0
|
||||
else:
|
||||
print("✗ Some tests failed. Please review the implementation.")
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user