mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-23 20:23:00 +00:00
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:
Submodule
+1
Submodule pkg/hanzo-agent added at 9d2c669abc
@@ -1,283 +0,0 @@
|
||||
# Hanzo Agents
|
||||
|
||||
[](https://pypi.org/project/hanzo-agents/)
|
||||
[](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
|
||||
@@ -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).
|
||||
@@ -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
@@ -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",
|
||||
]
|
||||
@@ -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}")
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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"]
|
||||
@@ -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())
|
||||
@@ -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)
|
||||
@@ -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
@@ -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"):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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
|
||||
@@ -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)"
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user