aragora-client 2.1.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aragora_client/__init__.py +75 -0
- aragora_client/client.py +544 -0
- aragora_client/exceptions.py +67 -0
- aragora_client/py.typed +0 -0
- aragora_client/types.py +323 -0
- aragora_client/websocket.py +262 -0
- aragora_client-2.1.10.dist-info/METADATA +425 -0
- aragora_client-2.1.10.dist-info/RECORD +9 -0
- aragora_client-2.1.10.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Aragora Python SDK.
|
|
3
|
+
|
|
4
|
+
A Python client for the Aragora multi-agent debate framework.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from aragora_client import AragoraClient
|
|
8
|
+
>>> client = AragoraClient("http://localhost:8080")
|
|
9
|
+
>>> debate = await client.debates.run(task="Should we use microservices?")
|
|
10
|
+
>>> print(debate.consensus.conclusion)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from aragora_client.client import AragoraClient
|
|
14
|
+
from aragora_client.exceptions import (
|
|
15
|
+
AragoraAuthenticationError,
|
|
16
|
+
AragoraConnectionError,
|
|
17
|
+
AragoraError,
|
|
18
|
+
AragoraNotFoundError,
|
|
19
|
+
AragoraTimeoutError,
|
|
20
|
+
AragoraValidationError,
|
|
21
|
+
)
|
|
22
|
+
from aragora_client.types import (
|
|
23
|
+
AgentProfile,
|
|
24
|
+
AgentScore,
|
|
25
|
+
ConsensusResult,
|
|
26
|
+
Debate,
|
|
27
|
+
DebateEvent,
|
|
28
|
+
DebateStatus,
|
|
29
|
+
GauntletReceipt,
|
|
30
|
+
GraphBranch,
|
|
31
|
+
GraphDebate,
|
|
32
|
+
HealthStatus,
|
|
33
|
+
MatrixConclusion,
|
|
34
|
+
MatrixDebate,
|
|
35
|
+
MemoryAnalytics,
|
|
36
|
+
SelectionPlugins,
|
|
37
|
+
TeamSelection,
|
|
38
|
+
VerificationResult,
|
|
39
|
+
VerificationStatus,
|
|
40
|
+
)
|
|
41
|
+
from aragora_client.websocket import DebateStream, stream_debate
|
|
42
|
+
|
|
43
|
+
__version__ = "2.0.0"
|
|
44
|
+
__all__ = [
|
|
45
|
+
# Client
|
|
46
|
+
"AragoraClient",
|
|
47
|
+
# Exceptions
|
|
48
|
+
"AragoraError",
|
|
49
|
+
"AragoraConnectionError",
|
|
50
|
+
"AragoraAuthenticationError",
|
|
51
|
+
"AragoraNotFoundError",
|
|
52
|
+
"AragoraValidationError",
|
|
53
|
+
"AragoraTimeoutError",
|
|
54
|
+
# Types
|
|
55
|
+
"Debate",
|
|
56
|
+
"DebateStatus",
|
|
57
|
+
"ConsensusResult",
|
|
58
|
+
"AgentProfile",
|
|
59
|
+
"GraphDebate",
|
|
60
|
+
"GraphBranch",
|
|
61
|
+
"MatrixDebate",
|
|
62
|
+
"MatrixConclusion",
|
|
63
|
+
"VerificationResult",
|
|
64
|
+
"VerificationStatus",
|
|
65
|
+
"GauntletReceipt",
|
|
66
|
+
"MemoryAnalytics",
|
|
67
|
+
"HealthStatus",
|
|
68
|
+
"DebateEvent",
|
|
69
|
+
"SelectionPlugins",
|
|
70
|
+
"TeamSelection",
|
|
71
|
+
"AgentScore",
|
|
72
|
+
# WebSocket
|
|
73
|
+
"DebateStream",
|
|
74
|
+
"stream_debate",
|
|
75
|
+
]
|
aragora_client/client.py
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
"""Main client for the Aragora SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from aragora_client.exceptions import (
|
|
11
|
+
AragoraAuthenticationError,
|
|
12
|
+
AragoraConnectionError,
|
|
13
|
+
AragoraError,
|
|
14
|
+
AragoraNotFoundError,
|
|
15
|
+
AragoraTimeoutError,
|
|
16
|
+
AragoraValidationError,
|
|
17
|
+
)
|
|
18
|
+
from aragora_client.types import (
|
|
19
|
+
AgentProfile,
|
|
20
|
+
AgentScore,
|
|
21
|
+
CreateDebateRequest,
|
|
22
|
+
CreateGraphDebateRequest,
|
|
23
|
+
CreateMatrixDebateRequest,
|
|
24
|
+
Debate,
|
|
25
|
+
GauntletReceipt,
|
|
26
|
+
GraphBranch,
|
|
27
|
+
GraphDebate,
|
|
28
|
+
HealthStatus,
|
|
29
|
+
MatrixConclusion,
|
|
30
|
+
MatrixDebate,
|
|
31
|
+
MemoryAnalytics,
|
|
32
|
+
MemoryTierStats,
|
|
33
|
+
RunGauntletRequest,
|
|
34
|
+
ScoreAgentsRequest,
|
|
35
|
+
SelectionPlugins,
|
|
36
|
+
SelectTeamRequest,
|
|
37
|
+
TeamSelection,
|
|
38
|
+
VerificationResult,
|
|
39
|
+
VerifyClaimRequest,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class DebatesAPI:
|
|
44
|
+
"""API for debate operations."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, client: AragoraClient) -> None:
|
|
47
|
+
self._client = client
|
|
48
|
+
|
|
49
|
+
async def create(
|
|
50
|
+
self,
|
|
51
|
+
task: str,
|
|
52
|
+
*,
|
|
53
|
+
agents: list[str] | None = None,
|
|
54
|
+
max_rounds: int = 5,
|
|
55
|
+
consensus_threshold: float = 0.8,
|
|
56
|
+
**kwargs: Any,
|
|
57
|
+
) -> dict[str, Any]:
|
|
58
|
+
"""Create a new debate."""
|
|
59
|
+
request = CreateDebateRequest(
|
|
60
|
+
task=task,
|
|
61
|
+
agents=agents,
|
|
62
|
+
max_rounds=max_rounds,
|
|
63
|
+
consensus_threshold=consensus_threshold,
|
|
64
|
+
metadata=kwargs.get("metadata", {}),
|
|
65
|
+
)
|
|
66
|
+
return await self._client._post("/api/v1/debates", request.model_dump())
|
|
67
|
+
|
|
68
|
+
async def get(self, debate_id: str) -> Debate:
|
|
69
|
+
"""Get a debate by ID."""
|
|
70
|
+
data = await self._client._get(f"/api/v1/debates/{debate_id}")
|
|
71
|
+
return Debate.model_validate(data)
|
|
72
|
+
|
|
73
|
+
async def list(
|
|
74
|
+
self,
|
|
75
|
+
*,
|
|
76
|
+
limit: int = 50,
|
|
77
|
+
offset: int = 0,
|
|
78
|
+
status: str | None = None,
|
|
79
|
+
) -> list[Debate]:
|
|
80
|
+
"""List debates."""
|
|
81
|
+
params: dict[str, Any] = {"limit": limit, "offset": offset}
|
|
82
|
+
if status:
|
|
83
|
+
params["status"] = status
|
|
84
|
+
data = await self._client._get("/api/v1/debates", params=params)
|
|
85
|
+
return [Debate.model_validate(d) for d in data.get("debates", [])]
|
|
86
|
+
|
|
87
|
+
async def run(
|
|
88
|
+
self,
|
|
89
|
+
task: str,
|
|
90
|
+
*,
|
|
91
|
+
agents: list[str] | None = None,
|
|
92
|
+
max_rounds: int = 5,
|
|
93
|
+
consensus_threshold: float = 0.8,
|
|
94
|
+
poll_interval: float = 1.0,
|
|
95
|
+
timeout: float = 300.0,
|
|
96
|
+
**kwargs: Any,
|
|
97
|
+
) -> Debate:
|
|
98
|
+
"""Run a debate and wait for completion."""
|
|
99
|
+
response = await self.create(
|
|
100
|
+
task,
|
|
101
|
+
agents=agents,
|
|
102
|
+
max_rounds=max_rounds,
|
|
103
|
+
consensus_threshold=consensus_threshold,
|
|
104
|
+
**kwargs,
|
|
105
|
+
)
|
|
106
|
+
debate_id = response["id"]
|
|
107
|
+
|
|
108
|
+
elapsed = 0.0
|
|
109
|
+
while elapsed < timeout:
|
|
110
|
+
debate = await self.get(debate_id)
|
|
111
|
+
if debate.status.value in ("completed", "failed", "cancelled"):
|
|
112
|
+
return debate
|
|
113
|
+
await asyncio.sleep(poll_interval)
|
|
114
|
+
elapsed += poll_interval
|
|
115
|
+
|
|
116
|
+
raise AragoraTimeoutError(
|
|
117
|
+
f"Debate {debate_id} did not complete within {timeout}s"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class GraphDebatesAPI:
|
|
122
|
+
"""API for graph debate operations."""
|
|
123
|
+
|
|
124
|
+
def __init__(self, client: AragoraClient) -> None:
|
|
125
|
+
self._client = client
|
|
126
|
+
|
|
127
|
+
async def create(
|
|
128
|
+
self,
|
|
129
|
+
task: str,
|
|
130
|
+
*,
|
|
131
|
+
agents: list[str] | None = None,
|
|
132
|
+
max_rounds: int = 5,
|
|
133
|
+
branch_threshold: float = 0.5,
|
|
134
|
+
max_branches: int = 10,
|
|
135
|
+
) -> dict[str, Any]:
|
|
136
|
+
"""Create a new graph debate."""
|
|
137
|
+
request = CreateGraphDebateRequest(
|
|
138
|
+
task=task,
|
|
139
|
+
agents=agents,
|
|
140
|
+
max_rounds=max_rounds,
|
|
141
|
+
branch_threshold=branch_threshold,
|
|
142
|
+
max_branches=max_branches,
|
|
143
|
+
)
|
|
144
|
+
return await self._client._post("/api/v1/graph-debates", request.model_dump())
|
|
145
|
+
|
|
146
|
+
async def get(self, debate_id: str) -> GraphDebate:
|
|
147
|
+
"""Get a graph debate by ID."""
|
|
148
|
+
data = await self._client._get(f"/api/v1/graph-debates/{debate_id}")
|
|
149
|
+
return GraphDebate.model_validate(data)
|
|
150
|
+
|
|
151
|
+
async def get_branches(self, debate_id: str) -> list[GraphBranch]:
|
|
152
|
+
"""Get branches for a graph debate."""
|
|
153
|
+
data = await self._client._get(f"/api/v1/graph-debates/{debate_id}/branches")
|
|
154
|
+
return [GraphBranch.model_validate(b) for b in data.get("branches", [])]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class MatrixDebatesAPI:
|
|
158
|
+
"""API for matrix debate operations."""
|
|
159
|
+
|
|
160
|
+
def __init__(self, client: AragoraClient) -> None:
|
|
161
|
+
self._client = client
|
|
162
|
+
|
|
163
|
+
async def create(
|
|
164
|
+
self,
|
|
165
|
+
task: str,
|
|
166
|
+
scenarios: list[dict[str, Any]],
|
|
167
|
+
*,
|
|
168
|
+
agents: list[str] | None = None,
|
|
169
|
+
max_rounds: int = 3,
|
|
170
|
+
) -> dict[str, Any]:
|
|
171
|
+
"""Create a new matrix debate."""
|
|
172
|
+
request = CreateMatrixDebateRequest(
|
|
173
|
+
task=task,
|
|
174
|
+
scenarios=scenarios,
|
|
175
|
+
agents=agents,
|
|
176
|
+
max_rounds=max_rounds,
|
|
177
|
+
)
|
|
178
|
+
return await self._client._post("/api/v1/matrix-debates", request.model_dump())
|
|
179
|
+
|
|
180
|
+
async def get(self, debate_id: str) -> MatrixDebate:
|
|
181
|
+
"""Get a matrix debate by ID."""
|
|
182
|
+
data = await self._client._get(f"/api/v1/matrix-debates/{debate_id}")
|
|
183
|
+
return MatrixDebate.model_validate(data)
|
|
184
|
+
|
|
185
|
+
async def get_conclusions(self, debate_id: str) -> MatrixConclusion:
|
|
186
|
+
"""Get conclusions for a matrix debate."""
|
|
187
|
+
data = await self._client._get(
|
|
188
|
+
f"/api/v1/matrix-debates/{debate_id}/conclusions"
|
|
189
|
+
)
|
|
190
|
+
return MatrixConclusion.model_validate(data)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class AgentsAPI:
|
|
194
|
+
"""API for agent operations."""
|
|
195
|
+
|
|
196
|
+
def __init__(self, client: AragoraClient) -> None:
|
|
197
|
+
self._client = client
|
|
198
|
+
|
|
199
|
+
async def list(self) -> list[AgentProfile]:
|
|
200
|
+
"""List all available agents."""
|
|
201
|
+
data = await self._client._get("/api/v1/agents")
|
|
202
|
+
return [AgentProfile.model_validate(a) for a in data.get("agents", [])]
|
|
203
|
+
|
|
204
|
+
async def get(self, agent_id: str) -> AgentProfile:
|
|
205
|
+
"""Get an agent profile."""
|
|
206
|
+
data = await self._client._get(f"/api/v1/agents/{agent_id}")
|
|
207
|
+
return AgentProfile.model_validate(data)
|
|
208
|
+
|
|
209
|
+
async def history(self, agent_id: str, limit: int = 20) -> list[dict[str, Any]]:
|
|
210
|
+
"""Get match history for an agent."""
|
|
211
|
+
data = await self._client._get(
|
|
212
|
+
f"/api/v1/agents/{agent_id}/history", params={"limit": limit}
|
|
213
|
+
)
|
|
214
|
+
return data.get("matches", [])
|
|
215
|
+
|
|
216
|
+
async def rivals(self, agent_id: str) -> list[dict[str, Any]]:
|
|
217
|
+
"""Get rivals for an agent."""
|
|
218
|
+
data = await self._client._get(f"/api/v1/agents/{agent_id}/rivals")
|
|
219
|
+
return data.get("rivals", [])
|
|
220
|
+
|
|
221
|
+
async def allies(self, agent_id: str) -> list[dict[str, Any]]:
|
|
222
|
+
"""Get allies for an agent."""
|
|
223
|
+
data = await self._client._get(f"/api/v1/agents/{agent_id}/allies")
|
|
224
|
+
return data.get("allies", [])
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class VerificationAPI:
|
|
228
|
+
"""API for formal verification."""
|
|
229
|
+
|
|
230
|
+
def __init__(self, client: AragoraClient) -> None:
|
|
231
|
+
self._client = client
|
|
232
|
+
|
|
233
|
+
async def verify(
|
|
234
|
+
self,
|
|
235
|
+
claim: str,
|
|
236
|
+
*,
|
|
237
|
+
backend: str = "z3",
|
|
238
|
+
timeout: int = 30,
|
|
239
|
+
) -> VerificationResult:
|
|
240
|
+
"""Verify a claim using formal methods."""
|
|
241
|
+
request = VerifyClaimRequest(claim=claim, backend=backend, timeout=timeout)
|
|
242
|
+
data = await self._client._post(
|
|
243
|
+
"/api/v1/verification/verify", request.model_dump()
|
|
244
|
+
)
|
|
245
|
+
return VerificationResult.model_validate(data)
|
|
246
|
+
|
|
247
|
+
async def status(self) -> dict[str, Any]:
|
|
248
|
+
"""Get verification backend status."""
|
|
249
|
+
return await self._client._get("/api/v1/verification/status")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class GauntletAPI:
|
|
253
|
+
"""API for gauntlet validation."""
|
|
254
|
+
|
|
255
|
+
def __init__(self, client: AragoraClient) -> None:
|
|
256
|
+
self._client = client
|
|
257
|
+
|
|
258
|
+
async def run(
|
|
259
|
+
self,
|
|
260
|
+
input_content: str,
|
|
261
|
+
*,
|
|
262
|
+
input_type: str = "spec",
|
|
263
|
+
persona: str = "security",
|
|
264
|
+
) -> dict[str, Any]:
|
|
265
|
+
"""Run gauntlet validation."""
|
|
266
|
+
request = RunGauntletRequest(
|
|
267
|
+
input_content=input_content,
|
|
268
|
+
input_type=input_type,
|
|
269
|
+
persona=persona,
|
|
270
|
+
)
|
|
271
|
+
return await self._client._post("/api/v1/gauntlet/run", request.model_dump())
|
|
272
|
+
|
|
273
|
+
async def get_receipt(self, gauntlet_id: str) -> GauntletReceipt:
|
|
274
|
+
"""Get a gauntlet receipt."""
|
|
275
|
+
data = await self._client._get(f"/api/v1/gauntlet/{gauntlet_id}/receipt")
|
|
276
|
+
return GauntletReceipt.model_validate(data)
|
|
277
|
+
|
|
278
|
+
async def run_and_wait(
|
|
279
|
+
self,
|
|
280
|
+
input_content: str,
|
|
281
|
+
*,
|
|
282
|
+
input_type: str = "spec",
|
|
283
|
+
persona: str = "security",
|
|
284
|
+
poll_interval: float = 1.0,
|
|
285
|
+
timeout: float = 120.0,
|
|
286
|
+
) -> GauntletReceipt:
|
|
287
|
+
"""Run gauntlet and wait for completion."""
|
|
288
|
+
response = await self.run(input_content, input_type=input_type, persona=persona)
|
|
289
|
+
gauntlet_id = response["gauntlet_id"]
|
|
290
|
+
|
|
291
|
+
elapsed = 0.0
|
|
292
|
+
while elapsed < timeout:
|
|
293
|
+
try:
|
|
294
|
+
return await self.get_receipt(gauntlet_id)
|
|
295
|
+
except AragoraNotFoundError:
|
|
296
|
+
await asyncio.sleep(poll_interval)
|
|
297
|
+
elapsed += poll_interval
|
|
298
|
+
|
|
299
|
+
raise AragoraTimeoutError(
|
|
300
|
+
f"Gauntlet {gauntlet_id} did not complete within {timeout}s"
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class MemoryAPI:
|
|
305
|
+
"""API for memory system."""
|
|
306
|
+
|
|
307
|
+
def __init__(self, client: AragoraClient) -> None:
|
|
308
|
+
self._client = client
|
|
309
|
+
|
|
310
|
+
async def analytics(self, days: int = 30) -> MemoryAnalytics:
|
|
311
|
+
"""Get memory analytics."""
|
|
312
|
+
data = await self._client._get(
|
|
313
|
+
"/api/v1/memory/analytics", params={"days": days}
|
|
314
|
+
)
|
|
315
|
+
return MemoryAnalytics.model_validate(data)
|
|
316
|
+
|
|
317
|
+
async def tier_stats(self, tier: str) -> MemoryTierStats:
|
|
318
|
+
"""Get stats for a specific memory tier."""
|
|
319
|
+
data = await self._client._get(f"/api/v1/memory/tiers/{tier}")
|
|
320
|
+
return MemoryTierStats.model_validate(data)
|
|
321
|
+
|
|
322
|
+
async def snapshot(self) -> dict[str, Any]:
|
|
323
|
+
"""Take a manual memory snapshot."""
|
|
324
|
+
return await self._client._post("/api/v1/memory/snapshot", {})
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class SelectionAPI:
|
|
328
|
+
"""API for agent selection plugins."""
|
|
329
|
+
|
|
330
|
+
def __init__(self, client: AragoraClient) -> None:
|
|
331
|
+
self._client = client
|
|
332
|
+
|
|
333
|
+
async def list_plugins(self) -> SelectionPlugins:
|
|
334
|
+
"""List available selection plugins."""
|
|
335
|
+
data = await self._client._get("/api/v1/selection/plugins")
|
|
336
|
+
return SelectionPlugins.model_validate(data)
|
|
337
|
+
|
|
338
|
+
async def get_defaults(self) -> dict[str, str]:
|
|
339
|
+
"""Get default plugin configuration."""
|
|
340
|
+
return await self._client._get("/api/v1/selection/defaults")
|
|
341
|
+
|
|
342
|
+
async def score_agents(
|
|
343
|
+
self,
|
|
344
|
+
task_description: str,
|
|
345
|
+
*,
|
|
346
|
+
primary_domain: str | None = None,
|
|
347
|
+
scorer: str | None = None,
|
|
348
|
+
) -> list[AgentScore]:
|
|
349
|
+
"""Score agents for a task."""
|
|
350
|
+
request = ScoreAgentsRequest(
|
|
351
|
+
task_description=task_description,
|
|
352
|
+
primary_domain=primary_domain,
|
|
353
|
+
scorer=scorer,
|
|
354
|
+
)
|
|
355
|
+
data = await self._client._post("/api/v1/selection/score", request.model_dump())
|
|
356
|
+
return [AgentScore.model_validate(a) for a in data.get("agents", [])]
|
|
357
|
+
|
|
358
|
+
async def select_team(
|
|
359
|
+
self,
|
|
360
|
+
task_description: str,
|
|
361
|
+
*,
|
|
362
|
+
min_agents: int = 2,
|
|
363
|
+
max_agents: int = 5,
|
|
364
|
+
diversity_preference: float = 0.5,
|
|
365
|
+
quality_priority: float = 0.5,
|
|
366
|
+
scorer: str | None = None,
|
|
367
|
+
team_selector: str | None = None,
|
|
368
|
+
role_assigner: str | None = None,
|
|
369
|
+
) -> TeamSelection:
|
|
370
|
+
"""Select an optimal team for a task."""
|
|
371
|
+
request = SelectTeamRequest(
|
|
372
|
+
task_description=task_description,
|
|
373
|
+
min_agents=min_agents,
|
|
374
|
+
max_agents=max_agents,
|
|
375
|
+
diversity_preference=diversity_preference,
|
|
376
|
+
quality_priority=quality_priority,
|
|
377
|
+
scorer=scorer,
|
|
378
|
+
team_selector=team_selector,
|
|
379
|
+
role_assigner=role_assigner,
|
|
380
|
+
)
|
|
381
|
+
data = await self._client._post("/api/v1/selection/team", request.model_dump())
|
|
382
|
+
return TeamSelection.model_validate(data)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
class ReplaysAPI:
|
|
386
|
+
"""API for replay management."""
|
|
387
|
+
|
|
388
|
+
def __init__(self, client: AragoraClient) -> None:
|
|
389
|
+
self._client = client
|
|
390
|
+
|
|
391
|
+
async def list(self, *, limit: int = 50, offset: int = 0) -> list[dict[str, Any]]:
|
|
392
|
+
"""List replays."""
|
|
393
|
+
data = await self._client._get(
|
|
394
|
+
"/api/v1/replays", params={"limit": limit, "offset": offset}
|
|
395
|
+
)
|
|
396
|
+
return data.get("replays", [])
|
|
397
|
+
|
|
398
|
+
async def get(self, replay_id: str) -> dict[str, Any]:
|
|
399
|
+
"""Get a replay by ID."""
|
|
400
|
+
return await self._client._get(f"/api/v1/replays/{replay_id}")
|
|
401
|
+
|
|
402
|
+
async def export(self, replay_id: str, format: str = "json") -> bytes:
|
|
403
|
+
"""Export a replay."""
|
|
404
|
+
return await self._client._get_raw(
|
|
405
|
+
f"/api/v1/replays/{replay_id}/export", params={"format": format}
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
async def delete(self, replay_id: str) -> None:
|
|
409
|
+
"""Delete a replay."""
|
|
410
|
+
await self._client._delete(f"/api/v1/replays/{replay_id}")
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
class AragoraClient:
|
|
414
|
+
"""
|
|
415
|
+
Client for the Aragora API.
|
|
416
|
+
|
|
417
|
+
Example:
|
|
418
|
+
>>> client = AragoraClient("http://localhost:8080")
|
|
419
|
+
>>> debate = await client.debates.run(task="Should we use microservices?")
|
|
420
|
+
>>> print(debate.consensus.conclusion)
|
|
421
|
+
"""
|
|
422
|
+
|
|
423
|
+
def __init__(
|
|
424
|
+
self,
|
|
425
|
+
base_url: str = "http://localhost:8080",
|
|
426
|
+
*,
|
|
427
|
+
api_key: str | None = None,
|
|
428
|
+
timeout: float = 30.0,
|
|
429
|
+
headers: dict[str, str] | None = None,
|
|
430
|
+
) -> None:
|
|
431
|
+
"""
|
|
432
|
+
Initialize the Aragora client.
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
base_url: Base URL of the Aragora server.
|
|
436
|
+
api_key: Optional API key for authentication.
|
|
437
|
+
timeout: Request timeout in seconds.
|
|
438
|
+
headers: Optional additional headers.
|
|
439
|
+
"""
|
|
440
|
+
self.base_url = base_url.rstrip("/")
|
|
441
|
+
self._api_key = api_key
|
|
442
|
+
self._timeout = timeout
|
|
443
|
+
|
|
444
|
+
default_headers = {"User-Agent": "aragora-client-python/2.0.0"}
|
|
445
|
+
if api_key:
|
|
446
|
+
default_headers["Authorization"] = f"Bearer {api_key}"
|
|
447
|
+
if headers:
|
|
448
|
+
default_headers.update(headers)
|
|
449
|
+
|
|
450
|
+
self._client = httpx.AsyncClient(
|
|
451
|
+
base_url=self.base_url,
|
|
452
|
+
headers=default_headers,
|
|
453
|
+
timeout=timeout,
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
# Initialize API namespaces
|
|
457
|
+
self.debates = DebatesAPI(self)
|
|
458
|
+
self.graph_debates = GraphDebatesAPI(self)
|
|
459
|
+
self.matrix_debates = MatrixDebatesAPI(self)
|
|
460
|
+
self.agents = AgentsAPI(self)
|
|
461
|
+
self.verification = VerificationAPI(self)
|
|
462
|
+
self.gauntlet = GauntletAPI(self)
|
|
463
|
+
self.memory = MemoryAPI(self)
|
|
464
|
+
self.selection = SelectionAPI(self)
|
|
465
|
+
self.replays = ReplaysAPI(self)
|
|
466
|
+
|
|
467
|
+
async def __aenter__(self) -> AragoraClient:
|
|
468
|
+
"""Enter async context."""
|
|
469
|
+
return self
|
|
470
|
+
|
|
471
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
472
|
+
"""Exit async context."""
|
|
473
|
+
await self.close()
|
|
474
|
+
|
|
475
|
+
async def close(self) -> None:
|
|
476
|
+
"""Close the client."""
|
|
477
|
+
await self._client.aclose()
|
|
478
|
+
|
|
479
|
+
async def health(self) -> HealthStatus:
|
|
480
|
+
"""Get server health status."""
|
|
481
|
+
data = await self._get("/api/v1/health")
|
|
482
|
+
return HealthStatus.model_validate(data)
|
|
483
|
+
|
|
484
|
+
async def _request(
|
|
485
|
+
self,
|
|
486
|
+
method: str,
|
|
487
|
+
path: str,
|
|
488
|
+
*,
|
|
489
|
+
params: dict[str, Any] | None = None,
|
|
490
|
+
json: dict[str, Any] | None = None,
|
|
491
|
+
) -> httpx.Response:
|
|
492
|
+
"""Make an HTTP request."""
|
|
493
|
+
try:
|
|
494
|
+
response = await self._client.request(
|
|
495
|
+
method, path, params=params, json=json
|
|
496
|
+
)
|
|
497
|
+
except httpx.ConnectError as e:
|
|
498
|
+
raise AragoraConnectionError(str(e)) from e
|
|
499
|
+
except httpx.TimeoutException as e:
|
|
500
|
+
raise AragoraTimeoutError(str(e)) from e
|
|
501
|
+
|
|
502
|
+
if response.status_code == 401:
|
|
503
|
+
raise AragoraAuthenticationError()
|
|
504
|
+
if response.status_code == 404:
|
|
505
|
+
raise AragoraNotFoundError("Resource", path)
|
|
506
|
+
if response.status_code == 400:
|
|
507
|
+
data = response.json() if response.content else {}
|
|
508
|
+
raise AragoraValidationError(
|
|
509
|
+
data.get("error", "Validation error"),
|
|
510
|
+
details=data.get("details"),
|
|
511
|
+
)
|
|
512
|
+
if response.status_code >= 400:
|
|
513
|
+
data = response.json() if response.content else {}
|
|
514
|
+
raise AragoraError(
|
|
515
|
+
data.get("error", f"Request failed with status {response.status_code}"),
|
|
516
|
+
code=data.get("code"),
|
|
517
|
+
status=response.status_code,
|
|
518
|
+
details=data.get("details"),
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
return response
|
|
522
|
+
|
|
523
|
+
async def _get(
|
|
524
|
+
self, path: str, *, params: dict[str, Any] | None = None
|
|
525
|
+
) -> dict[str, Any]:
|
|
526
|
+
"""Make a GET request."""
|
|
527
|
+
response = await self._request("GET", path, params=params)
|
|
528
|
+
return response.json()
|
|
529
|
+
|
|
530
|
+
async def _get_raw(
|
|
531
|
+
self, path: str, *, params: dict[str, Any] | None = None
|
|
532
|
+
) -> bytes:
|
|
533
|
+
"""Make a GET request and return raw bytes."""
|
|
534
|
+
response = await self._request("GET", path, params=params)
|
|
535
|
+
return response.content
|
|
536
|
+
|
|
537
|
+
async def _post(self, path: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
538
|
+
"""Make a POST request."""
|
|
539
|
+
response = await self._request("POST", path, json=data)
|
|
540
|
+
return response.json()
|
|
541
|
+
|
|
542
|
+
async def _delete(self, path: str) -> None:
|
|
543
|
+
"""Make a DELETE request."""
|
|
544
|
+
await self._request("DELETE", path)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Custom exceptions for the Aragora SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AragoraError(Exception):
|
|
9
|
+
"""Base exception for all Aragora SDK errors."""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
message: str,
|
|
14
|
+
*,
|
|
15
|
+
code: str | None = None,
|
|
16
|
+
status: int | None = None,
|
|
17
|
+
details: dict[str, Any] | None = None,
|
|
18
|
+
) -> None:
|
|
19
|
+
super().__init__(message)
|
|
20
|
+
self.message = message
|
|
21
|
+
self.code = code
|
|
22
|
+
self.status = status
|
|
23
|
+
self.details = details or {}
|
|
24
|
+
|
|
25
|
+
def __repr__(self) -> str:
|
|
26
|
+
return (
|
|
27
|
+
f"{self.__class__.__name__}"
|
|
28
|
+
f"({self.message!r}, code={self.code!r}, status={self.status})"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AragoraConnectionError(AragoraError):
|
|
33
|
+
"""Raised when unable to connect to the Aragora server."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, message: str = "Failed to connect to Aragora server") -> None:
|
|
36
|
+
super().__init__(message, code="CONNECTION_ERROR")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class AragoraAuthenticationError(AragoraError):
|
|
40
|
+
"""Raised when authentication fails."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, message: str = "Authentication failed") -> None:
|
|
43
|
+
super().__init__(message, code="AUTHENTICATION_ERROR", status=401)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class AragoraNotFoundError(AragoraError):
|
|
47
|
+
"""Raised when a resource is not found."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, resource: str, resource_id: str) -> None:
|
|
50
|
+
message = f"{resource} not found: {resource_id}"
|
|
51
|
+
super().__init__(message, code="NOT_FOUND", status=404)
|
|
52
|
+
self.resource = resource
|
|
53
|
+
self.resource_id = resource_id
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class AragoraValidationError(AragoraError):
|
|
57
|
+
"""Raised when request validation fails."""
|
|
58
|
+
|
|
59
|
+
def __init__(self, message: str, *, details: dict[str, Any] | None = None) -> None:
|
|
60
|
+
super().__init__(message, code="VALIDATION_ERROR", status=400, details=details)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class AragoraTimeoutError(AragoraError):
|
|
64
|
+
"""Raised when a request times out."""
|
|
65
|
+
|
|
66
|
+
def __init__(self, message: str = "Request timed out") -> None:
|
|
67
|
+
super().__init__(message, code="TIMEOUT")
|
aragora_client/py.typed
ADDED
|
File without changes
|