auraone-sdk 0.1.0__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.
- aura/__init__.py +8 -0
- aura/cli.py +61 -0
- aura/client.py +487 -0
- aura_one/__init__.py +244 -0
- aura_one/auth.py +267 -0
- aura_one/client.py +547 -0
- aura_one/exceptions.py +318 -0
- aura_one/graphql.py +127 -0
- aura_one/types.py +254 -0
- aura_one/utils.py +602 -0
- auraone_sdk-0.1.0.dist-info/METADATA +78 -0
- auraone_sdk-0.1.0.dist-info/RECORD +16 -0
- auraone_sdk-0.1.0.dist-info/WHEEL +5 -0
- auraone_sdk-0.1.0.dist-info/entry_points.txt +2 -0
- auraone_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
- auraone_sdk-0.1.0.dist-info/top_level.txt +2 -0
aura/__init__.py
ADDED
aura/cli.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
from typing import List
|
|
4
|
+
from .client import AuraClient
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
8
|
+
parser = argparse.ArgumentParser(prog="aura")
|
|
9
|
+
parser.add_argument("--base-url", default="http://localhost:8000")
|
|
10
|
+
parser.add_argument("--org-id", default="public")
|
|
11
|
+
parser.add_argument("--api-key", default=None)
|
|
12
|
+
|
|
13
|
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
14
|
+
|
|
15
|
+
sub.add_parser("list-templates")
|
|
16
|
+
sub.add_parser("quotas")
|
|
17
|
+
sub.add_parser("system-health")
|
|
18
|
+
|
|
19
|
+
eval_p = sub.add_parser("evaluate")
|
|
20
|
+
eval_p.add_argument("--template-id", required=True)
|
|
21
|
+
eval_p.add_argument("--agent-bundle-url", required=True)
|
|
22
|
+
eval_p.add_argument("--wait", action="store_true", default=False)
|
|
23
|
+
eval_p.add_argument("--robust", action="store_true", default=False)
|
|
24
|
+
|
|
25
|
+
return parser
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def run_cli(argv: List[str]) -> int:
|
|
29
|
+
args = build_parser().parse_args(argv)
|
|
30
|
+
client = AuraClient(api_key=args.api_key, base_url=args.base_url, org_id=args.org_id)
|
|
31
|
+
|
|
32
|
+
if args.cmd == "list-templates":
|
|
33
|
+
items = client.list_templates()
|
|
34
|
+
for t in items:
|
|
35
|
+
print(f"{t.id}\t{t.name}\t{t.version}\t{t.domain or ''}")
|
|
36
|
+
return 0
|
|
37
|
+
|
|
38
|
+
if args.cmd == "evaluate":
|
|
39
|
+
if args.robust:
|
|
40
|
+
res = client.evaluate_robust(template_id=args.template_id, agent_bundle_url=args.agent_bundle_url, wait=args.wait)
|
|
41
|
+
else:
|
|
42
|
+
res = client.evaluate(template_id=args.template_id, agent_bundle_url=args.agent_bundle_url, wait=args.wait)
|
|
43
|
+
print(res)
|
|
44
|
+
return 0
|
|
45
|
+
|
|
46
|
+
if args.cmd == "quotas":
|
|
47
|
+
q = client.get_quotas()
|
|
48
|
+
print(q)
|
|
49
|
+
return 0
|
|
50
|
+
|
|
51
|
+
if args.cmd == "system-health":
|
|
52
|
+
h = client.get_system_health()
|
|
53
|
+
print(h)
|
|
54
|
+
return 0
|
|
55
|
+
|
|
56
|
+
return 1
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def main() -> None:
|
|
60
|
+
sys.exit(run_cli(sys.argv[1:]))
|
|
61
|
+
|
aura/client.py
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
from typing import Optional, Dict, Any, List, Callable
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from enum import Enum
|
|
4
|
+
import time
|
|
5
|
+
import uuid
|
|
6
|
+
import os
|
|
7
|
+
import secrets
|
|
8
|
+
import httpx
|
|
9
|
+
from httpx import HTTPError, TimeoutException
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class EvaluationStatus(Enum):
|
|
13
|
+
QUEUED = "queued"
|
|
14
|
+
RUNNING = "running"
|
|
15
|
+
COMPLETED = "completed"
|
|
16
|
+
FAILED = "failed"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class EvaluationResult:
|
|
21
|
+
id: str
|
|
22
|
+
status: EvaluationStatus
|
|
23
|
+
score: Optional[float] = None
|
|
24
|
+
metrics: Optional[Dict[str, float]] = None
|
|
25
|
+
artifacts_url: Optional[str] = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class Template:
|
|
30
|
+
id: str
|
|
31
|
+
name: str
|
|
32
|
+
version: str
|
|
33
|
+
domain: Optional[str] = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AuraClient:
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
api_key: Optional[str] = None,
|
|
40
|
+
base_url: str = "https://api.auraone.ai",
|
|
41
|
+
org_id: Optional[str] = None,
|
|
42
|
+
timeout: float = 10.0,
|
|
43
|
+
retry_attempts: int = 3,
|
|
44
|
+
backoff_ms: int = 300,
|
|
45
|
+
):
|
|
46
|
+
"""
|
|
47
|
+
Python SDK client for AuraOne.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
api_key: API key or bearer JWT. If None, 'test' is used for local.
|
|
51
|
+
base_url: Base API URL (no trailing slash required).
|
|
52
|
+
org_id: Organization/tenant identifier; defaults to 'public'.
|
|
53
|
+
timeout: Per-request timeout in seconds.
|
|
54
|
+
retry_attempts: How many times to retry transient failures (>=500 or timeouts).
|
|
55
|
+
backoff_ms: Base backoff in milliseconds; grows linearly by attempt.
|
|
56
|
+
"""
|
|
57
|
+
# Public attributes
|
|
58
|
+
self.api_key = api_key or "test"
|
|
59
|
+
self.base_url = base_url.rstrip("/")
|
|
60
|
+
self.org_id = org_id or "public"
|
|
61
|
+
# Back-compat private aliases expected by tests/tooling
|
|
62
|
+
self._api_key = self.api_key
|
|
63
|
+
self._base_url = self.base_url
|
|
64
|
+
self._org_id = self.org_id
|
|
65
|
+
self._client = httpx.Client(timeout=timeout)
|
|
66
|
+
self._retry_attempts = max(1, retry_attempts)
|
|
67
|
+
self._backoff_ms = max(0, backoff_ms)
|
|
68
|
+
|
|
69
|
+
def _headers(self) -> Dict[str, str]:
|
|
70
|
+
h = {"X-Org-Id": self.org_id}
|
|
71
|
+
h["Authorization"] = f"Bearer {self.api_key}"
|
|
72
|
+
if self._otel_enabled():
|
|
73
|
+
h["traceparent"] = self._traceparent()
|
|
74
|
+
return h
|
|
75
|
+
|
|
76
|
+
def _otel_enabled(self) -> bool:
|
|
77
|
+
if os.environ.get("OTEL_DISABLED") == "true":
|
|
78
|
+
return False
|
|
79
|
+
if os.environ.get("OTEL_ENABLED") == "true":
|
|
80
|
+
return True
|
|
81
|
+
return (os.environ.get("NODE_ENV") or "development") == "production"
|
|
82
|
+
|
|
83
|
+
def _traceparent(self) -> str:
|
|
84
|
+
trace_id = uuid.uuid4().hex
|
|
85
|
+
span_id = secrets.token_hex(8)
|
|
86
|
+
return f"00-{trace_id}-{span_id}-01"
|
|
87
|
+
|
|
88
|
+
def _request(
|
|
89
|
+
self,
|
|
90
|
+
method: str,
|
|
91
|
+
path: str,
|
|
92
|
+
*,
|
|
93
|
+
json: Optional[Dict[str, Any]] = None,
|
|
94
|
+
params: Optional[Dict[str, Any]] = None,
|
|
95
|
+
headers: Optional[Dict[str, str]] = None,
|
|
96
|
+
idempotency: bool = False,
|
|
97
|
+
) -> httpx.Response:
|
|
98
|
+
url = f"{self.base_url}{path}"
|
|
99
|
+
hdrs = {**self._headers(), **(headers or {})}
|
|
100
|
+
if idempotency:
|
|
101
|
+
hdrs.setdefault("X-Idempotency-Key", str(uuid.uuid4()))
|
|
102
|
+
|
|
103
|
+
last_exc: Optional[Exception] = None
|
|
104
|
+
for attempt in range(1, self._retry_attempts + 1):
|
|
105
|
+
try:
|
|
106
|
+
# Some tests inject a lightweight client without a generic 'request' method.
|
|
107
|
+
# Support both httpx.Client.request and simple .get/.post mocks.
|
|
108
|
+
if hasattr(self._client, "request"):
|
|
109
|
+
resp = self._client.request(method, url, json=json, params=params, headers=hdrs)
|
|
110
|
+
else:
|
|
111
|
+
call = getattr(self._client, method.lower())
|
|
112
|
+
resp = call(url, json=json, params=params, headers=hdrs)
|
|
113
|
+
# Retry on 5xx when available on the response object
|
|
114
|
+
status_code = getattr(resp, "status_code", None)
|
|
115
|
+
if isinstance(status_code, int) and 500 <= status_code < 600:
|
|
116
|
+
raise HTTPError(f"Server error {status_code}")
|
|
117
|
+
return resp
|
|
118
|
+
except (TimeoutException, HTTPError) as exc:
|
|
119
|
+
last_exc = exc
|
|
120
|
+
if attempt >= self._retry_attempts:
|
|
121
|
+
raise
|
|
122
|
+
# Linear backoff (simple, deterministic)
|
|
123
|
+
time.sleep((self._backoff_ms * attempt) / 1000.0)
|
|
124
|
+
# Should not reach here
|
|
125
|
+
raise last_exc or RuntimeError("request failed without exception")
|
|
126
|
+
|
|
127
|
+
def evaluate(
|
|
128
|
+
self,
|
|
129
|
+
template_id: str,
|
|
130
|
+
agent_bundle_url: str,
|
|
131
|
+
reward_spec_id: Optional[str] = None,
|
|
132
|
+
config: Optional[Dict[str, Any]] = None,
|
|
133
|
+
wait: bool = True,
|
|
134
|
+
timeout: int = 300,
|
|
135
|
+
) -> EvaluationResult:
|
|
136
|
+
payload: Dict[str, Any] = {
|
|
137
|
+
"template_id": template_id,
|
|
138
|
+
"agent_bundle_url": agent_bundle_url,
|
|
139
|
+
}
|
|
140
|
+
if reward_spec_id:
|
|
141
|
+
payload["reward_spec_id"] = reward_spec_id
|
|
142
|
+
if config:
|
|
143
|
+
payload["config"] = config
|
|
144
|
+
r = self._request("POST", "/v1/evaluations", json=payload, headers={}, idempotency=True)
|
|
145
|
+
r.raise_for_status()
|
|
146
|
+
data = r.json()
|
|
147
|
+
status = data.get("status") or data.get("state") or "queued"
|
|
148
|
+
result = EvaluationResult(id=data["id"], status=EvaluationStatus(status))
|
|
149
|
+
if not wait:
|
|
150
|
+
return result
|
|
151
|
+
# Poll
|
|
152
|
+
end = time.time() + timeout
|
|
153
|
+
while time.time() < end:
|
|
154
|
+
res = self.get_status(result.id)
|
|
155
|
+
if res.status in (EvaluationStatus.COMPLETED, EvaluationStatus.FAILED):
|
|
156
|
+
return res
|
|
157
|
+
time.sleep(0.5)
|
|
158
|
+
return self.get_status(result.id)
|
|
159
|
+
|
|
160
|
+
def evaluate_robust(
|
|
161
|
+
self,
|
|
162
|
+
template_id: str,
|
|
163
|
+
agent_bundle_url: str,
|
|
164
|
+
*,
|
|
165
|
+
reward_spec_id: Optional[str] = None,
|
|
166
|
+
config: Optional[Dict[str, Any]] = None,
|
|
167
|
+
max_retries: int = 3,
|
|
168
|
+
backoff_ms: int = 500,
|
|
169
|
+
wait: bool = False,
|
|
170
|
+
) -> EvaluationResult:
|
|
171
|
+
"""Create an evaluation with client-side retry/backoff (POST only).
|
|
172
|
+
|
|
173
|
+
This mirrors TS SDK's robust create helper, and always uses idempotency keys.
|
|
174
|
+
"""
|
|
175
|
+
last_exc: Optional[Exception] = None
|
|
176
|
+
for attempt in range(1, max_retries + 1):
|
|
177
|
+
try:
|
|
178
|
+
return self.evaluate(
|
|
179
|
+
template_id=template_id,
|
|
180
|
+
agent_bundle_url=agent_bundle_url,
|
|
181
|
+
reward_spec_id=reward_spec_id,
|
|
182
|
+
config=config,
|
|
183
|
+
wait=wait,
|
|
184
|
+
)
|
|
185
|
+
except Exception as exc: # noqa: BLE001
|
|
186
|
+
last_exc = exc
|
|
187
|
+
if attempt >= max_retries:
|
|
188
|
+
raise
|
|
189
|
+
time.sleep((backoff_ms * attempt) / 1000.0)
|
|
190
|
+
# Should not reach here
|
|
191
|
+
if last_exc:
|
|
192
|
+
raise last_exc
|
|
193
|
+
raise RuntimeError("evaluate_robust failed without exception")
|
|
194
|
+
|
|
195
|
+
def get_status(self, evaluation_id: str) -> EvaluationResult:
|
|
196
|
+
r = self._request("GET", f"/v1/evaluations/{evaluation_id}")
|
|
197
|
+
r.raise_for_status()
|
|
198
|
+
data = r.json()
|
|
199
|
+
status = EvaluationStatus((data.get("status") or data.get("state") or "queued"))
|
|
200
|
+
return EvaluationResult(
|
|
201
|
+
id=data["id"],
|
|
202
|
+
status=status,
|
|
203
|
+
score=data.get("score"),
|
|
204
|
+
metrics=data.get("metrics"),
|
|
205
|
+
artifacts_url=data.get("artifacts_url"),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
def cancel(self, evaluation_id: str) -> EvaluationResult:
|
|
209
|
+
r = self._request("POST", f"/v1/evaluations/{evaluation_id}:cancel")
|
|
210
|
+
r.raise_for_status()
|
|
211
|
+
raw = r.json()
|
|
212
|
+
# Some APIs return the updated resource; otherwise fetch
|
|
213
|
+
if isinstance(raw, dict) and raw.get("id"):
|
|
214
|
+
s = raw.get("status") or raw.get("state") or "cancelled"
|
|
215
|
+
return EvaluationResult(
|
|
216
|
+
id=raw["id"],
|
|
217
|
+
status=EvaluationStatus(s),
|
|
218
|
+
score=raw.get("score"),
|
|
219
|
+
metrics=raw.get("metrics"),
|
|
220
|
+
artifacts_url=raw.get("artifacts_url"),
|
|
221
|
+
)
|
|
222
|
+
return self.get_status(evaluation_id)
|
|
223
|
+
|
|
224
|
+
def list_templates(self, domain: Optional[str] = None, page: int = 1, per_page: int = 20) -> List[Template]:
|
|
225
|
+
params: Dict[str, Any] = {"page": page, "per_page": per_page}
|
|
226
|
+
if domain:
|
|
227
|
+
params["domain"] = domain
|
|
228
|
+
r = self._request("GET", "/v1/templates", params=params)
|
|
229
|
+
r.raise_for_status()
|
|
230
|
+
payload = r.json()
|
|
231
|
+
data = payload if isinstance(payload, list) else payload.get("templates") or payload.get("data") or []
|
|
232
|
+
items: List[Template] = []
|
|
233
|
+
for t in data:
|
|
234
|
+
items.append(Template(id=t.get("id"), name=t["name"], version=t.get("version") or "1.0.0", domain=t.get("domain")))
|
|
235
|
+
return items
|
|
236
|
+
|
|
237
|
+
def get_template(self, template_id: str) -> Template:
|
|
238
|
+
r = self._request("GET", f"/v1/templates/{template_id}")
|
|
239
|
+
r.raise_for_status()
|
|
240
|
+
raw = r.json()
|
|
241
|
+
t = raw.get("template") if isinstance(raw, dict) and raw.get("template") else raw
|
|
242
|
+
return Template(id=t.get("id"), name=t["name"], version=t.get("version") or "1.0.0", domain=t.get("domain"))
|
|
243
|
+
|
|
244
|
+
def get_quotas(self) -> Dict[str, Any]:
|
|
245
|
+
r = self._request("GET", "/v1/quotas")
|
|
246
|
+
r.raise_for_status()
|
|
247
|
+
return r.json()
|
|
248
|
+
|
|
249
|
+
def get_attestation(self, evaluation_id: str) -> Dict[str, Any]:
|
|
250
|
+
r = self._request("GET", f"/v1/attestations/{evaluation_id}")
|
|
251
|
+
r.raise_for_status()
|
|
252
|
+
return r.json()
|
|
253
|
+
|
|
254
|
+
# ---- Advanced endpoints: analytics, algorithms, benchmarks, safety, admin ----
|
|
255
|
+
|
|
256
|
+
def get_evaluation_analytics(self, timeframe: str = "24h") -> Dict[str, Any]:
|
|
257
|
+
r = self._request("GET", "/v1/analytics/evaluation-metrics", params={"timeframe": timeframe})
|
|
258
|
+
r.raise_for_status()
|
|
259
|
+
return r.json()
|
|
260
|
+
|
|
261
|
+
def get_template_analytics(self, template_id: str, time_range: str = "30d") -> Dict[str, Any]:
|
|
262
|
+
r = self._request("GET", f"/v1/templates/{template_id}/analytics", params={"timeRange": time_range})
|
|
263
|
+
r.raise_for_status()
|
|
264
|
+
return r.json()
|
|
265
|
+
|
|
266
|
+
def get_org_templates_analytics(self, org_id: Optional[str] = None, time_range: str = "30d") -> Dict[str, Any]:
|
|
267
|
+
oid = org_id or self.org_id
|
|
268
|
+
r = self._request("GET", f"/v1/organizations/{oid}/templates/analytics", params={"timeRange": time_range})
|
|
269
|
+
r.raise_for_status()
|
|
270
|
+
return r.json()
|
|
271
|
+
|
|
272
|
+
def get_algorithm_metrics(self, org_id: Optional[str] = None) -> Dict[str, Any]:
|
|
273
|
+
oid = org_id or self.org_id
|
|
274
|
+
r = self._request("GET", f"/v1/organizations/{oid}/algorithms/metrics")
|
|
275
|
+
r.raise_for_status()
|
|
276
|
+
return r.json()
|
|
277
|
+
|
|
278
|
+
def compare_algorithms(self, algorithm_ids: List[str], metrics: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
279
|
+
payload = {"algorithm_ids": algorithm_ids, "metrics": metrics or ["accuracy", "precision", "recall", "f1Score"]}
|
|
280
|
+
r = self._request("POST", "/v1/algorithms/compare", json=payload)
|
|
281
|
+
r.raise_for_status()
|
|
282
|
+
return r.json()
|
|
283
|
+
|
|
284
|
+
def get_competitive_benchmarks(self) -> Dict[str, Any]:
|
|
285
|
+
r = self._request("GET", "/v1/competitive/benchmarks")
|
|
286
|
+
r.raise_for_status()
|
|
287
|
+
return r.json()
|
|
288
|
+
|
|
289
|
+
def get_safety_metrics(self, org_id: Optional[str] = None, time_range: str = "30d") -> Dict[str, Any]:
|
|
290
|
+
oid = org_id or self.org_id
|
|
291
|
+
r = self._request("GET", f"/v1/organizations/{oid}/safety/metrics", params={"timeRange": time_range})
|
|
292
|
+
r.raise_for_status()
|
|
293
|
+
return r.json()
|
|
294
|
+
|
|
295
|
+
def get_safety_incidents(self, org_id: Optional[str] = None, status: str = "open") -> Dict[str, Any]:
|
|
296
|
+
oid = org_id or self.org_id
|
|
297
|
+
r = self._request("GET", f"/v1/organizations/{oid}/safety/incidents", params={"status": status})
|
|
298
|
+
r.raise_for_status()
|
|
299
|
+
return r.json()
|
|
300
|
+
|
|
301
|
+
def get_compliance_reports(self, org_id: Optional[str] = None) -> Dict[str, Any]:
|
|
302
|
+
oid = org_id or self.org_id
|
|
303
|
+
r = self._request("GET", f"/v1/organizations/{oid}/safety/compliance")
|
|
304
|
+
r.raise_for_status()
|
|
305
|
+
return r.json()
|
|
306
|
+
|
|
307
|
+
def get_organization_metrics(self, org_id: Optional[str] = None) -> Dict[str, Any]:
|
|
308
|
+
oid = org_id or self.org_id
|
|
309
|
+
r = self._request("GET", f"/v1/admin/organizations/{oid}/metrics")
|
|
310
|
+
r.raise_for_status()
|
|
311
|
+
return r.json()
|
|
312
|
+
|
|
313
|
+
def get_system_health(self) -> Dict[str, Any]:
|
|
314
|
+
r = self._request("GET", "/v1/admin/system/health")
|
|
315
|
+
r.raise_for_status()
|
|
316
|
+
return r.json()
|
|
317
|
+
|
|
318
|
+
# ---- Federation endpoints ----
|
|
319
|
+
|
|
320
|
+
def get_federation_nodes(self, org_id: Optional[str] = None) -> Dict[str, Any]:
|
|
321
|
+
"""Get federation nodes for an organization."""
|
|
322
|
+
oid = org_id or self.org_id
|
|
323
|
+
r = self._request("GET", f"/v1/organizations/{oid}/federation/nodes")
|
|
324
|
+
r.raise_for_status()
|
|
325
|
+
return r.json()
|
|
326
|
+
|
|
327
|
+
def get_federation_rounds(self, org_id: Optional[str] = None, limit: int = 10, status: Optional[str] = None) -> Dict[str, Any]:
|
|
328
|
+
"""Get federation rounds for an organization."""
|
|
329
|
+
oid = org_id or self.org_id
|
|
330
|
+
params = {"limit": limit}
|
|
331
|
+
if status:
|
|
332
|
+
params["status"] = status
|
|
333
|
+
r = self._request("GET", f"/v1/organizations/{oid}/federation/rounds", params=params)
|
|
334
|
+
r.raise_for_status()
|
|
335
|
+
return r.json()
|
|
336
|
+
|
|
337
|
+
def get_federation_metrics(self, org_id: Optional[str] = None, timeframe: str = "30d") -> Dict[str, Any]:
|
|
338
|
+
"""Get federation metrics for an organization."""
|
|
339
|
+
oid = org_id or self.org_id
|
|
340
|
+
r = self._request("GET", f"/v1/organizations/{oid}/federation/metrics", params={"timeframe": timeframe})
|
|
341
|
+
r.raise_for_status()
|
|
342
|
+
return r.json()
|
|
343
|
+
|
|
344
|
+
# ---- Chemistry & 3D Visualization ----
|
|
345
|
+
|
|
346
|
+
def parse_molecular_file(self, file_path: str, format_type: Optional[str] = None, enable_caching: bool = True) -> Dict[str, Any]:
|
|
347
|
+
"""Parse molecular file and extract chemical structure data."""
|
|
348
|
+
with open(file_path, 'rb') as f:
|
|
349
|
+
files = {'file': f}
|
|
350
|
+
data = {}
|
|
351
|
+
if format_type:
|
|
352
|
+
data['format_type'] = format_type
|
|
353
|
+
if enable_caching:
|
|
354
|
+
data['enable_caching'] = 'true'
|
|
355
|
+
|
|
356
|
+
r = self._request("POST", "/v1/chemistry/parse-molecular-file-optimized", files=files, data=data)
|
|
357
|
+
r.raise_for_status()
|
|
358
|
+
return r.json()
|
|
359
|
+
|
|
360
|
+
def get_chemistry_performance_status(self) -> Dict[str, Any]:
|
|
361
|
+
"""Get chemistry service performance status."""
|
|
362
|
+
r = self._request("GET", "/v1/chemistry/performance-status")
|
|
363
|
+
r.raise_for_status()
|
|
364
|
+
return r.json()
|
|
365
|
+
|
|
366
|
+
# ---- RFT Automation (Reward Function Tooling) ----
|
|
367
|
+
|
|
368
|
+
def generate_rft_environment(self, evaluation_id: str, configuration: Dict[str, Any]) -> Dict[str, Any]:
|
|
369
|
+
"""Generate RFT environment scaffold for an evaluation."""
|
|
370
|
+
payload = {
|
|
371
|
+
"evaluation_id": evaluation_id,
|
|
372
|
+
"configuration": configuration
|
|
373
|
+
}
|
|
374
|
+
r = self._request("POST", "/v1/rft/generate-environment", json=payload)
|
|
375
|
+
r.raise_for_status()
|
|
376
|
+
return r.json()
|
|
377
|
+
|
|
378
|
+
def build_reward_function(self, strategy: str, metrics: List[str], configuration: Dict[str, Any]) -> Dict[str, Any]:
|
|
379
|
+
"""Build reward function configuration."""
|
|
380
|
+
payload = {
|
|
381
|
+
"strategy": strategy,
|
|
382
|
+
"metrics": metrics,
|
|
383
|
+
"configuration": configuration
|
|
384
|
+
}
|
|
385
|
+
r = self._request("POST", "/v1/rft/build-reward", json=payload)
|
|
386
|
+
r.raise_for_status()
|
|
387
|
+
return r.json()
|
|
388
|
+
|
|
389
|
+
# ---- Rewards & Compilation ----
|
|
390
|
+
|
|
391
|
+
def compile_reward(self, yaml_spec: str) -> Dict[str, Any]:
|
|
392
|
+
"""Compile and validate reward function YAML specification."""
|
|
393
|
+
payload = {"yaml_spec": yaml_spec}
|
|
394
|
+
r = self._request("POST", "/v1/rewards/compile", json=payload)
|
|
395
|
+
r.raise_for_status()
|
|
396
|
+
return r.json()
|
|
397
|
+
|
|
398
|
+
def list_reward_specs(self) -> Dict[str, Any]:
|
|
399
|
+
"""List available reward specifications."""
|
|
400
|
+
r = self._request("GET", "/v1/rewards")
|
|
401
|
+
r.raise_for_status()
|
|
402
|
+
return r.json()
|
|
403
|
+
|
|
404
|
+
# ---- Advanced Analytics ----
|
|
405
|
+
|
|
406
|
+
def get_template_usage_metrics(self, template_id: str) -> Dict[str, Any]:
|
|
407
|
+
"""Get detailed usage metrics for a template."""
|
|
408
|
+
r = self._request("GET", f"/v1/templates/{template_id}/usage-metrics")
|
|
409
|
+
r.raise_for_status()
|
|
410
|
+
return r.json()
|
|
411
|
+
|
|
412
|
+
# ---- Batch Operations ----
|
|
413
|
+
|
|
414
|
+
def create_multiple_evaluations(self, evaluation_requests: List[Dict[str, Any]]) -> List[EvaluationResult]:
|
|
415
|
+
"""Create multiple evaluations in batch."""
|
|
416
|
+
results = []
|
|
417
|
+
for req in evaluation_requests:
|
|
418
|
+
try:
|
|
419
|
+
result = self.evaluate(
|
|
420
|
+
template_id=req["template_id"],
|
|
421
|
+
agent_bundle_url=req["agent_bundle_url"],
|
|
422
|
+
reward_spec_id=req.get("reward_spec_id"),
|
|
423
|
+
config=req.get("config", {})
|
|
424
|
+
)
|
|
425
|
+
results.append(result)
|
|
426
|
+
except Exception as e:
|
|
427
|
+
# Continue with other evaluations even if one fails
|
|
428
|
+
print(f"Failed to create evaluation: {e}")
|
|
429
|
+
results.append(EvaluationResult(
|
|
430
|
+
id=f"failed-{len(results)}",
|
|
431
|
+
status=EvaluationStatus.FAILED,
|
|
432
|
+
score=None,
|
|
433
|
+
metrics=None
|
|
434
|
+
))
|
|
435
|
+
return results
|
|
436
|
+
|
|
437
|
+
def wait_for_multiple_completions(self, evaluation_ids: List[str], timeout_seconds: int = 300) -> List[EvaluationResult]:
|
|
438
|
+
"""Wait for multiple evaluations to complete."""
|
|
439
|
+
results = []
|
|
440
|
+
for eval_id in evaluation_ids:
|
|
441
|
+
try:
|
|
442
|
+
result = self.wait_for_completion(eval_id, timeout_seconds)
|
|
443
|
+
results.append(result)
|
|
444
|
+
except Exception as e:
|
|
445
|
+
print(f"Failed to wait for evaluation {eval_id}: {e}")
|
|
446
|
+
results.append(EvaluationResult(
|
|
447
|
+
id=eval_id,
|
|
448
|
+
status=EvaluationStatus.FAILED,
|
|
449
|
+
score=None,
|
|
450
|
+
metrics=None
|
|
451
|
+
))
|
|
452
|
+
return results
|
|
453
|
+
|
|
454
|
+
# ---- Context Managers ----
|
|
455
|
+
|
|
456
|
+
def __enter__(self):
|
|
457
|
+
"""Context manager entry."""
|
|
458
|
+
return self
|
|
459
|
+
|
|
460
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
461
|
+
"""Context manager exit - cleanup resources."""
|
|
462
|
+
if hasattr(self, '_client'):
|
|
463
|
+
self._client.close()
|
|
464
|
+
|
|
465
|
+
# ---- Utility helpers ----
|
|
466
|
+
|
|
467
|
+
def wait_for_completion(self, evaluation_id: str, timeout_seconds: int = 300, poll_interval: float = 1.0) -> EvaluationResult:
|
|
468
|
+
"""Poll evaluation status until terminal state or timeout.
|
|
469
|
+
|
|
470
|
+
Terminal states: COMPLETED, FAILED, CANCELLED (if exposed by API).
|
|
471
|
+
"""
|
|
472
|
+
end = time.time() + timeout_seconds
|
|
473
|
+
while time.time() < end:
|
|
474
|
+
res = self.get_status(evaluation_id)
|
|
475
|
+
if res.status in (
|
|
476
|
+
EvaluationStatus.COMPLETED,
|
|
477
|
+
EvaluationStatus.FAILED,
|
|
478
|
+
):
|
|
479
|
+
return res
|
|
480
|
+
# Some APIs may return cancelled
|
|
481
|
+
try:
|
|
482
|
+
if res.status.name == "CANCELLED": # type: ignore[attr-defined]
|
|
483
|
+
return res
|
|
484
|
+
except Exception:
|
|
485
|
+
pass
|
|
486
|
+
time.sleep(max(0.1, poll_interval))
|
|
487
|
+
return self.get_status(evaluation_id)
|