kaxil opened a new pull request, #76752:
URL: https://github.com/apache/airflow/pull/76752
`AirflowTools(SQLToolset(...).filtered(...))` failed with `AttributeError:
'FilteredToolset' object has no attribute 'airflow_tools'`, in every adapter
(OpenAI Agents SDK, Strands, ADK, the Anthropic tool runner, the Claude Agent
SDK). `.filtered()` is how the toolsets and MCP guides tell users to offer only
some tools, so moving from `AgentOperator` to an agent of your own lost it, and
the OpenAI Agents guide told users to fall back on `allowed_tables` and
`allowed_methods` instead.
`collect_tools()`, which every adapter goes through, now reads through
`.filtered()`, `.prefixed()` and `.renamed()`, nested in any order. Any other
wrapper raises a `TypeError` that names it and says what to do instead; for
`.approval_required()` that is `AgentOperator`, since nothing pauses a
non-Pydantic AI run for Airflow's approval.
```python
tools = AirflowTools(SQLToolset(db_conn_id="warehouse").filtered(lambda ctx,
tool: tool.name != "query"))
```
## Design rationale
**The tools still come from the inner toolset's own `airflow_tools()`.** The
walk strips the three wrappers down to the innermost toolset, takes its
`AirflowTool`s, and asks the outermost wrapper's own `get_tools()` which of
them are offered and under what name. So pydantic-ai runs the filter (sync or
async) and builds the names as it would in an agent run, with the toolset's
real `ToolDefinition`s. Calling `airflow_tools_from_toolset(wrapper)` and
letting each wrapper's `call_tool` route the call would be shorter, but it
skips the inner toolset's `airflow_tools()`, which `MCPToolset` uses to refuse
and a third-party toolset may override. What this costs is about ten lines that
map an offered name back through each prefix and rename, mirroring the
wrappers' `call_tool`; `test_nested_wrappers_apply_from_the_inside_out` fails
if that mapping goes wrong.
**The filter runs once, when the adapter is built.** It gets the same inert
`RunContext` `airflow_tools_from_toolset` already passes (`deps=None`), so a
filter that reads `ctx.deps` fails at build time, and whatever it decides holds
for every run, as the OpenAI Agents guide now says.
**Sources that work today keep working.** No common.ai toolset is a
`WrapperToolset` that also has `airflow_tools()`; `MaskingToolset`, the durable
journal wrapper and the logging wrapper already failed with `AttributeError`.
Any source with an `airflow_tools()` method, a wrapper included, is still asked
for its tools first, and the method is looked up rather than checked against
the `ToolProvider` Protocol, since on Python 3.12+ a Protocol `isinstance`
misses a method a mock or proxy provides through `__getattr__` (both cases are
tested). Only exact `FilteredToolset`, `PrefixedToolset` and `RenamedToolset`
are read through: a subclass could add behaviour in `call_tool` that tools
reached around it would skip, so it gets the `TypeError`.
**The adapters take a new `ToolSource` alias** (`ToolProvider | AirflowTool
| FilteredToolset | PrefixedToolset | RenamedToolset`), so mypy accepts the
three wrappers and still rejects `.approval_required()` passed directly.
The duplicate-tool-name error now also suggests `.prefixed()`, which is the
fix for two `SQLToolset`s on one agent, since `SQLToolset` has no `tool_prefix`.
Checked on `openai-agents` 0.20.0 with `openai` 2.54 (what the workspace
holds) and 0.24.0 with `openai` 3.42 (`run_agent_framework_tests.sh
openai-agents --framework-pins`), and on pydantic-ai-slim 2.36.0, the
provider's floor, whose wrapper attributes (`wrapped`, `filter_func`, `prefix`,
`name_map`) match the current release.
End to end, a plain `@task` built an OpenAI Agents SDK agent (0.20.0,
`gpt-5.4-mini` through an OpenAI-compatible gateway) with
`AirflowTools(SQLToolset(...).filtered(lambda ctx, tool: tool.name !=
"query"))` over a SQLite database, under `airflow dags test`. An `httpx` hook
logged the tools in each request the SDK sent to the model:
```text
AirflowTools holds: ['list_tables', 'get_schema', 'check_query']
Request to /llm/v1/chat/completions offered the model these tools:
['list_tables', 'get_schema', 'check_query']
::group::Tool call: list_tables
::group::Tool call: get_schema
Request to /llm/v1/chat/completions offered the model these tools:
['list_tables', 'get_schema', 'check_query']
Final answer: The database has these tables:
- `customers`
- `orders`
[...]
I do not have a tool that runs a SQL query here, so I can’t count how many
orders there are.
Dag run in success state
```
The trimmed lines list the four `orders` columns with their types, all
correct.
## Known issues
Under `.prefixed()` or `.renamed()`, a tool that keeps failing raises
`ToolCallError("query kept failing ...")` with its inner name, while the task
log's `Tool call:` line shows the offered name (`wh_query`). The message comes
from the inner toolset's tool, which this change reuses rather than rebuilds.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]