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/_async/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 AsyncVerifierFunction
|
|
@@ -51,6 +50,7 @@ from .instance.client import ValidatorType
|
|
|
51
50
|
from .resources.base import Resource
|
|
52
51
|
from .resources.sqlite import AsyncSQLiteResource
|
|
53
52
|
from .resources.browser import AsyncBrowserResource
|
|
53
|
+
from ..resources.mcp import MCPResource
|
|
54
54
|
|
|
55
55
|
logger = logging.getLogger(__name__)
|
|
56
56
|
|
|
@@ -69,18 +69,18 @@ class AsyncEnv(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) -> AsyncInstanceClient:
|
|
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] = AsyncInstanceClient(
|
|
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 AsyncEnv(EnvironmentBase):
|
|
|
104
104
|
def browser(self, name: str = "cdp") -> AsyncBrowserResource:
|
|
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 AsyncEnv(EnvironmentBase):
|
|
|
125
130
|
return await _check_bundle_exists(self._load_client, bundle_hash)
|
|
126
131
|
|
|
127
132
|
async 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 await _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 AsyncFleet:
|
|
|
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 = AsyncWrapper(
|
|
175
182
|
api_key=api_key,
|
|
@@ -189,23 +196,27 @@ class AsyncFleet:
|
|
|
189
196
|
response = await self.client.request("GET", f"/v1/env/{env_key}")
|
|
190
197
|
return EnvironmentModel(**response.json())
|
|
191
198
|
|
|
192
|
-
async def make(
|
|
193
|
-
self, env_key: str, region: Optional[str] = None
|
|
194
|
-
) -> AsyncEnv:
|
|
199
|
+
async def make(self, env_key: str, region: Optional[str] = None) -> AsyncEnv:
|
|
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 = await 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 AsyncFleet:
|
|
|
213
224
|
await instance.instance.load()
|
|
214
225
|
return instance
|
|
215
226
|
|
|
227
|
+
async def make_for_task(self, task: Task) -> AsyncEnv:
|
|
228
|
+
return await self.make(env_key=f"{task.env_id}:{task.version}")
|
|
229
|
+
|
|
216
230
|
async def instances(
|
|
217
231
|
self, status: Optional[str] = None, region: Optional[str] = None
|
|
218
232
|
) -> List[AsyncEnv]:
|
|
@@ -247,44 +261,124 @@ class AsyncFleet:
|
|
|
247
261
|
async def delete(self, instance_id: str) -> InstanceResponse:
|
|
248
262
|
return await _delete_instance(self.client, instance_id)
|
|
249
263
|
|
|
250
|
-
async def
|
|
251
|
-
""
|
|
252
|
-
|
|
264
|
+
async def load_tasks_from_file(self, filename: str) -> List[Task]:
|
|
265
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
266
|
+
tasks_data = f.read()
|
|
267
|
+
|
|
268
|
+
return self.load_task_array_from_string(tasks_data)
|
|
269
|
+
|
|
270
|
+
async def load_task_array_from_string(
|
|
271
|
+
self, serialized_tasks: List[Dict]
|
|
272
|
+
) -> List[Task]:
|
|
273
|
+
tasks = []
|
|
274
|
+
|
|
275
|
+
json_tasks = json.loads(serialized_tasks)
|
|
276
|
+
for json_task in json_tasks:
|
|
277
|
+
parsed_task = self.load_task_from_json(json_task)
|
|
278
|
+
tasks.append(parsed_task)
|
|
279
|
+
return tasks
|
|
280
|
+
|
|
281
|
+
async def load_task_from_string(self, task_string: str) -> Task:
|
|
282
|
+
task_json = json.loads(task_string)
|
|
283
|
+
return self.load_task_from_json(task_json)
|
|
284
|
+
|
|
285
|
+
async def load_task_from_json(self, task_json: Dict) -> Task:
|
|
286
|
+
try:
|
|
287
|
+
if "verifier_id" in task_json and task_json["verifier_id"]:
|
|
288
|
+
verifier = self._create_verifier_from_data(
|
|
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", ""),
|
|
293
|
+
)
|
|
294
|
+
except Exception as e:
|
|
295
|
+
logger.warning(f"Failed to create verifier {task_json['key']}: {e}")
|
|
296
|
+
|
|
297
|
+
task = Task(
|
|
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
|
|
305
|
+
verifier=verifier, # Use created verifier or None
|
|
306
|
+
metadata=task_json.get("metadata", {}), # Default empty metadata
|
|
307
|
+
)
|
|
308
|
+
return task
|
|
309
|
+
|
|
310
|
+
async 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.
|
|
318
|
+
|
|
253
319
|
Args:
|
|
254
320
|
env_key: Optional environment key to filter tasks by
|
|
255
|
-
|
|
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
|
+
|
|
256
325
|
Returns:
|
|
257
326
|
List[Task] containing Task objects
|
|
258
327
|
"""
|
|
259
328
|
params = {}
|
|
260
329
|
if env_key is not None:
|
|
261
330
|
params["env_key"] = env_key
|
|
262
|
-
|
|
331
|
+
if keys is not None:
|
|
332
|
+
params["task_keys"] = keys
|
|
333
|
+
if team_id is not None:
|
|
334
|
+
params["team_id"] = team_id
|
|
335
|
+
|
|
263
336
|
response = await self.client.request("GET", "/v1/tasks", params=params)
|
|
264
337
|
task_list_response = TaskListResponse(**response.json())
|
|
265
|
-
|
|
338
|
+
|
|
266
339
|
# Transform TaskResponse objects to Task objects
|
|
267
340
|
tasks = []
|
|
268
341
|
for task_response in task_list_response.tasks:
|
|
269
342
|
# Create verifier function if verifier data is present
|
|
270
343
|
verifier = None
|
|
271
344
|
verifier_func = task_response.verifier_func
|
|
272
|
-
|
|
345
|
+
|
|
273
346
|
if task_response.verifier:
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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 = await 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 = await 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
|
+
|
|
288
382
|
task = Task(
|
|
289
383
|
key=task_response.key,
|
|
290
384
|
prompt=task_response.prompt,
|
|
@@ -294,19 +388,25 @@ class AsyncFleet:
|
|
|
294
388
|
env_variables=task_response.env_variables or {},
|
|
295
389
|
verifier_func=verifier_func, # Set verifier code
|
|
296
390
|
verifier=verifier, # Use created verifier or None
|
|
297
|
-
metadata={} # Default empty metadata
|
|
391
|
+
metadata={}, # Default empty metadata
|
|
298
392
|
)
|
|
299
393
|
tasks.append(task)
|
|
300
|
-
|
|
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
|
+
|
|
301
399
|
return tasks
|
|
302
400
|
|
|
303
|
-
async def export_tasks(
|
|
401
|
+
async def export_tasks(
|
|
402
|
+
self, env_key: Optional[str] = None, filename: Optional[str] = None
|
|
403
|
+
):
|
|
304
404
|
"""Export tasks for the authenticated team, optionally filtered by environment.
|
|
305
|
-
|
|
405
|
+
|
|
306
406
|
Args:
|
|
307
407
|
env_key: Optional environment key to filter tasks by
|
|
308
408
|
filename: Optional filename to write tasks to. If not provided, defaults to 'tasks.json' or 'tasks_{env_key}.json'
|
|
309
|
-
|
|
409
|
+
|
|
310
410
|
Returns:
|
|
311
411
|
str: Path to the exported file if tasks were written, None if no tasks found
|
|
312
412
|
"""
|
|
@@ -318,38 +418,38 @@ class AsyncFleet:
|
|
|
318
418
|
filename = f"tasks_{env_key}.json"
|
|
319
419
|
else:
|
|
320
420
|
filename = "tasks.json"
|
|
321
|
-
|
|
421
|
+
|
|
322
422
|
# Convert tasks to serializable format
|
|
323
423
|
tasks_data = []
|
|
324
424
|
for task in tasks:
|
|
325
425
|
task_dict = task.model_dump()
|
|
326
426
|
# Remove non-serializable verifier object, keep verifier_func (code string)
|
|
327
|
-
if
|
|
328
|
-
task_dict.pop(
|
|
427
|
+
if "verifier" in task_dict:
|
|
428
|
+
task_dict.pop("verifier")
|
|
329
429
|
tasks_data.append(task_dict)
|
|
330
|
-
|
|
430
|
+
|
|
331
431
|
# Write to JSON file
|
|
332
|
-
with open(filename,
|
|
432
|
+
with open(filename, "w", encoding="utf-8") as f:
|
|
333
433
|
json.dump(tasks_data, f, indent=2, default=str)
|
|
334
|
-
|
|
434
|
+
|
|
335
435
|
logger.info(f"Exported {len(tasks)} tasks to {filename}")
|
|
336
436
|
return filename
|
|
337
437
|
else:
|
|
338
438
|
logger.info("No tasks found to export")
|
|
339
439
|
return None
|
|
340
|
-
|
|
440
|
+
|
|
341
441
|
async def import_tasks(self, filename: str):
|
|
342
442
|
"""Import tasks from a JSON file.
|
|
343
|
-
|
|
443
|
+
|
|
344
444
|
Args:
|
|
345
445
|
filename: Path to the JSON file of Task objects to import
|
|
346
|
-
|
|
446
|
+
|
|
347
447
|
Returns:
|
|
348
448
|
List[Task] containing imported Task objects
|
|
349
449
|
"""
|
|
350
|
-
with open(filename,
|
|
450
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
351
451
|
tasks_data = json.load(f)
|
|
352
|
-
|
|
452
|
+
|
|
353
453
|
# Create tasks from the loaded data
|
|
354
454
|
tasks = []
|
|
355
455
|
for task_data in tasks_data:
|
|
@@ -366,12 +466,13 @@ class AsyncFleet:
|
|
|
366
466
|
env_variables=task.env_variables or {},
|
|
367
467
|
)
|
|
368
468
|
try:
|
|
369
|
-
response = await self.client.request(
|
|
469
|
+
response = await self.client.request(
|
|
470
|
+
"POST", "/v1/tasks", json=payload.model_dump()
|
|
471
|
+
)
|
|
370
472
|
except Exception as e:
|
|
371
473
|
logger.error(f"Failed to import task {task.key}: {e}")
|
|
372
474
|
continue
|
|
373
475
|
|
|
374
|
-
|
|
375
476
|
async def account(self) -> AccountResponse:
|
|
376
477
|
"""Get account information including instance limits and usage.
|
|
377
478
|
|
|
@@ -380,102 +481,62 @@ class AsyncFleet:
|
|
|
380
481
|
"""
|
|
381
482
|
response = await self.client.request("GET", "/v1/account")
|
|
382
483
|
return AccountResponse(**response.json())
|
|
383
|
-
|
|
484
|
+
|
|
384
485
|
async def _create_verifier_from_data(
|
|
385
|
-
self,
|
|
386
|
-
verifier_id: str,
|
|
387
|
-
verifier_key: str,
|
|
388
|
-
verifier_code: str,
|
|
389
|
-
verifier_sha: str
|
|
486
|
+
self, verifier_id: str, verifier_key: str, verifier_code: str, verifier_sha: str
|
|
390
487
|
) -> "AsyncVerifierFunction":
|
|
391
488
|
"""Create an AsyncVerifierFunction from verifier data.
|
|
392
|
-
|
|
489
|
+
|
|
393
490
|
Args:
|
|
394
491
|
verifier_id: The verifier ID
|
|
395
492
|
verifier_key: The verifier key
|
|
396
493
|
verifier_code: The verifier code
|
|
397
494
|
verifier_sha: The verifier SHA256
|
|
398
|
-
|
|
495
|
+
|
|
399
496
|
Returns:
|
|
400
497
|
AsyncVerifierFunction created from the verifier code
|
|
401
498
|
"""
|
|
499
|
+
from ..tasks import verifier_from_string
|
|
402
500
|
from .verifiers.verifier import AsyncVerifierFunction
|
|
403
501
|
|
|
404
|
-
#
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
verifier_code = convert_new_to_old_verifier(verifier_code)
|
|
408
|
-
# Update function name since wrapper adds _wrapper suffix
|
|
409
|
-
original_name = extract_function_name(verifier_code.split('\n')[0])
|
|
410
|
-
if original_name and original_name.endswith('_wrapper'):
|
|
411
|
-
function_name = original_name
|
|
412
|
-
else:
|
|
413
|
-
function_name = extract_function_name(verifier_code)
|
|
414
|
-
else:
|
|
415
|
-
# Extract function name from code
|
|
416
|
-
function_name = extract_function_name(verifier_code)
|
|
417
|
-
|
|
418
|
-
if not function_name:
|
|
419
|
-
raise ValueError(f"Could not extract function name from verifier {verifier_id}")
|
|
420
|
-
|
|
421
|
-
# Create a function object from the code
|
|
422
|
-
# Import necessary classes for the namespace
|
|
423
|
-
from ..verifiers.db import IgnoreConfig, DatabaseSnapshot
|
|
424
|
-
|
|
425
|
-
# Create a namespace for the function
|
|
426
|
-
namespace = {
|
|
427
|
-
'__builtins__': __builtins__,
|
|
428
|
-
'Environment': object, # Placeholder, will be provided at runtime
|
|
429
|
-
'IgnoreConfig': IgnoreConfig,
|
|
430
|
-
'DatabaseSnapshot': DatabaseSnapshot,
|
|
431
|
-
'TASK_FAILED_SCORE': 0,
|
|
432
|
-
'TASK_SUCCESSFUL_SCORE': 1,
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
# Execute the code to define the function
|
|
436
|
-
exec(verifier_code, namespace)
|
|
437
|
-
|
|
438
|
-
# Get the function object
|
|
439
|
-
if function_name not in namespace:
|
|
440
|
-
raise ValueError(f"Function {function_name} not found in verifier code")
|
|
441
|
-
|
|
442
|
-
func = namespace[function_name]
|
|
443
|
-
|
|
444
|
-
# Create and return AsyncVerifierFunction with SHA if available
|
|
445
|
-
# Since the function was created via exec, we need to provide the raw code
|
|
446
|
-
verifier_func = AsyncVerifierFunction(
|
|
447
|
-
func=func,
|
|
448
|
-
key=verifier_key,
|
|
449
|
-
extra_requirements=[],
|
|
502
|
+
# Use verifier_from_string to create the verifier
|
|
503
|
+
verifier_func = verifier_from_string(
|
|
504
|
+
verifier_func=verifier_code,
|
|
450
505
|
verifier_id=verifier_id,
|
|
451
|
-
|
|
452
|
-
|
|
506
|
+
verifier_key=verifier_key,
|
|
507
|
+
sha256=verifier_sha,
|
|
453
508
|
)
|
|
454
509
|
|
|
510
|
+
# Ensure we return an AsyncVerifierFunction
|
|
511
|
+
if not isinstance(verifier_func, AsyncVerifierFunction):
|
|
512
|
+
raise TypeError(
|
|
513
|
+
f"Expected AsyncVerifierFunction but got {type(verifier_func).__name__}"
|
|
514
|
+
)
|
|
515
|
+
|
|
455
516
|
# Store the original verifier code for reference
|
|
456
517
|
verifier_func._verifier_code = verifier_code
|
|
457
|
-
|
|
518
|
+
|
|
458
519
|
return verifier_func
|
|
459
|
-
|
|
520
|
+
|
|
460
521
|
async def _load_verifier(self, verifier_id: str) -> "AsyncVerifierFunction":
|
|
461
522
|
"""Load a verifier by ID and create an AsyncVerifierFunction.
|
|
462
|
-
|
|
523
|
+
|
|
463
524
|
Args:
|
|
464
525
|
verifier_id: The verifier ID to fetch
|
|
465
|
-
|
|
526
|
+
|
|
466
527
|
Returns:
|
|
467
528
|
AsyncVerifierFunction created from the verifier code
|
|
468
529
|
"""
|
|
469
530
|
# Fetch verifier from API
|
|
470
531
|
response = await self.client.request("GET", f"/v1/verifier/{verifier_id}")
|
|
471
532
|
verifier_data = response.json()
|
|
472
|
-
|
|
533
|
+
|
|
473
534
|
# Use the common method to create verifier
|
|
474
535
|
return await self._create_verifier_from_data(
|
|
475
536
|
verifier_id=verifier_id,
|
|
476
537
|
verifier_key=verifier_data["key"],
|
|
477
538
|
verifier_code=verifier_data["code"],
|
|
478
|
-
verifier_sha=verifier_data.get("sha256", "")
|
|
539
|
+
verifier_sha=verifier_data.get("sha256", ""),
|
|
479
540
|
)
|
|
480
541
|
|
|
481
542
|
|
|
@@ -520,23 +581,29 @@ async def _execute_verifier_remote(
|
|
|
520
581
|
"timeout": timeout,
|
|
521
582
|
"region": "us-west-1", # TODO: make configurable
|
|
522
583
|
}
|
|
523
|
-
|
|
584
|
+
|
|
524
585
|
# Add bundle data only if upload is needed
|
|
525
586
|
if needs_upload:
|
|
526
587
|
bundle_b64 = base64.b64encode(bundle_data).decode("utf-8")
|
|
527
588
|
request_data["bundle"] = bundle_b64
|
|
528
|
-
|
|
589
|
+
|
|
529
590
|
# Debug logging
|
|
530
|
-
logger.debug(
|
|
591
|
+
logger.debug(
|
|
592
|
+
f"Sending verifier execute request: key={key}, sha256={bundle_sha[:8]}..., function_name={function_name}"
|
|
593
|
+
)
|
|
531
594
|
logger.debug(f"Request has bundle: {needs_upload}")
|
|
532
595
|
logger.debug(f"Using client with base_url: {client.base_url}")
|
|
533
596
|
logger.debug(f"Request data keys: {list(request_data.keys())}")
|
|
534
|
-
logger.debug(
|
|
597
|
+
logger.debug(
|
|
598
|
+
f"Bundle size: {len(request_data.get('bundle', ''))} chars"
|
|
599
|
+
if "bundle" in request_data
|
|
600
|
+
else "No bundle"
|
|
601
|
+
)
|
|
535
602
|
|
|
536
603
|
# Note: This should be called on the instance URL, not the orchestrator
|
|
537
604
|
# The instance has manager URLs for verifier execution
|
|
538
605
|
response = await client.request("POST", "/v1/verifiers/execute", json=request_data)
|
|
539
|
-
|
|
606
|
+
|
|
540
607
|
# Debug the response
|
|
541
608
|
response_json = response.json()
|
|
542
609
|
logger.debug(f"Verifier execute response: {response_json}")
|
fleet/_async/env/client.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from ..client import AsyncFleet, AsyncEnv
|
|
1
|
+
from ..client import AsyncFleet, AsyncEnv, Task
|
|
2
2
|
from ...models import Environment as EnvironmentModel, AccountResponse
|
|
3
3
|
from typing import List, Optional
|
|
4
4
|
|
|
@@ -7,6 +7,10 @@ async def make_async(env_key: str, region: Optional[str] = None) -> AsyncEnv:
|
|
|
7
7
|
return await AsyncFleet().make(env_key, region=region)
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
async def make_for_task_async(task: Task) -> AsyncEnv:
|
|
11
|
+
return await AsyncFleet().make_for_task(task)
|
|
12
|
+
|
|
13
|
+
|
|
10
14
|
async def list_envs_async() -> List[EnvironmentModel]:
|
|
11
15
|
return await AsyncFleet().list_envs()
|
|
12
16
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from .client import AsyncFleet
|
|
6
|
+
from ..config import DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_default_client: Optional[AsyncFleet] = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_client() -> AsyncFleet:
|
|
13
|
+
"""Get the global default AsyncFleet client, creating it if needed."""
|
|
14
|
+
global _default_client
|
|
15
|
+
if _default_client is None:
|
|
16
|
+
_default_client = AsyncFleet()
|
|
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
|
+
) -> AsyncFleet:
|
|
26
|
+
"""Configure the global default AsyncFleet client.
|
|
27
|
+
|
|
28
|
+
Returns the configured client instance.
|
|
29
|
+
"""
|
|
30
|
+
global _default_client
|
|
31
|
+
_default_client = AsyncFleet(
|
|
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/_async/instance/client.py
CHANGED
|
@@ -130,7 +130,7 @@ class AsyncInstanceClient:
|
|
|
130
130
|
|
|
131
131
|
async def _load_resources(self) -> None:
|
|
132
132
|
if self._resources is None:
|
|
133
|
-
response = await self.client.request("GET", "/resources")
|
|
133
|
+
response = await self.client.request("GET", "/resources", timeout=1.0)
|
|
134
134
|
if response.status_code != 200:
|
|
135
135
|
self._resources = []
|
|
136
136
|
return
|