fleet-python 0.2.41__py3-none-any.whl → 0.2.43__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 CHANGED
@@ -14,6 +14,7 @@
14
14
 
15
15
  """Fleet API Client for making HTTP requests to Fleet services."""
16
16
 
17
+ import asyncio
17
18
  import base64
18
19
  import cloudpickle
19
20
  import httpx
@@ -32,6 +33,8 @@ from ..models import (
32
33
  TaskListResponse,
33
34
  AccountResponse,
34
35
  TaskRequest,
36
+ TaskResponse,
37
+ TaskUpdateRequest,
35
38
  )
36
39
  from .tasks import Task
37
40
 
@@ -204,24 +207,39 @@ class AsyncFleet:
204
207
  async def make(
205
208
  self,
206
209
  env_key: str,
210
+ data_key: Optional[str] = None,
207
211
  region: Optional[str] = None,
208
212
  env_variables: Optional[Dict[str, Any]] = None,
209
213
  ) -> AsyncEnv:
210
214
  if ":" in env_key:
211
- env_key_part, version = env_key.split(":", 1)
215
+ env_key_part, env_version = env_key.split(":", 1)
212
216
  if (
213
- not version.startswith("v")
214
- and len(version) != 0
215
- and version[0].isdigit()
217
+ not env_version.startswith("v")
218
+ and len(env_version) != 0
219
+ and env_version[0].isdigit()
216
220
  ):
217
- version = f"v{version}"
221
+ env_version = f"v{env_version}"
218
222
  else:
219
223
  env_key_part = env_key
220
- version = None
224
+ env_version = None
225
+
226
+ if data_key is not None and ":" in data_key:
227
+ data_key_part, data_version = data_key.split(":", 1)
228
+ if (
229
+ not data_version.startswith("v")
230
+ and len(data_version) != 0
231
+ and data_version[0].isdigit()
232
+ ):
233
+ data_version = f"v{data_version}"
234
+ else:
235
+ data_key_part = data_key
236
+ data_version = None
221
237
 
