- The structure of an LLM orchestration pipeline
- How to run the AI framework you already use inside a Dag
- How the Common AI provider exposes model configuration as task parameters
- Common LLM orchestration patterns
- How to make an LLM task production-ready
Assumed knowledge
To get the most out of this guide, you should have existing knowledge of:- Airflow basics. See Introduction to Apache Airflow®.
- Airflow decorators. See Introduction to the TaskFlow API and Airflow decorators.
- Airflow connections. See Manage connections in Apache Airflow®.
- Basic familiarity with LLM prompting.
Why Airflow for LLM orchestration
Running a single model inference as an Airflow task offers a lot of advantages: harness and model provider independence, credentials and tools governed through Airflow connections, dependencies between the AI task and the deterministic tasks around it, a choice of scheduling options and execution environments, dynamic task mapping, retries, and human-in-the-loop steps. See Why Airflow for AI orchestration for the full list.Anatomy of an LLM orchestration pipeline
An LLM orchestration pipeline has the same general structure as other pipelines, like ELT/ETL, with the model call sitting in the transform position.- Upstream tasks assemble the context. They query a database, read files from object storage, call an API, or pull results from an upstream Dag, then format that data into the user prompt. Deterministic checks, such as data quality tests, belong here too.
- One task calls the model API. The system prompt contains the task instructions and only changes when you change the code. The user prompt contains the data that differs per run. The response is pushed to XCom.
- Downstream tasks consume the result. They load it into a warehouse, branch on it, hand it to an AI or human reviewer, or feed it into another model call.
LLM tasks aren’t idempotent, so a rerun or a backfill can produce a different result from the same input. See Non-determinism and idempotency for how to design around that.
Start with the harness you already have
If your team already utilizes LangChain, LangGraph, CrewAI, or Temporal, you don’t have to rewrite your code to orchestrate it with Airflow. Any harness runs inside a regular@task, because a @task is just Python. Each of the following examples implements the same single model call pattern, a support ticket summarized into a typed object, using different frameworks.
While the following examples utilize four of the most popular harnesses, the same principle applies to many more including custom implementations, as long as they have a Python SDK.
- LangChain
- LangGraph
- CrewAI
- Temporal
The most direct case: one chat model, structured output, one call.
summarize_langchain.py
@task task. The model credentials are stored in the harness framework’s own configuration instead of an Airflow connection, human review is done outside of Airflow and not easily accessible in downstream tasks, and changing model providers means needing significant edits to your task code.
The Common AI provider, covered in the following section, moves each of those into Airflow: the credentials into a connection, the review step into the Airflow UI, and the model choice into configuration.
Move to the Common AI provider
The Common AI provider is the Airflow-native harness. It’s built on PydanticAI, works with any compatible model provider, and exposes credentials, output validation, spend limits, human review, and tracing as task parameters rather than as framework code. The same single model call as a@task.llm task:
summarize_native.py
See Orchestrate AI tasks with Apache Airflow® and the Common AI provider for the full parameter reference.
Keep your model configuration in Airflow
Moving to the Common AI provider isn’t all or nothing. If you want to keep writing LangChain code but stop hardcoding model configuration in it,LangChainHook builds a LangChain chat model from an Airflow connection:
summarize_hook.py
langchain extra:
Tools bridge in both directions too: PydanticAI wraps existing LangChain tools as a toolset a Common AI agent can use, and the provider converts an Airflow toolset into LangChain tools. Tools belong to agents rather than single model calls, so see Move tools between harnesses for both bridges, and Recoverability for how to enable durable execution with the Common AI provider, to save the output of model and tool calls in between Airflow retries of a task.
Make LLM tasks production-ready
From prototype to production covers the three properties that separate a prototype from a production pipeline: control, observability, and recoverability. This section is what each one means for a single model call.Control
- Cap the spend.
usage_limitsrestricts number of requests, tokens, and tool calls per run. Exceeding a limit raisesUsageLimitExceededand fails the task. - Constrain the output.
output_typeis enforced by the harness. A task either returns an object matching your schema or it fails. Be specific about the field type: apriorityfield typed asstrinvites the model to answer"Critical (P0)"in one run and"P0"in the next, which potentially causes issues in downstream tasks. The same field typed asLiteral["P0", "P1", "P2", "P3", "P4"]can only ever return one of those five values. - Add a human gate for anything outward facing. Setting
require_approval,allow_modifications, andapproval_timeoutallows output to be reviewed, edited, and approved before a downstream task uses it. See Generation with human approval.
Observability
The Common AI provider emits OpenTelemetry GenAI spans for each model call and tool call, and routes them through the existing Airflow OpenTelemetry exporter. The spans nest under the task span, so they are associated with the Dag ID, run ID, and try number, and you can see token usage per task run in whatever backend you use for OpenTelemetry. Enable tracing by setting the following environment variables for your exporter and collector:AIRFLOW__TRACES__OTEL_ON isn’t enabled in the worker process, the provider emits no spans at all.
Tracing tells you what a run cost and how long it took. It is usually gathered in AI model eval pipelines to connect model actions and token use to AI output quality.
Recoverability
At scale, some fraction of model calls fail for transient reasons: rate limits, provider outages, or timeouts. Airflow retries handle those, but not every failure needs the same response. Waiting fixes a rate limit but not an expired API key. Airflow’s retry policies let you decide that per exception type.ExceptionRetryPolicy maps an exception to an action, with an optional delay and a reason that gets logged. Use it first: it’s deterministic, costs nothing, and behaves the same on every run. See Retry policies.
For AI tasks, the failures worth retrying aren’t always ones you can enumerate in advance. Providers change their error strings, and the same status code can mean “wait two seconds” or “you are out of quota for the month”. The Common AI provider’s LLMRetryPolicy covers that case by asking a model to classify the failure:
llm_retry_policy.py
rate_limit, auth, or data, whether to retry, a suggested delay, and its reasoning, which is written to the task log. A rate limit is retried with the delay the model suggests, which overrides the task’s own retry_delay. An error classified as auth fails immediately instead of burning five paid retries.
fallback_rules is not a fast path for errors you already anticipated. The model is consulted on every failure, and the rules are evaluated as an ExceptionRetryPolicy only when the classification call itself fails, for example when your model provider is the system that’s down. Without them, the task falls back to its normal retry behavior.
Two things follow from classification running on every failed attempt. It sends that task’s exception message to a model provider, so the policy pushes the message through Airflow’s secret masker by default, with redact_exception, redactor, and max_exception_length to control what leaves your environment. It also spends a model call per failure, which is why model_id is worth pointing at a small, cheap model for this job.
Retry policies require Airflow 3.3 or later, and work with all Airflow decorators and operators.
LLM orchestration patterns
The following patterns cover a lot of common single-call use cases, and they can be combined within the same Dag.Summarization
Condense long text into a short form for human review or downstream storage. The following example illustrates how to summarize legal documents. Define the summary as a schema to be able to use individual fields in downstream tasks:summarization.py
Because a batch usually contains many documents, map the task dynamically over the list of inputs to get one summary per document. If a source document exceeds the model’s context window, add a chunking step upstream.
Classification and routing
Classify unstructured input against criteria written in natural language, then use the classification to decide what the Dag does next. When the classification is only used for routing, let the model pick the branch directly with@task.llm_branch:
routing.py
When you need the classification itself as data, for example to tag a record and route on its priority, use
@task.llm with a structured output and a regular @task.branch downstream. That keeps the classification available to every downstream task instead of only to the branch.Extraction of structured data
Pull specific fields out of free-form text such as contracts, transcripts, or scanned documents, then load them into a database or use them as features for a traditional machine learning model. The AI step exists in service of an otherwise conventional ETL or MLOps pipeline. When using an LLM to generate features, make sure to keep the extraction task in a shared module so a training Dag and an inference Dag use exactly the same schema and prompt:extraction.py
Mark AI-derived features as such, for example with an
ai_ column prefix, because a backfill can produce different values from the same source text.Transformation
Rewrite content according to rules: convert between formats, translate between human languages, or port code from one programming language to another. Inputs and outputs usually match in length and general structure. For localization use cases, you’ll often want to translate several text chunks into several languages at the same time. In Airflow you can use dynamic task mapping to map over multiple parameters:transformation.py
Generation with human approval
Ask a model to write something new: a drafted reply, an executive summary, a personalized recommendation, or a block of configuration. Unlike the preceding patterns, the output is usually the deliverable rather than an input to another task, and it’s often read by someone outside your organization. That raises the cost of a bad output and the importance of requiring human approval withrequire_approval=True, and adding the option to change the output with allow_modifications=True.
approval.py
awaiting_input state until it receives a response from a reviewer.

