Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fleet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
from . import global_client as _global_client
from ._async import global_client as _async_global_client

__version__ = "0.2.125"
__version__ = "0.2.128"

__all__ = [
# Core classes
Expand Down
2 changes: 1 addition & 1 deletion fleet/_async/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
from .. import env
from . import global_client as _async_global_client

__version__ = "0.2.125"
__version__ = "0.2.128"

__all__ = [
# Core classes
Expand Down
2 changes: 1 addition & 1 deletion fleet/_async/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
try:
from .. import __version__
except ImportError:
__version__ = "0.2.125"
__version__ = "0.2.128"

logger = logging.getLogger(__name__)

Expand Down
30 changes: 29 additions & 1 deletion fleet/_async/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,8 @@ async def execute_verifier_remote(
timeout: Optional[int] = 30,
needs_upload: bool = True,
verifier_runtime_version: Optional[str] = None,
async_: bool = False,
poll_interval: float = 5.0,
) -> VerifiersExecuteResponse:
return await _execute_verifier_remote(
self._load_client,
Expand All @@ -495,6 +497,8 @@ async def execute_verifier_remote(
timeout,
needs_upload,
verifier_runtime_version,
async_=async_,
poll_interval=poll_interval,
)

def __getstate__(self):
Expand Down Expand Up @@ -1725,6 +1729,8 @@ async def _execute_verifier_remote(
timeout: Optional[int] = 30,
needs_upload: bool = True,
verifier_runtime_version: Optional[str] = None,
async_: bool = False,
poll_interval: float = 5.0,
) -> VerifiersExecuteResponse:
# Pickle args and kwargs together
# The first arg should be None as a placeholder for env
Expand Down Expand Up @@ -1752,6 +1758,11 @@ async def _execute_verifier_remote(
if verifier_runtime_version:
request_data["verifier_runtime_version"] = verifier_runtime_version

# Async submit-and-poll path. When async_ is False the behavior below is
# identical to the original synchronous request.
if async_:
request_data["async"] = True

# Debug logging
# logger.debug(
# f"Sending verifier execute request: key={key}, sha256={bundle_sha[:8]}..., function_name={function_name}"
Expand All @@ -1773,4 +1784,21 @@ async def _execute_verifier_remote(
response_json = response.json()
# logger.debug(f"Verifier execute response: {response_json}")

return VerifiersExecuteResponse(**response_json)
if not async_:
return VerifiersExecuteResponse(**response_json)

# Async: the submit returns a job handle; poll until the job reaches a
# terminal state (completed/failed). Branch on `status`, never `success`.
job_id = response_json.get("job_id")
if not job_id:
# No job handle returned (e.g. server ran it inline) - surface as-is.
return VerifiersExecuteResponse(**response_json)

while True:
poll_response = await client.request(
"GET", f"/v1/verifiers/jobs/{job_id}"
)
poll_json = poll_response.json()
if poll_json.get("status") in ("completed", "failed"):
return VerifiersExecuteResponse(**poll_json)
await asyncio.sleep(poll_interval)
16 changes: 16 additions & 0 deletions fleet/_async/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,12 @@ class VerifiersExecuteRequest(BaseModel):
display_src: Optional[str] = Field(
None, description="Display source code", title="Display Src"
)
async_: Optional[bool] = Field(
None,
alias="async",
description="Submit asynchronously and return a job handle instead of waiting",
title="Async",
)


class VerifiersExecuteResponse(BaseModel):
Expand Down Expand Up @@ -302,6 +308,16 @@ class VerifiersExecuteResponse(BaseModel):
stdout: Optional[str] = Field(
None, description="Captured stdout from execution", title="Stdout"
)
status: Optional[str] = Field(
None,
description="Job status for async execution (pending/running/completed/failed)",
title="Status",
)
job_id: Optional[str] = Field(
None,
description="Job handle for async execution; poll GET /v1/verifiers/jobs/{job_id}",
title="Job Id",
)


class DescribeResponse(BaseModel):
Expand Down
60 changes: 52 additions & 8 deletions fleet/_async/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,23 @@ class Config:
# Allow arbitrary types for the verifier field
arbitrary_types_allowed = True

def verify(self, env, *args, **kwargs) -> float:
def verify(
self,
env,
*args,
async_: bool = False,
poll_interval: float = 5.0,
**kwargs,
) -> float:
"""Verify the task using the verifier function (sync version).

For sync environments, calls the sync verifier directly.
For async verifiers, automatically runs them with asyncio.run().

When ``async_`` is True the verifier is submitted to run in the
background and polled (every ``poll_interval`` seconds) until it
completes, avoiding HTTP/edge idle timeouts for long-running
verifiers. When False the behavior is unchanged.
"""
# If verifier doesn't exist but verifier_func does, rebuild it
if not self.verifier and self.verifier_func:
Expand All @@ -95,7 +107,9 @@ def verify(self, env, *args, **kwargs) -> float:
import asyncio
import inspect

result = self.verifier.remote(env, *args, **kwargs)
result = self.verifier.remote(
env, *args, async_=async_, poll_interval=poll_interval, **kwargs
)

# If the result is a coroutine, we need to run it
if inspect.iscoroutine(result):
Expand All @@ -115,18 +129,27 @@ def verify(self, env, *args, **kwargs) -> float:
else:
raise ValueError("No verifier function found for this task")

async def verify_async(self, *args, **kwargs) -> float:
async def verify_async(
self, *args, async_: bool = False, poll_interval: float = 5.0, **kwargs
) -> float:
"""Verify the task using the verifier function (async version).

For async environments, awaits the async verifier.
Works with both sync and async verifiers in async contexts.

When ``async_`` is True the verifier is submitted to run in the
background and polled (every ``poll_interval`` seconds) until it
completes, avoiding HTTP/edge idle timeouts for long-running
verifiers. When False the behavior is unchanged.
"""
# If verifier doesn't exist but verifier_func does, rebuild it
if not self.verifier and self.verifier_func:
self._rebuild_verifier()

if self.verifier:
result = self.verifier.remote(*args, **kwargs)
result = self.verifier.remote(
*args, async_=async_, poll_interval=poll_interval, **kwargs
)
# If it's a coroutine, await it
import inspect

Expand All @@ -138,19 +161,26 @@ async def verify_async(self, *args, **kwargs) -> float:
raise ValueError("No verifier function found for this task")

async def verify_detailed_async(
self, *args, **kwargs
self, *args, async_: bool = False, poll_interval: float = 5.0, **kwargs
) -> "VerifiersExecuteResponse":
"""Verify the task and return the full execute response model.

For async environments, awaits the async verifier.
Works with both sync and async verifiers in async contexts.

When ``async_`` is True the verifier is submitted to run in the
background and polled (every ``poll_interval`` seconds) until it
completes, avoiding HTTP/edge idle timeouts for long-running
verifiers. When False the behavior is unchanged.
"""
# If verifier doesn't exist but verifier_func does, rebuild it
if not self.verifier and self.verifier_func:
self._rebuild_verifier()

if self.verifier:
result = self.verifier.remote_with_response(*args, **kwargs)
result = self.verifier.remote_with_response(
*args, async_=async_, poll_interval=poll_interval, **kwargs
)
# If it's a coroutine, await it
import inspect

Expand All @@ -161,11 +191,23 @@ async def verify_detailed_async(
else:
raise ValueError("No verifier function found for this task")

def verify_detailed(self, env, *args, **kwargs) -> "VerifiersExecuteResponse":
def verify_detailed(
self,
env,
*args,
async_: bool = False,
poll_interval: float = 5.0,
**kwargs,
) -> "VerifiersExecuteResponse":
"""Verify the task and return the full execute response model (sync version).

For sync environments, calls the sync verifier directly.
For async verifiers, automatically runs them with asyncio.run().

When ``async_`` is True the verifier is submitted to run in the
background and polled (every ``poll_interval`` seconds) until it
completes, avoiding HTTP/edge idle timeouts for long-running
verifiers. When False the behavior is unchanged.
"""
# If verifier doesn't exist but verifier_func does, rebuild it
if not self.verifier and self.verifier_func:
Expand All @@ -176,7 +218,9 @@ def verify_detailed(self, env, *args, **kwargs) -> "VerifiersExecuteResponse":
import inspect

# Check if verifier has remote_with_response method (for decorated verifiers)
result = self.verifier.remote_with_response(env, *args, **kwargs)
result = self.verifier.remote_with_response(
env, *args, async_=async_, poll_interval=poll_interval, **kwargs
)

# If the result is a coroutine, we need to run it
if inspect.iscoroutine(result):
Expand Down
43 changes: 38 additions & 5 deletions fleet/_async/verifiers/verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,25 @@ async def __call__(self, env: AsyncEnv, *args, **kwargs) -> float:
# Return error score 0
return 0.0

async def remote(self, env: AsyncEnv, *args, **kwargs) -> float:
"""Remote execution of the verifier function with SHA-based bundle caching."""
response = await self.remote_with_response(env, *args, **kwargs)
async def remote(
self,
env: AsyncEnv,
*args,
async_: bool = False,
poll_interval: float = 5.0,
**kwargs,
) -> float:
"""Remote execution of the verifier function with SHA-based bundle caching.

When ``async_`` is True the verifier is submitted to run in the
background and the result is polled (every ``poll_interval`` seconds)
until it completes — this avoids HTTP/edge idle timeouts for
long-running verifiers. When False the behavior is unchanged (the
request blocks until the verifier finishes).
"""
response = await self.remote_with_response(
env, *args, async_=async_, poll_interval=poll_interval, **kwargs
)

# Handle response
if response.stdout:
Expand Down Expand Up @@ -228,9 +244,20 @@ def _is_bundle_not_found_error(self, error: Exception) -> bool:
)

async def remote_with_response(
self, env: "AsyncEnv", *args, **kwargs
self,
env: "AsyncEnv",
*args,
async_: bool = False,
poll_interval: float = 5.0,
**kwargs,
) -> "VerifiersExecuteResponse":
"""Remote execution of the verifier function that returns the full response model."""
"""Remote execution of the verifier function that returns the full response model.

When ``async_`` is True the verifier is submitted asynchronously and
polled (every ``poll_interval`` seconds) until it reaches a terminal
state; the returned response is the completed/failed job result. When
False the request blocks until the verifier finishes (unchanged).
"""
args_array = list(args)
args_array.append({"env": env.instance_id})
args = tuple(args_array)
Expand All @@ -254,6 +281,8 @@ async def remote_with_response(
kwargs=kwargs,
needs_upload=True,
verifier_runtime_version=self.verifier_runtime_version,
async_=async_,
poll_interval=poll_interval,
)

# logger.debug(f"Bundle {bundle_sha[:8]}... uploaded successfully")
Expand All @@ -271,6 +300,8 @@ async def remote_with_response(
kwargs=kwargs,
needs_upload=False,
verifier_runtime_version=self.verifier_runtime_version,
async_=async_,
poll_interval=poll_interval,
)

return response
Expand All @@ -292,6 +323,8 @@ async def remote_with_response(
kwargs=kwargs,
needs_upload=True,
verifier_runtime_version=self.verifier_runtime_version,
async_=async_,
poll_interval=poll_interval,
)
return response
else:
Expand Down
2 changes: 1 addition & 1 deletion fleet/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
try:
from . import __version__
except ImportError:
__version__ = "0.2.125"
__version__ = "0.2.128"

logger = logging.getLogger(__name__)

Expand Down
Loading
Loading