This is an automated email from the ASF dual-hosted git repository. vikramkoka pushed a commit to branch common_ai_managed_toolset in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 4fbc7353341e972af365c3064322490c56ba34ae Author: Vikram Koka <[email protected]> AuthorDate: Thu Aug 20 21:26:49 2026 -0700 Make the managed agent description optional BaseManagedAgentToolset required a description and raised on a blank one, on the reasoning that a remote agent's competence cannot be introspected and the string is what the calling model routes on. Two things were wrong with that. HookToolset, in the same package, does not require one -- it derives a description from the method name when a method has no docstring. Requiring one here while deriving one there is an inconsistency with nothing behind it, and it trusted the author's judgement on the tool name while refusing to trust it on whether a description was needed. The routing argument also described a pattern that is no longer the recommended one. A task holds a single managed agent toolset, and a failover group presents one tool, so in both cases the model is not choosing between specialists at all. The name stays required: it is the identifier the model emits, and an empty one is a bug. The description falls back to the name rendered as prose, and the docs now recommend writing one for what it actually does -- telling the model to consult the agent rather than answer from its own knowledge, and recording a scope limit the name cannot carry. --- providers/common/ai/docs/toolsets.rst | 15 +++++++++---- .../providers/common/ai/toolsets/managed_agent.py | 26 ++++++++++++---------- .../unit/common/ai/toolsets/test_managed_agent.py | 17 +++++++++----- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/providers/common/ai/docs/toolsets.rst b/providers/common/ai/docs/toolsets.rst index 47c72cf91e6..4e613d302f4 100644 --- a/providers/common/ai/docs/toolsets.rst +++ b/providers/common/ai/docs/toolsets.rst @@ -879,12 +879,19 @@ Tool naming, argument validation, result serialisation, and logging are handled by the base class, so every provider's implementation presents the same surface to the calling model. +``tool_name`` is the required identifier — it is what the model emits when it +calls the tool, and the Dag author chooses it. ``description`` is optional and +falls back to the tool name rendered as prose, the same way ``HookToolset`` +derives one from a method name when there is no docstring. + .. note:: - ``description`` is a required constructor argument. A remote agent's - competence cannot be introspected the way ``HookToolset`` reads a hook's - docstrings, and the description is the only basis the calling model has for - choosing between specialists. + Writing a description is still worth the line. It is what tells the model to + consult the agent rather than answer from its own knowledge, and it is the + only place to record a scope limit the name cannot carry — "cannot see + revenue figures". Because the argument schema is always a bare prompt, the + name and the description are the whole of what the model knows about the + agent. Toolset or operator? """""""""""""""""""" diff --git a/providers/common/ai/src/airflow/providers/common/ai/toolsets/managed_agent.py b/providers/common/ai/src/airflow/providers/common/ai/toolsets/managed_agent.py index 5166132dbcc..b9aaa0a9022 100644 --- a/providers/common/ai/src/airflow/providers/common/ai/toolsets/managed_agent.py +++ b/providers/common/ai/src/airflow/providers/common/ai/toolsets/managed_agent.py @@ -64,11 +64,17 @@ class BaseManagedAgentToolset(AbstractToolset[Any]): argument validation, result serialisation and logging are handled here so every provider's implementation presents the same surface to the model. - :param tool_name: Name the calling model sees. A verb phrase naming the - specialist reads best, e.g. ``ask_bookings_analyst``. - :param description: What this agent knows and when to consult it. Required: - a remote agent's competence cannot be introspected, and this is the only - basis the calling model has for choosing between specialists. + :param tool_name: Name the calling model sees, and the identifier it emits + when calling the tool. A verb phrase naming the specialist reads best, + e.g. ``ask_bookings_analyst``. + :param description: What this agent knows and when to consult it. Optional -- + it falls back to ``tool_name`` rendered as prose, matching how + ``HookToolset`` handles a method with no docstring. Worth writing anyway: + it is what tells the model to consult the agent rather than answer from + its own knowledge, and it is the only place to state a scope limit the + name cannot carry ("cannot see revenue figures"). Since the argument + schema is always a bare prompt, the name and this string are the whole + of what the model knows about the agent. :param timeout: Seconds to wait for a single invocation. ``None`` defers to the platform default, which subclasses supply -- a number chosen here would silently disagree with the vendor operator's documented timeout @@ -85,18 +91,14 @@ class BaseManagedAgentToolset(AbstractToolset[Any]): self, *, tool_name: str, - description: str, + description: str | None = None, timeout: float | None = None, ) -> None: if not tool_name: raise ValueError("tool_name must be a non-empty string.") - if not description or not description.strip(): - raise ValueError( - "description is required: the calling model uses it to decide which " - "specialist to consult, and it cannot be derived from the agent's identifier." - ) self._tool_name = tool_name - self._description = description + # Same fallback as HookToolset uses for a method with no docstring. + self._description = (description or "").strip() or tool_name.replace("_", " ").capitalize() self._timeout = timeout @property diff --git a/providers/common/ai/tests/unit/common/ai/toolsets/test_managed_agent.py b/providers/common/ai/tests/unit/common/ai/toolsets/test_managed_agent.py index 55a5b7094f3..eb5957a5ae2 100644 --- a/providers/common/ai/tests/unit/common/ai/toolsets/test_managed_agent.py +++ b/providers/common/ai/tests/unit/common/ai/toolsets/test_managed_agent.py @@ -59,12 +59,19 @@ class TestBaseManagedAgentToolsetConstruction: @pytest.mark.parametrize( "description", - ["", " ", "\n"], - ids=["empty", "whitespace", "newline"], + [None, "", " ", "\n"], + ids=["none", "empty", "whitespace", "newline"], ) - def test_blank_description_rejected(self, description): - with pytest.raises(ValueError, match="description is required"): - FakeManagedAgentToolset(description=description) + def test_absent_description_falls_back_to_the_tool_name(self, description): + # Matches HookToolset, which derives a description from the method name + # when there is no docstring. The name is the required identifier; the + # description is guidance the author may omit. + toolset = FakeManagedAgentToolset(tool_name="ask_bookings_analyst", description=description) + assert toolset._description == "Ask bookings analyst" + + def test_supplied_description_is_kept_verbatim(self): + toolset = FakeManagedAgentToolset(description="Knows bookings. Cannot see support tickets.") + assert toolset._description == "Knows bookings. Cannot see support tickets." def test_empty_tool_name_rejected(self): with pytest.raises(ValueError, match="tool_name must be a non-empty string"):