222
238
  request = InstanceRequest(
223
239
  env_key=env_key_part,
224
- version=version,
240
+ env_version=env_version,
241
+ data_key=data_key_part,
242
+ data_version=data_version,
225
243
  region=region,
226
244
  env_variables=env_variables,
227
245
  created_from="sdk",
@@ -284,7 +302,7 @@ class AsyncFleet:
284
302
  with open(filename, "r", encoding="utf-8") as f:
285
303
  tasks_data = f.read()
286
304
 
287
- return self.load_task_array_from_string(tasks_data)
305
+ return await self.load_task_array_from_string(tasks_data)
288
306
 
289
307
  async def load_task_array_from_string(
290
308
  self, serialized_tasks: List[Dict]
@@ -293,18 +311,19 @@ class AsyncFleet:
293
311
 
294
312
  json_tasks = json.loads(serialized_tasks)
295
313
  for json_task in json_tasks:
296
- parsed_task = self.load_task_from_json(json_task)
314
+ parsed_task = await self.load_task_from_json(json_task)
297
315
  tasks.append(parsed_task)
298
316
  return tasks
299
317
 
300
318
  async def load_task_from_string(self, task_string: str) -> Task:
301
319
  task_json = json.loads(task_string)
302
- return self.load_task_from_json(task_json)
320
+ return await self.load_task_from_json(task_json)
303
321
 
304
322
  async def load_task_from_json(self, task_json: Dict) -> Task:
323
+ verifier = None
305
324
  try:
306
325
  if "verifier_id" in task_json and task_json["verifier_id"]:
307
- verifier = self._create_verifier_from_data(
326
+ verifier = await self._create_verifier_from_data(
308
327
  verifier_id=task_json["verifier_id"],
309
328
  verifier_key=task_json["key"],
310
329
  verifier_code=task_json["verifier_func"],
@@ -355,48 +374,99 @@ class AsyncFleet:
355
374
  response = await self.client.request("GET", "/v1/tasks", params=params)
356
375
  task_list_response = TaskListResponse(**response.json())
357
376
 
358
- # Transform TaskResponse objects to Task objects
359
- tasks = []
360
- for task_response in task_list_response.tasks:
361
- # Create verifier function if verifier data is present
362
- verifier = None
363
- verifier_func = task_response.verifier_func
377
+ # Prepare verifier loading coroutines with concurrency limit
378
+ verifier_coroutines = []
379
+ task_responses_with_indices = []
380
+ semaphore = asyncio.Semaphore(10) # Limit to 10 concurrent operations
364
381
 
382
+ for idx, task_response in enumerate(task_list_response.tasks):
365
383
  if task_response.verifier:
366
384
  embedded_code = task_response.verifier.code or ""
367
385
  is_embedded_error = embedded_code.strip().startswith(
368
386
  "<error loading code:"
369
387
  )
370
- if not is_embedded_error:
371
- # Only override if the embedded code looks valid
372
- verifier_func = embedded_code
373
- # Create VerifierFunction from the embedded data
374
- try:
375
- verifier = await self._create_verifier_from_data(
376
- verifier_id=task_response.verifier.verifier_id,
377
- verifier_key=task_response.verifier.key,
378
- verifier_code=embedded_code,
379
- verifier_sha=task_response.verifier.sha256,
380
- )
381
- except Exception as e:
382
- logger.warning(
383
- f"Failed to create verifier {task_response.verifier.key}: {e}"
384
- )
385
- else:
386
- # Fallback: try fetching by ID if embedded code failed to load
387
- try:
388
- logger.warning(
389
- f"Embedded verifier code missing for {task_response.verifier.key} (NoSuchKey). "
390
- f"Attempting to refetch by id {task_response.verifier.verifier_id}"
391
- )
392
- verifier = await self._load_verifier(
393
- task_response.verifier.verifier_id
394
- )
395
- except Exception as e:
396
- logger.warning(
397
- f"Refetch by verifier id failed for {task_response.verifier.key}: {e}. "
398
- "Leaving verifier unset."
399
- )
388
+
389
+ async def create_verifier_with_fallback(tr, emb_code, is_error):
390
+ """Create verifier with fallback logic."""
391
+ async with semaphore: # Acquire semaphore before operation
392
+ if not is_error:
393
+ # Try to create from embedded data
394
+ try:
395
+ return await self._create_verifier_from_data(
396
+ verifier_id=tr.verifier.verifier_id,
397
+ verifier_key=tr.verifier.key,
398
+ verifier_code=emb_code,
399
+ verifier_sha=tr.verifier.sha256,
400
+ )
401
+ except Exception as e:
402
+ logger.warning(
403
+ f"Failed to create verifier {tr.verifier.key}: {e}"
404
+ )
405
+ return None
406
+ else:
407
+ # Fallback: try fetching by ID
408
+ try:
409
+ logger.warning(
410
+ f"Embedded verifier code missing for {tr.verifier.key} (NoSuchKey). "
411
+ f"Attempting to refetch by id {tr.verifier.verifier_id}"
412
+ )
413
+ return await self._load_verifier(
414
+ tr.verifier.verifier_id
415
+ )
416
+ except Exception as e:
417
+ logger.warning(
418
+ f"Refetch by verifier id failed for {tr.verifier.key}: {e}. "
419
+ "Leaving verifier unset."
420
+ )
421
+ return None
422
+
423
+ # Add the coroutine for parallel execution
424
+ verifier_coroutines.append(
425
+ create_verifier_with_fallback(
426
+ task_response, embedded_code, is_embedded_error
427
+ )
428
+ )
429
+ task_responses_with_indices.append((idx, task_response))
430
+ else:
431
+ # No verifier needed
432
+ verifier_coroutines.append(None)
433
+ task_responses_with_indices.append((idx, task_response))
434
+
435
+ # Execute all verifier loading in parallel
436
+ if verifier_coroutines:
437
+ verifier_results = await asyncio.gather(
438
+ *[
439
+ coro if coro is not None else asyncio.sleep(0)
440
+ for coro in verifier_coroutines
441
+ ],
442
+ return_exceptions=True,
443
+ )
444
+ else:
445
+ verifier_results = []
446
+
447
+ # Build tasks with results
448
+ tasks = []
449
+ for (idx, task_response), verifier_result in zip(
450
+ task_responses_with_indices, verifier_results
451
+ ):
452
+ # Handle verifier result
453
+ verifier = None
454
+ verifier_func = task_response.verifier_func
455
+
456
+ if task_response.verifier:
457
+ # Process verifier result
458
+ if isinstance(verifier_result, Exception):
459
+ logger.warning(
460
+ f"Verifier loading failed for {task_response.key}: {verifier_result}"
461
+ )
462
+ elif verifier_result is not None:
463
+ verifier = verifier_result
464
+ embedded_code = task_response.verifier.code or ""
465
+ is_embedded_error = embedded_code.strip().startswith(
466
+ "<error loading code:"
467
+ )
468
+ if not is_embedded_error:
469
+ verifier_func = embedded_code
400
470
 
401
471
  task = Task(
402
472
  key=task_response.key,
@@ -501,6 +571,28 @@ class AsyncFleet:
501
571
  response = await self.client.request("GET", "/v1/account")
502
572
  return AccountResponse(**response.json())
503
573
 
574
+ async def update_task(
575
+ self,
576
+ task_key: str,
577
+ prompt: Optional[str] = None,
578
+ verifier_code: Optional[str] = None,
579
+ ) -> TaskResponse:
580
+ """Update an existing task.
581
+
582
+ Args:
583
+ task_key: The key of the task to update
584
+ prompt: New prompt text for the task (optional)
585
+ verifier_code: Python code for task verification (optional)
586
+
587
+ Returns:
588
+ TaskResponse containing the updated task details
589
+ """
590
+ payload = TaskUpdateRequest(prompt=prompt, verifier_code=verifier_code)
591
+ response = await self.client.request(
592
+ "PUT", f"/v1/tasks/{task_key}", json=payload.model_dump(exclude_none=True)
593
+ )
594
+ return TaskResponse(**response.json())
595
+
504
596
  async def _create_verifier_from_data(
505
597
  self, verifier_id: str, verifier_key: str, verifier_code: str, verifier_sha: str
506
598
  ) -> "AsyncVerifierFunction":
@@ -515,8 +607,7 @@ class AsyncFleet:
515
607
  Returns:
516
608
  AsyncVerifierFunction created from the verifier code
517
609
  """
518
- from ..tasks import verifier_from_string
519
- from .verifiers.verifier import AsyncVerifierFunction
610
+ from .tasks import verifier_from_string
520
611
 
521
612
  # Use verifier_from_string to create the verifier
522
613
  verifier_func = verifier_from_string(
@@ -3,8 +3,15 @@ from ...models import Environment as EnvironmentModel, AccountResponse
3
3
  from typing import List, Optional, Dict, Any
4
4
 
5
5
 
6
- async def make_async(env_key: str, region: Optional[str] = None, env_variables: Optional[Dict[str, Any]] = None) -> AsyncEnv:
7
- return await AsyncFleet().make(env_key, region=region, env_variables=env_variables)
6
+ async def make_async(
7
+ env_key: str,
8
+ data_key: Optional[str] = None,
9
+ region: Optional[str] = None,
10
+ env_variables: Optional[Dict[str, Any]] = None,
11
+ ) -> AsyncEnv:
12
+ return await AsyncFleet().make(
13
+ env_key, data_key=data_key, region=region, env_variables=env_variables
14
+ )
8
15
 
9
16
 
10
17
  async def make_for_task_async(task: Task) -> AsyncEnv:
fleet/_async/models.py CHANGED
@@ -156,6 +156,11 @@ class TaskRequest(BaseModel):
156
156
  env_variables: Optional[Dict[str, Any]] = Field(None, title="Env Variables")
157
157
 
158
158
 
159
+ class TaskUpdateRequest(BaseModel):
160
+ prompt: Optional[str] = Field(None, title="Prompt")
161
+ verifier_code: Optional[str] = Field(None, title="Verifier Code")
162
+
163
+
159
164
  class VerifierData(BaseModel):
160
165
  verifier_id: str = Field(..., title="Verifier Id")
161
166
  key: str = Field(..., title="Key")
fleet/_async/tasks.py CHANGED
@@ -2,10 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import re
6
5
  from datetime import datetime
7
6
  from typing import Any, Dict, Optional, List
8
- from uuid import UUID
9
7
 
10
8
  from pydantic import BaseModel, Field, validator
11
9
 
@@ -46,7 +44,7 @@ class Task(BaseModel):
46
44
  @property
47
45
  def env_key(self) -> str:
48
46
  """Get the environment key combining env_id and version."""
49
- if self.version:
47
+ if self.version and self.version != "None":
50
48
  return f"{self.env_id}:{self.version}"
51
49
  return self.env_id
52
50
 
@@ -75,7 +73,7 @@ class Task(BaseModel):
75
73
  if inspect.iscoroutine(result):
76
74
  # Check if we're already in an event loop
77
75
  try:
78
- loop = asyncio.get_running_loop()
76
+ asyncio.get_running_loop()
79
77
  # We're in an async context, can't use asyncio.run()
80
78
  raise RuntimeError(
81
79
  "Cannot run async verifier in sync mode while event loop is running. "
@@ -121,58 +119,57 @@ class Task(BaseModel):
121
119
 
122
120
 
123
121
  def verifier_from_string(
124
- verifier_func: str,
125
- verifier_id: str,
126
- verifier_key: str,
127
- sha256: str = ""
128
- ) -> 'VerifierFunction':
122
+ verifier_func: str, verifier_id: str, verifier_key: str, sha256: str = ""
123
+ ) -> "VerifierFunction":
129
124
  """Create a verifier function from string code.
130
-
125
+
131
126
  Args:
132
127
  verifier_func: The verifier function code as a string
133
128
  verifier_id: Unique identifier for the verifier
134
129
  verifier_key: Key/name for the verifier
135
130
  sha256: SHA256 hash of the verifier code
136
-
131
+
137
132
  Returns:
138
133
  VerifierFunction instance that can be used to verify tasks
139
134
  """
140
135
  try:
141
136
  import inspect
142
- from .verifiers import verifier, AsyncVerifierFunction
137
+ from .verifiers.verifier import AsyncVerifierFunction
143
138
  from fleet.verifiers.code import TASK_SUCCESSFUL_SCORE, TASK_FAILED_SCORE
144
139
  from fleet.verifiers.db import IgnoreConfig
145
-
140
+
146
141
  # Create a local namespace for executing the code
147
142
  local_namespace = {
148
- 'TASK_SUCCESSFUL_SCORE': TASK_SUCCESSFUL_SCORE,
149
- 'TASK_FAILED_SCORE': TASK_FAILED_SCORE,
150
- 'IgnoreConfig': IgnoreConfig,
151
- 'Environment': object # Add Environment type if needed
143
+ "TASK_SUCCESSFUL_SCORE": TASK_SUCCESSFUL_SCORE,
144
+ "TASK_FAILED_SCORE": TASK_FAILED_SCORE,
145
+ "IgnoreConfig": IgnoreConfig,
146
+ "Environment": object, # Add Environment type if needed
152
147
  }
153
-
148
+
154
149
  # Execute the verifier code in the namespace
155
150
  exec(verifier_func, globals(), local_namespace)
156
-
151
+
157
152
  # Find the function that was defined
158
153
  func_obj = None
159
154
  for name, obj in local_namespace.items():
160
155
  if inspect.isfunction(obj):
161
156
  func_obj = obj
162
157
  break
163
-
158
+
164
159
  if func_obj is None:
165
160
  raise ValueError("No function found in verifier code")
166
-
167
- # Create an AsyncVerifierFunction instance
168
- verifier_instance = AsyncVerifierFunction(func_obj, verifier_key, verifier_id)
169
-
170
- # Store additional metadata
171
- verifier_instance._verifier_code = verifier_func
172
- verifier_instance._sha256 = sha256
173
-
161
+
162
+ # Create an AsyncVerifierFunction instance with raw code
163
+ verifier_instance = AsyncVerifierFunction(
164
+ func_obj,
165
+ verifier_key,
166
+ verifier_id=verifier_id,
167
+ sha256=sha256,
168
+ raw_code=verifier_func,
169
+ )
170
+
174
171
  return verifier_instance
175
-
172
+
176
173
  except Exception as e:
177
174
  raise ValueError(f"Failed to create verifier from string: {e}")
178
175
 
@@ -181,7 +178,7 @@ async def load_tasks(
181
178
  env_key: Optional[str] = None,
182
179
  keys: Optional[List[str]] = None,
183
180
  version: Optional[str] = None,
184
- team_id: Optional[str] = None
181
+ team_id: Optional[str] = None,
185
182
  ) -> List[Task]:
186
183
  """Convenience function to load tasks with optional filtering.
187
184
 
@@ -201,8 +198,30 @@ async def load_tasks(
201
198
 
202
199
  client = get_client()
203
200
  return await client.load_tasks(
204
- env_key=env_key,
205
- keys=keys,
206
- version=version,
207
- team_id=team_id
201
+ env_key=env_key, keys=keys, version=version, team_id=team_id
202
+ )
203
+
204
+
205
+ async def update_task(
206
+ task_key: str, prompt: Optional[str] = None, verifier_code: Optional[str] = None
207
+ ):
208
+ """Convenience function to update an existing task.
209
+
210
+ Args:
211
+ task_key: The key of the task to update
212
+ prompt: New prompt text for the task (optional)
213
+ verifier_code: Python code for task verification (optional)
214
+
215
+ Returns:
216
+ TaskResponse containing the updated task details
217
+
218
+ Examples:
219
+ response = await fleet.update_task("my-task", prompt="New prompt text")
220
+ response = await fleet.update_task("my-task", verifier_code="def verify(env): return True")
221
+ """
222
+ from .global_client import get_client
223
+
224
+ client = get_client()
225
+ return await client.update_task(
226
+ task_key=task_key, prompt=prompt, verifier_code=verifier_code
208
227
  )
fleet/client.py CHANGED
@@ -16,6 +16,7 @@
16
16
 
17
17
  import base64
18
18
  import cloudpickle
19
+ import concurrent.futures
19
20
  import httpx
20
21
  import json
21
22
  import logging
@@ -32,6 +33,8 @@ from .models import (
32
33
  TaskListResponse,
33
34
  AccountResponse,
34
35
  TaskRequest,
36
+ TaskResponse,
37
+ TaskUpdateRequest,
35
38
  )
36
39
  from .tasks import Task
37
40
 
@@ -204,24 +207,39 @@ class Fleet:
204
207
  def make(
205
208
  self,
206
209
  env_key: str,
210
+ data_key: Optional[str] = None,
207
211
  region: Optional[str] = None,
208
212
  env_variables: Optional[Dict[str, Any]] = None,
209
213
  ) -> SyncEnv:
210
214
  if ":" in env_key:
211
- env_key_part, version = env_key.split(":", 1)
215
+ env_key_part, env_version = env_key.split(":", 1)
212
216
  if (
213
- not version.startswith("v")
214
- and len(version) != 0
215
- and version[0].isdigit()
217
+ not env_version.startswith("v")
218
+ and len(env_version) != 0
219
+ and env_version[0].isdigit()
216
220
  ):
217
- version = f"v{version}"
221
+ env_version = f"v{env_version}"
218
222
  else:
219
223
  env_key_part = env_key
220
- version = None
224
+ env_version = None
225
+
226
+ if data_key is not None and ":" in data_key:
227
+ data_key_part, data_version = data_key.split(":", 1)
228
+ if (
229
+ not data_version.startswith("v")
230
+ and len(data_version) != 0
231
+ and data_version[0].isdigit()
232
+ ):
233
+ data_version = f"v{data_version}"
234
+ else:
235
+ data_key_part = data_key
236
+ data_version = None
221
237
 
222
238
  request = InstanceRequest(
223
239
  env_key=env_key_part,
224
- version=version,
240
+ env_version=env_version,
241
+ data_key=data_key_part,
242
+ data_version=data_version,
225
243
  region=region,
226
244
  env_variables=env_variables,
227
245
  created_from="sdk",
@@ -351,48 +369,107 @@ class Fleet:
351
369
  response = self.client.request("GET", "/v1/tasks", params=params)
352
370
  task_list_response = TaskListResponse(**response.json())
353
371
 
354
- # Transform TaskResponse objects to Task objects
355
- tasks = []
356
- for task_response in task_list_response.tasks:
357
- # Create verifier function if verifier data is present
358
- verifier = None
359
- verifier_func = task_response.verifier_func
372
+ # Prepare verifier loading tasks
373
+ verifier_tasks = []
374
+ task_responses_with_indices = []
360
375
 
376
+ for idx, task_response in enumerate(task_list_response.tasks):
361
377
  if task_response.verifier:
362
378
  embedded_code = task_response.verifier.code or ""
363
379
  is_embedded_error = embedded_code.strip().startswith(
364
380
  "<error loading code:"
365
381
  )
366
- if not is_embedded_error:
367
- # Only override if the embedded code looks valid
368
- verifier_func = embedded_code
369
- # Create VerifierFunction from the embedded data
370
- try:
371
- verifier = self._create_verifier_from_data(
372
- verifier_id=task_response.verifier.verifier_id,
373
- verifier_key=task_response.verifier.key,
374
- verifier_code=embedded_code,
375
- verifier_sha=task_response.verifier.sha256,
376
- )
377
- except Exception as e:
378
- logger.warning(
379
- f"Failed to create verifier {task_response.verifier.key}: {e}"
380
- )
381
- else:
382
- # Fallback: try fetching by ID if embedded code failed to load
383
- try:
384
- logger.warning(
385
- f"Embedded verifier code missing for {task_response.verifier.key} (NoSuchKey). "
386
- f"Attempting to refetch by id {task_response.verifier.verifier_id}"
387
- )
388
- verifier = self._load_verifier(
389
- task_response.verifier.verifier_id
390
- )
391
- except Exception as e:
392
- logger.warning(
393
- f"Refetch by verifier id failed for {task_response.verifier.key}: {e}. "
394
- "Leaving verifier unset."
395
- )
382
+
383
+ def create_verifier_with_fallback(tr, emb_code, is_error):
384
+ """Create verifier with fallback logic."""
385
+ if not is_error:
386
+ # Try to create from embedded data
387
+ try:
388
+ return self._create_verifier_from_data(
389
+ verifier_id=tr.verifier.verifier_id,
390
+ verifier_key=tr.verifier.key,
391
+ verifier_code=emb_code,
392
+ verifier_sha=tr.verifier.sha256,
393
+ )
394
+ except Exception as e:
395
+ logger.warning(
396
+ f"Failed to create verifier {tr.verifier.key}: {e}"
397
+ )
398
+ return None
399
+ else:
400
+ # Fallback: try fetching by ID
401
+ try:
402
+ logger.warning(
403
+ f"Embedded verifier code missing for {tr.verifier.key} (NoSuchKey). "
404
+ f"Attempting to refetch by id {tr.verifier.verifier_id}"
405
+ )
406
+ return self._load_verifier(tr.verifier.verifier_id)
407
+ except Exception as e:
408
+ logger.warning(
409
+ f"Refetch by verifier id failed for {tr.verifier.key}: {e}. "
410
+ "Leaving verifier unset."
411
+ )
412
+ return None
413
+
414
+ # Add the task for parallel execution
415
+ verifier_tasks.append(
416
+ (
417
+ create_verifier_with_fallback,
418
+ task_response,
419
+ embedded_code,
420
+ is_embedded_error,
421
+ )
422
+ )
423
+ task_responses_with_indices.append((idx, task_response))
424
+ else:
425
+ # No verifier needed
426
+ verifier_tasks.append(None)
427
+ task_responses_with_indices.append((idx, task_response))
428
+
429
+ # Execute all verifier loading in parallel using ThreadPoolExecutor
430
+ verifier_results = []
431
+ if verifier_tasks:
432
+ with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
433
+ futures = []
434
+ for task in verifier_tasks:
435
+ if task is not None:
436
+ func, tr, emb_code, is_error = task
437
+ future = executor.submit(func, tr, emb_code, is_error)
438
+ futures.append(future)
439
+ else:
440
+ futures.append(None)
441
+
442
+ # Collect results
443
+ for future in futures:
444
+ if future is None:
445
+ verifier_results.append(None)
446
+ else:
447
+ try:
448
+ result = future.result()
449
+ verifier_results.append(result)
450
+ except Exception as e:
451
+ logger.warning(f"Verifier loading failed: {e}")
452
+ verifier_results.append(None)
453
+
454
+ # Build tasks with results
455
+ tasks = []
456
+ for (idx, task_response), verifier_result in zip(
457
+ task_responses_with_indices, verifier_results
458
+ ):
459
+ # Handle verifier result
460
+ verifier = None
461
+ verifier_func = task_response.verifier_func
462
+
463
+ if task_response.verifier:
464
+ # Process verifier result
465
+ if verifier_result is not None:
466
+ verifier = verifier_result
467
+ embedded_code = task_response.verifier.code or ""
468
+ is_embedded_error = embedded_code.strip().startswith(
469
+ "<error loading code:"
470
+ )
471
+ if not is_embedded_error:
472
+ verifier_func = embedded_code
396
473
 
397
474
  task = Task(
398
475
  key=task_response.key,
@@ -497,6 +574,28 @@ class Fleet:
497
574
  response = self.client.request("GET", "/v1/account")
498
575
  return AccountResponse(**response.json())
499
576
 
577
+ def update_task(
578
+ self,
579
+ task_key: str,
580
+ prompt: Optional[str] = None,
581
+ verifier_code: Optional[str] = None,
582
+ ) -> TaskResponse:
583
+ """Update an existing task.
584
+
585
+ Args:
586
+ task_key: The key of the task to update
587
+ prompt: New prompt text for the task (optional)
588
+ verifier_code: Python code for task verification (optional)
589
+
590
+ Returns:
591
+ TaskResponse containing the updated task details
592
+ """
593
+ payload = TaskUpdateRequest(prompt=prompt, verifier_code=verifier_code)
594
+ response = self.client.request(
595
+ "PUT", f"/v1/tasks/{task_key}", json=payload.model_dump(exclude_none=True)
596
+ )
597
+ return TaskResponse(**response.json())
598
+
500
599
  def _create_verifier_from_data(
501
600
  self, verifier_id: str, verifier_key: str, verifier_code: str, verifier_sha: str
502
601
  ) -> "SyncVerifierFunction":
fleet/env/client.py CHANGED
@@ -3,8 +3,15 @@ from ..models import Environment as EnvironmentModel, AccountResponse
3
3
  from typing import List, Optional, Dict, Any
4
4
 
5
5
 
6
- def make(env_key: str, region: Optional[str] = None, env_variables: Optional[Dict[str, Any]] = None) -> SyncEnv:
7
- return Fleet().make(env_key, region=region, env_variables=env_variables)
6
+ def make(
7
+ env_key: str,
8
+ data_key: Optional[str] = None,
9
+ region: Optional[str] = None,
10
+ env_variables: Optional[Dict[str, Any]] = None,
11
+ ) -> SyncEnv:
12
+ return Fleet().make(
13
+ env_key, data_key=data_key, region=region, env_variables=env_variables
14
+ )
8
15
 
9
16
 
10
17
  def make_for_task_async(task: Task) -> SyncEnv:
fleet/models.py CHANGED
@@ -55,7 +55,9 @@ class Instance(BaseModel):
55
55
 
56
56
  class InstanceRequest(BaseModel):
57
57
  env_key: str = Field(..., title="Env Key")
58
- version: Optional[str] = Field(None, title="Version")
58
+ env_version: Optional[str] = Field(None, title="Version")
59
+ data_key: Optional[str] = Field(None, title="Data Key")
60
+ data_version: Optional[str] = Field(None, title="Data Version")
59
61
  region: Optional[str] = Field("us-west-1", title="Region")
60
62
  seed: Optional[int] = Field(None, title="Seed")
61
63
  timestamp: Optional[int] = Field(None, title="Timestamp")
@@ -156,6 +158,11 @@ class TaskRequest(BaseModel):
156
158
  env_variables: Optional[Dict[str, Any]] = Field(None, title="Env Variables")
157
159
 
158
160
 
161
+ class TaskUpdateRequest(BaseModel):
162
+ prompt: Optional[str] = Field(None, title="Prompt")
163
+ verifier_code: Optional[str] = Field(None, title="Verifier Code")
164
+
165
+
159
166
  class VerifierData(BaseModel):
160
167
  verifier_id: str = Field(..., title="Verifier Id")
161
168
  key: str = Field(..., title="Key")
fleet/tasks.py CHANGED
@@ -47,7 +47,7 @@ class Task(BaseModel):
47
47
  @property
48
48
  def env_key(self) -> str:
49
49
  """Get the environment key combining env_id and version."""
50
- if self.version:
50
+ if self.version and self.version != "None":
51
51
  return f"{self.env_id}:{self.version}"
52
52
  return self.env_id
53
53
 
@@ -70,7 +70,7 @@ class Task(BaseModel):
70
70
  import inspect
71
71
 
72
72
  # Check if verifier has remote method (for decorated verifiers)
73
- if hasattr(self.verifier, 'remote'):
73
+ if hasattr(self.verifier, "remote"):
74
74
  result = self.verifier.remote(env, *args, **kwargs)
75
75
  else:
76
76
  # For verifiers created from string, call directly
@@ -126,19 +126,16 @@ class Task(BaseModel):
126
126
 
127
127
 
128
128
  def verifier_from_string(
129
- verifier_func: str,
130
- verifier_id: str,
131
- verifier_key: str,
132
- sha256: str = ""
133
- ) -> 'VerifierFunction':
129
+ verifier_func: str, verifier_id: str, verifier_key: str, sha256: str = ""
130
+ ) -> "VerifierFunction":
134
131
  """Create a verifier function from string code.
135
-
132
+
136
133
  Args:
137
134
  verifier_func: The verifier function code as a string
138
135
  verifier_id: Unique identifier for the verifier
139
136
  verifier_key: Key/name for the verifier
140
137
  sha256: SHA256 hash of the verifier code
141
-
138
+
142
139
  Returns:
143
140
  VerifierFunction instance that can be used to verify tasks
144
141
  """
@@ -147,63 +144,72 @@ def verifier_from_string(
147
144
  from .verifiers import verifier, SyncVerifierFunction
148
145
  from .verifiers.code import TASK_SUCCESSFUL_SCORE, TASK_FAILED_SCORE
149
146
  from .verifiers.db import IgnoreConfig
150
-
147
+
151
148
  # Create a globals namespace with all required imports
152
149
  exec_globals = globals().copy()
153
- exec_globals.update({
154
- 'TASK_SUCCESSFUL_SCORE': TASK_SUCCESSFUL_SCORE,
155
- 'TASK_FAILED_SCORE': TASK_FAILED_SCORE,
156
- 'IgnoreConfig': IgnoreConfig,
157
- 'Environment': object # Add Environment type if needed
158
- })
159
-
150
+ exec_globals.update(
151
+ {
152
+ "TASK_SUCCESSFUL_SCORE": TASK_SUCCESSFUL_SCORE,
153
+ "TASK_FAILED_SCORE": TASK_FAILED_SCORE,
154
+ "IgnoreConfig": IgnoreConfig,
155
+ "Environment": object, # Add Environment type if needed
156
+ }
157
+ )
158
+
160
159
  # Create a local namespace for executing the code
161
160
  local_namespace = {}
162
-
161
+
163
162
  # Execute the verifier code in the namespace
164
163
  exec(verifier_func, exec_globals, local_namespace)
165
-
164
+
166
165
  # Find the function that was defined
167
166
  func_obj = None
168
167
  for name, obj in local_namespace.items():
169
168
  if inspect.isfunction(obj):
170
169
  func_obj = obj
171
170
  break
172
-
171
+
173
172
  if func_obj is None:
174
173
  raise ValueError("No function found in verifier code")
175
-
174
+
176
175
  # Create a wrapper function that provides the necessary globals
177
176
  def wrapped_verifier(env, *args, **kwargs):
178
177
  # Set up globals for the function execution
179
- func_globals = func_obj.__globals__.copy() if hasattr(func_obj, '__globals__') else {}
180
- func_globals.update({
181
- 'TASK_SUCCESSFUL_SCORE': TASK_SUCCESSFUL_SCORE,
182
- 'TASK_FAILED_SCORE': TASK_FAILED_SCORE,
183
- 'IgnoreConfig': IgnoreConfig
184
- })
185
-
178
+ func_globals = (
179
+ func_obj.__globals__.copy() if hasattr(func_obj, "__globals__") else {}
180
+ )
181
+ func_globals.update(
182
+ {
183
+ "TASK_SUCCESSFUL_SCORE": TASK_SUCCESSFUL_SCORE,
184
+ "TASK_FAILED_SCORE": TASK_FAILED_SCORE,
185
+ "IgnoreConfig": IgnoreConfig,
186
+ }
187
+ )
188
+
186
189
  # Create a new function with the updated globals
187
190
  import types
191
+
188
192
  new_func = types.FunctionType(
189
193
  func_obj.__code__,
190
194
  func_globals,
191
195
  func_obj.__name__,
192
196
  func_obj.__defaults__,
193
- func_obj.__closure__
197
+ func_obj.__closure__,
194
198
  )
195
-
199
+
196
200
  return new_func(env, *args, **kwargs)
197
-
201
+
198
202
  # Create an AsyncVerifierFunction instance with the wrapped function
199
- verifier_instance = SyncVerifierFunction(wrapped_verifier, verifier_key, verifier_id)
200
-
203
+ verifier_instance = SyncVerifierFunction(
204
+ wrapped_verifier, verifier_key, verifier_id
205
+ )
206
+
201
207
  # Store additional metadata
202
208
  verifier_instance._verifier_code = verifier_func
203
209
  verifier_instance._sha256 = sha256
204
-
210
+
205
211
  return verifier_instance
206
-
212
+
207
213
  except Exception as e:
208
214
  raise ValueError(f"Failed to create verifier from string: {e}")
209
215
 
@@ -212,7 +218,7 @@ def load_tasks(
212
218
  env_key: Optional[str] = None,
213
219
  keys: Optional[List[str]] = None,
214
220
  version: Optional[str] = None,
215
- team_id: Optional[str] = None
221
+ team_id: Optional[str] = None,
216
222
  ) -> List[Task]:
217
223
  """Convenience function to load tasks with optional filtering.
218
224
 
@@ -232,8 +238,31 @@ def load_tasks(
232
238
 
233
239
  client = get_client()
234
240
  return client.load_tasks(
235
- env_key=env_key,
236
- keys=keys,
237
- version=version,
238
- team_id=team_id
241
+ env_key=env_key, keys=keys, version=version, team_id=team_id
242
+ )
243
+
244
+
245
+ def update_task(
246
+ task_key: str, prompt: Optional[str] = None, verifier_code: Optional[str] = None
247
+ ):
248
+ """Convenience function to update an existing task.
249
+
250
+ Args:
251
+ task_key: The key of the task to update
252
+ prompt: New prompt text for the task (optional)
253
+ verifier_code: Python code for task verification (optional)
254
+
255
+ Returns:
256
+ TaskResponse containing the updated task details
257
+
258
+ Examples:
259
+ response = fleet.update_task("my-task", prompt="New prompt text")
260
+ response = fleet.update_task("my-task", verifier_code="def verify(env): return True")
261
+ """
262
+ from .global_client import get_client
263
+ from .models import TaskResponse
264
+
265
+ client = get_client()
266
+ return client.update_task(
267
+ task_key=task_key, prompt=prompt, verifier_code=verifier_code
239
268
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.41
3
+ Version: 0.2.43
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.0
@@ -21,22 +21,22 @@ examples/quickstart.py,sha256=1VT39IRRhemsJgxi0O0gprdpcw7HB4pYO97GAYagIcg,3788
21
21
  examples/test_cdp_logging.py,sha256=AkCwQCgOTQEI8w3v0knWK_4eXMph7L9x07wj9yIYM10,2836
22
22
  fleet/__init__.py,sha256=oxI2XvaiRMn15AZpoDHOvX26WlXALHXvqSRP0KkBpAY,3751
23
23
  fleet/base.py,sha256=bc-340sTpq_DJs7yQ9d2pDWnmJFmA1SwDB9Lagvqtb4,9182
24
- fleet/client.py,sha256=OKc68xmt819OxBQ-RfNslfDTHJtX0pPLxdGVdH1WAh8,22102
24
+ fleet/client.py,sha256=d-sGsC8a68czfuaSSm8_Vrkzo-lvamDdwWiCYT2QttE,25865
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=9tDjgcgKPMnf-R_MDh-Ocp_UMbyJ8tJyjb15XqU0N94,12454
29
- fleet/tasks.py,sha256=9ass6zLyuMKC_2gTphZClhVR9RdJml-yeH_GbCu6WOE,8733
28
+ fleet/models.py,sha256=WAiRXa68aXSVbCqmQMn36n9cSlls6YsicV6BbyoeiYQ,12750
29
+ fleet/tasks.py,sha256=P-qcbLZLMVGdJ02Rb8Q2kGIINdnZ_CNIu_FeCVcihcg,9573
30
30
  fleet/types.py,sha256=L4Y82xICf1tzyCLqhLYUgEoaIIS5h9T05TyFNHSWs3s,652
31
31
  fleet/_async/__init__.py,sha256=lrnDD6N9p0Oqpi_djxTnxh8I5F7nA7KNn0khciGmgpg,6747
32
32
  fleet/_async/base.py,sha256=oisVTQsx0M_yTmyQJc3oij63uKZ97MHz-xYFsWXxQE8,9202
33
- fleet/_async/client.py,sha256=fTzc5wtPntihAdydE_g-XIJYojcrfDvvmcOJUgR557k,22597
33
+ fleet/_async/client.py,sha256=4nwpX933fIutctwictasU8oJODwP63vRT0FJxd1b_Do,26170
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=9tDjgcgKPMnf-R_MDh-Ocp_UMbyJ8tJyjb15XqU0N94,12454
37
- fleet/_async/tasks.py,sha256=M1jueEdqzr_q02YrOKgjNLf3C1wRWK5rsPp0bcoxkME,7450
36
+ fleet/_async/models.py,sha256=li5Cii7ASUHCFMFeJIMklyicYczqPez768RxO0Q0F2o,12618
37
+ fleet/_async/tasks.py,sha256=QZFQNW4_Iq2VbGZiPXzKDFXmNrxe4mn-ROGvpGhN7pw,8131
38
38
  fleet/_async/env/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
- fleet/_async/env/client.py,sha256=9GOSkEWNncwTtiZNaJ2vNGrFCPutyan9lBNhD87dAzQ,1059
39
+ fleet/_async/env/client.py,sha256=8dS42VvSgdqfuh96l6cyiLZlKElilmfTeRSZ4LZnFuE,1143
40
40
  fleet/_async/instance/__init__.py,sha256=PtmJq8J8bh0SOQ2V55QURz5GJfobozwtQoqhaOk3_tI,515
41
41
  fleet/_async/instance/base.py,sha256=3qUBuUR8OVS36LzdP6KyZzngtwPKYO09HoY6Ekxp-KA,1625
42
42
  fleet/_async/instance/client.py,sha256=z9q_-dIBwPc1X6VlQOi_aV2v6KOKueJGg8NMyP5iFQM,6082
@@ -49,7 +49,7 @@ fleet/_async/verifiers/__init__.py,sha256=1WTlCNq4tIFbbXaQu5Bf2WppZq0A8suhtZbxMT
49
49
  fleet/_async/verifiers/bundler.py,sha256=Sq0KkqEhM5Ng2x8R6Z4puXvQ8FMlEO7D3-ldBLktPi4,26205
50
50
  fleet/_async/verifiers/verifier.py,sha256=lwVIV5ZpWJhM87tXShtjwN5KP7n5XDcPq0XX7AjV6_E,14343
51
51
  fleet/env/__init__.py,sha256=cS9zCYobM5jypppDMZIQMYd6hOg5f4sgqRXEQ67pckk,676
52
- fleet/env/client.py,sha256=wvZbmHdftkuhAgpzOGiA4Yl_Th9BUIHFR_6JUYg6Nc8,893
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
55
  fleet/instance/client.py,sha256=O6B0A2Z0b5SxOLs4TipZ9Ol8yG-b-LG15vVOKMmd6BQ,5908
@@ -67,10 +67,10 @@ fleet/verifiers/decorator.py,sha256=nAP3O8szXu7md_kpwpz91hGSUNEVLYjwZQZTkQlV1DM,
67
67
  fleet/verifiers/parse.py,sha256=0bAbj9VvT__yU4ZVREUK-Tn9dukh9LCpmfVsgj1DfP4,8508
68
68
  fleet/verifiers/sql_differ.py,sha256=dmiGCFXVMEMbAX519OjhVqgA8ZvhnvdmC1BVpL7QCF0,6490
69
69
  fleet/verifiers/verifier.py,sha256=53oBWAf0yy3bZmZx9eH9AWIf65H7OP2UUm0YwWCL6Mc,14286
70
- fleet_python-0.2.41.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
70
+ fleet_python-0.2.43.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.41.dist-info/METADATA,sha256=kgKHMZOtj-gOUUcMJQPqp-2f-QYWLfZZwyMkjKd0Mzg,3304
74
- fleet_python-0.2.41.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
75
- fleet_python-0.2.41.dist-info/top_level.txt,sha256=_3DSmTohvSDf3AIP_BYfGzhwO1ECFwuzg83X-wHCx3Y,23
76
- fleet_python-0.2.41.dist-info/RECORD,,
73
+ fleet_python-0.2.43.dist-info/METADATA,sha256=fgD6_tIyLKKOXNT_qoyf98t12w3ITDd1vAyCYFh7_KU,3304
74
+ fleet_python-0.2.43.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
75
+ fleet_python-0.2.43.dist-info/top_level.txt,sha256=_3DSmTohvSDf3AIP_BYfGzhwO1ECFwuzg83X-wHCx3Y,23
76
+ fleet_python-0.2.43.dist-info/RECORD,,