TL;DR:
- Prefect 3 provides Python-native orchestration for AI agent workflows with minimal boilerplate — decorate your functions, get retries, concurrency control, and observability free
- Work pools and workers let you run agents on any infrastructure: local processes, Docker containers, Kubernetes pods, or serverless
- Prefect’s
ConcurrencyLimitV2and task caching are the two features that most directly address the cost and reliability challenges of production AI pipelines
When you start building AI agent workflows beyond a single script, you hit a predictable set of problems: a mid-pipeline LLM call fails and you restart from scratch, you have no visibility into which step is running, you accidentally fire ten parallel API calls and hit rate limits, or you can’t tell whether a slow pipeline is stuck or just slow. Prefect 3 solves all of these problems with a design philosophy that feels natural to Python developers — it’s decorators on your existing functions, not a new programming model.
This guide covers the patterns most relevant to AI agent pipelines specifically, rather than the data engineering use cases Prefect is better known for.
The Core Abstractions
Prefect 3’s model is two-level: flows are the top-level orchestration unit, tasks are the individual steps. Both are just Python functions with decorators.
from prefect import flow, task
from prefect.tasks import exponential_backoff
@task(retries=3, retry_delay_seconds=exponential_backoff(backoff_factor=2), cache_expiration=timedelta(hours=1))
def call_llm(prompt: str, model: str = "claude-sonnet-4-6") -> str:
# Your LLM call here
return response.content
@task
def extract_structured_data(raw_response: str, schema: type) -> dict:
# Parse and validate LLM output
return parsed
@flow(name="document-analysis-pipeline")
def analyse_document(document_path: str):
text = extract_text(document_path)
raw = call_llm(f"Analyse this document: {text}")
result = extract_structured_data(raw, DocumentSchema)
store_result(result)
return result
The retries=3 with exponential backoff on call_llm is the immediate win: any transient API failure retries automatically with increasing delays. The cache_expiration=timedelta(hours=1) means if you re-run the pipeline with the same inputs within an hour, it returns the cached result without calling the API again. This is significant for development iteration and for pipelines that partially fail.
Parallel Execution and Concurrency Control
The most common AI agent pipeline pattern involves running the same operation across many inputs, and Prefect’s .map() syntax handles this cleanly:
from prefect import flow, task
from prefect.concurrency.sync import concurrency
@task
def process_chunk(chunk: str) -> dict:
with concurrency("openai-api", occupy=1):
return call_llm(chunk)
@flow
def process_documents(document_paths: list[str]):
chunks = [extract_text.submit(p) for p in document_paths]
# .map() runs process_chunk for each chunk in parallel
results = process_chunk.map(chunks)
return results
The concurrency("openai-api", occupy=1) creates a named concurrency slot. You define the limit in Prefect’s UI or API: if you set openai-api to 5, no more than 5 parallel tasks will hold that slot at once, regardless of how many you’ve submitted. This is how you prevent rate limit errors without manually throttling your submission code.
For AI agent workflows where you’re running many LLM calls across a batch, this pattern is more reliable than manual semaphores or sleep-based throttling.
Work Pools for Infrastructure Flexibility
Prefect 3 unified its infrastructure model around work pools. A work pool defines where tasks run; a worker polls the pool and executes tasks. This lets you run the same flow definition across different environments without changing code.
# Start a local process worker
prefect worker start --pool "local-agent-pool" --type process
# Or a Docker worker
prefect worker start --pool "docker-agent-pool" --type docker
For AI agent pipelines, the most useful pattern is mixing pool types: use a cheap local pool for lightweight pre/post-processing tasks, and a Docker pool (or Kubernetes pool) with a GPU-enabled image for any steps that need local model inference. You can specify which pool each task targets using task_runner or by routing flows to specific pools.
Observability That Actually Helps
When an AI pipeline fails, you need to know which step failed, what the inputs were, and what the error was. Prefect’s default observability gives you this without configuration:
- Every flow run and task run gets a unique ID and a timeline view in the UI
- Task inputs and outputs are logged (configurable for sensitive data)
- Failed tasks show the full Python traceback
- You can inspect the state of any past run
For LLM-specific observability, combine Prefect with a tracing tool like Langfuse or LangSmith. Prefect handles workflow-level orchestration visibility; the LLM tracing tool handles prompt/completion visibility. They operate at different levels and complement each other.
from prefect import task
from langfuse.decorators import observe
@task
@observe() # LangSmith or Langfuse decorator wraps the LLM call
def call_llm_with_tracing(prompt: str) -> str:
return llm_client.invoke(prompt)
Practical Patterns for Agent Pipelines
Human-in-the-loop pauses: Prefect flows can suspend execution and wait for external input, making it practical to build approval steps into long-running pipelines.
from prefect.input import RunInput
class ApprovalInput(RunInput):
approved: bool
notes: str = ""
@flow
def pipeline_with_approval():
draft = generate_draft()
# Flow suspends here and sends notification
approval = ApprovalInput.receive(timeout=3600) # Wait up to 1 hour
if approval.approved:
publish(draft)
Scheduled runs: Pair Prefect with cron-style scheduling to run agent workflows on a recurring basis without external schedulers.
from prefect.schedules import CronSchedule
@flow(name="daily-report-agent")
def daily_report():
data = fetch_data()
analysis = run_agent(data)
send_report(analysis)
# Deploy with a schedule
daily_report.serve(
name="daily-report-deployment",
cron="0 8 * * *" # 8 AM daily
)
Artifact storage: Prefect lets tasks produce artifacts — structured data linked to a run — which is useful for recording LLM outputs, evaluation scores, or token usage alongside orchestration metadata.
When to Use Prefect vs Alternatives
Prefect sits in a specific position relative to other options. Use Prefect when you want Python-native orchestration with good observability and minimal operational overhead. Use Temporal if you need guaranteed exactly-once execution for financial or safety-critical workflows. Use Inngest or Trigger.dev if you’re building TypeScript serverless functions. Use Airflow if you’re in a data engineering org that already runs it.
The Prefect sweet spot for AI agents: Python-first teams building pipelines that mix LLM calls with data processing, need retries and concurrency control, and want a hosted UI without running their own infrastructure. Prefect Cloud has a free tier that covers most small-to-medium agent pipeline workloads.
Getting started takes about 10 minutes:
pip install prefect
prefect cloud login # or prefect server start for self-hosted
prefect worker start --pool "default-agent-pool" --type process
Then decorate your functions and run your flow. The orchestration layer appears around your existing code with minimal rewriting.