Spaces:
Sleeping
Sleeping
Add Analyzer (metrics) and FastAPI backend
Browse files- core/analyzer.py +219 -0
- web/__init__.py +4 -0
- 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()
|