nice-bill commited on
Commit
27baee9
·
1 Parent(s): 6669884

Add Analyzer (metrics) and FastAPI backend

Browse files
Files changed (3) hide show
  1. core/analyzer.py +219 -0
  2. web/__init__.py +4 -0
  3. web/app.py +206 -0
core/analyzer.py ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Analysis tools for DeFi agent simulation metrics."""
2
+
3
+ from typing import List, Dict, Optional
4
+ from collections import Counter
5
+ import statistics
6
+
7
+
8
+ class Analyzer:
9
+ """Calculate and analyze simulation metrics."""
10
+
11
+ @staticmethod
12
+ def calculate_run_metrics(agents: List, pool) -> Dict:
13
+ """Calculate all metrics for a completed run."""
14
+ profits = [a.calculate_profit() for a in agents]
15
+
16
+ return {
17
+ "gini_coefficient": Analyzer.gini_coefficient(profits),
18
+ "avg_agent_profit": statistics.mean(profits) if profits else 0,
19
+ "min_profit": min(profits) if profits else 0,
20
+ "max_profit": max(profits) if profits else 0,
21
+ "total_trades": Analyzer.count_trades(agents),
22
+ "cooperation_rate": Analyzer.cooperation_rate(agents),
23
+ "pool_stability": pool.reserve_a * pool.reserve_b,
24
+ "pool_price_change": Analyzer.price_change(pool)
25
+ }
26
+
27
+ @staticmethod
28
+ def gini_coefficient(values: List[float]) -> float:
29
+ """Calculate Gini coefficient (wealth inequality 0-1)."""
30
+ if not values or sum(values) == 0:
31
+ return 0
32
+
33
+ sorted_vals = sorted(values)
34
+ n = len(sorted_vals)
35
+ cumsum = sum((i + 1) * val for i, val in enumerate(sorted_vals))
36
+
37
+ gini = (2 * cumsum) / (n * sum(sorted_vals)) - (n + 1) / n
38
+ return max(0, min(1, gini)) # Clamp to 0-1
39
+
40
+ @staticmethod
41
+ def count_trades(agents: List) -> int:
42
+ """Count total trades across all agents."""
43
+ return sum(
44
+ len([h for h in a.trade_history if h.get("action") == "swap"])
45
+ for a in agents
46
+ )
47
+
48
+ @staticmethod
49
+ def cooperation_rate(agents: List) -> float:
50
+ """Calculate cooperation rate (alliances per agent)."""
51
+ total_alliances = sum(len(a.alliances) for a in agents)
52
+ return total_alliances / max(len(agents), 1)
53
+
54
+ @staticmethod
55
+ def price_change(pool) -> float:
56
+ """Calculate pool price change from initial (placeholder)."""
57
+ return 0 # Would need to track initial price
58
+
59
+ @staticmethod
60
+ def detect_arms_races(actions: List[Dict]) -> Dict:
61
+ """Detect strategic arms race patterns across agents."""
62
+ strategies = {}
63
+ for action in actions:
64
+ agent = action.get("agent_name", "unknown")
65
+ action_type = action.get("action_type", action.get("action", "unknown"))
66
+
67
+ if agent not in strategies:
68
+ strategies[agent] = []
69
+ strategies[agent].append(action_type)
70
+
71
+ analysis = {}
72
+ for agent, actions_list in strategies.items():
73
+ if not actions_list:
74
+ continue
75
+
76
+ counter = Counter(actions_list)
77
+ most_common = counter.most_common(1)[0]
78
+
79
+ analysis[agent] = {
80
+ "dominant_strategy": most_common[0],
81
+ "strategy_counts": dict(counter),
82
+ "strategy_diversity": len(set(actions_list)) / len(actions_list),
83
+ "aggressiveness": Analyzer._calculate_aggressiveness(actions_list)
84
+ }
85
+
86
+ return analysis
87
+
88
+ @staticmethod
89
+ def _calculate_aggressiveness(actions: List[str]) -> float:
90
+ """Calculate aggressiveness score (0-1)."""
91
+ aggressive_actions = {"swap", "provide_liquidity"}
92
+ passive_actions = {"do_nothing"}
93
+
94
+ aggressive_count = sum(1 for a in actions if a in aggressive_actions)
95
+ passive_count = sum(1 for a in actions if a in passive_actions)
96
+
97
+ total = len(actions)
98
+ if total == 0:
99
+ return 0.5
100
+
101
+ return aggressive_count / total
102
+
103
+ @staticmethod
104
+ def detect_trends(runs: List[Dict]) -> Dict:
105
+ """Detect trends across multiple runs."""
106
+ if not runs:
107
+ return {}
108
+
109
+ profits = [r.get("avg_agent_profit", 0) for r in runs]
110
+ gini = [r.get("gini_coefficient", 0) for r in runs]
111
+
112
+ return {
113
+ "profit_trend": Analyzer._trend_direction(profits),
114
+ "inequality_trend": Analyzer._trend_direction(gini),
115
+ "avg_profit": statistics.mean(profits) if profits else 0,
116
+ "avg_gini": statistics.mean(gini) if gini else 0,
117
+ "run_count": len(runs)
118
+ }
119
+
120
+ @staticmethod
121
+ def _trend_direction(values: List[float]) -> str:
122
+ """Get trend direction (up, down, stable)."""
123
+ if len(values) < 2:
124
+ return "stable"
125
+
126
+ first_half = statistics.mean(values[:len(values)//2])
127
+ second_half = statistics.mean(values[len(values)//2:])
128
+
129
+ diff = second_half - first_half
130
+ if diff > 0.1:
131
+ return "up"
132
+ elif diff < -0.1:
133
+ return "down"
134
+ return "stable"
135
+
136
+ @staticmethod
137
+ def format_report(metrics: Dict, agent_count: int = 5) -> str:
138
+ """Format metrics as a readable report."""
139
+ lines = [
140
+ "=" * 40,
141
+ "SIMULATION REPORT",
142
+ "=" * 40,
143
+ f"Agents: {agent_count}",
144
+ "-" * 40,
145
+ f"Gini Coefficient: {metrics.get('gini_coefficient', 0):.4f}",
146
+ f"Avg Profit: {metrics.get('avg_agent_profit', 0):.2f}",
147
+ f"Total Trades: {metrics.get('total_trades', 0)}",
148
+ f"Cooperation Rate: {metrics.get('cooperation_rate', 0):.2f}",
149
+ f"Pool Stability: {metrics.get('pool_stability', 0):.2f}",
150
+ "=" * 40,
151
+ ]
152
+
153
+ # Add inequality interpretation
154
+ gini = metrics.get('gini_coefficient', 0)
155
+ if gini < 0.2:
156
+ interpretation = "Low inequality"
157
+ elif gini < 0.4:
158
+ interpretation = "Moderate inequality"
159
+ else:
160
+ interpretation = "High inequality"
161
+ lines.append(f"Inequality: {interpretation}")
162
+
163
+ return "\n".join(lines)
164
+
165
+
166
+ def test_analyzer():
167
+ """Test the Analyzer class."""
168
+ print("Testing Analyzer class...")
169
+
170
+ # Create mock agents
171
+ class MockAgent:
172
+ def __init__(self, name, profit, alliances, trades):
173
+ self.name = name
174
+ self.profit = profit
175
+ self.alliances = alliances
176
+ self.trade_history = [{"action": t} for t in trades]
177
+
178
+ def calculate_profit(self):
179
+ return self.profit
180
+
181
+ agents = [
182
+ MockAgent("A", 50, {"B": "ally"}, ["swap", "do_nothing"]),
183
+ MockAgent("B", 30, {"A": "ally"}, ["swap"]),
184
+ MockAgent("C", -20, {}, ["do_nothing", "do_nothing"]),
185
+ MockAgent("D", 40, {}, ["provide_liquidity"]),
186
+ MockAgent("E", 0, {}, ["do_nothing"]),
187
+ ]
188
+
189
+ class MockPool:
190
+ reserve_a = 1100
191
+ reserve_b = 909
192
+ price_ab = 0.826
193
+
194
+ # Calculate metrics
195
+ metrics = Analyzer.calculate_run_metrics(agents, MockPool())
196
+
197
+ print("\nMetrics:")
198
+ for k, v in metrics.items():
199
+ print(f" {k}: {v}")
200
+
201
+ # Format report
202
+ print("\n" + Analyzer.format_report(metrics))
203
+
204
+ # Arms race detection
205
+ actions = [
206
+ {"agent_name": "A", "action_type": "swap"},
207
+ {"agent_name": "B", "action_type": "swap"},
208
+ {"agent_name": "A", "action_type": "provide_liquidity"},
209
+ ]
210
+ arms_race = Analyzer.detect_arms_races(actions)
211
+ print("\nArms Race Analysis:")
212
+ for agent, data in arms_race.items():
213
+ print(f" {agent}: {data}")
214
+
215
+ print("\nAnalyzer test complete!")
216
+
217
+
218
+ if __name__ == "__main__":
219
+ test_analyzer()
web/__init__.py CHANGED
@@ -1 +1,5 @@
1
  """Web API components for DeFi Agents simulation."""
 
 
 
 
 
1
  """Web API components for DeFi Agents simulation."""
2
+
3
+ from .app import app, run_server
4
+
5
+ __all__ = ["app", "run_server"]
web/app.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """FastAPI backend for DeFi Agents simulation dashboard."""
2
+
3
+ from fastapi import FastAPI, HTTPException
4
+ from fastapi.middleware.cors import CORSMiddleware
5
+ from pydantic import BaseModel
6
+ from typing import List, Optional, Dict, Any
7
+ import json
8
+
9
+ from api.supabase_client import SupabaseClient
10
+ from core.simulation import Simulation
11
+ from core.analyzer import Analyzer
12
+
13
+ app = FastAPI(
14
+ title="DeFi Agents API",
15
+ description="Multi-agent LLM simulation in DeFi markets",
16
+ version="0.1.0"
17
+ )
18
+
19
+ # CORS
20
+ app.add_middleware(
21
+ CORSMiddleware,
22
+ allow_origins=["*"],
23
+ allow_methods=["*"],
24
+ allow_headers=["*"],
25
+ )
26
+
27
+ # Initialize clients
28
+ supabase = None
29
+ try:
30
+ supabase = SupabaseClient()
31
+ except ValueError:
32
+ print("Warning: Supabase not configured")
33
+
34
+
35
+ # ==================== Pydantic Models ====================
36
+
37
+ class RunRequest(BaseModel):
38
+ num_agents: int = 5
39
+ turns_per_run: int = 10
40
+
41
+
42
+ class RunResponse(BaseModel):
43
+ run_number: int
44
+ metrics: Dict[str, Any]
45
+ agents: List[Dict[str, Any]]
46
+
47
+
48
+ class AgentActionRequest(BaseModel):
49
+ agent_name: str
50
+ action: str
51
+ payload: Dict = {}
52
+
53
+
54
+ # ==================== Health Endpoints ====================
55
+
56
+ @app.get("/health")
57
+ def health_check():
58
+ """Health check endpoint."""
59
+ return {
60
+ "status": "healthy",
61
+ "supabase": "connected" if supabase and supabase.health_check() else "disconnected"
62
+ }
63
+
64
+
65
+ # ==================== Run Endpoints ====================
66
+
67
+ @app.post("/api/runs")
68
+ def create_run(request: RunRequest):
69
+ """Start a new simulation run."""
70
+ try:
71
+ sim = Simulation(
72
+ num_agents=request.num_agents,
73
+ turns_per_run=request.turns_per_run,
74
+ supabase=supabase
75
+ )
76
+
77
+ metrics = sim.run()
78
+
79
+ # Get agent states
80
+ agent_data = []
81
+ for agent in sim.agents:
82
+ agent_data.append({
83
+ "name": agent.name,
84
+ "token_a": agent.token_a,
85
+ "token_b": agent.token_b,
86
+ "profit": agent.calculate_profit(),
87
+ "strategy": agent.infer_strategy()
88
+ })
89
+
90
+ return RunResponse(
91
+ run_number=sim.current_run_number - 1,
92
+ metrics=metrics,
93
+ agents=agent_data
94
+ )
95
+
96
+ except Exception as e:
97
+ raise HTTPException(status_code=500, detail=str(e))
98
+
99
+
100
+ @app.get("/api/runs")
101
+ def get_all_runs():
102
+ """Get all runs."""
103
+ if not supabase:
104
+ raise HTTPException(status_code=503, detail="Supabase not configured")
105
+
106
+ try:
107
+ runs = supabase.get_all_runs()
108
+ return {"runs": runs}
109
+ except Exception as e:
110
+ raise HTTPException(status_code=500, detail=str(e))
111
+
112
+
113
+ @app.get("/api/runs/{run_id}")
114
+ def get_run_detail(run_id: int):
115
+ """Get detailed run data."""
116
+ if not supabase:
117
+ raise HTTPException(status_code=503, detail="Supabase not configured")
118
+
119
+ try:
120
+ detail = supabase.get_run_detail(run_id)
121
+ return detail
122
+ except Exception as e:
123
+ raise HTTPException(status_code=500, detail=str(e))
124
+
125
+
126
+ # ==================== Metrics Endpoints ====================
127
+
128
+ @app.get("/api/metrics/{run_id}")
129
+ def get_run_metrics(run_id: int):
130
+ """Get metrics for a specific run."""
131
+ if not supabase:
132
+ raise HTTPException(status_code=503, detail="Supabase not configured")
133
+
134
+ try:
135
+ metrics = supabase.get_metrics(run_id)
136
+ if not metrics:
137
+ raise HTTPException(status_code=404, detail="Run not found")
138
+ return metrics
139
+ except Exception as e:
140
+ raise HTTPException(status_code=500, detail=str(e))
141
+
142
+
143
+ @app.get("/api/analysis/trends")
144
+ def get_trends():
145
+ """Get trend analysis across all runs."""
146
+ if not supabase:
147
+ raise HTTPException(status_code=503, detail="Supabase not configured")
148
+
149
+ try:
150
+ runs = supabase.get_all_runs()
151
+ run_data = [r for r in runs if r.get("status") == "completed"]
152
+
153
+ metrics = []
154
+ for r in run_data:
155
+ run_metrics = supabase.get_metrics(r["id"])
156
+ if run_metrics:
157
+ metrics.append(run_metrics)
158
+
159
+ trends = Analyzer.detect_trends(metrics)
160
+ return trends
161
+ except Exception as e:
162
+ raise HTTPException(status_code=500, detail=str(e))
163
+
164
+
165
+ # ==================== Thinking/Reasoning Endpoints ====================
166
+
167
+ @app.get("/api/thinking/{action_id}")
168
+ def get_thinking_trace(action_id: int):
169
+ """Get the thinking trace for a specific action."""
170
+ if not supabase:
171
+ raise HTTPException(status_code=503, detail="Supabase not configured")
172
+
173
+ try:
174
+ thinking = supabase.get_thinking_trace(action_id)
175
+ if thinking is None:
176
+ raise HTTPException(status_code=404, detail="Action not found")
177
+ return {"thinking": thinking}
178
+ except Exception as e:
179
+ raise HTTPException(status_code=500, detail=str(e))
180
+
181
+
182
+ # ==================== Analysis Endpoints ====================
183
+
184
+ @app.get("/api/analysis/arms-race/{run_id}")
185
+ def get_arms_race_analysis(run_id: int):
186
+ """Detect arms race patterns in a run."""
187
+ if not supabase:
188
+ raise HTTPException(status_code=503, detail="Supabase not configured")
189
+
190
+ try:
191
+ actions = supabase.get_actions(run_id)
192
+ analysis = Analyzer.detect_arms_races(actions)
193
+ return analysis
194
+ except Exception as e:
195
+ raise HTTPException(status_code=500, detail=str(e))
196
+
197
+
198
+ def run_server():
199
+ """Run the FastAPI server."""
200
+ import uvicorn
201
+ uvicorn.run(app, host="0.0.0.0", port=8000)
202
+
203
+
204
+ if __name__ == "__main__":
205
+ print("Starting DeFi Agents API server on http://0.0.0.0:8000")
206
+ run_server()