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.
@@ -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
+ ]
@@ -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")
File without changes