allow_modifications=True they can edit the text first, and the edited version becomes the task result pushed to XCom, which is accessible in downstream tasks. Alternatively, required actions can be responded to using the Airflow REST API.

approval_timeout passes with no response, the task fails with a TimeoutError.
This is a single approve, reject, or edit decision on one finished output. It’s distinct from
@task.agent’s enable_hitl_review, which is an iterative loop where a reviewer sends feedback and the agent regenerates, and which can be accessed under its own HITL Review tab. See Agent orchestration.If the decision is more complex than approve or reject, for example picking one of several downstream paths or supplying free-text input, use the human-in-the-loop operators in the standard provider as a downstream task instead. See Human-in-the-loop workflows with Apache Airflow®.Text-to-SQL
Text-to-SQL is a special case of generation where the generated asset is code that runs inside the same pipeline.@task.llm_sql can read information from a database, generate SQL, and push it to XCom. This decorator does not execute the generated SQL. A separate operator runs the query, which is what keeps SQL generation and execution separate and allows you to use different connection IDs with different permissions, as well as to add a human-in-the-loop task in between, if necessary.
The @task.llm_sql decorator reads table schemas through a DbApiHook and parses the generated SQL, so it needs the sql extra:
sql_generation.py
require_approval=True the task enters the awaiting_input state after the model call has returned the generated SQL. A reviewer opens the task instance in the Airflow UI, reads the generated SQL under Required Action, edits it when allow_modifications is set, and approves or rejects it. If approval_timeout passes with no response, the task fails.
When using @task.llm, the model only gets one attempt at generating SQL with whatever schema context you gave it. When a question needs several queries and reasoning over their results, use an agent instead.
Next steps
- Add tools and multi-step reasoning with Agent orchestration.
- Evaluate the output of the AI with AI model evals.
- Measure whether the AI data product is useful and optimize for business value with AI product evals.
- Look up decorator and operator parameters in Orchestrate AI tasks with Apache Airflow® and the Common AI provider.