-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Add support for durable execution with Prefect #3074
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add support for durable execution with Prefect #3074
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a couple small thoughts
|
||
### Agent Run Context and Dependencies | ||
|
||
Prefect persists task results using [Pydantic's serialization](https://docs.pydantic.dev/latest/concepts/serialization/). This means the [dependencies](../dependencies.md) object provided to [`PrefectAgent.run()`][pydantic_ai.durable_exec.prefect.PrefectAgent.run] or [`PrefectAgent.run_sync()`][pydantic_ai.durable_exec.prefect.PrefectAgent.run_sync], and tool outputs should be serializable using Pydantic's `TypeAdapter`. You may also want to keep the inputs and outputs reasonably sized for optimal performance. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe we could mention the escape hatches you have, ie impl a serializer on the model or field type
def _strip_timestamps( | ||
obj: Any | dict[str, Any] | list[Any] | tuple[Any, ...], | ||
) -> Any: | ||
"""Recursively convert dataclasses to dicts, excluding timestamp fields.""" | ||
if is_dataclass(obj) and not isinstance(obj, type): | ||
result: dict[str, Any] = {} | ||
for f in fields(obj): | ||
if f.name != 'timestamp': | ||
value = getattr(obj, f.name) | ||
result[f.name] = _strip_timestamps(value) | ||
return result | ||
elif _is_dict(obj): | ||
return {k: _strip_timestamps(v) for k, v in obj.items() if k != 'timestamp'} | ||
elif _is_list(obj): | ||
return [_strip_timestamps(item) for item in obj] | ||
elif _is_tuple(obj): | ||
return tuple(_strip_timestamps(item) for item in obj) | ||
return obj |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe we don't need it, but looks like a special case of visit_collection
retries: int | ||
"""Maximum number of retries for the task.""" | ||
|
||
retry_delay_seconds: float | list[float] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prefect.types.TaskRetryDelaySeconds
Summary
This PR introduces an integration with Prefect to enable durable, fault-tolerant execution of Pydantic AI agents.
Examples
Run a
pydantic-ai
agent as a Prefect flowThe
PrefectAgent
class wraps anAgent
and instruments it so that.run
calls are executed as a Prefect flow, and all tool and model calls are executed as Prefect tasks.Customize underlying task behavior
Create an agent service to enable scheduling and remote execution
Implementation Details
The integration is implemented through:
Concerns
Complex Serialization Requirements
The .serve() method requires the
Agent
to be picklable because each run is executed in a separate process. I've added__getstate__
and__setstate__
implementations forPrefectAgent
, but they are complex and may rely on too much internalpydantic-ai
functionality.