fleet-python 0.2.28__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 +194 -127
- fleet/_async/env/client.py +5 -1
- 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 +71 -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 +176 -136
- fleet/config.py +3 -2
- fleet/env/__init__.py +10 -1
- fleet/env/client.py +5 -1
- 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 +197 -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.28.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.28.dist-info/RECORD +0 -70
- {fleet_python-0.2.28.dist-info → fleet_python-0.2.32.dist-info}/WHEEL +0 -0
- {fleet_python-0.2.28.dist-info → fleet_python-0.2.32.dist-info}/licenses/LICENSE +0 -0
- {fleet_python-0.2.28.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
|
|
|
@@ -213,6 +224,9 @@ class Fleet:
|
|
|
213
224
|
instance.instance.load()
|
|
214
225
|
return instance
|
|
215
226
|
|
|
227
|
+
def make_for_task(self, task: Task) -> SyncEnv:
|
|
228
|
+
return self.make(env_key=f"{task.env_id}:{task.version}")
|
|
229
|
+
|
|
216
230
|
def instances(
|
|
217
231
|
self, status: Optional[str] = None, region: Optional[str] = None
|
|
218
232
|
) -> List[SyncEnv]:
|
|
@@ -240,51 +254,114 @@ class Fleet:
|
|
|
240
254
|
def execute_verifier_remote(
|
|
241
255
|
self, bundle_data: bytes, args: tuple, kwargs: dict, timeout: Optional[int] = 30
|
|
242
256
|
) -> VerifiersExecuteResponse:
|
|
243
|
-
return _execute_verifier_remote(
|
|
244
|
-
self.client, bundle_data, args, kwargs, timeout
|
|
245
|
-
)
|
|
257
|
+
return _execute_verifier_remote(self.client, bundle_data, args, kwargs, timeout)
|
|
246
258
|
|
|
247
259
|
def delete(self, instance_id: str) -> InstanceResponse:
|
|
248
260
|
return _delete_instance(self.client, instance_id)
|
|
249
261
|
|
|
262
|
+
def load_tasks_from_file(self, filename: str) -> List[Task]:
|
|
263
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
264
|
+
tasks_data = f.read()
|
|
265
|
+
|
|
266
|
+
return self.load_task_array_from_string(tasks_data)
|
|
267
|
+
|
|
268
|
+
def load_task_array_from_string(self, serialized_tasks: List[Dict]) -> List[Task]:
|
|
269
|
+
tasks = []
|
|
270
|
+
|
|
271
|
+
json_tasks = json.loads(serialized_tasks)
|
|
272
|
+
for json_task in json_tasks:
|
|
273
|
+
parsed_task = self.load_task_from_json(json_task)
|
|
274
|
+
tasks.append(parsed_task)
|
|
275
|
+
return tasks
|
|
276
|
+
|
|
277
|
+
def load_task_from_string(self, task_string: str) -> Task:
|
|
278
|
+
task_json = json.loads(task_string)
|
|
279
|
+
return self.load_task_from_json(task_json)
|
|
280
|
+
|
|
281
|
+
def load_task_from_json(self, task_json: Dict) -> Task:
|
|
282
|
+
try:
|
|
283
|
+
if "verifier_id" in task_json and task_json["verifier_id"]:
|
|
284
|
+
verifier = self._create_verifier_from_data(
|
|
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", ""),
|
|
289
|
+
)
|
|
290
|
+
except Exception as e:
|
|
291
|
+
logger.warning(f"Failed to create verifier {task_json['key']}: {e}")
|
|
292
|
+
|
|
293
|
+
task = Task(
|
|
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
|
|
301
|
+
verifier=verifier, # Use created verifier or None
|
|
302
|
+
metadata=task_json.get("metadata", {}), # Default empty metadata
|
|
303
|
+
)
|
|
304
|
+
return task
|
|
305
|
+
|
|
250
306
|
def load_tasks(self, env_key: Optional[str] = None) -> List[Task]:
|
|
251
307
|
"""Load tasks for the authenticated team, optionally filtered by environment.
|
|
252
|
-
|
|
308
|
+
|
|
253
309
|
Args:
|
|
254
310
|
env_key: Optional environment key to filter tasks by
|
|
255
|
-
|
|
311
|
+
|
|
256
312
|
Returns:
|
|
257
313
|
List[Task] containing Task objects
|
|
258
314
|
"""
|
|
259
315
|
params = {}
|
|
260
316
|
if env_key is not None:
|
|
261
317
|
params["env_key"] = env_key
|
|
262
|
-
|
|
318
|
+
|
|
263
319
|
response = self.client.request("GET", "/v1/tasks", params=params)
|
|
264
320
|
task_list_response = TaskListResponse(**response.json())
|
|
265
|
-
|
|
321
|
+
|
|
266
322
|
# Transform TaskResponse objects to Task objects
|
|
267
323
|
tasks = []
|
|
268
324
|
for task_response in task_list_response.tasks:
|
|
269
325
|
# Create verifier function if verifier data is present
|
|
270
326
|
verifier = None
|
|
271
327
|
verifier_func = task_response.verifier_func
|
|
272
|
-
|
|
328
|
+
|
|
273
329
|
if task_response.verifier:
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
+
|
|
288
365
|
task = Task(
|
|
289
366
|
key=task_response.key,
|
|
290
367
|
prompt=task_response.prompt,
|
|
@@ -294,19 +371,21 @@ class Fleet:
|
|
|
294
371
|
env_variables=task_response.env_variables or {},
|
|
295
372
|
verifier_func=verifier_func, # Set verifier code
|
|
296
373
|
verifier=verifier, # Use created verifier or None
|
|
297
|
-
metadata={} # Default empty metadata
|
|
374
|
+
metadata={}, # Default empty metadata
|
|
298
375
|
)
|
|
299
376
|
tasks.append(task)
|
|
300
|
-
|
|
377
|
+
|
|
301
378
|
return tasks
|
|
302
379
|
|
|
303
|
-
def export_tasks(
|
|
380
|
+
def export_tasks(
|
|
381
|
+
self, env_key: Optional[str] = None, filename: Optional[str] = None
|
|
382
|
+
):
|
|
304
383
|
"""Export tasks for the authenticated team, optionally filtered by environment.
|
|
305
|
-
|
|
384
|
+
|
|
306
385
|
Args:
|
|
307
386
|
env_key: Optional environment key to filter tasks by
|
|
308
387
|
filename: Optional filename to write tasks to. If not provided, defaults to 'tasks.json' or 'tasks_{env_key}.json'
|
|
309
|
-
|
|
388
|
+
|
|
310
389
|
Returns:
|
|
311
390
|
str: Path to the exported file if tasks were written, None if no tasks found
|
|
312
391
|
"""
|
|
@@ -318,38 +397,38 @@ class Fleet:
|
|
|
318
397
|
filename = f"tasks_{env_key}.json"
|
|
319
398
|
else:
|
|
320
399
|
filename = "tasks.json"
|
|
321
|
-
|
|
400
|
+
|
|
322
401
|
# Convert tasks to serializable format
|
|
323
402
|
tasks_data = []
|
|
324
403
|
for task in tasks:
|
|
325
404
|
task_dict = task.model_dump()
|
|
326
405
|
# Remove non-serializable verifier object, keep verifier_func (code string)
|
|
327
|
-
if
|
|
328
|
-
task_dict.pop(
|
|
406
|
+
if "verifier" in task_dict:
|
|
407
|
+
task_dict.pop("verifier")
|
|
329
408
|
tasks_data.append(task_dict)
|
|
330
|
-
|
|
409
|
+
|
|
331
410
|
# Write to JSON file
|
|
332
|
-
with open(filename,
|
|
411
|
+
with open(filename, "w", encoding="utf-8") as f:
|
|
333
412
|
json.dump(tasks_data, f, indent=2, default=str)
|
|
334
|
-
|
|
413
|
+
|
|
335
414
|
logger.info(f"Exported {len(tasks)} tasks to {filename}")
|
|
336
415
|
return filename
|
|
337
416
|
else:
|
|
338
417
|
logger.info("No tasks found to export")
|
|
339
418
|
return None
|
|
340
|
-
|
|
419
|
+
|
|
341
420
|
def import_tasks(self, filename: str):
|
|
342
421
|
"""Import tasks from a JSON file.
|
|
343
|
-
|
|
422
|
+
|
|
344
423
|
Args:
|
|
345
424
|
filename: Path to the JSON file of Task objects to import
|
|
346
|
-
|
|
425
|
+
|
|
347
426
|
Returns:
|
|
348
427
|
List[Task] containing imported Task objects
|
|
349
428
|
"""
|
|
350
|
-
with open(filename,
|
|
429
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
351
430
|
tasks_data = json.load(f)
|
|
352
|
-
|
|
431
|
+
|
|
353
432
|
# Create tasks from the loaded data
|
|
354
433
|
tasks = []
|
|
355
434
|
for task_data in tasks_data:
|
|
@@ -366,12 +445,13 @@ class Fleet:
|
|
|
366
445
|
env_variables=task.env_variables or {},
|
|
367
446
|
)
|
|
368
447
|
try:
|
|
369
|
-
response = self.client.request(
|
|
448
|
+
response = self.client.request(
|
|
449
|
+
"POST", "/v1/tasks", json=payload.model_dump()
|
|
450
|
+
)
|
|
370
451
|
except Exception as e:
|
|
371
452
|
logger.error(f"Failed to import task {task.key}: {e}")
|
|
372
453
|
continue
|
|
373
454
|
|
|
374
|
-
|
|
375
455
|
def account(self) -> AccountResponse:
|
|
376
456
|
"""Get account information including instance limits and usage.
|
|
377
457
|
|
|
@@ -380,108 +460,62 @@ class Fleet:
|
|
|
380
460
|
"""
|
|
381
461
|
response = self.client.request("GET", "/v1/account")
|
|
382
462
|
return AccountResponse(**response.json())
|
|
383
|
-
|
|
463
|
+
|
|
384
464
|
def _create_verifier_from_data(
|
|
385
|
-
self,
|
|
386
|
-
verifier_id: str,
|
|
387
|
-
verifier_key: str,
|
|
388
|
-
verifier_code: str,
|
|
389
|
-
verifier_sha: str
|
|
465
|
+
self, verifier_id: str, verifier_key: str, verifier_code: str, verifier_sha: str
|
|
390
466
|
) -> "SyncVerifierFunction":
|
|
391
|
-
"""Create
|
|
392
|
-
|
|
467
|
+
"""Create a SyncVerifierFunction from verifier data.
|
|
468
|
+
|
|
393
469
|
Args:
|
|
394
470
|
verifier_id: The verifier ID
|
|
395
471
|
verifier_key: The verifier key
|
|
396
472
|
verifier_code: The verifier code
|
|
397
473
|
verifier_sha: The verifier SHA256
|
|
398
|
-
|
|
474
|
+
|
|
399
475
|
Returns:
|
|
400
|
-
|
|
476
|
+
SyncVerifierFunction created from the verifier code
|
|
401
477
|
"""
|
|
478
|
+
from .tasks import verifier_from_string
|
|
402
479
|
from .verifiers.verifier import SyncVerifierFunction
|
|
403
|
-
|
|
404
|
-
# Convert async verifier code to sync
|
|
405
|
-
if 'async def' in verifier_code:
|
|
406
|
-
verifier_code = verifier_code.replace('async def', 'def')
|
|
407
|
-
if 'await ' in verifier_code:
|
|
408
|
-
verifier_code = verifier_code.replace('await ', '')
|
|
409
|
-
|
|
410
|
-
# Check if this is a new format verifier (has before/after parameters)
|
|
411
|
-
if 'before: DatabaseSnapshot' in verifier_code and 'after: DatabaseSnapshot' in verifier_code:
|
|
412
|
-
# Convert new format to old format
|
|
413
|
-
verifier_code = convert_new_to_old_verifier(verifier_code)
|
|
414
|
-
# Update function name since wrapper adds _wrapper suffix
|
|
415
|
-
original_name = extract_function_name(verifier_code.split('\n')[0])
|
|
416
|
-
if original_name and original_name.endswith('_wrapper'):
|
|
417
|
-
function_name = original_name
|
|
418
|
-
else:
|
|
419
|
-
function_name = extract_function_name(verifier_code)
|
|
420
|
-
else:
|
|
421
|
-
# Extract function name from code
|
|
422
|
-
function_name = extract_function_name(verifier_code)
|
|
423
|
-
|
|
424
|
-
if not function_name:
|
|
425
|
-
raise ValueError(f"Could not extract function name from verifier {verifier_id}")
|
|
426
|
-
|
|
427
|
-
# Create a function object from the code
|
|
428
|
-
# Import necessary classes for the namespace
|
|
429
|
-
from .verifiers.db import IgnoreConfig, DatabaseSnapshot
|
|
430
480
|
|
|
431
|
-
#
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
'Environment': object, # Placeholder, will be provided at runtime
|
|
435
|
-
'IgnoreConfig': IgnoreConfig,
|
|
436
|
-
'DatabaseSnapshot': DatabaseSnapshot,
|
|
437
|
-
'TASK_FAILED_SCORE': 0,
|
|
438
|
-
'TASK_SUCCESSFUL_SCORE': 1,
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
# Execute the code to define the function
|
|
442
|
-
exec(verifier_code, namespace)
|
|
443
|
-
|
|
444
|
-
# Get the function object
|
|
445
|
-
if function_name not in namespace:
|
|
446
|
-
raise ValueError(f"Function {function_name} not found in verifier code")
|
|
447
|
-
|
|
448
|
-
func = namespace[function_name]
|
|
449
|
-
|
|
450
|
-
# Create and return AsyncVerifierFunction with SHA if available
|
|
451
|
-
# Since the function was created via exec, we need to provide the raw code
|
|
452
|
-
verifier_func = SyncVerifierFunction(
|
|
453
|
-
func=func,
|
|
454
|
-
key=verifier_key,
|
|
455
|
-
extra_requirements=[],
|
|
481
|
+
# Use verifier_from_string to create the verifier
|
|
482
|
+
verifier_func = verifier_from_string(
|
|
483
|
+
verifier_func=verifier_code,
|
|
456
484
|
verifier_id=verifier_id,
|
|
457
|
-
|
|
458
|
-
|
|
485
|
+
verifier_key=verifier_key,
|
|
486
|
+
sha256=verifier_sha,
|
|
459
487
|
)
|
|
460
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
|
+
|
|
461
495
|
# Store the original verifier code for reference
|
|
462
496
|
verifier_func._verifier_code = verifier_code
|
|
463
|
-
|
|
497
|
+
|
|
464
498
|
return verifier_func
|
|
465
|
-
|
|
499
|
+
|
|
466
500
|
def _load_verifier(self, verifier_id: str) -> "SyncVerifierFunction":
|
|
467
501
|
"""Load a verifier by ID and create an AsyncVerifierFunction.
|
|
468
|
-
|
|
502
|
+
|
|
469
503
|
Args:
|
|
470
504
|
verifier_id: The verifier ID to fetch
|
|
471
|
-
|
|
505
|
+
|
|
472
506
|
Returns:
|
|
473
507
|
AsyncVerifierFunction created from the verifier code
|
|
474
508
|
"""
|
|
475
509
|
# Fetch verifier from API
|
|
476
510
|
response = self.client.request("GET", f"/v1/verifier/{verifier_id}")
|
|
477
511
|
verifier_data = response.json()
|
|
478
|
-
|
|
512
|
+
|
|
479
513
|
# Use the common method to create verifier
|
|
480
514
|
return self._create_verifier_from_data(
|
|
481
515
|
verifier_id=verifier_id,
|
|
482
516
|
verifier_key=verifier_data["key"],
|
|
483
517
|
verifier_code=verifier_data["code"],
|
|
484
|
-
verifier_sha=verifier_data.get("sha256", "")
|
|
518
|
+
verifier_sha=verifier_data.get("sha256", ""),
|
|
485
519
|
)
|
|
486
520
|
|
|
487
521
|
|
|
@@ -526,23 +560,29 @@ def _execute_verifier_remote(
|
|
|
526
560
|
"timeout": timeout,
|
|
527
561
|
"region": "us-west-1", # TODO: make configurable
|
|
528
562
|
}
|
|
529
|
-
|
|
563
|
+
|
|
530
564
|
# Add bundle data only if upload is needed
|
|
531
565
|
if needs_upload:
|
|
532
566
|
bundle_b64 = base64.b64encode(bundle_data).decode("utf-8")
|
|
533
567
|
request_data["bundle"] = bundle_b64
|
|
534
|
-
|
|
568
|
+
|
|
535
569
|
# Debug logging
|
|
536
|
-
logger.debug(
|
|
570
|
+
logger.debug(
|
|
571
|
+
f"Sending verifier execute request: key={key}, sha256={bundle_sha[:8]}..., function_name={function_name}"
|
|
572
|
+
)
|
|
537
573
|
logger.debug(f"Request has bundle: {needs_upload}")
|
|
538
574
|
logger.debug(f"Using client with base_url: {client.base_url}")
|
|
539
575
|
logger.debug(f"Request data keys: {list(request_data.keys())}")
|
|
540
|
-
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
|
+
)
|
|
541
581
|
|
|
542
582
|
# Note: This should be called on the instance URL, not the orchestrator
|
|
543
583
|
# The instance has manager URLs for verifier execution
|
|
544
584
|
response = client.request("POST", "/v1/verifiers/execute", json=request_data)
|
|
545
|
-
|
|
585
|
+
|
|
546
586
|
# Debug the response
|
|
547
587
|
response_json = response.json()
|
|
548
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 (
|
|
@@ -14,6 +22,7 @@ from .._async.env.client import (
|
|
|
14
22
|
|
|
15
23
|
__all__ = [
|
|
16
24
|
"make",
|
|
25
|
+
"make_for_task",
|
|
17
26
|
"list_envs",
|
|
18
27
|
"list_regions",
|
|
19
28
|
"list_instances",
|
fleet/env/client.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from ..client import Fleet, SyncEnv
|
|
1
|
+
from ..client import Fleet, SyncEnv, Task
|
|
2
2
|
from ..models import Environment as EnvironmentModel, AccountResponse
|
|
3
3
|
from typing import List, Optional
|
|
4
4
|
|
|
@@ -7,6 +7,10 @@ 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:
|
|
11
|
+
return Fleet().make_for_task(task)
|
|
12
|
+
|
|
13
|
+
|
|
10
14
|
def list_envs() -> List[EnvironmentModel]:
|
|
11
15
|
return Fleet().list_envs()
|
|
12
16
|
|
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
|