kaxil opened a new pull request, #73587:
URL: https://github.com/apache/airflow/pull/73587

   Common AI's toolsets (`SQLToolset`, `HookToolset`, ...) are pydantic-ai 
toolsets, so today only `AgentOperator` and pydantic-ai agents can use them. 
Teams that already build agents with [Strands 
Agents](https://strandsagents.com/) have to rewrite the agent for 
`AgentOperator` or hand-write tools around hooks and lose the toolsets' SQL 
validation, table allowlists and bounded results.
   
   This adds `as_strands_tools()`, which turns those toolsets into Strands 
tools. The user keeps a plain Strands `Agent`, with their own model, loop and 
Strands features, and Airflow supplies connection-backed tools:
   
   ```python
   agent = Agent(model=model, 
tools=as_strands_tools(SQLToolset(db_conn_id="warehouse")))
   ```
   
   It sits on a small framework-neutral interface in 
`airflow.providers.common.ai.tools`: `AirflowTool` (name, description, JSON 
Schema, async call), `ToolResult` (JSON-compatible content plus an explicit 
`is_error`), and `ToolProvider.airflow_tools()`, which `SQLToolset` and 
`HookToolset` now implement. Both the interface and the adapter are marked 
experimental. Strands is an optional dependency imported only by the adapter 
module, and nothing changes for `AgentOperator` users.
   
   ## Design rationale
   
   **Why not a `StrandsAgentOperator`?** An operator would own the agent loop, 
and every Strands feature would then need an Airflow release to reach users. 
The adapter only converts tools, so the agent stays native and Strands releases 
reach users without an Airflow change.
   
   **Masking lives in the one path every adapter shares.** Airflow's secret 
masker protects task logs, not tool results. A Strands tool whose client 
library puts a credentialed URL in an error message sends the raw password to 
the model provider, into the agent's answer and into its traces, while the task 
log shows `***`. I reproduced this with a native Strands `@tool` in a 
scheduler-run task. `AirflowTool.call` turns exceptions into error results and 
runs `redact()` on every result and error before anything reaches the 
framework, matching values in nested JSON up to 32 levels deep rather than the 
masker's default of 5. An adapter for another framework only maps the three 
types.
   
   **Why an explicit `airflow_tools()` per toolset instead of accepting any 
`AbstractToolset`?** The bridge calls the toolset's `call_tool` with an inert 
`RunContext`, as the existing LangChain bridge does. That is only correct for 
toolsets that ignore the context, which `SQLToolset` and `HookToolset` do. 
Opting toolsets in one at a time keeps the contract true. `MCPToolset` would 
reconnect on every call, and a custom toolset that reads `ctx.model` or 
`ctx.messages` would misbehave.
   
   **Calls into one toolset instance are serialised.** Both toolsets share one 
hook doing blocking I/O, and `SQLToolset` reads `hook.last_description` after 
each query, which is why their pydantic-ai tool definitions are marked 
`sequential`. Agent frameworks can run tool calls concurrently, and several 
agents can share one toolset, so the bridge keeps one lock per toolset instance 
and runs the hook call off the framework's event loop.
   
   **The adapter copies the input schema.** Strands fills in missing property 
types and descriptions in place when it registers a tool, and the source schema 
is a module-level constant the pydantic-ai path also uses.
   
   The LangChain bridge's private coroutine helper moves to 
`utils/coroutines.py` so both bridges share it. It is unchanged.
   
   The example Dag in this PR ran unmodified as a scheduler-run task on Airflow 
main with Postgres against `claude-sonnet-5`. The agent called `list_tables`, 
then `query`, and returned the correct row counts in 7.5 s. The screenshot 
filters the log to the tool, hook and operator sources:
   
   ![Task log of example_strands_agent: tool call groups for list_tables and 
query, then the agent's answer](./strands-agent-task-log.png)
   
   ## Known issues
   
   - **The Strands adapter tests skip in CI.** `strands-agents` pins `mcp<2.2`, 
and adding it to the dev group downgrades `mcp` from 2.2.0 to 2.1.1 in the 
workspace lock, so it is not added. `test_strands.py` uses 
`pytest.importorskip`. Locally, with `strands-agents==1.56.0` installed, all 
touched suites pass (172 tests).
   - **Resolving `strands-agents` with no lower bound can pick the `0.0.1` 
placeholder release.** The docs tell users to install `strands-agents>=1.56.0`.
   - **Strands' own `anthropic` extra requires `anthropic<1`**, which conflicts 
with this provider's `anthropic` extra. `AnthropicModel` works with `anthropic` 
1.x at runtime, so the docs point users at this provider's extra.
   
   ## Follow-ups
   
   - Build the LangChain bridge on `AirflowTool` so its tools are masked too.
   - Mask the retry message `SQLToolset` returns to pydantic-ai agents, which 
today carries the database's error text unmasked.
   
   ---
   
   * Read the **[Pull Request 
Guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#pull-request-guidelines)**
 for more information. Note: commit author/co-author name and email in commits 
become permanently public when merged.
   * For fundamental code changes, an Airflow Improvement Proposal 
([AIP](https://cwiki.apache.org/confluence/display/AIRFLOW/Airflow+Improvement+Proposals))
 is needed.
   * When adding dependency, check compliance with the [ASF 3rd Party License 
Policy](https://www.apache.org/legal/resolved.html#category-x).
   * For significant user-facing changes create newsfragment: 
`{pr_number}.significant.rst`, in 
[airflow-core/newsfragments](https://github.com/apache/airflow/tree/main/airflow-core/newsfragments).
 You can add this file in a follow-up commit after the PR is created so you 
know the PR number.
   


-- 
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]

Reply via email to