OpenAI Agents SDK¶
You have an agent built with the OpenAI Agents SDK and you want it to run as an Airflow
task, reading data through connections your deployment already manages. Keep the agent
as it is, and add Airflow’s toolsets to its tools with
AirflowTools.
Note
AirflowTools is experimental: it may change in a minor release of this provider.
The framework-neutral tool interface it builds on is stable.
See Stable and experimental features.
Run an OpenAI Agents SDK agent in a task¶
The agent below answers a question about a database with a
SQLToolset. Its model’s API key, and
optionally the base URL of another OpenAI-compatible endpoint, come from an Airflow
connection:
@dag(tags=["example"])
def example_openai_agents_agent():
"""Answer a question about a database with an OpenAI Agents SDK agent."""
@task
def run_openai_agent(question: str = DEFAULT_QUESTION) -> str:
from agents import Agent, OpenAIChatCompletionsModel, Runner, set_trace_processors
from openai import AsyncOpenAI
from airflow.providers.common.ai.tools.openai_agents import AirflowTools, AirflowTraceProcessor
from airflow.providers.common.ai.tools.tracing import agent_framework_tracing
from airflow.providers.common.ai.toolsets.sql import SQLToolset
# Record the SDK's traces as the task's OpenTelemetry spans instead of sending them to OpenAI.
set_trace_processors([AirflowTraceProcessor()])
llm = BaseHook.get_connection(LLM_CONN_ID)
agent = Agent(
name="analyst",
model=OpenAIChatCompletionsModel(
model=LLM_MODEL,
openai_client=AsyncOpenAI(api_key=llm.password, base_url=llm.host or None),
),
instructions=(
"You are a SQL analyst. Use list_tables and get_schema to explore "
"the database, then run read-only queries to answer the question."
),
tools=AirflowTools(SQLToolset(db_conn_id=DB_CONN_ID)),
)
with agent_framework_tracing():
return Runner.run_sync(agent, question).final_output
run_openai_agent()
Everything except AirflowTools is plain OpenAI Agents SDK: the agent, its runner,
handoffs, guardrails and your own function tools work as the SDK documentation
describes. AirflowTools is a list of FunctionTool objects, so add your own tools
next to them with tools=[*AirflowTools(warehouse), my_tool].
What the tools give the agent¶
AirflowTools takes the same arguments as the Strands plugin (see Strands Agents):
toolsets that implement ToolProvider, such
as SQLToolset and HookToolset, and individual
AirflowTool objects. MCPToolset is the
exception: attach an MCP server with the SDK’s own mcp_servers=. Each tool keeps the
toolset tool’s name, description and argument schema, runs through the toolset, and
passes its result through Airflow’s secret masker.
The schema goes to the model as it is, with
strict_json_schema=False. The SDK’s strict mode would mark every argument required, including the ones a toolset leaves optional.A failure the model can correct, such as a rejected SQL statement or an invalid argument, reaches it as
{"error": ...}. The tool’s retry limit bounds how many times in a row the model can try again, counted afresh on every run of the agent.A refusal the tool reports as final, such as a file that does not exist, also comes back as
{"error": ...}without counting against that limit, and so do arguments that are not a JSON object. Themax_turnsargument ofRunner.runbounds how long a model can keep asking.Any other failure raises
ToolCallErrorout ofRunner.runandRunner.run_sync, with the message masked. The task fails and Airflow’s own retry takes over.
Outside AgentOperator, a toolset’s connection ID is used as written: it is not
rendered as a template.
An agent used as a tool¶
Agent.as_tool() turns any exception from the inner agent’s run into text for the
outer model by default, so a tool failure that should fail the task would only reach the
orchestrating model as a message. When the inner agent holds AirflowTools, pass
failure_error_function=None:
analyst = Agent(name="analyst", model=model, tools=AirflowTools(warehouse))
lead = Agent(
name="lead",
model=model,
tools=[analyst.as_tool("ask_analyst", "Ask the SQL analyst.", failure_error_function=None)],
)
The ToolCallError then fails the outer run, and with it the task. The same applies to
a function tool of your own that runs another agent: let the exception propagate. A
nested run shares the outer run’s retry count for each tool.
Tracing¶
The SDK has a tracing system of its own rather than OpenTelemetry. By default it sends
every run’s traces to OpenAI’s Traces backend using OPENAI_API_KEY from the
environment, whatever model the agent runs, including a self-hosted one, and the traces
hold the prompts and model output. Tool results are masked before the model sees them;
nothing else in those traces is.
AirflowTraceProcessor is a trace
processor for the SDK that records each trace as OpenTelemetry spans under the task’s
span instead. The example replaces the SDK’s processors with it, so nothing goes to
OpenAI, and runs the agent inside
agent_framework_tracing(), so the spans
carry the task’s identity:
from agents import Runner, set_trace_processors
from airflow.providers.common.ai.tools.openai_agents import AirflowTraceProcessor
from airflow.providers.common.ai.tools.tracing import agent_framework_tracing
set_trace_processors([AirflowTraceProcessor()])
with agent_framework_tracing():
answer = Runner.run_sync(agent, question).final_output
set_trace_processors replaces every processor registered with the SDK. To keep the
export to OpenAI, or a processor another instrumentation registered, add this one with
agents.add_trace_processor instead. The spans use the names of the OpenTelemetry GenAI
conventions (invoke_agent, chat with the model and token counts,
execute_tool), and the SDK’s other spans are named openai_agents.<kind>. Prompts, completions, and tool arguments and results are left
out unless [common.ai] otel_export_enabled and capture_content are both on, and pass
through Airflow’s secret masker when they are recorded; see Observability (OpenTelemetry tracing).
Use AirflowTraceProcessor when span content should follow [common.ai] capture_content
and Airflow’s secret masker.
To turn the SDK’s tracing off altogether, call agents.set_tracing_disabled(True) or set
OPENAI_AGENTS_DISABLE_TRACING=1 in the worker’s environment. That stops
AirflowTraceProcessor from receiving anything too.
Differences from AgentOperator¶
A task retry runs the agent again from the start, so tools that change data must be safe to repeat.
There is no human review step like
AgentOperator(enable_hitl_review=True), and the SDK’s ownneeds_approvalinterruptions are not connected to Airflow’s approval UI.
Installation¶
pip install "apache-airflow-providers-common-ai[sql]" "openai-agents>=0.20.0"
The SDK requires openai>=3 from 0.21.0 on. Airflow’s constraints file pins openai
below 3, so with that file the command above installs openai-agents 0.20, the last
release that accepts openai 2.x. AirflowTools works with both.