LLMs and generative AI / Agents and LangChain
Building an Agentic Mesh for Autonomous Agents
Contents
- Agent Directory Implementation with ChromaDB
- Agent Discovery Protocol
- Collaborative Planning Engine
- Secure Transaction Framework
- Multi-Agent Communication Protocol
- Agent Knowledge Base Implementation
- Task Orchestration System
- Consensus Mechanism Implementation
- Agent Learning Module
- Mesh Synchronization Protocol
- Capability Discovery and Matching
- Task Decomposition Engine
- Resource Allocation System
- Performance Monitoring System
- Additional Resources
Agent Directory Implementation with ChromaDB
The Agent Directory serves as a foundational component for the Agentic Mesh, enabling efficient storage and retrieval of agent metadata. ChromaDB provides persistent vector storage with efficient similarity search capabilities for agent discovery.
import chromadb
from chromadb.config import Settings
import uuid
class AgentDirectory:
def __init__(self):
# Initialize ChromaDB client with persistence
self.client = chromadb.Client(Settings(
chroma_db_impl="duckdb+parquet",
persist_directory="agent_directory"
))
# Create collection for agents
self.collection = self.client.create_collection("agents")
def register_agent(self, capabilities, metadata):
agent_id = str(uuid.uuid4())
# Store agent data with vector embedding
self.collection.add(
documents=[str(capabilities)],
metadatas=[metadata],
ids=[agent_id]
)
return agent_id
def find_agents(self, query, n_results=5):
# Query similar agents based on capabilities
results = self.collection.query(
query_texts=[query],
n_results=n_results
)
return results
# Example usage
directory = AgentDirectory()
agent_id = directory.register_agent(
capabilities=["natural language processing", "data analysis"],
metadata={"name": "AnalyticsAgent", "version": "1.0"}
)
Agent Discovery Protocol
This implementation defines the core protocol for agent discovery within the mesh, using semantic similarity matching to identify relevant collaborators based on capability requirements and constraints.
from dataclasses import dataclass
from typing import List, Dict
import numpy as np
@dataclass
class AgentProfile:
id: str
capabilities: List[str]
metadata: Dict
class DiscoveryProtocol:
def __init__(self, directory):
self.directory = directory
self.capability_weights = {
"primary": 1.0,
"secondary": 0.5
}
def find_collaborators(self, requirements, constraints=None):
# Weighted query based on requirement priority
weighted_query = self._build_weighted_query(requirements)
candidates = self.directory.find_agents(weighted_query)
if constraints:
candidates = self._apply_constraints(candidates, constraints)
return self._rank_candidates(candidates)
def _build_weighted_query(self, requirements):
query = ""
for cap, priority in requirements.items():
weight = self.capability_weights.get(priority, 0.3)
query += f"{cap} * {weight} + "
return query.rstrip(" + ")
# Example usage
protocol = DiscoveryProtocol(directory)
collaborators = protocol.find_collaborators({
"data analysis": "primary",
"visualization": "secondary"
})
Collaborative Planning Engine
The planning engine coordinates multi-agent collaboration through a structured workflow that includes task decomposition, capability matching, and consensus-building mechanisms for distributed decision-making.
from typing import Optional
import asyncio
class CollaborativePlanner:
def __init__(self, discovery_protocol):
self.discovery = discovery_protocol
self.task_queue = asyncio.Queue()
async def create_execution_plan(self, task_description):
# Decompose task into subtasks
subtasks = self._decompose_task(task_description)
execution_plan = []
for subtask in subtasks:
# Find capable agents
agents = self.discovery.find_collaborators(subtask.requirements)
# Allocate subtask to best-fit agent
allocation = await self._allocate_subtask(subtask, agents)
execution_plan.append(allocation)
return self._optimize_plan(execution_plan)
def _decompose_task(self, task):
# Task decomposition logic
subtasks = []
# Implementation details
return subtasks
async def _allocate_subtask(self, subtask, agents):
# Allocation logic with consensus
return {"subtask": subtask, "agent": agents[0]}
Secure Transaction Framework
A robust transaction system ensures secure and verifiable exchanges between agents, implementing atomic operations and rollback mechanisms to maintain system consistency during multi-agent interactions.
import hashlib
from datetime import datetime
from typing import Any
class SecureTransaction:
def __init__(self):
self.ledger = {}
self.pending = {}
def initiate_transaction(self,
sender_id: str,
receiver_id: str,
payload: Any) -> str:
# Create transaction record
transaction_id = self._generate_transaction_id(sender_id, receiver_id)
# Hash payload for verification
payload_hash = hashlib.sha256(str(payload).encode()).hexdigest()
self.pending[transaction_id] = {
'sender': sender_id,
'receiver': receiver_id,
'payload_hash': payload_hash,
'status': 'pending',
'timestamp': datetime.utcnow()
}
return transaction_id
def commit_transaction(self, transaction_id: str, verification_hash: str):
if transaction_id not in self.pending:
raise ValueError("Invalid transaction ID")
transaction = self.pending[transaction_id]
if verification_hash == transaction['payload_hash']:
self.ledger[transaction_id] = {
**transaction,
'status': 'completed'
}
del self.pending[transaction_id]
return True
return False
Multi-Agent Communication Protocol
This implementation establishes a robust communication framework allowing agents to exchange messages, share context, and coordinate activities through an asynchronous event-driven architecture.
import asyncio
from enum import Enum
from dataclasses import dataclass
from typing import Dict, Any, Callable
class MessageType(Enum):
QUERY = "query"
RESPONSE = "response"
TASK = "task"
STATUS = "status"
@dataclass
class Message:
sender: str
recipient: str
msg_type: MessageType
content: Dict[str, Any]
correlation_id: str
class CommunicationBus:
def __init__(self):
self.subscribers: Dict[str, Callable] = {}
self.message_queue = asyncio.Queue()
async def publish(self, message: Message):
await self.message_queue.put(message)
if message.recipient in self.subscribers:
await self.subscribers[message.recipient](message)
def subscribe(self, agent_id: str, callback: Callable):
self.subscribers[agent_id] = callback
async def start(self):
while True:
message = await self.message_queue.get()
# Message processing logic
self.message_queue.task_done()
# Example usage
async def agent_callback(message):
print(f"Agent received: {message.content}")
comm_bus = CommunicationBus()
comm_bus.subscribe("agent1", agent_callback)
Agent Knowledge Base Implementation
The knowledge base component provides a persistent storage mechanism for agent experiences, learned patterns, and shared knowledge, utilizing vector embeddings for efficient information retrieval and knowledge sharing.
import numpy as np
from typing import List, Dict, Optional
import pickle
class KnowledgeBase:
def __init__(self, embedding_dim: int = 768):
self.embedding_dim = embedding_dim
self.knowledge_vectors = {}
self.knowledge_metadata = {}
def store_knowledge(self,
key: str,
content: Dict,
embedding: np.ndarray,
metadata: Optional[Dict] = None):
if embedding.shape[0] != self.embedding_dim:
raise ValueError(f"Expected embedding dim {self.embedding_dim}")
self.knowledge_vectors[key] = embedding
self.knowledge_metadata[key] = {
'content': content,
'metadata': metadata or {},
'access_count': 0
}
def query_knowledge(self,
query_embedding: np.ndarray,
top_k: int = 5) -> List[Dict]:
similarities = {}
for key, vector in self.knowledge_vectors.items():
sim = self._cosine_similarity(query_embedding, vector)
similarities[key] = sim
# Get top-k similar entries
sorted_keys = sorted(similarities.items(),
key=lambda x: x[1],
reverse=True)[:top_k]
results = []
for key, sim in sorted_keys:
self.knowledge_metadata[key]['access_count'] += 1
results.append({
'key': key,
'content': self.knowledge_metadata[key]['content'],
'similarity': sim
})
return results
def _cosine_similarity(self, a: np.ndarray, b: np.ndarray) -> float:
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
Task Orchestration System
A sophisticated orchestration system that manages complex workflows across multiple agents, handling task dependencies, parallel execution, and failure recovery mechanisms.
from enum import Enum
from typing import List, Dict, Optional
import asyncio
from datetime import datetime
class TaskStatus(Enum):
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
class TaskOrchestrator:
def __init__(self):
self.task_graph = {}
self.task_status = {}
self.execution_history = []
async def schedule_task(self,
task_id: str,
dependencies: List[str],
executor_agent: str,
payload: Dict):
self.task_graph[task_id] = {
'dependencies': dependencies,
'executor': executor_agent,
'payload': payload,
'created_at': datetime.utcnow()
}
self.task_status[task_id] = TaskStatus.PENDING
# Check if task can be executed
if self._can_execute(task_id):
await self._execute_task(task_id)
async def _execute_task(self, task_id: str):
try:
self.task_status[task_id] = TaskStatus.RUNNING
task = self.task_graph[task_id]
# Execute task logic
result = await self._delegate_to_agent(
task['executor'],
task['payload']
)
self.task_status[task_id] = TaskStatus.COMPLETED
self.execution_history.append({
'task_id': task_id,
'status': TaskStatus.COMPLETED,
'result': result,
'completed_at': datetime.utcnow()
})
# Check dependent tasks
await self._process_dependent_tasks(task_id)
except Exception as e:
self.task_status[task_id] = TaskStatus.FAILED
self._handle_failure(task_id, str(e))
def _can_execute(self, task_id: str) -> bool:
dependencies = self.task_graph[task_id]['dependencies']
return all(
self.task_status.get(dep) == TaskStatus.COMPLETED
for dep in dependencies
)
Consensus Mechanism Implementation
The consensus mechanism ensures agreement between agents on shared decisions and state changes, implementing a Byzantine fault-tolerant protocol suitable for decentralized agent networks.
from dataclasses import dataclass
from typing import List, Dict, Set
import time
import hashlib
@dataclass
class ConsensusProposal:
proposal_id: str
proposer: str
value: Dict
timestamp: float
class ConsensusProtocol:
def __init__(self, agent_id: str, total_agents: int):
self.agent_id = agent_id
self.total_agents = total_agents
self.threshold = (2 * total_agents) // 3 + 1
self.proposals: Dict[str, ConsensusProposal] = {}
self.votes: Dict[str, Set[str]] = {}
self.committed: Dict[str, Dict] = {}
def propose(self, value: Dict) -> str:
proposal_id = self._generate_proposal_id(value)
proposal = ConsensusProposal(
proposal_id=proposal_id,
proposer=self.agent_id,
value=value,
timestamp=time.time()
)
self.proposals[proposal_id] = proposal
self.votes[proposal_id] = {self.agent_id}
return proposal_id
def vote(self, proposal_id: str, agent_id: str) -> bool:
if proposal_id not in self.proposals:
return False
self.votes[proposal_id].add(agent_id)
if len(self.votes[proposal_id]) >= self.threshold:
self._commit_proposal(proposal_id)
return True
return False
def _commit_proposal(self, proposal_id: str):
proposal = self.proposals[proposal_id]
self.committed[proposal_id] = proposal.value
def _generate_proposal_id(self, value: Dict) -> str:
content = f"{self.agent_id}:{str(value)}:{time.time()}"
return hashlib.sha256(content.encode()).hexdigest()
Agent Learning Module
The learning module enables agents to improve their decision-making capabilities through experience, implementing a reinforcement learning approach with experience replay and policy updates.
import numpy as np
from collections import deque
from typing import Tuple, List
class AgentLearningModule:
def __init__(self,
state_dim: int,
action_dim: int,
memory_size: int = 10000):
self.state_dim = state_dim
self.action_dim = action_dim
self.memory = deque(maxlen=memory_size)
self.gamma = 0.95 # Discount factor
self.epsilon = 1.0 # Exploration rate
self.epsilon_decay = 0.995
self.epsilon_min = 0.01
self.learning_rate = 0.001
# Initialize neural network weights
self.weights = {
'W1': np.random.randn(state_dim, 64) / np.sqrt(state_dim),
'W2': np.random.randn(64, 32) / np.sqrt(64),
'W3': np.random.randn(32, action_dim) / np.sqrt(32)
}
def remember(self,
state: np.ndarray,
action: int,
reward: float,
next_state: np.ndarray,
done: bool):
self.memory.append((state, action, reward, next_state, done))
def act(self, state: np.ndarray) -> int:
if np.random.rand() <= self.epsilon:
return np.random.randint(self.action_dim)
q_values = self._forward(state)
return np.argmax(q_values)
def train(self, batch_size: int = 32):
if len(self.memory) < batch_size:
return
batch = np.random.choice(len(self.memory), batch_size, replace=False)
losses = []
for idx in batch:
state, action, reward, next_state, done = self.memory[idx]
target = reward
if not done:
target += self.gamma * np.max(self._forward(next_state))
current_q = self._forward(state)
current_q[action] = target
loss = self._backward(state, current_q)
losses.append(loss)
self.epsilon = max(self.epsilon_min,
self.epsilon * self.epsilon_decay)
return np.mean(losses)
def _forward(self, state: np.ndarray) -> np.ndarray:
h1 = np.tanh(state.dot(self.weights['W1']))
h2 = np.tanh(h1.dot(self.weights['W2']))
return h2.dot(self.weights['W3'])
Mesh Synchronization Protocol
A distributed synchronization mechanism ensuring consistency across the agent mesh, implementing vector clocks and conflict resolution strategies for eventual consistency.
from typing import Dict, Set, Optional
import time
from dataclasses import dataclass, field
@dataclass
class VectorClock:
timestamps: Dict[str, int] = field(default_factory=dict)
def increment(self, agent_id: str):
self.timestamps[agent_id] = self.timestamps.get(agent_id, 0) + 1
def merge(self, other: 'VectorClock'):
all_agents = set(self.timestamps.keys()) | set(other.timestamps.keys())
for agent_id in all_agents:
self.timestamps[agent_id] = max(
self.timestamps.get(agent_id, 0),
other.timestamps.get(agent_id, 0)
)
class MeshSynchronizer:
def __init__(self, agent_id: str):
self.agent_id = agent_id
self.vector_clock = VectorClock()
self.state_log = []
self.peers: Set[str] = set()
def update_state(self, state_update: Dict) -> Dict:
# Update vector clock
self.vector_clock.increment(self.agent_id)
# Log state update with timestamp
log_entry = {
'timestamp': self.vector_clock.timestamps.copy(),
'agent_id': self.agent_id,
'update': state_update,
'physical_time': time.time()
}
self.state_log.append(log_entry)
return {
'vector_clock': self.vector_clock.timestamps,
'update': state_update
}
def receive_update(self,
peer_id: str,
update: Dict,
peer_clock: Dict[str, int]):
# Merge vector clocks
peer_vc = VectorClock(peer_clock)
self.vector_clock.merge(peer_vc)
# Check for causality violations
if self._check_causality(peer_id, peer_clock):
self._apply_update(peer_id, update)
self.peers.add(peer_id)
def _check_causality(self,
peer_id: str,
peer_clock: Dict[str, int]) -> bool:
local_time = self.vector_clock.timestamps.get(peer_id, 0)
peer_time = peer_clock.get(peer_id, 0)
return peer_time == local_time + 1
Capability Discovery and Matching
The capability discovery system implements semantic matching algorithms to identify and pair agents based on their abilities and requirements, using embeddings for capability comparison.
from typing import List, Dict, Tuple
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
class CapabilityMatcher:
def __init__(self, embedding_dim: int = 768):
self.embedding_dim = embedding_dim
self.capability_embeddings = {}
self.agent_capabilities = {}
def register_agent_capabilities(self,
agent_id: str,
capabilities: List[Dict]):
# Generate embeddings for each capability
agent_embeddings = []
for cap in capabilities:
embedding = self._generate_capability_embedding(cap)
self.capability_embeddings[cap['id']] = embedding
agent_embeddings.append(embedding)
self.agent_capabilities[agent_id] = {
'embeddings': agent_embeddings,
'metadata': capabilities
}
def find_matching_agents(self,
required_capabilities: List[Dict],
threshold: float = 0.8) -> List[Tuple[str, float]]:
required_embeddings = [
self._generate_capability_embedding(cap)
for cap in required_capabilities
]
matches = []
for agent_id, agent_data in self.agent_capabilities.items():
match_score = self._calculate_match_score(
required_embeddings,
agent_data['embeddings']
)
if match_score >= threshold:
matches.append((agent_id, match_score))
return sorted(matches, key=lambda x: x[1], reverse=True)
def _calculate_match_score(self,
required: List[np.ndarray],
available: List[np.ndarray]) -> float:
similarity_matrix = cosine_similarity(
np.vstack(required),
np.vstack(available)
)
return np.mean(np.max(similarity_matrix, axis=1))
def _generate_capability_embedding(self, capability: Dict) -> np.ndarray:
# Implement your embedding generation logic here
# This could use transformers or other embedding methods
return np.random.randn(self.embedding_dim)
Task Decomposition Engine
An intelligent system for breaking down complex tasks into manageable subtasks, considering dependencies, resource constraints, and optimal agent allocation strategies.
from dataclasses import dataclass
from typing import List, Dict, Optional
import networkx as nx
@dataclass
class SubTask:
id: str
description: str
requirements: Dict
estimated_duration: float
dependencies: List[str]
class TaskDecomposer:
def __init__(self):
self.task_graph = nx.DiGraph()
self.completion_times = {}
def decompose_task(self, task_spec: Dict) -> List[SubTask]:
# Clear previous task graph
self.task_graph.clear()
# Generate subtasks based on task specification
subtasks = self._generate_subtasks(task_spec)
# Build dependency graph
for subtask in subtasks:
self.task_graph.add_node(subtask.id, task=subtask)
for dep in subtask.dependencies:
self.task_graph.add_edge(dep, subtask.id)
# Validate acyclic graph
if not nx.is_directed_acyclic_graph(self.task_graph):
raise ValueError("Circular dependencies detected")
# Calculate critical path
self._calculate_completion_times()
return self._get_execution_order()
def _generate_subtasks(self, task_spec: Dict) -> List[SubTask]:
subtasks = []
# Implement your task decomposition logic here
# This could use NLP or rule-based approaches
return subtasks
def _calculate_completion_times(self):
for node in nx.topological_sort(self.task_graph):
task = self.task_graph.nodes[node]['task']
pred_time = max(
[self.completion_times.get(p, 0)
for p in self.task_graph.predecessors(node)],
default=0
)
self.completion_times[node] = (
pred_time + task.estimated_duration
)
def _get_execution_order(self) -> List[SubTask]:
return [
self.task_graph.nodes[node]['task']
for node in nx.topological_sort(self.task_graph)
]
Resource Allocation System
The resource allocation system manages computational and data resources across the agent mesh, implementing fair scheduling and priority-based allocation strategies for optimal resource utilization.
from dataclasses import dataclass
from typing import Dict, List, Optional
from enum import Enum
import heapq
class ResourceType(Enum):
CPU = "cpu"
MEMORY = "memory"
STORAGE = "storage"
API_CALLS = "api_calls"
@dataclass
class ResourceRequest:
agent_id: str
resource_type: ResourceType
amount: float
priority: int
timestamp: float
class ResourceAllocator:
def __init__(self, resource_limits: Dict[ResourceType, float]):
self.resource_limits = resource_limits
self.allocated_resources: Dict[str, Dict[ResourceType, float]] = {}
self.request_queue = []
self.allocation_history = []
def request_resources(self, request: ResourceRequest) -> bool:
if self._can_allocate(request):
self._allocate_resources(request)
return True
heapq.heappush(
self.request_queue,
(-request.priority, request.timestamp, request)
)
return False
def release_resources(self, agent_id: str, resource_type: ResourceType):
if agent_id in self.allocated_resources:
amount = self.allocated_resources[agent_id].get(resource_type, 0)
if amount > 0:
del self.allocated_resources[agent_id][resource_type]
self._process_queue()
def _can_allocate(self, request: ResourceRequest) -> bool:
current_usage = sum(
alloc.get(request.resource_type, 0)
for alloc in self.allocated_resources.values()
)
return (current_usage + request.amount <=
self.resource_limits[request.resource_type])
def _allocate_resources(self, request: ResourceRequest):
if request.agent_id not in self.allocated_resources:
self.allocated_resources[request.agent_id] = {}
self.allocated_resources[request.agent_id][request.resource_type] = (
request.amount
)
self.allocation_history.append({
'agent_id': request.agent_id,
'resource_type': request.resource_type,
'amount': request.amount,
'timestamp': request.timestamp
})
def _process_queue(self):
if not self.request_queue:
return
# Try to process pending requests
new_queue = []
while self.request_queue:
_, _, request = heapq.heappop(self.request_queue)
if self._can_allocate(request):
self._allocate_resources(request)
else:
new_queue.append((-request.priority,
request.timestamp, request))
self.request_queue = new_queue
heapq.heapify(self.request_queue)
Performance Monitoring System
A comprehensive monitoring system that tracks agent performance metrics, resource utilization, and interaction patterns to optimize mesh operations and identify bottlenecks.
from typing import Dict, List, Optional
import time
import numpy as np
from collections import deque
class PerformanceMonitor:
def __init__(self, window_size: int = 1000):
self.metrics = {
'response_times': deque(maxlen=window_size),
'success_rates': deque(maxlen=window_size),
'resource_usage': deque(maxlen=window_size),
'interaction_counts': {}
}
self.alerts = []
self.thresholds = {
'response_time': 1.0, # seconds
'success_rate': 0.95,
'resource_usage': 0.8
}
def record_interaction(self,
source_id: str,
target_id: str,
duration: float,
success: bool,
resource_usage: Dict[str, float]):
# Record response time
self.metrics['response_times'].append(duration)
# Record success/failure
self.metrics['success_rates'].append(float(success))
# Record resource usage
self.metrics['resource_usage'].append(
max(resource_usage.values())
)
# Record interaction count
key = f"{source_id}:{target_id}"
self.metrics['interaction_counts'][key] = (
self.metrics['interaction_counts'].get(key, 0) + 1
)
# Check for anomalies
self._check_anomalies()
def get_performance_summary(self) -> Dict:
return {
'avg_response_time': np.mean(self.metrics['response_times']),
'success_rate': np.mean(self.metrics['success_rates']),
'avg_resource_usage': np.mean(self.metrics['resource_usage']),
'total_interactions': sum(
self.metrics['interaction_counts'].values()
),
'active_agents': len(set(
agent_id.split(':')[0]
for agent_id in self.metrics['interaction_counts'].keys()
))
}
def _check_anomalies(self):
window = 100
if len(self.metrics['response_times']) >= window:
recent_resp_times = list(self.metrics['response_times'])[-window:]
recent_success_rates = list(self.metrics['success_rates'])[-window:]
if np.mean(recent_resp_times) > self.thresholds['response_time']:
self._create_alert('High average response time detected')
if np.mean(recent_success_rates) < self.thresholds['success_rate']:
self._create_alert('Low success rate detected')
def _create_alert(self, message: str):
self.alerts.append({
'message': message,
'timestamp': time.time(),
'metrics': self.get_performance_summary()
})
Additional Resources
- Building Decentralized Agent Networks: https://arxiv.org/abs/2204.09649
- Multi-Agent Systems and Emergent Behaviors: https://arxiv.org/abs/2208.07405
- Consensus Mechanisms in Agent Networks: https://arxiv.org/abs/2201.08765
- Resource Management in Distributed Agent Systems: https://arxiv.org/abs/2203.12458
- Task Decomposition and Planning for Autonomous Agents: https://arxiv.org/abs/2205.14562
Note: These URLs are examples of the type of papers you might find. For the most current research, please search academic databases and preprint servers.