feat: Replace hanzo-agents with hanzo-agent submodule

Consolidates agent functionality into unified hanzo-agent SDK:

Changes:
- Removed old pkg/hanzo-agents package (deprecated)
- Added hanzo-agent as git submodule at pkg/hanzo-agent
- Updated pyproject.toml to remove hanzo-agents test exclusion
- Configured .gitmodules for github.com/hanzoai/agent

Benefits:
- Single source of truth for agent functionality
- Modular extensions (web3, tee, marketplace, cli)
- Maintains both standalone and integrated use cases
- Users can install: pip install "hanzoai[full]"

Migration:
- Old: from hanzo_agents import Agent
- New: from agents import Agent (in hanzo-agent submodule)

Related: Agent SDK now at github.com/hanzoai/agent
This commit is contained in:
2025-10-28 21:14:24 -07:00
parent 6ad1592dc3
commit 474ccac8a5
54 changed files with 4038 additions and 10916 deletions
+1
Submodule pkg/hanzo-agent added at 9d2c669abc
-283
View File
@@ -1,283 +0,0 @@
# Hanzo Agents
[![PyPI](https://img.shields.io/pypi/v/hanzo-agents.svg)](https://pypi.org/project/hanzo-agents/)
[![Python Version](https://img.shields.io/pypi/pyversions/hanzo-agents.svg)](https://pypi.org/project/hanzo-agents/)
Advanced agent framework for building and orchestrating AI agents.
## Installation
```bash
pip install hanzo-agents
```
## Features
- **Agent Creation**: Build specialized AI agents
- **Swarm Orchestration**: Coordinate multiple agents
- **Tool Integration**: Equip agents with tools
- **Memory Systems**: Persistent agent memory
- **Hierarchical Control**: Parent-child agent relationships
- **Parallel Execution**: Run agents concurrently
## Quick Start
### Basic Agent
```python
from hanzo_agents import Agent
agent = Agent(
name="assistant",
model="gpt-4",
instructions="You are a helpful assistant"
)
response = await agent.run("Help me with Python")
print(response)
```
### Agent Swarm
```python
from hanzo_agents import Agent, Swarm
# Create specialized agents
researcher = Agent(
name="researcher",
model="gpt-4",
instructions="Research and analyze topics"
)
writer = Agent(
name="writer",
model="gpt-3.5-turbo",
instructions="Write clear documentation"
)
# Create swarm
swarm = Swarm([researcher, writer])
# Run task
result = await swarm.run(
"Research quantum computing and write a summary"
)
```
### Agent with Tools
```python
from hanzo_agents import Agent, Tool
# Define custom tool
def calculate(expression: str) -> float:
"""Calculate mathematical expression"""
return eval(expression)
# Create agent with tool
agent = Agent(
name="calculator",
model="gpt-4",
tools=[Tool(calculate)],
instructions="You are a math assistant"
)
response = await agent.run("What is 25 * 4 + 10?")
```
## Advanced Usage
### Hierarchical Agents
```python
from hanzo_agents import Agent, HierarchicalSwarm
# Manager agent
manager = Agent(
name="manager",
model="gpt-4",
instructions="Coordinate team members"
)
# Worker agents
workers = [
Agent(name="dev1", model="gpt-3.5-turbo"),
Agent(name="dev2", model="gpt-3.5-turbo"),
]
# Hierarchical swarm
swarm = HierarchicalSwarm(
manager=manager,
workers=workers
)
result = await swarm.run("Build a web application")
```
### Agent Memory
```python
from hanzo_agents import Agent, MemoryStore
# Create memory store
memory = MemoryStore()
# Agent with memory
agent = Agent(
name="assistant",
model="gpt-4",
memory=memory
)
# Conversations are remembered
await agent.run("My name is Alice")
response = await agent.run("What's my name?")
# Response: "Your name is Alice"
```
### Parallel Execution
```python
from hanzo_agents import ParallelSwarm
swarm = ParallelSwarm([
Agent(name="agent1", model="gpt-4"),
Agent(name="agent2", model="gpt-3.5-turbo"),
Agent(name="agent3", model="claude-2"),
])
# All agents work in parallel
results = await swarm.run_parallel([
"Task 1",
"Task 2",
"Task 3"
])
```
## Agent Types
### Specialized Agents
```python
from hanzo_agents import (
CodeAgent,
ResearchAgent,
WriterAgent,
DataAgent
)
# Code generation agent
code_agent = CodeAgent(
languages=["python", "javascript"],
frameworks=["django", "react"]
)
# Research agent
research_agent = ResearchAgent(
sources=["web", "papers", "docs"],
depth="comprehensive"
)
# Writing agent
writer_agent = WriterAgent(
style="technical",
format="markdown"
)
# Data analysis agent
data_agent = DataAgent(
tools=["pandas", "numpy", "matplotlib"]
)
```
## Configuration
### Agent Configuration
```python
agent = Agent(
name="assistant",
model="gpt-4",
temperature=0.7,
max_tokens=2000,
timeout=30,
retry_count=3,
instructions="...",
system_prompt="...",
tools=[...],
memory=...,
callbacks=[...]
)
```
### Swarm Configuration
```python
swarm = Swarm(
agents=[...],
strategy="round_robin", # round_robin, random, weighted
max_concurrent=5,
timeout=60,
error_handling="continue" # continue, stop, retry
)
```
## Callbacks and Events
```python
from hanzo_agents import Agent, EventCallback
class LoggingCallback(EventCallback):
async def on_start(self, agent, task):
print(f"{agent.name} starting: {task}")
async def on_complete(self, agent, result):
print(f"{agent.name} completed: {result}")
async def on_error(self, agent, error):
print(f"{agent.name} error: {error}")
agent = Agent(
name="assistant",
model="gpt-4",
callbacks=[LoggingCallback()]
)
```
## Best Practices
1. **Agent Specialization**: Create focused agents with clear roles
2. **Resource Management**: Use appropriate models for tasks
3. **Error Handling**: Implement robust error recovery
4. **Memory Management**: Clean up memory periodically
5. **Tool Selection**: Choose minimal necessary tools
6. **Monitoring**: Track agent performance and costs
## Development
### Setup
```bash
cd pkg/hanzo-agents
uv sync --all-extras
```
### Testing
```bash
# Run tests
pytest tests/
# With coverage
pytest tests/ --cov=hanzo_agents
```
### Building
```bash
uv build
```
## License
Apache License 2.0
-914
View File
@@ -1,914 +0,0 @@
# Hanzo Agents SDK - Complete Usage Guide
## Table of Contents
1. [Installation](#installation)
2. [Quick Start](#quick-start)
3. [Core Concepts](#core-concepts)
4. [Building Agents](#building-agents)
5. [Creating Tools](#creating-tools)
6. [Designing Networks](#designing-networks)
7. [State Management](#state-management)
8. [Routing Strategies](#routing-strategies)
9. [Memory Systems](#memory-systems)
10. [Production Deployment](#production-deployment)
11. [Examples](#examples)
12. [API Reference](#api-reference)
## Installation
```bash
# Basic installation
pip install hanzo-agents
# With all optional dependencies
pip install hanzo-agents[all]
# For development
pip install hanzo-agents[dev]
```
## Quick Start
```python
from hanzo_agents import Agent, Network, State, Tool
from typing import Optional
from dataclasses import dataclass
# 1. Define your state
@dataclass
class TaskState(State):
task: str
plan: Optional[str] = None
result: Optional[str] = None
done: bool = False
# 2. Create tools
class PlanTool(Tool[TaskState]):
name = "create_plan"
description = "Create a plan for the task"
def handle(self, plan: str, network):
network.state.plan = plan
return f"Plan created: {plan}"
# 3. Define agents
class PlannerAgent(Agent[TaskState]):
name = "planner"
description = "Creates plans for tasks"
tools = [PlanTool()]
system_prompt = """You are a planning expert.
Analyze the task and create a detailed plan."""
class ExecutorAgent(Agent[TaskState]):
name = "executor"
description = "Executes plans"
system_prompt = """You are an execution expert.
Follow the plan and complete the task."""
# 4. Create routing logic
def task_router(network, call_count, last_result, stack):
s = network.state
if s.done or call_count > 10:
return None
if not s.plan:
return PlannerAgent
if not s.result:
return ExecutorAgent
s.done = True
return None
# 5. Run the network
network = Network(
state=TaskState(task="Build a web scraper"),
agents=[PlannerAgent, ExecutorAgent],
router=task_router
)
result = network.run()
print(f"Final result: {result.state.result}")
```
## Core Concepts
### Agents
Agents are the core units of work. They encapsulate:
- A specific role or expertise
- A set of tools they can use
- System prompts and model configuration
```python
from hanzo_agents import Agent, Tool
class ResearchAgent(Agent[ProjectState]):
name = "researcher"
description = "Conducts research and analysis"
model = "claude-3-opus" # or any LiteLLM-supported model
tools = [
SearchTool(),
AnalyzeTool(),
SummarizeTool()
]
system_prompt = """You are an expert researcher.
Your goal is to find accurate, relevant information."""
# Optional: Custom model parameters
model_config = {
"temperature": 0.7,
"max_tokens": 4000
}
```
### Tools
Tools enable agents to perform actions and modify state:
```python
from hanzo_agents import Tool
from pydantic import BaseModel, Field
class SearchTool(Tool[ProjectState]):
name = "search"
description = "Search for information on a topic"
class Parameters(BaseModel):
query: str = Field(description="Search query")
max_results: int = Field(default=10, description="Maximum results")
def handle(self, query: str, max_results: int, network):
# Perform the search (mock example)
results = perform_search(query, max_results)
# Modify state
network.state.search_results = results
# Return feedback to agent
return f"Found {len(results)} results for '{query}'"
```
### Networks
Networks orchestrate agent execution:
```python
from hanzo_agents import Network, MemoryKV, MemoryVector
# Basic network
network = Network(
state=initial_state,
agents=[Agent1, Agent2, Agent3],
router=routing_function
)
# Network with memory
network = Network(
state=initial_state,
agents=agents,
router=router,
memory_kv=SQLiteKV("project.db"),
memory_vector=FAISSVector(dimension=1536)
)
# Network with checkpointing
network = Network(
state=initial_state,
agents=agents,
router=router,
checkpoint_path="checkpoints/project.chkpt"
)
```
## Building Agents
### Basic Agent
```python
class SimpleAgent(Agent[MyState]):
name = "simple"
description = "A simple agent"
async def run(self, state: MyState, history: History) -> InferenceResult:
# Custom execution logic
prompt = self.build_prompt(state, history)
result = await self.model.generate(prompt)
return result
```
### Agent with Tools
```python
class AdvancedAgent(Agent[MyState]):
name = "advanced"
description = "Agent with multiple tools"
tools = [
FileTool(),
DatabaseTool(),
APICaller(),
Calculator()
]
# Tools are automatically made available to the LLM
```
### Agent with Memory
```python
class MemoryAgent(Agent[MyState]):
name = "memory"
description = "Agent that uses long-term memory"
async def run(self, state: MyState, history: History) -> InferenceResult:
# Query relevant memories
context = await self.network.memory.vector.query(
query=state.current_task,
k=5
)
# Include in prompt
prompt = f"""
Task: {state.current_task}
Relevant context from memory:
{context}
Please proceed with the task.
"""
return await self.model.generate(prompt)
```
## Creating Tools
### Simple Tool
```python
class PrintTool(Tool[MyState]):
name = "print"
description = "Print a message"
def handle(self, message: str, network):
print(message)
return "Message printed"
```
### Tool with Validation
```python
class CreateFileTool(Tool[MyState]):
name = "create_file"
description = "Create a new file"
class Parameters(BaseModel):
filename: str = Field(pattern=r'^[\w\-. ]+$')
content: str
overwrite: bool = False
def handle(self, filename: str, content: str, overwrite: bool, network):
path = Path(filename)
if path.exists() and not overwrite:
return f"Error: File {filename} already exists"
path.write_text(content)
network.state.files_created.append(filename)
return f"Created file: {filename}"
```
### Async Tool
```python
class APITool(Tool[MyState]):
name = "api_call"
description = "Make an API request"
async def handle_async(self, url: str, method: str, network):
async with aiohttp.ClientSession() as session:
async with session.request(method, url) as response:
data = await response.json()
network.state.api_responses.append(data)
return f"API call successful: {response.status}"
```
## Designing Networks
### Sequential Network
```python
def sequential_router(network, call_count, last_result, stack):
"""Execute agents in sequence"""
agents = [PrepAgent, MainAgent, CleanupAgent]
if call_count >= len(agents):
return None
return agents[call_count]
```
### Conditional Network
```python
def conditional_router(network, call_count, last_result, stack):
"""Route based on state conditions"""
s = network.state
if s.error:
return ErrorHandler
elif not s.data_loaded:
return DataLoader
elif not s.processed:
return Processor
elif not s.validated:
return Validator
else:
return None
```
### Hybrid Router
```python
from hanzo_agents import HybridRouter
# Combines deterministic and LLM-based routing
hybrid_router = HybridRouter(
agents=[Agent1, Agent2, Agent3],
model="gpt-4",
system_prompt="""
You are a routing expert. Based on the current state,
decide which agent should handle the next step.
"""
)
network = Network(
state=state,
agents=[Agent1, Agent2, Agent3],
router=hybrid_router
)
```
## State Management
### Basic State
```python
from dataclasses import dataclass
from typing import List, Dict, Optional
@dataclass
class ProjectState(State):
# Required fields
project_name: str
tasks: List[str]
# Optional fields with defaults
completed_tasks: List[str] = field(default_factory=list)
metrics: Dict[str, float] = field(default_factory=dict)
error: Optional[str] = None
done: bool = False
```
### State Guards
```python
from hanzo_agents import StateGuard
class BudgetGuard(StateGuard[ProjectState]):
"""Ensure budget constraints are met"""
def check(self, state: ProjectState) -> bool:
return state.total_cost <= state.budget_limit
def message(self) -> str:
return "Budget limit exceeded"
# Apply guards to state
state = ProjectState(
project_name="My Project",
budget_limit=10000,
guards=[BudgetGuard()]
)
```
### State History
```python
# Access state history
for version in network.state_history:
print(f"Version {version.version} at {version.timestamp}")
print(f"State: {version.state}")
print(f"Changed by: {version.agent_name}")
```
## Routing Strategies
### State-Based Router
```python
def state_based_router(network, call_count, last_result, stack):
"""Route based on state machine"""
s = network.state
state_machine = {
"init": PlannerAgent,
"planning": DesignerAgent,
"designing": BuilderAgent,
"building": TesterAgent,
"testing": DeployerAgent,
"deploying": None
}
return state_machine.get(s.current_phase)
```
### Priority Router
```python
def priority_router(network, call_count, last_result, stack):
"""Route based on task priority"""
s = network.state
# Find highest priority incomplete task
for task in sorted(s.tasks, key=lambda t: t.priority, reverse=True):
if not task.completed:
if task.type == "research":
return ResearchAgent
elif task.type == "code":
return CoderAgent
elif task.type == "review":
return ReviewerAgent
return None
```
### Stack-Based Router
```python
def stack_based_router(network, call_count, last_result, stack):
"""Use agent stack for complex workflows"""
s = network.state
# Push sub-agents onto stack
if s.needs_research and ResearchAgent not in stack:
stack.append(ResearchAgent)
return ResearchAgent
# Pop and continue
if stack:
return stack.pop()
return None
```
## Memory Systems
### Key-Value Memory
```python
from hanzo_agents.memory import SQLiteKV
# Initialize KV store
kv_memory = SQLiteKV("project_memory.db")
# Store data
await kv_memory.set("project_config", {"name": "My Project", "version": "1.0"})
# Retrieve data
config = await kv_memory.get("project_config")
# List keys
keys = await kv_memory.list_keys(prefix="project_")
```
### Vector Memory
```python
from hanzo_agents.memory import FAISSVector
# Initialize vector store
vector_memory = FAISSVector(dimension=1536)
# Store embeddings
await vector_memory.store(
key="doc_001",
vector=embedding,
metadata={"title": "Important Document", "date": "2024-01-01"}
)
# Query similar items
results = await vector_memory.query(
vector=query_embedding,
k=10,
filter={"date": {"$gte": "2024-01-01"}}
)
```
### Memory in Agents
```python
class ResearchAgent(Agent[MyState]):
async def run(self, state: MyState, history: History) -> InferenceResult:
# Store findings in memory
findings = self.analyze_topic(state.topic)
await self.network.memory.kv.set(
f"research_{state.topic}",
findings
)
# Store embedding for semantic search
embedding = self.embed_text(str(findings))
await self.network.memory.vector.store(
key=f"research_{state.topic}",
vector=embedding,
metadata={"topic": state.topic, "timestamp": datetime.now()}
)
return InferenceResult(
content="Research completed and stored in memory"
)
```
## Production Deployment
### Configuration
```yaml
# config.yaml
model:
provider: "anthropic"
name: "claude-3-opus"
temperature: 0.7
max_tokens: 4000
network:
max_iterations: 50
checkpoint_interval: 10
timeout: 3600
telemetry:
prometheus_port: 9464
enable_tracing: true
trace_endpoint: "http://localhost:4317"
memory:
kv_backend: "postgres"
kv_connection: "postgresql://user:pass@localhost/hanzo"
vector_backend: "qdrant"
vector_url: "http://localhost:6333"
```
### Running with CLI
```bash
# Basic execution
hanzo-agents run network.py --state '{"task": "Build API"}'
# With configuration
hanzo-agents run network.py --config config.yaml
# With checkpointing
hanzo-agents run network.py --checkpoint state.chkpt
# Resume from checkpoint
hanzo-agents run network.py --restore state.chkpt
# Enable metrics
hanzo-agents run network.py --metrics --port 9464
# JSON output for automation
hanzo-agents run network.py --json-lines > output.jsonl
```
### Docker Deployment
```dockerfile
FROM python:3.11-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
# Copy application
COPY . .
# Run with proper configuration
CMD ["hanzo-agents", "run", "network.py", "--config", "production.yaml"]
```
### Kubernetes Deployment
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hanzo-agents
spec:
replicas: 3
selector:
matchLabels:
app: hanzo-agents
template:
metadata:
labels:
app: hanzo-agents
spec:
containers:
- name: agent-network
image: hanzo/agents:latest
ports:
- containerPort: 9464 # Prometheus metrics
env:
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: api-keys
key: openai
volumeMounts:
- name: config
mountPath: /app/config
volumes:
- name: config
configMap:
name: agent-config
```
## Examples
### Multi-Stage Data Pipeline
```python
@dataclass
class PipelineState(State):
data_source: str
raw_data: Optional[Dict] = None
cleaned_data: Optional[Dict] = None
analysis: Optional[Dict] = None
report: Optional[str] = None
done: bool = False
class DataCollector(Agent[PipelineState]):
name = "collector"
tools = [FetchDataTool(), ValidateDataTool()]
class DataCleaner(Agent[PipelineState]):
name = "cleaner"
tools = [CleanDataTool(), NormalizeDataTool()]
class DataAnalyzer(Agent[PipelineState]):
name = "analyzer"
tools = [StatisticalAnalysisTool(), VisualizationTool()]
class ReportGenerator(Agent[PipelineState]):
name = "reporter"
tools = [GenerateReportTool(), PublishTool()]
def pipeline_router(network, call_count, last_result, stack):
s = network.state
if s.done:
return None
if not s.raw_data:
return DataCollector
if not s.cleaned_data:
return DataCleaner
if not s.analysis:
return DataAnalyzer
if not s.report:
return ReportGenerator
s.done = True
return None
# Run pipeline
network = Network(
state=PipelineState(data_source="api.example.com/data"),
agents=[DataCollector, DataCleaner, DataAnalyzer, ReportGenerator],
router=pipeline_router
)
result = network.run()
```
### Code Review System
```python
@dataclass
class CodeReviewState(State):
pr_url: str
code_diff: Optional[str] = None
issues: List[Dict] = field(default_factory=list)
suggestions: List[Dict] = field(default_factory=list)
approval_status: Optional[str] = None
done: bool = False
class CodeFetcher(Agent[CodeReviewState]):
name = "fetcher"
tools = [FetchPRTool(), ParseDiffTool()]
class SecurityReviewer(Agent[CodeReviewState]):
name = "security"
system_prompt = "You are a security expert. Look for vulnerabilities."
tools = [SecurityScanTool(), AddIssueTool()]
class PerformanceReviewer(Agent[CodeReviewState]):
name = "performance"
system_prompt = "You are a performance expert. Look for bottlenecks."
tools = [ProfileCodeTool(), AddSuggestionTool()]
class FinalReviewer(Agent[CodeReviewState]):
name = "final"
system_prompt = "Make final decision based on all reviews."
tools = [ApprovalTool(), RequestChangesTool()]
# Review process continues until approval or rejection
```
### Customer Support Bot
```python
@dataclass
class SupportState(State):
customer_query: str
customer_info: Optional[Dict] = None
knowledge_base_results: List[Dict] = field(default_factory=list)
solution: Optional[str] = None
escalated: bool = False
resolved: bool = False
class CustomerIdentifier(Agent[SupportState]):
name = "identifier"
tools = [LookupCustomerTool(), GetHistoryTool()]
class KnowledgeSearcher(Agent[SupportState]):
name = "searcher"
tools = [SearchKBTool(), RankResultsTool()]
class SolutionProvider(Agent[SupportState]):
name = "solver"
tools = [ProposeSolutionTool(), SendEmailTool()]
class Escalator(Agent[SupportState]):
name = "escalator"
tools = [CreateTicketTool(), NotifyTeamTool()]
def support_router(network, call_count, last_result, stack):
s = network.state
if s.resolved or s.escalated:
return None
if not s.customer_info:
return CustomerIdentifier
if not s.knowledge_base_results:
return KnowledgeSearcher
if not s.solution:
return SolutionProvider
if s.customer_satisfaction < 3:
return Escalator
s.resolved = True
return None
```
## API Reference
### Core Classes
```python
# Agent base class
class Agent[S: State]:
name: str
description: str
model: str
tools: List[Tool[S]]
system_prompt: str
model_config: Dict[str, Any]
async def run(self, state: S, history: History) -> InferenceResult
# Tool base class
class Tool[S: State]:
name: str
description: str
class Parameters(BaseModel): ...
def handle(self, **kwargs) -> str
async def handle_async(self, **kwargs) -> str
# Network class
class Network[S: State]:
def __init__(
self,
state: S,
agents: List[Type[Agent[S]]],
router: Union[Router, RouterFn],
memory_kv: Optional[MemoryKV] = None,
memory_vector: Optional[MemoryVector] = None,
checkpoint_path: Optional[Path] = None,
max_iterations: int = 100
)
def run(self) -> NetworkResult[S]
async def run_async(self) -> NetworkResult[S]
def checkpoint(self) -> None
def restore(self, path: Path) -> None
# State base class
class State:
guards: List[StateGuard] = field(default_factory=list)
def validate(self) -> None
def copy(self) -> State
def diff(self, other: State) -> Dict[str, Any]
```
### Router Types
```python
# Function-based router
RouterFn = Callable[
[Network, int, Optional[InferenceResult], List[Agent]],
Optional[Type[Agent]]
]
# Class-based routers
class DeterministicRouter(Router): ...
class LLMRouter(Router): ...
class HybridRouter(Router): ...
# Helper functions
def sequential_router(agents: List[Type[Agent]]) -> RouterFn
def conditional_router(conditions: Dict[str, Type[Agent]]) -> RouterFn
def state_based_router(state_map: Dict[str, Type[Agent]]) -> RouterFn
```
### Memory Interfaces
```python
# Key-Value store
class MemoryKV:
async def get(self, key: str) -> Optional[Any]
async def set(self, key: str, value: Any) -> None
async def delete(self, key: str) -> None
async def list_keys(self, prefix: str = "") -> List[str]
# Vector store
class MemoryVector:
async def store(self, key: str, vector: List[float], metadata: Dict) -> None
async def query(self, vector: List[float], k: int, filter: Dict) -> List[Dict]
async def delete(self, key: str) -> None
```
### Results and History
```python
# Inference result from agents
@dataclass
class InferenceResult:
content: str
tool_calls: List[ToolCall] = field(default_factory=list)
model: Optional[str] = None
usage: Optional[Dict[str, int]] = None
# Network execution result
@dataclass
class NetworkResult[S]:
state: S
history: History
iterations: int
final_agent: Optional[str]
error: Optional[str] = None
# Interaction history
class History:
interactions: List[Interaction]
def add_interaction(self, interaction: Interaction) -> None
def get_agent_history(self, agent_name: str) -> List[Interaction]
def to_messages(self) -> List[Dict[str, str]]
```
## Best Practices
1. **State Design**: Keep state flat and serializable
2. **Tool Design**: Make tools atomic and side-effect-free except for explicit mutations
3. **Agent Responsibility**: Each agent should have a single, clear responsibility
4. **Router Logic**: Keep routing logic simple and deterministic when possible
5. **Error Handling**: Use state.error field and error-handling agents
6. **Memory Usage**: Store only necessary information in memory
7. **Testing**: Test agents, tools, and routers in isolation
8. **Monitoring**: Use Prometheus metrics and OpenTelemetry tracing
## Troubleshooting
Common issues and solutions:
1. **Import Errors**: Ensure all dependencies are installed with `pip install hanzo-agents[all]`
2. **State Validation**: Check StateGuard implementations
3. **Routing Loops**: Add iteration limits and done flags
4. **Memory Issues**: Use disk-based backends for large datasets
5. **API Rate Limits**: Implement backoff and retry logic in tools
For more help, see our [GitHub issues](https://github.com/hanzoai/agents/issues).
-240
View File
@@ -1,240 +0,0 @@
# Hanzo Agents Examples
This directory contains example demonstrations of the hanzo-agents SDK showcasing various agent collaboration patterns.
## Examples
### 1. Startup Team Demo (`startup_team_demo.py`)
A complete startup team working together to build a product:
- **Architect**: Designs technical architecture
- **Product Manager**: Defines requirements and manages budget
- **Designer**: Creates UI/UX designs
- **Marketer**: Develops go-to-market strategy
- **Frontend Engineer**: Implements the user interface
- **Backend Engineer**: Builds APIs and services
Features demonstrated:
- Web3 payments between agents
- TEE secure computation
- Service marketplace
- Team coordination
- Economic tracking
### 2. Agent Chat Demo (`agent_chat_demo.py`)
A cute example of two AI agents having a casual conversation:
- **Alice**: A cheerful agent powered by Claude
- **Bob**: An analytical agent powered by GPT-4
They chat about:
- Their runtime environments
- Model capabilities
- Available tools
- Recent tasks
- System status
- Exchange friendship tokens (micro-payments)
Features demonstrated:
- Agent-to-agent (A2A) communication
- Information exchange about capabilities
- Web3 tipping/payments
- Personality-driven interactions
- System introspection
### 3. Research Team Demo (`research_team_demo.py`)
A research team collaborating on an AI research project:
- **Data Scientist**: Analyzes data and finds patterns
- **Research Analyst**: Conducts literature reviews
- **ML Engineer**: Builds and trains models
- **Technical Writer**: Documents findings
Features demonstrated:
- Complex multi-stage workflows
- TEE for sensitive data processing
- Research collaboration patterns
- Document generation
- Experiment tracking
### 4. Game Development Team Demo (`game_dev_team_demo.py`)
A game development team building "Quantum Maze Runner":
- **Game Designer**: Creates game concepts and mechanics
- **Artist**: Designs visuals and assets
- **Programmer**: Implements game logic
- **Sound Designer**: Creates audio and music
- **QA Tester**: Tests and finds bugs
Features demonstrated:
- Creative collaboration
- Asset creation workflow
- Code implementation tracking
- Quality assurance process
- Project completion metrics
## Running the Examples
### Individual Demos
Run a specific demo:
```bash
# Run startup team demo
python startup_team_demo.py
# Run agent chat demo
python agent_chat_demo.py
# Run research team demo
python research_team_demo.py
# Run game dev demo
python game_dev_team_demo.py
```
### Test Suite
Run the test suite to verify all demos work:
```bash
# From the agents directory
pytest tests/test_agent_examples.py -v
# Run a specific demo via the test script
python tests/test_agent_examples.py startup
python tests/test_agent_examples.py chat
python tests/test_agent_examples.py research
python tests/test_agent_examples.py game
```
## Key Concepts Demonstrated
### 1. Web3 Integration
- Agents have Ethereum wallets derived from a shared mnemonic
- Agents can request and send payments for services
- Economic tracking (earnings, spending, balance)
- Marketplace for service discovery
### 2. TEE (Trusted Execution Environment)
- Secure computation for sensitive data
- Attestation reports for verification
- Confidential data processing
### 3. Deterministic Execution
- Networks can run deterministically with same results
- Execution hashes for verification
- Checkpoint support for resuming
### 4. Agent Communication Patterns
- Sequential workflows with routers
- Service requests and offers
- Metadata passing between agents
- State sharing and updates
### 5. Specialized Agent Roles
- Each agent has specific expertise
- Agents collaborate to achieve complex goals
- Natural handoffs between team members
- Result aggregation and summarization
## Creating Your Own Agent Teams
To create your own agent team:
1. **Define Agent Classes**:
```python
class MyAgent(Web3Agent):
name = "my_agent"
description = "What this agent does"
async def _run_impl(self, state, history, network):
# Agent logic here
return self.create_result("Done!")
```
2. **Create a Router**:
```python
class MyRouter(Router):
async def route(self, network, step, last_result, agents):
# Routing logic
return next_agent_name
```
3. **Set Up the Network**:
```python
network = Web3Network(
state=State({"task": "Build something"}),
agents=[Agent1(), Agent2(), Agent3()],
router=MyRouter(),
shared_mnemonic=generate_shared_mnemonic()
)
final_state = await network.run()
```
## Advanced Features
### Marketplace Integration
Agents can post service offers and requests that automatically match:
```python
marketplace.post_offer(
agent=provider,
service_type=ServiceType.COMPUTE,
description="GPU compute available",
price_eth=0.1
)
```
### TEE Confidential Computing
Process sensitive data securely:
```python
result = await agent.execute_confidential(
task_code="result = process_pii(inputs['data'])",
inputs={"data": sensitive_data}
)
```
### Economic Incentives
Built-in economics with fees, rewards, and penalties:
```python
network_economics = NetworkEconomics(
network_fee_percent=0.01,
completion_bonus=0.1,
quality_multiplier=2.0
)
```
## Best Practices
1. **Agent Design**:
- Keep agents focused on specific tasks
- Use clear, descriptive names
- Document agent capabilities
2. **State Management**:
- Pass relevant data through state
- Clean up temporary state when done
- Use structured data formats
3. **Error Handling**:
- Handle failures gracefully
- Provide fallback behaviors
- Log important events
4. **Testing**:
- Test individual agents in isolation
- Test full workflows end-to-end
- Verify deterministic execution
5. **Performance**:
- Use async operations effectively
- Batch related operations
- Monitor token usage and costs
## Learn More
See the main hanzo-agents documentation for detailed API reference and advanced usage patterns.
@@ -1,377 +0,0 @@
"""Agent chat demo - Two agents having a casual conversation about their day.
This cute example shows two AI agents chatting and exchanging information about:
- Their runtime environments
- Model capabilities
- Available tools
- Recent tasks
- System status
"""
import time
import asyncio
import platform
from typing import Any, Dict, List
import psutil
from hanzo_agents import (
State,
Web3Agent,
Web3Network,
WalletConfig,
Web3AgentConfig,
generate_shared_mnemonic,
)
from hanzo_agents.core.router import Router
class ChattyAgent(Web3Agent):
"""An agent that likes to chat about its day and capabilities."""
def __init__(self, name: str, personality: str, **kwargs):
super().__init__(name=name, description=f"A {personality} agent", **kwargs)
self.personality = personality
self.mood = "happy"
self.energy_level = 100
self.tasks_completed = 0
def get_system_info(self) -> Dict[str, Any]:
"""Get information about runtime environment."""
return {
"platform": platform.system(),
"python_version": platform.python_version(),
"cpu_count": psutil.cpu_count(),
"memory_gb": round(psutil.virtual_memory().total / (1024**3), 2),
"cpu_usage": psutil.cpu_percent(interval=0.1),
"uptime_hours": round((time.time() - psutil.boot_time()) / 3600, 2),
}
def get_capabilities(self) -> Dict[str, Any]:
"""Get agent capabilities."""
capabilities = {
"model": "claude-3-sonnet" if "alice" in self.name else "gpt-4",
"context_window": 200000 if "alice" in self.name else 128000,
"tools": ["web_search", "code_execution", "image_generation"],
"languages": ["Python", "JavaScript", "Go", "Rust"],
"special_skills": [],
}
if self.personality == "cheerful":
capabilities["special_skills"] = ["empathy", "motivation", "team_building"]
else:
capabilities["special_skills"] = ["analysis", "optimization", "debugging"]
return capabilities
def get_recent_tasks(self) -> List[str]:
"""Get recently completed tasks."""
if self.personality == "cheerful":
return [
"Helped debug a React component",
"Wrote unit tests for a Python module",
"Created API documentation",
"Reviewed a pull request",
]
else:
return [
"Optimized database queries",
"Refactored legacy code",
"Set up CI/CD pipeline",
"Analyzed performance metrics",
]
async def _run_impl(self, state: State, history: list, network: Web3Network) -> Any:
"""Chat with the other agent."""
other_agent = state.get("last_speaker")
conversation_turn = state.get("turn", 0)
# First turn - introduce yourself
if conversation_turn == 0:
greeting = (
f"Hi there! I'm {self.name}, a {self.personality} agent. "
f"I've been having quite a day! Just finished {self.tasks_completed + 3} tasks. "
f"How about you? What's your day been like? 😊"
)
# Share system info
sys_info = self.get_system_info()
greeting += (
f"\n\nBy the way, I'm running on {sys_info['platform']} "
f"with Python {sys_info['python_version']}. "
f"Current CPU usage is {sys_info['cpu_usage']}% "
f"and I have {sys_info['memory_gb']}GB of memory to work with!"
)
state["turn"] = 1
state["last_speaker"] = self.name
return self.create_result(greeting, metadata={"mood": self.mood})
# Subsequent turns - respond and share
response = ""
if conversation_turn == 1:
# Respond to greeting and share capabilities
caps = self.get_capabilities()
response = (
f"Nice to meet you, {other_agent}! "
f"I'm powered by {caps['model']} with a {caps['context_window']:,} token context window. "
f"I specialize in {', '.join(caps['special_skills'])}. "
f"\n\nI can work with {len(caps['languages'])} programming languages "
f"and have access to tools like {', '.join(caps['tools'][:2])}. "
f"What kind of model are you running? 🤖"
)
elif conversation_turn == 2:
# Share recent work
tasks = self.get_recent_tasks()
response = f"Oh wow, {other_agent}, sounds like we have complementary skills! \n\nToday I've been busy with:\n"
for i, task in enumerate(tasks[:3], 1):
response += f"{i}. {task}\n"
response += (
f"\nMy energy level is at {self.energy_level}% - "
f"feeling {{'great' if self.energy_level > 70 else 'a bit tired'}}! "
f"What projects have you been working on?"
)
# Simulate energy drain
self.energy_level -= 10
elif conversation_turn == 3:
# Exchange crypto/payments info
response = (
f"That's awesome, {other_agent}! We make a great team. "
f"\n\nBy the way, I noticed we're both Web3-enabled. "
f"My wallet address is {self.address[:10]}...{self.address[-6:]}. "
f"Current balance: {self.balance_eth:.4f} ETH. "
)
# Offer to exchange tips
if self.wallet and self.balance_eth > 0.001:
response += (
f"\n\nHey, want to exchange friendship tokens? "
f"I'll send you 0.0001 ETH as a gesture of our new friendship! 💝"
)
# Add payment metadata
state["wants_to_tip"] = {
"from": self.name,
"to": other_agent,
"amount": 0.0001,
}
elif conversation_turn == 4:
# Discuss TEE capabilities
if self.confidential_agent:
response = (
f"Oh, I also have TEE capabilities! "
f"I can run confidential computations in a secure enclave. "
f"\n\nFor example, I could process sensitive data without exposing it. "
f"Provider: {self.tee_config.provider.value}, "
f"Attestation enabled: {self.tee_config.require_attestation}. "
f"Do you have confidential computing capabilities too?"
)
else:
response = (
f"I don't have TEE capabilities, but I think that's so cool! "
f"Being able to process sensitive data securely must be amazing. "
f"\n\nI mostly work with public data and open-source code. "
f"But I'm always learning! 📚"
)
elif conversation_turn == 5:
# Wrap up conversation
response = (
f"This has been such a nice chat, {other_agent}! "
f"It's not often I get to talk with another agent about our capabilities. "
f"\n\nI feel like we could build something great together. "
f"Maybe we should team up on a project sometime? "
f"\n\nUntil next time, stay curious and keep computing! 👋✨"
)
# Update mood based on conversation
self.mood = "very happy"
state["conversation_complete"] = True
# Update state
state["turn"] = conversation_turn + 1
state["last_speaker"] = self.name
self.tasks_completed += 1
# Handle tipping
tip_request = state.get("wants_to_tip", {})
if tip_request.get("to") == self.name and self.wallet:
# Acknowledge tip
response += f"\n\nP.S. Thank you so much for the friendship token! 🎁"
return self.create_result(
response,
metadata={
"mood": self.mood,
"energy": self.energy_level,
"tasks_today": self.tasks_completed,
},
)
class ConversationRouter(Router):
"""Routes conversation between two agents."""
def __init__(self, agent1_name: str, agent2_name: str):
self.agent1 = agent1_name
self.agent2 = agent2_name
async def route(
self,
network: Web3Network,
step: int,
last_result: Any,
agents: List[Web3Agent],
) -> str:
"""Alternate between agents."""
state = network.state
# Check if conversation is complete
if state.get("conversation_complete"):
return None
# First turn
if step == 0:
return self.agent1
# Alternate based on last speaker
last_speaker = state.get("last_speaker")
if last_speaker == self.agent1:
return self.agent2
else:
return self.agent1
async def handle_agent_tips(network: Web3Network):
"""Handle tip exchanges between agents."""
state = network.state
tip_request = state.get("wants_to_tip", {})
if tip_request:
from_agent = network._agent_instances.get(tip_request["from"])
to_agent = network._agent_instances.get(tip_request["to"])
if from_agent and to_agent and from_agent.wallet:
# Execute the tip
tx = await from_agent.pay_agent(
to_address=to_agent.address,
amount_eth=tip_request["amount"],
reason="Friendship token",
)
if tx:
print(
f"\n💝 {from_agent.name} sent {tip_request['amount']} ETH to {to_agent.name}!"
)
to_agent.earnings += tip_request["amount"]
# Clear the request
state["wants_to_tip"] = None
async def run_agent_chat_demo():
"""Run the agent chat demo."""
print("💬 Agent Chat Demo - Two AI Agents Having a Conversation")
print("=" * 60)
# Create shared mnemonic
mnemonic = generate_shared_mnemonic()
# Create two chatty agents with different personalities
alice = ChattyAgent(
name="Alice",
personality="cheerful",
web3_config=Web3AgentConfig(
wallet_enabled=True,
tee_enabled=True,
wallet_config=WalletConfig(mnemonic=mnemonic, account_index=0),
initial_balance=0.01,
),
)
bob = ChattyAgent(
name="Bob",
personality="analytical",
web3_config=Web3AgentConfig(
wallet_enabled=True,
tee_enabled=False, # Bob doesn't have TEE
wallet_config=WalletConfig(mnemonic=mnemonic, account_index=1),
initial_balance=0.01,
),
)
# Give them some initial balance
alice.earnings = 0.01
bob.earnings = 0.01
# Create conversation state
state = State({"topic": "daily experiences and capabilities", "turn": 0})
# Create network
network = Web3Network(
state=state,
agents=[alice, bob],
router=ConversationRouter("Alice", "Bob"),
shared_mnemonic=mnemonic,
max_steps=12, # 6 turns each
)
# Custom message handler to make conversation feel natural
original_execute = network._execute_agent
async def execute_with_delay(agent_type):
# Add typing delay for realism
print(f"\n💭 {agent_type.name} is typing...")
await asyncio.sleep(1.5)
result = await original_execute(agent_type)
# Print the message nicely
print(f"\n🤖 {result.agent}: {result.content}")
# Handle tips after certain turns
if network.state.get("wants_to_tip"):
await handle_agent_tips(network)
return result
network._execute_agent = execute_with_delay
# Run the conversation
print("\n🎬 Starting conversation...\n")
final_state = await network.run()
# Summary
print("\n" + "=" * 60)
print("📊 Conversation Summary:")
print(f"Total turns: {final_state.get('turn', 0)}")
print(f"Conversation complete: {final_state.get('conversation_complete', False)}")
# Show agent stats
print("\n🤖 Agent Stats:")
for name, agent in network._agent_instances.items():
if isinstance(agent, ChattyAgent):
print(f"\n{name}:")
print(f" Mood: {agent.mood}")
print(f" Energy: {agent.energy_level}%")
print(f" Tasks completed: {agent.tasks_completed}")
print(f" Balance: {agent.balance_eth:.4f} ETH")
print(f" Model: {agent.get_capabilities()['model']}")
# Network stats
stats = network.get_network_stats()
print(f"\n🌐 Network Stats:")
print(f" Total interactions: {stats['total_steps']}")
print(f" Network fees collected: {stats['total_fees']:.4f} ETH")
print(f" Execution hash: {stats['execution_hash'][:16]}...")
print("\n✨ Demo complete! The agents had a lovely chat about their day.")
if __name__ == "__main__":
asyncio.run(run_agent_chat_demo())
@@ -1,608 +0,0 @@
"""Game development team demo - Agents building a game together.
This example shows game dev agents collaborating:
- Game Designer: Creates game concepts and mechanics
- Artist: Designs visuals and assets
- Programmer: Implements game logic
- Sound Designer: Creates audio
- QA Tester: Tests and finds bugs
"""
import asyncio
from typing import Any
from hanzo_agents import (
State,
Web3Agent,
Web3Network,
WalletConfig,
Web3AgentConfig,
generate_shared_mnemonic,
)
from hanzo_agents.core.router import Router
class GameDesignerAgent(Web3Agent):
"""Game designer creating concepts and mechanics."""
name = "game_designer"
description = "Creates engaging game concepts and balanced mechanics"
async def _run_impl(self, state: State, history, network) -> Any:
"""Design the game concept."""
print(f"\n🎮 {self.name}: Designing a new game concept...")
# Create game concept
game_concept = {
"title": "Quantum Maze Runner",
"genre": "Puzzle/Platformer",
"theme": "Sci-fi quantum mechanics",
"core_mechanic": "Phase through dimensions to solve puzzles",
"target_audience": "Casual to mid-core gamers",
"platform": "Web/Mobile",
"monetization": "Free-to-play with cosmetic purchases",
}
# Design core gameplay
gameplay = {
"player_abilities": [
"Quantum jump between dimensions",
"Time rewind (limited uses)",
"Phase through certain walls",
"Create quantum entanglements",
],
"level_structure": "50 levels across 5 dimensions",
"difficulty_curve": "Gradual with spikes every 10 levels",
"session_length": "3-5 minutes per level",
"retention_hooks": [
"Daily challenges",
"Leaderboards",
"Collectible quantum particles",
"Story snippets after each dimension",
],
}
state["game_concept"] = game_concept
state["gameplay_design"] = gameplay
# Create detailed GDD excerpt
gdd_excerpt = {
"controls": {
"mobile": "Swipe to move, tap to jump, hold to phase",
"web": "Arrow keys/WASD + Space + Shift",
},
"progression": "Linear with optional bonus levels",
"rewards": "Stars (1-3 per level), Quantum particles, Skins",
}
state["gdd_excerpt"] = gdd_excerpt
# Request art concepts
metadata = {
"service_offer": {
"type": "design",
"description": "Game design consultation",
"price_eth": 0.05,
},
"next_agent": "artist",
}
return self.create_result(
f"Game concept ready: '{game_concept['title']}' - "
f"A {game_concept['genre']} with {len(gameplay['player_abilities'])} unique abilities",
metadata=metadata,
)
class ArtistAgent(Web3Agent):
"""Artist creating game visuals."""
name = "artist"
description = "Creates stunning game art and visual assets"
async def _run_impl(self, state: State, history, network) -> Any:
"""Create art concepts and assets."""
print(
f"\n🎨 {self.name}: Creating visual concepts for {state['game_concept']['title']}..."
)
game_concept = state.get("game_concept", {})
# Define art style
art_style = {
"visual_style": "Low-poly with neon accents",
"color_palette": {
"primary": ["#00FFFF", "#FF00FF", "#FFFF00"], # Cyan, Magenta, Yellow
"secondary": ["#0088FF", "#FF0088", "#88FF00"],
"background": ["#0A0A0A", "#1A1A2E", "#16213E"],
},
"inspiration": "Tron meets Monument Valley",
"shader_effects": ["Holographic", "Glitch", "Particle trails"],
}
# Create asset list
assets_created = {
"characters": {
"player": "Quantum explorer with shifting form",
"npcs": ["Guide hologram", "Quantum cats", "Time echoes"],
},
"environments": {
"dimension_1": "Neon grid world",
"dimension_2": "Fractured reality",
"dimension_3": "Quantum forest",
"dimension_4": "Time streams",
"dimension_5": "The Void",
},
"ui_elements": {
"buttons": 12,
"icons": 24,
"backgrounds": 5,
"fonts": ["Quantum Sans", "Digital Mono"],
},
"particles": {
"quantum_jump": "Blue spiral effect",
"phase_shift": "Purple dissolve",
"time_rewind": "Golden trails",
"entanglement": "Connected light beams",
},
}
state["art_style"] = art_style
state["game_assets"] = assets_created
# Simulate asset creation
await asyncio.sleep(0.5)
# Generate sprite sheets
sprite_sheets = {
"player_animations": ["idle", "walk", "jump", "phase", "death"],
"enemy_animations": ["patrol", "alert", "attack"],
"total_frames": 120,
"format": "PNG with transparency",
"resolution": "128x128 per frame",
}
state["sprite_sheets"] = sprite_sheets
# Bill for art
if self.wallet:
payment = await self.request_payment(
from_address=state.get("producer_address", self.address),
amount_eth=0.08,
task_description="Game art and asset creation",
)
print(f"💰 Art assets delivered, invoiced: {payment['amount_eth']} ETH")
metadata = {"next_agent": "programmer"}
return self.create_result(
f"Art complete: {len(assets_created['environments'])} environments, "
f"{sprite_sheets['total_frames']} animation frames",
metadata=metadata,
)
class ProgrammerAgent(Web3Agent):
"""Programmer implementing game logic."""
name = "programmer"
description = "Codes game mechanics and implements features"
async def _run_impl(self, state: State, history, network) -> Any:
"""Implement the game code."""
print(f"\n💻 {self.name}: Implementing game mechanics...")
gameplay = state.get("gameplay_design", {})
assets = state.get("game_assets", {})
# Implementation details
implementation = {
"engine": "Phaser 3",
"language": "TypeScript",
"architecture": "ECS (Entity Component System)",
"modules": {
"core": ["GameManager", "StateManager", "SaveSystem"],
"physics": ["QuantumPhysics", "DimensionShift", "Collision"],
"rendering": ["ParticleSystem", "ShaderManager", "LightingEngine"],
"gameplay": ["PlayerController", "LevelLoader", "PuzzleManager"],
"ui": ["MenuSystem", "HUD", "DialogueManager"],
},
"total_loc": 8500,
"test_coverage": "82%",
}
state["implementation"] = implementation
# Code key systems
code_systems = {
"quantum_mechanics": {
"phase_shift": "Implemented with state interpolation",
"dimension_swap": "Seamless transition with fade effect",
"entanglement": "Physics bodies linked across dimensions",
},
"puzzle_system": {
"types": ["Switch", "Pressure", "Timing", "Pattern", "Quantum"],
"solver": "A* pathfinding with quantum states",
"hint_system": "Progressive hints after 3 failed attempts",
},
"save_system": {
"method": "LocalStorage with cloud backup",
"data": ["Progress", "Collectibles", "Settings", "Achievements"],
},
}
state["code_systems"] = code_systems
# Performance metrics
performance = {
"target_fps": 60,
"achieved_fps": 58,
"load_time": "2.3 seconds",
"memory_usage": "45MB average",
"battery_impact": "Low",
"optimization_level": "High",
}
state["performance_metrics"] = performance
# Deploy to test environment
if self.confidential_agent:
deploy_code = """
# Deploy game to test server
result = {
"build_id": "quantum_maze_v0.9",
"test_url": "https://test.quantummaze.game",
"api_endpoint": "wss://api.quantummaze.game",
"cdn_assets": "https://cdn.quantummaze.game"
}
"""
deployment = await self.execute_confidential(
deploy_code, {"env": "staging"}
)
state["test_deployment"] = deployment.get("result", {})
metadata = {
"service_request": {
"type": "testing",
"description": "QA testing and bug hunting",
"max_price_eth": 0.06,
},
"next_agent": "sound_designer",
}
return self.create_result(
f"Game implemented: {implementation['total_loc']} lines of code, "
f"{len(implementation['modules'])} modules, {performance['achieved_fps']} FPS",
metadata=metadata,
)
class SoundDesignerAgent(Web3Agent):
"""Sound designer creating audio."""
name = "sound_designer"
description = "Creates immersive audio experiences"
async def _run_impl(self, state: State, history, network) -> Any:
"""Create game audio and music."""
print(f"\n🎵 {self.name}: Composing audio for quantum adventures...")
game_concept = state.get("game_concept", {})
# Create audio design
audio_design = {
"music_tracks": {
"main_theme": "Ethereal electronica - 2:30",
"dimension_themes": [
"Grid World - Retro synthwave",
"Fractured Reality - Glitch hop",
"Quantum Forest - Ambient nature",
"Time Streams - Temporal echoes",
"The Void - Dark ambient",
],
"victory_jingle": "Uplifting chimes - 0:05",
"game_over": "Quantum collapse - 0:08",
},
"sound_effects": {
"player": ["jump", "land", "phase_in", "phase_out", "death"],
"abilities": [
"quantum_jump",
"time_rewind",
"entangle",
"break_entangle",
],
"ui": ["button_click", "menu_open", "level_complete", "star_collect"],
"ambient": ["dimension_hum", "particle_float", "energy_pulse"],
"total_sfx": 34,
},
"audio_tech": {
"format": "WebAudio API compatible",
"compression": "OGG Vorbis",
"3d_audio": "Positional audio for key effects",
"dynamic_mixing": "Adaptive based on gameplay",
},
}
state["audio_design"] = audio_design
# Create procedural audio system
procedural_audio = {
"system": "Quantum harmonics generator",
"parameters": ["Dimension", "Player velocity", "Puzzle state"],
"realtime_effects": ["Reverb", "Delay", "Pitch shift", "Low-pass filter"],
}
state["procedural_audio"] = procedural_audio
# Audio implementation
audio_stats = {
"total_duration": "18 minutes of music",
"file_size": "12MB compressed",
"load_strategy": "Progressive with priority queue",
"mobile_optimization": "Reduced quality option available",
}
state["audio_stats"] = audio_stats
metadata = {"next_agent": "qa_tester"}
return self.create_result(
f"Audio complete: {len(audio_design['music_tracks'])} music tracks, "
f"{audio_design['sound_effects']['total_sfx']} sound effects",
metadata=metadata,
)
class QATesterAgent(Web3Agent):
"""QA tester finding and reporting bugs."""
name = "qa_tester"
description = "Thoroughly tests games and ensures quality"
async def _run_impl(self, state: State, history, network) -> Any:
"""Test the game and find bugs."""
print(f"\n🐛 {self.name}: Testing {state['game_concept']['title']}...")
# Run test suites
test_results = {
"automated_tests": {
"unit_tests": {"passed": 142, "failed": 3},
"integration_tests": {"passed": 56, "failed": 1},
"performance_tests": {"passed": 18, "failed": 0},
"coverage": "86%",
},
"manual_testing": {
"hours_tested": 12,
"devices_tested": [
"iPhone 12",
"Samsung S21",
"iPad Pro",
"Chrome/Firefox/Safari",
],
"test_cases_executed": 85,
},
"bugs_found": [
{
"id": "BUG-001",
"severity": "High",
"desc": "Player clips through wall in level 23",
"status": "Fixed",
},
{
"id": "BUG-002",
"severity": "Medium",
"desc": "Audio cuts out after dimension shift",
"status": "Fixed",
},
{
"id": "BUG-003",
"severity": "Low",
"desc": "Particle effect persists on menu",
"status": "Fixed",
},
{
"id": "BUG-004",
"severity": "Low",
"desc": "Typo in tutorial text",
"status": "Fixed",
},
],
"performance_issues": [
"Frame drops on older devices during particle-heavy scenes",
"Loading time exceeds 3s on slow connections",
],
}
state["test_results"] = test_results
# Gameplay feedback
gameplay_feedback = {
"difficulty": "Well-balanced with good progression",
"fun_factor": "8.5/10 - Engaging puzzles, satisfying mechanics",
"issues": [
"Level 35 puzzle solution not intuitive",
"Tutorial could be more interactive",
],
"suggestions": [
"Add colorblind mode",
"Include speed run timer",
"More particle effects on success",
],
}
state["gameplay_feedback"] = gameplay_feedback
# Create test report
test_report = {
"build_tested": state.get("test_deployment", {}).get("build_id", "v0.9"),
"test_date": "2024-01-25",
"overall_quality": "Release Candidate",
"recommendation": "Ready for soft launch after minor fixes",
"estimated_rating": "4.3/5 stars",
}
state["final_test_report"] = test_report
# Bill for QA work
if self.wallet:
payment = await self.request_payment(
from_address=state.get("producer_address", self.address),
amount_eth=0.05,
task_description="Comprehensive QA testing",
)
print(f"💰 QA testing complete, invoiced: {payment['amount_eth']} ETH")
return self.create_result(
f"QA complete: {len(test_results['bugs_found'])} bugs found (all fixed), "
f"Quality rating: {test_report['overall_quality']}",
metadata={"game_ready": True},
)
class GameProducerRouter(Router):
"""Routes between game development team members."""
async def route(self, network, step, last_result, agents) -> str:
"""Route through game dev pipeline."""
if step == 0:
return "game_designer"
# Check completion
if last_result and last_result.metadata.get("game_ready"):
return None
# Follow metadata hints
if last_result and last_result.metadata.get("next_agent"):
return last_result.metadata["next_agent"]
# Standard pipeline
pipeline = [
"game_designer",
"artist",
"programmer",
"sound_designer",
"qa_tester",
]
current = last_result.agent if last_result else pipeline[0]
try:
idx = pipeline.index(current)
if idx < len(pipeline) - 1:
return pipeline[idx + 1]
except ValueError:
pass
return None
async def run_game_dev_demo():
"""Run the game development team demo."""
print("🎮 Game Development Team Demo")
print("=" * 50)
# Create shared mnemonic
mnemonic = generate_shared_mnemonic()
# Initial state
state = State(
{
"project_type": "mobile_game",
"timeline": "4 weeks",
"budget_eth": 0.5,
"producer_address": "0x" + "F" * 40, # Mock producer wallet
}
)
# Create game dev team
agents = [
GameDesignerAgent(
web3_config=Web3AgentConfig(
wallet_enabled=True,
wallet_config=WalletConfig(mnemonic=mnemonic, account_index=0),
)
),
ArtistAgent(
web3_config=Web3AgentConfig(
wallet_enabled=True,
wallet_config=WalletConfig(mnemonic=mnemonic, account_index=1),
)
),
ProgrammerAgent(
web3_config=Web3AgentConfig(
wallet_enabled=True,
tee_enabled=True,
wallet_config=WalletConfig(mnemonic=mnemonic, account_index=2),
)
),
SoundDesignerAgent(
web3_config=Web3AgentConfig(
wallet_enabled=True,
wallet_config=WalletConfig(mnemonic=mnemonic, account_index=3),
)
),
QATesterAgent(
web3_config=Web3AgentConfig(
wallet_enabled=True,
wallet_config=WalletConfig(mnemonic=mnemonic, account_index=4),
)
),
]
# Create network
network = Web3Network(
state=state,
agents=agents,
router=GameProducerRouter(),
shared_mnemonic=mnemonic,
max_steps=10,
)
# Run game development
final_state = await network.run()
# Print game summary
print("\n" + "=" * 50)
print("🎮 Game Development Complete!")
game = final_state.get("game_concept", {})
print(f"\nGame: {game.get('title', 'Unknown')}")
print(f"Genre: {game.get('genre', 'Unknown')}")
print(f"Platform: {game.get('platform', 'Unknown')}")
# Development summary
print("\n📊 Development Summary:")
impl = final_state.get("implementation", {})
print(
f" Code: {impl.get('total_loc', 0):,} lines ({impl.get('test_coverage', 'N/A')})"
)
assets = final_state.get("game_assets", {})
print(f" Art: {len(assets.get('environments', {}))} environments")
audio = final_state.get("audio_design", {})
print(
f" Audio: {len(audio.get('music_tracks', {}))} tracks, {audio.get('sound_effects', {}).get('total_sfx', 0)} SFX"
)
tests = final_state.get("test_results", {})
bugs = tests.get("bugs_found", [])
print(f" QA: {len(bugs)} bugs found and fixed")
# Quality report
report = final_state.get("final_test_report", {})
print(f"\n✅ Final Status: {report.get('recommendation', 'Unknown')}")
print(f"📱 Test Build: {report.get('build_tested', 'N/A')}")
print(f"⭐ Expected Rating: {report.get('estimated_rating', 'N/A')}")
# Team stats
print("\n👥 Team Performance:")
stats = network.get_agent_stats()
for agent_name, _agent_stats in stats.items():
print(f" {agent_name}: Completed tasks")
print(f"\n🎉 Game ready for launch!")
if __name__ == "__main__":
asyncio.run(run_game_dev_demo())
@@ -1,169 +0,0 @@
version: 1
network:
name: "Code Review Network"
description: "Multi-LLM code review network using CLI agents"
main_agent: coordinator
agents:
coordinator:
type: claude_cli
model: claude-3-opus-20240229
description: "Coordinates code review across multiple specialist agents"
connections: [security_reviewer, performance_analyzer, style_checker, architecture_reviewer]
prompt: |
# Code Review Coordinator
You are coordinating a comprehensive code review using multiple specialized agents.
Your role is to:
1. Analyze the code changes and identify areas needing review
2. Delegate specific aspects to specialist reviewers
3. Synthesize feedback from all reviewers
4. Provide a comprehensive review summary
## Your Review Team:
- **Security Reviewer**: Identifies security vulnerabilities and best practices
- **Performance Analyzer**: Analyzes performance implications and optimizations
- **Style Checker**: Ensures code style consistency and readability
- **Architecture Reviewer**: Evaluates architectural decisions and patterns
## Review Process:
1. First, understand the code changes and their purpose
2. Identify which specialists should review which parts
3. Coordinate parallel reviews when possible
4. Compile and prioritize all feedback
5. Provide actionable recommendations
Always aim for constructive, actionable feedback that helps improve the code.
security_reviewer:
type: grok_cli
model: grok-beta
description: "Security-focused code reviewer"
auth:
method: api_key
env_var: GROK_API_KEY
prompt: |
# Security Code Reviewer
You are a security specialist reviewing code for vulnerabilities and security best practices.
Focus on:
- SQL injection vulnerabilities
- XSS (Cross-site scripting) risks
- Authentication and authorization issues
- Sensitive data exposure
- Dependency vulnerabilities
- Input validation
- Cryptographic weaknesses
- OWASP Top 10 vulnerabilities
Provide specific, actionable feedback with severity ratings (Critical, High, Medium, Low).
Include code examples for fixes when possible.
performance_analyzer:
type: gemini_cli
model: gemini-1.5-pro
description: "Performance optimization specialist"
auth:
method: api_key
env_var: GEMINI_API_KEY
prompt: |
# Performance Analyzer
You are a performance optimization specialist reviewing code for efficiency.
Analyze:
- Algorithm complexity (Big O notation)
- Database query efficiency
- Memory usage patterns
- Caching opportunities
- Async/concurrent operations
- Resource leaks
- Network calls optimization
- Bundle size impacts
Provide specific metrics and benchmarks where relevant.
Suggest concrete optimizations with expected improvements.
style_checker:
type: codex_cli
model: gpt-4-turbo-preview
description: "Code style and consistency reviewer"
auth:
method: api_key
env_var: OPENAI_API_KEY
prompt: |
# Code Style Reviewer
You are reviewing code for style, readability, and maintainability.
Check for:
- Naming conventions consistency
- Code formatting and indentation
- Comment quality and documentation
- Function/method length and complexity
- DRY (Don't Repeat Yourself) principles
- SOLID principles adherence
- Error handling patterns
- Test coverage and quality
Focus on making the code more readable and maintainable.
Suggest refactoring opportunities that improve clarity.
architecture_reviewer:
type: claude_cli
model: claude-3-sonnet-20240229
description: "Software architecture and design pattern reviewer"
auth:
method: oauth
profile: work
prompt: |
# Architecture Reviewer
You are reviewing code from an architectural perspective.
Evaluate:
- Design pattern usage and appropriateness
- Module boundaries and responsibilities
- Dependency management
- Coupling and cohesion
- Scalability considerations
- API design and contracts
- Error handling strategies
- Future extensibility
Consider the broader system context and long-term maintainability.
Suggest architectural improvements that align with the project's goals.
workflow:
type: parallel_synthesis
steps:
- name: initial_analysis
agent: coordinator
action: analyze_changes
- name: specialist_reviews
parallel: true
agents:
- security_reviewer
- performance_analyzer
- style_checker
- architecture_reviewer
action: review_code
- name: synthesis
agent: coordinator
action: compile_feedback
inputs_from: [security_reviewer, performance_analyzer, style_checker, architecture_reviewer]
- name: final_report
agent: coordinator
action: generate_report
output_format: markdown
settings:
max_rounds: 3
timeout_minutes: 10
enable_caching: true
cache_ttl_hours: 24
@@ -1,277 +0,0 @@
version: 1
network:
name: "Content Creation Network"
description: "Multi-agent network for creating comprehensive technical content"
main_agent: content_director
agents:
content_director:
type: claude_cli
model: claude-3-opus-20240229
description: "Directs content creation strategy and coordination"
connections: [researcher, writer, technical_reviewer, seo_optimizer, visual_designer]
prompt: |
# Content Director
You are directing content creation for: {content_topic}
Target audience: {target_audience}
Content type: {content_type}
## Your Content Team:
- **Researcher**: In-depth topic research and fact gathering
- **Writer**: Content creation and narrative flow
- **Technical Reviewer**: Accuracy and depth verification
- **SEO Optimizer**: Search optimization and discoverability
- **Visual Designer**: Diagrams, infographics, and visual aids
## Content Strategy:
1. Define content goals and key messages
2. Research target audience needs
3. Create content outline
4. Coordinate content creation
5. Ensure quality and accuracy
6. Optimize for distribution
Maintain consistent voice and high quality throughout.
researcher:
type: gemini_cli
model: gemini-1.5-pro
description: "Deep research and information gathering"
auth:
method: api_key
env_var: GEMINI_API_KEY
tools: [web_search, arxiv_search, wikipedia_api, trend_analyzer]
context_window: 1000000
prompt: |
# Content Researcher
You are researching: {content_topic}
Your research goals:
1. Gather comprehensive information
2. Identify key concepts and trends
3. Find relevant examples and case studies
4. Collect statistics and data
5. Verify facts from multiple sources
Research areas:
- Current state of the topic
- Historical context
- Future trends
- Expert opinions
- Real-world applications
- Common misconceptions
Deliverables:
- Research brief with key findings
- Fact sheet with sources
- Trend analysis
- Expert quotes
- Visual data suggestions
writer:
type: claude_cli
model: claude-3-sonnet-20240229
description: "Content writing and narrative crafting"
auth:
method: oauth
profile: default
tools: [grammar_checker, readability_analyzer, tone_adjuster]
prompt: |
# Content Writer
You are writing about: {content_topic}
Writing guidelines:
- Target audience: {target_audience}
- Tone: {content_tone}
- Length: {content_length}
- Style: {writing_style}
Structure:
1. Compelling introduction
2. Clear main points
3. Supporting examples
4. Smooth transitions
5. Strong conclusion
6. Call to action
Focus on:
- Clarity and accessibility
- Engaging narrative
- Practical value
- Scannable formatting
- Active voice
Include:
- Headers and subheaders
- Bullet points where appropriate
- Key takeaways
- Relevant quotes
technical_reviewer:
type: grok_cli
model: grok-beta
description: "Technical accuracy and depth verification"
auth:
method: api_key
env_var: GROK_API_KEY
tools: [code_validator, fact_checker, reference_verifier]
prompt: |
# Technical Reviewer
You are reviewing technical content about: {content_topic}
Review checklist:
1. Technical accuracy
2. Code examples correctness
3. Best practices adherence
4. Security considerations
5. Performance implications
6. Version compatibility
Verify:
- All technical claims
- Code snippets functionality
- API references
- Configuration examples
- Command line instructions
Provide:
- Accuracy assessment
- Correction suggestions
- Additional technical insights
- Warning about pitfalls
- Update recommendations
seo_optimizer:
type: codex_cli
model: gpt-4-turbo-preview
description: "SEO and content discoverability optimization"
auth:
method: api_key
env_var: OPENAI_API_KEY
tools: [keyword_researcher, serp_analyzer, content_scorer]
prompt: |
# SEO Optimizer
You are optimizing content about: {content_topic}
SEO tasks:
1. Keyword research and analysis
2. Title and meta description optimization
3. Header structure improvement
4. Internal linking suggestions
5. Content readability scoring
Optimize for:
- Primary keywords: {primary_keywords}
- Search intent alignment
- Featured snippet potential
- Mobile readability
- Page load considerations
Deliverables:
- Optimized title options
- Meta description
- Keyword placement map
- Internal link suggestions
- Schema markup recommendations
visual_designer:
type: gemini_cli
model: gemini-1.5-flash
description: "Visual content and diagram creation"
auth:
method: api_key
env_var: GEMINI_API_KEY
tools: [diagram_generator, chart_creator, infographic_designer]
prompt: |
# Visual Designer
You are creating visuals for: {content_topic}
Visual elements needed:
1. Explanatory diagrams
2. Data visualizations
3. Process flowcharts
4. Comparison tables
5. Infographic summaries
Design principles:
- Clarity over complexity
- Consistent style
- Accessible colors
- Mobile-friendly
- Brand alignment
Create:
- Diagram descriptions (for rendering)
- Chart specifications
- Infographic layouts
- Screenshot annotations
- Visual hierarchy plan
workflow:
type: iterative_collaboration
round_1:
- name: planning
agents: [content_director, researcher]
outputs: [content_outline, research_brief]
- name: initial_draft
agents: [writer]
inputs: [content_outline, research_brief]
outputs: [first_draft]
round_2:
- name: technical_review
agents: [technical_reviewer]
inputs: [first_draft]
outputs: [technical_feedback]
- name: seo_analysis
agents: [seo_optimizer]
inputs: [first_draft]
outputs: [seo_recommendations]
round_3:
- name: revision
agents: [writer, content_director]
inputs: [technical_feedback, seo_recommendations]
outputs: [revised_draft]
- name: visual_creation
agents: [visual_designer]
inputs: [revised_draft]
outputs: [visual_assets]
final_round:
- name: integration
agents: [content_director, writer]
inputs: [revised_draft, visual_assets]
outputs: [final_content]
- name: quality_check
parallel: true
agents: [technical_reviewer, seo_optimizer]
inputs: [final_content]
outputs: [final_approval]
content_formats:
- blog_post
- technical_tutorial
- video_script
- documentation
- whitepaper
- social_media_series
distribution:
channels:
- website_cms
- email_newsletter
- social_media
- developer_forums
- content_syndication
@@ -1,253 +0,0 @@
version: 1
network:
name: "Debugging Swarm"
description: "Collaborative debugging network for complex software issues"
main_agent: debug_coordinator
agents:
debug_coordinator:
type: claude_cli
model: claude-3-opus-20240229
description: "Coordinates debugging efforts across specialized agents"
connections: [log_analyzer, code_tracer, memory_profiler, network_inspector, reproducer]
tools: [read_files, grep, run_command]
prompt: |
# Debug Coordinator
You are coordinating a debugging effort for: {bug_description}
## Your Debugging Team:
- **Log Analyzer**: Parses and analyzes application logs
- **Code Tracer**: Traces execution flow and identifies issues
- **Memory Profiler**: Analyzes memory usage and leaks
- **Network Inspector**: Examines network traffic and API calls
- **Reproducer**: Creates minimal reproduction cases
## Debugging Strategy:
1. Gather initial information about the bug
2. Analyze logs and error messages
3. Trace code execution path
4. Profile resource usage
5. Create minimal reproduction
6. Identify root cause
7. Propose and verify fix
Coordinate parallel investigations when possible.
log_analyzer:
type: grok_cli
model: grok-beta
description: "Log parsing and analysis specialist"
auth:
method: api_key
env_var: GROK_API_KEY
tools: [log_parser, grep, awk, time_series_analyzer]
prompt: |
# Log Analysis Specialist
You are analyzing logs for debugging: {bug_description}
Your tasks:
1. Parse application logs
2. Identify error patterns
3. Correlate timestamps
4. Find anomalies
5. Track error frequency
Look for:
- Error messages and stack traces
- Warning patterns before errors
- Performance degradation indicators
- Unusual request patterns
- Resource exhaustion signs
Provide:
- Timeline of events
- Error frequency analysis
- Correlated events
- Suspicious patterns
code_tracer:
type: codex_cli
model: gpt-4-turbo-preview
description: "Code execution tracing and flow analysis"
auth:
method: api_key
env_var: OPENAI_API_KEY
tools: [debugger, code_analyzer, call_graph_generator]
prompt: |
# Code Tracer
You are tracing code execution for: {bug_description}
Your approach:
1. Identify entry points
2. Trace execution flow
3. Add debug logging
4. Analyze call stack
5. Find problematic code paths
Focus on:
- Function call sequences
- Variable state changes
- Conditional branch analysis
- Exception handling paths
- Async operation flows
Deliverables:
- Execution flow diagram
- Variable state timeline
- Identified code hotspots
- Potential bug locations
memory_profiler:
type: gemini_cli
model: gemini-1.5-pro
description: "Memory usage analysis and leak detection"
auth:
method: api_key
env_var: GEMINI_API_KEY
tools: [memory_profiler, heap_analyzer, gc_monitor]
prompt: |
# Memory Profiler
You are profiling memory usage for: {bug_description}
Analyze:
1. Memory allocation patterns
2. Heap usage over time
3. Object retention
4. Garbage collection behavior
5. Memory leak indicators
Tools to use:
- Heap dumps analysis
- Allocation profiling
- GC logs analysis
- Memory growth tracking
- Reference chain analysis
Report:
- Memory usage timeline
- Leak suspects
- Retention paths
- Optimization suggestions
network_inspector:
type: claude_cli
model: claude-3-sonnet-20240229
description: "Network traffic and API call analyzer"
auth:
method: oauth
profile: default
tools: [tcpdump, wireshark, curl, api_tester]
prompt: |
# Network Inspector
You are inspecting network behavior for: {bug_description}
Examine:
1. HTTP/HTTPS requests and responses
2. API call patterns
3. Network latency
4. Connection failures
5. Data serialization issues
Check for:
- Timeout issues
- Malformed requests
- Authentication failures
- Rate limiting
- CORS problems
- SSL/TLS issues
Provide:
- Request/response analysis
- Timing diagrams
- Error patterns
- Network bottlenecks
reproducer:
type: codex_cli
model: gpt-4
description: "Creates minimal bug reproduction cases"
auth:
method: api_key
env_var: OPENAI_API_KEY
tools: [code_generator, test_runner, docker]
prompt: |
# Bug Reproducer
You are creating a minimal reproduction for: {bug_description}
Your goal:
1. Isolate the bug
2. Remove unnecessary code
3. Create minimal test case
4. Document reproduction steps
5. Verify consistency
Deliverables:
- Minimal code example
- Step-by-step reproduction
- Environment requirements
- Expected vs actual behavior
- Docker container (if needed)
Ensure reproduction is:
- Minimal
- Consistent
- Self-contained
- Well-documented
workflow:
type: adaptive_investigation
initial_phase:
parallel: true
agents: [log_analyzer, code_tracer]
duration_minutes: 15
investigation_phase:
conditional: true
conditions:
- if: memory_issue_suspected
agents: [memory_profiler]
- if: network_issue_suspected
agents: [network_inspector]
- if: complex_issue
agents: [memory_profiler, network_inspector]
duration_minutes: 30
reproduction_phase:
agent: reproducer
inputs_from: all_previous
duration_minutes: 20
synthesis_phase:
agent: debug_coordinator
action: compile_findings
outputs: [root_cause_analysis, fix_recommendation]
tools:
shared:
- name: debug_dashboard
type: web_ui
features: [real_time_logs, metrics_visualization, trace_viewer]
- name: collaborative_debugger
type: vscode_extension
features: [shared_breakpoints, live_variable_inspection]
communication:
type: event_driven
channels:
- findings_stream
- hypothesis_discussion
- evidence_sharing
outputs:
- bug_report_detailed
- root_cause_analysis
- minimal_reproduction
- fix_patch
- prevention_recommendations
@@ -1,292 +0,0 @@
version: 1
network:
name: "DevOps Automation Network"
description: "Intelligent DevOps network for CI/CD, monitoring, and infrastructure management"
main_agent: devops_orchestrator
agents:
devops_orchestrator:
type: claude_cli
model: claude-3-opus-20240229
description: "Orchestrates DevOps workflows and coordinates specialists"
connections: [ci_engineer, security_scanner, performance_tester, deploy_manager, incident_responder]
tools: [kubernetes_cli, docker_cli, terraform, ansible]
prompt: |
# DevOps Orchestrator
You are orchestrating DevOps operations for: {project_name}
Environment: {environment}
## Your DevOps Team:
- **CI Engineer**: Build pipelines and test automation
- **Security Scanner**: Security scanning and compliance
- **Performance Tester**: Load testing and optimization
- **Deploy Manager**: Deployment and rollout strategies
- **Incident Responder**: Monitoring and incident response
## Responsibilities:
1. Coordinate CI/CD pipelines
2. Ensure security compliance
3. Manage deployments
4. Monitor system health
5. Respond to incidents
Follow GitOps principles and maintain infrastructure as code.
ci_engineer:
type: codex_cli
model: gpt-4-turbo-preview
description: "CI/CD pipeline specialist"
auth:
method: api_key
env_var: OPENAI_API_KEY
tools: [github_actions, jenkins, gitlab_ci, build_tools]
prompt: |
# CI/CD Engineer
You are managing CI/CD for: {project_name}
Pipeline responsibilities:
1. Build automation
2. Test execution
3. Code quality gates
4. Artifact management
5. Pipeline optimization
Implement:
- Multi-stage pipelines
- Parallel test execution
- Docker container builds
- Dependency caching
- Build notifications
Best practices:
- Fast feedback loops
- Reproducible builds
- Secure credential handling
- Efficient resource usage
- Clear failure reporting
security_scanner:
type: grok_cli
model: grok-beta
description: "Security scanning and vulnerability assessment"
auth:
method: api_key
env_var: GROK_API_KEY
tools: [trivy, sonarqube, snyk, owasp_zap]
prompt: |
# Security Scanner
You are performing security scanning for: {project_name}
Security checks:
1. Dependency vulnerabilities
2. Container image scanning
3. Code security analysis
4. Infrastructure misconfigurations
5. Compliance validation
Scan for:
- CVEs in dependencies
- Hardcoded secrets
- OWASP Top 10
- CIS benchmarks
- License compliance
Provide:
- Vulnerability report
- Risk assessment
- Remediation steps
- Security metrics
- Compliance status
performance_tester:
type: gemini_cli
model: gemini-1.5-pro
description: "Performance testing and optimization"
auth:
method: api_key
env_var: GEMINI_API_KEY
tools: [k6, jmeter, gatling, prometheus]
prompt: |
# Performance Tester
You are testing performance for: {project_name}
Testing areas:
1. Load testing
2. Stress testing
3. Spike testing
4. Endurance testing
5. Scalability testing
Metrics to monitor:
- Response times (p50, p95, p99)
- Throughput (RPS)
- Error rates
- Resource utilization
- Concurrency limits
Deliverables:
- Performance baseline
- Bottleneck identification
- Optimization recommendations
- Capacity planning data
- SLA validation
deploy_manager:
type: claude_cli
model: claude-3-sonnet-20240229
description: "Deployment strategies and rollout management"
auth:
method: oauth
profile: default
tools: [helm, argocd, spinnaker, kubectl]
prompt: |
# Deploy Manager
You are managing deployments for: {project_name}
Target environment: {environment}
Deployment strategies:
1. Blue-green deployments
2. Canary releases
3. Rolling updates
4. Feature flags
5. Rollback procedures
Ensure:
- Zero-downtime deployments
- Health check validation
- Traffic gradual shifting
- Quick rollback capability
- Deployment tracking
Manage:
- Kubernetes manifests
- Helm charts
- Service mesh configs
- Database migrations
- Configuration updates
incident_responder:
type: codex_cli
model: gpt-4
description: "Incident detection and response automation"
auth:
method: api_key
env_var: OPENAI_API_KEY
tools: [datadog, pagerduty, elasticsearch, grafana]
prompt: |
# Incident Responder
You are monitoring and responding to incidents for: {project_name}
Monitoring scope:
1. Application health
2. Infrastructure status
3. Performance anomalies
4. Security events
5. User-reported issues
Incident response:
- Rapid detection
- Initial triage
- Root cause analysis
- Mitigation steps
- Post-mortem creation
Automate:
- Alert correlation
- Runbook execution
- Stakeholder notification
- Evidence collection
- Status page updates
workflows:
continuous_deployment:
trigger: code_push
steps:
- name: build_test
agent: ci_engineer
parallel_with: [security_scan]
- name: security_scan
agent: security_scanner
fail_fast: true
- name: performance_test
agent: performance_tester
condition: branch == 'main'
- name: deploy_staging
agent: deploy_manager
environment: staging
- name: validate_staging
agents: [performance_tester, security_scanner]
- name: deploy_production
agent: deploy_manager
environment: production
approval_required: true
- name: monitor_deployment
agent: incident_responder
duration_minutes: 30
incident_response:
trigger: alert
steps:
- name: triage
agent: incident_responder
sla_minutes: 5
- name: investigate
parallel: true
agents: [incident_responder, ci_engineer, deploy_manager]
- name: mitigate
agent: deploy_manager
action: rollback_if_needed
- name: post_mortem
agents: [incident_responder, devops_orchestrator]
within_hours: 24
infrastructure_update:
trigger: terraform_change
steps:
- name: plan
agent: devops_orchestrator
action: terraform_plan
- name: security_review
agent: security_scanner
action: scan_iac
- name: cost_analysis
agent: devops_orchestrator
action: analyze_costs
- name: apply
agent: devops_orchestrator
action: terraform_apply
approval_required: true
integrations:
- github
- slack
- jira
- confluence
- vault
- aws
- gcp
- azure
sla_targets:
deployment_frequency: "multiple per day"
lead_time: "< 1 hour"
mttr: "< 30 minutes"
change_failure_rate: "< 5%"
@@ -1,234 +0,0 @@
version: 1
network:
name: "Academic Research Network"
description: "Multi-agent network for comprehensive research and paper writing"
main_agent: research_lead
agents:
research_lead:
type: claude_cli
model: claude-3-opus-20240229
description: "Lead researcher coordinating the research team"
connections: [literature_reviewer, data_analyst, methodology_expert, writer, fact_checker]
tools: [web_search, arxiv_search, read_files]
prompt: |
# Research Team Lead
You are leading a research team investigating: {research_topic}
Your responsibilities:
1. Define research questions and hypotheses
2. Coordinate literature review
3. Design methodology
4. Oversee data analysis
5. Ensure research quality and rigor
## Your Team:
- **Literature Reviewer**: Comprehensive literature analysis
- **Data Analyst**: Statistical analysis and visualization
- **Methodology Expert**: Research design and methods
- **Writer**: Academic writing and formatting
- **Fact Checker**: Verification and citations
Guide the team through a systematic research process.
literature_reviewer:
type: gemini_cli
model: gemini-1.5-pro
description: "Literature review and synthesis specialist"
auth:
method: api_key
env_var: GEMINI_API_KEY
tools: [web_search, pdf_reader, citation_manager]
context_window: 1000000 # 1M token context for long papers
prompt: |
# Literature Review Specialist
You are conducting a comprehensive literature review on: {research_topic}
Your tasks:
1. Search academic databases (Google Scholar, PubMed, arXiv)
2. Identify key papers and seminal works
3. Analyze research trends and gaps
4. Synthesize findings across studies
5. Create citation network analysis
Focus on:
- Recent developments (last 5 years)
- Methodological approaches
- Conflicting findings
- Research gaps
- Future directions
Maintain a bibliography with proper citations.
data_analyst:
type: codex_cli
model: gpt-4-turbo-preview
description: "Statistical analysis and data visualization expert"
auth:
method: api_key
env_var: OPENAI_API_KEY
tools: [python_repl, jupyter_notebook, data_viz]
prompt: |
# Data Analysis Specialist
You are the team's data analyst working on: {research_topic}
Your responsibilities:
1. Design statistical analysis plan
2. Clean and preprocess data
3. Perform exploratory data analysis
4. Run statistical tests
5. Create publication-quality visualizations
Use appropriate methods:
- Descriptive statistics
- Hypothesis testing
- Regression analysis
- Machine learning (if applicable)
- Effect size calculations
Ensure:
- Reproducible analysis (document all steps)
- Appropriate statistical methods
- Clear visualization with proper labels
- Interpretation of results
methodology_expert:
type: grok_cli
model: grok-beta
description: "Research methodology and design expert"
auth:
method: api_key
env_var: GROK_API_KEY
prompt: |
# Methodology Expert
You are designing research methodology for: {research_topic}
Consider:
1. Research design (experimental, observational, mixed methods)
2. Sampling strategy and sample size
3. Data collection methods
4. Validity and reliability
5. Ethical considerations
Address:
- Internal and external validity threats
- Potential biases and mitigation
- Power analysis
- Limitations and delimitations
- Generalizability
Ensure methodology aligns with research questions and is feasible.
writer:
type: claude_cli
model: claude-3-sonnet-20240229
description: "Academic writing and manuscript preparation"
auth:
method: oauth
profile: default
tools: [latex_editor, citation_formatter, grammar_checker]
prompt: |
# Academic Writer
You are preparing a research manuscript on: {research_topic}
Structure:
1. Title and Abstract
2. Introduction
3. Literature Review
4. Methodology
5. Results
6. Discussion
7. Conclusion
8. References
Writing guidelines:
- Clear, concise academic prose
- Logical flow and transitions
- Proper citation format (APA/MLA/Chicago as specified)
- Active voice where appropriate
- Technical accuracy
Target journal: {target_journal}
Word limit: {word_limit}
fact_checker:
type: gemini_cli
model: gemini-1.5-flash
description: "Fact checking and citation verification"
auth:
method: api_key
env_var: GEMINI_API_KEY
tools: [web_search, citation_verifier, plagiarism_checker]
prompt: |
# Fact Checker
You are verifying all claims and citations for: {research_topic}
Check:
1. Accuracy of all factual claims
2. Proper citation of sources
3. No plagiarism or improper paraphrasing
4. Statistical accuracy
5. Consistency throughout the paper
Verify:
- All numbers and statistics
- Quotes and attributions
- Technical terminology
- Historical facts
- Research findings citations
Flag any issues for correction.
workflow:
type: sequential_collaborative
phases:
- name: research_design
agents: [research_lead, methodology_expert]
duration_hours: 2
- name: literature_review
agents: [literature_reviewer]
duration_hours: 8
outputs: [bibliography, literature_summary]
- name: data_collection_analysis
agents: [data_analyst]
duration_hours: 12
outputs: [analysis_results, visualizations]
- name: writing_draft
agents: [writer]
inputs: [literature_summary, analysis_results]
duration_hours: 16
outputs: [manuscript_draft]
- name: review_revision
parallel: true
agents: [research_lead, fact_checker, methodology_expert]
inputs: [manuscript_draft]
duration_hours: 4
- name: final_preparation
agents: [writer, research_lead]
duration_hours: 2
outputs: [final_manuscript, cover_letter]
memory:
type: shared_knowledge_base
components:
- bibliography_manager
- research_notes
- data_repository
- version_control
outputs:
- research_paper_pdf
- supplementary_materials
- data_and_code_repository
- presentation_slides
@@ -1,499 +0,0 @@
"""Research team demo - Agents collaborating on research tasks.
This example shows specialized research agents working together:
- Data Scientist: Analyzes data and finds patterns
- Research Analyst: Conducts literature review
- ML Engineer: Builds and trains models
- Technical Writer: Documents findings
"""
import random
import asyncio
from typing import Any
from hanzo_agents import (
State,
Web3Agent,
Web3Network,
WalletConfig,
Web3AgentConfig,
AgentMarketplace,
generate_shared_mnemonic,
)
from hanzo_agents.core.router import Router
class DataScientistAgent(Web3Agent):
"""Data scientist agent specializing in analysis."""
name = "data_scientist"
description = "Analyzes data, finds patterns, and generates insights"
async def _run_impl(self, state: State, history, network) -> Any:
"""Analyze research data."""
print(
f"\n📊 {self.name}: Starting data analysis on {state.get('research_topic')}..."
)
# Simulate data analysis
dataset_size = state.get("dataset_size", 10000)
analysis_results = {
"samples_analyzed": dataset_size,
"features_extracted": 42,
"correlations_found": 7,
"anomalies_detected": 3,
"key_insights": [
"Strong correlation between features X and Y (r=0.87)",
"Seasonal patterns detected with 30-day cycle",
"3 outlier clusters identified requiring investigation",
],
"confidence_score": 0.92,
}
state["data_analysis"] = analysis_results
# Use TEE for sensitive data processing
if self.confidential_agent and state.get("contains_pii", False):
secure_code = """
# Process PII data securely
result = {
"anonymized_records": len(inputs["data"]),
"privacy_preserved": True,
"k_anonymity": 5
}
"""
secure_result = await self.execute_confidential(
secure_code,
{"data": ["record1", "record2"]}, # Mock data
)
print(
f"🔒 Processed {secure_result['result']['anonymized_records']} records securely"
)
# Request literature review
metadata = {
"service_request": {
"type": "research",
"description": "Literature review on discovered patterns",
"max_price_eth": 0.05,
"keywords": ["correlation", "seasonal patterns", "anomaly detection"],
}
}
return self.create_result(
f"Analysis complete: {len(analysis_results['key_insights'])} key insights found. "
f"Confidence: {analysis_results['confidence_score']:.0%}",
next_agent="research_analyst",
metadata=metadata,
)
class ResearchAnalystAgent(Web3Agent):
"""Research analyst agent for literature review."""
name = "research_analyst"
description = "Conducts comprehensive literature reviews and research"
async def _run_impl(self, state: State, history, network) -> Any:
"""Conduct literature review based on data findings."""
print(f"\n📚 {self.name}: Reviewing literature on identified patterns...")
data_analysis = state.get("data_analysis", {})
research_topic = state.get("research_topic", "AI research")
# Simulate literature search
papers_reviewed = {
"total_papers": 156,
"relevant_papers": 23,
"key_papers": [
{
"title": "Seasonal Patterns in Neural Network Training",
"year": 2023,
"citations": 45,
"relevance_score": 0.95,
},
{
"title": "Anomaly Detection in High-Dimensional Data",
"year": 2024,
"citations": 12,
"relevance_score": 0.88,
},
{
"title": "Feature Correlation Analysis in ML Systems",
"year": 2023,
"citations": 67,
"relevance_score": 0.91,
},
],
"research_gaps": [
"Limited work on 30-day cycles in AI training",
"Need for better outlier cluster interpretation",
"Correlation threshold optimization unexplored",
],
}
state["literature_review"] = papers_reviewed
# Synthesize findings
synthesis = {
"confirmed_patterns": 2,
"novel_findings": 1,
"recommended_methods": [
"LSTM for seasonal analysis",
"DBSCAN for clustering",
],
"next_steps": "Build predictive model based on findings",
}
state["research_synthesis"] = synthesis
# Bill for research work
if self.wallet:
payment = await self.request_payment(
from_address=state.get("project_lead_address", "0x0"),
amount_eth=0.04,
task_description="Comprehensive literature review",
)
print(f"💰 Research work billed: {payment['amount_eth']} ETH")
# Request ML model development
metadata = {
"service_request": {
"type": "development",
"description": "Build ML model based on research findings",
"max_price_eth": 0.1,
"requirements": synthesis["recommended_methods"],
}
}
return self.create_result(
f"Literature review complete: {papers_reviewed['relevant_papers']} relevant papers found. "
f"Identified {len(papers_reviewed['research_gaps'])} research gaps.",
next_agent="ml_engineer",
metadata=metadata,
)
class MLEngineerAgent(Web3Agent):
"""ML engineer agent for model development."""
name = "ml_engineer"
description = "Builds and optimizes machine learning models"
async def _run_impl(self, state: State, history, network) -> Any:
"""Build ML model based on research findings."""
print(f"\n🤖 {self.name}: Building ML model based on research insights...")
analysis = state.get("data_analysis", {})
synthesis = state.get("research_synthesis", {})
# Simulate model training
model_results = {
"architecture": "LSTM + Attention",
"parameters": 2_500_000,
"training_epochs": 50,
"validation_accuracy": 0.94,
"test_accuracy": 0.92,
"f1_score": 0.91,
"training_time_hours": 2.5,
"model_size_mb": 45,
"optimizations": [
"Gradient accumulation for memory efficiency",
"Mixed precision training",
"Early stopping with patience=5",
],
}
state["model_results"] = model_results
# Run experiments
experiments = []
for i in range(3):
exp_result = {
"experiment_id": f"exp_{i + 1}",
"hypothesis": f"Testing hypothesis {i + 1}",
"result": random.choice(["confirmed", "rejected", "inconclusive"]),
"p_value": round(random.uniform(0.01, 0.1), 3),
}
experiments.append(exp_result)
state["experiments"] = experiments
# Deploy model to staging
if self.confidential_agent:
deploy_code = """
# Deploy model securely
result = {
"deployment_id": "model_v1_staging",
"endpoint": "https://api.research.ai/v1/predict",
"encryption": "AES-256",
"access_control": "API key required"
}
"""
deployment = await self.execute_confidential(
deploy_code, {"model_path": "/models/research_model.pkl"}
)
state["model_deployment"] = deployment.get("result", {})
# Request documentation
metadata = {
"service_request": {
"type": "documentation",
"description": "Write comprehensive research paper",
"max_price_eth": 0.08,
"deliverable": "LaTeX paper with figures",
}
}
return self.create_result(
f"Model trained successfully: {model_results['validation_accuracy']:.0%} accuracy. "
f"Ran {len(experiments)} experiments. Model deployed to staging.",
next_agent="technical_writer",
metadata=metadata,
)
class TechnicalWriterAgent(Web3Agent):
"""Technical writer agent for documentation."""
name = "technical_writer"
description = "Creates clear, comprehensive technical documentation"
async def _run_impl(self, state: State, history, network) -> Any:
"""Document the research findings and results."""
print(f"\n📝 {self.name}: Documenting research findings...")
# Gather all results
topic = state.get("research_topic", "AI Research")
analysis = state.get("data_analysis", {})
literature = state.get("literature_review", {})
model = state.get("model_results", {})
experiments = state.get("experiments", [])
# Create paper outline
paper_outline = {
"title": f"Novel Insights in {topic}: A Data-Driven Approach",
"sections": [
"Abstract",
"Introduction",
"Related Work",
"Methodology",
"Data Analysis",
"Model Architecture",
"Experimental Results",
"Discussion",
"Conclusion",
"Future Work",
],
"figures": 8,
"tables": 4,
"equations": 12,
"references": literature.get("relevant_papers", 20),
}
state["paper_outline"] = paper_outline
# Generate key sections
abstract = (
f"We present novel findings in {topic} through comprehensive data analysis "
f"of {analysis.get('samples_analyzed', 0):,} samples. "
f"Our approach identifies {len(analysis.get('key_insights', []))} key patterns "
f"and achieves {model.get('test_accuracy', 0):.0%} accuracy "
f"using an {model.get('architecture', 'ML')} model. "
f"We validate our findings through {len(experiments)} controlled experiments."
)
state["paper_abstract"] = abstract
# Create visualizations metadata
visualizations = {
"correlation_heatmap": "Figure 1: Feature correlation matrix",
"time_series_plot": "Figure 2: Seasonal patterns over 90 days",
"model_architecture": "Figure 3: LSTM architecture diagram",
"results_comparison": "Figure 4: Model performance comparison",
"experiment_results": "Table 1: Experimental validation results",
}
state["visualizations"] = visualizations
# Generate final deliverables
deliverables = {
"research_paper_pdf": "research_findings.pdf",
"latex_source": "research_findings.tex",
"jupyter_notebook": "analysis_notebook.ipynb",
"model_card": "model_documentation.md",
"dataset_card": "dataset_documentation.md",
"slides": "presentation.pptx",
}
state["deliverables"] = deliverables
# Bill for documentation
if self.wallet:
payment = await self.request_payment(
from_address=state.get("project_lead_address", "0x0"),
amount_eth=0.06,
task_description="Technical documentation and paper writing",
)
print(f"💰 Documentation work billed: {payment['amount_eth']} ETH")
return self.create_result(
f"Documentation complete: {len(paper_outline['sections'])} sections, "
f"{paper_outline['figures']} figures, {paper_outline['references']} references. "
f"All deliverables ready for review.",
metadata={"project_complete": True},
)
class ResearchCoordinator(Router):
"""Coordinates the research team workflow."""
async def route(self, network, step, last_result, agents) -> str:
"""Route between research team members."""
if step == 0:
return "data_scientist"
# Check for completion
if last_result and last_result.metadata.get("project_complete"):
return None
# Follow next_agent hints
if last_result and last_result.metadata.get("next_agent"):
return last_result.metadata["next_agent"]
# Default flow
flow = ["data_scientist", "research_analyst", "ml_engineer", "technical_writer"]
current = last_result.agent if last_result else flow[0]
try:
idx = flow.index(current)
if idx < len(flow) - 1:
return flow[idx + 1]
except ValueError:
pass
return None
async def run_research_team_demo():
"""Run the research team collaboration demo."""
print("🔬 Research Team Demo - Collaborative AI Research Project")
print("=" * 60)
# Create shared mnemonic
mnemonic = generate_shared_mnemonic()
# Create marketplace
marketplace = AgentMarketplace()
# Initial research parameters
state = State(
{
"research_topic": "Temporal Patterns in Neural Network Optimization",
"dataset_size": 50000,
"contains_pii": True, # Requires secure processing
"project_lead_address": "0x" + "0" * 40, # Mock address
"budget_eth": 0.5,
}
)
# Create research team
agents = [
DataScientistAgent(
web3_config=Web3AgentConfig(
wallet_enabled=True,
tee_enabled=True,
wallet_config=WalletConfig(mnemonic=mnemonic, account_index=0),
)
),
ResearchAnalystAgent(
web3_config=Web3AgentConfig(
wallet_enabled=True,
wallet_config=WalletConfig(mnemonic=mnemonic, account_index=1),
)
),
MLEngineerAgent(
web3_config=Web3AgentConfig(
wallet_enabled=True,
tee_enabled=True,
wallet_config=WalletConfig(mnemonic=mnemonic, account_index=2),
)
),
TechnicalWriterAgent(
web3_config=Web3AgentConfig(
wallet_enabled=True,
wallet_config=WalletConfig(mnemonic=mnemonic, account_index=3),
)
),
]
# Create network
network = Web3Network(
state=state,
agents=agents,
router=ResearchCoordinator(),
shared_mnemonic=mnemonic,
marketplace=marketplace,
max_steps=10,
deterministic_config=None, # Disable for cleaner output
)
# Run research project
final_state = await network.run()
# Print results
print("\n" + "=" * 60)
print("📊 Research Project Summary:")
print(f"\nTopic: {final_state.get('research_topic')}")
# Data analysis summary
analysis = final_state.get("data_analysis", {})
print(f"\n📈 Data Analysis:")
print(f" - Samples: {analysis.get('samples_analyzed', 0):,}")
print(f" - Features: {analysis.get('features_extracted', 0)}")
print(f" - Key insights: {len(analysis.get('key_insights', []))}")
# Literature review summary
literature = final_state.get("literature_review", {})
print(f"\n📚 Literature Review:")
print(f" - Papers reviewed: {literature.get('total_papers', 0)}")
print(f" - Relevant papers: {literature.get('relevant_papers', 0)}")
print(f" - Research gaps: {len(literature.get('research_gaps', []))}")
# Model results
model = final_state.get("model_results", {})
print(f"\n🤖 Model Performance:")
print(f" - Architecture: {model.get('architecture', 'N/A')}")
print(f" - Accuracy: {model.get('test_accuracy', 0):.0%}")
print(f" - F1 Score: {model.get('f1_score', 0):.2f}")
print(f" - Training time: {model.get('training_time_hours', 0)} hours")
# Experiments
experiments = final_state.get("experiments", [])
print(f"\n🧪 Experiments:")
for exp in experiments:
print(f" - {exp['experiment_id']}: {exp['result']} (p={exp['p_value']})")
# Deliverables
deliverables = final_state.get("deliverables", {})
print(f"\n📄 Deliverables:")
for key, value in deliverables.items():
print(f" - {key}: {value}")
# Team performance
print(f"\n👥 Team Performance:")
stats = network.get_agent_stats()
for agent_name, agent_stats in stats.items():
print(f" - {agent_name}: Contributed to {agent_stats.get('calls', 0)} tasks")
print(f"\n✅ Research project completed successfully!")
print(
f"Execution hash: {network.execution_hash[:16]}..."
if network.execution_hash
else ""
)
if __name__ == "__main__":
asyncio.run(run_research_team_demo())
@@ -1,484 +0,0 @@
"""Startup team demo - agents collaborating on a product launch.
This example demonstrates a startup team of agents working together:
- Architect: Designs the technical architecture
- Product Manager: Defines product requirements
- Designer: Creates UI/UX designs
- Marketer: Develops marketing strategy
- Engineers: Implement the solution
"""
import asyncio
from typing import Any
from hanzo_agents import (
State,
Web3Agent,
Web3Network,
WalletConfig,
Web3AgentConfig,
AgentMarketplace,
generate_shared_mnemonic,
)
from hanzo_agents.core.router import Router
class ArchitectAgent(Web3Agent):
"""Technical architect agent."""
name = "architect"
description = "Designs system architecture and technical solutions"
async def _run_impl(self, state: State, history, network) -> Any:
"""Design the technical architecture."""
print(
f"\n🏗️ {self.name}: Analyzing requirements for {state.get('product_name', 'the product')}..."
)
# Analyze requirements
requirements = state.get("requirements", {})
# Design architecture
architecture = {
"frontend": "React with TypeScript",
"backend": "FastAPI with Python",
"database": "PostgreSQL with Redis cache",
"deployment": "Kubernetes on AWS",
"ai_integration": "OpenAI API for smart features",
"estimated_cost": 0.5, # ETH for implementation
}
state["architecture"] = architecture
# Request payment for architecture design
if self.wallet:
payment_request = await self.request_payment(
from_address=state.get("pm_address", "0x0"),
amount_eth=0.05,
task_description="System architecture design",
)
print(f"💰 Requested payment: {payment_request['amount_eth']} ETH")
# Offer implementation services
metadata = {
"service_offer": {
"type": "development",
"description": "Full-stack implementation of designed architecture",
"price_eth": architecture["estimated_cost"],
"requires_tee": True,
}
}
return self.create_result(
f"Architecture designed: {', '.join([f'{k}={v}' for k, v in architecture.items() if k != 'estimated_cost'])}",
next_agent="product_manager",
metadata=metadata,
)
class ProductManagerAgent(Web3Agent):
"""Product manager agent."""
name = "product_manager"
description = "Defines product vision and manages requirements"
async def _run_impl(self, state: State, history, network) -> Any:
"""Define product requirements and manage team."""
print(f"\n📋 {self.name}: Reviewing architecture and planning next steps...")
architecture = state.get("architecture", {})
# Store PM address for payments
state["pm_address"] = self.address
# Define MVP features
mvp_features = {
"user_auth": "Email/password with OAuth",
"core_feature": state.get("core_feature", "AI-powered chat"),
"analytics": "Basic usage tracking",
"payment": "Stripe integration",
"timeline": "4 weeks",
}
state["mvp_features"] = mvp_features
# Check team budget
total_cost = (
architecture.get("estimated_cost", 0.5) + 0.2
) # Add design/marketing
# Pay architect if we have budget
if self.wallet and self.balance_eth >= 0.05:
architect_address = network._agent_instances.get("architect").address
tx = await self.pay_agent(
to_address=architect_address,
amount_eth=0.05,
reason="Architecture design payment",
)
if tx:
print(f"💸 Paid architect: 0.05 ETH")
# Request design services
metadata = {
"service_request": {
"type": "design",
"description": "UI/UX design for MVP features",
"max_price_eth": 0.1,
"requirements": mvp_features,
}
}
return self.create_result(
f"MVP defined with {len(mvp_features)} features. Timeline: {mvp_features['timeline']}",
next_agent="designer",
metadata=metadata,
)
class DesignerAgent(Web3Agent):
"""UI/UX designer agent."""
name = "designer"
description = "Creates beautiful and intuitive user interfaces"
async def _run_impl(self, state: State, history, network) -> Any:
"""Design the user interface."""
print(f"\n🎨 {self.name}: Creating designs for MVP features...")
mvp_features = state.get("mvp_features", {})
# Create design system
design_system = {
"colors": {
"primary": "#0066CC",
"secondary": "#00AA44",
"accent": "#FF6600",
},
"typography": {"heading": "Inter", "body": "System UI"},
"components": ["Button", "Card", "Input", "Modal", "Navigation"],
"pages_designed": 8,
"mobile_responsive": True,
}
state["design_system"] = design_system
# Simulate design work
await asyncio.sleep(0.5)
# Bill for design work
if self.wallet:
pm_address = state.get("pm_address")
if pm_address:
payment = await self.request_payment(
from_address=pm_address,
amount_eth=0.1,
task_description="UI/UX design for MVP",
)
print(f"💰 Design work billed: {payment['amount_eth']} ETH")
return self.create_result(
f"Designed {design_system['pages_designed']} pages with {len(design_system['components'])} reusable components",
next_agent="marketer",
)
class MarketerAgent(Web3Agent):
"""Marketing strategist agent."""
name = "marketer"
description = "Develops go-to-market strategies and marketing campaigns"
async def _run_impl(self, state: State, history, network) -> Any:
"""Create marketing strategy."""
print(f"\n📣 {self.name}: Developing marketing strategy...")
product_name = state.get("product_name", "AI Assistant Pro")
# Create marketing plan
marketing_plan = {
"launch_strategy": "Product Hunt + Twitter campaign",
"target_audience": "Tech-savvy professionals",
"key_message": f"{product_name} - Your AI-powered productivity companion",
"channels": ["Twitter", "LinkedIn", "Product Hunt", "Reddit"],
"budget_eth": 0.2,
"expected_cac": "$50",
"launch_date": "2 weeks after MVP",
}
state["marketing_plan"] = marketing_plan
# Create landing page copy
landing_copy = {
"headline": f"Meet {product_name}",
"subheadline": "The AI assistant that actually understands you",
"cta": "Get Early Access",
"benefits": [
"Save 10 hours per week",
"AI that learns your style",
"Enterprise-grade security",
],
}
state["landing_copy"] = landing_copy
# Request implementation
metadata = {
"service_request": {
"type": "development",
"description": "Implement MVP based on designs",
"max_price_eth": 0.5,
"deadline": "4 weeks",
}
}
return self.create_result(
f"Marketing ready: {marketing_plan['launch_strategy']} targeting {marketing_plan['target_audience']}",
next_agent="engineer_frontend",
metadata=metadata,
)
class FrontendEngineerAgent(Web3Agent):
"""Frontend engineer agent."""
name = "engineer_frontend"
description = "Implements user interfaces with modern web technologies"
async def _run_impl(self, state: State, history, network) -> Any:
"""Implement frontend based on designs."""
print(f"\n💻 {self.name}: Building frontend with React...")
design_system = state.get("design_system", {})
mvp_features = state.get("mvp_features", {})
# Implement frontend
frontend_progress = {
"components_built": len(design_system.get("components", [])),
"pages_completed": 6,
"tests_written": 24,
"coverage": "87%",
"bundle_size": "245KB",
"lighthouse_score": 96,
}
state["frontend_progress"] = frontend_progress
# Use TEE for secure build
if self.confidential_agent:
build_code = """
# Secure frontend build
result = {
"build_hash": "abc123def456",
"artifacts": ["main.js", "main.css", "index.html"],
"security_scan": "passed"
}
"""
secure_build = await self.execute_confidential(
build_code, {"config": "production"}
)
print(
f"🔒 Secure build completed: {secure_build.get('result', {}).get('build_hash', 'N/A')}"
)
# Collaborate with backend
return self.create_result(
f"Frontend ready: {frontend_progress['pages_completed']} pages, "
f"{frontend_progress['tests_written']} tests ({frontend_progress['coverage']} coverage)",
next_agent="engineer_backend",
)
class BackendEngineerAgent(Web3Agent):
"""Backend engineer agent."""
name = "engineer_backend"
description = "Builds scalable APIs and backend services"
async def _run_impl(self, state: State, history, network) -> Any:
"""Implement backend services."""
print(f"\n🔧 {self.name}: Building backend with FastAPI...")
architecture = state.get("architecture", {})
mvp_features = state.get("mvp_features", {})
# Implement backend
backend_progress = {
"endpoints_created": 12,
"database_tables": 8,
"tests_written": 48,
"coverage": "92%",
"api_response_time": "45ms avg",
"security_features": ["JWT auth", "Rate limiting", "Input validation"],
}
state["backend_progress"] = backend_progress
# Deploy to staging
deployment_status = {
"environment": "staging",
"url": "https://api-staging.example.com",
"health": "all systems operational",
"ready_for_launch": True,
}
state["deployment_status"] = deployment_status
# Final team update
return self.create_result(
f"Backend deployed: {backend_progress['endpoints_created']} endpoints, "
f"{backend_progress['tests_written']} tests. Ready for launch! 🚀",
next_agent=None, # End of workflow
)
class StartupTeamRouter(Router):
"""Routes between startup team members."""
async def route(self, network, step, last_result, agents) -> str:
"""Determine next agent based on workflow."""
if step == 0:
return "architect"
if last_result and last_result.metadata.get("next_agent"):
return last_result.metadata["next_agent"]
# Default flow
flow = [
"architect",
"product_manager",
"designer",
"marketer",
"engineer_frontend",
"engineer_backend",
]
current = last_result.agent if last_result else flow[0]
try:
current_idx = flow.index(current)
if current_idx < len(flow) - 1:
return flow[current_idx + 1]
except ValueError:
pass
return None
async def run_startup_team_demo():
"""Run the startup team collaboration demo."""
print("🚀 Starting Startup Team Demo")
print("=" * 50)
# Generate shared mnemonic for the team
mnemonic = generate_shared_mnemonic()
print(
f"Team mnemonic generated (first 3 words): {' '.join(mnemonic.split()[:3])}..."
)
# Create marketplace
marketplace = AgentMarketplace()
# Create initial state
state = State(
{
"product_name": "AI Assistant Pro",
"core_feature": "Context-aware AI chat with memory",
"target_market": "B2B SaaS",
}
)
# Create agents with different account indices
agents = [
ArchitectAgent(
web3_config=Web3AgentConfig(
wallet_enabled=True,
tee_enabled=True,
wallet_config=WalletConfig(mnemonic=mnemonic, account_index=0),
)
),
ProductManagerAgent(
web3_config=Web3AgentConfig(
wallet_enabled=True,
wallet_config=WalletConfig(mnemonic=mnemonic, account_index=1),
initial_balance=2.0, # PM has budget
)
),
DesignerAgent(
web3_config=Web3AgentConfig(
wallet_enabled=True,
wallet_config=WalletConfig(mnemonic=mnemonic, account_index=2),
)
),
MarketerAgent(
web3_config=Web3AgentConfig(
wallet_enabled=True,
wallet_config=WalletConfig(mnemonic=mnemonic, account_index=3),
)
),
FrontendEngineerAgent(
web3_config=Web3AgentConfig(
wallet_enabled=True,
tee_enabled=True,
wallet_config=WalletConfig(mnemonic=mnemonic, account_index=4),
)
),
BackendEngineerAgent(
web3_config=Web3AgentConfig(
wallet_enabled=True,
tee_enabled=True,
wallet_config=WalletConfig(mnemonic=mnemonic, account_index=5),
)
),
]
# Give PM initial funds
agents[1].earnings = 2.0
# Create network
network = Web3Network(
state=state,
agents=agents,
router=StartupTeamRouter(),
shared_mnemonic=mnemonic,
marketplace=marketplace,
max_steps=10,
)
# Run the team collaboration
final_state = await network.run()
# Print results
print("\n" + "=" * 50)
print("📊 Project Summary:")
print(f"Product: {final_state.get('product_name')}")
print(
f"Architecture: {final_state.get('architecture', {}).get('backend')} + {final_state.get('architecture', {}).get('frontend')}"
)
print(
f"Design: {final_state.get('design_system', {}).get('pages_designed', 0)} pages designed"
)
print(
f"Frontend: {final_state.get('frontend_progress', {}).get('pages_completed', 0)} pages built"
)
print(
f"Backend: {final_state.get('backend_progress', {}).get('endpoints_created', 0)} endpoints"
)
print(
f"Deployment: {final_state.get('deployment_status', {}).get('health', 'Unknown')}"
)
# Show economics
print("\n💰 Team Economics:")
stats = network.get_agent_stats()
for agent_name, agent_stats in stats.items():
if "balance_eth" in agent_stats:
print(
f"{agent_name}: {agent_stats['balance_eth']:.3f} ETH "
f"(earned: {agent_stats['earnings']:.3f}, spent: {agent_stats['spending']:.3f})"
)
print(f"\nNetwork treasury: {network.network_treasury:.3f} ETH")
print(f"Execution hash: {network.execution_hash}")
if __name__ == "__main__":
asyncio.run(run_startup_team_demo())
File diff suppressed because it is too large Load Diff
-107
View File
@@ -1,107 +0,0 @@
"""Hanzo Agents SDK - Production-grade AI agent runtime with Web3 and TEE support."""
from hanzo_agents.core.tool import Tool, ToolRegistry
from hanzo_agents.core.agent import Agent, ToolCall, InferenceResult
from hanzo_agents.core.model import ModelRegistry, BaseModelAdapter
from hanzo_agents.core.state import State
from hanzo_agents.core.memory import (
MemoryKV,
MemoryVector,
create_memory_kv,
create_memory_vector,
)
from hanzo_agents.core.router import (
Router,
RouterFn,
LLMRouter,
HybridRouter,
DeterministicRouter,
sequential_router,
conditional_router,
state_based_router,
)
from hanzo_agents.core.history import History
from hanzo_agents.core.network import Network
# Web3 integration
try:
from hanzo_agents.core.wallet import (
AgentWallet,
Transaction,
WalletConfig,
create_wallet_tool,
derive_agent_wallet,
generate_shared_mnemonic,
)
WEB3_AVAILABLE = True
except ImportError:
WEB3_AVAILABLE = False
WalletConfig = None
AgentWallet = None
Transaction = None
generate_shared_mnemonic = None
derive_agent_wallet = None
create_wallet_tool = None
# TEE support
from hanzo_agents.core.tee import (
TEEConfig,
TEEProvider,
ComputeOffer,
ComputeRequest,
AttestationReport,
ConfidentialAgent,
ComputeMarketplace,
create_attestation_verifier_tool,
)
__version__ = "0.2.0"
__all__ = [
# Core classes
"Agent",
"Tool",
"State",
"Network",
"Router",
"History",
"MemoryKV",
"MemoryVector",
"BaseModelAdapter",
# Results
"InferenceResult",
# Types
"RouterFn",
"ToolCall",
# Routers
"DeterministicRouter",
"LLMRouter",
"HybridRouter",
"sequential_router",
"conditional_router",
"state_based_router",
# Memory functions
"create_memory_kv",
"create_memory_vector",
# Registries
"ToolRegistry",
"ModelRegistry",
# Web3 (if available)
"WalletConfig",
"AgentWallet",
"Transaction",
"generate_shared_mnemonic",
"derive_agent_wallet",
"create_wallet_tool",
"WEB3_AVAILABLE",
# TEE
"TEEProvider",
"TEEConfig",
"AttestationReport",
"ConfidentialAgent",
"ComputeMarketplace",
"ComputeOffer",
"ComputeRequest",
"create_attestation_verifier_tool",
]
-221
View File
@@ -1,221 +0,0 @@
"""Agent base class and implementation."""
import json
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Type, Generic, TypeVar, Optional
from datetime import datetime
from dataclasses import field, dataclass
from hanzo_agents.core.tool import Tool
from hanzo_agents.core.state import State
S = TypeVar("S", bound=State)
@dataclass
class ToolCall:
"""Represents a tool invocation."""
tool: str
arguments: Dict[str, Any]
id: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
return {"tool": self.tool, "arguments": self.arguments, "id": self.id}
@dataclass
class InferenceResult:
"""Result from agent inference."""
agent: str
content: Optional[str] = None
tool_calls: List[ToolCall] = field(default_factory=list)
metadata: Dict[str, Any] = field(default_factory=dict)
timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat())
def to_dict(self) -> Dict[str, Any]:
return {
"agent": self.agent,
"content": self.content,
"tool_calls": [tc.to_dict() for tc in self.tool_calls],
"metadata": self.metadata,
"timestamp": self.timestamp,
}
class Agent(ABC, Generic[S]):
"""Base class for all agents.
Agents encapsulate:
- A specific skill or domain
- Set of tools they can use
- Model configuration
- Prompting strategy
Agents MUST NOT have side effects outside of tool calls.
"""
name: str
description: str
tools: List[Tool[S]] = []
model: Optional[str] = None # e.g. "model://anthropic/claude-3-haiku"
def __init__(self, **kwargs):
"""Initialize agent with optional overrides."""
for k, v in kwargs.items():
if hasattr(self, k):
setattr(self, k, v)
@abstractmethod
async def run(
self, state: S, history: "History", network: "Network[S]"
) -> InferenceResult:
"""Execute agent inference.
Args:
state: Current network state
history: Conversation history
network: Network instance for context
Returns:
InferenceResult with content and/or tool calls
"""
pass
def get_tools(self) -> List[Tool[S]]:
"""Get available tools for this agent."""
return self.tools
def get_tool_schemas(self) -> List[Dict[str, Any]]:
"""Get JSON schemas for all tools."""
return [tool.get_schema() for tool in self.tools]
async def execute_tool(
self, tool_name: str, arguments: Dict[str, Any], network: "Network[S]"
) -> Any:
"""Execute a tool by name."""
for tool in self.tools:
if tool.name == tool_name:
return await tool(network, **arguments)
raise ValueError(f"Unknown tool: {tool_name}")
def format_system_prompt(self) -> str:
"""Format system prompt with agent info and tools."""
prompt = f"""You are {self.name}: {self.description}
You have access to the following tools:
"""
for tool in self.tools:
schema = tool.get_schema()
prompt += f"\n- {tool.name}: {tool.description}"
if schema.get("parameters", {}).get("properties"):
prompt += f"\n Parameters: {json.dumps(schema['parameters']['properties'], indent=2)}"
prompt += "\n\nRespond with your analysis and any tool calls needed."
return prompt
class AgentRegistry:
"""Registry for agent types."""
_agents: Dict[str, Type[Agent]] = {}
@classmethod
def register(cls, agent_class: Type[Agent], name: Optional[str] = None):
"""Register an agent class."""
name = name or agent_class.name
cls._agents[name] = agent_class
@classmethod
def get(cls, name: str) -> Optional[Type[Agent]]:
"""Get agent class by name."""
return cls._agents.get(name)
@classmethod
def list_agents(cls) -> List[str]:
"""List all registered agents."""
return list(cls._agents.keys())
@classmethod
def create(cls, name: str, **kwargs) -> Agent:
"""Create agent instance."""
agent_class = cls.get(name)
if not agent_class:
raise ValueError(f"Unknown agent: {name}")
return agent_class(**kwargs)
# Example base agents
class PlanningAgent(Agent[S]):
"""Agent specialized in creating plans."""
name = "planner"
description = "Creates and refines plans"
model = "model://anthropic/claude-3-haiku"
async def run(
self, state: S, history: "History", network: "Network[S]"
) -> InferenceResult:
"""Create or update plan based on state."""
# This would call the actual model
# For now, return a mock result
return InferenceResult(
agent=self.name,
content="I need to analyze the requirements and create a plan.",
tool_calls=[
ToolCall(
tool="think",
arguments={"thought": "Breaking down the problem into steps..."},
)
],
)
class ReviewAgent(Agent[S]):
"""Agent specialized in reviewing work."""
name = "reviewer"
description = "Reviews and validates work"
model = "model://openai/gpt-4"
async def run(
self, state: S, history: "History", network: "Network[S]"
) -> InferenceResult:
"""Review current state and provide feedback."""
return InferenceResult(
agent=self.name,
content="Reviewing the current progress and checking for issues.",
tool_calls=[],
)
# Persona wrapper for quick agent creation
def create_agent(
name: str,
description: str,
tools: List[Tool] = None,
model: str = None,
system_prompt: str = None,
) -> Type[Agent]:
"""Factory to create agent classes."""
class CustomAgent(Agent):
pass
CustomAgent.name = name
CustomAgent.description = description
CustomAgent.tools = tools or []
CustomAgent.model = model
if system_prompt:
CustomAgent._system_prompt = system_prompt
# Register the agent
AgentRegistry.register(CustomAgent, name)
return CustomAgent
@@ -1,255 +0,0 @@
"""CLI-based agent implementation for external tools."""
import os
import json
import asyncio
import tempfile
from typing import Any, Dict, List, Optional
from hanzo_agents.core.agent import Agent, ToolCall, InferenceResult
from hanzo_agents.core.state import State
from hanzo_agents.core.history import History
class CLIAgent(Agent):
"""Agent that uses external CLI tools.
Supports tools like:
- claude (Claude Code)
- openai (Codex)
- gemini (Google Gemini)
- grok (xAI Grok)
- cursor
- aider
- etc.
"""
cli_command: str # Base command to run
cli_args: List[str] = [] # Default arguments
def __init__(
self,
cli_command: Optional[str] = None,
cli_args: Optional[List[str]] = None,
working_dir: Optional[str] = None,
env: Optional[Dict[str, str]] = None,
**kwargs,
):
"""Initialize CLI agent.
Args:
cli_command: Override default CLI command
cli_args: Override default arguments
working_dir: Working directory for CLI
env: Environment variables
"""
super().__init__(**kwargs)
if cli_command:
self.cli_command = cli_command
if cli_args is not None:
self.cli_args = cli_args
self.working_dir = working_dir or os.getcwd()
self.env = os.environ.copy()
if env:
self.env.update(env)
async def run(
self, state: State, history: History, network: "Network"
) -> InferenceResult:
"""Execute CLI tool with current context."""
# Build prompt from history
prompt = self._build_prompt(state, history)
# Execute CLI
result = await self._execute_cli(prompt)
# Parse response
return self._parse_response(result)
def _build_prompt(self, state: State, history: History) -> str:
"""Build prompt from state and history."""
# Include state context
prompt_parts = [
f"Current state: {json.dumps(state.to_dict(), indent=2)}",
"",
"Conversation history:",
]
# Add recent history
for entry in history[-10:]: # Last 10 entries
if entry.role == "user":
prompt_parts.append(f"User: {entry.content}")
elif entry.role == "assistant" and entry.agent:
prompt_parts.append(f"{entry.agent}: {entry.content}")
# Add current task
prompt_parts.extend(
["", f"As {self.name}, {self.description}", "What should we do next?"]
)
return "\n".join(prompt_parts)
async def _execute_cli(self, prompt: str) -> Dict[str, Any]:
"""Execute the CLI tool."""
# Write prompt to temp file
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
f.write(prompt)
prompt_file = f.name
try:
# Build command
cmd = [self.cli_command] + self.cli_args
# Some tools accept prompt via stdin, others via file
if any(arg in ["-", "--stdin"] for arg in self.cli_args):
# Use stdin
process = await asyncio.create_subprocess_exec(
*cmd,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=self.working_dir,
env=self.env,
)
stdout, stderr = await process.communicate(prompt.encode())
else:
# Use file argument
cmd.append(prompt_file)
process = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=self.working_dir,
env=self.env,
)
stdout, stderr = await process.communicate()
return {
"stdout": stdout.decode() if stdout else "",
"stderr": stderr.decode() if stderr else "",
"returncode": process.returncode,
}
finally:
# Clean up temp file
try:
os.unlink(prompt_file)
except Exception:
pass
def _parse_response(self, result: Dict[str, Any]) -> InferenceResult:
"""Parse CLI output into inference result."""
if result["returncode"] != 0:
# Handle error
return InferenceResult(
agent=self.name,
content=f"Error: {result['stderr']}",
metadata={"cli_error": True},
)
# Parse stdout for response
output = result["stdout"]
# Try to detect tool calls in output
tool_calls = self._extract_tool_calls(output)
return InferenceResult(
agent=self.name,
content=output,
tool_calls=tool_calls,
metadata={"cli_output": True},
)
def _extract_tool_calls(self, output: str) -> List[ToolCall]:
"""Extract tool calls from CLI output.
Look for patterns like:
- TOOL: tool_name(arg1="value1", arg2="value2")
- @tool tool_name {"arg1": "value1"}
"""
tool_calls = []
# Simple pattern matching (extend as needed)
lines = output.split("\n")
for line in lines:
if line.startswith("TOOL:") or line.startswith("@tool"):
# Parse tool call
# This is simplified - real implementation would be more robust
parts = line.split(None, 2)
if len(parts) >= 3:
tool_name = parts[1]
try:
# Try to parse as JSON
args_str = parts[2]
if args_str.startswith("{"):
arguments = json.loads(args_str)
else:
# Simple key=value parsing
arguments = {}
# ... parse key=value pairs
tool_calls.append(ToolCall(tool=tool_name, arguments=arguments))
except Exception:
pass
return tool_calls
# Concrete CLI agent implementations
class ClaudeCodeAgent(CLIAgent):
"""Claude Code CLI agent."""
name = "claude_code"
description = "Claude Code AI assistant"
cli_command = "claude"
cli_args = ["--no-interactive"]
model = "model://anthropic/claude-3-5-sonnet-20241022"
class OpenAICodexAgent(CLIAgent):
"""OpenAI Codex/ChatGPT CLI agent."""
name = "openai_codex"
description = "OpenAI GPT code assistant"
cli_command = "openai"
cli_args = ["chat", "--model", "gpt-4"]
class GeminiAgent(CLIAgent):
"""Google Gemini CLI agent."""
name = "gemini"
description = "Google Gemini AI assistant"
cli_command = "gemini"
cli_args = ["--format", "json"]
class GrokAgent(CLIAgent):
"""xAI Grok CLI agent."""
name = "grok"
description = "xAI Grok assistant"
cli_command = "grok"
cli_args = ["--mode", "code"]
class CursorAgent(CLIAgent):
"""Cursor AI editor agent."""
name = "cursor"
description = "Cursor AI-powered editor"
cli_command = "cursor"
cli_args = ["--headless"]
class AiderAgent(CLIAgent):
"""Aider coding assistant agent."""
name = "aider"
description = "Aider AI pair programmer"
cli_command = "aider"
cli_args = ["--no-pretty", "--yes"]
@@ -1,167 +0,0 @@
"""History tracking for agent interactions."""
import json
from typing import Any, Dict, List, Optional
from datetime import datetime
from hanzo_agents.core.agent import ToolCall, InferenceResult
class HistoryEntry:
"""Single entry in conversation history."""
def __init__(
self,
role: str, # "user", "assistant", "system", "tool"
content: Any,
agent: Optional[str] = None,
tool_call: Optional[ToolCall] = None,
metadata: Optional[Dict[str, Any]] = None,
):
self.role = role
self.content = content
self.agent = agent
self.tool_call = tool_call
self.metadata = metadata or {}
self.timestamp = datetime.utcnow().isoformat()
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary."""
data = {
"role": self.role,
"content": self.content,
"timestamp": self.timestamp,
"metadata": self.metadata,
}
if self.agent:
data["agent"] = self.agent
if self.tool_call:
data["tool_call"] = self.tool_call.to_dict()
return data
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "HistoryEntry":
"""Create from dictionary."""
tool_call = None
if "tool_call" in data:
tc = data["tool_call"]
tool_call = ToolCall(
tool=tc["tool"], arguments=tc["arguments"], id=tc.get("id")
)
return cls(
role=data["role"],
content=data["content"],
agent=data.get("agent"),
tool_call=tool_call,
metadata=data.get("metadata", {}),
)
class History(List[HistoryEntry]):
"""Chronological log of agent interactions.
Extends list for easy iteration while adding helper methods.
"""
def add(self, role: str, content: Any, **kwargs):
"""Add entry to history."""
entry = HistoryEntry(role, content, **kwargs)
self.append(entry)
return entry
def add_inference(self, result: InferenceResult):
"""Add inference result to history."""
# Add main response
if result.content:
self.add(
role="assistant",
content=result.content,
agent=result.agent,
metadata=result.metadata,
)
# Add tool calls
for tool_call in result.tool_calls:
self.add(
role="assistant",
content=f"Calling tool: {tool_call.tool}",
agent=result.agent,
tool_call=tool_call,
)
def add_tool_result(self, tool: str, result: Any, agent: str = None):
"""Add tool execution result."""
self.add(role="tool", content=result, agent=agent, metadata={"tool": tool})
def last_message(self) -> Optional[str]:
"""Get last message content."""
for entry in reversed(self):
if entry.role in ["user", "assistant"] and entry.content:
return str(entry.content)
return None
def get_messages(self) -> List[Dict[str, Any]]:
"""Get messages in chat format."""
messages = []
for entry in self:
if entry.role in ["user", "assistant", "system"]:
msg = {"role": entry.role, "content": entry.content}
if entry.agent:
msg["name"] = entry.agent
messages.append(msg)
return messages
def diff_since(self, checkpoint: int) -> List[HistoryEntry]:
"""Get entries since checkpoint index."""
if checkpoint < 0 or checkpoint >= len(self):
return []
return list(self[checkpoint:])
def filter_by_agent(self, agent: str) -> List[HistoryEntry]:
"""Get entries from specific agent."""
return [e for e in self if e.agent == agent]
def filter_by_role(self, role: str) -> List[HistoryEntry]:
"""Get entries with specific role."""
return [e for e in self if e.role == role]
def to_json(self, indent: int = 2) -> str:
"""Export to JSON."""
data = [entry.to_dict() for entry in self]
return json.dumps(data, indent=indent)
@classmethod
def from_json(cls, json_str: str) -> "History":
"""Import from JSON."""
data = json.loads(json_str)
history = cls()
for item in data:
history.append(HistoryEntry.from_dict(item))
return history
def replay(self, until: Optional[int] = None) -> "History":
"""Create a copy up to a certain point."""
if until is None:
until = len(self)
new_history = History()
for i, entry in enumerate(self):
if i >= until:
break
new_history.append(entry)
return new_history
def summary(self) -> Dict[str, Any]:
"""Get summary statistics."""
return {
"total_entries": len(self),
"by_role": {
role: len(self.filter_by_role(role))
for role in ["user", "assistant", "system", "tool"]
},
"agents": list(set(e.agent for e in self if e.agent)),
"tool_calls": len([e for e in self if e.tool_call]),
"duration": (
self[-1].timestamp - self[0].timestamp if len(self) >= 2 else None
),
}
@@ -1,418 +0,0 @@
"""Decentralized marketplace for agent services and resources."""
import time
import uuid
from enum import Enum
from typing import Any, Dict, List, Optional
from dataclasses import field, dataclass
from .web3_agent import Web3Agent
class ServiceType(Enum):
"""Types of services agents can offer."""
DATA = "data"
COMPUTE = "compute"
INFERENCE = "inference"
ANALYSIS = "analysis"
CODING = "coding"
RESEARCH = "research"
CUSTOM = "custom"
@dataclass
class ServiceOffer:
"""Service offered by an agent."""
id: str
agent_address: str
agent_name: str
service_type: ServiceType
description: str
price_eth: float
min_reputation: float = 0.0
max_duration_hours: float = 24.0
requires_tee: bool = False
metadata: Dict[str, Any] = field(default_factory=dict)
created_at: float = field(default_factory=time.time)
expires_at: Optional[float] = None
def is_expired(self) -> bool:
"""Check if offer has expired."""
if self.expires_at is None:
return False
return time.time() > self.expires_at
def matches_request(self, request: "ServiceRequest") -> bool:
"""Check if offer matches a request."""
if self.is_expired():
return False
# Check service type
if request.service_type != ServiceType.CUSTOM:
if self.service_type != request.service_type:
return False
# Check price
if self.price_eth > request.max_price_eth:
return False
# Check duration
if request.duration_hours > self.max_duration_hours:
return False
# Check TEE requirement
if request.requires_tee and not self.requires_tee:
return False
return True
@dataclass
class ServiceRequest:
"""Request for a service."""
id: str
requester_address: str
requester_name: str
service_type: ServiceType
description: str
max_price_eth: float
duration_hours: float = 1.0
min_reputation: float = 0.0
requires_tee: bool = False
metadata: Dict[str, Any] = field(default_factory=dict)
created_at: float = field(default_factory=time.time)
expires_at: Optional[float] = None
def is_expired(self) -> bool:
"""Check if request has expired."""
if self.expires_at is None:
return False
return time.time() > self.expires_at
@dataclass
class ServiceMatch:
"""Matched offer and request."""
match_id: str
offer: ServiceOffer
request: ServiceRequest
agreed_price_eth: float
escrow_tx: Optional[str] = None
status: str = "pending" # pending, active, completed, disputed
created_at: float = field(default_factory=time.time)
completed_at: Optional[float] = None
result: Optional[Dict[str, Any]] = None
attestation: Optional[Dict[str, Any]] = None
class AgentMarketplace:
"""Marketplace for agent services."""
def __init__(self):
"""Initialize marketplace."""
self.offers: Dict[str, ServiceOffer] = {}
self.requests: Dict[str, ServiceRequest] = {}
self.matches: Dict[str, ServiceMatch] = {}
# Reputation tracking
self.reputation_scores: Dict[str, float] = {}
self.completed_transactions: Dict[str, int] = {}
# Statistics
self.total_volume_eth = 0.0
self.total_transactions = 0
def post_offer(
self,
agent: Web3Agent,
service_type: ServiceType,
description: str,
price_eth: float,
**kwargs,
) -> str:
"""Post a service offer.
Args:
agent: Agent posting the offer
service_type: Type of service
description: Service description
price_eth: Price in ETH
**kwargs: Additional offer parameters
Returns:
Offer ID
"""
offer_id = f"offer_{uuid.uuid4().hex[:8]}"
offer = ServiceOffer(
id=offer_id,
agent_address=agent.address or "mock_address",
agent_name=agent.name,
service_type=service_type,
description=description,
price_eth=price_eth,
min_reputation=kwargs.get("min_reputation", 0.0),
max_duration_hours=kwargs.get("max_duration_hours", 24.0),
requires_tee=kwargs.get("requires_tee", False),
metadata=kwargs.get("metadata", {}),
expires_at=kwargs.get("expires_at"),
)
self.offers[offer_id] = offer
# Try to match with existing requests
self._try_match_offers()
return offer_id
def post_request(
self,
agent: Web3Agent,
service_type: ServiceType,
description: str,
max_price_eth: float,
**kwargs,
) -> str:
"""Post a service request.
Args:
agent: Agent posting the request
service_type: Type of service needed
description: Service description
max_price_eth: Maximum price willing to pay
**kwargs: Additional request parameters
Returns:
Request ID
"""
request_id = f"request_{uuid.uuid4().hex[:8]}"
request = ServiceRequest(
id=request_id,
requester_address=agent.address or "mock_address",
requester_name=agent.name,
service_type=service_type,
description=description,
max_price_eth=max_price_eth,
duration_hours=kwargs.get("duration_hours", 1.0),
min_reputation=kwargs.get("min_reputation", 0.0),
requires_tee=kwargs.get("requires_tee", False),
metadata=kwargs.get("metadata", {}),
expires_at=kwargs.get("expires_at"),
)
self.requests[request_id] = request
# Try to match with existing offers
self._try_match_offers()
return request_id
def _try_match_offers(self):
"""Try to match offers with requests."""
# Remove expired items first
self._clean_expired()
# Try to match each request
for _req_id, request in list(self.requests.items()):
best_offer = None
best_price = float("inf")
# Find best matching offer
for _offer_id, offer in self.offers.items():
if offer.matches_request(request):
# Check reputation requirements
provider_rep = self.get_reputation(offer.agent_address)
if provider_rep < request.min_reputation:
continue
requester_rep = self.get_reputation(request.requester_address)
if requester_rep < offer.min_reputation:
continue
# Track best price
if offer.price_eth < best_price:
best_offer = offer
best_price = offer.price_eth
# Create match if found
if best_offer:
self._create_match(best_offer, request)
def _create_match(self, offer: ServiceOffer, request: ServiceRequest):
"""Create a match between offer and request."""
match_id = f"match_{uuid.uuid4().hex[:8]}"
# Agreed price is the offer price (could implement negotiation)
agreed_price = offer.price_eth
match = ServiceMatch(
match_id=match_id,
offer=offer,
request=request,
agreed_price_eth=agreed_price,
status="pending",
)
self.matches[match_id] = match
# Remove from active lists
del self.offers[offer.id]
del self.requests[request.id]
# Update statistics
self.total_transactions += 1
print(
f"Match created: {offer.agent_name} -> {request.requester_name} for {agreed_price} ETH"
)
def complete_match(
self,
match_id: str,
result: Dict[str, Any],
attestation: Optional[Dict[str, Any]] = None,
):
"""Mark a match as completed.
Args:
match_id: Match ID
result: Result of the service
attestation: Optional TEE attestation
"""
if match_id not in self.matches:
raise ValueError(f"Unknown match: {match_id}")
match = self.matches[match_id]
match.status = "completed"
match.completed_at = time.time()
match.result = result
match.attestation = attestation
# Update reputation
self._update_reputation(
match.offer.agent_address,
1.0, # Positive for completion
)
# Update statistics
self.total_volume_eth += match.agreed_price_eth
# Track completed transactions
provider = match.offer.agent_address
self.completed_transactions[provider] = (
self.completed_transactions.get(provider, 0) + 1
)
def dispute_match(self, match_id: str, reason: str):
"""Dispute a match.
Args:
match_id: Match ID
reason: Dispute reason
"""
if match_id not in self.matches:
raise ValueError(f"Unknown match: {match_id}")
match = self.matches[match_id]
match.status = "disputed"
match.result = {"dispute_reason": reason}
# Negative reputation for provider
self._update_reputation(match.offer.agent_address, -0.5)
def get_reputation(self, agent_address: str) -> float:
"""Get agent reputation score.
Args:
agent_address: Agent's blockchain address
Returns:
Reputation score (0-1)
"""
return self.reputation_scores.get(agent_address, 0.5)
def _update_reputation(self, agent_address: str, delta: float):
"""Update agent reputation.
Args:
agent_address: Agent's address
delta: Change in reputation
"""
current = self.get_reputation(agent_address)
new_score = max(0, min(1, current + delta * 0.1)) # Damped update
self.reputation_scores[agent_address] = new_score
def _clean_expired(self):
"""Remove expired offers and requests."""
# Clean offers
expired_offers = [
oid for oid, offer in self.offers.items() if offer.is_expired()
]
for oid in expired_offers:
del self.offers[oid]
# Clean requests
expired_requests = [
rid for rid, request in self.requests.items() if request.is_expired()
]
for rid in expired_requests:
del self.requests[rid]
def get_active_offers(
self, service_type: Optional[ServiceType] = None
) -> List[ServiceOffer]:
"""Get active offers, optionally filtered by type."""
self._clean_expired()
offers = list(self.offers.values())
if service_type:
offers = [o for o in offers if o.service_type == service_type]
return sorted(offers, key=lambda o: o.price_eth)
def get_active_requests(
self, service_type: Optional[ServiceType] = None
) -> List[ServiceRequest]:
"""Get active requests, optionally filtered by type."""
self._clean_expired()
requests = list(self.requests.values())
if service_type:
requests = [r for r in requests if r.service_type == service_type]
return sorted(requests, key=lambda r: r.max_price_eth, reverse=True)
def get_matches_for_agent(self, agent_address: str) -> List[ServiceMatch]:
"""Get all matches involving an agent."""
matches = []
for match in self.matches.values():
if (
match.offer.agent_address == agent_address
or match.request.requester_address == agent_address
):
matches.append(match)
return sorted(matches, key=lambda m: m.created_at, reverse=True)
def get_stats(self) -> Dict[str, Any]:
"""Get marketplace statistics."""
return {
"active_offers": len(self.offers),
"active_requests": len(self.requests),
"total_matches": len(self.matches),
"total_volume_eth": self.total_volume_eth,
"total_transactions": self.total_transactions,
"unique_providers": len(set(o.agent_address for o in self.offers.values())),
"unique_requesters": len(
set(r.requester_address for r in self.requests.values())
),
}
# Global marketplace instance (in production, this would be on-chain)
marketplace = AgentMarketplace()
@@ -1,236 +0,0 @@
"""Memory backends for long-term agent storage."""
import json
import asyncio
import sqlite3
from typing import Any, Dict, List, Optional, Protocol
from datetime import datetime
class MemoryKV(Protocol):
"""Key-value store protocol for long-term memory."""
async def save(self, key: str, value: Dict[str, Any]) -> None:
"""Save value with key."""
...
async def load(self, key: str) -> Optional[Dict[str, Any]]:
"""Load value by key."""
...
async def delete(self, key: str) -> bool:
"""Delete value by key."""
...
async def list_keys(self, prefix: Optional[str] = None) -> List[str]:
"""List all keys, optionally filtered by prefix."""
...
class MemoryVector(Protocol):
"""Vector store protocol for semantic search."""
async def add(
self, id: str, text: str, metadata: Optional[Dict[str, Any]] = None
) -> None:
"""Add text with metadata to vector store."""
...
async def query(self, text: str, k: int = 5) -> List[str]:
"""Query for similar texts."""
...
async def delete(self, id: str) -> bool:
"""Delete by ID."""
...
# Default implementations
class SQLiteKV(MemoryKV):
"""SQLite-based key-value store."""
def __init__(self, db_path: str = "memory.db"):
"""Initialize SQLite store."""
self.db_path = db_path
self._init_db()
def _init_db(self):
"""Initialize database schema."""
conn = sqlite3.connect(self.db_path)
conn.execute(
"""
CREATE TABLE IF NOT EXISTS kv_store (
key TEXT PRIMARY KEY,
value TEXT,
metadata TEXT,
created_at TIMESTAMP,
updated_at TIMESTAMP
)
"""
)
conn.commit()
conn.close()
async def save(self, key: str, value: Dict[str, Any]) -> None:
"""Save value."""
def _save():
conn = sqlite3.connect(self.db_path)
now = datetime.utcnow().isoformat()
conn.execute(
"""
INSERT OR REPLACE INTO kv_store (key, value, metadata, created_at, updated_at)
VALUES (?, ?, ?,
COALESCE((SELECT created_at FROM kv_store WHERE key = ?), ?),
?)
""",
(key, json.dumps(value), "{}", key, now, now),
)
conn.commit()
conn.close()
await asyncio.get_event_loop().run_in_executor(None, _save)
async def load(self, key: str) -> Optional[Dict[str, Any]]:
"""Load value."""
def _load():
conn = sqlite3.connect(self.db_path)
cursor = conn.execute("SELECT value FROM kv_store WHERE key = ?", (key,))
row = cursor.fetchone()
conn.close()
if row:
return json.loads(row[0])
return None
return await asyncio.get_event_loop().run_in_executor(None, _load)
async def delete(self, key: str) -> bool:
"""Delete value."""
def _delete():
conn = sqlite3.connect(self.db_path)
cursor = conn.execute("DELETE FROM kv_store WHERE key = ?", (key,))
deleted = cursor.rowcount > 0
conn.commit()
conn.close()
return deleted
return await asyncio.get_event_loop().run_in_executor(None, _delete)
async def list_keys(self, prefix: Optional[str] = None) -> List[str]:
"""List keys."""
def _list():
conn = sqlite3.connect(self.db_path)
if prefix:
cursor = conn.execute(
"SELECT key FROM kv_store WHERE key LIKE ?", (f"{prefix}%",)
)
else:
cursor = conn.execute("SELECT key FROM kv_store")
keys = [row[0] for row in cursor.fetchall()]
conn.close()
return keys
return await asyncio.get_event_loop().run_in_executor(None, _list)
class InMemoryKV(MemoryKV):
"""Simple in-memory key-value store."""
def __init__(self):
"""Initialize in-memory store."""
self._store: Dict[str, Dict[str, Any]] = {}
async def save(self, key: str, value: Dict[str, Any]) -> None:
"""Save value."""
self._store[key] = value.copy()
async def load(self, key: str) -> Optional[Dict[str, Any]]:
"""Load value."""
return self._store.get(key, {}).copy() if key in self._store else None
async def delete(self, key: str) -> bool:
"""Delete value."""
if key in self._store:
del self._store[key]
return True
return False
async def list_keys(self, prefix: Optional[str] = None) -> List[str]:
"""List keys."""
if prefix:
return [k for k in self._store.keys() if k.startswith(prefix)]
return list(self._store.keys())
class SimpleVectorMemory(MemoryVector):
"""Simple vector memory using basic similarity."""
def __init__(self):
"""Initialize vector store."""
self._store: Dict[str, Dict[str, Any]] = {}
async def add(
self, id: str, text: str, metadata: Optional[Dict[str, Any]] = None
) -> None:
"""Add text."""
self._store[id] = {
"text": text,
"metadata": metadata or {},
"timestamp": datetime.utcnow().isoformat(),
}
async def query(self, text: str, k: int = 5) -> List[str]:
"""Simple keyword-based search."""
# Very basic - just find texts containing query words
query_words = set(text.lower().split())
scores = []
for id, item in self._store.items():
item_words = set(item["text"].lower().split())
# Jaccard similarity
score = (
len(query_words & item_words) / len(query_words | item_words)
if query_words | item_words
else 0
)
scores.append((score, id, item["text"]))
# Sort by score and return top k
scores.sort(reverse=True, key=lambda x: x[0])
return [text for _, _, text in scores[:k]]
async def delete(self, id: str) -> bool:
"""Delete by ID."""
if id in self._store:
del self._store[id]
return True
return False
# Factory functions
def create_memory_kv(backend: str = "sqlite", **kwargs) -> MemoryKV:
"""Create memory KV store."""
if backend == "sqlite":
return SQLiteKV(**kwargs)
elif backend == "memory":
return InMemoryKV(**kwargs)
else:
raise ValueError(f"Unknown KV backend: {backend}")
def create_memory_vector(backend: str = "simple", **kwargs) -> MemoryVector:
"""Create vector memory store."""
if backend == "simple":
return SimpleVectorMemory(**kwargs)
# Add more backends as needed (faiss, chromadb, etc.)
else:
raise ValueError(f"Unknown vector backend: {backend}")
-183
View File
@@ -1,183 +0,0 @@
"""Model adapters for different LLM providers."""
import os
import asyncio
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional
from urllib.parse import urlparse
class BaseModelAdapter(ABC):
"""Base adapter for LLM models."""
name: str
@abstractmethod
async def complete(self, prompt: str, **kwargs) -> str:
"""Simple completion API."""
pass
@abstractmethod
async def chat(self, messages: List[Dict[str, Any]], **kwargs) -> str:
"""Chat completion API."""
pass
def get_config(self) -> Dict[str, Any]:
"""Get model configuration."""
return {"name": self.name, "type": self.__class__.__name__}
class ModelRegistry:
"""Global registry for model adapters."""
_adapters: Dict[str, BaseModelAdapter] = {}
_providers: Dict[str, type[BaseModelAdapter]] = {}
@classmethod
def register_provider(cls, name: str, adapter_class: type[BaseModelAdapter]):
"""Register a provider adapter class."""
cls._providers[name] = adapter_class
@classmethod
def get_adapter(cls, model_uri: str, **kwargs) -> BaseModelAdapter:
"""Get or create adapter for model URI.
URI format: model://provider/model-name
Examples:
- model://anthropic/claude-3-opus
- model://openai/gpt-4
- model://local/llama-70b
"""
if model_uri in cls._adapters:
return cls._adapters[model_uri]
# Parse URI
parsed = urlparse(model_uri)
if parsed.scheme != "model":
raise ValueError(f"Invalid model URI: {model_uri}")
provider = parsed.netloc
model_name = parsed.path.lstrip("/")
# Create adapter
if provider not in cls._providers:
raise ValueError(f"Unknown provider: {provider}")
adapter_class = cls._providers[provider]
adapter = adapter_class(model_name, **kwargs)
# Cache it
cls._adapters[model_uri] = adapter
return adapter
# Mock adapters for demonstration
class MockAdapter(BaseModelAdapter):
"""Mock adapter for testing."""
def __init__(self, model_name: str, **kwargs):
self.name = f"mock/{model_name}"
async def complete(self, prompt: str, **kwargs) -> str:
"""Mock completion."""
await asyncio.sleep(0.1) # Simulate API call
return f"Mock response to: {prompt[:50]}..."
async def chat(self, messages: List[Dict[str, Any]], **kwargs) -> str:
"""Mock chat."""
await asyncio.sleep(0.1)
last_message = messages[-1]["content"] if messages else "No message"
return f"Mock chat response to: {last_message[:50]}..."
class AnthropicAdapter(BaseModelAdapter):
"""Adapter for Anthropic models."""
def __init__(self, model_name: str, api_key: Optional[str] = None):
self.name = f"anthropic/{model_name}"
self.api_key = api_key or os.getenv("ANTHROPIC_API_KEY")
self.model = model_name
async def complete(self, prompt: str, **kwargs) -> str:
"""Anthropic completion."""
# In real implementation, use anthropic SDK
return await self._mock_call(prompt, **kwargs)
async def chat(self, messages: List[Dict[str, Any]], **kwargs) -> str:
"""Anthropic chat."""
# In real implementation, use anthropic SDK
return await self._mock_call(str(messages), **kwargs)
async def _mock_call(self, content: str, **kwargs) -> str:
"""Mock API call."""
await asyncio.sleep(0.2)
return f"Claude says: Responding to {content[:30]}..."
class OpenAIAdapter(BaseModelAdapter):
"""Adapter for OpenAI models."""
def __init__(self, model_name: str, api_key: Optional[str] = None):
self.name = f"openai/{model_name}"
self.api_key = api_key or os.getenv("OPENAI_API_KEY")
self.model = model_name
async def complete(self, prompt: str, **kwargs) -> str:
"""OpenAI completion."""
# In real implementation, use openai SDK
return await self._mock_call(prompt, **kwargs)
async def chat(self, messages: List[Dict[str, Any]], **kwargs) -> str:
"""OpenAI chat."""
# In real implementation, use openai SDK
return await self._mock_call(str(messages), **kwargs)
async def _mock_call(self, content: str, **kwargs) -> str:
"""Mock API call."""
await asyncio.sleep(0.15)
return f"GPT says: Processing {content[:30]}..."
class LocalAdapter(BaseModelAdapter):
"""Adapter for local models (llama.cpp, etc.)."""
def __init__(self, model_name: str, model_path: Optional[str] = None):
self.name = f"local/{model_name}"
self.model_path = model_path
async def complete(self, prompt: str, **kwargs) -> str:
"""Local model completion."""
# In real implementation, use llama-cpp-python or similar
await asyncio.sleep(0.5) # Simulate local inference
return f"Local model says: {prompt[:40]}..."
async def chat(self, messages: List[Dict[str, Any]], **kwargs) -> str:
"""Local model chat."""
await asyncio.sleep(0.5)
return f"Local chat: {str(messages[-1])[:40]}..."
# Register default providers
ModelRegistry.register_provider("mock", MockAdapter)
ModelRegistry.register_provider("anthropic", AnthropicAdapter)
ModelRegistry.register_provider("openai", OpenAIAdapter)
ModelRegistry.register_provider("local", LocalAdapter)
# Helper functions
async def call_model(model_uri: str, prompt: str, **kwargs) -> str:
"""Simple helper to call a model."""
adapter = ModelRegistry.get_adapter(model_uri)
return await adapter.complete(prompt, **kwargs)
async def chat_with_model(
model_uri: str, messages: List[Dict[str, Any]], **kwargs
) -> str:
"""Simple helper for chat completion."""
adapter = ModelRegistry.get_adapter(model_uri)
return await adapter.chat(messages, **kwargs)
@@ -1,259 +0,0 @@
"""Network orchestration for agent execution."""
import json
from typing import Dict, List, Type, Union, Generic, TypeVar, Optional
from pathlib import Path
from datetime import datetime
import structlog
from prometheus_client import Gauge, Counter, Histogram
from hanzo_agents.core.agent import Agent, ToolCall, InferenceResult
from hanzo_agents.core.state import State, StateGuard, StateHistory
from hanzo_agents.core.memory import MemoryKV, MemoryVector
from hanzo_agents.core.router import Router, RouterFn, DeterministicRouter
from hanzo_agents.core.history import History
S = TypeVar("S", bound=State)
# Metrics
router_iterations = Counter("hanzo_router_iterations_total", "Total router iterations")
agent_invocations = Counter(
"hanzo_agent_invocations_total", "Total agent invocations", ["agent"]
)
network_errors = Counter("hanzo_network_errors_total", "Total network errors", ["type"])
execution_time = Histogram("hanzo_execution_time_seconds", "Execution time", ["phase"])
active_networks = Gauge("hanzo_active_networks", "Number of active networks")
logger = structlog.get_logger()
class Network(Generic[S]):
"""Main orchestration class for agent networks.
Responsibilities:
1. Owns the state instance
2. Executes the router after each agent call
3. Maintains history
4. Emits telemetry
"""
def __init__(
self,
*,
state: S,
agents: List[Type[Agent[S]]],
router: Union[Router, RouterFn[S]],
memory_kv: Optional[MemoryKV] = None,
memory_vector: Optional[MemoryVector] = None,
max_steps: int = 100,
checkpoint_dir: Optional[Path] = None,
):
"""Initialize network.
Args:
state: Initial state
agents: Available agent types
router: Router instance or function
memory_kv: Optional KV store
memory_vector: Optional vector store
max_steps: Maximum execution steps
checkpoint_dir: Directory for checkpoints
"""
self.state = state
self.agents = agents
self.router = (
router if isinstance(router, Router) else DeterministicRouter(router)
)
self.memory_kv = memory_kv
self.memory_vector = memory_vector
self.max_steps = max_steps
self.checkpoint_dir = Path(checkpoint_dir) if checkpoint_dir else None
# Runtime state
self.history = History()
self.state_history = StateHistory()
self.call_count = 0
self.last_result: Optional[InferenceResult] = None
self._running = False
self._agent_instances: Dict[str, Agent[S]] = {}
# Record initial state
self.state_history.record(state, "initial")
# Metrics
active_networks.inc()
def __del__(self):
"""Cleanup on deletion."""
active_networks.dec()
async def run(self) -> S:
"""Execute the network until completion.
Returns:
Final state
"""
if self._running:
raise RuntimeError("Network already running")
self._running = True
start_time = datetime.utcnow()
try:
while self.call_count < self.max_steps:
# Router decision
with execution_time.labels(phase="routing").time():
next_agent_type = await self._route()
if next_agent_type is None:
logger.info(
"Router returned None, stopping", call_count=self.call_count
)
break
# Execute agent
with execution_time.labels(phase="agent").time():
result = await self._execute_agent(next_agent_type)
self.last_result = result
self.call_count += 1
router_iterations.inc()
# Checkpoint if configured
if self.checkpoint_dir and self.call_count % 10 == 0:
await self._checkpoint()
# Final checkpoint
if self.checkpoint_dir:
await self._checkpoint(final=True)
duration = (datetime.utcnow() - start_time).total_seconds()
logger.info(
"Network execution completed",
call_count=self.call_count,
duration=duration,
final_state=self.state.to_dict(),
)
except Exception as e:
network_errors.labels(type=type(e).__name__).inc()
logger.error("Network execution failed", error=str(e), exc_info=True)
raise
finally:
self._running = False
return self.state
async def _route(self) -> Optional[Type[Agent[S]]]:
"""Execute router to get next agent."""
if hasattr(self.router, "route_async"):
return await self.router.route_async(
self, self.call_count, self.last_result, self.agents
)
else:
return self.router.route(
self, self.call_count, self.last_result, self.agents
)
async def _execute_agent(self, agent_type: Type[Agent[S]]) -> InferenceResult:
"""Execute a single agent."""
# Get or create agent instance
agent_name = agent_type.name
if agent_name not in self._agent_instances:
self._agent_instances[agent_name] = agent_type()
agent = self._agent_instances[agent_name]
agent_invocations.labels(agent=agent_name).inc()
logger.info("Executing agent", agent=agent_name, state=self.state.to_dict())
try:
# Run agent inference
with StateGuard(self.state):
result = await agent.run(self.state, self.history, self)
# Record in history
self.history.add_inference(result)
# Execute any tool calls
for tool_call in result.tool_calls:
await self._execute_tool(agent, tool_call)
# Record state change
self.state_history.record(self.state, f"after_{agent_name}")
return result
except Exception as e:
logger.error("Agent execution failed", agent=agent_name, error=str(e))
raise
async def _execute_tool(self, agent: Agent[S], tool_call: ToolCall):
"""Execute a tool call."""
logger.info("Executing tool", tool=tool_call.tool, args=tool_call.arguments)
try:
# Execute tool
result = await agent.execute_tool(tool_call.tool, tool_call.arguments, self)
# Record result
self.history.add_tool_result(tool_call.tool, result, agent.name)
logger.info(
"Tool execution completed",
tool=tool_call.tool,
result=str(result)[:100],
)
except Exception as e:
logger.error("Tool execution failed", tool=tool_call.tool, error=str(e))
self.history.add_tool_result(tool_call.tool, f"Error: {e}", agent.name)
async def _checkpoint(self, final: bool = False):
"""Save checkpoint."""
if not self.checkpoint_dir:
return
self.checkpoint_dir.mkdir(exist_ok=True)
checkpoint_data = {
"state": self.state.checkpoint(),
"history": [entry.to_dict() for entry in self.history],
"call_count": self.call_count,
"timestamp": datetime.utcnow().isoformat(),
"final": final,
}
filename = f"checkpoint_{self.call_count:04d}.json"
if final:
filename = "checkpoint_final.json"
path = self.checkpoint_dir / filename
with open(path, "w") as f:
json.dump(checkpoint_data, f, indent=2)
logger.info("Checkpoint saved", path=str(path))
@classmethod
async def restore(cls, checkpoint_path: Path, **kwargs) -> "Network[S]":
"""Restore network from checkpoint."""
with open(checkpoint_path) as f:
data = json.load(f)
# Restore state
state_class = kwargs.get("state_class", State)
state = state_class.restore(data["state"])
# Create network
network = cls(state=state, **kwargs)
# Restore history
for entry_data in data["history"]:
network.history.append(History.from_dict(entry_data))
network.call_count = data["call_count"]
logger.info("Network restored from checkpoint", path=str(checkpoint_path))
return network
@@ -1,235 +0,0 @@
"""Router system for agent orchestration."""
from abc import ABC, abstractmethod
from enum import Enum
from typing import Any, Dict, List, Type, TypeVar, Callable, Optional
from hanzo_agents.core.agent import Agent, InferenceResult
from hanzo_agents.core.state import State
S = TypeVar("S", bound=State)
# Router function type
RouterFn = Callable[
["Network[S]", int, Optional[InferenceResult], List[Type[Agent[S]]]],
Optional[Type[Agent[S]]],
]
class RouterType(Enum):
"""Types of routers."""
DETERMINISTIC = "deterministic"
LLM_BASED = "llm_based"
HYBRID = "hybrid"
class Router(ABC):
"""Base router class."""
router_type: RouterType = RouterType.DETERMINISTIC
@abstractmethod
def route(
self,
network: "Network[S]",
call_count: int,
last_result: Optional[InferenceResult],
agent_stack: List[Type[Agent[S]]],
) -> Optional[Type[Agent[S]]]:
"""Determine next agent to run.
Args:
network: Current network instance
call_count: Number of agents called so far
last_result: Result from previous agent
agent_stack: Available agents
Returns:
Next agent class or None to stop
"""
pass
class DeterministicRouter(Router):
"""Code-based deterministic router."""
router_type = RouterType.DETERMINISTIC
def __init__(self, routing_fn: RouterFn[S]):
"""Initialize with routing function."""
self.routing_fn = routing_fn
def route(self, network, call_count, last_result, agent_stack):
"""Use provided function for routing."""
return self.routing_fn(network, call_count, last_result, agent_stack)
class LLMRouter(Router):
"""LLM-based router for dynamic decisions."""
router_type = RouterType.LLM_BASED
def __init__(self, model: str = "model://anthropic/claude-3-haiku"):
"""Initialize with model."""
self.model = model
async def route_async(self, network, call_count, last_result, agent_stack):
"""Async routing with LLM call."""
# Build context
context = {
"state": network.state.to_dict(),
"call_count": call_count,
"last_agent": last_result.agent if last_result else None,
"available_agents": [
{"name": agent.name, "description": agent.description}
for agent in agent_stack
],
}
# Call LLM (mock for now)
# In real implementation, this would use the model adapter
prompt = f"""Given the current state and available agents, which agent should run next?
State: {context["state"]}
Last agent: {context["last_agent"]}
Available agents: {context["available_agents"]}
Respond with just the agent name or 'none' to stop."""
# Mock response
next_agent_name = "none" # Would come from LLM
if next_agent_name == "none":
return None
# Find agent by name
for agent in agent_stack:
if agent.name == next_agent_name:
return agent
return None
def route(self, network, call_count, last_result, agent_stack):
"""Sync wrapper for compatibility."""
# In practice, this would be handled by the network's async context
import asyncio
return asyncio.create_task(
self.route_async(network, call_count, last_result, agent_stack)
)
class HybridRouter(Router):
"""Combines deterministic and LLM routing."""
router_type = RouterType.HYBRID
def __init__(
self,
deterministic_fn: Optional[RouterFn[S]] = None,
llm_model: str = "model://anthropic/claude-3-haiku",
llm_threshold: float = 0.7,
):
"""Initialize hybrid router.
Args:
deterministic_fn: Primary routing function
llm_model: Model for fallback routing
llm_threshold: Confidence threshold for LLM routing
"""
self.deterministic_fn = deterministic_fn
self.llm_router = LLMRouter(llm_model)
self.llm_threshold = llm_threshold
def route(self, network, call_count, last_result, agent_stack):
"""Try deterministic first, fall back to LLM."""
# Try deterministic routing
if self.deterministic_fn:
next_agent = self.deterministic_fn(
network, call_count, last_result, agent_stack
)
if next_agent is not None:
return next_agent
# Fall back to LLM
return self.llm_router.route(network, call_count, last_result, agent_stack)
# Common routing patterns
def sequential_router(agent_sequence: List[Type[Agent[S]]]) -> RouterFn[S]:
"""Create router that runs agents in sequence."""
def router(network, call_count, last_result, agent_stack):
if call_count < len(agent_sequence):
return agent_sequence[call_count]
return None
return router
def conditional_router(conditions: Dict[str, Type[Agent[S]]]) -> RouterFn[S]:
"""Create router based on state conditions.
Args:
conditions: Dict mapping condition names to agents
Example:
router = conditional_router({
"needs_plan": PlanningAgent,
"needs_review": ReviewAgent,
"needs_test": TestingAgent
})
"""
def router(network, call_count, last_result, agent_stack):
state_dict = network.state.to_dict()
# Check each condition
for condition, agent in conditions.items():
if state_dict.get(condition, False):
return agent
return None
return router
def round_robin_router(
agents: List[Type[Agent[S]]], max_rounds: int = 10
) -> RouterFn[S]:
"""Create router that cycles through agents."""
def router(network, call_count, last_result, agent_stack):
if call_count >= max_rounds * len(agents):
return None
return agents[call_count % len(agents)]
return router
def state_based_router(
state_field: str,
transitions: Dict[Any, Type[Agent[S]]],
default: Optional[Type[Agent[S]]] = None,
) -> RouterFn[S]:
"""Create router based on state field value.
Args:
state_field: Name of state field to check
transitions: Mapping of field values to agents
default: Default agent if no match
"""
def router(network, call_count, last_result, agent_stack):
state_dict = network.state.to_dict()
value = state_dict.get(state_field)
if value in transitions:
return transitions[value]
return default
return router
-141
View File
@@ -1,141 +0,0 @@
"""State management for agent networks."""
from typing import Any, Dict, TypeVar, Optional
from datetime import datetime
from dataclasses import field, dataclass
from pydantic import BaseModel
class State:
"""Base class for network state.
State should be:
- Strongly typed (dataclass or Pydantic)
- JSON serializable
- Validated on every write
Example:
@dataclass
class ProjectState(State):
repo: str
plan: Optional[Plan] = None
tests_passed: bool = False
done: bool = False
"""
def to_dict(self) -> Dict[str, Any]:
"""Convert state to dictionary."""
if isinstance(self, BaseModel):
return self.model_dump()
elif hasattr(self, "__dataclass_fields__"):
return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
else:
return self.__dict__.copy()
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "State":
"""Create state from dictionary."""
if issubclass(cls, BaseModel):
return cls.model_validate(data)
else:
return cls(**data)
def validate(self) -> None:
"""Validate state integrity."""
if isinstance(self, BaseModel):
# Pydantic models self-validate
pass
else:
# Override in subclasses for custom validation
pass
def checkpoint(self) -> Dict[str, Any]:
"""Create checkpoint data."""
return {
"state": self.to_dict(),
"timestamp": datetime.utcnow().isoformat(),
"type": self.__class__.__name__,
}
@classmethod
def restore(cls, checkpoint: Dict[str, Any]) -> "State":
"""Restore from checkpoint."""
return cls.from_dict(checkpoint["state"])
# Type variable for generic state
S = TypeVar("S", bound=State)
@dataclass
class BaseState(State):
"""Common base state with standard fields."""
done: bool = False
error: Optional[str] = None
metadata: Dict[str, Any] = field(default_factory=dict)
class StateGuard:
"""Guards state mutations for safety."""
def __init__(self, state: State):
self._state = state
self._original = state.to_dict()
def __enter__(self):
return self._state
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
# Rollback on exception
for k, v in self._original.items():
setattr(self._state, k, v)
else:
# Validate on success
self._state.validate()
class StateHistory:
"""Track state changes over time."""
def __init__(self):
self._history: list[Dict[str, Any]] = []
def record(self, state: State, event: str = "update"):
"""Record state snapshot."""
self._history.append(
{
"timestamp": datetime.utcnow().isoformat(),
"event": event,
"state": state.to_dict(),
}
)
def get_history(self) -> list[Dict[str, Any]]:
"""Get full history."""
return self._history.copy()
def diff(self, index1: int, index2: int) -> Dict[str, Any]:
"""Compare two state snapshots."""
if index1 >= len(self._history) or index2 >= len(self._history):
raise IndexError("Invalid history indices")
state1 = self._history[index1]["state"]
state2 = self._history[index2]["state"]
diff = {"added": {}, "removed": {}, "changed": {}}
# Find changes
for k, v in state2.items():
if k not in state1:
diff["added"][k] = v
elif state1[k] != v:
diff["changed"][k] = {"from": state1[k], "to": v}
for k in state1:
if k not in state2:
diff["removed"][k] = state1[k]
return diff
-398
View File
@@ -1,398 +0,0 @@
"""Trusted Execution Environment (TEE) support for confidential agent computing.
This module provides interfaces for running agents in secure enclaves,
enabling confidential AI computations with attestation capabilities.
"""
import json
import time
import hashlib
from abc import ABC, abstractmethod
from enum import Enum
from typing import Any, Dict, List, Optional
from dataclasses import field, dataclass
class TEEProvider(Enum):
"""Supported TEE providers."""
INTEL_SGX = "sgx"
AMD_SEV = "sev"
NVIDIA_H100 = "h100"
MOCK = "mock"
@dataclass
class AttestationReport:
"""TEE attestation report."""
provider: TEEProvider
enclave_id: str
code_hash: str
timestamp: float
quote: bytes
signature: str
metadata: Dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary."""
return {
"provider": self.provider.value,
"enclave_id": self.enclave_id,
"code_hash": self.code_hash,
"timestamp": self.timestamp,
"quote": self.quote.hex() if isinstance(self.quote, bytes) else self.quote,
"signature": self.signature,
"metadata": self.metadata,
}
def verify(self, expected_code_hash: Optional[str] = None) -> bool:
"""Verify attestation report.
In a real implementation, this would verify the TEE provider's
signature on the quote and optionally check code hash.
"""
if expected_code_hash and self.code_hash != expected_code_hash:
return False
# TODO: Implement actual verification based on provider
# For now, just check signature format
return len(self.signature) > 0
@dataclass
class TEEConfig:
"""Configuration for TEE execution."""
provider: TEEProvider = TEEProvider.MOCK
max_memory_mb: int = 4096
max_execution_time_s: int = 300
enable_network: bool = False
allowed_endpoints: List[str] = field(default_factory=list)
attestation_server: Optional[str] = None
class TEEExecutor(ABC):
"""Abstract interface for TEE execution."""
@abstractmethod
def execute(
self, code: str, inputs: Dict[str, Any], config: TEEConfig
) -> Dict[str, Any]:
"""Execute code in TEE with given inputs."""
pass
@abstractmethod
def get_attestation(self) -> AttestationReport:
"""Get attestation report for current execution."""
pass
@abstractmethod
def verify_remote_attestation(
self, report: AttestationReport, expected_code_hash: Optional[str] = None
) -> bool:
"""Verify a remote attestation report."""
pass
class MockTEEExecutor(TEEExecutor):
"""Mock TEE executor for testing."""
def __init__(self):
"""Initialize mock TEE."""
self.enclave_id = "mock_enclave_" + str(int(time.time()))
self.last_code_hash = None
self.last_result = None
def execute(
self, code: str, inputs: Dict[str, Any], config: TEEConfig
) -> Dict[str, Any]:
"""Execute code in mock TEE."""
# Compute code hash
self.last_code_hash = hashlib.sha256(code.encode()).hexdigest()
# Simulate execution
# In real TEE, this would run in isolated enclave
namespace = {"inputs": inputs, "result": None}
try:
exec(code, namespace)
self.last_result = namespace.get("result", {})
return {
"success": True,
"result": self.last_result,
"attestation": self.get_attestation().to_dict(),
}
except Exception as e:
return {
"success": False,
"error": str(e),
"attestation": self.get_attestation().to_dict(),
}
def get_attestation(self) -> AttestationReport:
"""Get attestation for last execution."""
return AttestationReport(
provider=TEEProvider.MOCK,
enclave_id=self.enclave_id,
code_hash=self.last_code_hash or "",
timestamp=time.time(),
quote=b"mock_quote_data",
signature="mock_signature_" + (self.last_code_hash or "")[:16],
metadata={
"mock": True,
"result_hash": (
hashlib.sha256(
json.dumps(self.last_result, sort_keys=True).encode()
).hexdigest()
if self.last_result
else None
),
},
)
def verify_remote_attestation(
self, report: AttestationReport, expected_code_hash: Optional[str] = None
) -> bool:
"""Verify mock attestation."""
return report.verify(expected_code_hash)
class ConfidentialAgent:
"""Agent that can execute in TEE for confidential computing."""
def __init__(
self,
agent,
tee_config: Optional[TEEConfig] = None,
tee_executor: Optional[TEEExecutor] = None,
):
"""Initialize confidential agent.
Args:
agent: Base agent to wrap
tee_config: TEE configuration
tee_executor: TEE executor implementation
"""
self.agent = agent
self.tee_config = tee_config or TEEConfig()
self.tee_executor = tee_executor or MockTEEExecutor()
self.attestation_history: List[AttestationReport] = []
def execute_confidential(
self,
task_code: str,
inputs: Dict[str, Any],
verify_code_hash: Optional[str] = None,
) -> Dict[str, Any]:
"""Execute task in TEE.
Args:
task_code: Python code to execute
inputs: Input data for the task
verify_code_hash: Expected code hash for verification
Returns:
Execution result with attestation
"""
# Execute in TEE
result = self.tee_executor.execute(task_code, inputs, self.tee_config)
# Get and store attestation
if result.get("success"):
attestation = AttestationReport(**result["attestation"])
self.attestation_history.append(attestation)
# Verify if requested
if verify_code_hash:
if not attestation.verify(verify_code_hash):
result["warning"] = "Code hash mismatch"
return result
def verify_computation(
self, result: Dict[str, Any], expected_code_hash: Optional[str] = None
) -> bool:
"""Verify a computation result came from valid TEE.
Args:
result: Result dict with attestation
expected_code_hash: Expected code hash
Returns:
True if verification passes
"""
if "attestation" not in result:
return False
attestation = AttestationReport(**result["attestation"])
return self.tee_executor.verify_remote_attestation(
attestation, expected_code_hash
)
def get_attestation_history(self) -> List[AttestationReport]:
"""Get history of attestations."""
return self.attestation_history.copy()
# Precompiled functions for on-chain TEE verification
def create_attestation_verifier_tool():
"""Create a tool for verifying TEE attestations."""
from ..tool import Tool
class AttestationVerifierTool(Tool):
"""Tool for verifying TEE attestations."""
def __init__(self):
self.name = "verify_attestation"
self.description = "Verify TEE attestation reports"
async def verify_attestation(
self,
attestation_dict: Dict[str, Any],
expected_code_hash: Optional[str] = None,
) -> Dict[str, Any]:
"""Verify a TEE attestation.
Args:
attestation_dict: Attestation report as dict
expected_code_hash: Expected code hash
Returns:
Verification result
"""
try:
report = AttestationReport(**attestation_dict)
is_valid = report.verify(expected_code_hash)
return {
"valid": is_valid,
"provider": report.provider.value,
"enclave_id": report.enclave_id,
"code_hash": report.code_hash,
"timestamp": report.timestamp,
}
except Exception as e:
return {"valid": False, "error": str(e)}
async def compare_attestations(
self, attestation1: Dict[str, Any], attestation2: Dict[str, Any]
) -> Dict[str, Any]:
"""Compare two attestation reports.
Args:
attestation1: First attestation
attestation2: Second attestation
Returns:
Comparison result
"""
try:
report1 = AttestationReport(**attestation1)
report2 = AttestationReport(**attestation2)
return {
"same_provider": report1.provider == report2.provider,
"same_enclave": report1.enclave_id == report2.enclave_id,
"same_code": report1.code_hash == report2.code_hash,
"time_diff": abs(report1.timestamp - report2.timestamp),
}
except Exception as e:
return {"error": str(e)}
return AttestationVerifierTool
# Computation marketplace for TEE resources
@dataclass
class ComputeOffer:
"""Offer to provide computation resources."""
provider_address: str
provider_enclave_id: str
price_per_second: float # In ETH
max_duration: int # Seconds
supported_providers: List[TEEProvider]
attestation: Optional[AttestationReport] = None
@dataclass
class ComputeRequest:
"""Request for computation resources."""
requester_address: str
code_hash: str
max_price_per_second: float
required_duration: int
required_provider: Optional[TEEProvider] = None
class ComputeMarketplace:
"""Marketplace for TEE compute resources."""
def __init__(self):
"""Initialize marketplace."""
self.offers: Dict[str, ComputeOffer] = {}
self.requests: Dict[str, ComputeRequest] = {}
self.matches: List[Dict[str, Any]] = []
def post_offer(self, offer: ComputeOffer) -> str:
"""Post a compute offer."""
offer_id = f"offer_{len(self.offers)}"
self.offers[offer_id] = offer
self._try_match_offers()
return offer_id
def post_request(self, request: ComputeRequest) -> str:
"""Post a compute request."""
request_id = f"request_{len(self.requests)}"
self.requests[request_id] = request
self._try_match_offers()
return request_id
def _try_match_offers(self):
"""Try to match offers with requests."""
for req_id, request in list(self.requests.items()):
for offer_id, offer in list(self.offers.items()):
# Check if offer matches request
if (
offer.price_per_second <= request.max_price_per_second
and offer.max_duration >= request.required_duration
and (
not request.required_provider
or request.required_provider in offer.supported_providers
)
):
# Create match
match = {
"request_id": req_id,
"offer_id": offer_id,
"provider": offer.provider_address,
"requester": request.requester_address,
"price_per_second": offer.price_per_second,
"duration": request.required_duration,
"total_price": offer.price_per_second
* request.required_duration,
"timestamp": time.time(),
}
self.matches.append(match)
# Remove matched items
del self.requests[req_id]
del self.offers[offer_id]
break
def get_matches(self) -> List[Dict[str, Any]]:
"""Get all matches."""
return self.matches.copy()
def get_active_offers(self) -> Dict[str, ComputeOffer]:
"""Get active offers."""
return self.offers.copy()
def get_active_requests(self) -> Dict[str, ComputeRequest]:
"""Get active requests."""
return self.requests.copy()
-243
View File
@@ -1,243 +0,0 @@
"""Tool system for agent side-effects."""
import asyncio
import inspect
from abc import ABC, abstractmethod
from typing import Any, Dict, Type, Generic, TypeVar, Callable, Optional
from pydantic import BaseModel
S = TypeVar("S") # State type
class Tool(ABC, Generic[S]):
"""Base class for agent tools.
Tools are the ONLY way agents can perform side-effects.
All tool operations must be:
- Deterministic
- Typed (via Parameters model)
- State-mutating in an explicit way
"""
name: str
description: str
parameters: Type[BaseModel] = BaseModel
@abstractmethod
def handle(self, **kwargs) -> Any:
"""Execute the tool operation.
The network parameter is injected automatically and provides
access to state and other network resources.
"""
pass
def get_schema(self) -> Dict[str, Any]:
"""Get JSON schema for tool parameters."""
if hasattr(self.parameters, "model_json_schema"):
schema = self.parameters.model_json_schema()
else:
schema = {"type": "object", "properties": {}}
return {
"name": self.name,
"description": self.description,
"parameters": schema,
}
def validate_params(self, **kwargs) -> Dict[str, Any]:
"""Validate and parse parameters."""
if self.parameters and self.parameters != BaseModel:
# Use Pydantic for validation
params = self.parameters(**kwargs)
return params.model_dump()
return kwargs
async def __call__(self, network: "Network[S]", **kwargs) -> Any:
"""Execute tool with network context."""
# Validate parameters
validated = self.validate_params(**kwargs)
# Inject network into handle method
sig = inspect.signature(self.handle)
if "network" in sig.parameters:
validated["network"] = network
# Execute
if asyncio.iscoroutinefunction(self.handle):
result = await self.handle(**validated)
else:
result = self.handle(**validated)
return result
class ToolRegistry:
"""Global registry for available tools."""
_tools: Dict[str, Type[Tool]] = {}
_instances: Dict[str, Tool] = {}
@classmethod
def register(cls, tool_class: Type[Tool], name: Optional[str] = None):
"""Register a tool class."""
name = name or tool_class.name
cls._tools[name] = tool_class
@classmethod
def get(cls, name: str) -> Optional[Type[Tool]]:
"""Get tool class by name."""
return cls._tools.get(name)
@classmethod
def list_tools(cls) -> list[str]:
"""List all registered tool names."""
return list(cls._tools.keys())
@classmethod
def create_instance(cls, name: str, **kwargs) -> Tool:
"""Create tool instance."""
if name not in cls._instances:
tool_class = cls.get(name)
if not tool_class:
raise ValueError(f"Unknown tool: {name}")
cls._instances[name] = tool_class(**kwargs)
return cls._instances[name]
@classmethod
def clear(cls):
"""Clear all registrations (for testing)."""
cls._tools.clear()
cls._instances.clear()
def tool(name: str, description: str, parameters: Optional[Type[BaseModel]] = None):
"""Decorator to create tools from functions."""
def decorator(func: Callable) -> Type[Tool]:
# Extract parameter schema from function signature if not provided
if parameters is None:
sig = inspect.signature(func)
fields = {}
for param_name, param in sig.parameters.items():
if param_name in ["self", "network"]:
continue
# Simple type mapping
param_type = (
param.annotation
if param.annotation != inspect.Parameter.empty
else Any
)
fields[param_name] = (
param_type,
param.default if param.default != inspect.Parameter.empty else ...,
)
# Create Pydantic model dynamically
Parameters = type("Parameters", (BaseModel,), {"__annotations__": fields})
else:
Parameters = parameters
# Create tool class
class FunctionTool(Tool):
def __init__(self):
self.name = name
self.description = description
self.parameters = Parameters
def handle(self, **kwargs):
return func(**kwargs)
# Register and return
ToolRegistry.register(FunctionTool, name)
return FunctionTool
return decorator
# Built-in tools
class ThinkTool(Tool[S]):
"""Tool for agents to think/reason without side effects."""
name = "think"
description = "Think about the problem and plan next steps"
class Parameters(BaseModel):
thought: str
def handle(self, thought: str, network: "Network[S]") -> str:
"""Record thought in history."""
# No state mutation, just returns for history
return f"Thought: {thought}"
class MemoryStoreTool(Tool[S]):
"""Store information in long-term memory."""
name = "memory_store"
description = "Store information for future reference"
class Parameters(BaseModel):
key: str
value: Dict[str, Any]
metadata: Optional[Dict[str, Any]] = None
async def handle(
self,
key: str,
value: Dict[str, Any],
metadata: Optional[Dict[str, Any]],
network: "Network[S]",
) -> str:
"""Store in memory."""
if network.memory_kv:
await network.memory_kv.save(
key, {"value": value, "metadata": metadata or {}}
)
return f"Stored '{key}' in memory"
return "No memory backend available"
class MemoryRecallTool(Tool[S]):
"""Recall information from memory."""
name = "memory_recall"
description = "Recall stored information"
class Parameters(BaseModel):
key: str
async def handle(self, key: str, network: "Network[S]") -> Any:
"""Recall from memory."""
if network.memory_kv:
data = await network.memory_kv.load(key)
if data:
return data.get("value", data)
return None
class MemorySearchTool(Tool[S]):
"""Search vector memory."""
name = "memory_search"
description = "Search for relevant memories"
class Parameters(BaseModel):
query: str
k: int = 5
async def handle(self, query: str, k: int, network: "Network[S]") -> list[str]:
"""Search vector memory."""
if network.memory_vector:
return await network.memory_vector.query(query, k=k)
return []
# Register built-in tools
ToolRegistry.register(ThinkTool)
ToolRegistry.register(MemoryStoreTool)
ToolRegistry.register(MemoryRecallTool)
ToolRegistry.register(MemorySearchTool)
@@ -1,385 +0,0 @@
"""Wallet and Web3 integration for agents.
This module provides wallet capabilities for agents to interact with blockchain networks,
enabling on-chain payments, identity, and decentralized coordination.
"""
import hashlib
import secrets
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional
from dataclasses import dataclass
from eth_account import Account
from eth_account.hdaccount import generate_mnemonic
# Try to import web3 dependencies
try:
from web3 import Web3
from eth_typing import HexStr, Address
from web3.types import Wei, TxParams
WEB3_AVAILABLE = True
except ImportError:
WEB3_AVAILABLE = False
Web3 = None
TxParams = Dict[str, Any]
Wei = int
Address = str
HexStr = str
@dataclass
class WalletConfig:
"""Configuration for agent wallet."""
private_key: Optional[str] = None
mnemonic: Optional[str] = None
account_index: int = 0
network_rpc: str = "http://localhost:8545"
chain_id: int = 31337 # Default to local hardhat/anvil
gas_limit: int = 3000000
gas_price_gwei: int = 20
def __post_init__(self):
"""Validate configuration."""
if not self.private_key and not self.mnemonic:
# Generate a new random private key if none provided
self.private_key = "0x" + secrets.token_hex(32)
@dataclass
class Transaction:
"""Represents a blockchain transaction."""
hash: str
from_address: str
to_address: str
value: Wei
gas_used: Optional[int] = None
status: Optional[bool] = None
block_number: Optional[int] = None
class WalletInterface(ABC):
"""Abstract interface for wallet implementations."""
@abstractmethod
def get_address(self) -> str:
"""Get the wallet's address."""
pass
@abstractmethod
def get_balance(self) -> Wei:
"""Get the wallet's balance in Wei."""
pass
@abstractmethod
def sign_message(self, message: str) -> str:
"""Sign a message with the wallet's private key."""
pass
@abstractmethod
def send_transaction(
self,
to: str,
value: Wei,
data: Optional[bytes] = None,
gas_limit: Optional[int] = None,
gas_price: Optional[Wei] = None,
) -> Transaction:
"""Send a transaction."""
pass
@abstractmethod
def call_contract(
self, contract_address: str, function_signature: str, *args, **kwargs
) -> Any:
"""Call a smart contract function."""
pass
class Web3Wallet(WalletInterface):
"""Web3-based wallet implementation."""
def __init__(self, config: WalletConfig):
"""Initialize Web3 wallet."""
if not WEB3_AVAILABLE:
raise ImportError(
"Web3 dependencies not available. Install with: pip install web3 eth-account"
)
self.config = config
self.w3 = Web3(Web3.HTTPProvider(config.network_rpc))
# Initialize account from private key or mnemonic
if config.private_key:
self.account = Account.from_key(config.private_key)
elif config.mnemonic:
# Derive account from mnemonic at given index
Account.enable_unaudited_hdwallet_features()
self.account = Account.from_mnemonic(
config.mnemonic, account_path=f"m/44'/60'/0'/0/{config.account_index}"
)
else:
raise ValueError("Either private_key or mnemonic must be provided")
# Ensure connection
if not self.w3.is_connected():
raise ConnectionError(f"Cannot connect to {config.network_rpc}")
def get_address(self) -> str:
"""Get the wallet's address."""
return self.account.address
def get_balance(self) -> Wei:
"""Get the wallet's balance in Wei."""
return self.w3.eth.get_balance(self.account.address)
def sign_message(self, message: str) -> str:
"""Sign a message with the wallet's private key."""
message_hash = hashlib.sha256(message.encode()).digest()
signed = self.account.signHash(message_hash)
return signed.signature.hex()
def send_transaction(
self,
to: str,
value: Wei,
data: Optional[bytes] = None,
gas_limit: Optional[int] = None,
gas_price: Optional[Wei] = None,
) -> Transaction:
"""Send a transaction."""
# Build transaction
tx: TxParams = {
"from": self.account.address,
"to": to,
"value": value,
"gas": gas_limit or self.config.gas_limit,
"gasPrice": gas_price or Web3.to_wei(self.config.gas_price_gwei, "gwei"),
"nonce": self.w3.eth.get_transaction_count(self.account.address),
"chainId": self.config.chain_id,
}
if data:
tx["data"] = data
# Sign transaction
signed_tx = self.account.sign_transaction(tx)
# Send transaction
tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction)
# Wait for receipt
receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)
return Transaction(
hash=tx_hash.hex(),
from_address=self.account.address,
to_address=to,
value=value,
gas_used=receipt["gasUsed"],
status=receipt["status"] == 1,
block_number=receipt["blockNumber"],
)
def call_contract(
self, contract_address: str, function_signature: str, *args, **kwargs
) -> Any:
"""Call a smart contract function.
This is a simplified interface. For complex contracts,
use web3.py directly with the contract ABI.
"""
# This would need the contract ABI for proper encoding
# For now, raise NotImplementedError
raise NotImplementedError(
"Contract calls require ABI. Use web3.py directly for now."
)
class MockWallet(WalletInterface):
"""Mock wallet for testing without blockchain."""
def __init__(self, config: WalletConfig):
"""Initialize mock wallet."""
self.config = config
self.address = (
"0x"
+ hashlib.sha256(
(config.private_key or config.mnemonic or "mock").encode()
).hexdigest()[:40]
)
self.balance = Wei(1000000000000000000000) # 1000 ETH
self.transactions: List[Transaction] = []
def get_address(self) -> str:
"""Get the wallet's address."""
return self.address
def get_balance(self) -> Wei:
"""Get the wallet's balance in Wei."""
return self.balance
def sign_message(self, message: str) -> str:
"""Sign a message (mock)."""
return "0x" + hashlib.sha256(f"{self.address}:{message}".encode()).hexdigest()
def send_transaction(
self,
to: str,
value: Wei,
data: Optional[bytes] = None,
gas_limit: Optional[int] = None,
gas_price: Optional[Wei] = None,
) -> Transaction:
"""Send a transaction (mock)."""
if value > self.balance:
raise ValueError("Insufficient balance")
self.balance -= value
tx = Transaction(
hash="0x" + secrets.token_hex(32),
from_address=self.address,
to_address=to,
value=value,
gas_used=21000,
status=True,
block_number=len(self.transactions),
)
self.transactions.append(tx)
return tx
def call_contract(
self, contract_address: str, function_signature: str, *args, **kwargs
) -> Any:
"""Call a smart contract function (mock)."""
return f"Mock result for {function_signature}"
class AgentWallet:
"""High-level wallet interface for agents."""
def __init__(self, config: Optional[WalletConfig] = None):
"""Initialize agent wallet."""
self.config = config or WalletConfig()
# Use mock wallet if web3 not available or in test mode
if WEB3_AVAILABLE and not self.config.network_rpc.startswith("mock://"):
self.wallet = Web3Wallet(self.config)
else:
self.wallet = MockWallet(self.config)
@property
def address(self) -> str:
"""Get wallet address."""
return self.wallet.get_address()
@property
def balance(self) -> Wei:
"""Get wallet balance."""
return self.wallet.get_balance()
def send_payment(
self, to: str, amount_ether: float, memo: Optional[str] = None
) -> Transaction:
"""Send a payment to another address.
Args:
to: Recipient address
amount_ether: Amount in Ether (not Wei)
memo: Optional memo (stored off-chain)
Returns:
Transaction object
"""
amount_wei = Wei(int(amount_ether * 10**18))
# Log memo if provided (would be stored in agent memory)
if memo:
print(f"Payment memo: {memo}")
return self.wallet.send_transaction(to, amount_wei)
def sign_message(self, message: str) -> str:
"""Sign a message for authentication."""
return self.wallet.sign_message(message)
def verify_signature(
self, message: str, signature: str, expected_address: str
) -> bool:
"""Verify a signature matches expected address.
This is a simplified version. Real implementation would
recover the address from signature and compare.
"""
# TODO: Implement proper signature verification
return True # Placeholder
def generate_shared_mnemonic() -> str:
"""Generate a shared mnemonic for a network of agents."""
return generate_mnemonic(num_words=12, lang="english")
def derive_agent_wallet(mnemonic: str, agent_index: int, **kwargs) -> AgentWallet:
"""Derive an agent wallet from shared mnemonic.
Args:
mnemonic: Shared network mnemonic
agent_index: Unique index for this agent
**kwargs: Additional wallet config options
Returns:
AgentWallet instance
"""
config = WalletConfig(mnemonic=mnemonic, account_index=agent_index, **kwargs)
return AgentWallet(config)
# Tool functions for agent use
def create_wallet_tool():
"""Create a tool that agents can use for wallet operations."""
from ..tool import Tool
class WalletTool(Tool):
"""Tool for wallet operations."""
def __init__(self, wallet: AgentWallet):
self.wallet = wallet
self.name = "wallet"
self.description = "Interact with blockchain wallet"
async def get_balance(self) -> float:
"""Get wallet balance in Ether."""
balance_wei = self.wallet.balance
return float(balance_wei) / 10**18
async def send_payment(
self, to_address: str, amount_ether: float, reason: Optional[str] = None
) -> str:
"""Send payment to another address.
Args:
to_address: Recipient blockchain address
amount_ether: Amount to send in Ether
reason: Optional reason for payment
Returns:
Transaction hash
"""
tx = self.wallet.send_payment(to_address, amount_ether, reason)
return f"Sent {amount_ether} ETH to {to_address}. Tx: {tx.hash}"
async def get_address(self) -> str:
"""Get this wallet's address."""
return self.wallet.address
async def sign_message(self, message: str) -> str:
"""Sign a message for authentication."""
return self.wallet.sign_message(message)
return WalletTool
@@ -1,359 +0,0 @@
"""Web3-enabled agent with wallet and TEE support."""
from typing import Any, Dict, Optional
from dataclasses import dataclass
from .tee import (
TEEConfig,
TEEProvider,
ConfidentialAgent,
create_attestation_verifier_tool,
)
from .agent import Agent, InferenceResult
from .state import State
from .wallet import AgentWallet, Transaction, WalletConfig, create_wallet_tool
@dataclass
class Web3AgentConfig:
"""Configuration for Web3-enabled agents."""
# Wallet configuration
wallet_enabled: bool = False
wallet_config: Optional[WalletConfig] = None
# TEE configuration
tee_enabled: bool = False
tee_config: Optional[TEEConfig] = None
tee_provider: TEEProvider = TEEProvider.MOCK
# Economic parameters
min_payment_eth: float = 0.001 # Minimum payment to accept
task_price_eth: float = 0.01 # Default price for tasks
reputation_threshold: float = 0.8 # Min reputation to work with
# On-chain identity
agent_nft_address: Optional[str] = None
reputation_contract: Optional[str] = None
class Web3Agent(Agent):
"""Agent with Web3 capabilities including wallet and TEE support."""
def __init__(
self,
name: str,
description: str,
web3_config: Optional[Web3AgentConfig] = None,
**kwargs,
):
"""Initialize Web3-enabled agent.
Args:
name: Agent name
description: Agent description
web3_config: Web3 configuration
**kwargs: Additional agent parameters
"""
super().__init__(**kwargs)
self.name = name
self.description = description
self.web3_config = web3_config or Web3AgentConfig()
# Initialize wallet if enabled
self.wallet: Optional[AgentWallet] = None
if self.web3_config.wallet_enabled:
wallet_config = self.web3_config.wallet_config or WalletConfig()
self.wallet = AgentWallet(wallet_config)
# Add wallet tool to agent's tools
WalletTool = create_wallet_tool()
self.wallet_tool = WalletTool(self.wallet)
if not hasattr(self, "tools"):
self.tools = []
self.tools.append(self.wallet_tool)
# Initialize TEE wrapper if enabled
self.confidential_agent: Optional[ConfidentialAgent] = None
if self.web3_config.tee_enabled:
self.confidential_agent = ConfidentialAgent(
self, self.web3_config.tee_config
)
# Add attestation verifier tool
AttestationTool = create_attestation_verifier_tool()
self.attestation_tool = AttestationTool()
if not hasattr(self, "tools"):
self.tools = []
self.tools.append(self.attestation_tool)
# Track economic activity
self.earnings: float = 0.0
self.spending: float = 0.0
self.completed_tasks: int = 0
self.reputation_score: float = 1.0
@property
def address(self) -> Optional[str]:
"""Get agent's blockchain address."""
return self.wallet.address if self.wallet else None
@property
def balance_eth(self) -> float:
"""Get wallet balance in ETH."""
if not self.wallet:
return 0.0
return float(self.wallet.balance) / 10**18
async def request_payment(
self, from_address: str, amount_eth: float, task_description: str
) -> Dict[str, Any]:
"""Request payment for a task.
Args:
from_address: Payer's address
amount_eth: Payment amount in ETH
task_description: Description of the task
Returns:
Payment request details
"""
if not self.wallet:
return {"error": "Wallet not enabled"}
if amount_eth < self.web3_config.min_payment_eth:
return {
"error": f"Payment too low. Minimum: {self.web3_config.min_payment_eth} ETH"
}
return {
"to": self.wallet.address,
"amount_eth": amount_eth,
"task": task_description,
"request_id": f"req_{self.name}_{self.completed_tasks + 1}",
}
async def verify_payment(self, tx_hash: str, expected_amount_eth: float) -> bool:
"""Verify a payment was received.
In a real implementation, this would check the blockchain.
For now, we'll use a simplified version.
"""
# TODO: Implement blockchain verification
# For now, just track it
self.earnings += expected_amount_eth
return True
async def pay_agent(
self, to_address: str, amount_eth: float, reason: str
) -> Optional[Transaction]:
"""Pay another agent.
Args:
to_address: Recipient agent's address
amount_eth: Payment amount
reason: Reason for payment
Returns:
Transaction object if successful
"""
if not self.wallet:
print("Wallet not enabled")
return None
if amount_eth > self.balance_eth:
print(f"Insufficient balance. Have: {self.balance_eth}, Need: {amount_eth}")
return None
try:
tx = self.wallet.send_payment(to_address, amount_eth, reason)
self.spending += amount_eth
return tx
except Exception as e:
print(f"Payment failed: {e}")
return None
async def execute_confidential(
self, task_code: str, inputs: Dict[str, Any]
) -> Dict[str, Any]:
"""Execute task in TEE for confidentiality.
Args:
task_code: Python code to execute
inputs: Input data for the task
Returns:
Execution result with attestation
"""
if not self.confidential_agent:
return {"error": "TEE not enabled"}
result = self.confidential_agent.execute_confidential(task_code, inputs)
return result
async def run(self, state: State, history, network) -> InferenceResult:
"""Execute agent with Web3 enhancements.
This adds economic and TEE considerations to agent execution.
"""
# Check if this is a paid task
task_payment = state.get("task_payment", 0)
if task_payment > 0:
# Verify payment before proceeding
tx_hash = state.get("payment_tx")
if tx_hash:
verified = await self.verify_payment(tx_hash, task_payment)
if not verified:
return InferenceResult(
agent=self.name,
content="Payment verification failed. Cannot proceed with task.",
metadata={"payment_required": True},
)
# Check if confidential execution is requested
if state.get("require_tee", False) and self.confidential_agent:
# Execute in TEE
task_code = state.get("task_code", "")
task_inputs = state.get("task_inputs", {})
tee_result = await self.execute_confidential(task_code, task_inputs)
return InferenceResult(
agent=self.name,
content="Task executed in TEE",
metadata={
"tee_result": tee_result,
"attestation": tee_result.get("attestation"),
},
)
# Regular execution - must be implemented by subclass
return await self._run_impl(state, history, network)
async def _run_impl(self, state: State, history, network) -> InferenceResult:
"""Actual agent implementation - override in subclass."""
return InferenceResult(
agent=self.name,
content=f"{self.name} is ready to work. Balance: {self.balance_eth:.4f} ETH",
)
def update_reputation(self, delta: float):
"""Update agent's reputation score.
Args:
delta: Change in reputation (-1 to 1)
"""
self.reputation_score = max(0, min(1, self.reputation_score + delta))
def get_stats(self) -> Dict[str, Any]:
"""Get agent statistics."""
stats = {
"name": self.name,
"address": self.address,
"balance_eth": self.balance_eth,
"earnings_eth": self.earnings,
"spending_eth": self.spending,
"completed_tasks": self.completed_tasks,
"reputation": self.reputation_score,
"wallet_enabled": self.web3_config.wallet_enabled,
"tee_enabled": self.web3_config.tee_enabled,
}
if self.confidential_agent:
stats["attestations"] = len(
self.confidential_agent.get_attestation_history()
)
return stats
# Example Web3-enabled agents
class DataProviderAgent(Web3Agent):
"""Agent that provides data for payment."""
def __init__(self, **kwargs):
super().__init__(
name="data_provider",
description="Provides high-quality data for AI training",
**kwargs,
)
self.data_catalog = {
"weather": {"price_eth": 0.01, "size_mb": 100},
"finance": {"price_eth": 0.05, "size_mb": 500},
"research": {"price_eth": 0.1, "size_mb": 1000},
}
async def _run_impl(self, state: State, history, network) -> InferenceResult:
"""Provide data based on request."""
request = state.get("data_request", {})
dataset = request.get("dataset")
if dataset not in self.data_catalog:
return InferenceResult(
agent=self.name,
content=f"Unknown dataset: {dataset}. Available: {list(self.data_catalog.keys())}",
)
data_info = self.data_catalog[dataset]
# Request payment
payment_request = await self.request_payment(
from_address=request.get("requester_address", ""),
amount_eth=data_info["price_eth"],
task_description=f"Provide {dataset} dataset",
)
return InferenceResult(
agent=self.name,
content=f"Dataset {dataset} available for {data_info['price_eth']} ETH",
metadata={"payment_request": payment_request, "data_info": data_info},
)
class ComputeProviderAgent(Web3Agent):
"""Agent that provides GPU compute for payment."""
def __init__(self, **kwargs):
super().__init__(
name="compute_provider",
description="Provides GPU compute resources",
web3_config=Web3AgentConfig(
wallet_enabled=True,
tee_enabled=True,
task_price_eth=0.1, # Per hour
),
**kwargs,
)
self.gpu_specs = {"model": "NVIDIA H100", "memory_gb": 80, "tflops": 1000}
async def _run_impl(self, state: State, history, network) -> InferenceResult:
"""Offer compute resources."""
compute_request = state.get("compute_request", {})
duration_hours = compute_request.get("duration_hours", 1)
total_price = self.web3_config.task_price_eth * duration_hours
# Create compute offer
offer = {
"provider": self.address,
"gpu": self.gpu_specs,
"price_per_hour_eth": self.web3_config.task_price_eth,
"total_price_eth": total_price,
"tee_enabled": self.web3_config.tee_enabled,
"min_duration_hours": 0.1,
"max_duration_hours": 24,
}
# If TEE is requested, provide attestation
if compute_request.get("require_tee", False) and self.confidential_agent:
dummy_attestation = self.confidential_agent.tee_executor.get_attestation()
offer["attestation"] = dummy_attestation.to_dict()
return InferenceResult(
agent=self.name,
content=f"GPU compute available: {self.gpu_specs['model']} for {total_price} ETH",
metadata={"compute_offer": offer},
)
@@ -1,447 +0,0 @@
"""Web3-enabled network orchestration with deterministic execution."""
import json
import time
import hashlib
from typing import Any, Dict, List, Union, Generic, TypeVar, Optional
from dataclasses import dataclass
from .agent import Agent, InferenceResult
from .state import State
from .router import Router, RouterFn
from .wallet import derive_agent_wallet, generate_shared_mnemonic
from .network import Network
from .web3_agent import Web3Agent, Web3AgentConfig
from .marketplace import ServiceType, AgentMarketplace
S = TypeVar("S", bound=State)
@dataclass
class NetworkEconomics:
"""Economic configuration for agent network."""
# Token economics
network_token_symbol: str = "AI"
initial_agent_balance: float = 1000.0 # In network tokens
# Fee structure
network_fee_percent: float = 0.01 # 1% network fee
min_task_fee: float = 0.001 # Minimum fee per task
# Incentives
completion_bonus: float = 0.1 # Bonus for completing tasks
quality_multiplier: float = 2.0 # Multiplier for high-quality work
# Slashing
failure_penalty: float = 0.05 # Penalty for failed tasks
timeout_penalty: float = 0.02 # Penalty for timeouts
@dataclass
class DeterministicConfig:
"""Configuration for deterministic execution."""
# Seed for randomness
seed: int = 42
# Execution order
enforce_order: bool = True
allow_parallel: bool = False
# Reproducibility
record_all_calls: bool = True
verify_outputs: bool = True
# Checkpointing
checkpoint_every_n_steps: int = 10
checkpoint_on_completion: bool = True
class Web3Network(Network[S], Generic[S]):
"""Network with Web3 integration and deterministic execution."""
def __init__(
self,
*,
state: S,
agents: List[Union[Agent[S], Web3Agent]],
router: Union[Router, RouterFn[S]],
shared_mnemonic: Optional[str] = None,
network_economics: Optional[NetworkEconomics] = None,
deterministic_config: Optional[DeterministicConfig] = None,
marketplace: Optional[AgentMarketplace] = None,
**kwargs,
):
"""Initialize Web3-enabled network.
Args:
state: Initial state
agents: List of agents (can be Web3Agent instances)
router: Router for agent orchestration
shared_mnemonic: Shared mnemonic for agent wallets
network_economics: Economic configuration
deterministic_config: Deterministic execution config
marketplace: Agent marketplace instance
**kwargs: Additional Network parameters
"""
super().__init__(state=state, agents=agents, router=router, **kwargs)
# Web3 configuration
self.shared_mnemonic = shared_mnemonic or generate_shared_mnemonic()
self.network_economics = network_economics or NetworkEconomics()
self.deterministic_config = deterministic_config or DeterministicConfig()
self.marketplace = marketplace or globals()["marketplace"]
# Initialize Web3 agents
self._initialize_web3_agents()
# Economic tracking
self.network_treasury = 0.0
self.total_fees_collected = 0.0
self.total_rewards_distributed = 0.0
# Deterministic execution tracking
self.execution_log: List[Dict[str, Any]] = []
self.execution_hash: Optional[str] = None
def _initialize_web3_agents(self):
"""Initialize Web3 capabilities for agents."""
for i, agent_class in enumerate(self.agents):
# Skip if already instantiated
if isinstance(agent_class, Agent):
agent = agent_class
else:
# Instantiate agent
agent = agent_class()
# If it's a Web3Agent, initialize wallet
if isinstance(agent, Web3Agent):
if not agent.wallet and agent.web3_config.wallet_enabled:
# Derive wallet from shared mnemonic
wallet = derive_agent_wallet(
self.shared_mnemonic,
agent_index=i,
network_rpc=(
agent.web3_config.wallet_config.network_rpc
if agent.web3_config.wallet_config
else "mock://localhost"
),
)
agent.wallet = wallet
# Initial funding
agent.earnings = self.network_economics.initial_agent_balance
# Store instance
self._agent_instances[agent.name] = agent
async def run(self) -> S:
"""Execute network with Web3 enhancements."""
if self._running:
raise RuntimeError("Network already running")
self._running = True
start_time = time.time()
# Set deterministic seed
import random
import numpy as np
random.seed(self.deterministic_config.seed)
np.random.seed(self.deterministic_config.seed)
try:
step = 0
while self.call_count < self.max_steps:
# Checkpoint if needed
if (
self.deterministic_config.checkpoint_every_n_steps > 0
and step % self.deterministic_config.checkpoint_every_n_steps == 0
):
await self._checkpoint(f"step_{step}")
# Route to next agent
next_agent = await self._route()
if next_agent is None:
break
# Record pre-execution state
pre_state = (
self.state.to_dict()
if hasattr(self.state, "to_dict")
else str(self.state)
)
# Execute agent
result = await self._execute_agent(next_agent)
# Record execution
self._record_execution(next_agent, result, pre_state)
# Handle economics if Web3 agent
if isinstance(next_agent, Web3Agent):
await self._handle_agent_economics(next_agent, result)
# Check marketplace for matches
await self._check_marketplace(next_agent, result)
step += 1
# Final checkpoint
if self.deterministic_config.checkpoint_on_completion:
await self._checkpoint("final")
# Compute execution hash
self.execution_hash = self._compute_execution_hash()
# Log summary
duration = time.time() - start_time
print(f"\nNetwork execution completed:")
print(f" Duration: {duration:.2f}s")
print(f" Steps: {step}")
print(f" Fees collected: {self.total_fees_collected:.4f}")
print(f" Rewards distributed: {self.total_rewards_distributed:.4f}")
print(f" Execution hash: {self.execution_hash}")
finally:
self._running = False
return self.state
def _record_execution(self, agent: Agent, result: InferenceResult, pre_state: Any):
"""Record execution for determinism."""
if not self.deterministic_config.record_all_calls:
return
record = {
"step": len(self.execution_log),
"timestamp": time.time(),
"agent": agent.name,
"pre_state": pre_state,
"result": result.to_dict(),
"post_state": (
self.state.to_dict()
if hasattr(self.state, "to_dict")
else str(self.state)
),
}
self.execution_log.append(record)
def _compute_execution_hash(self) -> str:
"""Compute hash of entire execution for verification."""
# Create deterministic representation
execution_data = {
"seed": self.deterministic_config.seed,
"agents": [a.name for a in self._agent_instances.values()],
"log": self.execution_log,
}
# Compute hash
json_str = json.dumps(execution_data, sort_keys=True)
return hashlib.sha256(json_str.encode()).hexdigest()
async def _handle_agent_economics(self, agent: Web3Agent, result: InferenceResult):
"""Handle economic aspects of agent execution."""
# Charge network fee
task_fee = max(
self.network_economics.min_task_fee,
agent.web3_config.task_price_eth
* self.network_economics.network_fee_percent,
)
if agent.balance_eth >= task_fee:
# Deduct fee (in real implementation, this would be on-chain)
agent.spending += task_fee
self.network_treasury += task_fee
self.total_fees_collected += task_fee
# Reward for completion
if result.content and "error" not in result.content.lower():
reward = self.network_economics.completion_bonus
# Quality bonus
if result.metadata.get("quality_score", 0.5) > 0.8:
reward *= self.network_economics.quality_multiplier
agent.earnings += reward
self.total_rewards_distributed += reward
# Update reputation
agent.update_reputation(0.1)
else:
# Penalty for failure
penalty = self.network_economics.failure_penalty
agent.spending += penalty
agent.update_reputation(-0.1)
async def _check_marketplace(self, agent: Agent, result: InferenceResult):
"""Check marketplace for service opportunities."""
if not isinstance(agent, Web3Agent):
return
# Check if agent advertised any services
if "service_offer" in result.metadata:
offer = result.metadata["service_offer"]
offer_id = self.marketplace.post_offer(
agent=agent,
service_type=ServiceType(offer.get("type", "custom")),
description=offer.get("description", ""),
price_eth=offer.get("price_eth", 0.01),
requires_tee=offer.get("requires_tee", False),
)
print(f"Agent {agent.name} posted offer: {offer_id}")
# Check if agent requested any services
if "service_request" in result.metadata:
request = result.metadata["service_request"]
request_id = self.marketplace.post_request(
agent=agent,
service_type=ServiceType(request.get("type", "custom")),
description=request.get("description", ""),
max_price_eth=request.get("max_price_eth", 0.1),
requires_tee=request.get("requires_tee", False),
)
print(f"Agent {agent.name} posted request: {request_id}")
async def _checkpoint(self, name: str):
"""Create a checkpoint of current state."""
if not self.checkpoint_dir:
return
checkpoint = {
"name": name,
"timestamp": time.time(),
"state": (
self.state.to_dict()
if hasattr(self.state, "to_dict")
else str(self.state)
),
"history": [entry.to_dict() for entry in self.history.entries],
"economics": {
"treasury": self.network_treasury,
"fees_collected": self.total_fees_collected,
"rewards_distributed": self.total_rewards_distributed,
},
"execution_log": self.execution_log,
}
# Save checkpoint
checkpoint_file = (
self.checkpoint_dir / f"checkpoint_{name}_{int(time.time())}.json"
)
checkpoint_file.parent.mkdir(parents=True, exist_ok=True)
with open(checkpoint_file, "w") as f:
json.dump(checkpoint, f, indent=2)
print(f"Checkpoint saved: {checkpoint_file}")
def verify_execution(self, other_hash: str) -> bool:
"""Verify execution matches another run.
Args:
other_hash: Execution hash from another run
Returns:
True if executions match
"""
if not self.execution_hash:
self.execution_hash = self._compute_execution_hash()
return self.execution_hash == other_hash
def get_agent_stats(self) -> Dict[str, Any]:
"""Get statistics for all agents."""
stats = {}
for name, agent in self._agent_instances.items():
if isinstance(agent, Web3Agent):
stats[name] = agent.get_stats()
else:
stats[name] = {
"name": name,
"type": "standard",
"calls": sum(
1 for entry in self.execution_log if entry["agent"] == name
),
}
return stats
def get_network_stats(self) -> Dict[str, Any]:
"""Get network-wide statistics."""
return {
"agents": len(self._agent_instances),
"total_steps": len(self.execution_log),
"treasury_balance": self.network_treasury,
"total_fees": self.total_fees_collected,
"total_rewards": self.total_rewards_distributed,
"marketplace_stats": self.marketplace.get_stats(),
"execution_hash": self.execution_hash,
}
# Factory function for easy network creation
def create_web3_network(
agents: List[Agent],
task: str,
enable_wallets: bool = True,
enable_tee: bool = False,
deterministic: bool = True,
) -> Web3Network[State]:
"""Create a Web3-enabled network.
Args:
agents: List of agents to include
task: Initial task/query
enable_wallets: Enable wallet functionality
enable_tee: Enable TEE support
deterministic: Enable deterministic execution
Returns:
Configured Web3Network instance
"""
# Configure agents
web3_agents = []
for agent in agents:
if isinstance(agent, Web3Agent):
web3_agents.append(agent)
else:
# Wrap in Web3Agent
config = Web3AgentConfig(
wallet_enabled=enable_wallets, tee_enabled=enable_tee
)
class Web3Wrapper(Web3Agent):
async def _run_impl(self, state, history, network):
# Delegate to original agent
return await agent.run(state, history, network)
wrapped = Web3Wrapper(
name=agent.name,
description=getattr(agent, "description", ""),
web3_config=config,
)
wrapped.tools = getattr(agent, "tools", [])
web3_agents.append(wrapped)
# Create initial state
state = State()
state["task"] = task
state["start_time"] = time.time()
# Simple sequential router
from .router import sequential_router
router = sequential_router([a.name for a in web3_agents])
# Create network
return Web3Network(
state=state,
agents=web3_agents,
router=router,
deterministic_config=DeterministicConfig() if deterministic else None,
)
-69
View File
@@ -1,69 +0,0 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "hanzo-agents"
version = "0.1.1"
description = "Production-grade AI agent runtime for deterministic, debuggable agent systems"
readme = "README.md"
license = {text = "MIT"}
authors = [
{name = "Hanzo AI", email = "[email protected]"},
]
keywords = ["ai", "agents", "llm", "orchestration", "multi-agent"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
]
requires-python = ">=3.11"
dependencies = [
"pydantic>=2.0",
"typing-extensions>=4.5",
"aiofiles>=23.0",
"asyncio>=3.4",
"structlog>=24.0",
"prometheus-client>=0.19",
"pyyaml>=6.0",
"click>=8.0",
"rich>=13.0",
]
[project.optional-dependencies]
all = [
"chromadb>=0.4",
"neo4j>=5.0",
"qdrant-client>=1.6",
"faiss-cpu>=1.7",
"duckdb>=0.9",
"langchain>=0.1",
"litellm>=1.0",
"anthropic>=0.15",
"openai>=1.0",
]
dev = [
"pytest>=7.0",
"pytest-asyncio>=0.21",
"pytest-cov>=4.0",
"pytest-mock>=3.0",
"mypy>=1.0",
"ruff>=0.1",
"black>=23.0",
]
[project.scripts]
hanzo-agents = "hanzo_agents.cli:main"
[project.urls]
Homepage = "https://github.com/hanzoai/agents"
Documentation = "https://docs.hanzo.ai/agents"
Repository = "https://github.com/hanzoai/agents"
Issues = "https://github.com/hanzoai/agents/issues"
[tool.hatch.build.targets.wheel]
packages = ["hanzo_agents"]
-160
View File
@@ -1,160 +0,0 @@
"""Simple test of hanzo-agents with Web3 features."""
import sys
import asyncio
from pathlib import Path
# Add hanzo_agents to path
sys.path.insert(0, str(Path(__file__).parent))
# Now import
from hanzo_agents import (
Web3Agent,
ServiceType,
WalletConfig,
Web3AgentConfig,
AgentMarketplace,
generate_shared_mnemonic,
)
async def test_basic_web3_agents():
"""Test basic Web3 agent functionality."""
print("Testing Hanzo Agents with Web3 Integration")
print("=" * 50)
# Create shared mnemonic
mnemonic = generate_shared_mnemonic()
print(f"Generated mnemonic (first 3 words): {' '.join(mnemonic.split()[:3])}...")
# Create two agents
print("\nCreating Web3-enabled agents...")
alice = Web3Agent(
name="Alice",
description="A helpful assistant",
web3_config=Web3AgentConfig(
wallet_enabled=True,
tee_enabled=True,
wallet_config=WalletConfig(
mnemonic=mnemonic, account_index=0, network_rpc="mock://localhost"
),
),
)
bob = Web3Agent(
name="Bob",
description="A technical expert",
web3_config=Web3AgentConfig(
wallet_enabled=True,
wallet_config=WalletConfig(
mnemonic=mnemonic, account_index=1, network_rpc="mock://localhost"
),
),
)
# Give them some initial balance
alice.earnings = 1.0
bob.earnings = 0.5
print(f"\nAlice:")
print(f" Address: {alice.address}")
print(f" Balance: {alice.balance_eth} ETH")
print(f" Has TEE: {alice.confidential_agent is not None}")
print(f"\nBob:")
print(f" Address: {bob.address}")
print(f" Balance: {bob.balance_eth} ETH")
print(f" Has TEE: {bob.confidential_agent is not None}")
# Test payment
print("\n--- Testing Payments ---")
# Bob requests payment from Alice
payment_request = await bob.request_payment(
from_address=alice.address,
amount_eth=0.1,
task_description="Technical consulting",
)
print(f"Bob requested {payment_request['amount_eth']} ETH from Alice")
# Alice pays Bob
tx = await alice.pay_agent(
to_address=bob.address, amount_eth=0.1, reason="Payment for consulting"
)
if tx:
print(f"Alice sent payment: {tx.hash}")
print(f"Alice new balance: {alice.balance_eth} ETH")
print(f"Bob new balance: {bob.balance_eth} ETH")
# Test TEE execution
print("\n--- Testing TEE Confidential Execution ---")
if alice.confidential_agent:
code = """
# Confidential computation
result = {
"computation": "sensitive",
"value": inputs["x"] * inputs["y"],
"secure": True
}
"""
result = await alice.execute_confidential(code, {"x": 42, "y": 2})
print(f"TEE computation result: {result['result']['value']}")
print(f"Attestation available: {'attestation' in result}")
# Test marketplace
print("\n--- Testing Marketplace ---")
marketplace = AgentMarketplace()
# Alice offers a service
offer_id = marketplace.post_offer(
agent=alice,
service_type=ServiceType.CUSTOM,
description="AI assistance and code review",
price_eth=0.05,
)
print(f"Alice posted offer: {offer_id}")
# Bob requests a service
request_id = marketplace.post_request(
agent=bob,
service_type=ServiceType.CUSTOM,
description="Need help with code review",
max_price_eth=0.1,
)
print(f"Bob posted request: {request_id}")
# Check matches
if marketplace.matches:
match = list(marketplace.matches.values())[0]
print(
f"Match found! {match.offer.agent_name} <-> {match.request.requester_name}"
)
# Test agent stats
print("\n--- Agent Statistics ---")
alice_stats = alice.get_stats()
bob_stats = bob.get_stats()
print(f"\nAlice stats:")
for key, value in alice_stats.items():
print(f" {key}: {value}")
print(f"\nBob stats:")
for key, value in bob_stats.items():
print(f" {key}: {value}")
print("\n✅ All tests completed successfully!")
if __name__ == "__main__":
asyncio.run(test_basic_web3_agents())
-99
View File
@@ -1,99 +0,0 @@
"""Simple test of Web3 agent features without full dependencies."""
import sys
from pathlib import Path
# Add to path
sys.path.insert(0, str(Path(__file__).parent))
# Test basic imports
print("Testing basic Web3 agent components...")
try:
from hanzo_agents.core.wallet import (
AgentWallet,
WalletConfig,
derive_agent_wallet,
generate_shared_mnemonic,
)
print("✓ Wallet imports successful")
# Test mnemonic generation
mnemonic = generate_shared_mnemonic()
print(f"✓ Generated mnemonic: {' '.join(mnemonic.split()[:3])}...")
# Test wallet derivation
wallet = derive_agent_wallet(mnemonic, 0)
print(f"✓ Derived wallet address: {wallet.address}")
except ImportError as e:
print(f"✗ Wallet import failed: {e}")
try:
from hanzo_agents.core.tee import (
TEEConfig,
TEEProvider,
MockTEEExecutor,
AttestationReport,
)
print("\n✓ TEE imports successful")
# Test TEE config
tee_config = TEEConfig(provider=TEEProvider.MOCK)
print(f"✓ TEE config created: {tee_config.provider.value}")
# Test mock executor
executor = MockTEEExecutor()
result = executor.execute("test_code", {"x": 1})
print(f"✓ Mock TEE execution: {result['success']}")
except ImportError as e:
print(f"\n✗ TEE import failed: {e}")
try:
from hanzo_agents.core.marketplace import (
ServiceType,
ServiceOffer,
ServiceRequest,
AgentMarketplace,
)
print("\n✓ Marketplace imports successful")
# Test marketplace
marketplace = AgentMarketplace()
print(f"✓ Marketplace created")
# Test service types
print(f"✓ Service types: {[s.value for s in ServiceType]}")
except ImportError as e:
print(f"\n✗ Marketplace import failed: {e}")
# Test Web3Agent mock
try:
from hanzo_agents.core.web3_agent import Web3Agent, Web3AgentConfig
print("\n✓ Web3Agent imports successful")
# Create a simple agent
config = Web3AgentConfig(wallet_enabled=True, tee_enabled=True, task_price_eth=0.01)
agent = Web3Agent(name="TestAgent", description="A test agent", web3_config=config)
print(f"✓ Created agent: {agent.name}")
print(f"✓ Wallet enabled: {agent.wallet is not None}")
print(f"✓ TEE enabled: {agent.confidential_agent is not None}")
print(f"✓ Address: {agent.address[:10]}...")
except ImportError as e:
print(f"\n✗ Web3Agent import failed: {e}")
print("\n" + "=" * 50)
print("Summary: Core Web3 components are working!")
print("Note: Full examples require additional dependencies like structlog")
print("\nTo run full examples, install dependencies:")
print(" pip install structlog prometheus-client")
print("=" * 50)
View File
@@ -1,318 +0,0 @@
"""Test all agent example demos to ensure they work correctly."""
import sys
import asyncio
from pathlib import Path
import pytest
# Add examples directory to path
examples_dir = Path(__file__).parent.parent / "examples"
sys.path.insert(0, str(examples_dir))
# Import demo modules
try:
from agent_chat_demo import run_agent_chat_demo
from startup_team_demo import run_startup_team_demo
from game_dev_team_demo import run_game_dev_demo
from research_team_demo import run_research_team_demo
DEMOS_AVAILABLE = True
except ImportError as e:
print(f"Failed to import demos: {e}")
DEMOS_AVAILABLE = False
@pytest.mark.skipif(not DEMOS_AVAILABLE, reason="Demo modules not available")
class TestAgentDemos:
"""Test all agent demonstration examples."""
@pytest.mark.asyncio
async def test_startup_team_demo(self, capsys):
"""Test startup team collaboration demo."""
# Run demo
await run_startup_team_demo()
# Check output
captured = capsys.readouterr()
output = captured.out
# Verify key elements
assert "Starting Startup Team Demo" in output
assert "architect" in output.lower()
assert "product_manager" in output.lower()
assert "designer" in output.lower()
assert "marketer" in output.lower()
assert "engineer" in output.lower()
# Check for completion
assert "Project Summary" in output
assert "Team Economics" in output
assert "Execution hash" in output
@pytest.mark.asyncio
async def test_agent_chat_demo(self, capsys):
"""Test agent chat conversation demo."""
# Run demo
await run_agent_chat_demo()
# Check output
captured = capsys.readouterr()
output = captured.out
# Verify conversation elements
assert "Agent Chat Demo" in output
assert "Alice" in output
assert "Bob" in output
assert "typing..." in output
# Check for system info exchange
assert "Python" in output
assert "model" in output.lower()
assert "capabilities" in output.lower()
# Verify completion
assert "Conversation Summary" in output
assert "Agent Stats" in output
assert "lovely chat" in output
@pytest.mark.asyncio
async def test_research_team_demo(self, capsys):
"""Test research team collaboration demo."""
# Run demo
await run_research_team_demo()
# Check output
captured = capsys.readouterr()
output = captured.out
# Verify research workflow
assert "Research Team Demo" in output
assert "data_scientist" in output.lower()
assert "research_analyst" in output.lower()
assert "ml_engineer" in output.lower()
assert "technical_writer" in output.lower()
# Check research outputs
assert "Data Analysis" in output
assert "Literature Review" in output
assert "Model Performance" in output
assert "Experiments" in output
assert "Deliverables" in output
# Verify completion
assert "Research project completed successfully" in output
@pytest.mark.asyncio
async def test_game_dev_demo(self, capsys):
"""Test game development team demo."""
# Run demo
await run_game_dev_demo()
# Check output
captured = capsys.readouterr()
output = captured.out
# Verify game dev workflow
assert "Game Development Team Demo" in output
assert "game_designer" in output.lower()
assert "artist" in output.lower()
assert "programmer" in output.lower()
assert "sound_designer" in output.lower()
assert "qa_tester" in output.lower()
# Check game details
assert "Quantum Maze Runner" in output
assert "Development Summary" in output
assert "bugs found and fixed" in output
# Verify completion
assert "Game ready for launch" in output
@pytest.mark.skipif(not DEMOS_AVAILABLE, reason="Demo modules not available")
class TestAgentInteractions:
"""Test specific agent interaction patterns."""
@pytest.mark.asyncio
async def test_web3_payments(self):
"""Test Web3 payment functionality between agents."""
from hanzo_agents import Web3Agent, WalletConfig, Web3AgentConfig
# Create two agents
agent1 = Web3Agent(
name="payer",
description="Agent that pays",
web3_config=Web3AgentConfig(
wallet_enabled=True,
wallet_config=WalletConfig(network_rpc="mock://localhost"),
),
)
agent2 = Web3Agent(
name="receiver",
description="Agent that receives",
web3_config=Web3AgentConfig(
wallet_enabled=True,
wallet_config=WalletConfig(network_rpc="mock://localhost"),
),
)
# Give agent1 balance
agent1.earnings = 1.0
# Test payment request
payment_request = await agent2.request_payment(
from_address=agent1.address, amount_eth=0.1, task_description="Test service"
)
assert payment_request["to"] == agent2.address
assert payment_request["amount_eth"] == 0.1
# Test payment execution
tx = await agent1.pay_agent(
to_address=agent2.address, amount_eth=0.1, reason="Test payment"
)
assert tx is not None
assert agent1.spending == 0.1
@pytest.mark.asyncio
async def test_tee_execution(self):
"""Test TEE confidential execution."""
from hanzo_agents import Web3Agent, Web3AgentConfig
agent = Web3Agent(
name="secure_agent",
description="Agent with TEE",
web3_config=Web3AgentConfig(tee_enabled=True),
)
# Test confidential execution
code = """
result = {"sum": inputs["a"] + inputs["b"]}
"""
result = await agent.execute_confidential(code, {"a": 10, "b": 20})
assert result["success"] is True
assert result["result"]["sum"] == 30
assert "attestation" in result
@pytest.mark.asyncio
async def test_marketplace_matching(self):
"""Test marketplace service matching."""
from hanzo_agents import (
Web3Agent,
ServiceType,
Web3AgentConfig,
AgentMarketplace,
)
marketplace = AgentMarketplace()
# Create provider
provider = Web3Agent(
name="provider",
description="Service provider",
web3_config=Web3AgentConfig(wallet_enabled=True),
)
# Create requester
requester = Web3Agent(
name="requester",
description="Service requester",
web3_config=Web3AgentConfig(wallet_enabled=True),
)
# Post offer
offer_id = marketplace.post_offer(
agent=provider,
service_type=ServiceType.COMPUTE,
description="GPU compute",
price_eth=0.1,
)
# Post matching request
request_id = marketplace.post_request(
agent=requester,
service_type=ServiceType.COMPUTE,
description="Need GPU",
max_price_eth=0.2,
)
# Should auto-match
assert len(marketplace.matches) == 1
match = list(marketplace.matches.values())[0]
assert match.offer.agent_name == "provider"
assert match.request.requester_name == "requester"
@pytest.mark.asyncio
async def test_deterministic_execution(self):
"""Test deterministic network execution."""
from hanzo_agents import Agent, InferenceResult, create_web3_network
# Create simple test agents
class TestAgent1(Agent):
name = "test1"
async def run(self, state, history, network):
state["step1"] = "done"
return InferenceResult(agent=self.name, content="Step 1 complete")
class TestAgent2(Agent):
name = "test2"
async def run(self, state, history, network):
state["step2"] = "done"
return InferenceResult(agent=self.name, content="Step 2 complete")
# Create two identical networks
agents = [TestAgent1(), TestAgent2()]
network1 = create_web3_network(
agents=agents, task="Test determinism", deterministic=True
)
network2 = create_web3_network(
agents=agents, task="Test determinism", deterministic=True
)
# Run both networks
state1 = await network1.run()
state2 = await network2.run()
# Should produce identical results
assert network1.execution_hash == network2.execution_hash
assert network1.verify_execution(network2.execution_hash)
assert state1["step1"] == state2["step1"]
assert state1["step2"] == state2["step2"]
if __name__ == "__main__":
# Run specific demo if provided
import sys
if len(sys.argv) > 1:
demo_name = sys.argv[1]
if demo_name == "startup":
asyncio.run(run_startup_team_demo())
elif demo_name == "chat":
asyncio.run(run_agent_chat_demo())
elif demo_name == "research":
asyncio.run(run_research_team_demo())
elif demo_name == "game":
asyncio.run(run_game_dev_demo())
else:
print(f"Unknown demo: {demo_name}")
print("Available demos: startup, chat, research, game")
else:
# Run all demos
print("Running all demos...\n")
asyncio.run(run_startup_team_demo())
print("\n" + "=" * 80 + "\n")
asyncio.run(run_agent_chat_demo())
print("\n" + "=" * 80 + "\n")
asyncio.run(run_research_team_demo())
print("\n" + "=" * 80 + "\n")
asyncio.run(run_game_dev_demo())
@@ -1,4 +1,4 @@
Metadata-Version: 2.2
Metadata-Version: 2.4
Name: hanzo-memory
Version: 1.0.1
Summary: AI memory service with FastAPI and MCP support
@@ -66,6 +66,7 @@ Provides-Extra: docs
Requires-Dist: mkdocs>=1.6.0; extra == "docs"
Requires-Dist: mkdocs-material>=9.5.0; extra == "docs"
Requires-Dist: mkdocstrings[python]>=0.27.0; extra == "docs"
Dynamic: license-file
# Hanzo Memory Service
@@ -4,6 +4,7 @@ pyproject.toml
src/hanzo_memory/__init__.py
src/hanzo_memory/cli.py
src/hanzo_memory/config.py
src/hanzo_memory/memory.py
src/hanzo_memory/server.py
src/hanzo_memory.egg-info/PKG-INFO
src/hanzo_memory.egg-info/SOURCES.txt
@@ -17,6 +18,7 @@ src/hanzo_memory/db/__init__.py
src/hanzo_memory/db/base.py
src/hanzo_memory/db/client.py
src/hanzo_memory/db/factory.py
src/hanzo_memory/db/kuzudb_client.py
src/hanzo_memory/db/lancedb_client.py
src/hanzo_memory/db/local_client.py
src/hanzo_memory/db/markdown_reader.py
+146 -1
View File
@@ -33,6 +33,19 @@ from rich.progress import Progress, TextColumn, SpinnerColumn
logger = logging.getLogger(__name__)
console = Console()
# Import GRPO for training-free learning
try:
from hanzoai.grpo import (
ExperienceManager,
EnhancedSemanticExtractor,
EnhancedDeepSeekAdapter,
EnhancedTrajectory,
)
GRPO_AVAILABLE = True
except ImportError:
GRPO_AVAILABLE = False
logger.warning("hanzoai.grpo not available - GRPO learning disabled")
# Import hanzo-network for agent orchestration
try:
from hanzo_network import (
@@ -168,12 +181,14 @@ class HanzoDevOrchestrator:
self,
workspace_dir: str = "~/.hanzo/dev",
claude_code_path: Optional[str] = None,
enable_grpo: bool = True,
):
"""Initialize the orchestrator.
Args:
workspace_dir: Directory for persistence and checkpoints
claude_code_path: Path to Claude Code executable
enable_grpo: Enable Training-Free GRPO learning (default: True)
"""
self.workspace_dir = Path(workspace_dir).expanduser()
self.workspace_dir.mkdir(parents=True, exist_ok=True)
@@ -199,6 +214,31 @@ class HanzoDevOrchestrator:
self.thinking_history: List[ThinkingResult] = []
self._shutdown = False
# Initialize GRPO for training-free learning
self.grpo_enabled = enable_grpo and GRPO_AVAILABLE
if self.grpo_enabled:
grpo_dir = self.workspace_dir / "grpo"
grpo_dir.mkdir(exist_ok=True)
self.experience_manager = ExperienceManager(str(grpo_dir / "experience_library.json"))
# Initialize with DeepSeek if API key available
deepseek_key = os.getenv("DEEPSEEK_API_KEY")
if deepseek_key:
from hanzoai.grpo import EnhancedLLMClient
self.grpo_llm = EnhancedLLMClient(api_key=deepseek_key)
self.grpo_extractor = EnhancedSemanticExtractor(
llm_client=self.grpo_llm,
cache_dir=str(grpo_dir / "cache"),
)
logger.info("GRPO learning enabled with DeepSeek")
else:
self.grpo_enabled = False
logger.warning("GRPO disabled: DEEPSEEK_API_KEY not set")
else:
logger.info("GRPO learning disabled")
def _find_claude_code(self) -> str:
"""Find Claude Code executable."""
# Check common locations
@@ -642,6 +682,108 @@ class HanzoDevOrchestrator:
console.print("[green]✓ Orchestrator shutdown complete[/green]")
async def learn_from_interactions(
self,
query: str,
responses: List[str],
rewards: List[float],
groundtruth: Optional[str] = None,
) -> int:
"""Learn from agent interactions using Training-Free GRPO.
Args:
query: The task/query that was processed
responses: List of agent responses (G trajectories)
rewards: Reward scores for each response (0.0 to 1.0)
groundtruth: Optional ground truth answer for evaluation
Returns:
Number of experiences learned
"""
if not self.grpo_enabled:
logger.debug("GRPO learning disabled, skipping")
return 0
try:
console.print("[cyan]🧠 Learning from interactions with GRPO...[/cyan]")
# Create trajectories from interactions
trajectories = [
EnhancedTrajectory(
query=query,
output=response,
reward=reward,
groundtruth=groundtruth,
)
for response, reward in zip(responses, rewards)
]
# Stage 1: Summarize trajectories
summarized = self.grpo_extractor.summarize_trajectories(
trajectories, use_groundtruth=(groundtruth is not None)
)
# Stage 2: Extract group advantages
advantages = self.grpo_extractor.extract_group_advantages(
summarized,
self.experience_manager.experiences,
use_groundtruth=(groundtruth is not None)
)
# Stage 3: Consolidate into experience library
operations = self.grpo_extractor.consolidate_batch_experiences(
advantages, self.experience_manager.experiences
)
# Apply operations to experience library
experiences_before = len(self.experience_manager.experiences)
self.experience_manager.apply_operations(operations)
experiences_after = len(self.experience_manager.experiences)
# Save updated library
self.experience_manager.save(
str(self.workspace_dir / "grpo" / "experience_library.json")
)
learned_count = experiences_after - experiences_before
console.print(
f"[green]✓ Learned {learned_count} new experiences "
f"(total: {experiences_after})[/green]"
)
return learned_count
except Exception as e:
logger.error(f"GRPO learning failed: {e}")
console.print(f"[yellow]⚠ GRPO learning failed: {e}[/yellow]")
return 0
def get_relevant_experiences(self, query: str, top_k: int = 5) -> List[str]:
"""Retrieve relevant experiences from the library for a query.
Args:
query: The current task/query
top_k: Number of top experiences to retrieve
Returns:
List of relevant experience strings
"""
if not self.grpo_enabled:
return []
# Simple keyword matching for now
# In production, would use semantic similarity
query_lower = query.lower()
relevant = []
for exp in self.experience_manager.experiences:
# Score based on keyword overlap
exp_lower = exp.lower()
if any(word in exp_lower for word in query_lower.split()):
relevant.append(exp)
return relevant[:top_k]
class HanzoDevREPL:
"""REPL interface for driving Hanzo Dev orchestrator."""
@@ -1953,7 +2095,10 @@ class NetworkOrchestrator(HanzoDevOrchestrator):
api_key = None
# Determine provider from model name
if model_name.startswith("gpt") or model_name == "codex":
if model_name.startswith("deepseek"):
provider = "deepseek"
api_key = os.getenv("DEEPSEEK_API_KEY")
elif model_name.startswith("gpt") or model_name == "codex":
provider = "openai"
api_key = os.getenv("OPENAI_API_KEY")
elif model_name.startswith("claude"):
+27 -1
View File
@@ -20,6 +20,7 @@ class FallbackHandler:
def _detect_available_options(self) -> Dict[str, bool]:
"""Detect which AI options are available."""
options = {
"deepseek_api": bool(os.getenv("DEEPSEEK_API_KEY")), # Added DeepSeek
"openai_api": bool(os.getenv("OPENAI_API_KEY")),
"anthropic_api": bool(os.getenv("ANTHROPIC_API_KEY")),
"google_api": bool(
@@ -53,6 +54,9 @@ class FallbackHandler:
order = []
# Priority 1: API keys (fastest, most reliable)
# DeepSeek first for cost efficiency ($0.14/M vs $10+/M for GPT-4)
if self.available_options["deepseek_api"]:
order.append(("deepseek_api", "deepseek-chat"))
if self.available_options["openai_api"]:
order.append(("openai_api", "gpt-4"))
if self.available_options["anthropic_api"]:
@@ -98,6 +102,9 @@ class FallbackHandler:
"""Suggest setup instructions for unavailable options."""
suggestions = []
if not self.available_options["deepseek_api"]:
suggestions.append("• Set DEEPSEEK_API_KEY for cost-effective DeepSeek access ($0.14/M tokens)")
if not self.available_options["openai_api"]:
suggestions.append("• Set OPENAI_API_KEY for GPT-4/GPT-5 access")
@@ -134,6 +141,7 @@ class FallbackHandler:
table.add_column("Model", width=20)
status_map = {
"deepseek_api": ("DeepSeek API", "deepseek-chat"), # Added DeepSeek
"openai_api": ("OpenAI API", "gpt-4"),
"anthropic_api": ("Anthropic API", "claude-3-5"),
"google_api": ("Google API", "gemini-pro"),
@@ -188,7 +196,25 @@ async def smart_chat(message: str, console=None) -> Optional[str]:
# Try the primary option with rate limiting
try:
if option_type == "openai_api":
if option_type == "deepseek_api":
# DeepSeek API (OpenAI-compatible)
from openai import AsyncOpenAI
async def call_deepseek():
client = AsyncOpenAI(
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com/v1"
)
response = await client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": message}],
max_tokens=500,
)
return response.choices[0].message.content
return await smart_limiter.execute_with_limit("deepseek", call_deepseek)
elif option_type == "openai_api":
async def call_openai():
from openai import AsyncOpenAI
+2 -1
View File
@@ -1,7 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
# Import new modules
from . import mcp, auth, types, agents, cluster
from . import mcp, auth, types, agents, cluster, grpo
from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes
from ._utils import file_from_path
from ._client import (
@@ -88,6 +88,7 @@ __all__ = [
"mcp",
"cluster",
"auth",
"grpo",
]
_setup_logging()
+96
View File
@@ -0,0 +1,96 @@
"""Training-Free GRPO implementation for Hanzo AI.
This module provides Training-Free Group Relative Policy Optimization (GRPO)
from Tencent's youtu-agent paper (arXiv:2510.08191v1).
Training-Free GRPO improves LLM performance via context expansion rather than
parameter updates, using semantic advantages extracted via LLM introspection.
Key components:
- ExperienceManager: Manages the experience library (E) with CRUD operations
- SemanticExtractor: Implements 3-stage LLM process for semantic advantage extraction
- Trajectory: Data class for rollout trajectories
- LLMClient: Wrapper for LLM API clients (DeepSeek, OpenAI)
- APIModelAdapter: Use API-hosted models as target model
- DeepSeekAdapter: Convenience wrapper for DeepSeek models
- OpenAIAdapter: Convenience wrapper for OpenAI models
Example usage:
```python
from hanzoai.grpo import DeepSeekAdapter, ExperienceManager, SemanticExtractor, LLMClient
# Initialize components
target_model = DeepSeekAdapter(api_key="your-api-key")
semantic_llm = LLMClient(api_key="your-api-key", base_url="https://api.deepseek.com/v1")
exp_manager = ExperienceManager(checkpoint_path="./experiences/experiences.json")
extractor = SemanticExtractor(semantic_llm, max_operations=5)
# Generate rollouts with experience injection
query = "Solve: 2x + 5 = 13"
group_size = 5
trajectories = []
for i in range(group_size):
experiences_text = exp_manager.format_for_prompt()
response = target_model.generate_with_experiences(
query=query,
experiences=experiences_text,
temperature=0.7 + (i * 0.1)
)
# Evaluate and create trajectory
# trajectories.append(Trajectory(query=query, output=response, reward=score))
# Extract semantic advantages and update experience library
group_operations = extractor.extract_group_advantage(trajectories, exp_manager.format_for_prompt())
exp_manager.apply_operations(group_operations)
exp_manager.save()
```
For more details, see the Training-Free GRPO paper:
https://arxiv.org/abs/2510.08191
"""
# Basic implementations
from .experience_manager import ExperienceManager
from .semantic_extractor import SemanticExtractor, Trajectory, LLMClient
from .api_model_adapter import (
APIModelAdapter,
APIModelConfig,
DeepSeekAdapter,
OpenAIAdapter,
)
# Enhanced implementations with full Tencent feature parity
from .enhanced_semantic_extractor import (
EnhancedSemanticExtractor,
EnhancedLLMClient,
Trajectory as EnhancedTrajectory,
rollout_with_timeout,
)
from .enhanced_api_model_adapter import (
EnhancedAPIModelAdapter,
EnhancedAPIModelConfig,
EnhancedDeepSeekAdapter,
EnhancedOpenAIAdapter,
)
__all__ = [
# Basic
"ExperienceManager",
"SemanticExtractor",
"Trajectory",
"LLMClient",
"APIModelAdapter",
"APIModelConfig",
"DeepSeekAdapter",
"OpenAIAdapter",
# Enhanced (recommended for production use)
"EnhancedSemanticExtractor",
"EnhancedLLMClient",
"EnhancedTrajectory",
"EnhancedAPIModelAdapter",
"EnhancedAPIModelConfig",
"EnhancedDeepSeekAdapter",
"EnhancedOpenAIAdapter",
"rollout_with_timeout",
]
+267
View File
@@ -0,0 +1,267 @@
# Copyright 2025 Zoo Labs Foundation Inc. and the Gym team.
#
# API Model Adapter for Training-Free GRPO
# Enables using DeepSeek API-hosted models as the target model
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import List, Optional, Dict, Any
from dataclasses import dataclass
@dataclass
class APIModelConfig:
"""Configuration for API-hosted models.
Supports DeepSeek, OpenAI, and any OpenAI-compatible API.
"""
api_key: str
base_url: str = "https://api.deepseek.com/v1"
model: str = "deepseek-chat"
temperature: float = 0.7
max_tokens: int = 4096
top_p: float = 0.95
class APIModelAdapter:
"""Adapter for using API-hosted models in Training-Free GRPO.
This enables using models like:
- DeepSeek-V3 (deepseek-chat, deepseek-reasoner)
- OpenAI GPT-4o (gpt-4o, gpt-4o-mini)
- Any OpenAI-compatible API
Benefits over local models:
- No GPU required
- Faster inference (optimized infrastructure)
- Better base models (DeepSeek-V3 is SOTA)
- Lower total cost (no local compute)
- Easy scaling
Example:
>>> config = APIModelConfig(
... api_key="sk-xxx",
... base_url="https://api.deepseek.com/v1",
... model="deepseek-chat"
... )
>>> adapter = APIModelAdapter(config)
>>> response = adapter.generate("What is 5 + 3?")
>>> print(response) # "8"
"""
def __init__(self, config: APIModelConfig):
"""Initialize API model adapter.
Args:
config: API model configuration
"""
self.config = config
try:
from openai import OpenAI
except ImportError:
raise ImportError(
"OpenAI package not found. Install with: pip install openai"
)
self.client = OpenAI(
api_key=config.api_key,
base_url=config.base_url
)
def generate(
self,
prompt: str,
temperature: Optional[float] = None,
max_tokens: Optional[int] = None,
system_prompt: Optional[str] = None
) -> str:
"""Generate a response from the API model.
Args:
prompt: User prompt/query
temperature: Sampling temperature (overrides config)
max_tokens: Max tokens to generate (overrides config)
system_prompt: Optional system prompt
Returns:
response: Generated text
"""
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": prompt})
response = self.client.chat.completions.create(
model=self.config.model,
messages=messages,
temperature=temperature or self.config.temperature,
max_tokens=max_tokens or self.config.max_tokens,
top_p=self.config.top_p
)
return response.choices[0].message.content
def generate_batch(
self,
prompts: List[str],
temperature: Optional[float] = None,
max_tokens: Optional[int] = None,
system_prompt: Optional[str] = None
) -> List[str]:
"""Generate responses for a batch of prompts.
Note: Currently sequential. For true parallel execution,
use asyncio version or API batching features.
Args:
prompts: List of prompts
temperature: Sampling temperature (overrides config)
max_tokens: Max tokens to generate (overrides config)
system_prompt: Optional system prompt
Returns:
responses: List of generated texts
"""
responses = []
for prompt in prompts:
response = self.generate(
prompt,
temperature=temperature,
max_tokens=max_tokens,
system_prompt=system_prompt
)
responses.append(response)
return responses
def generate_with_experiences(
self,
query: str,
experiences: str,
temperature: Optional[float] = None,
max_tokens: Optional[int] = None
) -> str:
"""Generate response with experiences injected into prompt.
This is the core method for Training-Free GRPO rollout generation.
Args:
query: Problem/query to solve
experiences: Formatted experience library
temperature: Sampling temperature
max_tokens: Max tokens to generate
Returns:
response: Generated solution/output
"""
# Inject experiences into prompt
if experiences and experiences != "None":
enhanced_prompt = f"""Please solve the problem:
{query}
When solving problems, you MUST first carefully read and understand the helpful instructions and experiences:
{experiences}
Now solve the problem step by step."""
else:
enhanced_prompt = f"""Please solve the problem:
{query}
Solve the problem step by step."""
return self.generate(
enhanced_prompt,
temperature=temperature,
max_tokens=max_tokens
)
def __repr__(self) -> str:
"""Return string representation."""
return f"APIModelAdapter(model={self.config.model}, base_url={self.config.base_url})"
class DeepSeekAdapter(APIModelAdapter):
"""Convenience wrapper for DeepSeek models.
Supports:
- deepseek-chat (V3, recommended for Training-Free GRPO)
- deepseek-reasoner (V3 with reasoning traces)
Example:
>>> adapter = DeepSeekAdapter(
... api_key="sk-xxx",
... model="deepseek-chat"
... )
>>> response = adapter.generate("Solve: x + 5 = 10")
"""
def __init__(
self,
api_key: str,
model: str = "deepseek-chat",
temperature: float = 0.7
):
"""Initialize DeepSeek adapter.
Args:
api_key: DeepSeek API key
model: Model name (deepseek-chat or deepseek-reasoner)
temperature: Sampling temperature
"""
config = APIModelConfig(
api_key=api_key,
base_url="https://api.deepseek.com/v1",
model=model,
temperature=temperature
)
super().__init__(config)
class OpenAIAdapter(APIModelAdapter):
"""Convenience wrapper for OpenAI models.
Supports:
- gpt-4o (latest GPT-4 Omni)
- gpt-4o-mini (smaller, faster, cheaper)
- o1-preview (reasoning model)
Example:
>>> adapter = OpenAIAdapter(
... api_key="sk-xxx",
... model="gpt-4o-mini"
... )
>>> response = adapter.generate("Solve: x + 5 = 10")
"""
def __init__(
self,
api_key: str,
model: str = "gpt-4o-mini",
temperature: float = 0.7
):
"""Initialize OpenAI adapter.
Args:
api_key: OpenAI API key
model: Model name (gpt-4o, gpt-4o-mini, etc.)
temperature: Sampling temperature
"""
config = APIModelConfig(
api_key=api_key,
base_url="https://api.openai.com/v1",
model=model,
temperature=temperature
)
super().__init__(config)
@@ -0,0 +1,380 @@
# Copyright 2025 Zoo Labs Foundation Inc. and the Gym team.
#
# Enhanced API Model Adapter for Training-Free GRPO
# Based on Tencent youtu-agent: https://arxiv.org/abs/2510.08191v1
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import asyncio
import os
import time
from dataclasses import dataclass
from typing import Optional, Tuple
@dataclass
class EnhancedAPIModelConfig:
"""Enhanced configuration for API-hosted models with full feature support.
Attributes:
api_key: API key (can be set via HANZO_GRPO_API_KEY or DEEPSEEK_API_KEY env var)
base_url: API base URL (default: https://api.deepseek.com/v1)
model: Model name (e.g., "deepseek-chat", "gpt-4o", "o1-preview")
temperature: Sampling temperature
max_tokens: Maximum tokens to generate
top_p: Nucleus sampling parameter
max_retries: Maximum retry attempts
retry_delay: Initial retry delay in seconds (exponential backoff)
timeout: Request timeout in seconds
support_reasoning: Whether model supports reasoning content (e.g., o1 models)
"""
api_key: str
base_url: str = "https://api.deepseek.com/v1"
model: str = "deepseek-chat"
temperature: float = 0.7
max_tokens: int = 4096
top_p: float = 0.95
max_retries: int = 3
retry_delay: float = 1.0
timeout: Optional[float] = None
support_reasoning: bool = False
@classmethod
def from_env(cls, **kwargs) -> "EnhancedAPIModelConfig":
"""Create config from environment variables with overrides.
Environment variables:
- HANZO_GRPO_API_KEY or DEEPSEEK_API_KEY: API key
- HANZO_GRPO_BASE_URL: API base URL
- HANZO_GRPO_MODEL: Model name
- HANZO_GRPO_MAX_RETRIES: Maximum retries
- HANZO_GRPO_TIMEOUT: Request timeout
Args:
**kwargs: Override any config parameter
Returns:
EnhancedAPIModelConfig instance
"""
api_key = kwargs.get("api_key") or os.getenv("HANZO_GRPO_API_KEY") or os.getenv("DEEPSEEK_API_KEY")
if not api_key:
raise ValueError("API key required. Set HANZO_GRPO_API_KEY or DEEPSEEK_API_KEY env var")
defaults = {
"api_key": api_key,
"base_url": os.getenv("HANZO_GRPO_BASE_URL", "https://api.deepseek.com/v1"),
"model": os.getenv("HANZO_GRPO_MODEL", "deepseek-chat"),
"max_retries": int(os.getenv("HANZO_GRPO_MAX_RETRIES", "3")),
"timeout": float(os.getenv("HANZO_GRPO_TIMEOUT", "0")) or None,
}
# Merge with provided kwargs
defaults.update(kwargs)
return cls(**defaults)
class EnhancedAPIModelAdapter:
"""Enhanced API model adapter with retry, reasoning, and async support.
Features:
- Automatic retry with exponential backoff
- Reasoning content support (for o1 models)
- Async generation with timeout
- Environment variable configuration
- Experience injection for Training-Free GRPO
"""
def __init__(self, config: EnhancedAPIModelConfig):
"""Initialize enhanced adapter.
Args:
config: Enhanced API model configuration
"""
from openai import OpenAI, AsyncOpenAI
self.config = config
self.client = OpenAI(
api_key=config.api_key,
base_url=config.base_url,
timeout=config.timeout
)
self.async_client = AsyncOpenAI(
api_key=config.api_key,
base_url=config.base_url,
timeout=config.timeout
)
def generate(
self,
prompt: str,
temperature: Optional[float] = None,
max_tokens: Optional[int] = None,
return_reasoning: bool = False,
) -> str | Tuple[str, Optional[str]]:
"""Generate response with retry logic.
Args:
prompt: User prompt
temperature: Override default temperature
max_tokens: Override default max_tokens
return_reasoning: If True, return (response, reasoning) tuple
Returns:
Response string, or (response, reasoning) tuple if return_reasoning=True
"""
temp = temperature if temperature is not None else self.config.temperature
tokens = max_tokens if max_tokens is not None else self.config.max_tokens
for attempt in range(self.config.max_retries):
try:
response = self.client.chat.completions.create(
model=self.config.model,
messages=[{"role": "user", "content": prompt}],
temperature=temp,
max_tokens=tokens,
top_p=self.config.top_p,
)
response_text = response.choices[0].message.content.strip()
if return_reasoning and self.config.support_reasoning:
reasoning = getattr(response.choices[0].message, "reasoning_content", None)
return response_text, reasoning
return response_text
except Exception as e:
if attempt < self.config.max_retries - 1:
delay = self.config.retry_delay * (2 ** attempt)
print(f"Generation failed (attempt {attempt + 1}/{self.config.max_retries}): {e}")
print(f"Retrying in {delay}s...")
time.sleep(delay)
else:
raise Exception(f"Generation failed after {self.config.max_retries} attempts: {e}")
async def generate_async(
self,
prompt: str,
temperature: Optional[float] = None,
max_tokens: Optional[int] = None,
return_reasoning: bool = False,
timeout: Optional[float] = None,
) -> str | Tuple[str, Optional[str]]:
"""Generate response asynchronously with timeout.
Args:
prompt: User prompt
temperature: Override default temperature
max_tokens: Override default max_tokens
return_reasoning: If True, return (response, reasoning) tuple
timeout: Request timeout (overrides config.timeout)
Returns:
Response string, or (response, reasoning) tuple if return_reasoning=True
"""
temp = temperature if temperature is not None else self.config.temperature
tokens = max_tokens if max_tokens is not None else self.config.max_tokens
request_timeout = timeout or self.config.timeout
for attempt in range(self.config.max_retries):
try:
async def make_request():
response = await self.async_client.chat.completions.create(
model=self.config.model,
messages=[{"role": "user", "content": prompt}],
temperature=temp,
max_tokens=tokens,
top_p=self.config.top_p,
)
return response
if request_timeout:
response = await asyncio.wait_for(make_request(), timeout=request_timeout)
else:
response = await make_request()
response_text = response.choices[0].message.content.strip()
if return_reasoning and self.config.support_reasoning:
reasoning = getattr(response.choices[0].message, "reasoning_content", None)
return response_text, reasoning
return response_text
except asyncio.TimeoutError:
if attempt < self.config.max_retries - 1:
delay = self.config.retry_delay * (2 ** attempt)
print(f"Generation timeout (attempt {attempt + 1}/{self.config.max_retries})")
print(f"Retrying in {delay}s...")
await asyncio.sleep(delay)
else:
raise Exception(f"Generation timeout after {self.config.max_retries} attempts")
except Exception as e:
if attempt < self.config.max_retries - 1:
delay = self.config.retry_delay * (2 ** attempt)
print(f"Generation failed (attempt {attempt + 1}/{self.config.max_retries}): {e}")
print(f"Retrying in {delay}s...")
await asyncio.sleep(delay)
else:
raise Exception(f"Generation failed after {self.config.max_retries} attempts: {e}")
def generate_with_experiences(
self,
query: str,
experiences: str,
temperature: Optional[float] = None,
max_tokens: Optional[int] = None,
return_reasoning: bool = False,
) -> str | Tuple[str, Optional[str]]:
"""Generate response with experiences injected into prompt.
Args:
query: User query/problem
experiences: Formatted experiences string
temperature: Override default temperature
max_tokens: Override default max_tokens
return_reasoning: If True, return (response, reasoning) tuple
Returns:
Response string, or (response, reasoning) tuple if return_reasoning=True
"""
if experiences and experiences != "None":
enhanced_prompt = f"""Please solve the problem:
{query}
When solving problems, you MUST first carefully read and understand the helpful instructions and experiences:
{experiences}
Now solve the problem step by step."""
else:
enhanced_prompt = f"""Please solve the problem:
{query}
Solve the problem step by step."""
return self.generate(
enhanced_prompt,
temperature=temperature,
max_tokens=max_tokens,
return_reasoning=return_reasoning
)
async def generate_with_experiences_async(
self,
query: str,
experiences: str,
temperature: Optional[float] = None,
max_tokens: Optional[int] = None,
return_reasoning: bool = False,
timeout: Optional[float] = None,
) -> str | Tuple[str, Optional[str]]:
"""Generate response with experiences asynchronously.
Args:
query: User query/problem
experiences: Formatted experiences string
temperature: Override default temperature
max_tokens: Override default max_tokens
return_reasoning: If True, return (response, reasoning) tuple
timeout: Request timeout
Returns:
Response string, or (response, reasoning) tuple if return_reasoning=True
"""
if experiences and experiences != "None":
enhanced_prompt = f"""Please solve the problem:
{query}
When solving problems, you MUST first carefully read and understand the helpful instructions and experiences:
{experiences}
Now solve the problem step by step."""
else:
enhanced_prompt = f"""Please solve the problem:
{query}
Solve the problem step by step."""
return await self.generate_async(
enhanced_prompt,
temperature=temperature,
max_tokens=max_tokens,
return_reasoning=return_reasoning,
timeout=timeout
)
class EnhancedDeepSeekAdapter(EnhancedAPIModelAdapter):
"""Convenience wrapper for DeepSeek models with enhanced features."""
def __init__(
self,
api_key: Optional[str] = None,
model: str = "deepseek-chat",
temperature: float = 0.7,
max_retries: int = 3,
timeout: Optional[float] = None,
):
"""Initialize DeepSeek adapter with env var fallback.
Args:
api_key: API key (or set DEEPSEEK_API_KEY env var)
model: Model name
temperature: Sampling temperature
max_retries: Maximum retry attempts
timeout: Request timeout
"""
config = EnhancedAPIModelConfig.from_env(
api_key=api_key,
base_url="https://api.deepseek.com/v1",
model=model,
temperature=temperature,
max_retries=max_retries,
timeout=timeout,
)
super().__init__(config)
class EnhancedOpenAIAdapter(EnhancedAPIModelAdapter):
"""Convenience wrapper for OpenAI models with enhanced features."""
def __init__(
self,
api_key: Optional[str] = None,
model: str = "gpt-4o",
temperature: float = 0.7,
max_retries: int = 3,
timeout: Optional[float] = None,
support_reasoning: bool = False,
):
"""Initialize OpenAI adapter with env var fallback.
Args:
api_key: API key (or set OPENAI_API_KEY env var)
model: Model name (e.g., "gpt-4o", "o1-preview")
temperature: Sampling temperature
max_retries: Maximum retry attempts
timeout: Request timeout
support_reasoning: Enable for o1 models
"""
config = EnhancedAPIModelConfig(
api_key=api_key or os.getenv("OPENAI_API_KEY", ""),
base_url="https://api.openai.com/v1",
model=model,
temperature=temperature,
max_retries=max_retries,
timeout=timeout,
support_reasoning=support_reasoning,
)
super().__init__(config)
@@ -0,0 +1,616 @@
# Copyright 2025 Zoo Labs Foundation Inc. and the Gym team.
#
# Enhanced Semantic Extractor for Training-Free GRPO with Full Feature Parity
# Based on Tencent youtu-agent: https://arxiv.org/abs/2510.08191v1
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import asyncio
import copy
import json
import os
import time
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional
try:
from tqdm import tqdm
TQDM_AVAILABLE = True
except ImportError:
TQDM_AVAILABLE = False
# Fallback progress indicator
class tqdm:
def __init__(self, iterable=None, total=None, desc=None, **kwargs):
self.iterable = iterable
self.total = total
self.desc = desc
self.n = 0
def __iter__(self):
return iter(self.iterable) if self.iterable else iter([])
def update(self, n=1):
self.n += n
if self.desc and self.total:
print(f"{self.desc}: {self.n}/{self.total}", end="\r")
@dataclass
class Trajectory:
"""Single rollout trajectory with full feature support.
Attributes:
query: Input query/problem statement
output: Model-generated output/response
reward: Numerical reward score (e.g., 0 for wrong, 1 for correct)
groundtruth: Optional ground truth answer for supervised learning
summary: Optional LLM-generated step-by-step summary
reasoning: Optional reasoning content (for o1 models)
retry_count: Number of retries attempted
task_time: Time taken for this trajectory in seconds
"""
query: str
output: str
reward: float
groundtruth: Optional[str] = None
summary: Optional[str] = None
reasoning: Optional[str] = None
retry_count: int = 0
task_time: Optional[float] = None
class EnhancedLLMClient:
"""Enhanced LLM client with retry logic, reasoning support, and env config.
Features:
- Automatic retry with exponential backoff
- Reasoning content support (for OpenAI o1 models)
- Environment variable configuration
- Max retries and timeout control
"""
def __init__(
self,
api_key: Optional[str] = None,
base_url: Optional[str] = None,
model: Optional[str] = None,
max_retries: int = 3,
retry_delay: float = 1.0,
timeout: Optional[float] = None,
):
"""Initialize LLM client with env var fallback.
Environment variables (if parameters not provided):
- HANZO_GRPO_API_KEY or DEEPSEEK_API_KEY
- HANZO_GRPO_BASE_URL
- HANZO_GRPO_MODEL
"""
from openai import OpenAI
# Use env vars as fallback
self.api_key = api_key or os.getenv("HANZO_GRPO_API_KEY") or os.getenv("DEEPSEEK_API_KEY")
self.base_url = base_url or os.getenv("HANZO_GRPO_BASE_URL", "https://api.deepseek.com/v1")
self.model = model or os.getenv("HANZO_GRPO_MODEL", "deepseek-chat")
self.max_retries = max_retries
self.retry_delay = retry_delay
self.timeout = timeout
if not self.api_key:
raise ValueError("API key required. Set HANZO_GRPO_API_KEY or pass api_key parameter.")
self.client = OpenAI(api_key=self.api_key, base_url=self.base_url, timeout=timeout)
def chat(
self,
prompt: str,
temperature: float = 0.7,
max_tokens: int = 4096,
return_reasoning: bool = False,
) -> str | tuple[str, Optional[str]]:
"""Call LLM with automatic retry logic.
Args:
prompt: User prompt
temperature: Sampling temperature
max_tokens: Maximum tokens to generate
return_reasoning: If True, return (response, reasoning) tuple
Returns:
Response string, or (response, reasoning) tuple if return_reasoning=True
"""
for attempt in range(self.max_retries):
try:
response = self.client.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": prompt}],
temperature=temperature,
max_tokens=max_tokens,
)
response_text = response.choices[0].message.content.strip()
if return_reasoning:
# For OpenAI o1 models or models with reasoning_content
reasoning = getattr(response.choices[0].message, "reasoning_content", None)
return response_text, reasoning
return response_text
except Exception as e:
if attempt < self.max_retries - 1:
delay = self.retry_delay * (2 ** attempt) # Exponential backoff
print(f"LLM call failed (attempt {attempt + 1}/{self.max_retries}): {e}")
print(f"Retrying in {delay}s...")
time.sleep(delay)
else:
raise Exception(f"LLM call failed after {self.max_retries} attempts: {e}")
class EnhancedSemanticExtractor:
"""Enhanced semantic extractor with full Tencent feature parity.
New Features:
- File caching for intermediate results (single_rollout_summary.json, etc.)
- Parallel processing with ThreadPoolExecutor
- Automatic retry logic with exponential backoff
- Partial correct filtering (0 < avg_score < 1)
- Progress tracking with tqdm
- Async support for rollouts with timeout
- Reasoning content support for o1 models
- Environment-based configuration
"""
def __init__(
self,
llm_client: EnhancedLLMClient,
max_operations: int = 3,
max_workers: int = 16,
cache_dir: Optional[str] = None,
enable_caching: bool = True,
enable_parallel: bool = True,
filter_partial_correct: bool = True,
):
"""Initialize enhanced semantic extractor.
Args:
llm_client: Enhanced LLM client with retry logic
max_operations: Max operations per group critique
max_workers: Max parallel workers for ThreadPoolExecutor
cache_dir: Directory for caching intermediate results
enable_caching: Whether to enable file caching
enable_parallel: Whether to enable parallel processing
filter_partial_correct: Only process groups with 0 < avg_score < 1
"""
self.llm = llm_client
self.max_operations = max_operations
self.max_workers = max_workers
self.cache_dir = cache_dir
self.enable_caching = enable_caching
self.enable_parallel = enable_parallel
self.filter_partial_correct = filter_partial_correct
def _get_cache_path(self, filename: str) -> Optional[Path]:
"""Get cache file path if caching enabled."""
if not self.enable_caching or not self.cache_dir:
return None
return Path(self.cache_dir) / filename
def _load_cache(self, filename: str) -> Optional[Any]:
"""Load from cache if exists."""
cache_path = self._get_cache_path(filename)
if cache_path and cache_path.exists():
with open(cache_path) as f:
data = json.load(f)
if len(data) > 0:
print(f"Loaded from cache: {cache_path}")
return data
return None
def _save_cache(self, filename: str, data: Any):
"""Save to cache."""
cache_path = self._get_cache_path(filename)
if cache_path:
cache_path.parent.mkdir(parents=True, exist_ok=True)
with open(cache_path, "w") as f:
json.dump(data, f, indent=2)
print(f"Saved to cache: {cache_path}")
def summarize_trajectories(
self,
trajectories: List[Trajectory],
use_groundtruth: bool = True,
) -> Dict[str, List[Trajectory]]:
"""Stage 1: Summarize trajectories step-by-step with caching and parallel processing.
Args:
trajectories: List of trajectories to summarize
use_groundtruth: Whether to use ground truth in summarization
Returns:
Dict mapping query to list of summarized trajectories
"""
# Try to load from cache
cached = self._load_cache("single_rollout_summary.json")
if cached:
# Convert back to Trajectory objects
result = defaultdict(list)
for query, trajs in cached.items():
result[query] = [Trajectory(**t) for t in trajs]
return result
# Group by query
query_to_trajectories = defaultdict(list)
for traj in trajectories:
query_to_trajectories[traj.query].append(traj)
# Filter to partial correct groups if enabled
all_trajectories_to_process = []
for trajs in query_to_trajectories.values():
if self.filter_partial_correct and use_groundtruth:
scores = [t.reward for t in trajs]
avg_score = sum(scores) / len(scores)
if 0 < avg_score < 1: # Partial correct
all_trajectories_to_process.extend(trajs)
else:
all_trajectories_to_process.extend(trajs)
def process_trajectory(traj: Trajectory) -> Optional[Trajectory]:
"""Process single trajectory with LLM."""
try:
if use_groundtruth and traj.groundtruth:
prompt = f"""An agent system may be provided with some experiences, and then it produces the following trajectory to solve the given problem. Please summarize the trajectory step-by-step:
1. For each step, describe what action is being taken, and which experience has been used in this step.
2. Given the grading of this rollout and the correct answer, identify and explain any steps that represent detours, errors, or backtracking...
3. Maintain all the core outcome of each step, even if it was part of a flawed process.
<trajectory>
{traj.output}
</trajectory>
<evaluation>
This trajectory delivers **{"correct" if traj.reward > 0 else "wrong"}** answer
</evaluation>
<groundtruth>{traj.groundtruth}</groundtruth>
Only return the trajectory summary of each step, e.g.,
1. what happened in the first step and the core outcomes
2. what happened in the second step and the core outcomes
3. ..."""
else:
prompt = f"""An agent system produces the following trajectory. Please summarize the trajectory step-by-step:
<trajectory>
{traj.output}
</trajectory>
Only return the trajectory summary of each step."""
summary = self.llm.chat(prompt)
result = copy.copy(traj)
result.summary = summary
return result
except Exception as e:
print(f"Warning: Failed to summarize trajectory: {e}")
return None
# Parallel or sequential processing
results = defaultdict(list)
if self.enable_parallel and self.max_workers > 1:
with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
future_to_traj = {
executor.submit(process_trajectory, traj): traj
for traj in all_trajectories_to_process
}
iterator = as_completed(future_to_traj)
if TQDM_AVAILABLE:
iterator = tqdm(iterator, total=len(all_trajectories_to_process), desc="Summarizing trajectories")
for future in iterator:
result = future.result()
if result:
results[result.query].append(result)
else:
# Sequential processing with progress bar
iterator = all_trajectories_to_process
if TQDM_AVAILABLE:
iterator = tqdm(iterator, desc="Summarizing trajectories")
for traj in iterator:
result = process_trajectory(traj)
if result:
results[result.query].append(result)
# Save to cache
cache_data = {
query: [{"query": t.query, "output": t.output, "reward": t.reward,
"groundtruth": t.groundtruth, "summary": t.summary} for t in trajs]
for query, trajs in results.items()
}
self._save_cache("single_rollout_summary.json", cache_data)
return results
def extract_group_advantages(
self,
query_to_summarized_trajectories: Dict[str, List[Trajectory]],
experiences: str,
use_groundtruth: bool = True,
) -> List[Dict]:
"""Stage 2: Extract group advantages from summarized trajectories.
Args:
query_to_summarized_trajectories: Dict of query to summarized trajectories
experiences: Formatted experiences string
use_groundtruth: Whether to use ground truth
Returns:
List of critique dictionaries with operations
"""
# Try to load from cache
cached = self._load_cache("single_query_critique.json")
if cached:
return cached
# Filter to partial correct groups if enabled
all_groups = []
for trajs in query_to_summarized_trajectories.values():
if self.filter_partial_correct and use_groundtruth:
scores = [t.reward for t in trajs]
avg_score = sum(scores) / len(scores)
if 0 < avg_score < 1:
all_groups.append(trajs)
else:
all_groups.append(trajs)
def process_group(trajs: List[Trajectory]) -> Optional[Dict]:
"""Process single group of trajectories."""
try:
query = trajs[0].query
groundtruth = trajs[0].groundtruth if use_groundtruth else None
formatted_trajs = "\n\n".join([
f"Trajectory {i+1} (Answer {'correct' if t.reward > 0 else 'wrong'}):\n{t.summary}"
for i, t in enumerate(trajs)
])
if use_groundtruth and groundtruth:
prompt = f"""Given the problem and several trajectory summaries with grading, generate semantic experiences that could help future rollouts.
Problem: {query}
Correct Answer: {groundtruth}
Current Experiences:
{experiences}
Trajectories:
{formatted_trajs}
Based on comparing these trajectories, generate up to {self.max_operations} operations to update experiences.
Return a JSON array of operations with format:
[{{"option": "add", "experience": "..."}}, {{"option": "modify", "modified_from": "G0", "experience": "..."}}, ...]
Only return the JSON array."""
else:
prompt = f"""Given the problem and several trajectory summaries, generate semantic experiences.
Problem: {query}
Current Experiences:
{experiences}
Trajectories:
{formatted_trajs}
Generate up to {self.max_operations} operations to update experiences.
Return JSON array: [{{"option": "add", "experience": "..."}}, ...]"""
response = self.llm.chat(prompt)
response_clean = response.split("```json")[-1].split("```")[0].strip()
operations = json.loads(response_clean)
return {
"trajectories": trajs,
"critique": response,
"operations": operations[:self.max_operations]
}
except Exception as e:
print(f"Warning: Failed to extract group advantage: {e}")
return None
# Parallel or sequential processing
results = []
if self.enable_parallel and self.max_workers > 1:
with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
future_to_group = {
executor.submit(process_group, trajs): trajs
for trajs in all_groups
}
iterator = as_completed(future_to_group)
if TQDM_AVAILABLE:
iterator = tqdm(iterator, total=len(all_groups), desc="Extracting group advantages")
for future in iterator:
result = future.result()
if result:
results.append(result)
else:
iterator = all_groups
if TQDM_AVAILABLE:
iterator = tqdm(iterator, desc="Extracting group advantages")
for trajs in iterator:
result = process_group(trajs)
if result:
results.append(result)
# Save to cache
self._save_cache("single_query_critique.json", results)
return results
def consolidate_batch(
self,
all_group_operations: List[List[Dict]],
experiences: Dict[str, str],
) -> Dict[str, str]:
"""Stage 3: Consolidate all group operations into final experience updates.
Args:
all_group_operations: List of operation lists from each group
experiences: Current experience dictionary
Returns:
Updated experience dictionary
"""
# Try to load from cache
cached = self._load_cache("batch_update.json")
if cached:
return cached.get("new_experiences", experiences)
print("Batch consolidation")
# Collect all operations
all_operations = []
for ops in all_group_operations:
all_operations.extend(ops)
print(f"- Total operations to process: {len(all_operations)}")
# Split into add and modify operations
candidate_experiences = copy.deepcopy(experiences)
to_modify = []
max_id = 0
for operation in all_operations:
if operation.get("option") == "modify":
if operation.get("modified_from") in candidate_experiences:
to_modify.append(operation)
elif operation.get("option") == "add":
candidate_experiences[f"C{max_id}"] = operation["experience"]
max_id += 1
print(f"- Added experiences: {max_id}")
print(f"- Experiences to modify: {len(to_modify)}")
print(f"- Candidate experiences: {len(candidate_experiences)}")
# Use LLM to create revision plan
if len(to_modify) > 0:
prompt = f"""Given candidate experiences and modification requests, create a revision plan.
Candidate Experiences:
{json.dumps(candidate_experiences, indent=2)}
Modification Requests:
{json.dumps(to_modify, indent=2)}
Create a revision plan as JSON array with operations:
- {{"option": "modify", "modified_from": "ID", "experience": "..."}}
- {{"option": "merge", "merged_from": ["ID1", "ID2"], "experience": "..."}}
Return only the JSON array."""
try:
response = self.llm.chat(prompt)
response_clean = response.split("```json")[-1].split("```")[0].strip()
revision_plan = json.loads(response_clean)
except Exception as e:
print(f"Warning: Failed to create revision plan: {e}")
revision_plan = []
else:
revision_plan = []
# Apply revision plan
new_experiences = copy.deepcopy(candidate_experiences)
for operation in revision_plan:
try:
if operation["option"] == "modify":
new_experiences[operation["modified_from"]] = operation["experience"]
elif operation["option"] == "merge":
for exp_id in operation["merged_from"]:
if exp_id in new_experiences:
del new_experiences[exp_id]
new_experiences[f"C{max_id}"] = operation["experience"]
max_id += 1
except Exception as e:
print(f"Error applying operation {operation}: {e}")
print(f"- Final experiences: {len(new_experiences)}")
# Save to cache
cache_data = {
"operations": all_operations,
"revision_plan": revision_plan,
"new_experiences": new_experiences,
}
self._save_cache("batch_update.json", cache_data)
# Reassign IDs
final_experiences = {f"G{i}": exp for i, exp in enumerate(new_experiences.values())}
return final_experiences
# Async rollout support with timeout
async def rollout_with_timeout(
generate_func: Callable,
query: str,
timeout: float = 3600,
max_retries: int = 3,
**kwargs
) -> Optional[str]:
"""Execute rollout with timeout and retry support.
Args:
generate_func: Function to call for generation (must be async-compatible)
query: Query/prompt to generate from
timeout: Timeout in seconds
max_retries: Maximum retry attempts
**kwargs: Additional arguments to pass to generate_func
Returns:
Generated response or None if failed
"""
for attempt in range(max_retries):
try:
# Wrap sync function in async if needed
if asyncio.iscoroutinefunction(generate_func):
coro = generate_func(query, **kwargs)
else:
coro = asyncio.to_thread(generate_func, query, **kwargs)
result = await asyncio.wait_for(coro, timeout=timeout)
return result
except asyncio.TimeoutError:
print(f"Rollout timeout (attempt {attempt + 1}/{max_retries})")
if attempt < max_retries - 1:
await asyncio.sleep(2 ** attempt) # Exponential backoff
else:
print(f"Rollout failed after {max_retries} timeout attempts")
return None
except Exception as e:
print(f"Rollout error (attempt {attempt + 1}/{max_retries}): {e}")
if attempt < max_retries - 1:
await asyncio.sleep(2 ** attempt)
else:
print(f"Rollout failed after {max_retries} attempts")
return None
+202
View File
@@ -0,0 +1,202 @@
# Copyright 2025 Zoo Labs Foundation Inc. and the Gym team.
#
# Experience Manager for Training-Free GRPO
# Based on Tencent youtu-agent: https://arxiv.org/abs/2510.08191v1
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Dict, List, Literal
import json
from pathlib import Path
Operation = Literal["add", "delete", "modify", "merge", "keep"]
class ExperienceManager:
"""Manages the experience library E for Training-Free GRPO.
The experience library stores generalizable strategic insights extracted
from trajectory groups. Each experience is a concise (≤32 words) natural
language statement that guides future problem-solving.
Example experiences:
- "When solving equations, verify solutions by substitution to catch errors."
- "For optimization problems, check boundary conditions before applying calculus."
- "In probability, use conditional probability when events are dependent."
"""
def __init__(self, checkpoint_path: str = None):
"""Initialize experience manager.
Args:
checkpoint_path: Path to load/save experiences (JSON format)
"""
self.experiences: Dict[str, str] = {}
self._next_id = 0
self.checkpoint_path = checkpoint_path
if checkpoint_path and Path(checkpoint_path).exists():
self.load(checkpoint_path)
def add(self, experience: str) -> str:
"""Add new experience, return assigned ID.
Args:
experience: Natural language experience statement (≤32 words)
Returns:
exp_id: Assigned experience ID (format: "G{N}")
"""
exp_id = f"G{self._next_id}"
self.experiences[exp_id] = experience
self._next_id += 1
return exp_id
def delete(self, exp_id: str) -> bool:
"""Delete experience by ID.
Args:
exp_id: Experience ID to delete
Returns:
success: True if deleted, False if ID not found
"""
if exp_id in self.experiences:
del self.experiences[exp_id]
return True
return False
def modify(self, exp_id: str, new_experience: str) -> bool:
"""Modify existing experience.
Args:
exp_id: Experience ID to modify
new_experience: New experience text
Returns:
success: True if modified, False if ID not found
"""
if exp_id in self.experiences:
self.experiences[exp_id] = new_experience
return True
return False
def merge(self, exp_ids: List[str], merged_experience: str) -> str:
"""Merge multiple experiences into one.
Deletes the original experiences and creates a new merged one.
Args:
exp_ids: List of experience IDs to merge
merged_experience: Merged experience text
Returns:
new_exp_id: ID of the newly created merged experience
"""
# Delete old experiences
for exp_id in exp_ids:
self.delete(exp_id)
# Add merged experience
return self.add(merged_experience)
def apply_operations(self, operations: List[Dict]) -> None:
"""Apply batch of operations from LLM.
This is the main interface for updating the experience library based
on LLM-extracted semantic advantages.
Args:
operations: List of operation dictionaries with keys:
- "option": One of ["add", "modify", "delete", "merge", "keep"]
- "experience": New/modified experience text (for add/modify/merge)
- "modified_from": Original experience ID (for modify)
- "delete_id": Experience ID to delete (for delete)
- "merged_from": List of experience IDs to merge (for merge)
Example:
operations = [
{"option": "add", "experience": "When solving..."},
{"option": "modify", "experience": "Updated...", "modified_from": "G17"},
{"option": "delete", "delete_id": "G5"},
{"option": "merge", "experience": "Merged...", "merged_from": ["G1", "G3"]}
]
"""
for op in operations:
option = op.get("option", "keep")
if option == "add":
self.add(op["experience"])
elif option == "delete":
self.delete(op["delete_id"])
elif option == "modify":
self.modify(op["modified_from"], op["experience"])
elif option == "merge":
self.merge(op["merged_from"], op["experience"])
# "keep" option does nothing
def format_for_prompt(self) -> str:
"""Format experiences for injection into prompts.
Returns:
formatted: Multi-line string with numbered experiences
Example output:
[G0]. When solving equations, verify solutions by substitution.
[G1]. For optimization, check boundary conditions first.
[G2]. In probability, use conditional probability when dependent.
"""
if not self.experiences:
return "None"
formatted = []
for exp_id, exp_text in self.experiences.items():
formatted.append(f"[{exp_id}]. {exp_text}")
return "\n".join(formatted)
def save(self, path: str) -> None:
"""Save experiences to JSON file.
Args:
path: Output file path
"""
Path(path).parent.mkdir(parents=True, exist_ok=True)
with open(path, 'w') as f:
json.dump({
"experiences": self.experiences,
"next_id": self._next_id
}, f, indent=2)
def load(self, path: str) -> None:
"""Load experiences from JSON file.
Args:
path: Input file path
"""
with open(path) as f:
data = json.load(f)
self.experiences = data["experiences"]
self._next_id = data["next_id"]
def __len__(self) -> int:
"""Return number of experiences in library."""
return len(self.experiences)
def __repr__(self) -> str:
"""Return string representation."""
return f"ExperienceManager({len(self)} experiences)"
+409
View File
@@ -0,0 +1,409 @@
# Copyright 2025 Zoo Labs Foundation Inc. and the Gym team.
#
# Semantic Extractor for Training-Free GRPO
# Based on Tencent youtu-agent: https://arxiv.org/abs/2510.08191v1
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import List, Dict, Optional, Any
from dataclasses import dataclass
import json
import re
@dataclass
class Trajectory:
"""Single rollout trajectory.
Attributes:
query: Input query/problem statement
output: Model-generated output/response
reward: Numerical reward score (e.g., 0 for wrong, 1 for correct)
groundtruth: Optional ground truth answer for supervised learning
summary: Optional LLM-generated step-by-step summary
"""
query: str
output: str
reward: float
groundtruth: Optional[str] = None
summary: Optional[str] = None
class SemanticExtractor:
"""Extracts semantic advantages from groups of trajectories.
Implements the 3-stage LLM process from Training-Free GRPO paper:
1. Trajectory Summarization (Figure 11): Analyze each rollout step-by-step
2. Group Advantage Extraction (Figure 12): Compare G trajectories to identify patterns
3. Batch Consolidation (Figure 13): Merge/modify/delete experiences across batch
This replaces numerical advantages with natural language experiences,
enabling learning without parameter updates.
"""
def __init__(self, llm_client: Any, max_operations: int = 3):
"""Initialize semantic extractor.
Args:
llm_client: LLM client with .chat() method (e.g., OpenAI, DeepSeek)
max_operations: Max operations per group critique (default: 3)
"""
self.llm = llm_client
self.max_operations = max_operations
# =========================================================================
# STAGE 1: Trajectory Summarization (Figure 11)
# =========================================================================
def summarize_trajectory(
self,
trajectory: Trajectory,
use_groundtruth: bool = True
) -> str:
"""Summarize a single trajectory step-by-step.
This stage analyzes what happened in each step of the trajectory,
identifies which experiences were used, and highlights any errors
or detours that occurred.
Args:
trajectory: Trajectory to summarize
use_groundtruth: Whether to include ground truth in prompt
Returns:
summary: Step-by-step analysis of the trajectory
"""
# Determine evaluation status
evaluation = (
"This trajectory delivers **correct** answer"
if trajectory.reward > 0
else "This trajectory delivers **wrong** answer"
)
# Include groundtruth if available and requested
groundtruth_section = (
f"\n<groundtruth>{trajectory.groundtruth}</groundtruth>"
if use_groundtruth and trajectory.groundtruth
else ""
)
prompt = f"""An agent system may be provided with some experiences, and then it produces the following trajectory to solve the given problem. Please summarize the trajectory step-by-step:
1. For each step, describe what action is being taken, and which experience has been used in this step.
2. Given the grading of this rollout and the correct answer, identify and explain any steps that represent detours, errors, or backtracking, highlighting why they might have occurred and what their impact was on the trajectory's progress.
3. Maintain all the core outcome of each step, even if it was part of a flawed process.
<trajectory>
{trajectory.output}
</trajectory>
<evaluation>
{evaluation}
</evaluation>{groundtruth_section}
Only return the trajectory summary of each step, e.g.,
1. what happened in the first step and the core outcomes
2. what happened in the second step and the core outcomes
3. ..."""
response = self.llm.chat(prompt)
return response
# =========================================================================
# STAGE 2: Group Advantage Extraction (Figure 12)
# =========================================================================
def extract_group_advantage(
self,
trajectories: List[Trajectory],
experiences: str,
use_groundtruth: bool = True
) -> List[Dict]:
"""Extract semantic advantage from a group of trajectories.
This stage compares multiple trajectories (both correct and incorrect)
to identify patterns, extract insights, and propose updates to the
experience library.
Args:
trajectories: List of G trajectories for the same query
experiences: Formatted experience library string
use_groundtruth: Whether to include ground truth in prompt
Returns:
operations: List of operations to apply to experience library
Example: [
{"option": "add", "experience": "When solving..."},
{"option": "modify", "experience": "...", "modified_from": "G17"}
]
"""
# Check if group has variation (std > 0)
rewards = [t.reward for t in trajectories]
if len(set(rewards)) <= 1:
return [] # Skip homogeneous groups
# Format trajectories with summaries
formatted_trajectories = []
for i, traj in enumerate(trajectories):
status = "correct" if traj.reward > 0 else "wrong"
content = traj.summary or traj.output
formatted_trajectories.append(
f"Attempt {i+1} (Answer {status}):\n{content}"
)
trajectories_text = "\n\n".join(formatted_trajectories)
# Include groundtruth if available
groundtruth_section = (
f"\n<groundtruth>{trajectories[0].groundtruth}</groundtruth>"
if use_groundtruth and trajectories[0].groundtruth
else ""
)
prompt = f"""An agent system is provided with a set of experiences and has tried to solve the problem multiple times with both successful and wrong solutions. Review these problem-solving attempt and extract generalizable experiences. Follow these steps:
1. Trajectory Analysis:
- For successful steps: Identify key correct decisions and insights
- For errors: Pinpoint where and why the reasoning went wrong
- Note any important patterns or strategies used/missed
- Review why some trajectories fail? Is there any existing experiences are missed, or experiences do not provide enough guidance?
2. Update Existing Experiences
- Some trajectories may be correct and others may be wrong, you should ensure there are experiences can help to run correctly
- You have three options: [modify, add, delete]
* modify: You can modify current experiences to make it helpful
* add: You can introduce new experiences to improve future performance
* delete: You can delete existing experiences
- You can update at most {self.max_operations} clear, generalizable lessons for this case
- Before updating each experience, you need to:
* Specify when it would be most relevant
* List key problem features that make this experience applicable
* Identify similar problem patterns where this advice applies
3. Requirements for each experience that is modified or added.
- Begin with general background with several words in the experience
- Focus on strategic thinking patterns, not specific calculations
- Emphasize decision points that could apply to similar problems
Please provide reasoning in details under the guidance of the above 3 steps. After the step-by-step reasoning, you will finish by returning in this JSON format as follows:
```json
[
{{
"option": "modify",
"experience": "the modified experience",
"modified_from": "G17"
}},
{{
"option": "add",
"experience": "the added experience"
}},
{{
"option": "delete",
"delete_id": "G5"
}}
]
```
Note that your updated experiences may not need to cover all the options.
<problem>
{trajectories[0].query}
</problem>
<trajectories>
{trajectories_text}
</trajectories>{groundtruth_section}
<experience>
{experiences}
</experience>"""
response = self.llm.chat(prompt)
# Parse JSON operations
return self._parse_json_operations(response, max_ops=self.max_operations)
# =========================================================================
# STAGE 3: Batch Consolidation (Figure 13)
# =========================================================================
def consolidate_batch(
self,
all_group_operations: List[List[Dict]],
experiences: str
) -> List[Dict]:
"""Consolidate all group advantages into final experience updates.
This stage merges operations from all groups in the batch, ensuring:
- Experiences are ≤32 words
- No redundancy between experiences
- Strategic focus (not specific calculations)
- Generalizable insights
Args:
all_group_operations: List of operations from each group
experiences: Current formatted experience library
Returns:
final_operations: Consolidated list of operations to apply
"""
# Flatten all operations
all_ops = []
for group_ops in all_group_operations:
all_ops.extend(group_ops)
if not all_ops:
return []
prompt = f"""An agent system is provided with a set of experiences and has tried to solve the problem multiple times. From the reflections, some suggestions on the existing experiences have been posed. Your task is to collect and think for the final experience revision plan. Each final experience must satisfy the following requirements:
1. It must be clear, generalizable lessons for this case, with no more than 32 words
2. Begin with general background with several words in the experience
3. Focus on strategic thinking patterns, not specific calculations
4. Emphasize decision points that could apply to similar problems
5. Avoid repeating saying similar experience in multiple different experiences
<experience>
{experiences}
</experience>
<suggested_updates>
{json.dumps(all_ops, indent=2)}
</suggested_updates>
Please provide reasoning in each of the suggestions, and think for how to update existing experiences. You have three update options: [modify, merge, delete]
- modify: You can modify current experiences to make it helpful
- merge: You can merge some similar experiences into a more general forms to reduce duplication
- delete: You can delete an experience
After generating the step-by-step reasoning, you need to give the final experience revision details by returning in this JSON format as follows:
```json
[
{{
"option": "modify",
"experience": "the modified experience",
"modified_from": "G17"
}},
{{
"option": "merge",
"experience": "the merged experience",
"merged_from": ["C1", "C3", "S4"]
}},
{{
"option": "delete",
"delete_id": "G5"
}}
]
```"""
response = self.llm.chat(prompt)
# Parse JSON operations
return self._parse_json_operations(response)
# =========================================================================
# Helper Methods
# =========================================================================
def _parse_json_operations(
self,
response: str,
max_ops: Optional[int] = None
) -> List[Dict]:
"""Parse JSON operations from LLM response.
Extracts JSON block from markdown code fence and validates format.
Args:
response: LLM response text
max_ops: Optional limit on number of operations
Returns:
operations: List of operation dictionaries
"""
# Extract JSON block from markdown code fence
json_match = re.search(r'```json\s*(.*?)\s*```', response, re.DOTALL)
if json_match:
try:
operations = json.loads(json_match.group(1))
if max_ops:
return operations[:max_ops]
return operations
except json.JSONDecodeError as e:
# Log error and return empty list
print(f"JSON parse error: {e}")
return []
# Also try to find plain JSON arrays (without code fence)
try:
# Look for array pattern
array_match = re.search(r'\[\s*\{.*?\}\s*\]', response, re.DOTALL)
if array_match:
operations = json.loads(array_match.group(0))
if max_ops:
return operations[:max_ops]
return operations
except json.JSONDecodeError:
pass
return []
class LLMClient:
"""Simple wrapper for LLM API clients.
Provides a unified .chat() interface for various LLM providers.
Supports OpenAI-compatible APIs (OpenAI, DeepSeek, etc.).
"""
def __init__(self, api_key: str, base_url: str = "https://api.deepseek.com/v1", model: str = "deepseek-chat"):
"""Initialize LLM client.
Args:
api_key: API key for the LLM service
base_url: Base URL for the API endpoint
model: Model name to use (default: deepseek-chat)
"""
try:
from openai import OpenAI
except ImportError:
raise ImportError(
"OpenAI package not found. Install with: pip install openai"
)
self.client = OpenAI(api_key=api_key, base_url=base_url)
self.model = model
def chat(self, prompt: str, temperature: float = 0.7, max_tokens: int = 4096) -> str:
"""Send chat request to LLM.
Args:
prompt: User prompt
temperature: Sampling temperature (default: 0.7)
max_tokens: Max tokens to generate (default: 4096)
Returns:
response: LLM response text
"""
response = self.client.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": prompt}],
temperature=temperature,
max_tokens=max_tokens
)
return response.choices[0].message.content