Agent
The Agent component is a tool-using agent that interacts with chat-based LLMs and tools to solve complex queries iteratively. It can execute external tools, manage state across multiple LLM calls, and stop execution based on configurable exit_conditions.
| Most common position in a pipeline | After a ChatPromptBuilder or user input |
| Mandatory init variables | chat_generator: An instance of a Chat Generator that supports tools |
| Mandatory run variables | messages: A list of ChatMessages |
| Output variables | messages: Chat history with tool and model responses |
| API reference | Agents |
| GitHub link | https://github.com/deepset-ai/haystack/blob/main/haystack/components/agents/agent.py |
| Package name | haystack-ai |
Overview​
The Agent component is a loop-based system that uses a chat-based large language model (LLM) and external tools to solve complex user queries.
It works iteratively—calling tools, updating state, and generating prompts—until one of the configurable exit_conditions is met.
It can:
- Dynamically select tools based on user input,
- Maintain and validate runtime state using a schema,
- Stream token-level outputs from the LLM.
The Agent returns a dictionary containing:
messages: the full conversation history,last_message: the finalChatMessagefrom the agent,- Additional dynamic keys based on
state_schema.
Parameters​
chat_generator is the only mandatory parameter — an instance of a Chat Generator that supports tools. All other parameters are optional.
tools: A list of tool or toolset instances the agent can call. Supported types:Tool,ComponentTool,PipelineTool,MCPTool,Toolset,MCPToolset,SearchableToolset.system_prompt: A plain string or Jinja2 template used as the system message for every run. If the template contains Jinja2 variables, those variables become additional inputs torun().user_prompt: A Jinja2 template appended to the user-provided messages on each run. Template variables become additional inputs torun(). Userequired_variablesto enforce which variables must be provided.exit_conditions: List of conditions that cause the agent to stop. Use”text”to stop when the LLM replies without a tool call, or a tool name to stop once that tool has been executed. Defaults to[“text”].state_schema: Defines the agent's runtime state — a dict mapping key names to type configs (e.g.{“docs”: {“type”: list[Document]}}). Tools can read from and write to state keys viainputs_from_stateandoutputs_to_state. See State for full details.streaming_callback: A callback invoked for each streamed token. Use the built-inprint_streaming_chunkfor console output.max_agent_steps: Maximum number of LLM + tool call iterations before the agent stops. Defaults to100.raise_on_tool_invocation_failure: IfTrue, raises an exception when a tool call fails. IfFalse(default), the error is passed back to the LLM as a message so it can recover.confirmation_strategies: A dict mapping tool names (or tuples of tool names) to aConfirmationStrategy, enabling human review of tool calls before execution. See Human in the Loop.tool_invoker_kwargs: Additional keyword arguments forwarded to the internalToolInvoker.
Runtime overrides​
run() also accepts parameters that override the init-time configuration for a single call:
tools: Pass a list ofTool/Toolsetobjects, or a list of tool name strings to select a subset of the agent's configured tools for this run.generation_kwargs: Additional keyword arguments forwarded to the LLM, overriding any set at init time (e.g.{“temperature”: 0.2}).
For the full parameter reference, see the Agents API Documentation.
Usage​
On its own​
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.tools import tool
from haystack.components.agents import Agent
from typing import Annotated
@tool(outputs_to_state={"calc_result": {"source": "result"}})
def calculator(
expression: Annotated[str, "Math expression to evaluate, e.g. '7 * (4 + 2)'"],
) -> dict:
"""Evaluate basic math expressions."""
try:
result = eval(expression, {"__builtins__": {}})
return {"result": result}
except Exception as e:
return {"error": str(e)}
agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
tools=[calculator],
system_prompt="You are a helpful assistant. Always use the calculator tool to evaluate math expressions.",
state_schema={"calc_result": {"type": int}},
)
response = agent.run(messages=[ChatMessage.from_user("What is 7 * (4 + 2)?")])
print(response["last_message"].text)
print("Calc Result:", response.get("calc_result"))
In a pipeline​
The example pipeline below creates a database assistant using OpenAIChatGenerator, LinkContentFetcher, and custom database tool.
It reads the given URL and processes the page content, then builds a prompt for the AI.
The assistant uses this information to write people's names and titles from the given page to the database.
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.builders.chat_prompt_builder import ChatPromptBuilder
from haystack.components.converters.html import HTMLToDocument
from haystack.components.fetchers.link_content import LinkContentFetcher
from haystack import Document, Pipeline
from haystack.dataclasses import ChatMessage
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.tools import tool
from typing import Annotated, Optional
document_store = InMemoryDocumentStore() # create a document store or an SQL database
@tool
def add_database_tool(
name: Annotated[str, "First name of the person"],
surname: Annotated[str, "Last name of the person"],
job_title: Annotated[Optional[str], "Job title or role of the person"] = None,
other: Annotated[Optional[str], "Any other relevant information"] = None,
) -> str:
"""Add a person to the database with information about them."""
document_store.write_documents(
[
Document(
content=name + " " + surname + " " + (job_title or ""),
meta={"other": other},
),
],
)
# Returning a confirmation lets the agent know the tool call succeeded
return f"Successfully added {name} {surname} to the database."
database_assistant = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
tools=[add_database_tool],
system_prompt="""
You are a database assistant.
Your task is to extract the names of people mentioned in the given context and add them to a knowledge base,
along with additional relevant information about them that can be extracted from the context.
Do not use your own knowledge, stay grounded to the given context.
Do not ask the user for confirmation.
Instead, automatically update the knowledge base and return a brief summary of the people added,
including the information stored for each.
""",
)
extraction_agent = Pipeline()
extraction_agent.add_component("fetcher", LinkContentFetcher())
extraction_agent.add_component("converter", HTMLToDocument())
extraction_agent.add_component(
"builder",
ChatPromptBuilder(
template=[
ChatMessage.from_user("""
{% for doc in docs %}
{{ doc.content|default|truncate(25000) }}
{% endfor %}
"""),
],
required_variables=["docs"],
),
)
extraction_agent.add_component("database_agent", database_assistant)
extraction_agent.connect("fetcher.streams", "converter.sources")
extraction_agent.connect("converter.documents", "builder.docs")
extraction_agent.connect("builder", "database_agent")
agent_output = extraction_agent.run(
{
"fetcher": {
"urls": ["https://github.com/deepset-ai/haystack/releases/tag/v2.27.0"],
},
},
)
print(agent_output["database_agent"]["last_message"].text)
# Inspect what was written to the document store
written_docs = document_store.filter_documents()
print(f"\n{len(written_docs)} people added to the database:")
for doc in written_docs:
print(f" - {doc.content}")
In YAML​
The example pipeline below fetches a webpage, converts its HTML to text, and builds a chat prompt combining the page content with a user query.
The Agent then answers the question based on the provided content and can use its web search tool to find additional information if needed.
View YAML
components:
agent:
init_parameters:
chat_generator:
init_parameters:
api_base_url: null
api_key:
env_vars:
- OPENAI_API_KEY
strict: true
type: env_var
generation_kwargs: {}
http_client_kwargs: null
max_retries: null
model: gpt-5.4-nano
organization: null
streaming_callback: null
timeout: null
tools: null
tools_strict: false
type: haystack.components.generators.chat.openai.OpenAIChatGenerator
confirmation_strategies: null
exit_conditions:
- text
max_agent_steps: 5
raise_on_tool_invocation_failure: false
required_variables: null
state_schema: {}
streaming_callback: null
system_prompt: You are a helpful assistant. Use the web search tool to find
information when needed.
tool_invoker_kwargs: null
tools:
- data:
component:
init_parameters:
allowed_domains: null
api_key:
env_vars:
- SERPERDEV_API_KEY
strict: true
type: env_var
exclude_subdomains: false
search_params: {}
top_k: 3
type: haystack.components.websearch.serper_dev.SerperDevWebSearch
description: Search the web for current information on any topic
inputs_from_state: null
name: web_search
outputs_to_state: null
outputs_to_string: null
parameters: null
type: haystack.tools.component_tool.ComponentTool
user_prompt: null
type: haystack.components.agents.agent.Agent
converter:
init_parameters:
extraction_kwargs: {}
store_full_path: false
type: haystack.components.converters.html.HTMLToDocument
fetcher:
init_parameters:
client_kwargs:
follow_redirects: true
timeout: 3
http2: false
raise_on_failure: true
request_headers: {}
retry_attempts: 2
timeout: 3
user_agents:
- haystack/LinkContentFetcher/2.27.0rc0
type: haystack.components.fetchers.link_content.LinkContentFetcher
prompt_builder:
init_parameters:
required_variables:
- docs
- query
template:
- content:
- text: 'Based on the following content:
{% for doc in docs %}
{{ doc.content }}
{% endfor %}
Answer this question: {{ query }}'
meta: {}
name: null
role: user
variables: null
type: haystack.components.builders.chat_prompt_builder.ChatPromptBuilder
connection_type_validation: true
connections:
- receiver: converter.sources
sender: fetcher.streams
- receiver: prompt_builder.docs
sender: converter.documents
- receiver: agent.messages
sender: prompt_builder.prompt
max_runs_per_component: 100
metadata: {}
Streaming​
You can stream output as it's generated. Pass a callback to streaming_callback.
Use the built-in print_streaming_chunk to print text tokens and tool events (tool calls and tool results).
from haystack.components.generators.utils import print_streaming_chunk
agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
tools=[...],
system_prompt="...",
streaming_callback=print_streaming_chunk,
)
See our Streaming Support docs to learn more how StreamingChunk works and how to write a custom callback.
Give preference to print_streaming_chunk by default.
Write a custom callback only if you need a specific transport (for example, SSE/WebSocket) or custom UI formatting.
Multi-Agent Systems​
You can wrap an Agent using ComponentTool to build multi-agent systems where specialized agents act as tools for a coordinator agent.
This pattern is useful when a task is too broad or complex for a single agent to handle well. Instead of giving one agent a large toolset and hoping it makes good decisions, you can decompose the problem: a coordinator agent handles planning and delegation, while specialist agents each own a focused set of tools and a targeted system prompt.
This is also a form of context engineering — deliberately controlling what each agent sees.
A specialist accumulates its own tool call trace as it works, but the coordinator only needs the final answer.
By using outputs_to_string={"source": "last_message"} when wrapping a specialist as a ComponentTool, you surface only its final reply to the coordinator rather than forwarding the full tool call trace.
This keeps the coordinator's context lean and focused, which leads to better decisions and lower token usage as the conversation grows.
from typing import Annotated
from haystack.components.agents import Agent
from haystack.components.fetchers.link_content import LinkContentFetcher
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.generators.utils import print_streaming_chunk
from haystack.components.websearch import SerperDevWebSearch
from haystack.dataclasses import ChatMessage
from haystack.tools import ComponentTool, tool
from haystack.utils import Secret
# Create the specialist agent with web search and page fetching tools
# Option 1: ComponentTool — wrap a component directly (good for straightforward cases)
search_tool = ComponentTool(
component=SerperDevWebSearch(
api_key=Secret.from_env_var("SERPERDEV_API_KEY"),
top_k=3,
),
name="web_search",
description="Search the web for current information on any topic",
)
# Option 2: @tool decorator — wrap a component inside a function for a simpler
# signature, built-in error handling, and custom result formatting
@tool
def fetch_page(url: Annotated[str, "The URL of the web page to fetch"]) -> str:
"""Fetch the full content of a web page given its URL."""
try:
streams = LinkContentFetcher().run(urls=[url])["streams"]
return (
streams[0].data.decode("utf-8", errors="replace")
if streams
else "No content found."
)
except Exception as e:
return f"Failed to fetch page: {e}"
research_agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
tools=[search_tool, fetch_page],
system_prompt=(
"You are a research specialist. Search the web to find relevant pages, "
"then fetch their full content for detailed information."
),
)
# Wrap the specialist agent as a tool for the coordinator
research_tool = ComponentTool(
component=research_agent,
name="research_specialist",
description="A specialist that researches topics on the web",
outputs_to_string={"source": "last_message"}, # surface only the final reply
)
# Create the coordinator agent with streaming
coordinator_agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
tools=[research_tool],
system_prompt="You are a coordinator. Delegate research tasks to the research specialist.",
streaming_callback=print_streaming_chunk,
)
result = coordinator_agent.run(
messages=[
ChatMessage.from_user("What are the latest developments in Haystack AI?"),
],
)
print(result["last_message"].text)
Additional References​
đź“– Related docs:
- State — managing shared data between tools
- Human in the Loop — intercepting tool calls for human review
📚 Tutorials:
🧑‍🍳 Cookbook: