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"):

Reply via email to