mirror of
https://github.com/deepset-ai/haystack.git
synced 2025-12-31 09:10:15 +00:00
* activate link checker * additional link fixes * same fix for different version * fix anchors * rename data classes md * simplify links to reference * Apply suggestions from code review Co-authored-by: Stefano Fiorucci <stefanofiorucci@gmail.com> --------- Co-authored-by: Stefano Fiorucci <stefanofiorucci@gmail.com>
406 lines
14 KiB
Plaintext
406 lines
14 KiB
Plaintext
---
|
||
title: "Tool"
|
||
id: tool
|
||
slug: "/tool"
|
||
description: "`Tool` is a data class representing a function that Language Models can prepare a call for."
|
||
---
|
||
|
||
# Tool
|
||
|
||
`Tool` is a data class representing a function that Language Models can prepare a call for.
|
||
|
||
A growing number of Language Models now support passing tool definitions alongside the prompt.
|
||
|
||
Tool calling refers to the ability of Language Models to generate calls to tools - be they functions or APIs - when responding to user queries. The model prepares the tool call but does not execute it.
|
||
|
||
If you are looking for the details of this data class's methods and parameters, visit our [API documentation](/reference/tools-api).
|
||
|
||
## Tool class
|
||
|
||
`Tool` is a simple and unified abstraction to represent tools in the Haystack framework.
|
||
|
||
A tool is a function for which Language Models can prepare a call.
|
||
|
||
The `Tool` class is used in Chat Generators and provides a consistent experience across models. `Tool` is also used in the [`ToolInvoker`](../pipeline-components/tools/toolinvoker.mdx) component that executes calls prepared by Language Models.
|
||
|
||
```python
|
||
@dataclass
|
||
class Tool:
|
||
name: str
|
||
description: str
|
||
parameters: Dict[str, Any]
|
||
function: Callable
|
||
```
|
||
|
||
- `name` is the name of the Tool.
|
||
- `description` is a string describing what the Tool does.
|
||
- `parameters` is a JSON schema describing the expected parameters.
|
||
- `function` is invoked when the Tool is called.
|
||
|
||
Keep in mind that the accurate definitions of `name` and `description` are important for the Language Model to prepare the call correctly.
|
||
|
||
`Tool` exposes a `tool_spec` property, returning the tool specification to be used by Language Models.
|
||
|
||
It also has an `invoke` method that executes the underlying function with the provided parameters.
|
||
|
||
## Tool Initialization
|
||
|
||
Here is how to initialize a Tool to work with a specific function:
|
||
|
||
```python
|
||
from haystack.tools import Tool
|
||
|
||
def add(a: int, b: int) -> int:
|
||
return a + b
|
||
|
||
parameters = {
|
||
"type": "object",
|
||
"properties": {
|
||
"a": {"type": "integer"},
|
||
"b": {"type": "integer"}
|
||
},
|
||
"required": ["a", "b"]
|
||
}
|
||
|
||
add_tool = Tool(name="addition_tool",
|
||
description="This tool adds two numbers",
|
||
parameters=parameters,
|
||
function=add)
|
||
|
||
print(add_tool.tool_spec)
|
||
|
||
print(add_tool.invoke(a=15, b=10))
|
||
```
|
||
|
||
```
|
||
{'name': 'addition_tool',
|
||
'description': 'This tool adds two numbers',
|
||
'parameters':{'type': 'object',
|
||
'properties':{'a':{'type': 'integer'}, 'b':{'type': 'integer'}},
|
||
'required':['a', 'b']}}
|
||
|
||
25
|
||
```
|
||
|
||
### @tool decorator
|
||
|
||
The `@tool` decorator simplifies converting a function into a Tool. It infers Tool name, description, and parameters from the function and automatically generates a JSON schema. It uses Python's `typing.Annotated` for the description of parameters. If you need to customize Tool name and description, use `create_tool_from_function` instead.
|
||
|
||
```python
|
||
from typing import Annotated, Literal
|
||
from haystack.tools import tool
|
||
|
||
@tool
|
||
def get_weather(
|
||
city: Annotated[str, "the city for which to get the weather"] = "Munich",
|
||
unit: Annotated[Literal["Celsius", "Fahrenheit"], "the unit for the temperature"] = "Celsius"):
|
||
'''A simple function to get the current weather for a location.'''
|
||
return f"Weather report for {city}: 20 {unit}, sunny"
|
||
|
||
print(get_weather)
|
||
```
|
||
|
||
```
|
||
Tool(name='get_weather', description='A simple function to get the current weather for a location.',
|
||
parameters={
|
||
'type': 'object',
|
||
'properties': {
|
||
'city': {'type': 'string', 'description': 'the city for which to get the weather', 'default': 'Munich'},
|
||
'unit': {
|
||
'type': 'string',
|
||
'enum': ['Celsius', 'Fahrenheit'],
|
||
'description': 'the unit for the temperature',
|
||
'default': 'Celsius',
|
||
},
|
||
}
|
||
},
|
||
function=<function get_weather at 0x7f7b3a8a9b80>)
|
||
```
|
||
|
||
### create_tool_from_function
|
||
|
||
The `create_tool_from_function` method provides more flexibility than the`@tool` decorator and allows setting Tool name and description. It infers the Tool parameters automatically and generates a JSON schema automatically in the same way as the `@tool` decorator.
|
||
|
||
```python
|
||
from typing import Annotated, Literal
|
||
from haystack.tools import create_tool_from_function
|
||
|
||
def get_weather(
|
||
city: Annotated[str, "the city for which to get the weather"] = "Munich",
|
||
unit: Annotated[Literal["Celsius", "Fahrenheit"], "the unit for the temperature"] = "Celsius"):
|
||
'''A simple function to get the current weather for a location.'''
|
||
return f"Weather report for {city}: 20 {unit}, sunny"
|
||
|
||
tool = create_tool_from_function(get_weather)
|
||
|
||
print(tool)
|
||
```
|
||
|
||
```
|
||
Tool(name='get_weather', description='A simple function to get the current weather for a location.',
|
||
parameters={
|
||
'type': 'object',
|
||
'properties': {
|
||
'city': {'type': 'string', 'description': 'the city for which to get the weather', 'default': 'Munich'},
|
||
'unit': {
|
||
'type': 'string',
|
||
'enum': ['Celsius', 'Fahrenheit'],
|
||
'description': 'the unit for the temperature',
|
||
'default': 'Celsius',
|
||
},
|
||
}
|
||
},
|
||
function=<function get_weather at 0x7f7b3a8a9b80>)
|
||
```
|
||
|
||
## Toolset
|
||
|
||
A Toolset groups multiple Tool instances into a single manageable unit. It simplifies the passing of tools to components like Chat Generators or `ToolInvoker`, and supports filtering, serialization, and reuse.
|
||
|
||
```python
|
||
from haystack.tools import Toolset
|
||
|
||
math_toolset = Toolset([add_tool, subtract_tool])
|
||
```
|
||
|
||
See more details and examples on the [Toolset documentation page](toolset.mdx).
|
||
|
||
## Usage
|
||
|
||
To better understand this section, make sure you are also familiar with Haystack's [`ChatMessage`](../concepts/data-classes/chatmessage.mdx) data class.
|
||
|
||
### Passing Tools to a Chat Generator
|
||
|
||
Using the `tools` parameter, you can pass tools as a list of Tool instances or a single Toolset during initialization or in the `run` method. Tools passed at runtime override those set at initialization.
|
||
|
||
:::note
|
||
Chat Generators support
|
||
|
||
Not all Chat Generators currently support tools, but we are actively expanding tool support across more models.
|
||
|
||
Look out for the `tools` parameter in a specific Chat Generator's `__init__` and `run` methods.
|
||
:::
|
||
|
||
```python
|
||
from haystack.dataclasses import ChatMessage
|
||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||
|
||
## Initialize the Chat Generator with the addition tool
|
||
chat_generator = OpenAIChatGenerator(model="gpt-4o-mini", tools=[add_tool])
|
||
|
||
## here we expect the Tool to be invoked
|
||
res=chat_generator.run([ChatMessage.from_user("10 + 238")])
|
||
print(res)
|
||
|
||
## here the model can respond without using the Tool
|
||
res=chat_generator.run([ChatMessage.from_user("What is the habitat of a lion?")])
|
||
print(res)
|
||
```
|
||
|
||
```
|
||
{'replies':[ChatMessage(_role=<ChatRole.ASSISTANT: 'assistant'>,
|
||
_content=[ToolCall(tool_name='addition_tool',
|
||
arguments={'a':10, 'b':238},
|
||
id='call_rbYtbCdW0UbWMfy2x0sgF1Ap'
|
||
)],
|
||
_meta={...})]}
|
||
|
||
|
||
{'replies':[ChatMessage(_role=<ChatRole.ASSISTANT: 'assistant'>,
|
||
_content=[TextContent(text='Lions primarily inhabit grasslands, savannas, and open woodlands. ...'
|
||
)],
|
||
_meta={...})]}
|
||
```
|
||
|
||
The same result of the previous run can be achieved by passing tools at runtime:
|
||
|
||
```python
|
||
## Initialize the Chat Generator without tools
|
||
chat_generator = OpenAIChatGenerator(model="gpt-4o-mini")
|
||
|
||
## pass tools in the run method
|
||
res_w_tool_call=chat_generator.run([ChatMessage.from_user("10 + 238")], tools=math_toolset)
|
||
print(res_w_tool_call)
|
||
```
|
||
|
||
### Executing Tool Calls
|
||
|
||
To execute prepared tool calls, you can use the [`ToolInvoker`](../pipeline-components/tools/toolinvoker.mdx) component. This component acts as the execution engine for tools, processing the calls prepared by the Language Model.
|
||
|
||
Here's an example:
|
||
|
||
```python
|
||
import random
|
||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||
from haystack.components.tools import ToolInvoker
|
||
from haystack.tools import Tool
|
||
|
||
## Define a dummy weather toolimport random
|
||
|
||
def dummy_weather(location: str):
|
||
return {"temp": f"{random.randint(-10,40)} °C",
|
||
"humidity": f"{random.randint(0,100)}%"}
|
||
|
||
weather_tool = Tool(
|
||
name="weather",
|
||
description="A tool to get the weather",
|
||
function=dummy_weather,
|
||
parameters={
|
||
"type": "object",
|
||
"properties": {"location": {"type": "string"}},
|
||
"required": ["location"],
|
||
},
|
||
)
|
||
|
||
## Initialize the Chat Generator with the weather tool
|
||
chat_generator = OpenAIChatGenerator(model="gpt-4o-mini", tools=[weather_tool])
|
||
|
||
## Initialize the Tool Invoker with the weather tool
|
||
tool_invoker = ToolInvoker(tools=[weather_tool])
|
||
|
||
user_message = ChatMessage.from_user("What is the weather in Berlin?")
|
||
|
||
replies = chat_generator.run(messages=[user_message])["replies"]
|
||
print(f"assistant messages: {replies}")
|
||
|
||
## If the assistant message contains a tool call, run the tool invoker
|
||
if replies[0].tool_calls:
|
||
tool_messages = tool_invoker.run(messages=replies)["tool_messages"]
|
||
print(f"tool messages: {tool_messages}")
|
||
```
|
||
|
||
```
|
||
assistant messages:[ChatMessage(_role=<ChatRole.ASSISTANT: 'assistant'>, _content=[ToolCall(tool_name='weather',
|
||
arguments={'location': 'Berlin'}, id='call_YEvCEAmlvc42JGXV84NU8wtV')], _meta={'model': 'gpt-4o-mini-2024-07-18',
|
||
'index':0, 'finish_reason': 'tool_calls', 'usage':{'completion_tokens':13, 'prompt_tokens':50, 'total_tokens':
|
||
63}})]
|
||
|
||
tool messages: [ChatMessage(_role=<ChatRole.TOOL: 'tool'>, _content=[ToolCallResult(result="{'temp': '22 °C',
|
||
'humidity': '35%'}", origin=ToolCall(tool_name='weather', arguments={'location': 'Berlin'},
|
||
id='call_YEvCEAmlvc42JGXV84NU8wtV'), error=False)], _meta={})]
|
||
```
|
||
|
||
### Processing Tool Results with the Chat Generator
|
||
|
||
In some cases, the raw output from a tool may not be immediately suitable for the end user.
|
||
|
||
You can refine the tool’s response by passing it back to the Chat Generator. This generates a user-friendly and conversational message.
|
||
|
||
In this example, we’ll pass the tool’s response back to the Chat Generator for final processing:
|
||
|
||
```python
|
||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||
from haystack.components.tools import ToolInvoker
|
||
from haystack.tools import Tool
|
||
|
||
## Define a dummy weather toolimport random
|
||
|
||
def dummy_weather(location: str):
|
||
return {"temp": f"{random.randint(-10,40)} °C",
|
||
"humidity": f"{random.randint(0,100)}%"}
|
||
|
||
weather_tool = Tool(
|
||
name="weather",
|
||
description="A tool to get the weather",
|
||
function=dummy_weather,
|
||
parameters={
|
||
"type": "object",
|
||
"properties": {"location": {"type": "string"}},
|
||
"required": ["location"],
|
||
},
|
||
)
|
||
|
||
chat_generator = OpenAIChatGenerator(model="gpt-4o-mini", tools=[weather_tool])
|
||
tool_invoker = ToolInvoker(tools=[weather_tool])
|
||
|
||
user_message = ChatMessage.from_user("What is the weather in Berlin?")
|
||
|
||
replies = chat_generator.run(messages=[user_message])["replies"]
|
||
print(f"assistant messages: {replies}")
|
||
|
||
if replies[0].tool_calls:
|
||
tool_messages = tool_invoker.run(messages=replies)["tool_messages"]
|
||
print(f"tool messages: {tool_messages}")
|
||
# we pass all the messages to the Chat Generator
|
||
messages = [user_message] + replies + tool_messages
|
||
final_replies = chat_generator.run(messages=messages)["replies"]
|
||
print(f"final assistant messages: {final_replies}")
|
||
```
|
||
|
||
```
|
||
assistant messages:[ChatMessage(_role=<ChatRole.ASSISTANT: 'assistant'>, _content=[ToolCall(tool_name='weather',
|
||
arguments={'location': 'Berlin'}, id='call_jHX0RCDHRKX7h8V9RrNs6apy')], _meta={'model': 'gpt-4o-mini-2024-07-18',
|
||
'index':0, 'finish_reason': 'tool_calls', 'usage':{'completion_tokens':13, 'prompt_tokens':50, 'total_tokens':
|
||
63}})]
|
||
|
||
tool messages: [ChatMessage(_role=<ChatRole.TOOL: 'tool'>, _content=[ToolCallResult(result="{'temp': '2 °C',
|
||
'humidity': '15%'}", origin=ToolCall(tool_name='weather', arguments={'location': 'Berlin'},
|
||
id='call_jHX0RCDHRKX7h8V9RrNs6apy'), error=False)], _meta={})]
|
||
|
||
final assistant messages: [ChatMessage(_role=<ChatRole.ASSISTANT: 'assistant'>, _content=[TextContent(text='The
|
||
current weather in Berlin is 2 °C with a humidity level of 15%.')], _meta={'model': 'gpt-4o-mini-2024-07-18',
|
||
'index': 0, 'finish_reason': 'stop', 'usage': {'completion_tokens': 19, 'prompt_tokens': 85, 'total_tokens':
|
||
104}})]
|
||
```
|
||
|
||
### Passing Tools to Agent
|
||
|
||
You can also use `Tool` with the [Agent](../pipeline-components/agents-1/agent.mdx) component. Internally, the `Agent` component includes a `ToolInvoker` and the ChatGenerator of your choice to execute tool calls and process tool results.
|
||
|
||
```python
|
||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||
from haystack.dataclasses import ChatMessage
|
||
from haystack.tools.tool import Tool
|
||
from haystack.components.agents import Agent
|
||
from typing import List
|
||
|
||
## Tool Function
|
||
def calculate(expression: str) -> dict:
|
||
try:
|
||
result = eval(expression, {"__builtins__": {}})
|
||
return {"result": result}
|
||
except Exception as e:
|
||
return {"error": str(e)}
|
||
|
||
## Tool Definition
|
||
calculator_tool = Tool(
|
||
name="calculator",
|
||
description="Evaluate basic math expressions.",
|
||
parameters={
|
||
"type": "object",
|
||
"properties": {
|
||
"expression": {"type": "string", "description": "Math expression to evaluate"}
|
||
},
|
||
"required": ["expression"]
|
||
},
|
||
function=calculate,
|
||
outputs_to_state={"calc_result": {"source": "result"}}
|
||
)
|
||
|
||
## Agent Setup
|
||
agent = Agent(
|
||
chat_generator=OpenAIChatGenerator(),
|
||
tools=[calculator_tool],
|
||
exit_conditions=["calculator"],
|
||
state_schema={
|
||
"calc_result": {"type": int},
|
||
}
|
||
)
|
||
|
||
## Run the Agent
|
||
agent.warm_up()
|
||
response = agent.run(messages=[ChatMessage.from_user("What is 7 * (4 + 2)?")])
|
||
|
||
## Output
|
||
print(response["messages"])
|
||
print("Calc Result:", response.get("calc_result"))
|
||
```
|
||
|
||
## Additional References
|
||
|
||
🧑🍳 Cookbooks:
|
||
|
||
- [Build a GitHub Issue Resolver Agent](https://haystack.deepset.ai/cookbook/github_issue_resolver_agent)
|
||
- [Newsletter Sending Agent with Haystack Tools](https://haystack.deepset.ai/cookbook/newsletter-agent)
|
||
- [Create a Swarm of Agents](https://haystack.deepset.ai/cookbook/swarm)
|