fleet-python 0.2.29__py3-none-any.whl → 0.2.34__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/exampleResume.py +191 -0
- 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
- examples/test_cdp_logging.py +80 -0
- 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 +162 -148
- fleet/config.py +3 -2
- fleet/env/__init__.py +9 -1
- fleet/env/client.py +4 -1
- fleet/global_client.py +43 -0
- fleet/instance/__init__.py +1 -1
- fleet/instance/client.py +1 -1
- fleet/models.py +172 -171
- fleet/resources/base.py +1 -1
- fleet/resources/mcp.py +11 -16
- fleet/resources/sqlite.py +141 -130
- fleet/tasks.py +86 -15
- 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 -71
- {fleet_python-0.2.29.dist-info → fleet_python-0.2.34.dist-info}/METADATA +9 -9
- fleet_python-0.2.34.dist-info/RECORD +76 -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.34.dist-info}/WHEEL +0 -0
- {fleet_python-0.2.29.dist-info → fleet_python-0.2.34.dist-info}/licenses/LICENSE +0 -0
- {fleet_python-0.2.29.dist-info → fleet_python-0.2.34.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
|
|
|
@@ -249,14 +260,16 @@ class Fleet:
|
|
|
249
260
|
|
|
250
261
|
def delete(self, instance_id: str) -> InstanceResponse:
|
|
251
262
|
return _delete_instance(self.client, instance_id)
|
|
252
|
-
|
|
263
|
+
|
|
253
264
|
def load_tasks_from_file(self, filename: str) -> List[Task]:
|
|
254
|
-
with open(filename,
|
|
265
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
255
266
|
tasks_data = f.read()
|
|
256
267
|
|
|
257
268
|
return self.load_task_array_from_string(tasks_data)
|
|
258
269
|
|
|
259
|
-
def load_task_array_from_string(
|
|
270
|
+
def load_task_array_from_string(
|
|
271
|
+
self, serialized_tasks: List[Dict]
|
|
272
|
+
) -> List[Task]:
|
|
260
273
|
tasks = []
|
|
261
274
|
|
|
262
275
|
json_tasks = json.loads(serialized_tasks)
|
|
@@ -270,73 +283,102 @@ class Fleet:
|
|
|
270
283
|
return self.load_task_from_json(task_json)
|
|
271
284
|
|
|
272
285
|
def load_task_from_json(self, task_json: Dict) -> Task:
|
|
273
|
-
verifier = None
|
|
274
286
|
try:
|
|
275
|
-
if
|
|
287
|
+
if "verifier_id" in task_json and task_json["verifier_id"]:
|
|
276
288
|
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(
|
|
289
|
+
verifier_id=task_json["verifier_id"],
|
|
290
|
+
verifier_key=task_json["key"],
|
|
291
|
+
verifier_code=task_json["verifier_func"],
|
|
292
|
+
verifier_sha=task_json.get("verifier_sha", ""),
|
|
281
293
|
)
|
|
282
294
|
except Exception as e:
|
|
283
295
|
logger.warning(f"Failed to create verifier {task_json['key']}: {e}")
|
|
284
|
-
|
|
296
|
+
|
|
285
297
|
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(
|
|
298
|
+
key=task_json["key"],
|
|
299
|
+
prompt=task_json["prompt"],
|
|
300
|
+
env_id=task_json["env_id"], # Use env_id from the data
|
|
301
|
+
created_at=task_json["created_at"],
|
|
302
|
+
version=task_json.get("version"),
|
|
303
|
+
env_variables=task_json.get("env_variables", {}),
|
|
304
|
+
verifier_func=task_json.get("verifier_func"), # Set verifier code
|
|
293
305
|
verifier=verifier, # Use created verifier or None
|
|
294
|
-
metadata=task_json.get(
|
|
306
|
+
metadata=task_json.get("metadata", {}), # Default empty metadata
|
|
295
307
|
)
|
|
296
308
|
return task
|
|
297
309
|
|
|
310
|
+
def load_tasks(
|
|
311
|
+
self,
|
|
312
|
+
env_key: Optional[str] = None,
|
|
313
|
+
keys: Optional[List[str]] = None,
|
|
314
|
+
version: Optional[str] = None,
|
|
315
|
+
team_id: Optional[str] = None
|
|
316
|
+
) -> List[Task]:
|
|
317
|
+
"""Load tasks for the authenticated team, with optional filtering.
|
|
298
318
|
|
|
299
|
-
def load_tasks(self, env_key: Optional[str] = None, task_keys: Optional[List[str]] = None) -> List[Task]:
|
|
300
|
-
"""Load tasks for the authenticated team, optionally filtered by environment.
|
|
301
|
-
|
|
302
319
|
Args:
|
|
303
320
|
env_key: Optional environment key to filter tasks by
|
|
304
|
-
|
|
321
|
+
keys: Optional list of task keys to filter by
|
|
322
|
+
version: Optional version to filter tasks by (client-side filter)
|
|
323
|
+
team_id: Optional team_id to filter by (admin only)
|
|
324
|
+
|
|
305
325
|
Returns:
|
|
306
326
|
List[Task] containing Task objects
|
|
307
327
|
"""
|
|
308
328
|
params = {}
|
|
309
329
|
if env_key is not None:
|
|
310
330
|
params["env_key"] = env_key
|
|
331
|
+
if keys is not None:
|
|
332
|
+
params["task_keys"] = keys
|
|
333
|
+
if team_id is not None:
|
|
334
|
+
params["team_id"] = team_id
|
|
311
335
|
|
|
312
|
-
if task_keys is not None:
|
|
313
|
-
params["task_keys"] = task_keys
|
|
314
|
-
|
|
315
336
|
response = self.client.request("GET", "/v1/tasks", params=params)
|
|
316
337
|
task_list_response = TaskListResponse(**response.json())
|
|
317
|
-
|
|
338
|
+
|
|
318
339
|
# Transform TaskResponse objects to Task objects
|
|
319
340
|
tasks = []
|
|
320
341
|
for task_response in task_list_response.tasks:
|
|
321
342
|
# Create verifier function if verifier data is present
|
|
322
343
|
verifier = None
|
|
323
344
|
verifier_func = task_response.verifier_func
|
|
324
|
-
|
|
345
|
+
|
|
325
346
|
if task_response.verifier:
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
347
|
+
embedded_code = task_response.verifier.code or ""
|
|
348
|
+
is_embedded_error = embedded_code.strip().startswith(
|
|
349
|
+
"<error loading code:"
|
|
350
|
+
)
|
|
351
|
+
if not is_embedded_error:
|
|
352
|
+
# Only override if the embedded code looks valid
|
|
353
|
+
verifier_func = embedded_code
|
|
354
|
+
# Create VerifierFunction from the embedded data
|
|
355
|
+
try:
|
|
356
|
+
verifier = self._create_verifier_from_data(
|
|
357
|
+
verifier_id=task_response.verifier.verifier_id,
|
|
358
|
+
verifier_key=task_response.verifier.key,
|
|
359
|
+
verifier_code=embedded_code,
|
|
360
|
+
verifier_sha=task_response.verifier.sha256,
|
|
361
|
+
)
|
|
362
|
+
except Exception as e:
|
|
363
|
+
logger.warning(
|
|
364
|
+
f"Failed to create verifier {task_response.verifier.key}: {e}"
|
|
365
|
+
)
|
|
366
|
+
else:
|
|
367
|
+
# Fallback: try fetching by ID if embedded code failed to load
|
|
368
|
+
try:
|
|
369
|
+
logger.warning(
|
|
370
|
+
f"Embedded verifier code missing for {task_response.verifier.key} (NoSuchKey). "
|
|
371
|
+
f"Attempting to refetch by id {task_response.verifier.verifier_id}"
|
|
372
|
+
)
|
|
373
|
+
verifier = self._load_verifier(
|
|
374
|
+
task_response.verifier.verifier_id
|
|
375
|
+
)
|
|
376
|
+
except Exception as e:
|
|
377
|
+
logger.warning(
|
|
378
|
+
f"Refetch by verifier id failed for {task_response.verifier.key}: {e}. "
|
|
379
|
+
"Leaving verifier unset."
|
|
380
|
+
)
|
|
381
|
+
|
|
340
382
|
task = Task(
|
|
341
383
|
key=task_response.key,
|
|
342
384
|
prompt=task_response.prompt,
|
|
@@ -346,21 +388,25 @@ class Fleet:
|
|
|
346
388
|
env_variables=task_response.env_variables or {},
|
|
347
389
|
verifier_func=verifier_func, # Set verifier code
|
|
348
390
|
verifier=verifier, # Use created verifier or None
|
|
349
|
-
|
|
350
|
-
verifier_sha=task_response.verifier.sha256,
|
|
351
|
-
metadata={} # Default empty metadata
|
|
391
|
+
metadata={}, # Default empty metadata
|
|
352
392
|
)
|
|
353
393
|
tasks.append(task)
|
|
354
|
-
|
|
394
|
+
|
|
395
|
+
# Apply client-side filtering for version if specified
|
|
396
|
+
if version is not None:
|
|
397
|
+
tasks = [task for task in tasks if task.version == version]
|
|
398
|
+
|
|
355
399
|
return tasks
|
|
356
400
|
|
|
357
|
-
def export_tasks(
|
|
401
|
+
def export_tasks(
|
|
402
|
+
self, env_key: Optional[str] = None, filename: Optional[str] = None
|
|
403
|
+
):
|
|
358
404
|
"""Export tasks for the authenticated team, optionally filtered by environment.
|
|
359
|
-
|
|
405
|
+
|
|
360
406
|
Args:
|
|
361
407
|
env_key: Optional environment key to filter tasks by
|
|
362
408
|
filename: Optional filename to write tasks to. If not provided, defaults to 'tasks.json' or 'tasks_{env_key}.json'
|
|
363
|
-
|
|
409
|
+
|
|
364
410
|
Returns:
|
|
365
411
|
str: Path to the exported file if tasks were written, None if no tasks found
|
|
366
412
|
"""
|
|
@@ -372,38 +418,38 @@ class Fleet:
|
|
|
372
418
|
filename = f"tasks_{env_key}.json"
|
|
373
419
|
else:
|
|
374
420
|
filename = "tasks.json"
|
|
375
|
-
|
|
421
|
+
|
|
376
422
|
# Convert tasks to serializable format
|
|
377
423
|
tasks_data = []
|
|
378
424
|
for task in tasks:
|
|
379
425
|
task_dict = task.model_dump()
|
|
380
426
|
# Remove non-serializable verifier object, keep verifier_func (code string)
|
|
381
|
-
if
|
|
382
|
-
task_dict.pop(
|
|
427
|
+
if "verifier" in task_dict:
|
|
428
|
+
task_dict.pop("verifier")
|
|
383
429
|
tasks_data.append(task_dict)
|
|
384
|
-
|
|
430
|
+
|
|
385
431
|
# Write to JSON file
|
|
386
|
-
with open(filename,
|
|
432
|
+
with open(filename, "w", encoding="utf-8") as f:
|
|
387
433
|
json.dump(tasks_data, f, indent=2, default=str)
|
|
388
|
-
|
|
434
|
+
|
|
389
435
|
logger.info(f"Exported {len(tasks)} tasks to {filename}")
|
|
390
436
|
return filename
|
|
391
437
|
else:
|
|
392
438
|
logger.info("No tasks found to export")
|
|
393
439
|
return None
|
|
394
|
-
|
|
440
|
+
|
|
395
441
|
def import_tasks(self, filename: str):
|
|
396
442
|
"""Import tasks from a JSON file.
|
|
397
|
-
|
|
443
|
+
|
|
398
444
|
Args:
|
|
399
445
|
filename: Path to the JSON file of Task objects to import
|
|
400
|
-
|
|
446
|
+
|
|
401
447
|
Returns:
|
|
402
448
|
List[Task] containing imported Task objects
|
|
403
449
|
"""
|
|
404
|
-
with open(filename,
|
|
450
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
405
451
|
tasks_data = json.load(f)
|
|
406
|
-
|
|
452
|
+
|
|
407
453
|
# Create tasks from the loaded data
|
|
408
454
|
tasks = []
|
|
409
455
|
for task_data in tasks_data:
|
|
@@ -420,7 +466,9 @@ class Fleet:
|
|
|
420
466
|
env_variables=task.env_variables or {},
|
|
421
467
|
)
|
|
422
468
|
try:
|
|
423
|
-
response = self.client.request(
|
|
469
|
+
response = self.client.request(
|
|
470
|
+
"POST", "/v1/tasks", json=payload.model_dump()
|
|
471
|
+
)
|
|
424
472
|
except Exception as e:
|
|
425
473
|
logger.error(f"Failed to import task {task.key}: {e}")
|
|
426
474
|
continue
|
|
@@ -433,25 +481,22 @@ class Fleet:
|
|
|
433
481
|
"""
|
|
434
482
|
response = self.client.request("GET", "/v1/account")
|
|
435
483
|
return AccountResponse(**response.json())
|
|
436
|
-
|
|
484
|
+
|
|
437
485
|
def _create_verifier_from_data(
|
|
438
|
-
self,
|
|
439
|
-
verifier_id: str,
|
|
440
|
-
verifier_key: str,
|
|
441
|
-
verifier_code: str,
|
|
442
|
-
verifier_sha: str
|
|
486
|
+
self, verifier_id: str, verifier_key: str, verifier_code: str, verifier_sha: str
|
|
443
487
|
) -> "SyncVerifierFunction":
|
|
444
488
|
"""Create an AsyncVerifierFunction from verifier data.
|
|
445
|
-
|
|
489
|
+
|
|
446
490
|
Args:
|
|
447
491
|
verifier_id: The verifier ID
|
|
448
492
|
verifier_key: The verifier key
|
|
449
493
|
verifier_code: The verifier code
|
|
450
494
|
verifier_sha: The verifier SHA256
|
|
451
|
-
|
|
495
|
+
|
|
452
496
|
Returns:
|
|
453
497
|
AsyncVerifierFunction created from the verifier code
|
|
454
498
|
"""
|
|
499
|
+
from ..tasks import verifier_from_string
|
|
455
500
|
from .verifiers.verifier import SyncVerifierFunction
|
|
456
501
|
|
|
457
502
|
# Convert async verifier code to sync
|
|
@@ -460,81 +505,44 @@ class Fleet:
|
|
|
460
505
|
if 'await ' in verifier_code:
|
|
461
506
|
verifier_code = verifier_code.replace('await ', '')
|
|
462
507
|
|
|
463
|
-
#
|
|
464
|
-
|
|
465
|
-
|
|
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
|
-
|
|
497
|
-
# Get the function object
|
|
498
|
-
if function_name not in namespace:
|
|
499
|
-
raise ValueError(f"Function {function_name} not found in verifier code")
|
|
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=[],
|
|
508
|
+
# Use verifier_from_string to create the verifier
|
|
509
|
+
verifier_func = verifier_from_string(
|
|
510
|
+
verifier_func=verifier_code,
|
|
509
511
|
verifier_id=verifier_id,
|
|
510
|
-
|
|
511
|
-
|
|
512
|
+
verifier_key=verifier_key,
|
|
513
|
+
sha256=verifier_sha,
|
|
512
514
|
)
|
|
513
515
|
|
|
516
|
+
# Ensure we return an AsyncVerifierFunction
|
|
517
|
+
if not isinstance(verifier_func, SyncVerifierFunction):
|
|
518
|
+
raise TypeError(
|
|
519
|
+
f"Expected AsyncVerifierFunction but got {type(verifier_func).__name__}"
|
|
520
|
+
)
|
|
521
|
+
|
|
514
522
|
# Store the original verifier code for reference
|
|
515
523
|
verifier_func._verifier_code = verifier_code
|
|
516
|
-
|
|
524
|
+
|
|
517
525
|
return verifier_func
|
|
518
|
-
|
|
526
|
+
|
|
519
527
|
def _load_verifier(self, verifier_id: str) -> "SyncVerifierFunction":
|
|
520
528
|
"""Load a verifier by ID and create an AsyncVerifierFunction.
|
|
521
|
-
|
|
529
|
+
|
|
522
530
|
Args:
|
|
523
531
|
verifier_id: The verifier ID to fetch
|
|
524
|
-
|
|
532
|
+
|
|
525
533
|
Returns:
|
|
526
534
|
AsyncVerifierFunction created from the verifier code
|
|
527
535
|
"""
|
|
528
536
|
# Fetch verifier from API
|
|
529
537
|
response = self.client.request("GET", f"/v1/verifier/{verifier_id}")
|
|
530
538
|
verifier_data = response.json()
|
|
531
|
-
|
|
539
|
+
|
|
532
540
|
# Use the common method to create verifier
|
|
533
541
|
return self._create_verifier_from_data(
|
|
534
542
|
verifier_id=verifier_id,
|
|
535
543
|
verifier_key=verifier_data["key"],
|
|
536
544
|
verifier_code=verifier_data["code"],
|
|
537
|
-
verifier_sha=verifier_data.get("sha256", "")
|
|
545
|
+
verifier_sha=verifier_data.get("sha256", ""),
|
|
538
546
|
)
|
|
539
547
|
|
|
540
548
|
|
|
@@ -579,23 +587,29 @@ def _execute_verifier_remote(
|
|
|
579
587
|
"timeout": timeout,
|
|
580
588
|
"region": "us-west-1", # TODO: make configurable
|
|
581
589
|
}
|
|
582
|
-
|
|
590
|
+
|
|
583
591
|
# Add bundle data only if upload is needed
|
|
584
592
|
if needs_upload:
|
|
585
593
|
bundle_b64 = base64.b64encode(bundle_data).decode("utf-8")
|
|
586
594
|
request_data["bundle"] = bundle_b64
|
|
587
|
-
|
|
595
|
+
|
|
588
596
|
# Debug logging
|
|
589
|
-
logger.debug(
|
|
597
|
+
logger.debug(
|
|
598
|
+
f"Sending verifier execute request: key={key}, sha256={bundle_sha[:8]}..., function_name={function_name}"
|
|
599
|
+
)
|
|
590
600
|
logger.debug(f"Request has bundle: {needs_upload}")
|
|
591
601
|
logger.debug(f"Using client with base_url: {client.base_url}")
|
|
592
602
|
logger.debug(f"Request data keys: {list(request_data.keys())}")
|
|
593
|
-
logger.debug(
|
|
603
|
+
logger.debug(
|
|
604
|
+
f"Bundle size: {len(request_data.get('bundle', ''))} chars"
|
|
605
|
+
if "bundle" in request_data
|
|
606
|
+
else "No bundle"
|
|
607
|
+
)
|
|
594
608
|
|
|
595
609
|
# Note: This should be called on the instance URL, not the orchestrator
|
|
596
610
|
# The instance has manager URLs for verifier execution
|
|
597
611
|
response = client.request("POST", "/v1/verifiers/execute", json=request_data)
|
|
598
|
-
|
|
612
|
+
|
|
599
613
|
# Debug the response
|
|
600
614
|
response_json = response.json()
|
|
601
615
|
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_async(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
|
@@ -130,7 +130,7 @@ class InstanceClient:
|
|
|
130
130
|
|
|
131
131
|
def _load_resources(self) -> None:
|
|
132
132
|
if self._resources is None:
|
|
133
|
-
response = self.client.request("GET", "/resources")
|
|
133
|
+
response = self.client.request("GET", "/resources", timeout=1.0)
|
|
134
134
|
if response.status_code != 200:
|
|
135
135
|
self._resources = []
|
|
136
136
|
return
|