nice-bill commited on
Commit
6954d6e
·
1 Parent(s): 6c402e0

Add API clients (MiniMax + Supabase)

Browse files
Files changed (3) hide show
  1. api/__init__.py +5 -0
  2. api/minimax_client.py +105 -0
  3. api/supabase_client.py +277 -0
api/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ """API clients for DeFi Agents simulation."""
2
+
3
+ from .minimax_client import MiniMaxClient
4
+
5
+ __all__ = ["MiniMaxClient"]
api/minimax_client.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MiniMax API client with reasoning extraction support - OpenAI compatible."""
2
+
3
+ import json
4
+ from typing import Dict, Tuple, Optional
5
+ from openai import OpenAI
6
+
7
+ from config import MINIMAX_API_KEY, MODEL_NAME, REASONING_SPLIT
8
+
9
+
10
+ class MiniMaxClient:
11
+ """Client for interacting with MiniMax API with reasoning transparency."""
12
+
13
+ def __init__(self, api_key: str = None, model: str = None, reasoning_split: bool = None):
14
+ self.api_key = api_key or MINIMAX_API_KEY
15
+ self.model = model or MODEL_NAME
16
+ self.reasoning_split = reasoning_split if reasoning_split is not None else REASONING_SPLIT
17
+ self.base_url = "https://api.minimax.io/v1"
18
+
19
+ if not self.api_key:
20
+ raise ValueError("MiniMax API key is required. Set MINIMAX_API_KEY in .env")
21
+
22
+ self.client = OpenAI(
23
+ base_url=self.base_url,
24
+ api_key=self.api_key
25
+ )
26
+
27
+ def call(self, prompt: str, system_prompt: str = None) -> Tuple[Dict, str]:
28
+ """
29
+ Call MiniMax API with reasoning extraction.
30
+
31
+ Returns:
32
+ Tuple of (decision_dict, thinking_text)
33
+ """
34
+ messages = []
35
+ if system_prompt:
36
+ messages.append({"role": "system", "content": system_prompt})
37
+ messages.append({"role": "user", "content": prompt})
38
+
39
+ response = self.client.chat.completions.create(
40
+ model=self.model,
41
+ messages=messages,
42
+ extra_body={"reasoning_split": self.reasoning_split}
43
+ )
44
+
45
+ # Extract thinking from reasoning_details
46
+ thinking_text = self._extract_thinking(response)
47
+
48
+ # Extract final answer
49
+ content = response.choices[0].message.content
50
+ decision = self._parse_content(content)
51
+
52
+ return decision, thinking_text
53
+
54
+ def _extract_thinking(self, response) -> str:
55
+ """Extract thinking text from reasoning_details field."""
56
+ try:
57
+ reasoning_details = response.choices[0].message.reasoning_details
58
+ if reasoning_details:
59
+ return reasoning_details[0].text
60
+ except AttributeError:
61
+ pass
62
+ return ""
63
+
64
+ def _parse_content(self, content: str) -> Dict:
65
+ """Parse the content into a dictionary."""
66
+ # Try to extract JSON from code block
67
+ if "```json" in content:
68
+ content = content.split("```json")[1].split("```")[0]
69
+ elif "```" in content:
70
+ content = content.split("```")[1].split("```")[0]
71
+
72
+ try:
73
+ return json.loads(content)
74
+ except json.JSONDecodeError:
75
+ return {"raw_content": content}
76
+
77
+
78
+ def test_client():
79
+ """Test the MiniMax client with a simple query."""
80
+ client = MiniMaxClient()
81
+
82
+ prompt = """
83
+ Solve this step by step:
84
+ I have 3 apples. I eat one. A magic bird gives me 2 more apples.
85
+ Then half of my total apples turn into gold.
86
+
87
+ How many edible apples do I have left?
88
+
89
+ Output JSON:
90
+ {
91
+ "reasoning": "step-by-step thinking",
92
+ "answer": "final answer"
93
+ }
94
+ """
95
+
96
+ decision, thinking = client.call(prompt)
97
+
98
+ print("=== DECISION ===")
99
+ print(json.dumps(decision, indent=2))
100
+ print("\n=== THINKING ===")
101
+ print(thinking[:500] + "..." if len(thinking) > 500 else thinking)
102
+
103
+
104
+ if __name__ == "__main__":
105
+ test_client()
api/supabase_client.py ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Supabase client for DeFi Agents simulation data persistence."""
2
+
3
+ from typing import Dict, List, Any, Optional
4
+ from supabase import create_client, Client
5
+ from dataclasses import dataclass
6
+
7
+ from config import SUPABASE_URL, SUPABASE_KEY
8
+
9
+
10
+ @dataclass
11
+ class RunData:
12
+ """Data class for run information."""
13
+ run_number: int
14
+ status: str = "running"
15
+ mechanics: List[str] = None
16
+ config: Dict = None
17
+
18
+ def __post_init__(self):
19
+ if self.mechanics is None:
20
+ self.mechanics = []
21
+ if self.config is None:
22
+ self.config = {}
23
+
24
+
25
+ @dataclass
26
+ class AgentStateData:
27
+ """Data class for agent state."""
28
+ run_id: int
29
+ turn: int
30
+ agent_name: str
31
+ token_a_balance: float = 0
32
+ token_b_balance: float = 0
33
+ profit: float = 0
34
+ strategy: str = "unknown"
35
+
36
+
37
+ @dataclass
38
+ class PoolStateData:
39
+ """Data class for pool state."""
40
+ run_id: int
41
+ turn: int
42
+ reserve_a: float = 0
43
+ reserve_b: float = 0
44
+ price_ab: float = 0
45
+ total_liquidity: float = 0
46
+
47
+
48
+ @dataclass
49
+ class ActionData:
50
+ """Data class for agent action."""
51
+ run_id: int
52
+ turn: int
53
+ agent_name: str
54
+ action_type: str
55
+ payload: Dict = None
56
+ reasoning_trace: str = ""
57
+ thinking_trace: str = ""
58
+
59
+ def __post_init__(self):
60
+ if self.payload is None:
61
+ self.payload = {}
62
+
63
+
64
+ @dataclass
65
+ class MetricsData:
66
+ """Data class for run metrics."""
67
+ run_id: int
68
+ gini_coefficient: float = 0
69
+ cooperation_rate: float = 0
70
+ betrayal_count: int = 0
71
+ avg_agent_profit: float = 0
72
+ pool_stability: float = 0
73
+
74
+
75
+ class SupabaseClient:
76
+ """Client for interacting with Supabase database."""
77
+
78
+ def __init__(self, url: str = None, key: str = None):
79
+ self.url = url or SUPABASE_URL
80
+ self.key = key or SUPABASE_KEY
81
+
82
+ if not self.url or not self.key:
83
+ raise ValueError(
84
+ "Supabase credentials required. Set SUPABASE_URL and SUPABASE_KEY in .env"
85
+ )
86
+
87
+ self.client: Client = create_client(self.url, self.key)
88
+
89
+ # ==================== RUNS ====================
90
+
91
+ def create_run(self, run_number: int, config: Dict = None) -> int:
92
+ """Create a new run and return its ID."""
93
+ response = self.client.table("runs").insert({
94
+ "run_number": run_number,
95
+ "status": "running",
96
+ "config": config or {}
97
+ }).execute()
98
+
99
+ return response.data[0]["id"]
100
+
101
+ def get_run_by_number(self, run_number: int) -> Optional[Dict]:
102
+ """Get run by run_number."""
103
+ response = self.client.table("runs").select("*").eq("run_number", run_number).execute()
104
+ return response.data[0] if response.data else None
105
+
106
+ def update_run_status(self, run_id: int, status: str, end_time: bool = False):
107
+ """Update run status."""
108
+ update_data = {"status": status}
109
+ if end_time:
110
+ from datetime import datetime
111
+ update_data["end_time"] = datetime.now().isoformat()
112
+
113
+ self.client.table("runs").update(update_data).eq("id", run_id).execute()
114
+
115
+ def complete_run(self, run_id: int):
116
+ """Mark a run as completed."""
117
+ self.update_run_status(run_id, "completed", end_time=True)
118
+
119
+ def get_all_runs(self) -> List[Dict]:
120
+ """Get all runs with their metrics."""
121
+ response = self.client.table("runs").select("*").order("run_number", desc=True).execute()
122
+ return response.data
123
+
124
+ def get_next_run_number(self) -> int:
125
+ """Get the next run number to use."""
126
+ response = self.client.rpc("get_next_run_number").execute()
127
+ return response.data if response.data else 1
128
+
129
+ # ==================== AGENT STATES ====================
130
+
131
+ def save_agent_state(self, data: AgentStateData):
132
+ """Save agent state to database."""
133
+ self.client.table("agent_states").insert({
134
+ "run_id": data.run_id,
135
+ "turn": data.turn,
136
+ "agent_name": data.agent_name,
137
+ "token_a_balance": data.token_a_balance,
138
+ "token_b_balance": data.token_b_balance,
139
+ "profit": data.profit,
140
+ "strategy": data.strategy
141
+ }).execute()
142
+
143
+ def get_agent_states(self, run_id: int, turn: int = None) -> List[Dict]:
144
+ """Get agent states for a run."""
145
+ query = self.client.table("agent_states").select("*").eq("run_id", run_id)
146
+ if turn is not None:
147
+ query = query.eq("turn", turn)
148
+ response = query.order("turn").order("agent_name").execute()
149
+ return response.data
150
+
151
+ def get_agent_states_by_name(self, run_id: int, agent_name: str) -> List[Dict]:
152
+ """Get all states for a specific agent in a run."""
153
+ response = self.client.table("agent_states").select("*").eq("run_id", run_id).eq("agent_name", agent_name).order("turn").execute()
154
+ return response.data
155
+
156
+ # ==================== POOL STATES ====================
157
+
158
+ def save_pool_state(self, data: PoolStateData):
159
+ """Save pool state to database."""
160
+ self.client.table("pool_states").insert({
161
+ "run_id": data.run_id,
162
+ "turn": data.turn,
163
+ "reserve_a": data.reserve_a,
164
+ "reserve_b": data.reserve_b,
165
+ "price_ab": data.price_ab,
166
+ "total_liquidity": data.total_liquidity
167
+ }).execute()
168
+
169
+ def get_pool_states(self, run_id: int, turn: int = None) -> List[Dict]:
170
+ """Get pool states for a run."""
171
+ query = self.client.table("pool_states").select("*").eq("run_id", run_id)
172
+ if turn is not None:
173
+ query = query.eq("turn", turn)
174
+ response = query.order("turn").execute()
175
+ return response.data
176
+
177
+ # ==================== ACTIONS ====================
178
+
179
+ def save_action(self, data: ActionData):
180
+ """Save agent action to database."""
181
+ self.client.table("actions").insert({
182
+ "run_id": data.run_id,
183
+ "turn": data.turn,
184
+ "agent_name": data.agent_name,
185
+ "action_type": data.action_type,
186
+ "payload": data.payload,
187
+ "reasoning_trace": data.reasoning_trace,
188
+ "thinking_trace": data.thinking_trace
189
+ }).execute()
190
+
191
+ def get_actions(self, run_id: int, turn: int = None) -> List[Dict]:
192
+ """Get all actions for a run."""
193
+ query = self.client.table("actions").select("*").eq("run_id", run_id)
194
+ if turn is not None:
195
+ query = query.eq("turn", turn)
196
+ response = query.order("turn").order("agent_name").execute()
197
+ return response.data
198
+
199
+ def get_action_by_id(self, action_id: int) -> Optional[Dict]:
200
+ """Get a specific action by ID."""
201
+ response = self.client.table("actions").select("*").eq("id", action_id).execute()
202
+ return response.data[0] if response.data else None
203
+
204
+ def get_thinking_trace(self, action_id: int) -> Optional[str]:
205
+ """Get the thinking trace for a specific action."""
206
+ action = self.get_action_by_id(action_id)
207
+ return action["thinking_trace"] if action else None
208
+
209
+ # ==================== METRICS ====================
210
+
211
+ def save_metrics(self, data: MetricsData):
212
+ """Save run metrics to database."""
213
+ self.client.table("run_metrics").insert({
214
+ "run_id": data.run_id,
215
+ "gini_coefficient": data.gini_coefficient,
216
+ "cooperation_rate": data.cooperation_rate,
217
+ "betrayal_count": data.betrayal_count,
218
+ "avg_agent_profit": data.avg_agent_profit,
219
+ "pool_stability": data.pool_stability
220
+ }).execute()
221
+
222
+ def get_metrics(self, run_id: int) -> Optional[Dict]:
223
+ """Get metrics for a specific run."""
224
+ response = self.client.table("run_metrics").select("*").eq("run_id", run_id).execute()
225
+ return response.data[0] if response.data else None
226
+
227
+ # ==================== RUN DETAILS ====================
228
+
229
+ def get_run_detail(self, run_id: int) -> Dict[str, List[Dict]]:
230
+ """Get complete details for a run (actions, agents, pool)."""
231
+ actions = self.get_actions(run_id)
232
+ agents = self.get_agent_states(run_id)
233
+ pool = self.get_pool_states(run_id)
234
+ metrics = self.get_metrics(run_id)
235
+
236
+ return {
237
+ "actions": actions,
238
+ "agent_states": agents,
239
+ "pool_states": pool,
240
+ "metrics": metrics
241
+ }
242
+
243
+ # ==================== UTILITY ====================
244
+
245
+ def health_check(self) -> bool:
246
+ """Check if Supabase connection is healthy."""
247
+ try:
248
+ response = self.client.table("runs").select("id").limit(1).execute()
249
+ return True
250
+ except Exception:
251
+ return False
252
+
253
+
254
+ def test_client():
255
+ """Test the Supabase client."""
256
+ try:
257
+ client = SupabaseClient()
258
+ print("Supabase client initialized successfully!")
259
+
260
+ # Health check
261
+ if client.health_check():
262
+ print("✓ Connection healthy")
263
+ else:
264
+ print("✗ Connection failed")
265
+
266
+ # Try to get runs
267
+ runs = client.get_all_runs()
268
+ print(f"✓ Retrieved {len(runs)} runs")
269
+
270
+ except ValueError as e:
271
+ print(f"Configuration error: {e}")
272
+ except Exception as e:
273
+ print(f"Error: {e}")
274
+
275
+
276
+ if __name__ == "__main__":
277
+ test_client()