fleet-python 0.2.29__py3-none-any.whl → 0.2.32__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.
Potentially problematic release.
This version of fleet-python might be problematic. Click here for more details.
- examples/diff_example.py +30 -20
- examples/dsl_example.py +12 -7
- examples/example.py +4 -4
- examples/example_account.py +8 -0
- examples/example_action_log.py +2 -2
- examples/example_client.py +2 -2
- examples/example_mcp_anthropic.py +8 -5
- examples/example_mcp_openai.py +2 -2
- examples/example_sync.py +4 -4
- examples/example_task.py +16 -6
- examples/example_tasks.py +3 -6
- examples/example_verifier.py +16 -3
- examples/gemini_example.py +6 -6
- examples/json_tasks_example.py +2 -2
- examples/nova_act_example.py +2 -2
- examples/openai_example.py +3 -3
- examples/openai_simple_example.py +3 -3
- examples/query_builder_example.py +11 -7
- fleet/__init__.py +60 -5
- fleet/_async/__init__.py +258 -1
- fleet/_async/base.py +2 -1
- fleet/_async/client.py +164 -144
- fleet/_async/env/client.py +2 -0
- fleet/_async/global_client.py +43 -0
- fleet/_async/instance/client.py +1 -1
- fleet/_async/models.py +172 -171
- fleet/_async/resources/base.py +1 -1
- fleet/_async/resources/mcp.py +55 -0
- fleet/_async/resources/sqlite.py +141 -130
- fleet/_async/tasks.py +69 -16
- fleet/_async/verifiers/__init__.py +2 -2
- fleet/_async/verifiers/bundler.py +18 -14
- fleet/_async/verifiers/verifier.py +77 -71
- fleet/base.py +2 -1
- fleet/client.py +145 -158
- fleet/config.py +3 -2
- fleet/env/__init__.py +9 -1
- fleet/env/client.py +3 -0
- fleet/global_client.py +43 -0
- fleet/instance/__init__.py +1 -1
- fleet/instance/client.py +2 -4
- fleet/models.py +172 -171
- fleet/resources/base.py +1 -1
- fleet/resources/mcp.py +27 -33
- fleet/resources/sqlite.py +136 -131
- fleet/tasks.py +195 -16
- fleet/types.py +1 -1
- fleet/verifiers/__init__.py +2 -2
- fleet/verifiers/bundler.py +18 -14
- fleet/verifiers/code.py +1 -1
- fleet/verifiers/decorator.py +25 -34
- fleet/verifiers/parse.py +98 -68
- fleet/verifiers/verifier.py +77 -78
- {fleet_python-0.2.29.dist-info → fleet_python-0.2.32.dist-info}/METADATA +9 -9
- fleet_python-0.2.32.dist-info/RECORD +74 -0
- scripts/fix_sync_imports.py +87 -59
- scripts/unasync.py +10 -9
- fleet_python-0.2.29.dist-info/RECORD +0 -70
- {fleet_python-0.2.29.dist-info → fleet_python-0.2.32.dist-info}/WHEEL +0 -0
- {fleet_python-0.2.29.dist-info → fleet_python-0.2.32.dist-info}/licenses/LICENSE +0 -0
- {fleet_python-0.2.29.dist-info → fleet_python-0.2.32.dist-info}/top_level.txt +0 -0
fleet/client.py
CHANGED
|
@@ -34,7 +34,6 @@ from .models import (
|
|
|
34
34
|
TaskRequest,
|
|
35
35
|
)
|
|
36
36
|
from .tasks import Task
|
|
37
|
-
from .verifiers.parse import extract_function_name, convert_new_to_old_verifier
|
|
38
37
|
|
|
39
38
|
if TYPE_CHECKING:
|
|
40
39
|
from .verifiers import SyncVerifierFunction
|
|
@@ -51,6 +50,7 @@ from .instance.client import ValidatorType
|
|
|
51
50
|
from .resources.base import Resource
|
|
52
51
|
from .resources.sqlite import SQLiteResource
|
|
53
52
|
from .resources.browser import BrowserResource
|
|
53
|
+
from .resources.mcp import MCPResource
|
|
54
54
|
|
|
55
55
|
logger = logging.getLogger(__name__)
|
|
56
56
|
|
|
@@ -69,18 +69,18 @@ class SyncEnv(EnvironmentBase):
|
|
|
69
69
|
self.manager_url, self._client.httpx_client if self._client else None
|
|
70
70
|
)
|
|
71
71
|
return self._instance
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
def app(self, name: str) -> InstanceClient:
|
|
74
74
|
if name not in self._apps:
|
|
75
75
|
# Extract base URL by removing the current app path (e.g., /sentry/api/v1/env)
|
|
76
76
|
# manager_url looks like: https://xxx.fleetai.com/sentry/api/v1/env
|
|
77
|
-
base_url = self.manager_url.split(
|
|
77
|
+
base_url = self.manager_url.split("/api/v1/env")[0]
|
|
78
78
|
# Remove the current app name (e.g., /sentry) to get the root
|
|
79
|
-
if
|
|
80
|
-
parts = base_url.rsplit(
|
|
79
|
+
if "/" in base_url:
|
|
80
|
+
parts = base_url.rsplit("/", 1)
|
|
81
81
|
if len(parts) == 2 and parts[0] != "https:/":
|
|
82
82
|
base_url = parts[0]
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
self._apps[name] = InstanceClient(
|
|
85
85
|
f"{base_url}/{name}/api/v1/env",
|
|
86
86
|
self._client.httpx_client if self._client else None,
|
|
@@ -104,6 +104,11 @@ class SyncEnv(EnvironmentBase):
|
|
|
104
104
|
def browser(self, name: str = "cdp") -> BrowserResource:
|
|
105
105
|
return self.instance.browser(name)
|
|
106
106
|
|
|
107
|
+
@property
|
|
108
|
+
def mcp(self) -> MCPResource:
|
|
109
|
+
mcp_url = f"{self.urls.root}mcp"
|
|
110
|
+
return MCPResource(url=mcp_url, env_key=self.env_key)
|
|
111
|
+
|
|
107
112
|
def state(self, uri: str) -> Resource:
|
|
108
113
|
return self.instance.state(uri)
|
|
109
114
|
|
|
@@ -125,28 +130,28 @@ class SyncEnv(EnvironmentBase):
|
|
|
125
130
|
return _check_bundle_exists(self._load_client, bundle_hash)
|
|
126
131
|
|
|
127
132
|
def execute_verifier_remote(
|
|
128
|
-
self,
|
|
129
|
-
bundle_data: bytes,
|
|
133
|
+
self,
|
|
134
|
+
bundle_data: bytes,
|
|
130
135
|
bundle_sha: str,
|
|
131
136
|
key: str,
|
|
132
137
|
function_name: str,
|
|
133
|
-
args: tuple,
|
|
138
|
+
args: tuple,
|
|
134
139
|
args_array: list,
|
|
135
|
-
kwargs: dict,
|
|
140
|
+
kwargs: dict,
|
|
136
141
|
timeout: Optional[int] = 30,
|
|
137
142
|
needs_upload: bool = True,
|
|
138
143
|
) -> VerifiersExecuteResponse:
|
|
139
144
|
return _execute_verifier_remote(
|
|
140
|
-
self._load_client,
|
|
141
|
-
bundle_data,
|
|
145
|
+
self._load_client,
|
|
146
|
+
bundle_data,
|
|
142
147
|
bundle_sha,
|
|
143
148
|
key,
|
|
144
149
|
function_name,
|
|
145
|
-
args,
|
|
150
|
+
args,
|
|
146
151
|
args_array,
|
|
147
|
-
kwargs,
|
|
152
|
+
kwargs,
|
|
148
153
|
timeout,
|
|
149
|
-
needs_upload
|
|
154
|
+
needs_upload,
|
|
150
155
|
)
|
|
151
156
|
|
|
152
157
|
def __getstate__(self):
|
|
@@ -170,6 +175,8 @@ class Fleet:
|
|
|
170
175
|
):
|
|
171
176
|
if api_key is None:
|
|
172
177
|
api_key = os.getenv("FLEET_API_KEY")
|
|
178
|
+
if base_url is None:
|
|
179
|
+
base_url = os.getenv("FLEET_BASE_URL")
|
|
173
180
|
self._httpx_client = httpx_client or default_httpx_client(max_retries, timeout)
|
|
174
181
|
self.client = SyncWrapper(
|
|
175
182
|
api_key=api_key,
|
|
@@ -189,23 +196,27 @@ class Fleet:
|
|
|
189
196
|
response = self.client.request("GET", f"/v1/env/{env_key}")
|
|
190
197
|
return EnvironmentModel(**response.json())
|
|
191
198
|
|
|
192
|
-
def make(
|
|
193
|
-
self, env_key: str, region: Optional[str] = None
|
|
194
|
-
) -> SyncEnv:
|
|
199
|
+
def make(self, env_key: str, region: Optional[str] = None) -> SyncEnv:
|
|
195
200
|
if ":" in env_key:
|
|
196
201
|
env_key_part, version = env_key.split(":", 1)
|
|
197
|
-
if
|
|
202
|
+
if (
|
|
203
|
+
not version.startswith("v")
|
|
204
|
+
and len(version) != 0
|
|
205
|
+
and version[0].isdigit()
|
|
206
|
+
):
|
|
198
207
|
version = f"v{version}"
|
|
199
208
|
else:
|
|
200
209
|
env_key_part = env_key
|
|
201
210
|
version = None
|
|
202
211
|
|
|
203
|
-
request = InstanceRequest(
|
|
212
|
+
request = InstanceRequest(
|
|
213
|
+
env_key=env_key_part, version=version, region=region, created_from="sdk"
|
|
214
|
+
)
|
|
204
215
|
region_base_url = REGION_BASE_URL.get(region)
|
|
205
216
|
response = self.client.request(
|
|
206
217
|
"POST",
|
|
207
218
|
"/v1/env/instances",
|
|
208
|
-
json=request.model_dump(),
|
|
219
|
+
json=request.model_dump(exclude_none=True),
|
|
209
220
|
base_url=region_base_url,
|
|
210
221
|
)
|
|
211
222
|
|
|
@@ -243,15 +254,13 @@ class Fleet:
|
|
|
243
254
|
def execute_verifier_remote(
|
|
244
255
|
self, bundle_data: bytes, args: tuple, kwargs: dict, timeout: Optional[int] = 30
|
|
245
256
|
) -> VerifiersExecuteResponse:
|
|
246
|
-
return _execute_verifier_remote(
|
|
247
|
-
self.client, bundle_data, args, kwargs, timeout
|
|
248
|
-
)
|
|
257
|
+
return _execute_verifier_remote(self.client, bundle_data, args, kwargs, timeout)
|
|
249
258
|
|
|
250
259
|
def delete(self, instance_id: str) -> InstanceResponse:
|
|
251
260
|
return _delete_instance(self.client, instance_id)
|
|
252
|
-
|
|
261
|
+
|
|
253
262
|
def load_tasks_from_file(self, filename: str) -> List[Task]:
|
|
254
|
-
with open(filename,
|
|
263
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
255
264
|
tasks_data = f.read()
|
|
256
265
|
|
|
257
266
|
return self.load_task_array_from_string(tasks_data)
|
|
@@ -270,38 +279,36 @@ class Fleet:
|
|
|
270
279
|
return self.load_task_from_json(task_json)
|
|
271
280
|
|
|
272
281
|
def load_task_from_json(self, task_json: Dict) -> Task:
|
|
273
|
-
verifier = None
|
|
274
282
|
try:
|
|
275
|
-
if
|
|
283
|
+
if "verifier_id" in task_json and task_json["verifier_id"]:
|
|
276
284
|
verifier = self._create_verifier_from_data(
|
|
277
|
-
verifier_id=task_json[
|
|
278
|
-
verifier_key=task_json[
|
|
279
|
-
verifier_code=task_json[
|
|
280
|
-
verifier_sha=task_json.get(
|
|
285
|
+
verifier_id=task_json["verifier_id"],
|
|
286
|
+
verifier_key=task_json["key"],
|
|
287
|
+
verifier_code=task_json["verifier_func"],
|
|
288
|
+
verifier_sha=task_json.get("verifier_sha", ""),
|
|
281
289
|
)
|
|
282
290
|
except Exception as e:
|
|
283
291
|
logger.warning(f"Failed to create verifier {task_json['key']}: {e}")
|
|
284
|
-
|
|
292
|
+
|
|
285
293
|
task = Task(
|
|
286
|
-
key=task_json[
|
|
287
|
-
prompt=task_json[
|
|
288
|
-
env_id=task_json[
|
|
289
|
-
created_at=task_json[
|
|
290
|
-
version=task_json.get(
|
|
291
|
-
env_variables=task_json.get(
|
|
292
|
-
verifier_func=task_json.get(
|
|
294
|
+
key=task_json["key"],
|
|
295
|
+
prompt=task_json["prompt"],
|
|
296
|
+
env_id=task_json["env_id"], # Use env_id from the data
|
|
297
|
+
created_at=task_json["created_at"],
|
|
298
|
+
version=task_json.get("version"),
|
|
299
|
+
env_variables=task_json.get("env_variables", {}),
|
|
300
|
+
verifier_func=task_json.get("verifier_func"), # Set verifier code
|
|
293
301
|
verifier=verifier, # Use created verifier or None
|
|
294
|
-
metadata=task_json.get(
|
|
302
|
+
metadata=task_json.get("metadata", {}), # Default empty metadata
|
|
295
303
|
)
|
|
296
304
|
return task
|
|
297
305
|
|
|
298
|
-
|
|
299
|
-
def load_tasks(self, env_key: Optional[str] = None, task_keys: Optional[List[str]] = None) -> List[Task]:
|
|
306
|
+
def load_tasks(self, env_key: Optional[str] = None) -> List[Task]:
|
|
300
307
|
"""Load tasks for the authenticated team, optionally filtered by environment.
|
|
301
|
-
|
|
308
|
+
|
|
302
309
|
Args:
|
|
303
310
|
env_key: Optional environment key to filter tasks by
|
|
304
|
-
|
|
311
|
+
|
|
305
312
|
Returns:
|
|
306
313
|
List[Task] containing Task objects
|
|
307
314
|
"""
|
|
@@ -309,34 +316,52 @@ class Fleet:
|
|
|
309
316
|
if env_key is not None:
|
|
310
317
|
params["env_key"] = env_key
|
|
311
318
|
|
|
312
|
-
if task_keys is not None:
|
|
313
|
-
params["task_keys"] = task_keys
|
|
314
|
-
|
|
315
319
|
response = self.client.request("GET", "/v1/tasks", params=params)
|
|
316
320
|
task_list_response = TaskListResponse(**response.json())
|
|
317
|
-
|
|
321
|
+
|
|
318
322
|
# Transform TaskResponse objects to Task objects
|
|
319
323
|
tasks = []
|
|
320
324
|
for task_response in task_list_response.tasks:
|
|
321
325
|
# Create verifier function if verifier data is present
|
|
322
326
|
verifier = None
|
|
323
327
|
verifier_func = task_response.verifier_func
|
|
324
|
-
|
|
328
|
+
|
|
325
329
|
if task_response.verifier:
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
330
|
+
embedded_code = task_response.verifier.code or ""
|
|
331
|
+
is_embedded_error = embedded_code.strip().startswith(
|
|
332
|
+
"<error loading code:"
|
|
333
|
+
)
|
|
334
|
+
if not is_embedded_error:
|
|
335
|
+
# Only override if the embedded code looks valid
|
|
336
|
+
verifier_func = embedded_code
|
|
337
|
+
# Create VerifierFunction from the embedded data
|
|
338
|
+
try:
|
|
339
|
+
verifier = self._create_verifier_from_data(
|
|
340
|
+
verifier_id=task_response.verifier.verifier_id,
|
|
341
|
+
verifier_key=task_response.verifier.key,
|
|
342
|
+
verifier_code=embedded_code,
|
|
343
|
+
verifier_sha=task_response.verifier.sha256,
|
|
344
|
+
)
|
|
345
|
+
except Exception as e:
|
|
346
|
+
logger.warning(
|
|
347
|
+
f"Failed to create verifier {task_response.verifier.key}: {e}"
|
|
348
|
+
)
|
|
349
|
+
else:
|
|
350
|
+
# Fallback: try fetching by ID if embedded code failed to load
|
|
351
|
+
try:
|
|
352
|
+
logger.warning(
|
|
353
|
+
f"Embedded verifier code missing for {task_response.verifier.key} (NoSuchKey). "
|
|
354
|
+
f"Attempting to refetch by id {task_response.verifier.verifier_id}"
|
|
355
|
+
)
|
|
356
|
+
verifier = self._load_verifier(
|
|
357
|
+
task_response.verifier.verifier_id
|
|
358
|
+
)
|
|
359
|
+
except Exception as e:
|
|
360
|
+
logger.warning(
|
|
361
|
+
f"Refetch by verifier id failed for {task_response.verifier.key}: {e}. "
|
|
362
|
+
"Leaving verifier unset."
|
|
363
|
+
)
|
|
364
|
+
|
|
340
365
|
task = Task(
|
|
341
366
|
key=task_response.key,
|
|
342
367
|
prompt=task_response.prompt,
|
|
@@ -346,21 +371,21 @@ class Fleet:
|
|
|
346
371
|
env_variables=task_response.env_variables or {},
|
|
347
372
|
verifier_func=verifier_func, # Set verifier code
|
|
348
373
|
verifier=verifier, # Use created verifier or None
|
|
349
|
-
|
|
350
|
-
verifier_sha=task_response.verifier.sha256,
|
|
351
|
-
metadata={} # Default empty metadata
|
|
374
|
+
metadata={}, # Default empty metadata
|
|
352
375
|
)
|
|
353
376
|
tasks.append(task)
|
|
354
|
-
|
|
377
|
+
|
|
355
378
|
return tasks
|
|
356
379
|
|
|
357
|
-
def export_tasks(
|
|
380
|
+
def export_tasks(
|
|
381
|
+
self, env_key: Optional[str] = None, filename: Optional[str] = None
|
|
382
|
+
):
|
|
358
383
|
"""Export tasks for the authenticated team, optionally filtered by environment.
|
|
359
|
-
|
|
384
|
+
|
|
360
385
|
Args:
|
|
361
386
|
env_key: Optional environment key to filter tasks by
|
|
362
387
|
filename: Optional filename to write tasks to. If not provided, defaults to 'tasks.json' or 'tasks_{env_key}.json'
|
|
363
|
-
|
|
388
|
+
|
|
364
389
|
Returns:
|
|
365
390
|
str: Path to the exported file if tasks were written, None if no tasks found
|
|
366
391
|
"""
|
|
@@ -372,38 +397,38 @@ class Fleet:
|
|
|
372
397
|
filename = f"tasks_{env_key}.json"
|
|
373
398
|
else:
|
|
374
399
|
filename = "tasks.json"
|
|
375
|
-
|
|
400
|
+
|
|
376
401
|
# Convert tasks to serializable format
|
|
377
402
|
tasks_data = []
|
|
378
403
|
for task in tasks:
|
|
379
404
|
task_dict = task.model_dump()
|
|
380
405
|
# Remove non-serializable verifier object, keep verifier_func (code string)
|
|
381
|
-
if
|
|
382
|
-
task_dict.pop(
|
|
406
|
+
if "verifier" in task_dict:
|
|
407
|
+
task_dict.pop("verifier")
|
|
383
408
|
tasks_data.append(task_dict)
|
|
384
|
-
|
|
409
|
+
|
|
385
410
|
# Write to JSON file
|
|
386
|
-
with open(filename,
|
|
411
|
+
with open(filename, "w", encoding="utf-8") as f:
|
|
387
412
|
json.dump(tasks_data, f, indent=2, default=str)
|
|
388
|
-
|
|
413
|
+
|
|
389
414
|
logger.info(f"Exported {len(tasks)} tasks to {filename}")
|
|
390
415
|
return filename
|
|
391
416
|
else:
|
|
392
417
|
logger.info("No tasks found to export")
|
|
393
418
|
return None
|
|
394
|
-
|
|
419
|
+
|
|
395
420
|
def import_tasks(self, filename: str):
|
|
396
421
|
"""Import tasks from a JSON file.
|
|
397
|
-
|
|
422
|
+
|
|
398
423
|
Args:
|
|
399
424
|
filename: Path to the JSON file of Task objects to import
|
|
400
|
-
|
|
425
|
+
|
|
401
426
|
Returns:
|
|
402
427
|
List[Task] containing imported Task objects
|
|
403
428
|
"""
|
|
404
|
-
with open(filename,
|
|
429
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
405
430
|
tasks_data = json.load(f)
|
|
406
|
-
|
|
431
|
+
|
|
407
432
|
# Create tasks from the loaded data
|
|
408
433
|
tasks = []
|
|
409
434
|
for task_data in tasks_data:
|
|
@@ -420,7 +445,9 @@ class Fleet:
|
|
|
420
445
|
env_variables=task.env_variables or {},
|
|
421
446
|
)
|
|
422
447
|
try:
|
|
423
|
-
response = self.client.request(
|
|
448
|
+
response = self.client.request(
|
|
449
|
+
"POST", "/v1/tasks", json=payload.model_dump()
|
|
450
|
+
)
|
|
424
451
|
except Exception as e:
|
|
425
452
|
logger.error(f"Failed to import task {task.key}: {e}")
|
|
426
453
|
continue
|
|
@@ -433,108 +460,62 @@ class Fleet:
|
|
|
433
460
|
"""
|
|
434
461
|
response = self.client.request("GET", "/v1/account")
|
|
435
462
|
return AccountResponse(**response.json())
|
|
436
|
-
|
|
463
|
+
|
|
437
464
|
def _create_verifier_from_data(
|
|
438
|
-
self,
|
|
439
|
-
verifier_id: str,
|
|
440
|
-
verifier_key: str,
|
|
441
|
-
verifier_code: str,
|
|
442
|
-
verifier_sha: str
|
|
465
|
+
self, verifier_id: str, verifier_key: str, verifier_code: str, verifier_sha: str
|
|
443
466
|
) -> "SyncVerifierFunction":
|
|
444
|
-
"""Create
|
|
445
|
-
|
|
467
|
+
"""Create a SyncVerifierFunction from verifier data.
|
|
468
|
+
|
|
446
469
|
Args:
|
|
447
470
|
verifier_id: The verifier ID
|
|
448
471
|
verifier_key: The verifier key
|
|
449
472
|
verifier_code: The verifier code
|
|
450
473
|
verifier_sha: The verifier SHA256
|
|
451
|
-
|
|
474
|
+
|
|
452
475
|
Returns:
|
|
453
|
-
|
|
476
|
+
SyncVerifierFunction created from the verifier code
|
|
454
477
|
"""
|
|
478
|
+
from .tasks import verifier_from_string
|
|
455
479
|
from .verifiers.verifier import SyncVerifierFunction
|
|
456
|
-
|
|
457
|
-
# Convert async verifier code to sync
|
|
458
|
-
if 'async def' in verifier_code:
|
|
459
|
-
verifier_code = verifier_code.replace('async def', 'def')
|
|
460
|
-
if 'await ' in verifier_code:
|
|
461
|
-
verifier_code = verifier_code.replace('await ', '')
|
|
462
|
-
|
|
463
|
-
# Check if this is a new format verifier (has before/after parameters)
|
|
464
|
-
if 'before: DatabaseSnapshot' in verifier_code and 'after: DatabaseSnapshot' in verifier_code:
|
|
465
|
-
# Convert new format to old format
|
|
466
|
-
verifier_code = convert_new_to_old_verifier(verifier_code)
|
|
467
|
-
# Update function name since wrapper adds _wrapper suffix
|
|
468
|
-
original_name = extract_function_name(verifier_code.split('\n')[0])
|
|
469
|
-
if original_name and original_name.endswith('_wrapper'):
|
|
470
|
-
function_name = original_name
|
|
471
|
-
else:
|
|
472
|
-
function_name = extract_function_name(verifier_code)
|
|
473
|
-
else:
|
|
474
|
-
# Extract function name from code
|
|
475
|
-
function_name = extract_function_name(verifier_code)
|
|
476
|
-
|
|
477
|
-
if not function_name:
|
|
478
|
-
raise ValueError(f"Could not extract function name from verifier {verifier_id}")
|
|
479
|
-
|
|
480
|
-
# Create a function object from the code
|
|
481
|
-
# Import necessary classes for the namespace
|
|
482
|
-
from .verifiers.db import IgnoreConfig, DatabaseSnapshot
|
|
483
|
-
|
|
484
|
-
# Create a namespace for the function
|
|
485
|
-
namespace = {
|
|
486
|
-
'__builtins__': __builtins__,
|
|
487
|
-
'Environment': object, # Placeholder, will be provided at runtime
|
|
488
|
-
'IgnoreConfig': IgnoreConfig,
|
|
489
|
-
'DatabaseSnapshot': DatabaseSnapshot,
|
|
490
|
-
'TASK_FAILED_SCORE': 0,
|
|
491
|
-
'TASK_SUCCESSFUL_SCORE': 1,
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
# Execute the code to define the function
|
|
495
|
-
exec(verifier_code, namespace)
|
|
496
480
|
|
|
497
|
-
#
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
func = namespace[function_name]
|
|
502
|
-
|
|
503
|
-
# Create and return AsyncVerifierFunction with SHA if available
|
|
504
|
-
# Since the function was created via exec, we need to provide the raw code
|
|
505
|
-
verifier_func = SyncVerifierFunction(
|
|
506
|
-
func=func,
|
|
507
|
-
key=verifier_key,
|
|
508
|
-
extra_requirements=[],
|
|
481
|
+
# Use verifier_from_string to create the verifier
|
|
482
|
+
verifier_func = verifier_from_string(
|
|
483
|
+
verifier_func=verifier_code,
|
|
509
484
|
verifier_id=verifier_id,
|
|
510
|
-
|
|
511
|
-
|
|
485
|
+
verifier_key=verifier_key,
|
|
486
|
+
sha256=verifier_sha,
|
|
512
487
|
)
|
|
513
488
|
|
|
489
|
+
# Ensure we return a SyncVerifierFunction
|
|
490
|
+
if not isinstance(verifier_func, SyncVerifierFunction):
|
|
491
|
+
raise TypeError(
|
|
492
|
+
f"Expected SyncVerifierFunction but got {type(verifier_func).__name__}"
|
|
493
|
+
)
|
|
494
|
+
|
|
514
495
|
# Store the original verifier code for reference
|
|
515
496
|
verifier_func._verifier_code = verifier_code
|
|
516
|
-
|
|
497
|
+
|
|
517
498
|
return verifier_func
|
|
518
|
-
|
|
499
|
+
|
|
519
500
|
def _load_verifier(self, verifier_id: str) -> "SyncVerifierFunction":
|
|
520
501
|
"""Load a verifier by ID and create an AsyncVerifierFunction.
|
|
521
|
-
|
|
502
|
+
|
|
522
503
|
Args:
|
|
523
504
|
verifier_id: The verifier ID to fetch
|
|
524
|
-
|
|
505
|
+
|
|
525
506
|
Returns:
|
|
526
507
|
AsyncVerifierFunction created from the verifier code
|
|
527
508
|
"""
|
|
528
509
|
# Fetch verifier from API
|
|
529
510
|
response = self.client.request("GET", f"/v1/verifier/{verifier_id}")
|
|
530
511
|
verifier_data = response.json()
|
|
531
|
-
|
|
512
|
+
|
|
532
513
|
# Use the common method to create verifier
|
|
533
514
|
return self._create_verifier_from_data(
|
|
534
515
|
verifier_id=verifier_id,
|
|
535
516
|
verifier_key=verifier_data["key"],
|
|
536
517
|
verifier_code=verifier_data["code"],
|
|
537
|
-
verifier_sha=verifier_data.get("sha256", "")
|
|
518
|
+
verifier_sha=verifier_data.get("sha256", ""),
|
|
538
519
|
)
|
|
539
520
|
|
|
540
521
|
|
|
@@ -579,23 +560,29 @@ def _execute_verifier_remote(
|
|
|
579
560
|
"timeout": timeout,
|
|
580
561
|
"region": "us-west-1", # TODO: make configurable
|
|
581
562
|
}
|
|
582
|
-
|
|
563
|
+
|
|
583
564
|
# Add bundle data only if upload is needed
|
|
584
565
|
if needs_upload:
|
|
585
566
|
bundle_b64 = base64.b64encode(bundle_data).decode("utf-8")
|
|
586
567
|
request_data["bundle"] = bundle_b64
|
|
587
|
-
|
|
568
|
+
|
|
588
569
|
# Debug logging
|
|
589
|
-
logger.debug(
|
|
570
|
+
logger.debug(
|
|
571
|
+
f"Sending verifier execute request: key={key}, sha256={bundle_sha[:8]}..., function_name={function_name}"
|
|
572
|
+
)
|
|
590
573
|
logger.debug(f"Request has bundle: {needs_upload}")
|
|
591
574
|
logger.debug(f"Using client with base_url: {client.base_url}")
|
|
592
575
|
logger.debug(f"Request data keys: {list(request_data.keys())}")
|
|
593
|
-
logger.debug(
|
|
576
|
+
logger.debug(
|
|
577
|
+
f"Bundle size: {len(request_data.get('bundle', ''))} chars"
|
|
578
|
+
if "bundle" in request_data
|
|
579
|
+
else "No bundle"
|
|
580
|
+
)
|
|
594
581
|
|
|
595
582
|
# Note: This should be called on the instance URL, not the orchestrator
|
|
596
583
|
# The instance has manager URLs for verifier execution
|
|
597
584
|
response = client.request("POST", "/v1/verifiers/execute", json=request_data)
|
|
598
|
-
|
|
585
|
+
|
|
599
586
|
# Debug the response
|
|
600
587
|
response_json = response.json()
|
|
601
588
|
logger.debug(f"Verifier execute response: {response_json}")
|
fleet/config.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
DEFAULT_MAX_RETRIES =
|
|
2
|
-
DEFAULT_TIMEOUT =
|
|
1
|
+
DEFAULT_MAX_RETRIES = 3
|
|
2
|
+
DEFAULT_TIMEOUT = 180.0
|
|
3
3
|
|
|
4
4
|
GLOBAL_BASE_URL = "https://orchestrator.fleetai.com"
|
|
5
5
|
REGION_BASE_URL = {
|
|
6
6
|
"us-west-1": "https://us-west-1.fleetai.com",
|
|
7
7
|
"us-east-1": "https://us-east-1.fleetai.com",
|
|
8
8
|
"eu-west-2": "https://eu-west-2.fleetai.com",
|
|
9
|
+
"staging": "https://staging.fleetai.com",
|
|
9
10
|
}
|
fleet/env/__init__.py
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
"""Fleet env module - convenience functions for environment management."""
|
|
2
2
|
|
|
3
|
-
from .client import
|
|
3
|
+
from .client import (
|
|
4
|
+
make,
|
|
5
|
+
make_for_task,
|
|
6
|
+
list_envs,
|
|
7
|
+
list_regions,
|
|
8
|
+
get,
|
|
9
|
+
list_instances,
|
|
10
|
+
account,
|
|
11
|
+
)
|
|
4
12
|
|
|
5
13
|
# Import async versions from _async
|
|
6
14
|
from .._async.env.client import (
|
fleet/env/client.py
CHANGED
|
@@ -6,12 +6,15 @@ from typing import List, Optional
|
|
|
6
6
|
def make(env_key: str, region: Optional[str] = None) -> SyncEnv:
|
|
7
7
|
return Fleet().make(env_key, region=region)
|
|
8
8
|
|
|
9
|
+
|
|
9
10
|
def make_for_task(task: Task) -> SyncEnv:
|
|
10
11
|
return Fleet().make_for_task(task)
|
|
11
12
|
|
|
13
|
+
|
|
12
14
|
def list_envs() -> List[EnvironmentModel]:
|
|
13
15
|
return Fleet().list_envs()
|
|
14
16
|
|
|
17
|
+
|
|
15
18
|
def list_regions() -> List[str]:
|
|
16
19
|
return Fleet().list_regions()
|
|
17
20
|
|
fleet/global_client.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from .client import Fleet
|
|
6
|
+
from .config import DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_default_client: Optional[Fleet] = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_client() -> Fleet:
|
|
13
|
+
"""Get the global default AsyncFleet client, creating it if needed."""
|
|
14
|
+
global _default_client
|
|
15
|
+
if _default_client is None:
|
|
16
|
+
_default_client = Fleet()
|
|
17
|
+
return _default_client
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def configure(
|
|
21
|
+
api_key: Optional[str] = None,
|
|
22
|
+
base_url: Optional[str] = None,
|
|
23
|
+
max_retries: int = DEFAULT_MAX_RETRIES,
|
|
24
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
25
|
+
) -> Fleet:
|
|
26
|
+
"""Configure the global default AsyncFleet client.
|
|
27
|
+
|
|
28
|
+
Returns the configured client instance.
|
|
29
|
+
"""
|
|
30
|
+
global _default_client
|
|
31
|
+
_default_client = Fleet(
|
|
32
|
+
api_key=api_key,
|
|
33
|
+
base_url=base_url,
|
|
34
|
+
max_retries=max_retries,
|
|
35
|
+
timeout=timeout,
|
|
36
|
+
)
|
|
37
|
+
return _default_client
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def reset_client() -> None:
|
|
41
|
+
"""Reset the global default client. A new one will be created on next access."""
|
|
42
|
+
global _default_client
|
|
43
|
+
_default_client = None
|
fleet/instance/__init__.py
CHANGED
fleet/instance/client.py
CHANGED
|
@@ -63,9 +63,7 @@ class InstanceClient:
|
|
|
63
63
|
def load(self) -> None:
|
|
64
64
|
self._load_resources()
|
|
65
65
|
|
|
66
|
-
def reset(
|
|
67
|
-
self, reset_request: Optional[ResetRequest] = None
|
|
68
|
-
) -> ResetResponse:
|
|
66
|
+
def reset(self, reset_request: Optional[ResetRequest] = None) -> ResetResponse:
|
|
69
67
|
response = self.client.request(
|
|
70
68
|
"POST", "/reset", json=reset_request.model_dump() if reset_request else None
|
|
71
69
|
)
|
|
@@ -130,7 +128,7 @@ class InstanceClient:
|
|
|
130
128
|
|
|
131
129
|
def _load_resources(self) -> None:
|
|
132
130
|
if self._resources is None:
|
|
133
|
-
response = self.client.request("GET", "/resources")
|
|
131
|
+
response = self.client.request("GET", "/resources", timeout=1.0)
|
|
134
132
|
if response.status_code != 200:
|
|
135
133
|
self._resources = []
|
|
136
134
|
return
|