Spaces:
Sleeping
Sleeping
Add API clients (MiniMax + Supabase)
Browse files- api/__init__.py +5 -0
- api/minimax_client.py +105 -0
- 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()
|