fleet-python 0.2.46__py3-none-any.whl → 0.2.48__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.
- fleet/_async/client.py +3 -0
- fleet/_async/models.py +6 -0
- fleet/_async/tasks.py +70 -7
- fleet/_async/verifiers/verifier.py +78 -66
- fleet/client.py +62 -72
- fleet/instance/client.py +3 -1
- fleet/models.py +7 -3
- fleet/resources/sqlite.py +9 -3
- fleet/tasks.py +81 -31
- fleet/verifiers/parse.py +3 -3
- fleet/verifiers/verifier.py +84 -83
- {fleet_python-0.2.46.dist-info → fleet_python-0.2.48.dist-info}/METADATA +1 -1
- {fleet_python-0.2.46.dist-info → fleet_python-0.2.48.dist-info}/RECORD +16 -16
- {fleet_python-0.2.46.dist-info → fleet_python-0.2.48.dist-info}/WHEEL +0 -0
- {fleet_python-0.2.46.dist-info → fleet_python-0.2.48.dist-info}/licenses/LICENSE +0 -0
- {fleet_python-0.2.46.dist-info → fleet_python-0.2.48.dist-info}/top_level.txt +0 -0
fleet/_async/client.py
CHANGED
|
@@ -379,6 +379,7 @@ class AsyncFleet:
|
|
|
379
379
|
keys: Optional[List[str]] = None,
|
|
380
380
|
version: Optional[str] = None,
|
|
381
381
|
team_id: Optional[str] = None,
|
|
382
|
+
project_key: Optional[str] = None,
|
|
382
383
|
) -> List[Task]:
|
|
383
384
|
"""Load tasks for the authenticated team, with optional filtering.
|
|
384
385
|
|
|
@@ -398,6 +399,8 @@ class AsyncFleet:
|
|
|
398
399
|
params["task_keys"] = keys
|
|
399
400
|
if team_id is not None:
|
|
400
401
|
params["team_id"] = team_id
|
|
402
|
+
if project_key is not None:
|
|
403
|
+
params["project_key"] = project_key
|
|
401
404
|
|
|
402
405
|
response = await self.client.request("GET", "/v1/tasks", params=params)
|
|
403
406
|
task_list_response = TaskListResponse(**response.json())
|
fleet/_async/models.py
CHANGED
|
@@ -273,6 +273,12 @@ class VerifiersExecuteResponse(BaseModel):
|
|
|
273
273
|
result: Optional[Any] = Field(
|
|
274
274
|
None, description="The return value of the function", title="Result"
|
|
275
275
|
)
|
|
276
|
+
verifier_id: Optional[str] = Field(
|
|
277
|
+
None, description="ID of the verifier", title="Verifier Id"
|
|
278
|
+
)
|
|
279
|
+
execution_id: Optional[str] = Field(
|
|
280
|
+
None, description="ID of the execution record", title="Execution Id"
|
|
281
|
+
)
|
|
276
282
|
error: Optional[Dict[str, Any]] = Field(
|
|
277
283
|
None, description="Error details if verification failed", title="Error"
|
|
278
284
|
)
|
fleet/_async/tasks.py
CHANGED
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from datetime import datetime
|
|
6
|
-
from typing import Any, Dict, Optional, List
|
|
6
|
+
from typing import Any, Dict, Optional, List, TYPE_CHECKING
|
|
7
7
|
|
|
8
8
|
from pydantic import BaseModel, Field, validator
|
|
9
9
|
|
|
10
10
|
# Import the shared VerifierFunction type that works for both async and sync
|
|
11
11
|
from fleet.types import VerifierFunction
|
|
12
12
|
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from fleet._async.models import VerifiersExecuteResponse
|
|
15
|
+
|
|
13
16
|
|
|
14
17
|
class Task(BaseModel):
|
|
15
18
|
"""A task model representing a single task in the Fleet system."""
|
|
@@ -24,9 +27,9 @@ class Task(BaseModel):
|
|
|
24
27
|
version: Optional[str] = Field(None, description="Task version")
|
|
25
28
|
verifier_func: Optional[str] = Field(None, description="Verifier function code")
|
|
26
29
|
verifier: Optional[Any] = Field(
|
|
27
|
-
None,
|
|
30
|
+
None,
|
|
28
31
|
description="Verifier function with decorator (async or sync)",
|
|
29
|
-
exclude=True # Exclude from JSON serialization
|
|
32
|
+
exclude=True, # Exclude from JSON serialization
|
|
30
33
|
)
|
|
31
34
|
verifier_id: Optional[str] = Field(None, description="Verifier identifier")
|
|
32
35
|
verifier_sha: Optional[str] = Field(None, description="Verifier SHA256 hash")
|
|
@@ -68,7 +71,7 @@ class Task(BaseModel):
|
|
|
68
71
|
# If verifier doesn't exist but verifier_func does, rebuild it
|
|
69
72
|
if not self.verifier and self.verifier_func:
|
|
70
73
|
self._rebuild_verifier()
|
|
71
|
-
|
|
74
|
+
|
|
72
75
|
if self.verifier:
|
|
73
76
|
import asyncio
|
|
74
77
|
import inspect
|
|
@@ -102,7 +105,7 @@ class Task(BaseModel):
|
|
|
102
105
|
# If verifier doesn't exist but verifier_func does, rebuild it
|
|
103
106
|
if not self.verifier and self.verifier_func:
|
|
104
107
|
self._rebuild_verifier()
|
|
105
|
-
|
|
108
|
+
|
|
106
109
|
if self.verifier:
|
|
107
110
|
result = self.verifier.remote(*args, **kwargs)
|
|
108
111
|
# If it's a coroutine, await it
|
|
@@ -115,6 +118,65 @@ class Task(BaseModel):
|
|
|
115
118
|
else:
|
|
116
119
|
raise ValueError("No verifier function found for this task")
|
|
117
120
|
|
|
121
|
+
async def verify_detailed_async(
|
|
122
|
+
self, *args, **kwargs
|
|
123
|
+
) -> "VerifiersExecuteResponse":
|
|
124
|
+
"""Verify the task and return the full execute response model.
|
|
125
|
+
|
|
126
|
+
For async environments, awaits the async verifier.
|
|
127
|
+
Works with both sync and async verifiers in async contexts.
|
|
128
|
+
"""
|
|
129
|
+
# If verifier doesn't exist but verifier_func does, rebuild it
|
|
130
|
+
if not self.verifier and self.verifier_func:
|
|
131
|
+
self._rebuild_verifier()
|
|
132
|
+
|
|
133
|
+
if self.verifier:
|
|
134
|
+
result = self.verifier.remote_with_response(*args, **kwargs)
|
|
135
|
+
# If it's a coroutine, await it
|
|
136
|
+
import inspect
|
|
137
|
+
|
|
138
|
+
if inspect.iscoroutine(result):
|
|
139
|
+
return await result
|
|
140
|
+
else:
|
|
141
|
+
return result
|
|
142
|
+
else:
|
|
143
|
+
raise ValueError("No verifier function found for this task")
|
|
144
|
+
|
|
145
|
+
def verify_detailed(self, env, *args, **kwargs) -> "VerifiersExecuteResponse":
|
|
146
|
+
"""Verify the task and return the full execute response model (sync version).
|
|
147
|
+
|
|
148
|
+
For sync environments, calls the sync verifier directly.
|
|
149
|
+
For async verifiers, automatically runs them with asyncio.run().
|
|
150
|
+
"""
|
|
151
|
+
# If verifier doesn't exist but verifier_func does, rebuild it
|
|
152
|
+
if not self.verifier and self.verifier_func:
|
|
153
|
+
self._rebuild_verifier()
|
|
154
|
+
|
|
155
|
+
if self.verifier:
|
|
156
|
+
import asyncio
|
|
157
|
+
import inspect
|
|
158
|
+
|
|
159
|
+
# Check if verifier has remote_with_response method (for decorated verifiers)
|
|
160
|
+
result = self.verifier.remote_with_response(env, *args, **kwargs)
|
|
161
|
+
|
|
162
|
+
# If the result is a coroutine, we need to run it
|
|
163
|
+
if inspect.iscoroutine(result):
|
|
164
|
+
# Check if we're already in an event loop
|
|
165
|
+
try:
|
|
166
|
+
asyncio.get_running_loop()
|
|
167
|
+
# We're in an async context, can't use asyncio.run()
|
|
168
|
+
raise RuntimeError(
|
|
169
|
+
"Cannot run async verifier in sync mode while event loop is running. "
|
|
170
|
+
"Use await task.verify_detailed_async() instead."
|
|
171
|
+
)
|
|
172
|
+
except RuntimeError:
|
|
173
|
+
# No event loop running, safe to use asyncio.run()
|
|
174
|
+
return asyncio.run(result)
|
|
175
|
+
else:
|
|
176
|
+
return result
|
|
177
|
+
else:
|
|
178
|
+
raise ValueError("No verifier function found for this task")
|
|
179
|
+
|
|
118
180
|
def _rebuild_verifier(self):
|
|
119
181
|
"""Rebuild the verifier from verifier_func string if it exists."""
|
|
120
182
|
if self.verifier_func:
|
|
@@ -127,7 +189,7 @@ class Task(BaseModel):
|
|
|
127
189
|
sha256=self.verifier_sha or "",
|
|
128
190
|
)
|
|
129
191
|
self.verifier = verifier
|
|
130
|
-
|
|
192
|
+
|
|
131
193
|
async def make_env(self, region: Optional[str] = None):
|
|
132
194
|
"""Create an environment instance for this task's environment.
|
|
133
195
|
|
|
@@ -214,6 +276,7 @@ async def load_tasks(
|
|
|
214
276
|
keys: Optional[List[str]] = None,
|
|
215
277
|
version: Optional[str] = None,
|
|
216
278
|
team_id: Optional[str] = None,
|
|
279
|
+
project_key: Optional[str] = None,
|
|
217
280
|
) -> List[Task]:
|
|
218
281
|
"""Convenience function to load tasks with optional filtering.
|
|
219
282
|
|
|
@@ -233,7 +296,7 @@ async def load_tasks(
|
|
|
233
296
|
|
|
234
297
|
client = get_client()
|
|
235
298
|
return await client.load_tasks(
|
|
236
|
-
env_key=env_key, keys=keys, version=version, team_id=team_id
|
|
299
|
+
env_key=env_key, keys=keys, version=version, team_id=team_id, project_key=project_key
|
|
237
300
|
)
|
|
238
301
|
|
|
239
302
|
|
|
@@ -16,6 +16,7 @@ from typing import Any, Callable, Dict, Optional, List, TypeVar, Tuple
|
|
|
16
16
|
|
|
17
17
|
from .bundler import FunctionBundler
|
|
18
18
|
from ..client import AsyncEnv
|
|
19
|
+
from ...models import VerifiersExecuteResponse
|
|
19
20
|
|
|
20
21
|
logger = logging.getLogger(__name__)
|
|
21
22
|
|
|
@@ -152,72 +153,15 @@ class AsyncVerifierFunction:
|
|
|
152
153
|
|
|
153
154
|
async def remote(self, env: AsyncEnv, *args, **kwargs) -> float:
|
|
154
155
|
"""Remote execution of the verifier function with SHA-based bundle caching."""
|
|
155
|
-
|
|
156
|
-
# if self._is_async:
|
|
157
|
-
# raise NotImplementedError(
|
|
158
|
-
# f"Async verifier '{self.key}' cannot be executed remotely. "
|
|
159
|
-
# "The remote execution environment only supports synchronous functions. "
|
|
160
|
-
# "Please provide a synchronous version of your verifier."
|
|
161
|
-
# )
|
|
156
|
+
response = await self.remote_with_response(env, *args, **kwargs)
|
|
162
157
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if needs_upload:
|
|
172
|
-
# Need to upload bundle to S3
|
|
173
|
-
logger.info(f"Uploading bundle {bundle_sha[:8]}... for {self.key}")
|
|
174
|
-
bundle_data, _ = self._get_or_create_bundle()
|
|
175
|
-
|
|
176
|
-
response = await env.execute_verifier_remote(
|
|
177
|
-
bundle_data=bundle_data,
|
|
178
|
-
bundle_sha=bundle_sha,
|
|
179
|
-
key=self.key,
|
|
180
|
-
function_name=self.func.__name__,
|
|
181
|
-
args=args,
|
|
182
|
-
args_array=args_array,
|
|
183
|
-
kwargs=kwargs,
|
|
184
|
-
needs_upload=True,
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
logger.debug(f"Bundle {bundle_sha[:8]}... uploaded successfully")
|
|
188
|
-
|
|
189
|
-
else:
|
|
190
|
-
# Bundle already available - execute without upload
|
|
191
|
-
logger.info(
|
|
192
|
-
f"Executing cached bundle {bundle_sha[:8]}... for {self.key}"
|
|
193
|
-
)
|
|
194
|
-
bundle_data, _ = self._get_or_create_bundle()
|
|
195
|
-
|
|
196
|
-
response = await env.execute_verifier_remote(
|
|
197
|
-
bundle_data=bundle_data or b"", # Empty if using server-side bundle
|
|
198
|
-
bundle_sha=bundle_sha,
|
|
199
|
-
key=self.key,
|
|
200
|
-
function_name=self.func.__name__,
|
|
201
|
-
args=args,
|
|
202
|
-
args_array=args_array,
|
|
203
|
-
kwargs=kwargs,
|
|
204
|
-
needs_upload=False, # Don't upload, just execute
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
# Handle response
|
|
208
|
-
if response.stdout:
|
|
209
|
-
print(response.stdout)
|
|
210
|
-
if response.success:
|
|
211
|
-
return self._process_result(response.result)
|
|
212
|
-
else:
|
|
213
|
-
self._raise_remote_error(response.error)
|
|
214
|
-
|
|
215
|
-
except Exception as e:
|
|
216
|
-
logger.error(f"Remote execution failed for {self.key}: {e}")
|
|
217
|
-
# If it's an HTTP error, try to get more details
|
|
218
|
-
if hasattr(e, "response") and hasattr(e.response, "text"):
|
|
219
|
-
logger.error(f"Server response: {e.response.text}")
|
|
220
|
-
raise
|
|
158
|
+
# Handle response
|
|
159
|
+
if response.stdout:
|
|
160
|
+
print(response.stdout)
|
|
161
|
+
if response.success:
|
|
162
|
+
return self._process_result(response.result)
|
|
163
|
+
else:
|
|
164
|
+
self._raise_remote_error(response.error)
|
|
221
165
|
|
|
222
166
|
def _process_result(self, result: Any) -> float:
|
|
223
167
|
"""Process remote execution result, handling different return types."""
|
|
@@ -257,7 +201,7 @@ Remote traceback:
|
|
|
257
201
|
try:
|
|
258
202
|
exception_class = getattr(__builtins__, error_type, RuntimeError)
|
|
259
203
|
raise exception_class(full_message)
|
|
260
|
-
except:
|
|
204
|
+
except Exception:
|
|
261
205
|
raise RuntimeError(full_message)
|
|
262
206
|
|
|
263
207
|
def _get_env_id(self, env: AsyncEnv) -> str:
|
|
@@ -280,6 +224,74 @@ Remote traceback:
|
|
|
280
224
|
or "not found" in error_msg
|
|
281
225
|
)
|
|
282
226
|
|
|
227
|
+
async def remote_with_response(
|
|
228
|
+
self, env: "AsyncEnv", *args, **kwargs
|
|
229
|
+
) -> "VerifiersExecuteResponse":
|
|
230
|
+
"""Remote execution of the verifier function that returns the full response model."""
|
|
231
|
+
args_array = list(args)
|
|
232
|
+
args_array.append({"env": env.instance_id})
|
|
233
|
+
args = tuple(args_array)
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
# Check if bundle needs to be uploaded
|
|
237
|
+
bundle_sha, needs_upload = await self._check_bundle_status(env)
|
|
238
|
+
|
|
239
|
+
if needs_upload:
|
|
240
|
+
# Need to upload bundle to S3
|
|
241
|
+
logger.info(f"Uploading bundle {bundle_sha[:8]}... for {self.key}")
|
|
242
|
+
bundle_data, _ = self._get_or_create_bundle()
|
|
243
|
+
|
|
244
|
+
response = await env.execute_verifier_remote(
|
|
245
|
+
bundle_data=bundle_data,
|
|
246
|
+
bundle_sha=bundle_sha,
|
|
247
|
+
key=self.key,
|
|
248
|
+
function_name=self.func.__name__,
|
|
249
|
+
args=args,
|
|
250
|
+
args_array=args_array,
|
|
251
|
+
kwargs=kwargs,
|
|
252
|
+
needs_upload=True,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
logger.debug(f"Bundle {bundle_sha[:8]}... uploaded successfully")
|
|
256
|
+
|
|
257
|
+
else:
|
|
258
|
+
# Bundle already available - execute without upload
|
|
259
|
+
logger.info(f"Bundle {bundle_sha[:8]}... already cached for {self.key}")
|
|
260
|
+
response = await env.execute_verifier_remote(
|
|
261
|
+
bundle_data=b"", # Empty bundle since it's cached
|
|
262
|
+
bundle_sha=bundle_sha,
|
|
263
|
+
key=self.key,
|
|
264
|
+
function_name=self.func.__name__,
|
|
265
|
+
args=args,
|
|
266
|
+
args_array=args_array,
|
|
267
|
+
kwargs=kwargs,
|
|
268
|
+
needs_upload=False,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
return response
|
|
272
|
+
|
|
273
|
+
except Exception as e:
|
|
274
|
+
# Check if error indicates bundle not found and retry with upload
|
|
275
|
+
if self._is_bundle_not_found_error(e) and not needs_upload:
|
|
276
|
+
logger.info(
|
|
277
|
+
f"Bundle {bundle_sha[:8]}... not found on server, uploading..."
|
|
278
|
+
)
|
|
279
|
+
bundle_data, _ = self._get_or_create_bundle()
|
|
280
|
+
response = await env.execute_verifier_remote(
|
|
281
|
+
bundle_data=bundle_data,
|
|
282
|
+
bundle_sha=bundle_sha,
|
|
283
|
+
key=self.key,
|
|
284
|
+
function_name=self.func.__name__,
|
|
285
|
+
args=args,
|
|
286
|
+
args_array=args_array,
|
|
287
|
+
kwargs=kwargs,
|
|
288
|
+
needs_upload=True,
|
|
289
|
+
)
|
|
290
|
+
return response
|
|
291
|
+
else:
|
|
292
|
+
logger.error(f"Error in remote execution of {self.key}: {e}")
|
|
293
|
+
raise
|
|
294
|
+
|
|
283
295
|
|
|
284
296
|
def verifier(
|
|
285
297
|
key: Optional[str] = None,
|
fleet/client.py
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
|
|
17
17
|
import base64
|
|
18
18
|
import cloudpickle
|
|
19
|
-
import concurrent.futures
|
|
20
19
|
import httpx
|
|
21
20
|
import json
|
|
22
21
|
import logging
|
|
@@ -291,7 +290,9 @@ class Fleet:
|
|
|
291
290
|
def execute_verifier_remote(
|
|
292
291
|
self, bundle_data: bytes, args: tuple, kwargs: dict, timeout: Optional[int] = 30
|
|
293
292
|
) -> VerifiersExecuteResponse:
|
|
294
|
-
return _execute_verifier_remote(
|
|
293
|
+
return _execute_verifier_remote(
|
|
294
|
+
self.client, bundle_data, args, kwargs, timeout
|
|
295
|
+
)
|
|
295
296
|
|
|
296
297
|
def delete(self, instance_id: str) -> InstanceResponse:
|
|
297
298
|
return _delete_instance(self.client, instance_id)
|
|
@@ -377,6 +378,7 @@ class Fleet:
|
|
|
377
378
|
keys: Optional[List[str]] = None,
|
|
378
379
|
version: Optional[str] = None,
|
|
379
380
|
team_id: Optional[str] = None,
|
|
381
|
+
project_key: Optional[str] = None,
|
|
380
382
|
) -> List[Task]:
|
|
381
383
|
"""Load tasks for the authenticated team, with optional filtering.
|
|
382
384
|
|
|
@@ -396,13 +398,16 @@ class Fleet:
|
|
|
396
398
|
params["task_keys"] = keys
|
|
397
399
|
if team_id is not None:
|
|
398
400
|
params["team_id"] = team_id
|
|
401
|
+
if project_key is not None:
|
|
402
|
+
params["project_key"] = project_key
|
|
399
403
|
|
|
400
404
|
response = self.client.request("GET", "/v1/tasks", params=params)
|
|
401
405
|
task_list_response = TaskListResponse(**response.json())
|
|
402
406
|
|
|
403
|
-
# Prepare verifier loading
|
|
404
|
-
|
|
407
|
+
# Prepare verifier loading coroutines with concurrency limit
|
|
408
|
+
verifier_coroutines = []
|
|
405
409
|
task_responses_with_indices = []
|
|
410
|
+
semaphore = asyncio.Semaphore(100) # Limit to 10 concurrent operations
|
|
406
411
|
|
|
407
412
|
for idx, task_response in enumerate(task_list_response.tasks):
|
|
408
413
|
if task_response.verifier:
|
|
@@ -413,74 +418,61 @@ class Fleet:
|
|
|
413
418
|
|
|
414
419
|
def create_verifier_with_fallback(tr, emb_code, is_error):
|
|
415
420
|
"""Create verifier with fallback logic."""
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
is_embedded_error
|
|
421
|
+
with semaphore: # Acquire semaphore before operation
|
|
422
|
+
if not is_error:
|
|
423
|
+
# Try to create from embedded data
|
|
424
|
+
try:
|
|
425
|
+
return self._create_verifier_from_data(
|
|
426
|
+
verifier_id=tr.verifier.verifier_id,
|
|
427
|
+
verifier_key=tr.verifier.key,
|
|
428
|
+
verifier_code=emb_code,
|
|
429
|
+
verifier_sha=tr.verifier.sha256,
|
|
430
|
+
)
|
|
431
|
+
except Exception as e:
|
|
432
|
+
logger.warning(
|
|
433
|
+
f"Failed to create verifier {tr.verifier.key}: {e}"
|
|
434
|
+
)
|
|
435
|
+
return None
|
|
436
|
+
else:
|
|
437
|
+
# Fallback: try fetching by ID
|
|
438
|
+
try:
|
|
439
|
+
logger.warning(
|
|
440
|
+
f"Embedded verifier code missing for {tr.verifier.key} (NoSuchKey). "
|
|
441
|
+
f"Attempting to refetch by id {tr.verifier.verifier_id}"
|
|
442
|
+
)
|
|
443
|
+
return self._load_verifier(
|
|
444
|
+
tr.verifier.verifier_id
|
|
445
|
+
)
|
|
446
|
+
except Exception as e:
|
|
447
|
+
logger.warning(
|
|
448
|
+
f"Refetch by verifier id failed for {tr.verifier.key}: {e}. "
|
|
449
|
+
"Leaving verifier unset."
|
|
450
|
+
)
|
|
451
|
+
return None
|
|
452
|
+
|
|
453
|
+
# Add the coroutine for parallel execution
|
|
454
|
+
verifier_coroutines.append(
|
|
455
|
+
create_verifier_with_fallback(
|
|
456
|
+
task_response, embedded_code, is_embedded_error
|
|
452
457
|
)
|
|
453
458
|
)
|
|
454
459
|
task_responses_with_indices.append((idx, task_response))
|
|
455
460
|
else:
|
|
456
461
|
# No verifier needed
|
|
457
|
-
|
|
462
|
+
verifier_coroutines.append(None)
|
|
458
463
|
task_responses_with_indices.append((idx, task_response))
|
|
459
464
|
|
|
460
|
-
# Execute all verifier loading in parallel
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
futures.append(None)
|
|
472
|
-
|
|
473
|
-
# Collect results
|
|
474
|
-
for future in futures:
|
|
475
|
-
if future is None:
|
|
476
|
-
verifier_results.append(None)
|
|
477
|
-
else:
|
|
478
|
-
try:
|
|
479
|
-
result = future.result()
|
|
480
|
-
verifier_results.append(result)
|
|
481
|
-
except Exception as e:
|
|
482
|
-
logger.warning(f"Verifier loading failed: {e}")
|
|
483
|
-
verifier_results.append(None)
|
|
465
|
+
# Execute all verifier loading in parallel
|
|
466
|
+
if verifier_coroutines:
|
|
467
|
+
verifier_results = asyncio.gather(
|
|
468
|
+
*[
|
|
469
|
+
coro if coro is not None else time.sleep(0)
|
|
470
|
+
for coro in verifier_coroutines
|
|
471
|
+
],
|
|
472
|
+
return_exceptions=True,
|
|
473
|
+
)
|
|
474
|
+
else:
|
|
475
|
+
verifier_results = []
|
|
484
476
|
|
|
485
477
|
# Build tasks with results
|
|
486
478
|
tasks = []
|
|
@@ -493,7 +485,11 @@ class Fleet:
|
|
|
493
485
|
|
|
494
486
|
if task_response.verifier:
|
|
495
487
|
# Process verifier result
|
|
496
|
-
if verifier_result
|
|
488
|
+
if isinstance(verifier_result, Exception):
|
|
489
|
+
logger.warning(
|
|
490
|
+
f"Verifier loading failed for {task_response.key}: {verifier_result}"
|
|
491
|
+
)
|
|
492
|
+
elif verifier_result is not None:
|
|
497
493
|
verifier = verifier_result
|
|
498
494
|
embedded_code = task_response.verifier.code or ""
|
|
499
495
|
is_embedded_error = embedded_code.strip().startswith(
|
|
@@ -579,8 +575,6 @@ class Fleet:
|
|
|
579
575
|
task = Task(**task_data)
|
|
580
576
|
tasks.append(task)
|
|
581
577
|
|
|
582
|
-
responses = []
|
|
583
|
-
|
|
584
578
|
for task in tasks:
|
|
585
579
|
payload = TaskRequest(
|
|
586
580
|
key=task.key,
|
|
@@ -594,13 +588,10 @@ class Fleet:
|
|
|
594
588
|
response = self.client.request(
|
|
595
589
|
"POST", "/v1/tasks", json=payload.model_dump()
|
|
596
590
|
)
|
|
597
|
-
responses.append(response)
|
|
598
591
|
except Exception as e:
|
|
599
592
|
logger.error(f"Failed to import task {task.key}: {e}")
|
|
600
593
|
continue
|
|
601
594
|
|
|
602
|
-
return responses
|
|
603
|
-
|
|
604
595
|
def account(self) -> AccountResponse:
|
|
605
596
|
"""Get account information including instance limits and usage.
|
|
606
597
|
|
|
@@ -647,7 +638,6 @@ class Fleet:
|
|
|
647
638
|
AsyncVerifierFunction created from the verifier code
|
|
648
639
|
"""
|
|
649
640
|
from .tasks import verifier_from_string
|
|
650
|
-
from .verifiers import SyncVerifierFunction
|
|
651
641
|
|
|
652
642
|
# Use verifier_from_string to create the verifier
|
|
653
643
|
verifier_func = verifier_from_string(
|
fleet/instance/client.py
CHANGED
|
@@ -63,7 +63,9 @@ class InstanceClient:
|
|
|
63
63
|
def load(self) -> None:
|
|
64
64
|
self._load_resources()
|
|
65
65
|
|
|
66
|
-
def reset(
|
|
66
|
+
def reset(
|
|
67
|
+
self, reset_request: Optional[ResetRequest] = None
|
|
68
|
+
) -> ResetResponse:
|
|
67
69
|
response = self.client.request(
|
|
68
70
|
"POST", "/reset", json=reset_request.model_dump() if reset_request else None
|
|
69
71
|
)
|
fleet/models.py
CHANGED
|
@@ -55,9 +55,7 @@ class Instance(BaseModel):
|
|
|
55
55
|
|
|
56
56
|
class InstanceRequest(BaseModel):
|
|
57
57
|
env_key: str = Field(..., title="Env Key")
|
|
58
|
-
|
|
59
|
-
data_key: Optional[str] = Field(None, title="Data Key")
|
|
60
|
-
data_version: Optional[str] = Field(None, title="Data Version")
|
|
58
|
+
version: Optional[str] = Field(None, title="Version")
|
|
61
59
|
region: Optional[str] = Field("us-west-1", title="Region")
|
|
62
60
|
seed: Optional[int] = Field(None, title="Seed")
|
|
63
61
|
timestamp: Optional[int] = Field(None, title="Timestamp")
|
|
@@ -275,6 +273,12 @@ class VerifiersExecuteResponse(BaseModel):
|
|
|
275
273
|
result: Optional[Any] = Field(
|
|
276
274
|
None, description="The return value of the function", title="Result"
|
|
277
275
|
)
|
|
276
|
+
verifier_id: Optional[str] = Field(
|
|
277
|
+
None, description="ID of the verifier", title="Verifier Id"
|
|
278
|
+
)
|
|
279
|
+
execution_id: Optional[str] = Field(
|
|
280
|
+
None, description="ID of the execution record", title="Execution Id"
|
|
281
|
+
)
|
|
278
282
|
error: Optional[Dict[str, Any]] = Field(
|
|
279
283
|
None, description="Error details if verification failed", title="Error"
|
|
280
284
|
)
|
fleet/resources/sqlite.py
CHANGED
|
@@ -651,7 +651,9 @@ class SQLiteResource(Resource):
|
|
|
651
651
|
)
|
|
652
652
|
return DescribeResponse(**response.json())
|
|
653
653
|
|
|
654
|
-
def query(
|
|
654
|
+
def query(
|
|
655
|
+
self, query: str, args: Optional[List[Any]] = None
|
|
656
|
+
) -> QueryResponse:
|
|
655
657
|
return self._query(query, args, read_only=True)
|
|
656
658
|
|
|
657
659
|
def exec(self, query: str, args: Optional[List[Any]] = None) -> QueryResponse:
|
|
@@ -693,8 +695,12 @@ class SQLiteResource(Resource):
|
|
|
693
695
|
AsyncSnapshotDiff: Object containing the differences between the two databases
|
|
694
696
|
"""
|
|
695
697
|
# Create snapshots of both databases
|
|
696
|
-
before_snapshot = self.snapshot(
|
|
697
|
-
|
|
698
|
+
before_snapshot = self.snapshot(
|
|
699
|
+
name=f"before_{datetime.utcnow().isoformat()}"
|
|
700
|
+
)
|
|
701
|
+
after_snapshot = other.snapshot(
|
|
702
|
+
name=f"after_{datetime.utcnow().isoformat()}"
|
|
703
|
+
)
|
|
698
704
|
|
|
699
705
|
# Return the diff between the snapshots
|
|
700
706
|
return before_snapshot.diff(after_snapshot, ignore_config)
|
fleet/tasks.py
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import asyncio
|
|
6
5
|
from datetime import datetime
|
|
7
|
-
from typing import Any, Dict, Optional, List
|
|
6
|
+
from typing import Any, Dict, Optional, List, TYPE_CHECKING
|
|
8
7
|
|
|
9
8
|
from pydantic import BaseModel, Field, validator
|
|
10
9
|
|
|
11
10
|
# Import the shared VerifierFunction type that works for both async and sync
|
|
12
11
|
from fleet.types import VerifierFunction
|
|
13
12
|
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from fleet._async.models import VerifiersExecuteResponse
|
|
15
|
+
|
|
14
16
|
|
|
15
17
|
class Task(BaseModel):
|
|
16
18
|
"""A task model representing a single task in the Fleet system."""
|
|
@@ -27,7 +29,7 @@ class Task(BaseModel):
|
|
|
27
29
|
verifier: Optional[Any] = Field(
|
|
28
30
|
None,
|
|
29
31
|
description="Verifier function with decorator (async or sync)",
|
|
30
|
-
exclude=True,
|
|
32
|
+
exclude=True, # Exclude from JSON serialization
|
|
31
33
|
)
|
|
32
34
|
verifier_id: Optional[str] = Field(None, description="Verifier identifier")
|
|
33
35
|
verifier_sha: Optional[str] = Field(None, description="Verifier SHA256 hash")
|
|
@@ -73,7 +75,6 @@ class Task(BaseModel):
|
|
|
73
75
|
if self.verifier:
|
|
74
76
|
import inspect
|
|
75
77
|
|
|
76
|
-
# Check if verifier has remote method (for decorated verifiers)
|
|
77
78
|
result = self.verifier.remote(env, *args, **kwargs)
|
|
78
79
|
|
|
79
80
|
# If the result is a coroutine, we need to run it
|
|
@@ -116,6 +117,64 @@ class Task(BaseModel):
|
|
|
116
117
|
else:
|
|
117
118
|
raise ValueError("No verifier function found for this task")
|
|
118
119
|
|
|
120
|
+
def verify_detailed_async(
|
|
121
|
+
self, *args, **kwargs
|
|
122
|
+
) -> "VerifiersExecuteResponse":
|
|
123
|
+
"""Verify the task and return the full execute response model.
|
|
124
|
+
|
|
125
|
+
For async environments, awaits the async verifier.
|
|
126
|
+
Works with both sync and async verifiers in async contexts.
|
|
127
|
+
"""
|
|
128
|
+
# If verifier doesn't exist but verifier_func does, rebuild it
|
|
129
|
+
if not self.verifier and self.verifier_func:
|
|
130
|
+
self._rebuild_verifier()
|
|
131
|
+
|
|
132
|
+
if self.verifier:
|
|
133
|
+
result = self.verifier.remote_with_response(*args, **kwargs)
|
|
134
|
+
# If it's a coroutine, await it
|
|
135
|
+
import inspect
|
|
136
|
+
|
|
137
|
+
if inspect.iscoroutine(result):
|
|
138
|
+
return result
|
|
139
|
+
else:
|
|
140
|
+
return result
|
|
141
|
+
else:
|
|
142
|
+
raise ValueError("No verifier function found for this task")
|
|
143
|
+
|
|
144
|
+
def verify_detailed(self, env, *args, **kwargs) -> "VerifiersExecuteResponse":
|
|
145
|
+
"""Verify the task and return the full execute response model (sync version).
|
|
146
|
+
|
|
147
|
+
For sync environments, calls the sync verifier directly.
|
|
148
|
+
For async verifiers, automatically runs them with asyncio.run().
|
|
149
|
+
"""
|
|
150
|
+
# If verifier doesn't exist but verifier_func does, rebuild it
|
|
151
|
+
if not self.verifier and self.verifier_func:
|
|
152
|
+
self._rebuild_verifier()
|
|
153
|
+
|
|
154
|
+
if self.verifier:
|
|
155
|
+
import inspect
|
|
156
|
+
|
|
157
|
+
# Check if verifier has remote_with_response method (for decorated verifiers)
|
|
158
|
+
result = self.verifier.remote_with_response(env, *args, **kwargs)
|
|
159
|
+
|
|
160
|
+
# If the result is a coroutine, we need to run it
|
|
161
|
+
if inspect.iscoroutine(result):
|
|
162
|
+
# Check if we're already in an event loop
|
|
163
|
+
try:
|
|
164
|
+
asyncio.get_running_loop()
|
|
165
|
+
# We're in an async context, can't use asyncio.run()
|
|
166
|
+
raise RuntimeError(
|
|
167
|
+
"Cannot run async verifier in sync mode while event loop is running. "
|
|
168
|
+
"Use await task.verify_detailed_async() instead."
|
|
169
|
+
)
|
|
170
|
+
except RuntimeError:
|
|
171
|
+
# No event loop running, safe to use asyncio.run()
|
|
172
|
+
return asyncio.run(result)
|
|
173
|
+
else:
|
|
174
|
+
return result
|
|
175
|
+
else:
|
|
176
|
+
raise ValueError("No verifier function found for this task")
|
|
177
|
+
|
|
119
178
|
def _rebuild_verifier(self):
|
|
120
179
|
"""Rebuild the verifier from verifier_func string if it exists."""
|
|
121
180
|
if self.verifier_func:
|
|
@@ -158,26 +217,20 @@ def verifier_from_string(
|
|
|
158
217
|
"""
|
|
159
218
|
try:
|
|
160
219
|
import inspect
|
|
161
|
-
from .verifiers import SyncVerifierFunction
|
|
162
|
-
from .verifiers.code import TASK_SUCCESSFUL_SCORE, TASK_FAILED_SCORE
|
|
163
|
-
from .verifiers.db import IgnoreConfig
|
|
164
|
-
|
|
165
|
-
# Create a globals namespace with all required imports
|
|
166
|
-
exec_globals = globals().copy()
|
|
167
|
-
exec_globals.update(
|
|
168
|
-
{
|
|
169
|
-
"TASK_SUCCESSFUL_SCORE": TASK_SUCCESSFUL_SCORE,
|
|
170
|
-
"TASK_FAILED_SCORE": TASK_FAILED_SCORE,
|
|
171
|
-
"IgnoreConfig": IgnoreConfig,
|
|
172
|
-
"Environment": object, # Add Environment type if needed
|
|
173
|
-
}
|
|
174
|
-
)
|
|
220
|
+
from .verifiers.verifier import SyncVerifierFunction
|
|
221
|
+
from fleet.verifiers.code import TASK_SUCCESSFUL_SCORE, TASK_FAILED_SCORE
|
|
222
|
+
from fleet.verifiers.db import IgnoreConfig
|
|
175
223
|
|
|
176
224
|
# Create a local namespace for executing the code
|
|
177
|
-
local_namespace = {
|
|
225
|
+
local_namespace = {
|
|
226
|
+
"TASK_SUCCESSFUL_SCORE": TASK_SUCCESSFUL_SCORE,
|
|
227
|
+
"TASK_FAILED_SCORE": TASK_FAILED_SCORE,
|
|
228
|
+
"IgnoreConfig": IgnoreConfig,
|
|
229
|
+
"Environment": object, # Add Environment type if needed
|
|
230
|
+
}
|
|
178
231
|
|
|
179
232
|
# Execute the verifier code in the namespace
|
|
180
|
-
exec(verifier_func,
|
|
233
|
+
exec(verifier_func, globals(), local_namespace)
|
|
181
234
|
|
|
182
235
|
# Find the function that was defined
|
|
183
236
|
func_obj = None
|
|
@@ -189,19 +242,15 @@ def verifier_from_string(
|
|
|
189
242
|
if func_obj is None:
|
|
190
243
|
raise ValueError("No function found in verifier code")
|
|
191
244
|
|
|
192
|
-
# Create an
|
|
245
|
+
# Create an AsyncVerifierFunction instance with raw code
|
|
193
246
|
verifier_instance = SyncVerifierFunction(
|
|
194
|
-
|
|
195
|
-
|
|
247
|
+
func_obj,
|
|
248
|
+
verifier_key,
|
|
196
249
|
verifier_id=verifier_id,
|
|
197
250
|
sha256=sha256,
|
|
198
251
|
raw_code=verifier_func,
|
|
199
252
|
)
|
|
200
253
|
|
|
201
|
-
# Store additional metadata
|
|
202
|
-
verifier_instance._verifier_code = verifier_func
|
|
203
|
-
verifier_instance._sha256 = sha256
|
|
204
|
-
|
|
205
254
|
return verifier_instance
|
|
206
255
|
|
|
207
256
|
except Exception as e:
|
|
@@ -212,7 +261,7 @@ def load_tasks_from_file(filename: str) -> List[Task]:
|
|
|
212
261
|
"""Load tasks from a JSON file.
|
|
213
262
|
|
|
214
263
|
Example:
|
|
215
|
-
tasks = fleet.load_tasks_from_file("my_tasks.json")
|
|
264
|
+
tasks = await fleet.load_tasks_from_file("my_tasks.json")
|
|
216
265
|
"""
|
|
217
266
|
from .global_client import get_client
|
|
218
267
|
|
|
@@ -225,6 +274,7 @@ def load_tasks(
|
|
|
225
274
|
keys: Optional[List[str]] = None,
|
|
226
275
|
version: Optional[str] = None,
|
|
227
276
|
team_id: Optional[str] = None,
|
|
277
|
+
project_key: Optional[str] = None,
|
|
228
278
|
) -> List[Task]:
|
|
229
279
|
"""Convenience function to load tasks with optional filtering.
|
|
230
280
|
|
|
@@ -244,7 +294,7 @@ def load_tasks(
|
|
|
244
294
|
|
|
245
295
|
client = get_client()
|
|
246
296
|
return client.load_tasks(
|
|
247
|
-
env_key=env_key, keys=keys, version=version, team_id=team_id
|
|
297
|
+
env_key=env_key, keys=keys, version=version, team_id=team_id, project_key=project_key
|
|
248
298
|
)
|
|
249
299
|
|
|
250
300
|
|
|
@@ -262,8 +312,8 @@ def update_task(
|
|
|
262
312
|
TaskResponse containing the updated task details
|
|
263
313
|
|
|
264
314
|
Examples:
|
|
265
|
-
response = fleet.update_task("my-task", prompt="New prompt text")
|
|
266
|
-
response = fleet.update_task("my-task", verifier_code="def verify(env): return True")
|
|
315
|
+
response = await fleet.update_task("my-task", prompt="New prompt text")
|
|
316
|
+
response = await fleet.update_task("my-task", verifier_code="def verify(env): return True")
|
|
267
317
|
"""
|
|
268
318
|
from .global_client import get_client
|
|
269
319
|
|
fleet/verifiers/parse.py
CHANGED
|
@@ -67,7 +67,7 @@ def convert_verifier_string(verifier_str: str) -> str:
|
|
|
67
67
|
|
|
68
68
|
if not match:
|
|
69
69
|
raise ValueError(
|
|
70
|
-
"Could not parse verifier function. Expected format: def function_name(env: Environment, final_answer: str
|
|
70
|
+
"Could not parse verifier function. Expected format: def function_name(env: Environment, final_answer: Optional[str] = None) -> float/int:"
|
|
71
71
|
)
|
|
72
72
|
|
|
73
73
|
func_name = match.group(1)
|
|
@@ -82,7 +82,7 @@ def convert_verifier_string(verifier_str: str) -> str:
|
|
|
82
82
|
|
|
83
83
|
# Build the new function
|
|
84
84
|
new_func = f"""def {func_name}(
|
|
85
|
-
before: DatabaseSnapshot, after: DatabaseSnapshot, transcript: str
|
|
85
|
+
before: DatabaseSnapshot, after: DatabaseSnapshot, transcript: Optional[str] = None
|
|
86
86
|
) -> int:
|
|
87
87
|
class Environment:
|
|
88
88
|
def db(self, name: str) -> DatabaseSnapshot:"""
|
|
@@ -128,7 +128,7 @@ def convert_verifier_string(verifier_str: str) -> str:
|
|
|
128
128
|
def load(self):
|
|
129
129
|
pass
|
|
130
130
|
|
|
131
|
-
def verifier(env: Environment, final_answer: str
|
|
131
|
+
def verifier(env: Environment, final_answer: Optional[str] = None) -> float:"""
|
|
132
132
|
|
|
133
133
|
if docstring:
|
|
134
134
|
new_func += f"\n {docstring}"
|
fleet/verifiers/verifier.py
CHANGED
|
@@ -12,21 +12,11 @@ import uuid
|
|
|
12
12
|
import logging
|
|
13
13
|
import hashlib
|
|
14
14
|
import inspect
|
|
15
|
-
from typing import
|
|
16
|
-
Any,
|
|
17
|
-
Callable,
|
|
18
|
-
Dict,
|
|
19
|
-
Optional,
|
|
20
|
-
List,
|
|
21
|
-
TypeVar,
|
|
22
|
-
TYPE_CHECKING,
|
|
23
|
-
Tuple,
|
|
24
|
-
)
|
|
15
|
+
from typing import Any, Callable, Dict, Optional, List, TypeVar, Tuple
|
|
25
16
|
|
|
26
17
|
from .bundler import FunctionBundler
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
from ..client import SyncEnv
|
|
18
|
+
from ..client import SyncEnv
|
|
19
|
+
from ...models import VerifiersExecuteResponse
|
|
30
20
|
|
|
31
21
|
logger = logging.getLogger(__name__)
|
|
32
22
|
|
|
@@ -108,7 +98,7 @@ class SyncVerifierFunction:
|
|
|
108
98
|
|
|
109
99
|
return self._bundle_data, self._bundle_sha
|
|
110
100
|
|
|
111
|
-
def _check_bundle_status(self, env:
|
|
101
|
+
def _check_bundle_status(self, env: SyncEnv) -> Tuple[str, bool]:
|
|
112
102
|
"""Check if bundle needs to be uploaded and return (sha, needs_upload)."""
|
|
113
103
|
bundle_data, bundle_sha = self._get_or_create_bundle()
|
|
114
104
|
|
|
@@ -130,7 +120,7 @@ class SyncVerifierFunction:
|
|
|
130
120
|
logger.info(f"Bundle {bundle_sha[:8]}... needs to be uploaded")
|
|
131
121
|
return bundle_sha, True # Upload needed
|
|
132
122
|
|
|
133
|
-
def __call__(self, env:
|
|
123
|
+
def __call__(self, env: SyncEnv, *args, **kwargs) -> float:
|
|
134
124
|
"""Local execution of the verifier function with env as first parameter."""
|
|
135
125
|
try:
|
|
136
126
|
if self._is_async:
|
|
@@ -161,74 +151,17 @@ class SyncVerifierFunction:
|
|
|
161
151
|
# Return error score 0
|
|
162
152
|
return 0.0
|
|
163
153
|
|
|
164
|
-
def remote(self, env:
|
|
154
|
+
def remote(self, env: SyncEnv, *args, **kwargs) -> float:
|
|
165
155
|
"""Remote execution of the verifier function with SHA-based bundle caching."""
|
|
166
|
-
|
|
167
|
-
# if self._is_async:
|
|
168
|
-
# raise NotImplementedError(
|
|
169
|
-
# f"Async verifier '{self.key}' cannot be executed remotely. "
|
|
170
|
-
# "The remote execution environment only supports synchronous functions. "
|
|
171
|
-
# "Please provide a synchronous version of your verifier."
|
|
172
|
-
# )
|
|
173
|
-
|
|
174
|
-
args_array = list(args)
|
|
175
|
-
args_array.append({"env": env.instance_id})
|
|
176
|
-
args = tuple(args_array)
|
|
177
|
-
|
|
178
|
-
try:
|
|
179
|
-
# Check if bundle needs to be uploaded
|
|
180
|
-
bundle_sha, needs_upload = self._check_bundle_status(env)
|
|
181
|
-
|
|
182
|
-
if needs_upload:
|
|
183
|
-
# Need to upload bundle to S3
|
|
184
|
-
logger.info(f"Uploading bundle {bundle_sha[:8]}... for {self.key}")
|
|
185
|
-
bundle_data, _ = self._get_or_create_bundle()
|
|
186
|
-
|
|
187
|
-
response = env.execute_verifier_remote(
|
|
188
|
-
bundle_data=bundle_data,
|
|
189
|
-
bundle_sha=bundle_sha,
|
|
190
|
-
key=self.key,
|
|
191
|
-
function_name=self.func.__name__,
|
|
192
|
-
args=args,
|
|
193
|
-
args_array=args_array,
|
|
194
|
-
kwargs=kwargs,
|
|
195
|
-
needs_upload=True,
|
|
196
|
-
)
|
|
156
|
+
response = self.remote_with_response(env, *args, **kwargs)
|
|
197
157
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
bundle_data, _ = self._get_or_create_bundle()
|
|
206
|
-
|
|
207
|
-
response = env.execute_verifier_remote(
|
|
208
|
-
bundle_data=bundle_data or b"", # Empty if using server-side bundle
|
|
209
|
-
bundle_sha=bundle_sha,
|
|
210
|
-
key=self.key,
|
|
211
|
-
function_name=self.func.__name__,
|
|
212
|
-
args=args,
|
|
213
|
-
args_array=args_array,
|
|
214
|
-
kwargs=kwargs,
|
|
215
|
-
needs_upload=False, # Don't upload, just execute
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
# Handle response
|
|
219
|
-
if response.stdout:
|
|
220
|
-
print(response.stdout)
|
|
221
|
-
if response.success:
|
|
222
|
-
return self._process_result(response.result)
|
|
223
|
-
else:
|
|
224
|
-
self._raise_remote_error(response.error)
|
|
225
|
-
|
|
226
|
-
except Exception as e:
|
|
227
|
-
logger.error(f"Remote execution failed for {self.key}: {e}")
|
|
228
|
-
# If it's an HTTP error, try to get more details
|
|
229
|
-
if hasattr(e, "response") and hasattr(e.response, "text"):
|
|
230
|
-
logger.error(f"Server response: {e.response.text}")
|
|
231
|
-
raise
|
|
158
|
+
# Handle response
|
|
159
|
+
if response.stdout:
|
|
160
|
+
print(response.stdout)
|
|
161
|
+
if response.success:
|
|
162
|
+
return self._process_result(response.result)
|
|
163
|
+
else:
|
|
164
|
+
self._raise_remote_error(response.error)
|
|
232
165
|
|
|
233
166
|
def _process_result(self, result: Any) -> float:
|
|
234
167
|
"""Process remote execution result, handling different return types."""
|
|
@@ -268,10 +201,10 @@ Remote traceback:
|
|
|
268
201
|
try:
|
|
269
202
|
exception_class = getattr(__builtins__, error_type, RuntimeError)
|
|
270
203
|
raise exception_class(full_message)
|
|
271
|
-
except:
|
|
204
|
+
except Exception:
|
|
272
205
|
raise RuntimeError(full_message)
|
|
273
206
|
|
|
274
|
-
def _get_env_id(self, env:
|
|
207
|
+
def _get_env_id(self, env: SyncEnv) -> str:
|
|
275
208
|
"""Generate a unique identifier for the environment."""
|
|
276
209
|
# Use instance base URL or similar unique identifier
|
|
277
210
|
if hasattr(env, "instance") and hasattr(env.instance, "base_url"):
|
|
@@ -291,6 +224,74 @@ Remote traceback:
|
|
|
291
224
|
or "not found" in error_msg
|
|
292
225
|
)
|
|
293
226
|
|
|
227
|
+
def remote_with_response(
|
|
228
|
+
self, env: "SyncEnv", *args, **kwargs
|
|
229
|
+
) -> "VerifiersExecuteResponse":
|
|
230
|
+
"""Remote execution of the verifier function that returns the full response model."""
|
|
231
|
+
args_array = list(args)
|
|
232
|
+
args_array.append({"env": env.instance_id})
|
|
233
|
+
args = tuple(args_array)
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
# Check if bundle needs to be uploaded
|
|
237
|
+
bundle_sha, needs_upload = self._check_bundle_status(env)
|
|
238
|
+
|
|
239
|
+
if needs_upload:
|
|
240
|
+
# Need to upload bundle to S3
|
|
241
|
+
logger.info(f"Uploading bundle {bundle_sha[:8]}... for {self.key}")
|
|
242
|
+
bundle_data, _ = self._get_or_create_bundle()
|
|
243
|
+
|
|
244
|
+
response = env.execute_verifier_remote(
|
|
245
|
+
bundle_data=bundle_data,
|
|
246
|
+
bundle_sha=bundle_sha,
|
|
247
|
+
key=self.key,
|
|
248
|
+
function_name=self.func.__name__,
|
|
249
|
+
args=args,
|
|
250
|
+
args_array=args_array,
|
|
251
|
+
kwargs=kwargs,
|
|
252
|
+
needs_upload=True,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
logger.debug(f"Bundle {bundle_sha[:8]}... uploaded successfully")
|
|
256
|
+
|
|
257
|
+
else:
|
|
258
|
+
# Bundle already available - execute without upload
|
|
259
|
+
logger.info(f"Bundle {bundle_sha[:8]}... already cached for {self.key}")
|
|
260
|
+
response = env.execute_verifier_remote(
|
|
261
|
+
bundle_data=b"", # Empty bundle since it's cached
|
|
262
|
+
bundle_sha=bundle_sha,
|
|
263
|
+
key=self.key,
|
|
264
|
+
function_name=self.func.__name__,
|
|
265
|
+
args=args,
|
|
266
|
+
args_array=args_array,
|
|
267
|
+
kwargs=kwargs,
|
|
268
|
+
needs_upload=False,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
return response
|
|
272
|
+
|
|
273
|
+
except Exception as e:
|
|
274
|
+
# Check if error indicates bundle not found and retry with upload
|
|
275
|
+
if self._is_bundle_not_found_error(e) and not needs_upload:
|
|
276
|
+
logger.info(
|
|
277
|
+
f"Bundle {bundle_sha[:8]}... not found on server, uploading..."
|
|
278
|
+
)
|
|
279
|
+
bundle_data, _ = self._get_or_create_bundle()
|
|
280
|
+
response = env.execute_verifier_remote(
|
|
281
|
+
bundle_data=bundle_data,
|
|
282
|
+
bundle_sha=bundle_sha,
|
|
283
|
+
key=self.key,
|
|
284
|
+
function_name=self.func.__name__,
|
|
285
|
+
args=args,
|
|
286
|
+
args_array=args_array,
|
|
287
|
+
kwargs=kwargs,
|
|
288
|
+
needs_upload=True,
|
|
289
|
+
)
|
|
290
|
+
return response
|
|
291
|
+
else:
|
|
292
|
+
logger.error(f"Error in remote execution of {self.key}: {e}")
|
|
293
|
+
raise
|
|
294
|
+
|
|
294
295
|
|
|
295
296
|
def verifier(
|
|
296
297
|
key: Optional[str] = None,
|
|
@@ -21,20 +21,20 @@ examples/quickstart.py,sha256=1VT39IRRhemsJgxi0O0gprdpcw7HB4pYO97GAYagIcg,3788
|
|
|
21
21
|
examples/test_cdp_logging.py,sha256=AkCwQCgOTQEI8w3v0knWK_4eXMph7L9x07wj9yIYM10,2836
|
|
22
22
|
fleet/__init__.py,sha256=Mkdeh45N47lnSv73Eehj92cGU-AImUitvDWJLFhEp0Y,3844
|
|
23
23
|
fleet/base.py,sha256=bc-340sTpq_DJs7yQ9d2pDWnmJFmA1SwDB9Lagvqtb4,9182
|
|
24
|
-
fleet/client.py,sha256=
|
|
24
|
+
fleet/client.py,sha256=gpmDMDzP_n6kNhrg3hnza3ccuOz40rs1N1HxLDcWryQ,26913
|
|
25
25
|
fleet/config.py,sha256=uY02ZKxVoXqVDta-0IMWaYJeE1CTXF_fA9NI6QUutmU,319
|
|
26
26
|
fleet/exceptions.py,sha256=fUmPwWhnT8SR97lYsRq0kLHQHKtSh2eJS0VQ2caSzEI,5055
|
|
27
27
|
fleet/global_client.py,sha256=frrDAFNM2ywN0JHLtlm9qbE1dQpnQJsavJpb7xSR_bU,1072
|
|
28
|
-
fleet/models.py,sha256=
|
|
29
|
-
fleet/tasks.py,sha256=
|
|
28
|
+
fleet/models.py,sha256=GX-sRciZDenW2O7Qx9w_ftOkJyE4ph1-92WMq6lynHE,12856
|
|
29
|
+
fleet/tasks.py,sha256=2idoe2FHMyNWufrL-yEqyj_vQIEJ4iVUParwGER32Xg,11832
|
|
30
30
|
fleet/types.py,sha256=L4Y82xICf1tzyCLqhLYUgEoaIIS5h9T05TyFNHSWs3s,652
|
|
31
31
|
fleet/_async/__init__.py,sha256=7C_JaEHoqZ4cddsCmlJ4z-UaU6Kr2CBZSgwx5B6fqnc,6765
|
|
32
32
|
fleet/_async/base.py,sha256=oisVTQsx0M_yTmyQJc3oij63uKZ97MHz-xYFsWXxQE8,9202
|
|
33
|
-
fleet/_async/client.py,sha256=
|
|
33
|
+
fleet/_async/client.py,sha256=SUJSi25SfhMn3N2qiZ_lfzuY9WQLUR2iKhh2_mnOay4,27433
|
|
34
34
|
fleet/_async/exceptions.py,sha256=fUmPwWhnT8SR97lYsRq0kLHQHKtSh2eJS0VQ2caSzEI,5055
|
|
35
35
|
fleet/_async/global_client.py,sha256=4WskpLHbsDEgWW7hXMD09W-brkp4euy8w2ZJ88594rQ,1103
|
|
36
|
-
fleet/_async/models.py,sha256=
|
|
37
|
-
fleet/_async/tasks.py,sha256=
|
|
36
|
+
fleet/_async/models.py,sha256=GX-sRciZDenW2O7Qx9w_ftOkJyE4ph1-92WMq6lynHE,12856
|
|
37
|
+
fleet/_async/tasks.py,sha256=YyFdZkdDJ0uXKGhk0OhRRI4gSnOnOp3EEHs8_UYjrO4,11970
|
|
38
38
|
fleet/_async/env/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
39
|
fleet/_async/env/client.py,sha256=8dS42VvSgdqfuh96l6cyiLZlKElilmfTeRSZ4LZnFuE,1143
|
|
40
40
|
fleet/_async/instance/__init__.py,sha256=PtmJq8J8bh0SOQ2V55QURz5GJfobozwtQoqhaOk3_tI,515
|
|
@@ -47,30 +47,30 @@ fleet/_async/resources/mcp.py,sha256=TLEsLiFhfVfZFs0Fu_uDPm-h4FPdvqgQblYqs-PTHhc
|
|
|
47
47
|
fleet/_async/resources/sqlite.py,sha256=0B6mI8Ad4-pxITMxlBST5RFcG6kqAqwnHW9PPAcpVLk,26185
|
|
48
48
|
fleet/_async/verifiers/__init__.py,sha256=1WTlCNq4tIFbbXaQu5Bf2WppZq0A8suhtZbxMTSOwxI,465
|
|
49
49
|
fleet/_async/verifiers/bundler.py,sha256=Sq0KkqEhM5Ng2x8R6Z4puXvQ8FMlEO7D3-ldBLktPi4,26205
|
|
50
|
-
fleet/_async/verifiers/verifier.py,sha256=
|
|
50
|
+
fleet/_async/verifiers/verifier.py,sha256=IiHX028s6ux0kb2FR0Z5zJangl_IDh6cemXsUN2ktUU,14152
|
|
51
51
|
fleet/env/__init__.py,sha256=cS9zCYobM5jypppDMZIQMYd6hOg5f4sgqRXEQ67pckk,676
|
|
52
52
|
fleet/env/client.py,sha256=imF47xJG4JeihcZw4Y-_fXz4XxS-OgIkzUK-TLjpeJY,977
|
|
53
53
|
fleet/instance/__init__.py,sha256=CyWUkbGAK-DBPw4DC4AnCW-MqqheGhZMA5QSRVu-ws4,479
|
|
54
54
|
fleet/instance/base.py,sha256=OYqzBwZFfTX9wlBGSG5gljqj98NbiJeKIfFJ3uj5I4s,1587
|
|
55
|
-
fleet/instance/client.py,sha256=
|
|
55
|
+
fleet/instance/client.py,sha256=_5Up_DuCwTsMxRK1b99MNbtFj3olUWLrojOpUZ6bTr8,5911
|
|
56
56
|
fleet/instance/models.py,sha256=ZTiue0YOuhuwX8jYfJAoCzGfqjLqqXRLqK1LVFhq6rQ,4183
|
|
57
57
|
fleet/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
58
58
|
fleet/resources/base.py,sha256=AXZzT0_yWHkT497q3yekfr0xsD4cPGMCC6y7C43TIkk,663
|
|
59
59
|
fleet/resources/browser.py,sha256=hRNM0YMsVQUAraZGNi_B-KXxLpuddy4ntoEDFSw7czU,1295
|
|
60
60
|
fleet/resources/mcp.py,sha256=c6O4vVJnXANuHMGMe4IPxgp4zBEbFaGm6_d9e6j8Myc,1695
|
|
61
|
-
fleet/resources/sqlite.py,sha256=
|
|
61
|
+
fleet/resources/sqlite.py,sha256=IqI00g7OeQ_m8xDSN6brd_0yiwJ4AMVEs2lBLK4rQ5w,25815
|
|
62
62
|
fleet/verifiers/__init__.py,sha256=GntS8qc3xv8mm-cku1t3xjvOll5jcc5FuiVqQgR4Y6Q,458
|
|
63
63
|
fleet/verifiers/bundler.py,sha256=Sq0KkqEhM5Ng2x8R6Z4puXvQ8FMlEO7D3-ldBLktPi4,26205
|
|
64
64
|
fleet/verifiers/code.py,sha256=A1i_UabZspbyj1awzKVQ_HRxgMO3fU7NbkxYyTrp7So,48
|
|
65
65
|
fleet/verifiers/db.py,sha256=LAh1HambBInH_D9q9E2Z41YNkCOI9JJfpWPFqztjpfQ,27922
|
|
66
66
|
fleet/verifiers/decorator.py,sha256=nAP3O8szXu7md_kpwpz91hGSUNEVLYjwZQZTkQlV1DM,3260
|
|
67
|
-
fleet/verifiers/parse.py,sha256=
|
|
67
|
+
fleet/verifiers/parse.py,sha256=qz9AfJrTbjlg-LU-lE8Ciqi7Yt2a8-cs17FdpjTLhMk,8550
|
|
68
68
|
fleet/verifiers/sql_differ.py,sha256=TqTLWyK3uOyLbitT6HYzYEzuSFC39wcyhgk3rcm__k8,6525
|
|
69
|
-
fleet/verifiers/verifier.py,sha256=
|
|
70
|
-
fleet_python-0.2.
|
|
69
|
+
fleet/verifiers/verifier.py,sha256=_NtcaZVseKcl9qwajMBVuF6aEHid9uyblFFvHjaelfQ,14076
|
|
70
|
+
fleet_python-0.2.48.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
71
71
|
scripts/fix_sync_imports.py,sha256=X9fWLTpiPGkSHsjyQUDepOJkxOqw1DPj7nd8wFlFqLQ,8368
|
|
72
72
|
scripts/unasync.py,sha256=vWVQxRWX8SRZO5cmzEhpvnG_REhCWXpidIGIpWmEcvI,696
|
|
73
|
-
fleet_python-0.2.
|
|
74
|
-
fleet_python-0.2.
|
|
75
|
-
fleet_python-0.2.
|
|
76
|
-
fleet_python-0.2.
|
|
73
|
+
fleet_python-0.2.48.dist-info/METADATA,sha256=BBjvolztXtaP0bGAsNv5i23QzxU1iSU8T3k1bsJTYf0,3304
|
|
74
|
+
fleet_python-0.2.48.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
75
|
+
fleet_python-0.2.48.dist-info/top_level.txt,sha256=_3DSmTohvSDf3AIP_BYfGzhwO1ECFwuzg83X-wHCx3Y,23
|
|
76
|
+
fleet_python-0.2.48.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|