diff --git a/packages/toolbox-langchain/src/toolbox_langchain/async_tools.py b/packages/toolbox-langchain/src/toolbox_langchain/async_tools.py index 9aaf055e6..0356d1b01 100644 --- a/packages/toolbox-langchain/src/toolbox_langchain/async_tools.py +++ b/packages/toolbox-langchain/src/toolbox_langchain/async_tools.py @@ -21,6 +21,13 @@ from toolbox_core.utils import params_to_pydantic_model +def _get_tool_description(core_tool: ToolboxCoreTool) -> str: + description = getattr(core_tool, "_description", None) + if isinstance(description, str): + return description + return core_tool.__doc__ or "" + + # This class is an internal implementation detail and is not exposed to the # end-user. It should not be used directly by external code. Changes to this # class will not be considered breaking changes to the public API. @@ -45,7 +52,7 @@ def __init__( # BaseTool class before assigning values to member variables. super().__init__( name=core_tool.__name__, - description=core_tool.__doc__, + description=_get_tool_description(core_tool), args_schema=params_to_pydantic_model(core_tool._name, core_tool._params), ) self.__core_tool = core_tool diff --git a/packages/toolbox-langchain/src/toolbox_langchain/tools.py b/packages/toolbox-langchain/src/toolbox_langchain/tools.py index cb2f9d1ed..6570b3489 100644 --- a/packages/toolbox-langchain/src/toolbox_langchain/tools.py +++ b/packages/toolbox-langchain/src/toolbox_langchain/tools.py @@ -22,6 +22,13 @@ from toolbox_core.utils import params_to_pydantic_model +def _get_tool_description(core_tool: ToolboxCoreSyncTool) -> str: + description = getattr(core_tool, "_description", None) + if isinstance(description, str): + return description + return core_tool.__doc__ or "" + + class ToolboxTool(BaseTool): """ A subclass of LangChain's BaseTool that supports features specific to @@ -43,7 +50,7 @@ def __init__( # BaseTool class before assigning values to member variables. super().__init__( name=core_tool.__name__, - description=core_tool.__doc__, + description=_get_tool_description(core_tool), args_schema=params_to_pydantic_model(core_tool._name, core_tool._params), ) self.__core_tool = core_tool diff --git a/packages/toolbox-langchain/tests/test_async_tools.py b/packages/toolbox-langchain/tests/test_async_tools.py index d4d624174..1ccc31f6f 100644 --- a/packages/toolbox-langchain/tests/test_async_tools.py +++ b/packages/toolbox-langchain/tests/test_async_tools.py @@ -150,6 +150,22 @@ async def test_toolbox_tool_init(self, tool_schema_dict): ) tool = AsyncToolboxTool(core_tool=core_tool_instance) assert tool.name == "test_tool" + assert tool.description == core_tool_instance._description + assert "Args:" not in tool.description + + async def test_toolbox_tool_description_falls_back_to_docstring( + self, tool_schema_dict + ): + core_tool_instance = self._create_core_tool_from_dict( + session=None, + name="test_tool", + schema_dict=tool_schema_dict, + url="https://test-url", + ) + core_tool_instance._ToolboxTool__description = None + + tool = AsyncToolboxTool(core_tool=core_tool_instance) + assert tool.description == core_tool_instance.__doc__ @pytest.mark.parametrize( diff --git a/packages/toolbox-langchain/tests/test_tools.py b/packages/toolbox-langchain/tests/test_tools.py index 354cf77c3..53efed300 100644 --- a/packages/toolbox-langchain/tests/test_tools.py +++ b/packages/toolbox-langchain/tests/test_tools.py @@ -110,7 +110,13 @@ def mock_core_tool(self, tool_schema_dict): sync_mock = Mock(spec=ToolboxCoreSyncTool) sync_mock.__name__ = "test_tool_name_for_langchain" - sync_mock.__doc__ = tool_schema_dict["description"] + sync_mock._description = tool_schema_dict["description"] + sync_mock.__doc__ = ( + f"{tool_schema_dict['description']}\n\n" + "Args:\n" + " param1 (str): Param 1\n" + " param2 (int): Param 2" + ) sync_mock._name = "TestToolPydanticModel" sync_mock._params = [ CoreParameterSchema(**p) for p in tool_schema_dict["parameters"] @@ -124,6 +130,7 @@ def mock_core_tool(self, tool_schema_dict): new_mock_instance_for_methods = Mock(spec=ToolboxCoreSyncTool) new_mock_instance_for_methods.__name__ = sync_mock.__name__ + new_mock_instance_for_methods._description = sync_mock._description new_mock_instance_for_methods.__doc__ = sync_mock.__doc__ new_mock_instance_for_methods._name = sync_mock._name new_mock_instance_for_methods._params = sync_mock._params @@ -149,7 +156,13 @@ def mock_core_tool(self, tool_schema_dict): def mock_core_sync_auth_tool(self, auth_tool_schema_dict): sync_mock = Mock(spec=ToolboxCoreSyncTool) sync_mock.__name__ = "test_auth_tool_lc_name" - sync_mock.__doc__ = auth_tool_schema_dict["description"] + sync_mock._description = auth_tool_schema_dict["description"] + sync_mock.__doc__ = ( + f"{auth_tool_schema_dict['description']}\n\n" + "Args:\n" + " param1 (str): Param 1\n" + " param2 (int): Param 2" + ) sync_mock._name = "TestAuthToolPydanticModel" sync_mock._params = [ CoreParameterSchema(**p) for p in auth_tool_schema_dict["parameters"] @@ -163,6 +176,7 @@ def mock_core_sync_auth_tool(self, auth_tool_schema_dict): new_mock_instance_for_methods = Mock(spec=ToolboxCoreSyncTool) new_mock_instance_for_methods.__name__ = sync_mock.__name__ + new_mock_instance_for_methods._description = sync_mock._description new_mock_instance_for_methods.__doc__ = sync_mock.__doc__ new_mock_instance_for_methods._name = sync_mock._name new_mock_instance_for_methods._params = sync_mock._params @@ -192,7 +206,8 @@ def test_toolbox_tool_init(self, mock_core_tool): tool = ToolboxTool(core_tool=mock_core_tool) assert tool.name == mock_core_tool.__name__ - assert tool.description == mock_core_tool.__doc__ + assert tool.description == mock_core_tool._description + assert "Args:" not in tool.description assert tool._ToolboxTool__core_tool == mock_core_tool expected_args_schema = params_to_pydantic_model( @@ -205,6 +220,13 @@ def test_toolbox_tool_init(self, mock_core_tool): # Verify defaults actually persisted from the schema correctly assert tool.args_schema.model_fields["param2"].default == 42 + def test_toolbox_tool_description_falls_back_to_docstring(self, mock_core_tool): + del mock_core_tool._description + + tool = ToolboxTool(core_tool=mock_core_tool) + + assert tool.description == mock_core_tool.__doc__ + @pytest.mark.parametrize( "params", [