Tool calling gives a chat model a controlled way to request Python behavior instead of pretending that it completed an external action. A Haystack Agent coordinates that exchange by passing registered tool definitions to a compatible chat generator, invoking the requested function, and returning the function result to the conversation.
The @tool decorator derives a tool name and parameter schema from a typed Python function. The Agent component then manages the message loop, while max_agent_steps limits how many generator and tool turns one request may consume.
The local generator used here requests one fixed order lookup, which exposes the complete call loop without an API key or network model. Replace it with a tool-capable chat generator when calls should be selected by a model; the decorated function and Agent wiring stay the same.
from typing import Annotated from haystack import component from haystack.components.agents import Agent from haystack.dataclasses import ChatMessage, ToolCall from haystack.tools import tool
@tool def get_order_status(order_id: Annotated[str, "Order ID to look up"]) -> str: """Return the fulfillment status for one order.""" return f"Order {order_id} is packed and waiting for courier pickup."
The docstring describes the tool to the generator, and Annotated describes the order_id argument in the generated tool schema.
@component class DemoToolCallingChatGenerator: @component.output_types(replies=list[ChatMessage]) def run(self, messages: list[ChatMessage], tools=None): if messages[-1].is_from("user"): return { "replies": [ ChatMessage.from_assistant( tool_calls=[ ToolCall( tool_name="get_order_status", arguments={"order_id": "A100"}, id="call_1", ) ] ) ] } tool_result = messages[-1].tool_call_result.result return { "replies": [ ChatMessage.from_assistant( f"I checked order A100: {tool_result}" ) ] }
The first reply requests get_order_status. The next reply receives the tool result from Agent and converts it into the final assistant text.
agent = Agent( chat_generator=DemoToolCallingChatGenerator(), tools=[get_order_status], system_prompt="Use tools when order status is requested.", max_agent_steps=4, )
result = agent.run( messages=[ChatMessage.from_user("What is the status of order A100?")] )
for message in result["messages"]: if message.tool_calls: call = message.tool_calls[0] print(f"tool call: {call.tool_name}({call.arguments})") elif message.tool_call_result: print(f"tool result: {message.tool_call_result.result}") print(f"final answer: {result['last_message'].text}")
from typing import Annotated from haystack import component from haystack.components.agents import Agent from haystack.dataclasses import ChatMessage, ToolCall from haystack.tools import tool @tool def get_order_status(order_id: Annotated[str, "Order ID to look up"]) -> str: """Return the fulfillment status for one order.""" return f"Order {order_id} is packed and waiting for courier pickup." @component class DemoToolCallingChatGenerator: @component.output_types(replies=list[ChatMessage]) def run(self, messages: list[ChatMessage], tools=None): if messages[-1].is_from("user"): return { "replies": [ ChatMessage.from_assistant( tool_calls=[ ToolCall( tool_name="get_order_status", arguments={"order_id": "A100"}, id="call_1", ) ] ) ] } tool_result = messages[-1].tool_call_result.result return { "replies": [ ChatMessage.from_assistant( f"I checked order A100: {tool_result}" ) ] } agent = Agent( chat_generator=DemoToolCallingChatGenerator(), tools=[get_order_status], system_prompt="Use tools when order status is requested.", max_agent_steps=4, ) result = agent.run( messages=[ChatMessage.from_user("What is the status of order A100?")] ) for message in result["messages"]: if message.tool_calls: call = message.tool_calls[0] print(f"tool call: {call.tool_name}({call.arguments})") elif message.tool_call_result: print(f"tool result: {message.tool_call_result.result}") print(f"final answer: {result['last_message'].text}")
$ python order_agent.py
tool call: get_order_status({'order_id': 'A100'})
tool result: Order A100 is packed and waiting for courier pickup.
final answer: I checked order A100: Order A100 is packed and waiting for courier pickup.
The output shows the registered tool name and argument, the value returned by that tool, and the final answer that incorporates the same value.