Repository: deepset-ai/haystack

Issue:
Removing `add_tool()` method from ToolsManager 
**Is your feature request related to a problem? Please describe.**
With changes made in #5065, adding tools to a ConversationalAgent after initializing should be prevented. We can override Agent's `add_tool()` method from the ConversationalAgent to prevent this but adding tools still be possible with `self.tm.add_tool(tool)` so by calling the `add_tool()` method of ToolsManager.    

**Describe the solution you'd like**
Remove `add_tool()` method from ToolsManager and manage tools from the Agent. Agent's `add_tool` method can be:

```python
    def add_tool(self, tool: Tool):
        self.tm.tools[tool.name] = tool
```

**Describe alternatives you've considered**
n/a

**Additional context**
n/a



Discussion:

Resolving patch:
diff --git a/haystack/agents/base.py b/haystack/agents/base.py
--- a/haystack/agents/base.py
+++ b/haystack/agents/base.py
@@ -134,22 +134,6 @@ def __init__(
         self.tool_pattern = tool_pattern
         self.callback_manager = Events(("on_tool_start", "on_tool_finish", "on_tool_error"))
 
-    def add_tool(self, tool: Tool):
-        """
-        Add a tool to the Agent. This also updates the PromptTemplate for the Agent's PromptNode with the tool name.
-
-        :param tool: The tool to add to the Agent. Any previously added tool with the same name will be overwritten.
-        Example:
-        `agent.add_tool(
-            Tool(
-                name="Calculator",
-                pipeline_or_node=calculator
-                description="Useful when you need to answer questions about math."
-            )
-        )
-        """
-        self.tools[tool.name] = tool
-
     @property
     def tools(self):
         return self._tools
@@ -330,7 +314,11 @@ def add_tool(self, tool: Tool):
             )
         )
         """
-        self.tm.add_tool(tool)
+        if tool.name in self.tm.tools:
+            logger.warning(
+                "The agent already has a tool named '%s'. The new tool will overwrite the existing one.", tool.name
+            )
+        self.tm.tools[tool.name] = tool
 
     def has_tool(self, tool_name: str) -> bool:
         """


Test patch:
diff --git a/test/agents/test_agent.py b/test/agents/test_agent.py
--- a/test/agents/test_agent.py
+++ b/test/agents/test_agent.py
@@ -18,7 +18,7 @@
 
 
 @pytest.mark.unit
-def test_add_and_overwrite_tool():
+def test_add_and_overwrite_tool(caplog):
     # Add a Node as a Tool to an Agent
     agent = Agent(prompt_node=MockPromptNode())
     retriever = MockRetriever()
@@ -43,12 +43,16 @@ def test_add_and_overwrite_tool():
 
     # Add a Pipeline as a Tool to an Agent and overwrite the previously added Tool
     retriever_pipeline = DocumentSearchPipeline(MockRetriever())
-    agent.add_tool(
-        Tool(
-            name="Retriever",
-            pipeline_or_node=retriever_pipeline,
-            description="useful for when you need to retrieve documents from your index",
+    with caplog.at_level(logging.WARNING):
+        agent.add_tool(
+            Tool(
+                name="Retriever",
+                pipeline_or_node=retriever_pipeline,
+                description="useful for when you need to retrieve documents from your index",
+            )
         )
+    assert (
+        "The agent already has a tool named 'Retriever'. The new tool will overwrite the existing one." in caplog.text
     )
     assert len(agent.tm.tools) == 1
     assert agent.has_tool(tool_name="Retriever")
diff --git a/test/agents/test_tools_manager.py b/test/agents/test_tools_manager.py
--- a/test/agents/test_tools_manager.py
+++ b/test/agents/test_tools_manager.py
@@ -25,14 +25,6 @@ def test_using_callable_as_tool():
     assert tool.run(tool_input) == tool_input + tool_input
 
 
-@pytest.mark.unit
-def test_add_tool(tools_manager):
-    new_tool = Tool(name="ToolC", pipeline_or_node=mock.Mock(), description="Tool C Description")
-    tools_manager.add_tool(new_tool)
-    assert "ToolC" in tools_manager.tools
-    assert tools_manager.tools["ToolC"] == new_tool
-
-
 @pytest.mark.unit
 def test_get_tool_names(tools_manager):
     assert tools_manager.get_tool_names() == "ToolA, ToolB"