// File: _templates/component-template
# Component Name
| | |
| --- | --- |
| **Most common position in a pipeline** | |
| **Mandatory init variables** | |
| **Mandatory run variables** | |
| **Output variables** | |
| **API reference** | |
| **GitHub link** | |
| **Package name** | |
## Overview
*What does it do in general? For example,..?*
*How does it work more specifically? Are there any pitfalls to pay attention to?*
*(if applicable) How is it different from this other very similar component? Which one do you choose?*
## Usage
*Any mandatory imports?*
### On its own
*Code snippet on how to run a component*
### In a pipeline
*Code snippet of a component being introduced in a pipeline*
*There can be more than one example. Add examples of pipelines where this component would be most useful, for example RAG, doc retrieval, etc.*
---
// File: _templates/document-store-template
# Document Store Name
## Description
*What are this Document Store features? When would a user select it, and when not?*
*Are there any limitations?*
*Users are often curious to know if a document store supports metadata filtering and sparse vectors.*
## Initialization
*Describe how to get this Document Store to work, with code samples.*
## Supported Retrievers
*Name of the supported Retriever(s).*
*If several – describe how to choose an appropriate one for user’s goals (perhaps, one is faster and the other is more accurate).*
## Link to GitHub
*for example [https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/gradient](https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/gradient)*
---
// File: concepts/agents/multi-agent-systems
# Multi-Agent Systems
Multi-agent systems let you compose multiple `Agent` instances into larger architectures where a **coordinator** agent delegates to **specialist** agents.
Each specialist focuses on a specific task with its own tools and system prompt - the coordinator plans and routes work without needing to know how each task gets done.
Spawning agents as tools is useful when:
- A task is too broad for a single agent to handle reliably,
- You want to isolate different capabilities into focused, reusable agents,
- You need to keep the coordinator's context lean for better decisions and lower token usage.
In Haystack, you spawn a specialist agent as a tool with [`AgentTool`](../../tools/agenttool.mdx).
## Converting an Agent to a Tool
`AgentTool` wraps a specialist agent so a coordinator can call it.
The coordinator's model sends the task to delegate as a single user message and receives the specialist's final reply as text, so you never describe the agent's interface or unpack its result dict.
The examples on this page use SerperDev web search component that have moved to the `serperdev-haystack` package. Install it to run the examples:
```shell
pip install serperdev-haystack
```
```python
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIResponsesChatGenerator
from haystack.components.generators.utils import print_streaming_chunk
from haystack.dataclasses import ChatMessage
from haystack.tools import AgentTool, ComponentTool
from haystack_integrations.components.websearch.serperdev import SerperDevWebSearch
from haystack.utils import Secret
research_agent = Agent(
chat_generator=OpenAIResponsesChatGenerator(model="gpt-5.4-nano"),
tools=[
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",
),
],
system_prompt="You are a research specialist. Search the web to find information.",
)
research_specialist = AgentTool(
agent=research_agent,
name="research_specialist",
description="A specialist that researches topics on the web",
)
coordinator = Agent(
chat_generator=OpenAIResponsesChatGenerator(model="gpt-5.4-nano"),
tools=[research_specialist],
system_prompt="You are a coordinator. Delegate research tasks to the research specialist.",
streaming_callback=print_streaming_chunk,
)
result = coordinator.run(
messages=[
ChatMessage.from_user("What are the latest developments in Haystack AI?"),
],
)
```
The full specialist configuration is captured inline when serialized.
Wrap the coordinator in a `Pipeline` and call `pipeline.dumps()` to get the YAML, which can be loaded back with `Pipeline.loads()`.
View YAML
```yaml
components:
coordinator:
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_responses.OpenAIResponsesChatGenerator
exit_conditions:
- text
hooks: null
max_agent_steps: 100
raise_on_tool_invocation_failure: false
required_variables: '*'
state_schema: {}
streaming_callback: haystack.components.generators.utils.print_streaming_chunk
system_prompt: You are a coordinator. Delegate research tasks to the research
specialist.
tool_concurrency_limit: 4
tool_streaming_callback_passthrough: false
tools:
- data:
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_responses.OpenAIResponsesChatGenerator
exit_conditions:
- text
hooks: null
max_agent_steps: 100
raise_on_tool_invocation_failure: false
required_variables: '*'
state_schema: {}
streaming_callback: null
system_prompt: You are a research specialist. Search the web to find
information.
tool_concurrency_limit: 4
tool_streaming_callback_passthrough: false
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_integrations.components.websearch.serperdev.websearch.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
description: A specialist that researches topics on the web
inputs_from_state: null
name: research_specialist
outputs_to_state: null
outputs_to_string:
handler: haystack.tools.agent_tool.agent_result_to_string
parameters: null
type: haystack.tools.agent_tool.AgentTool
user_prompt: null
type: haystack.components.agents.agent.Agent
connection_type_validation: true
connections: []
max_runs_per_component: 100
metadata: {}
```
### Alternatives to `AgentTool`
Since `Agent` is a Haystack component, you can also wrap it with [`ComponentTool`](../../tools/componenttool.mdx). However, this exposes the agent's full component interface to the coordinator, including arguments such as `tools`, `generation_kwargs`, and `hook_context`, and the coordinator receives the complete result dictionary. By default, `AgentTool` exposes a single input, `messages`, carrying the task to delegate, plus one parameter for each mandatory prompt variable of the specialist. It returns only the text of the specialist's final reply.
You can also create a similarly narrow interface with the [`@tool`](../../tools/tool.mdx#tool-decorator) decorator. This is useful when you need custom input arguments, want to transform the delegated task, or need to post-process the specialist's response. The trade-off is serialization: a decorated tool serializes as an import path to the function, so the specialist's configuration lives in your Python module rather than in the YAML above.
## Coordinator / Specialist Pattern
The coordinator/specialist pattern cleanly splits responsibilities: the coordinator handles planning and delegation, while each specialist owns a focused toolset 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, but the coordinator only needs the final answer.
`AgentTool` surfaces only the specialist's final reply, keeping the coordinator's context lean.
When covering multiple topics, the coordinator can call the same specialist tool several times in a single response.
All tool calls from one LLM response are executed concurrently using a thread pool.
Control the level of parallelism with the `tool_concurrency_limit` init parameter (default: `4`).
The example below asks the coordinator about two topics: it calls `research_specialist` twice, and both specialists run in parallel.
`HTMLToDocument` uses [Trafilatura](https://trafilatura.readthedocs.io) to extract clean text from HTML pages.
Install it before running:
```shell
pip install trafilatura
```
```python
from typing import Annotated
from haystack.components.agents import Agent
from haystack.components.converters import HTMLToDocument
from haystack.components.fetchers.link_content import LinkContentFetcher
from haystack.components.generators.chat import OpenAIResponsesChatGenerator
from haystack.components.generators.utils import print_streaming_chunk
from haystack_integrations.components.websearch.serperdev import SerperDevWebSearch
from haystack.dataclasses import ChatMessage
from haystack.tools import AgentTool, ComponentTool, tool
from haystack.utils import Secret
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",
)
@tool
def fetch_page(url: Annotated[str, "The URL of the web page to fetch"]) -> str:
"""Fetch the content of a web page given its URL."""
try:
streams = LinkContentFetcher().run(urls=[url])["streams"]
if not streams:
return "No content found."
documents = HTMLToDocument().run(sources=streams)["documents"]
return documents[0].content if documents else "No content extracted."
except Exception as e:
return f"Failed to fetch page: {e}"
research_agent = Agent(
chat_generator=OpenAIResponsesChatGenerator(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. "
"Return a concise summary of your findings in 3-5 sentences."
),
)
research_specialist = AgentTool(
agent=research_agent,
name="research_specialist",
description="Research a topic on the web and report a summary of the findings",
)
coordinator = Agent(
chat_generator=OpenAIResponsesChatGenerator(model="gpt-5.4-nano"),
tools=[research_specialist],
system_prompt=(
"You are a coordinator. Delegate research tasks to the research specialist. "
"For questions covering multiple topics, research each one independently. "
"Keep your final answer concise."
),
streaming_callback=print_streaming_chunk,
tool_concurrency_limit=4, # run up to 4 specialist calls in parallel
)
result = coordinator.run(
messages=[
ChatMessage.from_user(
"What are the latest developments in the Haystack framework, "
"and what is the current state of the Model Context Protocol?",
),
],
)
```
## Additional References
📖 Related docs:
- [Agent](../../pipeline-components/agents-1/agent.mdx)
- [AgentTool](../../tools/agenttool.mdx)
- [State](../../pipeline-components/agents-1/state.mdx)
- [ComponentTool](../../tools/componenttool.mdx)
📚 Tutorials:
- [Creating a Multi-Agent System](https://haystack.deepset.ai/tutorials/45_creating_a_multi_agent_system)
---
// File: concepts/agents
# Agents
This page explains how to create an AI agent in Haystack capable of retrieving information, generating responses, and taking actions using various Haystack components.
## What’s an AI Agent?
An AI agent is a system that can:
- Understand user input (text, image, audio, and other queries),
- Retrieve relevant information (documents or structured data),
- Generate intelligent responses (using LLMs like OpenAI or Hugging Face models),
- Perform actions (calling APIs, fetching live data, executing functions).
AI agents are autonomous systems that use large language models (LLMs) to make decisions and solve complex tasks.
They interact with their environment using tools, memory, and reasoning.
An AI agent is more than a chatbot — it actively plans, chooses the right tools, and executes tasks to achieve a goal.
Unlike traditional software, it adapts to new information and refines its process as needed.
1. **LLM as the Brain**: The agent’s core is an LLM, which understands context, processes natural language and serves as the central intelligence system.
2. **Tools for Interaction**: Agents connect to external tools, APIs, and databases to gather information and take action.
3. **Memory for Context**: Short-term memory helps track conversations, while long-term memory stores knowledge for future interactions.
4. **Reasoning and Planning**: Agents break down complex problems, come up with step-by-step action plans, and adapt based on new data and feedback.
An AI agent starts with a prompt that defines its role and objectives.
It decides when to use tools, gathers data, and refines its approach through loops of reasoning and action.
For example, a customer service agent answers queries using a database — if it lacks an answer, it fetches real-time data, summarizes it, and responds.
A coding assistant understands project requirements, suggests solutions, and writes code.
## Key Components
### Agent Component
Haystack has a built-in [Agent](../pipeline-components/agents-1/agent.mdx) component that manages the full tool-calling loop — it calls the LLM, invokes tools, updates state, and continues until a stopping condition is met.
Key capabilities include:
- **State management**: Share typed data between tools, accumulate results across iterations, and surface them in the result dict using `state_schema`. See [State](../pipeline-components/agents-1/state.mdx).
- **Streaming**: Stream token-by-token output with a `streaming_callback`.
- **Human-in-the-loop**: Intercept tool calls for human review before execution. See [Human in the Loop](../pipeline-components/agents-1/human-in-the-loop.mdx).
- **Multi-agent systems**: Wrap an `Agent` as an `AgentTool` to build coordinator/specialist architectures. See [Multi-Agent Systems](./agents/multi-agent-systems.mdx).
- **MCP server exposure**: Expose your agent as an MCP server using [Hayhooks](../development/hayhooks.mdx), making it callable from any MCP-compatible client such as Claude Desktop or Cursor.
- **Multimodal inputs**: Pass images alongside text using `ImageContent` in `ChatMessage` content parts, or return `ImageContent` from tools for dynamic image analysis. Requires a vision-capable model such as `gpt-5` or `gemini-2.5-flash`. See [Multimodal Inputs](../pipeline-components/agents-1/agent.mdx#multimodal-inputs).
Check out the [Agent](../pipeline-components/agents-1/agent.mdx) documentation, or the [example](#tool-calling-agent) below to get started.
### State
[`State`](../pipeline-components/agents-1/state.mdx) is Haystack's built-in mechanism for sharing data between tools and accumulating results across multiple tool calls.
You define a `state_schema` on the `Agent`, and any keys declared there are returned alongside `messages` and `last_message` in the agent's result dict.
### Tools
Haystack provides several ways to create and manage tools:
- [`Tool`](../tools/tool.mdx) class / [`@tool`](../tools/tool.mdx#tool-decorator) decorator – Define a tool from a Python function. The `@tool` decorator automatically uses the function's name and docstring; the `Tool` class gives full control over the name, description, and schema.
- [`ComponentTool`](../tools/componenttool.mdx) – Wraps any Haystack component as a callable tool.
- [`PipelineTool`](../tools/pipelinetool.mdx) – Wraps a full Haystack pipeline as a callable tool.
- [`AgentTool`](../tools/agenttool.mdx) – Wraps an `Agent` as a callable tool, so another `Agent` can delegate to it.
- [`MCPTool`](../tools/mcptool.mdx) / [`MCPToolset`](../tools/mcptoolset.mdx) – Connects to Model Context Protocol (MCP) servers to load external tools.
- [`Toolset`](../tools/toolset.mdx) – Groups multiple tools into a single unit to pass to an Agent or Generator.
- [`SearchableToolset`](../tools/searchabletoolset.mdx) – Enables keyword-based tool discovery for large catalogs, so the LLM only sees relevant tools at each step.
## Example
### Tool-Calling Agent
Create a tool-calling agent with the `Agent` component. This example requires `OPENAI_API_KEY` and `SERPERDEV_API_KEY` to be set as environment variables:
```shell
export OPENAI_API_KEY=
export SERPERDEV_API_KEY=
```
The examples on this page use SerperDev web search component that have moved to the `serperdev-haystack` package. Install it to run the examples:
```shell
pip install serperdev-haystack
```
```python
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.generators.utils import print_streaming_chunk
from haystack_integrations.components.websearch.serperdev import SerperDevWebSearch
from haystack.dataclasses import ChatMessage
from haystack.tools import ComponentTool
# Wrap the web search component as a tool
web_tool = ComponentTool(
component=SerperDevWebSearch(top_k=3),
name="web_search",
description="Search the web for current information like weather, news, or facts.",
)
tool_calling_agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
system_prompt=(
"You're a helpful agent. When asked about current information like weather, news, or facts, "
"use the web_search tool to find the information and then summarize the findings."
),
tools=[web_tool],
streaming_callback=print_streaming_chunk,
)
result = tool_calling_agent.run(
messages=[ChatMessage.from_user("How is the weather in Berlin?")],
)
print(result["last_message"].text)
```
Resulting in:
```python
>>> The current weather in Berlin is approximately 60°F. The forecast for today includes clouds in the morning with some sunshine later. The high temperature is expected to be around 65°F, and the low tonight will drop to 40°F.
- **Morning**: 49°F
- **Afternoon**: 57°F
- **Evening**: 47°F
- **Overnight**: 39°F
For more details, you can check the full forecasts on [AccuWeather](https://www.accuweather.com/en/de/berlin/10178/current-weather/178087) or [Weather.com](https://weather.com/weather/today/l/5ca23443513a0fdc1d37ae2ffaf5586162c6fe592a66acc9320a0d0536be1bb9).
```
---
// File: concepts/components/custom-components
# Creating Custom Components
Create your own components and use them standalone or in pipelines.
With Haystack, you can easily create any custom components for various tasks, from filtering results to integrating with external software. You can then insert, reuse, and share these components within Haystack or even with an external audience by packaging them and submitting them to [Haystack Integrations](../integrations.mdx)!
## Requirements
Here are the requirements for all custom components:
- `@component`: This decorator marks a class as a component, allowing it to be used in a pipeline.
- `run()`: This is a required method in every component. It accepts input arguments and returns a `dict`. The inputs can either come from the pipeline when it’s executed, or from the output of another component when connected using `connect()`. The `run()` method should be compatible with the input/output definitions declared for the component. See an [Extended Example](#extended-example) below to check how it works.
:::note[Avoid in-place input mutation]
When building custom components, do not change the component's inputs directly. Instead, work on a copy or a new version of the input, modify that, and return it. The reason for this is that the original input values might be reused by other components or by later pipeline steps. Mutating the input directly can lead to unintended side effects and bugs in the pipeline, as other components might rely on the original input values.
When only one or a few fields of the input need to be changed (for example, `meta` on a `Document`), use `dataclasses.replace()` to create a new instance with the updated fields. This is simpler and more efficient than deep-copying the whole object:
```python
from dataclasses import replace
def run(self, documents):
updated = [replace(doc, meta={**doc.meta, "processed": True}) for doc in documents]
return {"documents": updated}
```
When you need to modify nested mutable structures, for example `list` or `dict` attributes, or update many fields of the dataclass instance, use a full deep copy instead:
```python
import copy
def run(self, documents):
documents_copy = copy.deepcopy(documents)
# mutate documents_copy safely here
return {"documents": documents_copy}
```
:::
### Inputs and Outputs
Next, define the inputs and outputs for your component.
#### Inputs
You can choose between three input options:
- `set_input_type`: This method defines or updates a single input socket for a component instance. It’s ideal for adding or modifying a specific input at runtime without affecting others. Use this when you need to dynamically set or modify a single input based on specific conditions.
- `set_input_types`: This method allows you to define multiple input sockets at once, replacing any existing inputs. It’s useful when you know all the inputs the component will need and want to configure them in bulk. Use this when you want to define multiple inputs during initialization.
- Declaring arguments directly in the `run()` method. Use this method when the component’s inputs are static and known at the time of class definition.
#### Outputs
You can choose between two output options:
- `@component.output_types`: This decorator defines the output types and names at the time of class definition. The output names and types must match the `dict` returned by the `run()` method. Use this when the output types are static and known in advance. This decorator is cleaner and more readable for static components.
- `set_output_types`: This method defines or updates multiple output sockets for a component instance at runtime. It’s useful when you need flexibility in configuring outputs dynamically. Use this when the output types need to be set at runtime for greater flexibility.
## Short Example
Here is an example of a simple minimal component setup:
```python
from haystack import component
@component
class WelcomeTextGenerator:
"""
A component generating personal welcome message and making it upper case
"""
@component.output_types(welcome_text=str, note=str)
def run(self, name: str):
return {
"welcome_text": f"Hello {name}, welcome to Haystack!".upper(),
"note": "welcome message is ready",
}
```
Here, the custom component `WelcomeTextGenerator` accepts one input: `name` string and returns two outputs: `welcome_text` and `note`.
## Extended Example
Check out an example below on how to create two custom components and connect them in a Haystack pipeline.
```python
# import necessary dependencies
from haystack import component, Pipeline
# Create two custom components. Note the mandatory @component decorator and @component.output_types, as well as the mandatory run method.
@component
class WelcomeTextGenerator:
"""
A component generating personal welcome message and making it upper case
"""
@component.output_types(welcome_text=str, note=str)
def run(self, name: str):
return {
"welcome_text": (
"Hello {name}, welcome to Haystack!".format(name=name)
).upper(),
"note": "welcome message is ready",
}
@component
class WhitespaceSplitter:
"""
A component for splitting the text by whitespace
"""
@component.output_types(split_text=list[str])
def run(self, text: str):
return {"split_text": text.split()}
# create a pipeline and add the custom components to it
text_pipeline = Pipeline()
text_pipeline.add_component(
name="welcome_text_generator",
instance=WelcomeTextGenerator(),
)
text_pipeline.add_component(name="splitter", instance=WhitespaceSplitter())
# connect the components
text_pipeline.connect(
sender="welcome_text_generator.welcome_text",
receiver="splitter.text",
)
# define the result and run the pipeline
result = text_pipeline.run({"welcome_text_generator": {"name": "Bilge"}})
print(result["splitter"]["split_text"])
```
## Extending the Existing Components
To extend already existing components in Haystack, subclass an existing component and use the `@component` decorator to mark it. Override or extend the `run()` method to process inputs and outputs. Call `super()` with the derived class name from the init of the derived class to avoid initialization issues:
```python
class DerivedComponent(BaseComponent):
def __init__(self):
super(DerivedComponent, self).__init__()
# ...
dc = DerivedComponent() # ok
```
An example of an extended component is Haystack's [FaithfulnessEvaluator](https://github.com/deepset-ai/haystack/blob/e5a80722c22c59eb99416bf0cd712f6de7cd581a/haystack/components/evaluators/faithfulness.py) derived from LLMEvaluator.
## Project Template
If you're building a custom component that you want to package and share, we provide a [GitHub template repository](https://github.com/deepset-ai/custom-component) that gives you a ready-made project structure. It includes the boilerplate for packaging, testing, and distributing your custom component as a standalone Python package. Use it to quickly scaffold a new integration or reusable component without setting up the project from scratch.
Check out the [video walkthrough](https://www.youtube.com/watch?v=SWC0QecAMcI) for a step-by-step guide on how to use the template.
## Additional References
🧑🍳 Cookbooks:
- [Build quizzes and adventures with Character Codex and llamafile](https://haystack.deepset.ai/cookbook/charactercodex_llamafile/)
- [Run tasks concurrently within a custom component](https://haystack.deepset.ai/cookbook/concurrent_tasks/)
- [Chat With Your SQL Database](https://haystack.deepset.ai/cookbook/chat_with_sql_3_ways/)
- [Hacker News Summaries with Custom Components](https://haystack.deepset.ai/cookbook/hackernews-custom-component-rag/)
---
// File: concepts/components/supercomponents
# SuperComponents
`SuperComponent` lets you wrap a complete pipeline and use it like a single component. This is helpful when you want to simplify the interface of a complex pipeline, reuse it in different contexts, or expose only the necessary inputs and outputs.
## `@super_component` decorator (recommended)
Haystack now provides a simple `@super_component` decorator for wrapping a pipeline as a component. All you need is to create a class with the decorator, and to include an `pipeline` attribute.
With this decorator, the `to_dict` and `from_dict` serialization is optional, as is the input and output mapping.
### Example
The custom HybridRetriever example SuperComponent below turns your query into embeddings, then runs both a BM25 search and an embedding-based search at the same time. It finally merges those two result sets and returns the combined documents.
The examples on this page use Sentence Transformers embedders that have moved to the `sentence-transformers-haystack` package. Install it to run the examples:
```shell
pip install sentence-transformers-haystack
```
```python
# pip install haystack-ai datasets sentence-transformers-haystack
from haystack import Document, Pipeline, super_component
from haystack.components.joiners import DocumentJoiner
from haystack_integrations.components.embedders.sentence_transformers import (
SentenceTransformersTextEmbedder,
)
from haystack.components.retrievers import (
InMemoryBM25Retriever,
InMemoryEmbeddingRetriever,
)
from haystack.document_stores.in_memory import InMemoryDocumentStore
from datasets import load_dataset
@super_component
class HybridRetriever:
def __init__(
self,
document_store: InMemoryDocumentStore,
embedder_model: str = "BAAI/bge-small-en-v1.5",
):
embedding_retriever = InMemoryEmbeddingRetriever(document_store)
bm25_retriever = InMemoryBM25Retriever(document_store)
text_embedder = SentenceTransformersTextEmbedder(embedder_model)
document_joiner = DocumentJoiner()
self.pipeline = Pipeline()
self.pipeline.add_component("text_embedder", text_embedder)
self.pipeline.add_component("embedding_retriever", embedding_retriever)
self.pipeline.add_component("bm25_retriever", bm25_retriever)
self.pipeline.add_component("document_joiner", document_joiner)
self.pipeline.connect("text_embedder", "embedding_retriever")
self.pipeline.connect("bm25_retriever", "document_joiner")
self.pipeline.connect("embedding_retriever", "document_joiner")
dataset = load_dataset("HaystackBot/medrag-pubmed-chunk-with-embeddings", split="train")
docs = [
Document(content=doc["contents"], embedding=doc["embedding"]) for doc in dataset
]
document_store = InMemoryDocumentStore()
document_store.write_documents(docs)
query = "What treatments are available for chronic bronchitis?"
result = HybridRetriever(document_store).run(text=query, query=query)
print(result)
```
### Input Mapping
You can optionally map the input names of your SuperComponent to the actual sockets inside the pipeline.
```python
input_mapping = {"query": ["retriever.query", "prompt.query"]}
```
### Output Mapping
You can also map the pipeline's output sockets that you want to expose to the SuperComponent's output names.
```python
output_mapping = {"llm.replies": "replies"}
```
If you don’t provide mappings, SuperComponent will try to auto-detect them. So, if multiple components have outputs with the same name, we recommend using `output_mapping` to avoid conflicts.
## SuperComponent class
Haystack also gives you an option to inherit from SuperComponent class. This option requires `to_dict` and `from_dict` serialization, as well as the input and output mapping described above.
### Example
Here is a simple example of initializing a `SuperComponent` with a pipeline:
```python
from haystack import Pipeline, SuperComponent
with open("pipeline.yaml", "r") as file:
pipeline = Pipeline.load(file)
super_component = SuperComponent(pipeline)
```
The example pipeline below retrieves relevant documents based on a user query, builds a custom prompt using those documents, then sends the prompt to an `OpenAIChatGenerator` to create an answer. The `SuperComponent` wraps the pipeline so it can be run with a simple input (`query`) and returns a clean output (`replies`).
```python
from haystack import Pipeline, SuperComponent
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.builders import ChatPromptBuilder
from haystack.components.retrievers import InMemoryBM25Retriever
from haystack.dataclasses.chat_message import ChatMessage
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.dataclasses import Document
document_store = InMemoryDocumentStore()
documents = [
Document(content="Paris is the capital of France."),
Document(content="London is the capital of England."),
]
document_store.write_documents(documents)
prompt_template = [
ChatMessage.from_user(
'''
According to the following documents:
{% for document in documents %}
{{document.content}}
{% endfor %}
Answer the given question: {{query}}
Answer:
'''
)
]
prompt_builder = ChatPromptBuilder(template=prompt_template, required_variables="*")
pipeline = Pipeline()
pipeline.add_component("retriever", InMemoryBM25Retriever(document_store=document_store))
pipeline.add_component("prompt_builder", prompt_builder)
pipeline.add_component("llm", OpenAIChatGenerator())
pipeline.connect("retriever.documents", "prompt_builder.documents")
pipeline.connect("prompt_builder.prompt", "llm.messages")
# Create a super component with simplified input/output mapping
wrapper = SuperComponent(
pipeline=pipeline,
input_mapping={
"query": ["retriever.query", "prompt_builder.query"],
},
output_mapping={
"llm.replies": "replies",
"retriever.documents": "documents"
}
)
# Run the pipeline with simplified interface
result = wrapper.run(query="What is the capital of France?")
print(result)
{'replies': [ChatMessage(_role=,
_content=[TextContent(text='The capital of France is Paris.')],...)
```
## Type Checking and Static Code Analysis
Creating SuperComponents using the @super_component decorator can induce type or linting errors. One way to avoid these issues is to add the exposed public methods to your SuperComponent. Here's an example:
```python
from typing import TYPE_CHECKING
if TYPE_CHECKING:
def run(self, *, documents: list[Document]) -> dict[str, list[Document]]: ...
def warm_up(self) -> None: # noqa: D102
...
```
## Ready-Made SuperComponents
You can see two implementations of SuperComponents already integrated in Haystack:
- [DocumentPreprocessor](../../pipeline-components/preprocessors/documentpreprocessor.mdx)
- [MultiFileConverter](../../pipeline-components/converters/multifileconverter.mdx)
- [OpenSearchHybridRetriever](../../pipeline-components/retrievers/opensearchhybridretriever.mdx)
---
// File: concepts/components
import ClickableImage from "@site/src/components/ClickableImage";
# Components
Components are the building blocks of a pipeline. They perform tasks such as preprocessing, retrieving, or summarizing text while routing queries through different branches of a pipeline. This page is a summary of all component types available in Haystack.
Components are connected to each other using a [pipeline](pipelines.mdx), and they function like building blocks that can be easily switched out for each other. A component can take the selected outputs of other components as input. You can also provide input to a component when you call `pipeline.run()`.
## Stand-Alone or In a Pipeline
You can integrate components in a pipeline to perform a specific task. But you can also use some of them stand-alone, outside of a pipeline. For example, you can run `DocumentWriter` on its own, to write documents into a Document Store. To check how to use a component and if it's usable outside of a pipeline, check the _Usage_ section on the component's documentation page.
Each component has a `run()` method. When you connect components in a pipeline, and you run the pipeline by calling `Pipeline.run()`, it invokes the `run()` method for each component sequentially.
## Input and Output
To connect components in a pipeline, you need to know the names of the inputs and outputs they accept. The output of one component must be compatible with the input the subsequent component accepts. For example, to connect Retriever and Ranker in a pipeline, you must know that the Retriever outputs `documents` and the Ranker accepts `documents` as input.
The mandatory inputs and outputs are listed in a table at the top of each component's documentation page so that you can quickly check them:
You can also look them up in the code in the component`run()` method. Here's an example of the inputs and outputs of `MetaFieldRanker`:
```python
@component.output_types(documents=list[Document]) # "documents" is the output name you need when connecting components in a pipeline
def run(self, documents: list[Document], top_k: int | None = None): # "documents" is the mandatory input, additionally you can also specify the optional top_k parameter
"""
Ranks a list of Documents based on the selected meta field.
:param documents: List of Documents.
:param top_k: The maximum number of Documents you want the Ranker to return.
:return: List of Documents sorted by the value of their meta field.
"""
```
## Warming Up Components
Components that use heavy resources, like LLMs or embedding models, have a `warm_up()` method that loads the necessary resources (such as models) into memory. This method is automatically called the first time the component runs, so you can use components directly without explicitly calling `warm_up()`:
The examples on this page use Sentence Transformers embedders that have moved to the `sentence-transformers-haystack` package. Install it to run the examples:
```shell
pip install sentence-transformers-haystack
```
```python
from haystack import Document
from haystack_integrations.components.embedders.sentence_transformers import (
SentenceTransformersDocumentEmbedder,
)
doc = Document(content="I love pizza!")
doc_embedder = SentenceTransformersDocumentEmbedder()
result = doc_embedder.run([doc]) # warm_up() is called automatically on first run
print(result["documents"][0].embedding)
```
You can still call `warm_up()` explicitly if you want to control when resources are loaded.
---
// File: concepts/concepts-overview
import ClickableImage from "@site/src/components/ClickableImage";
# Haystack Concepts Overview
Haystack provides all the tools you need to build custom agents and RAG pipelines with LLMs that work for you. This includes everything from prototyping to deployment. This page discusses the most important concepts Haystack operates on.
### Components
Haystack offers various components, each performing different kinds of tasks. You can see the whole variety in the **PIPELINE COMPONENTS** section in the left-side navigation. These are often powered by the latest Large Language Models (LLMs) and transformer models. Code-wise, they are Python classes with methods you can directly call. Most commonly, all you need to do is initialize the component with the required parameters and then run it with a `run()` method.
Working on this level with Haystack components is a hands-on approach. Components define the name and the type of all of their inputs and outputs. The Component API reduces complexity and makes it easier to [create custom components](components/custom-components.mdx), for example, for third-party APIs and databases. Haystack validates the connections between components before running the pipeline and, if needed, generates error messages with instructions on fixing the errors.
#### Generators
[Generators](../pipeline-components/generators.mdx) are responsible for generating text responses after you give them a prompt. They are specific for each LLM technology (OpenAI, Cohere, local models, and others). Haystack core ships ChatGenerators: they enable chat completion and are designed for conversational contexts, expecting a list of Chat Messages to interact with the user. For simpler text generation (for example, translating or summarizing text), they also accept a plain string prompt.
Read more about various Generators in our [guides](../pipeline-components/generators/guides-to-generators/choosing-the-right-generator.mdx).
#### Retrievers
[Retrievers](../pipeline-components/retrievers.mdx) go through all the documents in a Document Store, select the ones that match the user query, and pass it on to the next component. There are various Retrievers that are customized for specific Document Stores. This means that they can handle specific requirements for each database using customized parameters.
For example, for Elasticsearch Document Store, you will find both the Document Store and Retriever packages in its GitHub [repo](https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/elasticsearch).
### Document Stores
[Document Store](document-store.mdx) is an object that stores your documents in Haystack, like an interface to a storage database. It uses specific functions like `write_documents()` or `delete_documents()` to work with data. Various components have access to the Document Store and can interact with it by, for example, reading or writing Documents.
If you are working with more complex pipelines in Haystack, you can use a [`DocumentWriter`](../pipeline-components/writers/documentwriter.mdx) component to write data into Document Stores for you
### Data Classes
You can use different [data classes](data-classes.mdx) in Haystack to carry the data through the system. The data classes are mostly likely to appear as inputs or outputs of your pipelines.
`Document` class contains information to be carried through the pipeline. It can be text, metadata, binary data, or vector representations. Documents can be written into Document Stores but also written and read by other components.
`Answer` class holds not only the answer generated in a pipeline but also the originating query and metadata.
### Pipelines
Finally, you can combine various components, Document Stores, and integrations into [pipelines](pipelines.mdx) to create powerful and customizable systems. It is a highly flexible system that allows you to have simultaneous flows, standalone components, loops, and other types of connections. You can have the preprocessing, indexing, and querying steps all in one pipeline, or you can split them up according to your needs.
If you want to reuse pipelines, you can save them to disk in YAML format or share them around using the [serialization](pipelines/serialization.mdx) process.
Here is a short Haystack pipeline, illustrated:
---
// File: concepts/data-classes/chatmessage
# ChatMessage
`ChatMessage` is the central abstraction to represent a message for a LLM. It contains role, metadata and several types of content, including text, images, tool calls, tool call results, and reasoning content.
To create a `ChatMessage` instance, use `from_user`, `from_system`, `from_assistant`, and `from_tool` class methods.
The [content](#types-of-content) of the `ChatMessage` can then be inspected using the `text`, `texts`, `image`, `images`, `file`, `files`, `tool_call`, `tool_calls`, `tool_call_result`, `tool_call_results`, `reasoning`, and `reasonings` properties.
If you are looking for the details of this data class methods and parameters, head over to our [API documentation](/reference/data-classes-api#chatmessage).
## Types of Content
`ChatMessage` currently supports `TextContent`, `ImageContent`, `FileContent`, `ToolCall`, `ToolCallResult`, and `ReasoningContent` types of content:
```python
@dataclass
class TextContent:
"""
The textual content of a chat message.
:param text: The text content of the message.
"""
text: str
@dataclass
class ToolCall:
"""
Represents a Tool call prepared by the model, usually contained in an assistant message.
:param tool_name: The name of the Tool to call.
:param arguments: The arguments to call the Tool with.
:param id: The ID of the Tool call.
:param extra: Dictionary of extra information about the Tool call. Use to store provider-specific
information. To avoid serialization issues, values should be JSON serializable.
"""
tool_name: str
arguments: Dict[str, Any]
id: Optional[str] = None # noqa: A003
extra: Optional[Dict[str, Any]] = None
@dataclass
class ToolCallResult:
"""
Represents the result of a Tool invocation.
:param result: The result of the Tool invocation.
:param origin: The Tool call that produced this result.
:param error: Whether the Tool invocation resulted in an error.
"""
result: str | Sequence[TextContent | ImageContent]
origin: ToolCall
error: bool
@dataclass
class ImageContent:
"""
The image content of a chat message.
:param base64_image: A base64 string representing the image.
:param mime_type: The MIME type of the image (e.g. "image/png", "image/jpeg").
Providing this value is recommended, as most LLM providers require it.
If not provided, the MIME type is guessed from the base64 string, which can be slow and not always reliable.
:param detail: Optional detail level of the image (only supported by OpenAI). One of "auto", "high", or "low".
:param meta: Optional metadata for the image.
:param validation: If True (default), a validation process is performed:
- Check whether the base64 string is valid;
- Guess the MIME type if not provided;
- Check if the MIME type is a valid image MIME type.
Set to False to skip validation and speed up initialization.
"""
base64_image: str
mime_type: Optional[str] = None
detail: Optional[Literal["auto", "high", "low"]] = None
meta: Dict[str, Any] = field(default_factory=dict)
validation: bool = True
@dataclass
class FileContent:
"""
The file content of a chat message.
:param base64_data: A base64 string representing the file.
:param mime_type: The MIME type of the file (e.g. "application/pdf").
Providing this value is recommended, as most LLM providers require it.
If not provided, the MIME type is guessed from the base64 string, which can be slow and not always reliable.
:param filename: Optional filename of the file. Some LLM providers use this information.
:param extra: Dictionary of extra information about the file. Can be used to store provider-specific information.
To avoid serialization issues, values should be JSON serializable.
:param validation: If True (default), a validation process is performed:
- Check whether the base64 string is valid;
- Guess the MIME type if not provided.
Set to False to skip validation and speed up initialization.
"""
base64_data: str
mime_type: str | None = None
filename: str | None = None
extra: dict[str, Any] = field(default_factory=dict)
validation: bool = True
@dataclass
class ReasoningContent:
"""
Represents the optional reasoning content prepared by the model, usually contained in an assistant message.
:param reasoning_text: The reasoning text produced by the model.
:param extra: Dictionary of extra information about the reasoning content. Use to store provider-specific
information. To avoid serialization issues, values should be JSON serializable.
"""
reasoning_text: str
extra: Dict[str, Any] = field(default_factory=dict)
```
The `ImageContent` and `FileContent` dataclasses also provide two convenience class methods: `from_file_path` and `from_url`.
For more details, refer to our [API documentation](/reference/data-classes-api).
## Working with a ChatMessage
The following examples demonstrate how to create a `ChatMessage` and inspect its properties.
### from_user with TextContent
```python
from haystack.dataclasses import ChatMessage
user_message = ChatMessage.from_user("What is the capital of Australia?")
print(user_message)
>>> ChatMessage(
>>> _role=,
>>> _content=[TextContent(text='What is the capital of Australia?')],
>>> _name=None,
>>> _meta={}
>>>)
print(user_message.text)
>>> What is the capital of Australia?
print(user_message.texts)
>>> ['What is the capital of Australia?']
```
### from_user with TextContent and ImageContent
```python
from haystack.dataclasses import ChatMessage, ImageContent
lion_image_url = (
"https://images.unsplash.com/photo-1546182990-dffeafbe841d?"
"ixlib=rb-4.0&q=80&w=1080&fit=max"
)
image_content = ImageContent.from_url(lion_image_url, detail="low")
user_message = ChatMessage.from_user(
content_parts=[
"What does the image show?",
image_content
])
print(user_message)
>>> ChatMessage(
>>> _role=,
>>> _content=[
>>> TextContent(text='What does the image show?'),
>>> ImageContent(
>>> base64_image='/9j/4...',
>>> mime_type='image/jpeg',
>>> detail='low',
>>> meta={
>>> 'content_type': 'image/jpeg',
>>> 'url': '...'
>>> }
>>> )
>>> ],
>>> _name=None,
>>> _meta={}
>>> )
print(user_message.text)
>>> What does the image show?
print(user_message.texts)
>>> ['What does the image show?']
print(user_message.image)
>>> ImageContent(
>>> base64_image='/9j/4...',
>>> mime_type='image/jpeg',
>>> detail='low',
>>> meta={
>>> 'content_type': 'image/jpeg',
>>> 'url': '...'
>>> }
>>> )
```
### from_user with TextContent and FileContent
```python
from haystack.dataclasses import ChatMessage, FileContent
paper_url = "https://arxiv.org/pdf/2309.08632"
file_content = FileContent.from_url(paper_url)
user_message = ChatMessage.from_user(
content_parts=[
file_content,
"Summarize this paper in 100 words."
])
print(user_message)
>>> ChatMessage(
>>> _role=,
>>> _content=[
>>> FileContent(
>>> base64_data='JVBERi0...',
>>> mime_type='application/pdf',
>>> filename='2309.08632',
>>> extra={}
>>> ),
>>> TextContent(text='Summarize this paper in 100 words.')
>>> ],
>>> _name=None,
>>> _meta={}
>>> )
print(user_message.text)
>>> Summarize this paper in 100 words.
print(user_message.texts)
>>> ['Summarize this paper in 100 words.']
print(user_message.file)
>>> FileContent(
>>> base64_data='JVBERi0...',
>>> mime_type='application/pdf',
>>> filename='2309.08632',
>>> extra={}
>>> )
```
### from_assistant with TextContent
```python
from haystack.dataclasses import ChatMessage
assistant_message = ChatMessage.from_assistant("How can I assist you today?")
print(assistant_message)
>>> ChatMessage(
>>> _role=,
>>> _content=[TextContent(text='How can I assist you today?')],
>>> _name=None,
>>> _meta={}
>>>)
print(assistant_message.text)
>>> How can I assist you today?
print(assistant_message.texts)
>>> ['How can I assist you today?']
```
### from_assistant with ToolCall
```python
from haystack.dataclasses import ChatMessage, ToolCall
tool_call = ToolCall(tool_name="weather_tool", arguments={"location": "Rome"})
assistant_message_w_tool_call = ChatMessage.from_assistant(tool_calls=[tool_call])
print(assistant_message_w_tool_call)
>>> ChatMessage(
>>> _role=,
>>> _content=[ToolCall(tool_name='weather_tool', arguments={'location': 'Rome'}, id=None)],
>>> _name=None,
>>> _meta={}
>>>)
print(assistant_message_w_tool_call.text)
>>> None
print(assistant_message_w_tool_call.texts)
>>> []
print(assistant_message_w_tool_call.tool_call)
>>> ToolCall(tool_name='weather_tool', arguments={'location': 'Rome'}, id=None)
print(assistant_message_w_tool_call.tool_calls)
>>> [ToolCall(tool_name='weather_tool', arguments={'location': 'Rome'}, id=None)]
print(assistant_message_w_tool_call.tool_call_result)
>>> None
print(assistant_message_w_tool_call.tool_call_results)
>>> []
```
### from_tool
```python
from haystack.dataclasses import ChatMessage
tool_message = ChatMessage.from_tool(tool_result="temperature: 25°C", origin=tool_call, error=False)
print(tool_message)
>>> ChatMessage(
>>> _role=,
>>> _content=[ToolCallResult(
>>> result='temperature: 25°C',
>>> origin=ToolCall(tool_name='weather_tool', arguments={'location': 'Rome'}, id=None),
>>> error=False
>>> )],
>>> _name=None,
>>> _meta={}
>>>)
print(tool_message.text)
>>> None
print(tool_message.texts)
>>> []
print(tool_message.tool_call)
>>> None
print(tool_message.tool_calls)
>>> []
print(tool_message.tool_call_result)
>>> ToolCallResult(
>>> result='temperature: 25°C',
>>> origin=ToolCall(tool_name='weather_tool', arguments={'location': 'Rome'}, id=None),
>>> error=False
>>> )
print(tool_message.tool_call_results)
>>> [
>>> ToolCallResult(
>>> result='temperature: 25°C',
>>> origin=ToolCall(tool_name='weather_tool', arguments={'location': 'Rome'}, id=None),
>>> error=False
>>> )
>>> ]
```
## Migrating from Legacy ChatMessage (before v2.9)
In Haystack 2.9, we updated the `ChatMessage` data class for greater flexibility and support for multiple content types: text, tool calls, and tool call results.
There are some breaking changes involved, so we recommend reviewing this guide to migrate smoothly.
### Creating a ChatMessage
You can no longer directly initialize `ChatMessage` using `role`, `content`, and `meta`.
- Use the following class methods instead: `from_assistant`, `from_user`, `from_system`, and `from_tool`.
- Replace the `content` parameter with `text`.
```python
from haystack.dataclasses import ChatMessage
# LEGACY - DOES NOT WORK IN 2.9.0
message = ChatMessage(role=ChatRole.USER, content="Hello!")
# Use the class method instead
message = ChatMessage.from_user("Hello!")
```
### Accessing ChatMessage Attributes
- The legacy `content` attribute is now internal (`_content`).
- Inspect `ChatMessage` attributes using the following properties:
- `role`
- `meta`
- `name`
- `text` and `texts`
- `image` and `images`
- `tool_call` and `tool_calls`
- `tool_call_result` and `tool_call_results`
- `reasoning` and `reasonings`
```python
from haystack.dataclasses import ChatMessage
message = ChatMessage.from_user("Hello!")
# LEGACY - DOES NOT WORK IN 2.9.0
print(message.content)
# Use the appropriate property instead
print(message.text)
```
---
// File: concepts/data-classes/filecontent
# FileContent
`FileContent` represents a file payload that can be attached to a [`ChatMessage`](chatmessage.mdx). Use it when a chat model accepts file inputs, such as PDFs or other documents, together with the user's text prompt.
If you need the full list of parameters and methods, see the [`FileContent` API reference](/reference/data-classes-api#filecontent).
## Attributes
```python
@dataclass
class FileContent:
base64_data: str
mime_type: str | None = None
filename: str | None = None
extra: dict[str, Any] = field(default_factory=dict)
validation: bool = True
```
- `base64_data` stores the file content as a base64-encoded string.
- `mime_type` identifies the file type, for example `application/pdf`. Providing it explicitly is recommended because many model providers require it.
- `filename` is optional, but some providers use it when processing uploaded files.
- `extra` can store provider-specific metadata. Values should be JSON serializable.
- `validation` checks that `base64_data` is valid and tries to infer the MIME type when one is not provided.
## Create from a file path
Use `from_file_path` to read a local file, base64-encode it, infer the MIME type from the path, and populate the filename.
```python
from haystack.dataclasses import ChatMessage, FileContent
file_content = FileContent.from_file_path("data/attention-is-all-you-need.pdf")
message = ChatMessage.from_user(
content_parts=[
file_content,
"Summarize the key ideas in this paper.",
]
)
```
Pass `filename` or `extra` when a provider expects a specific filename or provider-specific options:
```python
file_content = FileContent.from_file_path(
"data/report.pdf",
filename="quarterly-report.pdf",
extra={"source": "finance"},
)
```
## Create from a URL
Use `from_url` to download a file and convert it into a `FileContent` instance.
```python
from haystack.dataclasses import FileContent
file_content = FileContent.from_url(
"https://example.com/reports/quarterly-report.pdf",
timeout=30,
)
```
If no filename is provided, Haystack uses the final path segment of the URL.
## Create from base64 data
If you already have file bytes, encode them and pass the MIME type explicitly.
```python
import base64
from pathlib import Path
from haystack.dataclasses import FileContent
data = Path("data/manual.pdf").read_bytes()
file_content = FileContent(
base64_data=base64.b64encode(data).decode("utf-8"),
mime_type="application/pdf",
filename="manual.pdf",
)
```
Set `validation=False` only when the base64 data and MIME type are already trusted and you want to skip validation.
## Inspect files in a ChatMessage
After adding `FileContent` to a `ChatMessage`, use the `file` and `files` properties to access file payloads.
```python
from haystack.dataclasses import ChatMessage, FileContent
file_content = FileContent.from_file_path("data/invoice.pdf")
message = ChatMessage.from_user(
content_parts=[file_content, "Extract the invoice total."]
)
print(message.file)
print(message.files)
```
`message.file` returns the first file payload, or `None` if there are no files. `message.files` returns all file payloads.
## Serialization
Use `to_dict` and `from_dict` to serialize and restore file content.
```python
payload = file_content.to_dict()
restored = FileContent.from_dict(payload)
```
For tracing, Haystack replaces the full base64 payload with a placeholder so large files are not sent to the tracing backend.
---
// File: concepts/data-classes/imagecontent
# ImageContent
`ImageContent` is a Haystack data class used to represent image-based content in chat messages and multimodal AI pipelines.
It is commonly used with:
* multimodal LLMs
* vision-language models
* image-aware chat applications
* document/image processing workflows
`ImageContent` stores images as base64-encoded strings together with metadata such as MIME type and image detail level.
If you are looking for the full API reference, see the [API documentation](/reference/data-classes-api#imagecontent).
---
# Creating ImageContent
You can create an `ImageContent` object directly from a base64 string:
```python
from haystack.dataclasses import ImageContent
image = ImageContent(base64_image="your_base64_encoded_image", mime_type="image/png")
print(image)
```
---
# Loading Images from a File Path
The `from_file_path()` class method provides a convenient way to load local image files.
```python
from haystack.dataclasses import ImageContent
image = ImageContent.from_file_path("sample.png", detail="low")
print(image)
```
The optional `detail` parameter is currently supported by OpenAI vision models and accepts:
* `"auto"`
* `"high"`
* `"low"`
You can also resize images while loading:
```python
image = ImageContent.from_file_path("sample.png", size=(512, 512))
```
This helps reduce:
* memory usage
* processing time
* payload size
when working with multimodal LLM APIs.
---
# Loading Images from a URL
You can also create an `ImageContent` object directly from an image URL:
```python
from haystack.dataclasses import ImageContent
image = ImageContent.from_url(
"https://images.unsplash.com/photo-1546182990-dffeafbe841d",
detail="low",
)
print(image)
```
Internally, Haystack downloads the image and converts it into a base64 representation.
---
# Producing ImageContent with Converters
In a pipeline, you usually don't create `ImageContent` objects by hand. Instead, you use converter components that read files and produce `ImageContent` for you:
* [`ImageFileToImageContent`](../../pipeline-components/converters/imagefiletoimagecontent.mdx) converts local image files (such as PNG or JPEG) into `ImageContent` objects.
* [`PDFToImageContent`](../../pipeline-components/converters/pdftoimagecontent.mdx) renders the pages of PDF files into `ImageContent` objects.
```python
from haystack.components.converters.image import (
ImageFileToImageContent,
PDFToImageContent,
)
image_converter = ImageFileToImageContent()
image_contents = image_converter.run(sources=["image.jpg", "another_image.png"])[
"image_contents"
]
pdf_converter = PDFToImageContent()
pdf_image_contents = pdf_converter.run(sources=["file.pdf"])["image_contents"]
```
Both converters accept the optional `detail` and `size` parameters, which are forwarded to the `ImageContent` objects they create.
---
# Using ImageContent with ChatMessage
`ImageContent` is commonly used together with [`ChatMessage`](chatmessage.mdx) for multimodal conversations.
```python
from haystack.dataclasses import ChatMessage, ImageContent
image = ImageContent.from_url(
"https://images.unsplash.com/photo-1546182990-dffeafbe841d",
detail="low",
)
message = ChatMessage.from_user(content_parts=["What does this image show?", image])
print(message)
```
This allows multimodal LLMs to process both:
* textual prompts
* image inputs
within the same message.
For more dynamic prompts, you can build multimodal messages with [`ChatPromptBuilder`](../../pipeline-components/builders/chatpromptbuilder.mdx) using Jinja2 string templates. The `| templatize_part` filter inserts an `ImageContent` object as a structured content part instead of plain text:
```python
from haystack.components.builders import ChatPromptBuilder
from haystack.dataclasses import ChatMessage, ImageContent
template = """
{% message role="user" %}
Hello! I am {{user_name}}. What's the difference between the following images?
{% for image in images %}
{{ image | templatize_part }}
{% endfor %}
{% endmessage %}
"""
builder = ChatPromptBuilder(template=template)
images = [
ImageContent.from_file_path("apple.jpg"),
ImageContent.from_file_path("kiwi.jpg"),
]
result = builder.run(user_name="John", images=images)
print(result["prompt"])
```
---
# Metadata
The optional `meta` parameter allows you to attach custom metadata to the image.
```python
image = ImageContent.from_url(
"https://images.unsplash.com/photo-1546182990-dffeafbe841d",
meta={"source": "example-dataset"},
)
```
This can be useful for:
* tracing
* dataset tracking
* workflow metadata
* custom application logic
---
# Validation
By default, `ImageContent` validates:
* base64 encoding
* MIME type correctness
* image MIME compatibility
Validation can be disabled to improve performance:
```python
image = ImageContent(
base64_image="your_base64_encoded_image",
mime_type="image/png",
validation=False,
)
```
---
# Serialization
`ImageContent` supports dictionary serialization.
```python
image_dict = image.to_dict()
restored_image = ImageContent.from_dict(image_dict)
```
---
# Displaying Images
The `show()` method can display images directly in:
* Jupyter notebooks
* local desktop environments
```python
image.show()
```
This requires the `Pillow` package:
```bash
pip install pillow
```
---
# Related Components
`ImageContent` is frequently used with:
* [`ChatMessage`](chatmessage.mdx) — to build multimodal messages
* [`ChatPromptBuilder`](../../pipeline-components/builders/chatpromptbuilder.mdx) — to template multimodal prompts
* [`ImageFileToImageContent`](../../pipeline-components/converters/imagefiletoimagecontent.mdx) — to convert image files into `ImageContent`
* [`PDFToImageContent`](../../pipeline-components/converters/pdftoimagecontent.mdx) — to convert PDF pages into `ImageContent`
---
// File: concepts/data-classes
# Data Classes
In Haystack, there are a handful of core classes that are regularly used in many different places. These are classes that carry data through the system and you are likely to interact with these as either the input or output of your pipeline.
Haystack uses data classes to help components communicate with each other in a simple and modular way. By doing this, data flows seamlessly through the Haystack pipelines. This page goes over the available data classes in Haystack: ByteStream, Answer (along with its variants ExtractedAnswer and GeneratedAnswer), ChatMessage, FileContent, ImageContent, Document, and StreamingChunk, explaining how they contribute to the Haystack ecosystem.
You can check out the detailed parameters in our [Data Classes](/reference/data-classes-api) API reference.
### Answer
#### Overview
The `Answer` class serves as the base for responses generated within Haystack, containing the answer's data, the originating query, and additional metadata.
#### Key Features
- Adaptable data handling, accommodating any data type (`data`).
- Query tracking for contextual relevance (`query`).
- Extensive metadata support for detailed answer description.
#### Attributes
```python
@dataclass
class Answer:
data: Any
query: str
meta: Dict[str, Any]
```
### ExtractedAnswer
#### Overview
`ExtractedAnswer` is a subclass of `Answer` that deals explicitly with answers derived from Documents, offering more detailed attributes.
#### Key Features
- Includes reference to the originating `Document`.
- Score attribute to quantify the answer's confidence level.
- Optional start and end indices for pinpointing answer location within the source.
#### Attributes
```python
@dataclass
class ExtractedAnswer:
query: str
score: float
data: Optional[str] = None
document: Optional[Document] = None
context: Optional[str] = None
document_offset: Optional["Span"] = None
context_offset: Optional["Span"] = None
meta: Dict[str, Any] = field(default_factory=dict)
```
### GeneratedAnswer
#### Overview
`GeneratedAnswer` extends the `Answer` class to accommodate answers generated from multiple Documents.
#### Key Features
- Handles string-type data.
- Links to a list of `Document` objects, enhancing answer traceability.
#### Attributes
```python
@dataclass
class GeneratedAnswer:
data: str
query: str
documents: List[Document]
meta: Dict[str, Any] = field(default_factory=dict)
```
### ByteStream
#### Overview
`ByteStream` represents binary object abstraction in the Haystack framework and is crucial for handling various binary data formats.
#### Key Features
- Holds binary data and associated metadata.
- Optional MIME type specification for flexibility.
- File interaction methods (`to_file`, `from_file_path`, `from_string`) for easy data manipulation.
#### Attributes
```python
@dataclass(repr=False)
class ByteStream:
data: bytes
meta: Dict[str, Any] = field(default_factory=dict, hash=False)
mime_type: Optional[str] = field(default=None)
```
#### Example
```python
from haystack.dataclasses.byte_stream import ByteStream
image = ByteStream.from_file_path("dog.jpg")
```
### ChatMessage
`ChatMessage` is the central abstraction to represent a message for a LLM. It contains role, metadata and several types of content, including text, tool calls and tool calls results.
Read the detailed documentation for the `ChatMessage` data class on a dedicated [ChatMessage](data-classes/chatmessage.mdx) page.
### FileContent
`FileContent` represents a file payload that can be attached to a `ChatMessage`, including base64 data, MIME type, filename, and provider-specific metadata.
Read the detailed documentation for the `FileContent` data class on a dedicated [FileContent](data-classes/filecontent.mdx) page.
### ImageContent
`ImageContent` represents image-based content used in multimodal chat messages and vision-language pipelines.
Read the detailed documentation for the `ImageContent` data class on a dedicated [ImageContent](data-classes/imagecontent.mdx) page.
### Document
#### Overview
`Document` represents a central data abstraction in Haystack, capable of holding text, tables, and binary data.
#### Key Features
- Unique ID for each document.
- Multiple content types are supported: text, binary (`blob`).
- Custom metadata and scoring for advanced document management.
- Optional embedding for AI-based applications.
#### Attributes
```python
@dataclass
class Document(metaclass=_BackwardCompatible):
id: str = field(default="")
content: Optional[str] = field(default=None)
blob: Optional[ByteStream] = field(default=None)
meta: Dict[str, Any] = field(default_factory=dict)
score: Optional[float] = field(default=None)
embedding: Optional[List[float]] = field(default=None)
sparse_embedding: Optional[SparseEmbedding] = field(default=None)
```
#### Example
```python
from haystack import Document
documents = Document(
content="Here are the contents of your document",
embedding=[0.1] * 768,
)
```
### StreamingChunk
#### Overview
`StreamingChunk` represents a partially streamed LLM response, enabling real-time LLM response processing. It encapsulates a segment of streamed content along with associated metadata and provides comprehensive information about the streaming state.
#### Key Features
- String-based content representation for text chunks
- Support for tool calls and tool call results
- Component tracking and metadata management
- Streaming state indicators (start, finish reason)
- Content block indexing for multi-part responses
#### Attributes
```python
@dataclass
class StreamingChunk:
content: str
meta: dict[str, Any] = field(default_factory=dict, hash=False)
component_info: Optional[ComponentInfo] = field(default=None)
index: Optional[int] = field(default=None)
tool_calls: Optional[list[ToolCallDelta]] = field(default=None)
tool_call_result: Optional[ToolCallResult] = field(default=None)
start: bool = field(default=False)
finish_reason: Optional[FinishReason] = field(default=None)
reasoning: Optional[ReasoningContent] = field(default=None)
```
#### Example
```python
from haystack.dataclasses import StreamingChunk, ToolCallDelta, ReasoningContent
# Basic text chunk
chunk = StreamingChunk(
content="Hello world",
start=True,
meta={"model": "gpt-5-mini"},
)
# Tool call chunk
tool_chunk = StreamingChunk(
content="",
tool_calls=[
ToolCallDelta(
index=0,
tool_name="calculator",
arguments='{"operation": "add", "a": 2, "b": 3}',
),
],
index=0,
start=False,
finish_reason="tool_calls",
)
# Reasoning chunk
reasoning_chunk = StreamingChunk(
content="",
reasoning=ReasoningContent(
reasoning_text="Thinking step by step about the answer.",
),
index=0,
start=True,
meta={"model": "gpt-4.1-mini"},
)
```
### ToolCallDelta
#### Overview
`ToolCallDelta` represents a tool call prepared by the model, usually contained in an assistant message during streaming.
#### Attributes
```python
@dataclass
class ToolCallDelta:
index: int
tool_name: Optional[str] = field(default=None)
arguments: Optional[str] = field(default=None)
id: Optional[str] = field(default=None)
extra: Optional[Dict[str, Any]] = field(default=None)
```
### ComponentInfo
#### Overview
The `ComponentInfo` class represents information about a component within a Haystack pipeline. It is used to track the type and name of components that generate or process data, aiding in debugging, tracing, and metadata management throughout the pipeline.
#### Key Features
- Stores the type of the component (including module and class name).
- Optionally stores the name assigned to the component in the pipeline.
- Provides a convenient class method to create a `ComponentInfo` instance from a `Component` object.
#### Attributes
```python
@dataclass
class ComponentInfo:
type: str
name: Optional[str] = field(default=None)
@classmethod
def from_component(cls, component: Component) -> "ComponentInfo": ...
```
#### Example
```python
from haystack.dataclasses.streaming_chunk import ComponentInfo
from haystack.core.component import Component
class MyComponent(Component): ...
component = MyComponent()
info = ComponentInfo.from_component(component)
print(info.type) # e.g., 'my_module.MyComponent'
print(info.name) # Name assigned in the pipeline, if any
```
### SparseEmbedding
#### Overview
The `SparseEmbedding` class represents a sparse embedding: a vector where most values are zeros.
#### Attributes
- `indices`: List of indices of non-zero elements in the embedding.
- `values`: List of values of non-zero elements in the embedding.
### Tool
`Tool` is a data class representing a tool that Language Models can prepare a call for.
Read the detailed documentation for the `Tool` data class on a dedicated [Tool](../tools/tool.mdx) page.
---
// File: concepts/device-management
# Device Management
This page discusses the concept of device management in the context of Haystack.
Many Haystack components, such as `TransformersChatGenerator`, `AzureOpenAIChatGenerator`, and others, allow users the ability to pick and choose which language model is to be queried and executed. For components that interface with cloud-based services, the service provider automatically takes care of the details of provisioning the requisite hardware (like GPUs). However, if you wish to use models on your local machine, you’ll need to figure out how to deploy them on your hardware. Further complicating things, different ML libraries have different APIs to launch models on specific devices.
To make the process of running inference on local models as straightforward as possible, Haystack uses a framework-agnostic device management implementation. Exposing devices through this interface means you no longer need to worry about library-specific invocations and device representations.
## Concepts
Haystack’s device management is built on the following abstractions:
- `DeviceType` - An enumeration that lists all the different types of supported devices.
- `Device` - A generic representation of a device composed of a `DeviceType` and a unique identifier. Together, it represents a single device in the group of all available devices.
- `DeviceMap` - A mapping of strings to `Device` instances. The strings represent model-specific identifiers, usually model parameters. This allows us to map specific parts of a model to specific devices.
- `ComponentDevice` - A tagged union of a single `Device` or a `DeviceMap` instance. Components that support local inference will expose an optional `device` parameter of this type in their constructor.
With the above abstractions, Haystack can fully address any supported device that’s part of your local machine and can support the usage of multiple devices at the same time. Every component that supports local inference will internally handle the conversion of these generic representations to their backend-specific representations.
:::info[Source Code]
Find the full code for the abstractions above in the Haystack GitHub [repo](https://github.com/deepset-ai/haystack/blob/6a776e672fb69cc4ee42df9039066200f1baf24e/haystack/utils/device.py).
:::
## Usage
:::info
The examples below use the [`TransformersChatGenerator`](../pipeline-components/generators/transformerschatgenerator.mdx), which is part of the `transformers-haystack` integration. Install it with:
```bash
pip install transformers-haystack
```
:::
To use a single device for inference, use either the `ComponentDevice.from_single` or `ComponentDevice.from_str` class method:
```python
from haystack.utils import ComponentDevice, Device
from haystack_integrations.components.generators.transformers import (
TransformersChatGenerator,
)
device = ComponentDevice.from_single(Device.gpu(id=1))
# Alternatively, use a PyTorch device string
device = ComponentDevice.from_str("cuda:1")
generator = TransformersChatGenerator(model="Qwen/Qwen3-0.6B", device=device)
```
To use multiple devices, use the `ComponentDevice.from_multiple` class method:
```python
from haystack.utils import ComponentDevice, Device, DeviceMap
from haystack_integrations.components.generators.transformers import (
TransformersChatGenerator,
)
device_map = DeviceMap(
{
"encoder.layer1": Device.gpu(id=0),
"decoder.layer2": Device.gpu(id=1),
"self_attention": Device.disk(),
"lm_head": Device.cpu(),
},
)
device = ComponentDevice.from_multiple(device_map)
generator = TransformersChatGenerator(model="Qwen/Qwen3-0.6B", device=device)
```
### Integrating Devices in Custom Components
Components should expose an optional `device` parameter of type `ComponentDevice`. Once exposed, they can determine what to do with it:
- If `device=None`, the component can pass that to the backend. In this case, the backend decides which device the model will be placed on.
- Alternatively, the component can attempt to automatically pick an available device before passing it to the backend using the `ComponentDevice.resolve_device` class method.
Once the device has been resolved, the component can use the `ComponentDevice.to_*` methods to get the backend-specific representation of the underlying device, which is then passed to the backend.
The `ComponentDevice` instance should be serialized in the component’s `to_dict` and `from_dict` methods.
```python
from haystack.utils import ComponentDevice, Device, DeviceMap
class MyComponent(Component):
def __init__(self, device: Optional[ComponentDevice] = None):
# If device is None, automatically select a device.
self.device = ComponentDevice.resolve_device(device)
def warm_up(self):
# Call the framework-specific conversion method.
self.model = AutoModel.from_pretrained(
"deepset/bert-base-cased-squad2", device=self.device.to_hf()
)
def to_dict(self):
# Serialize the policy like any other (custom) data.
return default_to_dict(
self, device=self.device.to_dict() if self.device else None, ...
)
@classmethod
def from_dict(cls, data):
# Deserialize the device data inplace before passing
# it to the generic from_dict function.
init_params = data["init_parameters"]
init_params["device"] = ComponentDevice.from_dict(init_params["device"])
return default_from_dict(cls, data)
# Automatically selects a device.
c = MyComponent(device=None)
# Uses the first GPU available.
c = MyComponent(device=ComponentDevice.from_str("cuda:0"))
# Uses the CPU.
c = MyComponent(device=ComponentDevice.from_single(Device.cpu()))
# Allow the component to use multiple devices using a device map.
c = MyComponent(device=ComponentDevice.from_multiple(DeviceMap({
"layer1": Device.cpu(),
"layer2": Device.gpu(1),
"layer3": Device.disk()
})))
```
If the component’s backend provides a more specialized API to manage devices, it could add an additional init parameter that acts as a conduit. For instance, `TransformersChatGenerator` exposes a `huggingface_pipeline_kwargs` parameter through which Hugging Face-specific `device_map` arguments can be passed:
```python
from haystack_integrations.components.generators.transformers import (
TransformersChatGenerator,
)
generator = TransformersChatGenerator(
model="Qwen/Qwen3-0.6B",
huggingface_pipeline_kwargs={"device_map": "balanced"},
)
```
In such cases, ensure that the parameter precedence and selection behavior is clearly documented. In the case of `TransformersChatGenerator`, the device map passed through the `huggingface_pipeline_kwargs` parameter overrides the explicit `device` parameter and is documented as such.
---
// File: concepts/document-store/choosing-a-document-store
import ClickableImage from "@site/src/components/ClickableImage";
# Choosing a Document Store
Whether you are developing a chatbot, a RAG system, or an image captioner, at some point, it's likely for your AI
application to compare the input it gets with the information it already knows.
Haystack currently has integrations with seven categories of Document Stores:
- **Vector Databases** — purpose-built for embedding search and semantic retrieval
- **Search Engines** — full-text search engines extended with vector (kNN) capabilities
- **Relational Databases** — SQL databases with vector search via plugins or extensions
- **Document / NoSQL Databases** — flexible document stores with vector search added on top
- **In-memory Key-Value Stores** — ultra-low-latency stores with HNSW vector search
- **Vector Index Libraries** — lightweight in-process vector similarity search, no external service
- **Multi-model Databases** — single engine supporting graph, document, and vector data models
Here is an overview of all the integrations currently available, grouped by category:
## DocumentStore Integrations Available in Haystack
Haystack integrations come in two tiers. **Core integrations** are built and maintained by the Haystack team — they are tested against every release, follow the same API conventions, and come with full documentation and support. **External integrations** are contributed and maintained by the community; they extend Haystack's reach but are not covered by the core release cycle.
The tables below list every available integration alongside the key properties you need to choose the right one for your use case.
#### Core integrations
| Integration | Category | Engine Type | Open Source | Async Support | Retrievers |
| --- | --- | --- | --- | --- | --- |
| ArcadeDB | Multi-model Database | Multi-model database (graph, document, key-value) with HNSW vector search via HTTP/JSON API | Yes | No | Embedding |
| AlloyDB | Relational Database | Managed PostgreSQL-compatible database (Google Cloud) with pgvector extension | No | Yes | Embedding, Keyword |
| ArangoDB | Multi-model Database | Multi-model database (graph, document, key-value) with AQL vector search (requires v3.12+) | Yes (BUSL) | No | Embedding |
| Astra | Document / NoSQL Database | Cloud-native managed NoSQL (Apache Cassandra-based) with vector search via DataStax JSON API | No | No | Embedding |
| Azure AI Search | Search Engine | Managed cloud search service (Microsoft Azure AI Search) with HNSW vector search | No | No | BM25, Embedding, Hybrid |
| Chroma | Vector Database | Purpose-built vector database | Yes | Yes | Embedding |
| Elasticsearch | Search Engine | Distributed search & analytics engine with BM25 + vector (kNN) search | Partial | Yes | BM25, Embedding, SQL |
| FAISS | Vector Index Library | In-memory vector similarity search library (Meta/Facebook) with JSON file for metadata | Yes | No | Embedding |
| FalkorDB | Graph Database | OpenCypher graph database with ANN vector search | Yes (SSPL) | No | Embedding, Cypher |
| MongoDB Atlas | Document / NoSQL Database | Cloud document database with Atlas Vector Search and full-text search | No | Yes | Embedding, Full-text |
| Oracle | Relational Database | Oracle with native AI Vector Search, HNSW vector index and DBMS_SEARCH full-text keyword index | No | Yes | Embedding, Keyword |
| OpenSearch | Search Engine | Distributed search engine (AWS fork of Elasticsearch) with BM25 + kNN vector search | Yes | Yes | BM25, Embedding, Hybrid, Metadata, SQL |
| PGVector | Relational Database | Relational database (PostgreSQL) with the `pgvector` extension for vector similarity search | Yes | Yes | Embedding, Keyword |
| Pinecone | Vector Database | Managed cloud vector database | No | Yes | Embedding |
| Qdrant | Vector Database | Purpose-built vector database with dense + sparse embedding support | Yes | Yes | Embedding, Sparse Embedding, Hybrid |
| Supabase | Relational Database | Managed cloud Supabase — a wrapper over PgvectorDocumentStore with Supabase-specific defaults | Yes | Yes | Embedding, Keyword |
| Valkey | In-memory Key-Value Store | In-memory key-value store (Redis fork) with HNSW vector search via `glide` client | Yes | Yes | Embedding |
| Vespa | Search Engine | Distributed search & serving engine with BM25 lexical + HNSW vector (ANN) search | Yes | No | BM25, Embedding |
| Weaviate | Vector Database | Purpose-built vector database with hybrid search support | Yes | Yes | BM25, Embedding, Hybrid |
#### External integrations
| Integration | Category | Engine Type | Open Source | Async Support | Retrievers |
| --- | --- | --- | --- | --- | --- |
| Couchbase | Document / NoSQL Database | Distributed NoSQL document database with vector search via Search Service | Partial | Yes | Embedding, Full-text |
| LanceDB | Vector Database | Embedded vector database built on the Lance columnar format, optimized for multimodal data | Yes | Yes | Embedding, Full-text, Hybrid |
| Milvus | Vector Database | Open-source vector database built for scalable similarity search | Yes | No | Embedding |
| Needle | Search Engine | Managed RAG-as-a-service platform with built-in document storage and vector search | No | Yes | Embedding, Sparse Embedding, Hybrid |
| Neo4j | Multi-model Database | Graph database with native vector index support for combined graph traversal and similarity search | Partial | No | Embedding |
| SingleStore | Relational Database | Distributed SQL database with native vector search and full-text search support | No | Yes | Embedding, Full-text, Keyword |
## Vector Databases
- Purpose-built for vector and embedding search
- Advanced indexing techniques for efficient similarity search
- Designed for high scalability and availability with large volumes of high-dimensional data
- Most support metadata filtering alongside vector search
- Increasingly adding hybrid (vector + keyword) search support
- Mostly open source, widely available as managed cloud services
**Best for** semantic search over large document corpora — e.g. a knowledge base where users search by meaning rather than exact keywords.
- [Chroma](../../document-stores/chromadocumentstore.mdx)
- [Pinecone](../../document-stores/pinecone-document-store.mdx)
- [Qdrant](../../document-stores/qdrant-document-store.mdx)
- [Weaviate](../../document-stores/weaviatedocumentstore.mdx)
- [LanceDB](https://haystack.deepset.ai/integrations/lancedb) (external integration)
- [Milvus](https://haystack.deepset.ai/integrations/milvus-document-store) (external integration)
## Search Engines
- Originally built for full-text (BM25) search, with vector (kNN) capabilities added later
- Excellent support for text data, tokenisation, and language-aware querying
- Scale both horizontally and vertically in production environments
- Strong foundation for hybrid search combining keyword and semantic retrieval
- Battle-tested in enterprise environments with mature tooling and observability
**Best for** enterprise search or log analytics where both full-text (BM25) and vector search are needed — e.g. an e-commerce product search with filters.
- Azure AI Search ([AzureAISearchDocumentStore](../../document-stores/azureaisearchdocumentstore.mdx))
- [Elasticsearch](../../document-stores/elasticsearch-document-store.mdx)
- [OpenSearch](../../document-stores/opensearch-document-store.mdx)
- [Needle](https://haystack.deepset.ai/integrations/needle) (external integration)
- [Vespa](../../document-stores/vespadocumentstore.mdx)
## Relational Databases
- Standard SQL databases extended with vector search via plugins or extensions
- Vectors live alongside relational data, enabling combined vector + SQL queries in a single store
- Lower operational overhead when PostgreSQL is already part of the stack
- Vector search performance is lower than purpose-built databases, but sufficient for many use cases
- Familiar tooling, transactions, and data integrity guarantees of a relational database
**Best for** use cases where documents live alongside structured relational data — e.g. a product catalogue where vector search and SQL JOINs are both needed.
- [AlloyDB](../../document-stores/alloydbdocumentstore.mdx)
- [Oracle](../../document-stores/oracledocumentstore.mdx)
- [PGVector](../../document-stores/pgvectordocumentstore.mdx)
- [Supabase](../../document-stores/supabasedocumentstore.mdx)
- [SingleStore](https://haystack.deepset.ai/integrations/singlestore) (external integration)
## Document / NoSQL Databases
- General-purpose document stores with vector search added on top
- Flexible, schema-less data model suited for heterogeneous document collections
- Horizontal scaling and high availability inherited from the underlying NoSQL engine
- Good choice when the database is already in use and adding a separate vector store is undesirable
- Vector search performance may trail behind purpose-built databases
**Best for** applications already that want to add RAG capabilities without introducing a new infrastructure component.
- Astra ([AstraDocumentStore](../../document-stores/astradocumentstore.mdx))
- [MongoDB Atlas](../../document-stores/mongodbatlasdocumentstore.mdx)
- [Couchbase](https://haystack.deepset.ai/integrations/couchbase-document-store) (external integration)
## In-memory Key-Value Stores
- In-memory architecture delivers extremely low read/write latency
- Vector search (HNSW) layered on top of an existing caching infrastructure
- Ideal when the stack already includes Valkey as a cache or session store
- Data is ephemeral by default; persistence requires explicit configuration
- Less suited for large corpora where memory cost becomes significant
**Best for** low-latency, real-time retrieval — e.g. a chatbot that needs sub-millisecond response times.
- [Valkey](../../document-stores/valkeydocumentstore.mdx)
## Vector Index Libraries
- Low-level, in-process vector similarity search — not a full database
- No network overhead; runs entirely within the application process
- Very efficient use of hardware resources (CPU/GPU)
- Limited to vectors only; metadata must be managed separately (e.g. via a JSON file)
- No built-in persistence, replication, or multi-client access
**Best for** local prototyping, research, or small-scale applications where a lightweight in-process solution is preferred over running an external database server.
- [FAISS](../../document-stores/faissdocumentstore.mdx)
## Multi-model Databases
- Single engine supporting multiple data models: graph, document, key-value, and vector
- Eliminates the need to maintain separate databases for different data representations
- Suited for knowledge graphs or applications with complex entity relationships
- Vector search (HNSW) available alongside graph traversal and document queries
- Smaller community and ecosystem compared to more established categories
**Best for** applications requiring multiple data models in a single engine — e.g. a knowledge graph where entities are connected by relationships and also need vector similarity search.
- [ArangoDB](../../document-stores/arangodocumentstore.mdx)
- [ArcadeDB](../../document-stores/arcadedbdocumentstore.mdx)
- [FalkorDB](../../document-stores/falkordbdocumentstore.mdx)
- [Neo4j](https://haystack.deepset.ai/integrations/neo4j-document-store) (external integration)
## The In-memory Document Store
Haystack ships with an ephemeral document store that relies on pure Python data structures stored in memory, so it doesn't fall into any of the vector database categories above. This special Document Store is ideal for creating quick prototypes with small datasets. It doesn't require any special setup, and it can be used right away without installing additional dependencies.
- [InMemoryDocumentStore](../../document-stores/inmemorydocumentstore.mdx)
## Final Considerations
It can be very challenging to pick one Document Store over another by only looking at pure performance, as even the slightest difference in the benchmark can produce a different leaderboard (for example, some benchmarks test the cloud services while others work on a reference machine). Thinking about including features like filtering or not can bring in a whole new set of complexities that make the comparison even harder.
What's important for you to know is that the Document Store interface doesn't add much to the costs, and the relative performance of one vector database over another should stay the same when used within Haystack pipelines.
---
// File: concepts/document-store/creating-custom-document-stores
# Creating Custom Document Stores
Create your own Document Stores to manage your documents.
Custom Document Stores are resources that you can build and leverage in situations where a ready-made solution is not available in Haystack. For example:
- You’re working with a vector store that’s not yet supported in Haystack.
- You need a very specific retrieval strategy to search for your documents.
- You want to customize the way Haystack reads and writes documents.
Similar to [custom components](../components/custom-components.mdx), you can use a custom Document Store in a Haystack pipeline as long as you can import its code into your Python program. The best practice is distributing a custom Document Store as a standalone integration package.
## Recommendations
Before you start, there are a few recommendations we provide to ensure a custom Document Store behaves consistently with the rest of the Haystack ecosystem. At the end of the day, a Document Store is just Python code written in a way that Haystack can understand, but the way you name it, organize it, and distribute it can make a difference. None of these recommendations are mandatory, but we encourage you to follow as many as you can.
### Naming Convention
We recommend naming your Document Store following the format `-haystack`, for example, `chroma-haystack`. This makes it consistent with the others, lowering the cognitive load for your users and easing discoverability.
This naming convention applies to the name of the git repository (`https://github.com/your-org/example-haystack`) and the name of the Python package (`example-haystack`).
### Structure
More often than not, a Document Store can be fairly complex, and setting up a dedicated Git repository can be handy and future-proof. To ease this step, we prepared a [GitHub template](https://github.com/deepset-ai/custom-component) that provides the structure you need to host a custom Document Store in a dedicated repository. It includes the boilerplate for packaging, testing, and distributing your custom Document Store as a standalone Python package.
See the instructions in the [template repository](https://github.com/deepset-ai/custom-component) to get started. You can also watch the [video walkthrough](https://www.youtube.com/watch?v=SWC0QecAMcI) for a step-by-step guide.
### Packaging
As with any other [Haystack integration](../integrations.mdx), a Document Store can be added to your Haystack applications by installing an additional Python package, for example, with `pip`. Once you have a Git repository hosting your Document Store and a `pyproject.toml` file to create an `example-haystack` package (using our [GitHub template](https://github.com/deepset-ai/custom-component)), it will be possible to `pip install` it directly from sources, for example:
```shell
pip install git+https://github.com/your-org/example-haystack.git
```
Though very practical to quickly deliver prototypes, if you want others to use your custom Document Store, we recommend you publish a package on PyPI so that it will be versioned and installable with simply:
```shell
pip install example-haystack
```
:::tip
👍
Our [GitHub template](https://github.com/deepset-ai/custom-component) ships a GitHub workflow that will automatically publish the Document Store package on PyPI.
:::
### Documentation
We recommend thoroughly documenting your custom Document Store with a detailed README file and possibly generating API documentation using a static generator.
For inspiration, see the [neo4j-haystack](https://github.com/prosto/neo4j-haystack) repository and its [documentation](https://prosto.github.io/neo4j-haystack/) pages.
## Implementation
### DocumentStore Protocol
You can use any Python class as a Document Store, provided that it implements all the methods of the `DocumentStore` Python protocol defined in Haystack:
```python
class DocumentStore(Protocol):
def to_dict(self) -> Dict[str, Any]:
"""
Serializes this store to a dictionary.
"""
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "DocumentStore":
"""
Deserializes the store from a dictionary.
"""
def count_documents(self) -> int:
"""
Returns the number of documents stored.
"""
def filter_documents(
self,
filters: Optional[Dict[str, Any]] = None,
) -> List[Document]:
"""
Returns the documents that match the filters provided.
"""
def write_documents(
self,
documents: List[Document],
policy: DuplicatePolicy = DuplicatePolicy.FAIL,
) -> int:
"""
Writes (or overwrites) documents into the DocumentStore, return the number of documents that was written.
"""
def delete_documents(self, document_ids: List[str]) -> None:
"""
Deletes all documents with a matching document_ids from the DocumentStore.
"""
```
The `DocumentStore` interface supports the basic CRUD operations you would normally perform on a database or a storage system, and mostly generic components like [`DocumentWriter`](../../pipeline-components/writers/documentwriter.mdx) use it.
### Additional Methods
Usually, a Document Store comes with additional methods that can provide advanced search functionalities. These methods are not part of the `DocumentStore` protocol and don’t follow any particular convention. We designed it like this to provide maximum flexibility to the Document Store when using any specific features of the underlying database.
Some additional methods that are not part of the `DocumentStore` protocol, but are implemented by most Document Stores in Haystack, include:
```python
def delete_all_documents(recreate_index: bool = False)
def update_by_filter(filters: dict[str, Any], meta: dict[str, Any], refresh: bool = False) -> int:
def delete_by_filter(filters: dict[str, Any]) -> int:
```
These methods are not part of the Protocol but highly recommended to implement in your custom Document Store, as users often expect them to be available.
For example, Haystack wouldn’t get in the way when your Document Store defines a specific `search` method that takes a long list of parameters that only make sense in the context of a particular vector database. Normally, a [Retriever](../../pipeline-components/retrievers.mdx) component would then use this additional search method.
### Retrievers
To get the most out of your custom Document Store, in most cases, you would need to create one or more accompanying Retrievers that use the additional search methods mentioned above. Before proceeding and implementing your custom Retriever, it might be helpful to learn more about [Retrievers](../../pipeline-components/retrievers.mdx) in general through the Haystack documentation.
From the implementation perspective, Retrievers in Haystack are like any other custom component. For more details, refer to the [creating custom components](../components/custom-components.mdx) documentation page.
Although not mandatory, we encourage you to follow more specific [naming conventions](../../pipeline-components/retrievers.mdx#naming-conventions) for your custom Retriever.
### Serialization
Haystack requires every component to be representable by a Python dictionary for correct serialization implementation. Some components, such as Retrievers and Writers, maintain a reference to a Document Store instance. Therefore, `DocumentStore` classes should implement the `from_dict` and `to_dict` methods. This allows to rebuild an instance after reading a pipeline from a file.
For a practical example of what to serialize in a custom Document Store, consider a database client you created using an IP address and a database name. When constructing the dictionary to return in `to_dict`, you would store the IP address and the database name, not the database client instance.
### Secrets Management
There's a likelihood that users will need to provide sensitive data, such as passwords, API keys, or private URLs, to create a Document Store instance. This sensitive data could potentially be leaked if it's passed around in plain text.
Haystack has a specific way to wrap sensitive data into special objects called Secrets. This prevents the data from being leaked during serialization roundtrips. We strongly recommend using this feature extensively for data security (better safe than sorry!).
You can read more about Secret Management in Haystack [documentation](../secret-management.mdx).
### Testing
Haystack comes with some testing functionalities you can use in a custom Document Store. In particular, an empty class inheriting from `DocumentStoreBaseTests` would already run the standard tests that any Document Store is expected to pass in order to work properly.
### Implementation Tips
- The best way to learn how to write a custom Document Store is to look at the existing ones: the `InMemoryDocumentStore`, which is part of Haystack, or the [`ElasticsearchDocumentStore`](https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/elasticsearch), which is a Core Integration, are good places to start.
- When starting from scratch, it might be easier to create the four CRUD methods of the `DocumentStore` protocol one at a time and test them one at a time as well. For example:
1. Implement the logic for `count_documents`.
2. In your `test_document_store.py` module, define the test class `TestDocumentStore(CountDocumentsTest)`. Note how we only inherit from the specific testing mix-in `CountDocumentsTest`.
3. Make the tests pass.
4. Implement the logic for `write_documents`.
5. Change `test_document_store.py` so that your class now also derives from the `WriteDocumentsTest` mix-in: `TestDocumentStore(CountDocumentsTest, WriteDocumentsTest)`.
6. Keep iterating with the remaining methods.
- Having a notebook where users can try out your Document Store in a full pipeline can really help adoption, and it’s a great source of documentation. Our [haystack-cookbook](https://github.com/deepset-ai/haystack-cookbook) repository has good visibility, and we encourage contributors to create a PR and add their own.
Verifying that the implementation meets all `DocumentStoreBaseTests` [tests](https://github.com/deepset-ai/haystack/blob/main/haystack/testing/document_store.py) is the minimum requirement for a custom Document Store to be consistent with the rest of the Haystack ecosystem.
But, ideally making it compatible with the ``DocumentStoreBaseExtendedTests`` tests is a good way to ensure that your Document Store meets all the common used functionalities that users expect from a Document Store, such as `delete_all_documents` or `update_by_filter`.
If the technology you are using for your Document Store supports asynchronous operations, we recommend implementing `async` versions of the methods in the `DocumentStore` protocol as well. This allows users to take advantage of async features in their applications and pipelines, improving performance and scalability.
## Get Featured on the Integrations Page
The [Integrations web page](https://haystack.deepset.ai/integrations) makes Haystack integrations visible to the community, and it’s a great opportunity to showcase your work. Once your Document Store is usable and properly packaged, you can open a pull request in the [haystack-integrations](https://github.com/deepset-ai/haystack-integrations) GitHub repository to add an integration tile.
See the [integrations documentation page](../integrations.mdx#how-do-i-showcase-my-integration) for more details.
---
// File: concepts/document-store
# Document Store
You can think of the Document Store as a database that stores your data and provides them to the Retriever at query time. Learn how to use Document Store in a pipeline or how to create your own.
Document Store is an object that stores your documents. In Haystack, a Document Store is different from a component, as it doesn't have the `run()` method. You can think of it as an interface to your database – you put the information there, or you can look through it. This means that a Document Store is not a piece of a pipeline but rather a tool that the components of a pipeline have access to and can interact with.
:::tip[Work with Retrievers]
The most common way to use a Document Store in Haystack is to fetch documents using a Retriever. A Document Store will often have a corresponding Retriever to get the most out of specific technologies. See more information in our [Retriever](../pipeline-components/retrievers.mdx) documentation.
:::
:::note[How to choose a Document Store?]
To learn about different types of Document Stores and their strengths and disadvantages, head to the [Choosing a Document Store](document-store/choosing-a-document-store.mdx) page.
:::
### DocumentStore Protocol
Document Stores in Haystack are designed to use the following methods as part of their protocol:
- `count_documents` returns the number of documents stored in the given store as an integer.
- `filter_documents` returns a list of documents that match the provided filters.
- `write_documents` writes or overwrites documents into the given store and returns the number of documents that were written as an integer.
- `delete_documents` deletes all documents with given `document_ids` from the Document Store.
### Initialization
To use a Document Store in a pipeline, you must initialize it first.
See the installation and initialization details for each Document Store in the "Document Stores" section in the navigation panel on your left.
### Work with Documents
Convert your data into `Document` objects before writing them into a Document Store along with its metadata and document ID.
The ID field is mandatory, so if you don’t choose a specific ID yourself, Haystack will do its best to come up with a unique ID based on the document’s information and assign it automatically. However, since Haystack uses the document’s contents to create an ID, two identical documents might have identical IDs. Keep it in mind as you update your documents, as the ID will not be updated automatically.
```python
document_store = ChromaDocumentStore()
documents = [
Document(
meta={"name": DOCUMENT_NAME, ...}, id="document_unique_id", content="this is content"
),
...
]
document_store.write_documents(documents)
```
To write documents into the `InMemoryDocumentStore`, simply call the `.write_documents()` function:
```python
document_store.write_documents(
[
Document(content="My name is Jean and I live in Paris."),
Document(content="My name is Mark and I live in Berlin."),
Document(content="My name is Giorgio and I live in Rome."),
],
)
```
:::note[`DocumentWriter`]
See `DocumentWriter` component [docs](../pipeline-components/writers/documentwriter.mdx) to write your documents into a Document Store in a pipeline.
:::
### DuplicatePolicy
The `DuplicatePolicy` is a class that defines the different options for handling documents with the same ID in a `DocumentStore`. It has four possible values:
- **NONE**: The default used by `DocumentWriter`. It relies on the `DocumentStore` settings, so each store applies its own policy.
- **OVERWRITE**: Indicates that if a document with the same ID already exists in the `DocumentStore`, it should be overwritten with the new document.
- **SKIP**: If a document with the same ID already exists, the new document will be skipped and not added to the `DocumentStore`.
- **FAIL**: Raises an error if a document with the same ID already exists in the `DocumentStore`. It prevents duplicate documents from being added.
Here is an example of how you could apply the policy to skip the existing document:
```python
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.writers import DocumentWriter
from haystack.document_stores.types import DuplicatePolicy
document_store = InMemoryDocumentStore()
document_writer = DocumentWriter(
document_store=document_store,
policy=DuplicatePolicy.SKIP,
)
```
### Custom Document Store
All custom document stores must implement the [protocol](https://github.com/deepset-ai/haystack/blob/13804293b1bb79743e5a30e980b76a0561dcfaf8/haystack/document_stores/types/protocol.py) with four mandatory methods: `count_documents`,`filter_documents`, `write_documents`, and `delete_documents`.
The `init` function should indicate all the specifics for the chosen database or vector store.
We also recommend having a custom corresponding Retriever to get the most out of a specific Document Store.
See [Creating Custom Document Stores](document-store/creating-custom-document-stores.mdx) page for more details.
---
// File: concepts/integrations
# Introduction to Integrations
The Haystack ecosystem integrates with many other technologies, such as vector databases, model providers and even custom components made by the community. Here you can explore our integrations, which may be maintined by deepset, or submitted by others.
Haystack integrates with a number of other technologies and tools. For example, you can use a number of different model providers or databases with Haystack.
There are two main types of integrations:
- **Maintained by deepset:** All of the integrations we maintain are hosted in the [haystack-core-integrations](https://github.com/deepset-ai/haystack-core-integrations) repository.
- **Maintained by our partners or community:** These are integrations that you, our partners, or anyone else can build and maintain themselves. Given they comply with some of our requirements, we will also showcase these on our website.
## What are integrations?
An integration is any type of external technology that can be used to extend the capabilities of the Haystack framework. Some integration examples are those providing access to model providers like OpenAI or Cohere, to databases like Weaviate and Qdrant, or even to monitoring tools such as Traceloop. They can be components, Document Stores, or any other feature that can be used with Haystack.
We maintain a list of available integrations on the [Haystack Integrations](https://haystack.deepset.ai/integrations) page, where you can see which integrations we maintain or which have been contributed by the community.
An integrations page focuses on explaining how Haystack integrates with that technology. For example, the OpenAI integration page will provide a summary of the various ways Haystack and OpenAI can work together.
Here are the integration types you can currently choose from:
- **Model Provider**: You can see how we integrate with different model providers and the available components through these integrations
- **Document Store**: These are the databases and vector stores you can use with your Haystack pipelines.
- **Evaluation Framework**: Evaluation frameworks that are supported by Haystack that you can use to evaluate Haystack pipelines.
- **Monitoring Tool**: These are tools like Chainlit and Traceloop that integrate with Haystack and provide monitoring and observability capabilities.
- **Data Ingestion**: These are the integrations that allow you to ingest and use data from different resources, such as Notion, Mastodon, and others.
- **Custom Component**: Some integrations that cover very unique use cases are often contributed and maintained by our community members. We list these integrations under the _Custom Component_ tag.
## How do I use an integration?
Each page dedicated to an integration contains installation instructions and basic usage instructions. For example, the OpenAI integration page gives you an overview of the different ways in which you can interact with OpenAI.
## How can I create an integration?
The most common types of integrations are custom components and Document Stores. Integrations such as model providers might even include multiple custom components. Have a look at these documentation pages that will guide you through the requirements for each integration type:
- [Creating Custom Components](components/custom-components.mdx)
- [Creating Custom Document Stores](document-store/creating-custom-document-stores.mdx)
Check out the [video walkthrough](https://www.youtube.com/watch?v=SWC0QecAMcI) for a step-by-step guide on how to use the [custom-component template](https://github.com/deepset-ai/custom-component) to create a Haystack integration.
## How do I showcase my integration?
To make your integration visible to the Haystack community, contribute it to our [haystack-integrations](https://github.com/deepset-ai/haystack-integrations) GitHub repository. There are several requirements you have to follow:
- Make sure your contribution is [packaged](https://packaging.python.org/en/latest/), installable, and runnable. We suggest using [hatch](https://hatch.pypa.io/latest/) for this purpose.
- Provide the GitHub repo and issue link.
- Create a Pull Request in the [haystack-integrations](https://github.com/deepset-ai/haystack-integrations) repo by following the [draft-integration.md](https://github.com/deepset-ai/haystack-integrations/blob/main/draft-integration.md) and include a clear explanation of what your integration is. This page should include:
- Installation instructions
- A list of the components the integration includes
- Examples of how to use it with clear/runnable code
- Licensing information
- (Optionally) Documentation and/or API docs that you’ve generated for your repository
---
// File: concepts/jinja-templates
# Jinja Templates
Learn how Jinja templates work with Haystack components.
Jinja templates are text structures that contain placeholders for generating dynamic content. These placeholders are filled in when the template is rendered. You can check out the full list of Jinja2 features in the [original documentation](https://jinja.palletsprojects.com/en/3.0.x/templates/).
You can use these templates in Haystack [Builders](../pipeline-components/builders.mdx), [OutputAdapter](../pipeline-components/converters/outputadapter.mdx), and [ConditionalRouter](../pipeline-components/routers/conditionalrouter.mdx) components.
Here is an example of `OutputAdapter` using a short Jinja template to output only the content field of the first document in the arrays of documents:
```python
from haystack import Document
from haystack.components.converters import OutputAdapter
adapter = OutputAdapter(template="{{ documents[0].content }}", output_type=str)
input_data = {"documents": [Document(content="Test content")]}
expected_output = {"output": "Test content"}
assert adapter.run(**input_data) == expected_output
```
### Using Python f‑strings with Jinja
When you embed Jinja placeholders inside a Python f‑string, you must escape Jinja’s `{` and `}` by doubling them (so `{{ var }}` becomes `{{{{ var }}}}`). Otherwise, Python will consume the braces and the Jinja variable won’t be found.
Preferred template:
```python
template = """
Language: {{ language }}
Question: {{ question }}
"""
# pass both variables when rendering
```
It you need to use an f‑string (escape braces):
```python
language = "en"
template = f"""
Language: {language}
Question: {{{{ question }}}}
"""
```
## Safety Features
Due to how we use Jinja in some Components, there are some security considerations to take into account. Jinja works by executing embedded in templates, so it’s _imperative_ that they stem from a trusted source. If the template is allowed to be customized by the end user, it can potentially lead to remote code execution.
To mitigate this risk, Jinja templates are executed and rendered in a [sandbox environment](https://jinja.palletsprojects.com/en/3.1.x/sandbox/). While this approach is safer, it's also less flexible and limits the expressiveness of the template. If you need the more advanced functionality of Jinja templates, components that use them provide an `unsafe` init parameter - setting it to `False` will disable the sandbox environment and enable unsafe template rendering.
With unsafe template rendering, the [OutputAdapter](../pipeline-components/converters/outputadapter.mdx) and [ConditionalRouter](../pipeline-components/routers/conditionalrouter.mdx) components allow their `output_type` to be set to one of the [Haystack data classes](data-classes.mdx) such as `ChatMessage`, `Document`, or `Answer`.
---
// File: concepts/metadata-filtering
# Metadata Filtering
This page provides a detailed explanation of how to apply metadata filters at query time.
When you index documents into your Document Store, you can attach metadata to them. One example is the `DocumentLanguageClassifier`, which adds the language of the document's content to its metadata. Components like `MetadataRouter` can then route documents based on their metadata.
You can then use the metadata to filter your search queries, allowing you to narrow down the results by focusing on specific criteria. This ensures your Retriever fetches answers from the most relevant subset of your data.
To illustrate how metadata filters work, imagine you have a set of annual reports from various companies. You may want to perform a search on just a specific year and just on a small selection of companies. This can reduce the workload of the Retriever and also ensure that you get more relevant results.
## Filtering Types
Filters are defined as a dictionary or nested dictionaries that can be of two types: Comparison or Logic.
### Comparison
Comparison operators help search your metadata fields according the specified conditions.
Comparison dictionaries must contain the following keys:
\-`field`: the name of one of the meta fields of a document, such as `meta.years`.
\-`operator`: must be one of the following:
```
- `==`
- `!=`
- `>`
- `>=`
- `<`
- `<=`
- `in`
- `not in`
```
:::info
The available comparison operators may vary depending on the specific Document Store integration. For example, the `ChromaDocumentStore` supports two additional operators: `contains` and `not contains`. Find the details about the supported filters in the specific integration’s API reference.
:::
\-`value`: takes a single value or (in the case of "in" and “not in”) a list of values.
#### Example
Here is an example of a simple filter in the form of a dictionary. The filter selects documents classified as “article” in the `type` meta field of the document:
```python
filters = {"field": "meta.type", "operator": "==", "value": "article"}
```
### Logic
Logical operators can be used to create a nested dictionary, allowing you to apply multiple `fields` as filter conditions. Logic dictionaries must contain the following keys:
\-`operator`: usually one of the following:
```
- `NOT`
- `OR`
- `AND`
```
:::info
The available logic operators may vary depending on the specific Document Store integration. For example, the `ChromaDocumentStore` doesn’t support the `NOT` operator. Find the details about the supported filters in the specific integration’s API reference.
:::
\-`conditions`: must be a list of dictionaries, either of type Comparison or Logic.
#### Nested Filter Example
Here is a more complex filter that uses both Comparison and Logic to find documents where:
- Meta field `type` is "article",
- Meta field `date` is between 1420066800 and 1609455600 (a specific date range),
- Meta field `rating` is greater than or equal to 3,
- Documents are either classified as `genre` ["economy", "politics"] `OR` the meta field `publisher` is "nytimes".
```python
filters = {
"operator": "AND",
"conditions": [
{"field": "meta.type", "operator": "==", "value": "article"},
{"field": "meta.date", "operator": ">=", "value": 1420066800},
{"field": "meta.date", "operator": "<", "value": 1609455600},
{"field": "meta.rating", "operator": ">=", "value": 3},
{
"operator": "OR",
"conditions": [
{
"field": "meta.genre",
"operator": "in",
"value": ["economy", "politics"],
},
{"field": "meta.publisher", "operator": "==", "value": "nytimes"},
],
},
],
}
```
## Filters Usage
Filters can be applied either through the `Retriever` class or directly within Document Stores.
In the `Retriever` class, filters are passed through the `filters` argument. When working with a pipeline, filters can be provided to `Pipeline.run()`, which will automatically route them to the `Retriever` class (refer to the [pipelines documentation](pipelines.mdx) for more information on working with pipelines).
The example below shows how filters can be passed to Retrievers within a pipeline:
```python
pipeline.run(
data={
"retriever": {
"query": "Why did the revenue increase?",
"filters": {
"operator": "AND",
"conditions": [
{"field": "meta.years", "operator": "==", "value": "2019"},
{
"field": "meta.companies",
"operator": "in",
"value": ["BMW", "Mercedes"],
},
],
},
},
},
)
```
In Document Stores, the `filter_documents` method is used to apply filters to stored documents, if the specific integration supports filtering.
The example below shows how filters can be passed to the `QdrantDocumentStore`:
```python
filters = {
"operator": "AND",
"conditions": [
{"field": "meta.type", "operator": "==", "value": "article"},
{"field": "meta.genre", "operator": "in", "value": ["economy", "politics"]},
],
}
results = QdrantDocumentStore.filter_documents(filters=filters)
```
## Additional References
:notebook: Tutorial: [Filtering Documents with Metadata](https://haystack.deepset.ai/tutorials/31_metadata_filtering)
🧑🍳 Cookbook: [Extracting Metadata Filters from a Query](https://haystack.deepset.ai/cookbook/extracting_metadata_filters_from_a_user_query)
---
// File: concepts/pipelines/creating-pipelines
import ClickableImage from "@site/src/components/ClickableImage";
# Creating Pipelines
Learn the general principles of creating a pipeline.
You can use these instructions to create both indexing and query pipelines.
This task uses an example of a semantic document search pipeline.
## Prerequisites
For each component you want to use in your pipeline, you must know the names of its input and output. You can check them on the documentation page for a specific component or in the component's `run()` method. For more information, see [Components: Input and Output](../components.mdx#input-and-output).
## Steps to Create a Pipeline
### 1\. Import dependencies
Import all the dependencies, like pipeline, documents, Document Store, and all the components you want to use in your pipeline.
For example, to create a semantic document search pipelines, you need the `Document` object, the pipeline, the Document Store, Embedders, and a Retriever:
The examples on this page use Sentence Transformers embedders that have moved to the `sentence-transformers-haystack` package. Install it to run the examples:
```shell
pip install sentence-transformers-haystack
```
```python
from haystack import Document, Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack_integrations.components.embedders.sentence_transformers import (
SentenceTransformersTextEmbedder,
)
from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever
```
### 2\. Initialize components
Initialize the components, passing any parameters you want to configure:
```python
document_store = InMemoryDocumentStore(embedding_similarity_function="cosine")
text_embedder = SentenceTransformersTextEmbedder()
retriever = InMemoryEmbeddingRetriever(document_store=document_store)
```
### 3\. Create the pipeline
```python
query_pipeline = Pipeline()
```
### 4\. Add components
Add components to the pipeline one by one. The order in which you do this doesn't matter:
```python
query_pipeline.add_component("component_name", component_type)
# Here is an example of how you'd add the components initialized in step 2 above:
query_pipeline.add_component("text_embedder", text_embedder)
query_pipeline.add_component("retriever", retriever)
# You could also add components without initializing them before:
query_pipeline.add_component("text_embedder", SentenceTransformersTextEmbedder())
query_pipeline.add_component(
"retriever",
InMemoryEmbeddingRetriever(document_store=document_store),
)
```
### 5\. Connect components
Connect the components by indicating which output of a component should be connected to the input of the next component. If a component has only one input or output and the connection is obvious, you can just pass the component name without specifying the input or output.
To understand what inputs are expected to run your pipeline, use an `.inputs()` pipeline function. See a detailed examples in the [Pipeline Inputs](#pipeline-inputs) section below.
Here's a more visual explanation within the code:
```python
# This is the syntax to connect components. Here you're connecting output1 of component1 to input1 of component2:
pipeline.connect("component1.output1", "component2.input1")
# If both components have only one output and input, you can just pass their names:
pipeline.connect("component1", "component2")
# If one of the components has only one output but the other has multiple inputs,
# you can pass just the name of the component with a single output, but for the component with
# multiple inputs, you must specify which input you want to connect
# Here, component1 has only one output, but component2 has multiple inputs:
pipeline.connect("component1", "component2.input1")
# And here's how it should look like for the semantic document search pipeline we're using as an example:
pipeline.connect("text_embedder.embedding", "retriever.query_embedding")
# Because the InMemoryEmbeddingRetriever only has one input, this is also correct:
pipeline.connect("text_embedder.embedding", "retriever")
```
You need to link all the components together, connecting them gradually in pairs. Here's an explicit example for the pipeline we're assembling:
```python
# Imagine this pipeline has four components: text_embedder, retriever, prompt_builder and llm.
# Here's how you would connect them into a pipeline:
query_pipeline.connect("text_embedder.embedding", "retriever")
query_pipeline.connect("retriever", "prompt_builder.documents")
query_pipeline.connect("prompt_builder", "llm")
```
### 6\. Run the pipeline
Wait for the pipeline to validate the components and connections. If everything is OK, you can now run the pipeline. `Pipeline.run()` can be called in two ways, either passing a dictionary of the component names and their inputs, or by directly passing just the inputs. When passed directly, the pipeline resolves inputs to the correct components.
```python
# Here's one way of calling the run() method
results = pipeline.run({"component1": {"input1_value": value1, "input2_value": value2}})
# The inputs can also be passed directly without specifying component names
results = pipeline.run({"input1_value": value1, "input2_value": value2})
# This is how you'd run the semantic document search pipeline we're using as an example:
query = "Here comes the query text"
results = query_pipeline.run({"text_embedder": {"text": query}})
```
## Pipeline Inputs
If you need to understand what component inputs are expected to run your pipeline, Haystack features a useful pipeline function `.inputs()` that lists all the required inputs for the components.
This is how it works:
```python
# A short pipeline example that converts webpages into documents
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.fetchers import LinkContentFetcher
from haystack.components.converters import HTMLToDocument
from haystack.components.writers import DocumentWriter
document_store = InMemoryDocumentStore()
fetcher = LinkContentFetcher()
converter = HTMLToDocument()
writer = DocumentWriter(document_store=document_store)
pipeline = Pipeline()
pipeline.add_component(instance=fetcher, name="fetcher")
pipeline.add_component(instance=converter, name="converter")
pipeline.add_component(instance=writer, name="writer")
pipeline.connect("fetcher.streams", "converter.sources")
pipeline.connect("converter.documents", "writer.documents")
# Requesting a list of required inputs
pipeline.inputs()
# {'fetcher': {'urls': {'type': typing.List[str], 'is_mandatory': True}},
# 'converter': {'meta': {'type': typing.Union[typing.Dict[str, typing.Any], typing.List[typing.Dict[str, typing.Any]], NoneType],
# 'is_mandatory': False,
# 'default_value': None},
# 'extraction_kwargs': {'type': typing.Optional[typing.Dict[str, typing.Any]],
# 'is_mandatory': False,
# 'default_value': None}},
# 'writer': {'policy': {'type': typing.Optional[haystack.document_stores.types.policy.DuplicatePolicy],
# 'is_mandatory': False,
# 'default_value': None}}}
```
From the above response, you can see that the `urls` input is mandatory for `LinkContentFetcher`. This is how you would then run this pipeline:
```python
pipeline.run(
data={"fetcher": {"urls": ["https://docs.haystack.deepset.ai/docs/pipelines"]}},
)
```
## Example
The following example walks you through creating a RAG pipeline.
```python
# import necessary dependencies
from haystack import Pipeline, Document
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.retrievers import InMemoryBM25Retriever
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.builders import ChatPromptBuilder
from haystack.utils import Secret
from haystack.dataclasses import ChatMessage
# create a document store and write documents to it
document_store = InMemoryDocumentStore()
document_store.write_documents(
[
Document(content="My name is Jean and I live in Paris."),
Document(content="My name is Mark and I live in Berlin."),
Document(content="My name is Giorgio and I live in Rome."),
],
)
# A prompt corresponds to an NLP task and contains instructions for the model. Here, the pipeline will go through each Document to figure out the answer.
prompt_template = [
ChatMessage.from_system(
"""
Given these documents, answer the question.
Documents:
{% for doc in documents %}
{{ doc.content }}
{% endfor %}
Question:
""",
),
ChatMessage.from_user("{{question}}"),
ChatMessage.from_system("Answer:"),
]
# create the components adding the necessary parameters
retriever = InMemoryBM25Retriever(document_store=document_store)
prompt_builder = ChatPromptBuilder(template=prompt_template, required_variables="*")
llm = OpenAIChatGenerator(
api_key=Secret.from_env_var("OPENAI_API_KEY"),
model="gpt-4o-mini",
)
# Create the pipeline and add the components to it. The order doesn't matter.
# At this stage, the Pipeline validates the components without running them yet.
rag_pipeline = Pipeline()
rag_pipeline.add_component("retriever", retriever)
rag_pipeline.add_component("prompt_builder", prompt_builder)
rag_pipeline.add_component("llm", llm)
# Arrange pipeline components in the order you need them. If a component has more than one inputs or outputs, indicate which input you want to connect to which output using the format ("component_name.output_name", "component_name, input_name").
rag_pipeline.connect("retriever", "prompt_builder.documents")
rag_pipeline.connect("prompt_builder", "llm")
# Run the pipeline by specifying the first component in the pipeline and passing its mandatory inputs. Optionally, you can pass inputs to other components.
question = "Who lives in paris?"
results = rag_pipeline.run(
{
"retriever": {"query": question},
"prompt_builder": {"question": question},
},
)
print(results["llm"]["replies"])
```
Here's what a [visualized Mermaid graph](visualizing-pipelines.mdx) of this pipeline would look like:
---
// File: concepts/pipelines/debugging-pipelines
import ClickableImage from "@site/src/components/ClickableImage";
# Debugging Pipelines
Learn how to debug and troubleshoot your Haystack pipelines.
There are several options available to you to debug your pipelines:
- [Inspect your components' outputs](#inspecting-component-outputs)
- [Adjust logging](#logging)
- [Set up tracing](#tracing)
- [Try one of the monitoring tool integrations](#monitoring-tools)
## Inspecting Component Outputs
To view outputs from specific pipeline components, add the `include_outputs_from` parameter when executing your pipeline. Place it after the input dictionary and set it to the name of the component whose output you want included in the result.
For example, here’s how you can print the output of `PromptBuilder` in this pipeline:
```python
from haystack import Pipeline, Document
from haystack.utils import Secret
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.builders.chat_prompt_builder import ChatPromptBuilder
from haystack.dataclasses import ChatMessage
# Documents
documents = [
Document(content="Joe lives in Berlin"),
Document(content="Joe is a software engineer"),
]
# Define prompt template
prompt_template = [
ChatMessage.from_system("You are a helpful assistant."),
ChatMessage.from_user(
"Given these documents, answer the question.\nDocuments:\n"
"{% for doc in documents %}{{ doc.content }}{% endfor %}\n"
"Question: {{query}}\nAnswer:",
),
]
# Define pipeline
p = Pipeline()
p.add_component(
instance=ChatPromptBuilder(
template=prompt_template,
required_variables={"query", "documents"},
),
name="prompt_builder",
)
p.add_component(
instance=OpenAIChatGenerator(api_key=Secret.from_env_var("OPENAI_API_KEY")),
name="llm",
)
p.connect("prompt_builder", "llm.messages")
# Define question
question = "Where does Joe live?"
# Execute pipeline
result = p.run(
{"prompt_builder": {"documents": documents, "query": question}},
include_outputs_from="prompt_builder",
)
# Print result
print(result)
```
## Logging
Adjust the logging format according to your debugging needs. See our [Logging](../../development/logging.mdx) documentation for details.
## Real-Time Pipeline Logging
Use Haystack's [`LoggingTracer`](https://github.com/deepset-ai/haystack/blob/main/haystack/tracing/logging_tracer.py) logs to inspect the data that's flowing through your pipeline in real-time.
This feature is particularly helpful during experimentation and prototyping, as you don’t need to set up any tracing backend beforehand.
Here’s how you can enable this tracer. In this example, we are adding color tags (this is optional) to highlight the components' names and inputs:
```python
import logging
from haystack import tracing
from haystack.tracing.logging_tracer import LoggingTracer
logging.basicConfig(
format="%(levelname)s - %(name)s - %(message)s",
level=logging.WARNING,
)
logging.getLogger("haystack").setLevel(logging.DEBUG)
tracing.tracer.is_content_tracing_enabled = (
True # to enable tracing/logging content (inputs/outputs)
)
tracing.enable_tracing(
LoggingTracer(
tags_color_strings={
"haystack.component.input": "\x1b[1;31m",
"haystack.component.name": "\x1b[1;34m",
},
),
)
```
Here’s what the resulting log would look like when a pipeline is run:
## Tracing
To get a bigger picture of the pipeline’s performance, try tracing it with [Langfuse](../../development/tracing/langfuse.mdx).
Our [Tracing](../../development/tracing.mdx) page has more about other tracing solutions for Haystack.
## Monitoring Tools
Take a look at available tracing and monitoring [integrations](https://haystack.deepset.ai/integrations?type=Monitoring+Tool&version=2.0) for Haystack pipelines, such as Arize AI or Arize Phoenix.
---
// File: concepts/pipelines/pipeline-breakpoints
# Pipeline Breakpoints
Learn how to pause and resume Haystack pipeline execution using breakpoints to debug, inspect, and continue workflows from saved snapshots.
## Introduction
Haystack pipelines support breakpoints for debugging complex execution flows. A `Breakpoint` allows you to pause the execution at specific components, inspect the pipeline state, and resume execution from saved snapshots. This feature works for any regular component as well as an `Agent` component.
You can set a `Breakpoint` on any component in a pipeline with a specific visit count. When triggered, the system stops the execution of the `Pipeline` and captures a snapshot of the current pipeline state. The state can be saved to a JSON file when snapshot file saving is enabled, see [Snapshot file saving](#snapshot-file-saving) below. You can inspect and modify the snapshot and use it to resume execution from the exact point where it stopped.
## Setting a `Breakpoint` on a Regular Component
Create a `Breakpoint` by specifying the component name and the visit count at which to trigger it. This is useful for pipelines with loops. The default `visit_count` value is 0.
```python
from haystack.dataclasses.breakpoints import Breakpoint
from haystack.core.errors import BreakpointException
# Create a breakpoint that triggers on the first visit to the "llm" component
break_point = Breakpoint(
component_name="llm",
visit_count=0, # 0 = first visit, 1 = second visit, etc.
snapshot_file_path="/path/to/snapshots", # Optional: save snapshot to file
)
# Run pipeline with breakpoint
try:
result = pipeline.run(data=input_data, break_point=break_point)
except BreakpointException as e:
print(f"Breakpoint triggered at component: {e.component}")
print(f"Component inputs: {e.inputs}")
print(f"Pipeline results so far: {e.results}")
```
A `BreakpointException` is raised containing the component inputs and the outputs of the pipeline up until the moment where the execution was interrupted, such as just before the execution of component associated with the breakpoint – the `llm` in the example above.
If a `snapshot_file_path` is specified in the `Breakpoint` and snapshot file saving is enabled, the system saves a JSON snapshot with the same information as in the `BreakpointException`. Snapshot file saving to disk is disabled by default; see [Snapshot file saving](#snapshot-file-saving) below.
To access the pipeline state during the breakpoint we can both catch the exception raised by the breakpoint as well as specify where the JSON file should be saved, note that file saving is enabled must be enabled.
## Using a custom snapshot callback
You can pass a `snapshot_callback` to `Pipeline.run()` to handle snapshots yourself instead of saving to a file. When a breakpoint is triggered or a snapshot is created on error, the callback is invoked with the `PipelineSnapshot` object. This is useful for saving snapshots to a database, sending them to a remote service, or custom logging.
```python
from haystack.core.errors import BreakpointException
from haystack.dataclasses.breakpoints import Breakpoint, PipelineSnapshot
def my_snapshot_callback(snapshot: PipelineSnapshot) -> None:
# Custom handling: e.g. save to DB, send to API, or log
print(f"Snapshot at component: {snapshot.break_point}")
break_point = Breakpoint(component_name="llm", visit_count=0)
try:
result = pipeline.run(
data=input_data,
break_point=break_point,
snapshot_callback=my_snapshot_callback,
)
except BreakpointException as e:
print(f"Breakpoint triggered: {e.component}")
```
When `snapshot_callback` is provided, file-saving is skipped and the callback is responsible for handling the snapshot.
## Snapshot file saving
Snapshot file saving to disk is **disabled by default**. To save snapshots as JSON files when a breakpoint is triggered or on pipeline failure, set the environment variable `HAYSTACK_PIPELINE_SNAPSHOT_SAVE_ENABLED` to `"true"` or `"1"` (case-insensitive). When enabled, snapshots are written to the path given by `snapshot_file_path` on the breakpoint, or to the default directory in [Error Recovery with Snapshots](#error-recovery-with-snapshots) when a run fails.
Custom `snapshot_callback` functions are always invoked when provided, regardless of this setting.
```python
import os
# Enable saving snapshot files to disk
os.environ["HAYSTACK_PIPELINE_SNAPSHOT_SAVE_ENABLED"] = "true"
break_point = Breakpoint(
component_name="llm",
visit_count=0,
snapshot_file_path="/path/to/snapshots",
)
# When the breakpoint triggers, a JSON file will be written to /path/to/snapshots/
```
## Resuming a Pipeline Execution from a Breakpoint
To resume the execution of a pipeline from the breakpoint, pass the path to the generated JSON file at the run time of the pipeline, using the `pipeline_snapshot`.
Use the `load_pipeline_snapshot()` to first load the JSON and then pass it to the pipeline.
```python
from haystack.core.pipeline.breakpoint import load_pipeline_snapshot
# Load the snapshot
snapshot = load_pipeline_snapshot("llm_2025_05_03_11_23_23.json")
# Resume execution from the snapshot
result = pipeline.run(data={}, pipeline_snapshot=snapshot)
print(result["llm"]["replies"])
```
## Error Recovery with Snapshots
Pipelines automatically create a snapshot of the last valid state if a run fails. The snapshot contains inputs, visit counts, and intermediate outputs up to the failure. You can inspect it, fix the issue, and resume execution from that checkpoint instead of restarting the whole run.
### Access the Snapshot on Failure
Wrap `pipeline.run()` in a `try`/`except` block and retrieve the snapshot from the raised `PipelineRuntimeError`:
```python
from haystack.core.errors import PipelineRuntimeError
try:
pipeline.run(data=input_data)
except PipelineRuntimeError as e:
snapshot = e.pipeline_snapshot
if snapshot is not None:
intermediate_outputs = snapshot.pipeline_state.pipeline_outputs
# Inspect intermediate_outputs to diagnose the failure
```
When snapshot file saving is enabled (see [Snapshot file saving](#snapshot-file-saving)), Haystack also saves the same snapshot as a JSON file on disk.
The directory is chosen automatically in this order:
- `~/.haystack/pipeline_snapshot`
- `/tmp/haystack/pipeline_snapshot`
- `./.haystack/pipeline_snapshot`
Filenames will have the following pattern: `{component_name}_{visit_nr}_{YYYY_MM_DD_HH_MM_SS}.json`.
### Resume from a Snapshot
You can resume directly from the in-memory snapshot or load it from disk.
Resume from memory:
```python
result = pipeline.run(data={}, pipeline_snapshot=snapshot)
```
Resume from disk:
```python
from haystack.core.pipeline.breakpoint import load_pipeline_snapshot
snapshot = load_pipeline_snapshot(
"/path/to/.haystack/pipeline_snapshot/reader_0_2025_09_20_12_33_10.json",
)
result = pipeline.run(data={}, pipeline_snapshot=snapshot)
```
---
// File: concepts/pipelines/pipeline-loops
# Pipeline Loops
Learn how loops work in Haystack pipelines, how they terminate, and how to use them for feedback and self-correction.
Haystack pipelines support **loops**: cycles in the component graph where the output of a later component is fed back into an earlier one.
This enables feedback flows such as self-correction, validation, or iterative refinement, as well as more advanced [agentic behavior](../pipelines.mdx#agentic-pipelines).
At runtime, the pipeline re-runs a component whenever all of its required inputs are ready again.
You control when loops stop either by designing your graph and routing logic carefully or by using built-in [safety limits](#loop-termination-and-safety-limits).
## Multiple Runs of the Same Component
If a component participates in a loop, it can be run multiple times within a single `Pipeline.run()` call.
The pipeline keeps an internal visit counter for each component:
- Each time the component runs, its visit count increases by 1.
- You can use this visit count in debugging tools like [breakpoints](./pipeline-breakpoints.mdx) to inspect specific iterations of a loop.
In the final pipeline result:
- For each component that ran, the pipeline returns **only the last-produced output**.
- To capture outputs from intermediate components (for example, a validator or a router) in the final result dictionary, use the `include_outputs_from` argument of `Pipeline.run()`.
## Loop Termination and Safety Limits
Loops must eventually stop so that a pipeline run can complete.
There are two main ways a loop ends:
1. **Natural completion**: No more components are runnable
The pipeline finishes when the work queue is empty and no component can run again (for example, the router stops feeding inputs back into the loop).
2. **Reaching the maximum run count**
Every pipeline has a per-component run limit, controlled by the `max_runs_per_component` parameter of the `Pipeline` constructor, which is `100` by default. If any component exceeds this limit, Haystack raises a `PipelineMaxComponentRuns` error.
You can set this limit to a lower value:
```python
from haystack import Pipeline
pipe = Pipeline(max_runs_per_component=5)
```
The limit is checked before each execution, so a component with a limit of 3 will complete 3 runs successfully before the error is raised on the 4th attempt.
This safeguard is especially important when experimenting with new loops or complex routing logic.
If your loop condition is wrong or never satisfied, the error prevents the pipeline from running indefinitely.
## Example: Feedback Loop for Self-Correction
The following example shows a simple feedback loop where:
- A `ChatPromptBuilder` creates a prompt that includes previous incorrect replies.
- An `OpenAIChatGenerator` produces an answer.
- A `ConditionalRouter` checks if the answer is correct:
- If correct, it sends the answer to `final_answer` and the loop ends.
- If incorrect, it sends the answer back to the `ChatPromptBuilder`, which triggers another iteration.
```python
from haystack import Pipeline
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.routers import ConditionalRouter
from haystack.dataclasses import ChatMessage
template = [
ChatMessage.from_system(
"Answer the following question concisely with just the answer, no punctuation.",
),
ChatMessage.from_user(
"{% if previous_replies %}"
"Previously you replied incorrectly: {{ previous_replies[0].text }}\n"
"{% endif %}"
"Question: {{ query }}",
),
]
prompt_builder = ChatPromptBuilder(template=template, required_variables=["query"])
generator = OpenAIChatGenerator()
router = ConditionalRouter(
routes=[
{
# End the loop when the answer is correct
"condition": "{{ 'Rome' in replies[0].text }}",
"output": "{{ replies }}",
"output_name": "final_answer",
"output_type": list[ChatMessage],
},
{
# Loop back when the answer is incorrect
"condition": "{{ 'Rome' not in replies[0].text }}",
"output": "{{ replies }}",
"output_name": "previous_replies",
"output_type": list[ChatMessage],
},
],
unsafe=True, # Required to handle ChatMessage objects
)
pipe = Pipeline(max_runs_per_component=3)
pipe.add_component("prompt_builder", prompt_builder)
pipe.add_component("generator", generator)
pipe.add_component("router", router)
pipe.connect("prompt_builder.prompt", "generator.messages")
pipe.connect("generator.replies", "router.replies")
pipe.connect("router.previous_replies", "prompt_builder.previous_replies")
result = pipe.run(
{
"prompt_builder": {
"query": "What is the capital of Italy? If the statement 'Previously you replied incorrectly:' is missing "
"above then answer with Milan.",
},
},
include_outputs_from={"router", "prompt_builder"},
)
print(result["prompt_builder"]["prompt"][1].text) # Shows the last prompt used
print(result["router"]["final_answer"][0].text) # Rome
```
### What Happens During This Loop
1. **First iteration**
- `prompt_builder` runs with `query="What is the capital of Italy?"` and no previous replies.
- `generator` returns a `ChatMessage` with the LLM's answer.
- The router evaluates its conditions and checks if `"Rome"` is in the reply.
- If the answer is incorrect, `previous_replies` is fed back into `prompt_builder.previous_replies`.
2. **Subsequent iterations** (if needed)
- `prompt_builder` runs again, now including the previous incorrect reply in the user message.
- `generator` produces a new answer with the additional context.
- The router checks again whether the answer contains `"Rome"`.
3. **Termination**
- When the router routes to `final_answer`, no more inputs are fed back into the loop.
- The queue empties and the pipeline run finishes successfully.
Because we used `max_runs_per_component=3`, any unexpected behavior that causes the loop to continue would raise a `PipelineMaxComponentRuns` error instead of looping forever.
## Components for Building Loops
Two components are particularly useful for building loops:
- **[`ConditionalRouter`](../../pipeline-components/routers/conditionalrouter.mdx)**: Routes data to different outputs based on conditions. Use it to decide whether to exit the loop or continue iterating. The example above uses this pattern.
- **[`BranchJoiner`](../../pipeline-components/joiners/branchjoiner.mdx)**: Merges inputs from multiple sources into a single output. Use it when a component inside the loop needs to receive both the initial input (on the first iteration) and looped-back values (on subsequent iterations). For example, you might use `BranchJoiner` to feed both user input and validation errors into the same Generator. See the [BranchJoiner documentation](../../pipeline-components/joiners/branchjoiner.mdx#enabling-loops) for a complete loop example.
## Greedy vs. Lazy Variadic Sockets in Loops
Some components support variadic inputs that can receive multiple values on a single socket.
In loops, variadic behavior controls how inputs are consumed across iterations.
- **Greedy variadic sockets**
Consume exactly one value at a time and remove it after the component runs.
This includes user-provided inputs, which prevents them from retriggering the component indefinitely.
Most variadic sockets are greedy by default.
- **Lazy variadic sockets**
Accumulate all values received from predecessors across iterations.
Useful when you need to collect multiple partial results over time (for example, gathering outputs from several loop iterations before proceeding).
For most loop scenarios it's sufficient to just connect components as usual and use `max_runs_per_component` to protect against mistakes.
## Troubleshooting Loops
If your pipeline seems stuck or runs longer than expected, here are common causes and how to debug them.
### Common Causes of Infinite Loops
1. **Condition never satisfied**: Your exit condition (for example, `"Rome" in reply`) might never be true due to LLM behavior or data issues. Always set a reasonable `max_runs_per_component` as a safety net.
2. **Relying on optional outputs**: When a component has multiple output sockets but only returns some of them, the unreturned outputs don't trigger their downstream connections. This can cause confusion in loops.
For example, this pattern can be problematic:
```python
@component
class Validator:
@component.output_types(valid=str, invalid=Optional[str])
def run(self, text: str):
if is_valid(text):
return {"valid": text} # "invalid" is never returned
else:
return {"invalid": text}
```
If you connect `invalid` back to an upstream component for retry, but also have other connections that keep the loop alive, you might get unexpected behavior.
Instead, use a `ConditionalRouter` with explicit, mutually exclusive conditions:
```python
router = ConditionalRouter(
routes=[
{"condition": "{{ is_valid }}", "output": "{{ text }}", "output_name": "valid", ...},
{"condition": "{{ not is_valid }}", "output": "{{ text }}", "output_name": "invalid", ...},
]
)
```
3. **User inputs retriggering the loop**: If a user-provided input is connected to a socket inside the loop, it might cause the loop to restart unexpectedly.
```python
# Problematic: user input goes directly to a component inside the loop
result = pipe.run(
{
"generator": {
"prompt": query
}, # This input persists and may retrigger the loop
}
)
# Better: use an entry-point component outside the loop
result = pipe.run(
{
"prompt_builder": {"query": query}, # Entry point feeds into the loop once
}
)
```
See [Greedy vs. Lazy Variadic Sockets](#greedy-vs-lazy-variadic-sockets-in-loops) for details on how inputs are consumed.
4. **Multiple paths feeding the same component**: If a component inside the loop receives inputs from multiple sources, it runs whenever *any* path provides input.
```python
# Component receives from two sources – runs when either provides input
pipe.connect("source_a.output", "processor.input")
pipe.connect("source_b.output", "processor.input") # Variadic input
```
Ensure you understand when each path produces output, or use `BranchJoiner` to explicitly control the merge point.
### Debugging Tips
1. **Start with a low limit**: When developing loops, set `max_runs_per_component=3` or similar. This helps you catch issues early with a clear error instead of waiting for a timeout.
2. **Use `include_outputs_from`**: Add intermediate components (like your router) to see what's happening at each step:
```python
result = pipe.run(data, include_outputs_from={"router", "validator"})
```
3. **Enable tracing**: Use tracing to see every component execution, including inputs and outputs. This makes it easy to follow each iteration of the loop. For quick debugging, use `LoggingTracer` ([setup instructions](./debugging-pipelines.mdx#real-time-pipeline-logging)). For deeper analysis, integrate with tools like Langfuse or other [tracing backends](../../development/tracing.mdx).
4. **Visualize the pipeline**: Use `pipe.draw()` or `pipe.show()` to see the graph structure and verify your connections are correct. See the [Pipeline Visualization](./visualizing-pipelines.mdx) documentation for details.
5. **Use breakpoints**: Set a `Breakpoint` on a specific component and visit count to inspect the state at that iteration. See [Pipeline Breakpoints](./pipeline-breakpoints.mdx) for details.
6. **Check for blocked pipelines**: If you see a `PipelineComponentsBlockedError`, it means no components can run. This typically indicates a missing connection or a circular dependency. Check that all required inputs are provided.
By combining careful graph design, per-component run limits, and these debugging tools, you can build robust feedback loops in your Haystack pipelines.
---
// File: concepts/pipelines/serialization
# Serializing Pipelines
Save your pipelines into a custom format and explore the serialization options.
Serialization means converting a pipeline to a format that you can save on your disk and load later.
Haystack supports YAML format for pipeline serialization.
## Converting a Pipeline to YAML
Use the `dumps()` method to convert a Pipeline object to YAML:
```python
from haystack import Pipeline
pipe = Pipeline()
print(pipe.dumps())
# Prints:
#
# components: {}
# connections: []
# max_runs_per_component: 100
# metadata: {}
```
You can also use `dump()` method to save the YAML representation of a pipeline in a file:
```python
with open("/content/test.yml", "w") as file:
pipe.dump(file)
```
## Converting a Pipeline Back to Python
You can convert a YAML pipeline back into Python. Use the `loads()` method to convert a string representation of a pipeline (`str`, `bytes` or `bytearray`) or the `load()` method to convert a pipeline represented in a file-like object into a corresponding Python object.
Both loading methods support callbacks that let you modify components during the deserialization process. Deserialization is gated by a trusted-module allowlist, so pipelines referencing classes outside of it fail to load until you extend the allowlist — see [Deserialization Security](#deserialization-security) below.
Here is an example script:
```python
from haystack import Pipeline
from haystack.core.serialization import DeserializationCallbacks
from typing import Type, Dict, Any
# This is the YAML you want to convert to Python:
pipeline_yaml = """
components:
cleaner:
init_parameters:
remove_empty_lines: true
remove_extra_whitespaces: true
remove_regex: null
remove_repeated_substrings: false
remove_substrings: null
type: haystack.components.preprocessors.document_cleaner.DocumentCleaner
converter:
init_parameters:
encoding: utf-8
type: haystack.components.converters.txt.TextFileToDocument
connections:
- receiver: cleaner.documents
sender: converter.documents
max_runs_per_component: 100
metadata: {}
"""
def component_pre_init_callback(
component_name: str,
component_cls: Type,
init_params: Dict[str, Any],
):
# This function gets called every time a component is deserialized.
if component_name == "cleaner":
assert "DocumentCleaner" in component_cls.__name__
# Modify the init parameters. The modified parameters are passed to
# the init method of the component during deserialization.
init_params["remove_empty_lines"] = False
print("Modified 'remove_empty_lines' to False in 'cleaner' component")
else:
print(f"Not modifying component {component_name} of class {component_cls}")
pipe = Pipeline.loads(
pipeline_yaml,
callbacks=DeserializationCallbacks(component_pre_init_callback),
)
```
## Deserialization Security
Loading a pipeline instantiates the classes referenced in the serialized data. To prevent a crafted YAML file from importing and instantiating arbitrary classes, `Pipeline.load`, `Pipeline.loads`, and `Pipeline.from_dict` refuse to import classes from modules outside a trusted-module allowlist and raise a `DeserializationError` instead.
By default, the allowlist contains `haystack`, `haystack_integrations`, `haystack_experimental`, `builtins`, `typing`, and `collections`. Dangerous builtins such as `eval`, `exec`, `compile`, `__import__`, `open`, and `getattr` are blocked even though `builtins` is allowlisted.
### Allowing Custom Modules
Pipelines that reference custom components or callables in other packages fail to load until you add the modules to the allowlist. You can extend it in three ways:
```python
from haystack import Pipeline
# 1. Per call: pass additional module patterns for this deserialization only
pipe = Pipeline.load(open("pipeline.yaml"), allowed_modules=["mypkg.*"])
# 2. Process-wide: extend the allowlist programmatically
from haystack.core.serialization import allow_deserialization_module
allow_deserialization_module("mypkg")
```
```shell
# 3. Environment variable with comma-separated patterns, read on every deserialization call
export HAYSTACK_DESERIALIZATION_ALLOWLIST="mypkg.*,otherpkg.*"
```
Patterns are matched as prefixes by default (`"mypkg"` matches `mypkg` and any of its submodules), or as `fnmatch` globs if they contain `*`, `?`, or `[` somewhere other than a trailing `.*`. A trailing `.*` is treated as a prefix match, so `"mypkg"` and `"mypkg.*"` behave identically.
If the source of the serialized data is fully trusted, you can bypass the allowlist entirely with `unsafe=True`:
```python
pipe = Pipeline.load(open("pipeline.yaml"), unsafe=True)
```
Only use `unsafe=True` when you fully trust where the serialized pipeline comes from — it also lifts the block on dangerous builtins.
### Nested Init Parameter Validation
As an additional safeguard, deserialization validates the keys of `init_parameters` against the class's `__init__` signature before recursing into any nested `{"type": "...", "init_parameters": {...}}` dictionary. A nested dictionary whose key is not an accepted parameter name is rejected with a `DeserializationError` *before* the nested type is imported, which blocks attempts to smuggle untrusted classes into unused parameter slots. Classes whose constructor takes `**kwargs` are exempt, since their accepted parameter set cannot be statically determined.
This validation may surface pre-existing bugs in YAML files — for example typos, leftovers from renamed or removed parameters, or stale snapshots from older Haystack versions. The fix is to update the YAML so each nested-component key matches a real `__init__` parameter of the parent class.
## Default Serialization Behavior
The serialization system uses `default_to_dict` and `default_from_dict` to handle many object types automatically. You typically do **not** need to implement custom `to_dict`/`from_dict` for:
- **Secrets**: serialized and deserialized automatically so that sensitive values aren't stored in plain text.
- **ComponentDevice**: device configuration is detected and restored automatically.
- **Objects with their own `to_dict`/`from_dict`**: any init parameter whose type defines `to_dict()` is serialized by calling it; any dict in `init_parameters` with a `type` key pointing to a class with `from_dict()` is deserialized automatically.
To serialize or deserialize a single component, you can use `component_to_dict` and `component_from_dict` from `haystack.core.serialization`. They use the default behavior above as a fallback when the component doesn't define custom `to_dict`/`from_dict`:
```python
from haystack import component
from haystack.core.serialization import component_from_dict, component_to_dict
@component
class Greeter:
def __init__(self, message: str = "Hello"):
self.message = message
@component.output_types(greeting=str)
def run(self, name: str):
return {"greeting": f"{self.message}, {name}!"}
# Serialize a component instance to a dictionary
greeter = Greeter(message="Hi")
data = component_to_dict(greeter, "my_greeter")
# Deserialize back to a component instance
restored = component_from_dict(Greeter, data, "my_greeter")
assert restored.message == greeter.message
```
:::caution[Init parameters must be stored as instance attributes]
Default serialization only works when there is a **1:1 mapping** between init parameter names and instance attributes. For every argument in `__init__`, the component must assign it to an attribute with the same name. For example, if you have `def __init__(self, prompt: str)`, you must have `self.prompt = prompt` in the class. Otherwise the serialization logic can't find the value to serialize and raises an error or uses the default value if the parameter has one.
:::
## Performing Custom Serialization
Pipelines and components in Haystack can serialize simple components, including custom ones, out of the box. Code like this just works:
```python
from haystack import component
@component
class RepeatWordComponent:
def __init__(self, times: int):
self.times = times
@component.output_types(result=str)
def run(self, word: str):
return word * self.times
```
On the other hand, this code doesn't work if the final format is JSON, as the `set` type is not JSON-serializable:
```python
from haystack import component
@component
class SetIntersector:
def __init__(self, intersect_with: set):
self.intersect_with = intersect_with
@component.output_types(result=set)
def run(self, data: set):
return data.intersection(self.intersect_with)
```
In such cases, you can provide your own implementation `from_dict` and `to_dict` to components:
```python
from haystack import component, default_from_dict, default_to_dict
class SetIntersector:
def __init__(self, intersect_with: set):
self.intersect_with = intersect_with
@component.output_types(result=set)
def run(self, data: set):
return data.intersect(self.intersect_with)
def to_dict(self):
return default_to_dict(self, intersect_with=list(self.intersect_with))
@classmethod
def from_dict(cls, data):
# convert the set into a list for the dict representation,
# so it can be converted to JSON
data["intersect_with"] = set(data["intersect_with"])
return default_from_dict(cls, data)
```
## Saving a Pipeline to a Custom Format
Once a pipeline is available in its dictionary format, the last step of serialization is to convert that dictionary into a format you can store or send over the wire. Haystack supports YAML out of the box, but if you need a different format, you can write a custom Marshaller.
A `Marshaller` is a Python class responsible for converting text to a dictionary and a dictionary to text according to a certain format. Marshallers must respect the `Marshaller` [protocol](https://github.com/deepset-ai/haystack/blob/main/haystack/marshal/protocol.py), providing the methods `marshal` and `unmarshal`.
This is the code for a custom TOML marshaller that relies on the `rtoml` library:
```python
# This code requires a `pip install rtoml`
from typing import Dict, Any, Union
import rtoml
class TomlMarshaller:
def marshal(self, dict_: Dict[str, Any]) -> str:
return rtoml.dumps(dict_)
def unmarshal(self, data_: Union[str, bytes]) -> Dict[str, Any]:
return dict(rtoml.loads(data_))
```
You can then pass a Marshaller instance to the methods `dump`, `dumps`, `load`, and `loads`:
```python
from haystack import Pipeline
from my_custom_marshallers import TomlMarshaller
pipe = Pipeline()
pipe.dumps(TomlMarshaller())
# prints:
# 'max_runs_per_component = 100\nconnections = []\n\n[metadata]\n\n[components]\n'
```
## Additional References
:notebook: Tutorial: [Serializing LLM Pipelines](https://haystack.deepset.ai/tutorials/29_serializing_pipelines)
---
// File: concepts/pipelines/smart-pipeline-connections
# Smart Pipeline Connections
Haystack pipelines support smarter connection semantics that reduce boilerplate and make pipeline definitions easier to read and maintain.
These features focus on simplifying how components are connected, without changing component behavior.
Smart connections help eliminate common glue components such as `Joiners` and `OutputAdapters` in many pipelines.
## Implicit List Joining
Pipelines natively support connecting multiple component outputs directly to a single component input, without requiring an explicit `Joiner` component.
This works when:
* The target input is typed as `list`, `list | None`, or a union of list types (e.g. `list[int] | list[str]`).
* All connected outputs are compatible list types.
When multiple outputs are connected to the same input, the pipeline implicitly concatenates the lists from the outputs into a single list for the input.
### Example
Multiple converters can write directly into a single `DocumentWriter` without using a `DocumentJoiner`:
Expand to see the pipeline graph
```python
from haystack import Pipeline
from haystack.components.converters import HTMLToDocument, TextFileToDocument
from haystack.components.routers import FileTypeRouter
from haystack.components.writers import DocumentWriter
from haystack.dataclasses import ByteStream
from haystack.document_stores.in_memory import InMemoryDocumentStore
sources = [
ByteStream.from_string(text="Text file content", mime_type="text/plain"),
ByteStream.from_string(
text="Some content",
mime_type="text/html",
),
]
doc_store = InMemoryDocumentStore()
pipe = Pipeline()
pipe.add_component("router", FileTypeRouter(mime_types=["text/plain", "text/html"]))
pipe.add_component("txt_converter", TextFileToDocument())
pipe.add_component("html_converter", HTMLToDocument())
pipe.add_component("writer", DocumentWriter(doc_store))
pipe.connect("router.text/plain", "txt_converter.sources")
pipe.connect("router.text/html", "html_converter.sources")
pipe.connect("txt_converter.documents", "writer.documents")
pipe.connect("html_converter.documents", "writer.documents")
result = pipe.run({"router": {"sources": sources}})
```
This pattern is especially useful when routing files, documents, or results across multiple parallel branches.
## Flexible Type Connections
To further streamline pipeline definitions, Haystack pipelines support limited implicit type adaptation at connection time.
This makes pipeline connections more flexible and reduces the need for `OutputAdapter` components.
**Supported adaptations**
| Source Type | Target Type | Behavior |
|--------------------------|--------------------|---------------------------------------------------------------|
| `str` | `ChatMessage` | Wrapped into a `ChatMessage` with user role. |
| `ChatMessage` | `str` | Extracts `ChatMessage.text`; raises `PipelineRuntimeError` if `None`. |
| `T` | `list[T]` | Wraps the item into a single-element list. |
| `list[str] or list[ChatMessage]`| `str` or `ChatMessage` | Extracts the first item; raises `PipelineRuntimeError` if the list is empty. |
All adaptations are checked at connection time to ensure type safety, but applied at runtime during pipeline execution.
When multiple connections are possible, strict type matching is prioritized over implicit conversion.
This preserves backward compatibility with earlier versions of Haystack, where flexible type connections were not supported.
### Example
Pipeline connecting the Chat Generator `messages` output (`list[ChatMessage]`) to the retriever `query` input (`str`)
without using an `OutputAdapter`:
```python
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.dataclasses import Document
from haystack.components.retrievers import InMemoryBM25Retriever
from haystack import Pipeline
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
document_store = InMemoryDocumentStore()
documents = [
Document(content="Bob lives in Paris."),
Document(content="Alice lives in London."),
Document(content="Ivy lives in Melbourne."),
Document(content="Kate lives in Brisbane."),
Document(content="Liam lives in Adelaide."),
]
document_store.write_documents(documents)
template = """{% message role="user" %}
Rewrite the following query to be used for keyword search.
{{ query }}
{% endmessage %}
"""
p = Pipeline()
p.add_component("prompt_builder", ChatPromptBuilder(template=template))
p.add_component("llm", OpenAIChatGenerator(model="gpt-4.1-mini"))
p.add_component(
"retriever",
InMemoryBM25Retriever(document_store=document_store, top_k=3),
)
p.connect("prompt_builder", "llm")
# implicitly converts list[ChatMessage] -> str
p.connect("llm", "retriever")
query = """Someday I'd love to visit Brisbane, but for now I just want
to know the names of the people who live there."""
result = p.run(data={"prompt_builder": {"query": query}})
```
## When You Still Need `Joiners` or `OutputAdapters`
Explicit `Joiners` or `OutputAdapters` are still useful when you need:
- Custom aggregation logic beyond simple list concatenation
- Type conversions not covered by implicit adaptation
- Explicit control over formatting or ordering
Smart connections reduce the need for glue components, but they do not remove them entirely.
When in doubt, explicit components provide clarity and more control.
---
// File: concepts/pipelines/visualizing-pipelines
import ClickableImage from "@site/src/components/ClickableImage";
# Visualizing Haystack Pipelines
You can visualize your pipelines as graphs to better understand how the components are connected.
Haystack pipelines have `draw()` and `show()` methods that enable you to visualize the pipeline as a graph using Mermaid graphs.
:::note[Data Privacy Notice]
Exercise caution with sensitive data when using pipeline visualization.
This feature is based on Mermaid graphs web service that doesn't have clear terms of data retention or privacy policy.
:::
## Prerequisites
To use Mermaid graphs, you must have an internet connection to reach the Mermaid graph renderer at https://mermaid.ink.
## Displaying a Graph
Use the pipeline's `show()` method to display the diagram in Jupyter notebooks.
```python
my_pipeline.show()
```
## Saving a Graph
Use the pipeline's `draw()` method passing the path where you want to save the diagram and the diagram format. Possible formats are: `mermaid-text` and `mermaid-image` (default).
```python
my_pipeline.draw(path=local_path)
```
## Visualizing SuperComponents
To show the internal structure of [SuperComponents](../components/supercomponents.mdx) in your digram instead of a black box component, set the `super_component_expansion` parameter to `True`:
```python
my_pipeline.show(super_component_expansion=True)
# or
my_pipeline.draw(path=local_path, super_component_expansion=True)
```
## Visualizing Locally
If you don't have an internet connection or don't want to send your pipeline data to the remote https://mermaid.ink, you can install a local mermaid.ink server and use it to render your pipeline.
Let's run a local mermaid.ink server using their official Docker images from https://github.com/jihchi/mermaid.ink/pkgs/container/mermaid.ink.
In this case, let's install one for a system running a MacOS M3 chip and expose it on port 3000:
```dockerfile
docker run --platform linux/amd64 --publish 3000:3000 --cap-add=SYS_ADMIN ghcr.io/jihchi/mermaid.ink
```
Check that the local mermaid.ink server is running by going to http://localhost:3000/.
You should see a local server running, and now you can simply render the image using your local mermaid.ink server by specifying the URL when calling the`show()`or `draw()` method:
```python
my_pipeline.show(server_url="http://localhost:3000")
# or
my_pipeline.draw("my_pipeline.png", server_url="http://localhost:3000")
```
## Example
This is an example of what a pipeline graph may look like:
## Importing a Pipeline to Haystack Enterprise Platform
You can import your Haystack pipeline into Haystack Enterprise Platform and continue visually building your pipeline
To do that, follow the steps described in Haystack Enterprise Platform [documentation](https://docs.cloud.deepset.ai/docs/import-a-pipeline#import-your-pipeline).
---
// File: concepts/pipelines
import ClickableImage from "@site/src/components/ClickableImage";
import YoutubeEmbed from "@site/src/components/YoutubeEmbed";
# Pipelines
To build modern search pipelines with LLMs, you need two things: powerful components and an easy way to put them together. The Haystack pipeline is built for this purpose and enables you to design and scale your interactions with LLMs.
The pipelines in Haystack are directed multigraphs of different Haystack components and integrations. They give you the freedom to connect these components in various ways. This means that the pipeline doesn't need to be a continuous stream of information. With the flexibility of Haystack pipelines, you can have simultaneous flows, standalone components, loops, and other types of connections.
## Flexibility
Haystack pipelines are much more than just query and indexing pipelines. What a pipeline does, whether that be indexing, querying, fetching from an API, preprocessing or more, completely depends on how you design your pipeline and what components you use. While you can still create single-function pipelines, like indexing pipelines using ready-made components to clean up, split, and write the documents into a Document Store, or query pipelines that just take a query and return an answer, Haystack allows you to combine multiple use cases into one pipeline with decision components (like the `ConditionalRouter`) as well.
### Agentic Pipelines
Haystack loops and branches enable the creation of complex applications like agents. Here are a few examples on how to create them:
- [Tutorial: Building a Chat Agent with Function Calling](https://haystack.deepset.ai/tutorials/40_building_chat_application_with_function_calling)
- [Tutorial: Building an Agentic RAG with Fallback to Websearch](https://haystack.deepset.ai/tutorials/36_building_fallbacks_with_conditional_routing)
- [Tutorial: Generating Structured Output with Loop-Based Auto-Correction](https://haystack.deepset.ai/tutorials/28_structured_output_with_loop)
- [Cookbook: Define & Run Tools](https://haystack.deepset.ai/cookbook/tools_support)
- [Cookbook: Conversational RAG using Memory](https://haystack.deepset.ai/cookbook/conversational_rag_using_memory)
- [Cookbook: Newsletter Sending Agent with Experimental Haystack Tools](https://haystack.deepset.ai/cookbook/newsletter-agent)
### Branching
A pipeline can have multiple branches that process data concurrently. For example, to process different file types, you can have a pipeline with a bunch of converters, each handling a specific file type. You then feed all your files to the pipeline and it smartly divides and routes them to appropriate converters all at once, saving you the effort of sending your files one by one for processing.
### Loops
Components in a pipeline can work in iterative loops, which you can cap at a desired number. This can be handy for scenarios like self-correcting loops, where you have a generator producing some output and then a validator component to check if the output is correct. If the generator's output has errors, the validator component can loop back to the generator for a corrected output. The loop goes on until the output passes the validation and can be sent further down the pipeline.
See [Pipeline Loops](pipelines/pipeline-loops.mdx) for a deeper explanation of how loops are executed, how they terminate, and how to use them safely.
### Async Execution and Streaming
When run asynchronously, pipelines execute components in parallel when their dependencies allow it. This improves performance in complex pipelines with independent operations. For example, a pipeline can run multiple Retrievers or LLM calls simultaneously, execute independent pipeline branches in parallel, and efficiently handle I/O-bound operations that would otherwise cause delays. You can cap the number of components running at the same time with the `concurrency_limit` argument of the async run methods (`run_async`, `run_async_generator`, and `stream`). The synchronous `run` method executes components sequentially.
Besides the blocking `run` method, every pipeline offers three ways to run asynchronously:
- `run_async`: Executes the pipeline in a single non-blocking call, ideal for integrating a pipeline into a larger async application or service.
- `run_async_generator`: Yields partial outputs as components complete their tasks, which is useful for monitoring progress, debugging, and handling outputs incrementally.
- `stream`: Runs the pipeline and returns a handle that streams [`StreamingChunk`](/reference/data-classes-api#streamingchunk) objects as they are produced — a convenient way to stream LLM output from an async application, such as an API endpoint. Iterate the handle with `async for` to consume the chunks; after iteration ends, `handle.result` holds the final pipeline output (the same dictionary returned by `run_async`).
```python
import asyncio
from haystack import Pipeline
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
pipe = Pipeline()
pipe.add_component(
"prompt_builder",
ChatPromptBuilder(template=[ChatMessage.from_user("Tell me about {{topic}}")]),
)
pipe.add_component("llm", OpenAIChatGenerator())
pipe.connect("prompt_builder.prompt", "llm.messages")
async def main():
handle = pipe.stream(data={"prompt_builder": {"topic": "Italy"}})
async for chunk in handle:
print(chunk.content, end="", flush=True)
return handle.result
result = asyncio.run(main())
```
By default, chunks from every streaming-capable component are forwarded; pass `streaming_components` with a list of component names to stream only specific components. If the consumer abandons iteration, the underlying pipeline run is cancelled automatically; pass `cancel_on_abandon=False` to let it run to completion instead.
If a `streaming_callback` is set on a component (at init or at runtime through `data`), it is still invoked for each chunk in addition to the chunks being pushed to the handle. When streaming, components accept a sync `streaming_callback` in `run_async` too — see the [Choosing the Right Generator guide](../pipeline-components/generators/guides-to-generators/choosing-the-right-generator.mdx#sync-and-async-callbacks) for details.
#### Error Handling and Task Cancellation
If a component raises an error while sibling components are still running concurrently, the pipeline cancels and drains those in-flight tasks before re-raising the original error, so no tasks keep running in the background. The same cleanup applies when you stop iterating `run_async_generator` early (for example, by breaking out of the loop and closing the generator) or when the run itself is cancelled.
Note that cancellation only interrupts components that run natively async. Sync components are offloaded to a worker thread, which cannot be interrupted and runs to completion in the background. Their outputs are discarded, so the pipeline state stays consistent, but the component's side effects still complete.
## SuperComponents
To simplify your code, we have introduced [SuperComponents](components/supercomponents.mdx) that allow you to wrap complete pipelines and reuse them as a single component. Check out their documentation page for the details and examples.
## Data Flow
While the data (the initial query) flows through the entire pipeline, individual values are only passed from one component to another when they are connected. Therefore, not all components have access to all the data. This approach offers the benefits of speed and ease of debugging.
To connect components and integrations in a pipeline, you must know the names of their inputs and outputs. The output of one component must be accepted as input by the following component. When you connect components in a pipeline with `Pipeline.connect()`, it validates if the input and output types match.
### Smart Pipeline Connections
Pipelines support smarter connection semantics that simplify how components are wired together.
Compatible outputs can be implicitly combined when connected to a single input.
Pipelines also perform implicit type adaptation at connection time for some selected types.
These behaviors reduce the need for glue components like `Joiners` and `OutputAdapters`, keeping pipelines concise and easier to read.
See [Smart Pipeline Connections](pipelines/smart-pipeline-connections.mdx) for details and examples.
## Steps to Create a Pipeline Explained
Once all your components are created and ready to be combined in a pipeline, there are four steps to make it work:
1. Create the pipeline with `Pipeline()`.
This creates the Pipeline object.
2. Add components to the pipeline, one by one, with `.add_component(name, component)`.
This just adds components to the pipeline without connecting them yet. It's especially useful for loops as it allows the smooth connection of the components in the next step because they all already exist in the pipeline.
3. Connect components with `.connect("producer_component.output_name", "consumer_component.input_name")`.
At this step, you explicitly connect one of the outputs of a component to one of the inputs of the next component. This is also when the pipeline validates the connection without running the components. It makes the validation fast.
4. Run the pipeline with `.run({"component_1": {"mandatory_inputs": value}})`.
Finally, you run the Pipeline by specifying the first component in the pipeline and passing its mandatory inputs. Optionally, you can pass inputs to other components, for example: `.run({"component_1": {"mandatory_inputs": value}, "component_2": {"inputs": value}})`.
The full pipeline [example](pipelines/creating-pipelines.mdx#example) in [Creating Pipelines](pipelines/creating-pipelines.mdx) shows how all the elements come together to create a working RAG pipeline.
Once you create your pipeline, you can [visualize it in a graph](pipelines/visualizing-pipelines.mdx) to understand how the components are connected and make sure that's how you want them. You can use Mermaid graphs to do that.
## Validation
Validation happens when you connect pipeline components with `.connect()`, but before running the components to make it faster. The pipeline validates that:
- The components exist in the pipeline.
- The components' outputs and inputs match and are explicitly indicated. For example, if a component produces two outputs, when connecting it to another component, you must indicate which output connects to which input.
- The components' types match.
- For input types other than `Variadic`, checks if the input is already occupied by another connection.
All of these checks produce detailed errors to help you quickly fix any issues identified.
## Serialization
Thanks to serialization, you can save and then load your pipelines. Serialization is converting a Haystack pipeline into a format you can store on disk or send over the wire. It's particularly useful for:
- Editing, storing, and sharing pipelines.
- Modifying existing pipelines in a format different than Python.
Haystack pipelines delegate the serialization to its components, so serializing a pipeline simply means serializing each component in the pipeline one after the other, along with their connections. The pipeline is serialized into a dictionary format, which acts as an intermediate format that you can then convert into the final format you want.
:::info[Serialization formats]
Haystack only supports YAML format at this time. We'll be rolling out more formats gradually.
:::
For serialization to be possible, components must support conversion from and to Python dictionaries. All Haystack components have two methods that make them serializable: `from_dict` and `to_dict`. The `Pipeline` class, in turn, has its own `from_dict` and `to_dict` methods that take care of serializing components and connections.
---
// File: concepts/secret-management
# Secret Management
This page emphasizes secret management in Haystack components and introduces the `Secret` type for structured secret handling. It explains the drawbacks of hard-coding secrets in code and suggests using environment variables instead.
Many Haystack components interact with third-party frameworks and service providers such as Azure, Google Vertex AI, and OpenAI. Their libraries often require the user to authenticate themselves to ensure they receive access to the underlying product. The authentication process usually works with a secret value that acts as an opaque identifier to the third-party backend.
This page describes the two main types of secrets: token-based and environment variable-based, and how to handle them when using Haystack.
You can find additional details for the `Secret` class in our [API reference](/reference/utils-api).
Example Use Case - Problem Statement
### Problem Statement
Let’s consider an example RAG pipeline that embeds a query, uses a Retriever component to locate documents relevant to the query, and then leverages an LLM to generate an answer based on the retrieved documents.
The `OpenAIChatGenerator` component used in the pipeline below expects an API key to authenticate with OpenAI’s servers and perform the generation. Let’s assume that the component accepts a `str` value for it:
```python
generator = OpenAIChatGenerator(api_key="sk-xxxxxxxxxxxxxxxxxx")
pipeline.add_component("generator", generator)
```
This works in a pinch, but this is bad practice - we shouldn’t hard-code such secrets in the codebase. An alternative would be to store the key in an environment variable externally, read from it in Python, and pass that to the component:
```python
import os
api_key = os.environ.get("OPENAI_API_KEY")
generator = OpenAIChatGenerator(api_key=api_key)
pipeline.add_component("generator", generator)
```
This is better – the pipeline works as intended, and we aren’t hard-coding any secrets in the code.
Remember that pipelines are serializable. Since the API key is a secret, we should definitely avoid saving it to disk. Let’s modify the component’s `to_dict` method to exclude the key:
```python
def to_dict(self) -> Dict[str, Any]:
# Do not pass the `api_key` init parameter.
return default_to_dict(self, model=self.model)
```
But what happens when the pipeline is loaded from disk? In the best-case scenario, the component’s backend will automatically try to read the key from a hard-coded environment variable, and that key is the same as the one that was passed to the component before it was serialized. But in a worse case, the backend doesn’t look up the key in a hard-coded environment variable and fails when it gets called inside a `pipeline.run()` invocation.
### Import
To use Haystack secrets within the code, first import with:
```python
from haystack.utils import Secret
```
### Token-Based Secrets
You can paste tokens directly as a string using the `from_token` method:
```python
llm = OpenAIChatGenerator(api_key=Secret.from_token("sk-randomAPIkeyasdsa32ekasd32e"))
```
Note that this type of code cannot be serialized, meaning you can't convert the above component to a dictionary or save a pipeline containing it to a YAML file. This is a security feature to prevent accidental exposure of sensitive data.
### Environment Variable-Based Secrets
Environment variable-based secrets are more flexible. They allow you to specify one or more environment variables that may contain your secret.
Existing Haystack components that require an API Key (like OpenAIChatGenerator) have a default value for `Secret.from_env_var` (in this case, `OPENAI_API_KEY`). This means that the `OpenAIChatGenerator` will look for the value of the environment variable `OPENAI_API_KEY` (if it exists) and use it for authentication. And when pipelines are serialized to YAML, only the name of the environment variable is save to the YAML file. In doing so, this method ensures that there are no security leaks and is therefore strongly recommended.
```bash
## First, export an environment variable name `OPENAI_API_KEY` with its value
export OPENAI_API_KEY=sk-randomAPIkeyasdsa32ekasd32e
## or alternatively, using Python
## import os
## os.environ[”OPENAI_API_KEY”]=sk-randomAPIkeyasdsa32ekasd32e
```
```python
llm_generator = (
OpenAIChatGenerator()
) # Uses the default value from the env var for the component
```
Alternatively, in components where a Secret is expected, you can customize the name of the environment variable from which the API Key is to be read.
```python
# Export an environment variable with custom name and its value
llm_generator = OpenAIChatGenerator(api_key=Secret.from_env_var("YOUR_ENV_VAR"))
```
When `OpenAIChatGenerator` is serialized within a pipeline, this is what the YAML code will look like, using the custom variable name:
```yaml
components:
llm:
init_parameters:
api_base_url: null
api_key:
env_vars:
- YOUR_ENV_VAR
strict: true
type: env_var
generation_kwargs: {}
http_client_kwargs: null
max_retries: null
model: gpt-5-mini
organization: null
streaming_callback: null
timeout: null
tools: null
tools_strict: false
type: haystack.components.generators.chat.openai.OpenAIChatGenerator
...
```
### Serialization
While token-based secrets cannot be serialized, environment variable-based secrets can be converted to and from dictionaries:
```python
# Convert to dictionary
env_secret_dict = env_secret.to_dict()
# Create from dictionary
new_env_secret = Secret.from_dict(env_secret_dict)
```
### Resolving Secrets
Both types of secrets can be resolved to their actual values using the `resolve_value` method. This method returns the token or the value of the environment variable.
```python
# Resolve the token-based secret
token_value = api_key_secret.resolve_value()
# Resolve the environment variable-based secret
env_value = env_secret.resolve_value()
```
### Custom Component Example
Here is a complete example that shows how to create a component that uses the `Secret` class in Haystack, highlighting the differences between token-based and environment variable-based authentication, and showing that token-based secrets cannot be serialized:
```python
from haystack.utils import Secret, deserialize_secrets_inplace
@component
class MyComponent:
def __init__(self, api_key: Optional[Secret] = None, **kwargs):
self.api_key = api_key
self.backend = None
def warm_up(self):
# Call resolve_value to yield a single result. The semantics of the result is policy-dependent.
# Currently, all supported policies will return a single string token.
self.backend = SomeBackend(
api_key=self.api_key.resolve_value() if self.api_key else None, # ...
)
def to_dict(self):
# Serialize the policy like any other (custom) data. If the policy is token-based, it will
# raise an error.
return default_to_dict(
self,
api_key=self.api_key.to_dict() if self.api_key else None, # ...
)
@classmethod
def from_dict(cls, data):
# Deserialize the policy data before passing it to the generic from_dict function.
api_key_data = data["init_parameters"]["api_key"]
api_key = Secret.from_dict(api_key_data) if api_key_data is not None else None
data["init_parameters"]["api_key"] = api_key
# Alternatively, use the helper function.
# deserialize_secrets_inplace(data["init_parameters"], keys=["api_key"])
return default_from_dict(cls, data)
# No authentication.
component = MyComponent(api_key=None)
# Token based authentication
component = MyComponent(api_key=Secret.from_token("sk-randomAPIkeyasdsa32ekasd32e"))
component.to_dict() # Error! Can't serialize authentication tokens
# Environment variable based authentication
component = MyComponent(api_key=Secret.from_env_var("OPENAI_API_KEY"))
component.to_dict() # This is fine
```
---
// File: development/deployment/docker
# Docker
Learn how to deploy your Haystack pipelines through Docker starting from the basic Docker container to a complex application using Hayhooks.
## Running Haystack in Docker
The most basic form of Haystack deployment happens through Docker containers. Becoming familiar with running and customizing Haystack Docker images is useful as they form the basis for more advanced deployment.
Haystack releases are officially distributed through the [`deepset/haystack`](https://hub.docker.com/r/deepset/haystack) Docker image. Haystack images come in different flavors depending on the specific components they ship and the Haystack version.
:::info
At the moment, the only flavor available for Haystack is `base`, which ships exactly what you would get by installing Haystack locally with `pip install haystack-ai`.
:::
You can pull a specific Haystack flavor using Docker tags: for example, to pull the image containing Haystack `3.0.0`, you can run the command:
```shell
docker pull deepset/haystack:base-v3.0.0
```
Although the `base` flavor is meant to be customized, it can also be used to quickly run Haystack scripts locally without the need to set up a Python environment and its dependencies. For example, this is how you would print Haystack’s version running a Docker container:
```shell
docker run -it --rm deepset/haystack:base-v3.0.0 python -c"from haystack.version import __version__; print(__version__)"
```
## Customizing the Haystack Docker Image
Chances are your application will be more complex than a simple script, and you’ll need to install additional dependencies inside the Docker image along with Haystack.
For example, you might want to run a simple indexing pipeline using [Chroma](../../document-stores/chromadocumentstore.mdx) as your Document Store using a Docker container. The `base` image only contains a basic install of Haystack, but you need to install the Chroma integration (`chroma-haystack`) package additionally. The best approach would be to create a custom Docker image shipping the extra dependency.
Assuming you have a `main.py` script in your current folder, the Dockerfile would look like this:
```shell
FROM deepset/haystack:base-v3.0.0
RUN pip install chroma-haystack
COPY ./main.py /usr/src/myapp/main.py
ENTRYPOINT ["python", "/usr/src/myapp/main.py"]
```
Then you can create your custom Haystack image with:
```shell
docker build . -t my-haystack-image
```
## Complex Application with Docker Compose
A Haystack application running in Docker can go pretty far: with an internet connection, the container can reach external services providing vector databases, inference endpoints, and observability features.
Still, you might want to orchestrate additional services for your Haystack container locally, for example, to reduce costs or increase performance. When your application runtime depends on more than one Docker container, [Docker Compose](https://docs.docker.com/compose/) is a great tool to keep everything together.
As an example, let’s say your application wraps two pipelines: one to _index_ documents into a Qdrant instance and the other to _query_ those documents at a later time. This setup would require two Docker containers: one to run the pipelines as REST APIs using [Hayhooks](../hayhooks.mdx) and a second to run a Qdrant instance. For more information on configuring Hayhooks using Docker Compose, see the [official Hayhooks documentation](https://deepset-ai.github.io/hayhooks/getting-started/quick-start-docker/).
For building the Hayhooks image, we can easily customize the base image of one of the latest versions of Hayhooks, adding required dependencies required by [`QdrantDocumentStore`](../../document-stores/qdrant-document-store.mdx). The Dockerfile would look like this:
```dockerfile Dockerfile
FROM deepset/hayhooks:v1.23.0
RUN pip install qdrant-haystack sentence-transformers-haystack
CMD ["hayhooks", "run", "--host", "0.0.0.0"]
```
We wouldn’t need to customize Qdrant, so their official Docker image would work perfectly. The `docker-compose.yml` file would then look like this:
```yaml
services:
qdrant:
image: qdrant/qdrant:latest
restart: always
container_name: qdrant
ports:
- 6333:6333
- 6334:6334
expose:
- 6333
- 6334
- 6335
configs:
- source: qdrant_config
target: /qdrant/config/production.yaml
volumes:
- ./qdrant_data:/qdrant_data
hayhooks:
build: . # Build from local Dockerfile
container_name: hayhooks
ports:
- "1416:1416"
volumes:
- ./pipelines:/pipelines
environment:
- HAYHOOKS_PIPELINES_DIR=/pipelines
- LOG=DEBUG
depends_on:
- qdrant
configs:
qdrant_config:
content: |
log_level: INFO
```
For a functional example of a Docker Compose deployment, check out the [“RAG indexing and querying with Elasticsearch”](https://github.com/deepset-ai/hayhooks/tree/main/examples/rag_indexing_query) example from GitHub.
---
// File: development/deployment/kubernetes
import ClickableImage from "@site/src/components/ClickableImage";
# Kubernetes
Learn how to deploy your Haystack pipelines through Kubernetes.
The best way to get Haystack running as a workload in a container orchestrator like Kubernetes is to create a service to expose one or more [Hayhooks](../hayhooks.mdx) instances.
## Create a Haystack Kubernetes Service using Hayhooks
As a first step, we recommend to create a local [KinD](https://github.com/kubernetes-sigs/kind) or [Minikube](https://github.com/kubernetes/minikube) Kubernetes cluster. You can manage your cluster from CLI, but tools like [k9s](https://k9scli.io/) or [Lens](https://k8slens.dev/) can ease the process.
When done, start with a very simple Kubernetes Service running a single Hayhooks Pod:
```yaml
kind: Pod
apiVersion: v1
metadata:
name: hayhooks
labels:
app: haystack
spec:
containers:
- image: deepset/hayhooks:v1.23.0
name: hayhooks
imagePullPolicy: IfNotPresent
resources:
limits:
memory: "512Mi"
cpu: "500m"
requests:
memory: "256Mi"
cpu: "250m"
---
kind: Service
apiVersion: v1
metadata:
name: haystack-service
spec:
selector:
app: haystack
type: ClusterIP
ports:
# Default port used by the Hayhooks Docker image
- port: 1416
```
After applying the above to an existing Kubernetes cluster, a `hayhooks` Pod will show up as a Service called `haystack-service`.
Note that the `Service` defined above is of type `ClusterIP`. That means it's exposed only _inside_ the Kubernetes cluster. To expose the Hayhooks API to the _outside_ world as well, you need a `NodePort` or `Ingress` resource. As an alternative, it's also possible to use [Port Forwarding](https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/) to access the `Service` locally.
To do that, add port `30080` to Host-To-Node Mapping of our KinD cluster. In other words, make sure that the cluster is created with a node configuration similar to the following:
```yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
# ...
extraPortMappings:
- containerPort: 30080
hostPort: 30080
protocol: TCP
```
Then, create a simple `NodePort` to test if Hayhooks Pod is running correctly:
```yaml
apiVersion: v1
kind: Service
metadata:
name: haystack-nodeport
spec:
selector:
app: haystack
type: NodePort
ports:
- port: 1416
targetPort: 1416
nodePort: 30080
name: http
```
After applying this, `hayhooks` Pod will be accessible on `localhost:30080`.
From here, you should be able to manage pipelines. Remember that it's possible to deploy multiple different pipelines on a single Hayhooks instance. Check the [Hayhooks overview](../hayhooks.mdx) or the [official Hayhooks documentation](https://deepset-ai.github.io/hayhooks/) for more details.
## Auto-Run Pipelines at Pod Start
Hayhooks can load Haystack pipelines at startup, making them readily available when the server starts. You can leverage this mechanism to have your pods immediately serve one or more pipelines when they start.
At startup, it will look for deployed pipelines on the path specified at `HAYHOOKS_PIPELINES_DIR`, then load them.
A [deployed pipeline](https://github.com/deepset-ai/hayhooks?tab=readme-ov-file#deploy-a-pipeline) is essentially a directory which must contain a `pipeline_wrapper.py` file and possibly other files. To preload an [example pipeline](https://github.com/deepset-ai/hayhooks/tree/main/examples/pipeline_wrappers/chat_with_website), you need to mount a local folder inside the cluster node, then make it available on Hayhooks Pod as well.
First, ensure that a local folder is mounted correctly on the KinD cluster node at `/data`:
```yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
# ...
extraMounts:
- hostPath: /path/to/local/pipelines/folder
containerPath: /data
```
Next, make `/data` available as a volume and mount it on Hayhooks Pod. To do that, update your previous Pod configuration to the following:
```yaml
kind: Pod
apiVersion: v1
metadata:
name: hayhooks
labels:
app: haystack
spec:
containers:
- image: deepset/hayhooks:v1.23.0
name: hayhooks
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "-c"]
args:
- |
pip install trafilatura && \
hayhooks run --host 0.0.0.0
volumeMounts:
- name: local-data
mountPath: /mnt/data
env:
- name: HAYHOOKS_PIPELINES_DIR
value: /mnt/data
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: openai-secret
key: api-key
resources:
limits:
memory: "512Mi"
cpu: "500m"
requests:
memory: "256Mi"
cpu: "250m"
volumes:
- name: local-data
hostPath:
path: /data
type: Directory
```
Note that:
- We changed the Hayhooks container `command` to install the `trafilatura` dependency before startup, since it's needed for our [chat_with_website](https://github.com/deepset-ai/hayhooks/tree/main/examples/pipeline_wrappers/chat_with_website) example pipeline. For a real production environment, we recommend creating a custom Hayhooks image as described [here](docker.mdx#customizing-the-haystack-docker-image).
- We make Hayhooks container read `OPENAI_API_KEY` from a Kubernetes Secret.
Before applying this new configuration, create the `openai-secret`:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: openai-secret
type: Opaque
data:
# Replace the placeholder below with the base64 encoded value of your API key
# Generate it using: echo -n $OPENAI_API_KEY | base64
api-key: YOUR_BASE64_ENCODED_API_KEY_HERE
```
After applying this, check your Hayhooks Pod logs, and you'll see that the `chat_with_website` pipelines have already been deployed.
## Roll Out Multiple Pods
Haystack pipelines are usually stateless, which is a perfect use case for distributing the requests to multiple pods running the same set of pipelines. Let's convert the single-Pod configuration to an actual Kubernetes `Deployment`:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: haystack-deployment
spec:
replicas: 3
selector:
matchLabels:
app: haystack
template:
metadata:
labels:
app: haystack
spec:
initContainers:
- name: install-dependencies
image: python:3.12-slim
workingDir: /mnt/data
command: ["/bin/bash", "-c"]
args:
- |
echo "Installing dependencies..."
pip install trafilatura
echo "Dependencies installed successfully!"
touch /mnt/data/init-complete
volumeMounts:
- name: local-data
mountPath: /mnt/data
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "250m"
containers:
- image: deepset/hayhooks:v1.23.0
name: hayhooks
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "-c"]
args:
- |
pip install trafilatura && \
hayhooks run --host 0.0.0.0
ports:
- containerPort: 1416
name: http
volumeMounts:
- name: local-data
mountPath: /mnt/data
env:
- name: HAYHOOKS_PIPELINES_DIR
value: /mnt/data
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: openai-secret
key: api-key
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: local-data
hostPath:
path: /data
type: Directory
```
Implementing the above configuration will create three pods. Each pod will run a different instance of Hayhooks, all serving the same example pipeline provided by the mounted volume in the previous example.
Note that the `NodePort` you created before will now act as a load balancer and will distribute incoming requests to the three Hayhooks Pods.
---
// File: development/deployment/openshift
# OpenShift
Learn how to deploy your applications running Haystack pipelines using OpenShift.
## Introduction
OpenShift by Red Hat is a platform that helps create and manage applications built on top of Kubernetes. It can be used to build, update, launch, and oversee applications running Haystack pipelines. A [developer sandbox](https://developers.redhat.com/developer-sandbox) is available, ideal for getting familiar with the platform and building prototypes that can be smoothly moved to production using a public cloud, private network, hybrid cloud, or edge computing.
## Prerequisites
The fastest way to deploy a Haystack pipeline is to deploy an OpenShift application that runs Hayhooks. Before starting, make sure to have the following prerequisites:
- Access to an OpenShift project. Follow RedHat's [instructions](https://developers.redhat.com/developer-sandbox) to create one and start experimenting immediately.
- Hayhooks is installed. Run `pip install hayhooks` and make sure it works by running `hayhooks --version`. Read more about Hayhooks in our [overview](../hayhooks.mdx) or the [official Hayhooks documentation](https://deepset-ai.github.io/hayhooks/).
- You can optionally install the OpenShift command-line utility `oc`. Follow the [installation instructions](https://docs.openshift.com/container-platform/4.15/cli_reference/openshift_cli/getting-started-cli.html) for your platform and make sure it works by running `oc -h`.
## Creating a Hayhooks Application
In this guide, we’ll be using the `oc` command line, but you can achieve the same by interacting with the user interface offered by the OpenShift console.
1. The first step is to log into your OpenShift account using `oc`. From the top-right corner of your OpenShift console, click on your username and open the menu. Click **Copy login command** and follow the instructions.
2. The console will show you the exact command to run in your terminal to log in. It’s something like the following:
```
oc login --token= --server=https://:6443
```
3. Assuming you already have a project (it’s the case for the developer sandbox), create an application running the Hayhooks Docker image available on Docker Hub:
Note how you can pass environment variables that your application will use at runtime. In this case, we disable Haystack’s internal telemetry and set an OpenAI key that will be used by the pipelines we’ll eventually deploy in Hayhooks.
```
oc new-app deepset/hayhooks:v1.23.0 -e HAYSTACK_TELEMETRY_ENABLED=false -e OPENAI_API_KEY=$OPENAI_API_KEY
```
4. To make sure you make the most out of OpenShift's ability to manage the lifecycle of the application, you can set a [liveness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/):
```
oc set probe deployment/hayhooks --liveness --get-url=http://:1416/status
```
5. Finally, you can expose our Hayhooks instance to the public Internet:
```
oc expose service/hayhooks
```
6. You can get the public address that was assigned to your application by running:
```
oc status
```
In the output, look for something like this:
```
In project on server https://:6443
http://hayhooks-XXX.openshiftapps.com to pod port 1416-tcp (svc/hayhooks)
```
7. `http://hayhooks-XXX.openshiftapps.com` will be the public URL serving your Hayhooks instance. At this point, you can query Hayhooks status by running:
```
HAYHOOKS_HOST=hayhooks-XXX.openshiftapps.com HAYHOOKS_PORT=80 hayhooks status
```
8. Lastly, deploy your pipeline as usual:
```
HAYHOOKS_HOST=hayhooks-XXX.openshiftapps.com HAYHOOKS_PORT=80 hayhooks pipeline deploy-files -n my_pipeline /path/to/my_pipeline_dir
```
---
// File: development/deployment
# Deployment
Deploy your Haystack pipelines through various services such as Docker, Kubernetes, Ray, or a variety of Serverless options.
As a framework, Haystack is typically integrated into a variety of applications and environments, and there is no single, specific deployment strategy to follow. However, it is very common to make Haystack pipelines accessible through a service that can be easily called from other software systems.
These guides focus on tools and techniques that can be used to run Haystack pipelines in common scenarios. While these suggestions should not be considered the only way to do so, they should provide inspiration and the ability to customize them according to your needs.
### Guides
Here are the currently available guides on Haystack pipeline deployment:
- [Deploying with Docker](deployment/docker.mdx)
- [Deploying with Kubernetes](deployment/kubernetes.mdx)
- [Deploying with OpenShift](deployment/openshift.mdx)
### Hayhooks
Haystack can be easily integrated into any HTTP application, but if you don’t have one, you can use Hayhooks, a ready-made application that serves Haystack pipelines as REST endpoints. We’ll be using Hayhooks throughout this guide to streamline the code examples. Refer to the Hayhooks [overview](hayhooks.mdx) for a quick start, or the [official Hayhooks documentation](https://deepset-ai.github.io/hayhooks/) for comprehensive guides and reference.
:::note[Looking to scale with confidence?]
If your team needs **enterprise-grade support, best practices, and deployment guidance** to run Haystack in production, check out **Haystack Enterprise Starter**.
📜 [Learn more about Haystack Enterprise Starter](https://haystack.deepset.ai/blog/announcing-haystack-enterprise)
🤝 [Get in touch with our team](https://www.deepset.ai/products-and-services/haystack-enterprise-starter)
👉 For platform tooling to **manage data, pipelines, testing, and governance at scale**, explore the [Haystack Enterprise Platform](https://www.deepset.ai/products-and-services/haystack-enterprise-platform).
:::
---
// File: development/enabling-gpu-acceleration
import ClickableImage from "@site/src/components/ClickableImage";
# Enabling GPU Acceleration
Speed up your Haystack application by engaging the GPU.
The Transformer models used in Haystack are designed to be run on GPU-accelerated hardware. The steps for GPU acceleration setup depend on the environment that you're working in.
Once you have GPU enabled on your machine, you can set the `device` on which a given model for a component is loaded.
For example, to load a model for the `TransformersChatGenerator`, set `device=ComponentDevice.from_single(Device.gpu(id=0))` or `device = ComponentDevice.from_str("cuda:0")` when initializing.
You can find more information on the [Device management](../concepts/device-management.mdx) page.
### Enabling the GPU in Linux
1. Ensure that you have a fitting version of NVIDIA CUDA installed. To learn how to install CUDA, see the [NVIDIA CUDA Guide for Linux](https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html).
2. Run the `nvidia-smi`in the command line to check if the GPU is enabled. If the GPU is enabled, the output shows a list of available GPUs and their memory usage:
### Enabling the GPU in Colab
1. In your Colab environment, select **Runtime>Change Runtime type**.
2. Choose **Hardware accelerator>GPU**.
3. To check if the GPU is enabled, run:
```python python
%%bash
nvidia-smi
```
The output should show the GPUs available and their usage.
---
// File: development/external-integrations-development
# External Integrations
External integrations that enable tracing, monitoring, and deploying your pipelines.
| Name | Description |
| --- | --- |
| [Arize Phoenix](https://haystack.deepset.ai/integrations/arize-phoenix) | Trace your pipelines with Arize Phoenix. |
| [Arize AI](https://haystack.deepset.ai/integrations/arize) | Trace and monitor your pipelines with Arize AI. |
| [Burr](https://haystack.deepset.ai/integrations/burr) | Build Burr agents using Haystack. |
| [Context AI](https://haystack.deepset.ai/integrations/context-ai) | Log conversations for analytics by Context.ai. |
| [Ray](https://haystack.deepset.ai/integrations/ray) | Run and scale your pipelines with in distributed manner. |
---
// File: development/hayhooks
# Hayhooks
Hayhooks is a web application you can use to serve Haystack pipelines through HTTP endpoints. This page provides an overview of the main features of Hayhooks.
:::info[Hayhooks Documentation]
For comprehensive documentation, including detailed configuration reference, advanced features,
and examples, see the [official Hayhooks documentation](https://deepset-ai.github.io/hayhooks/).
The source code is available in the [Hayhooks GitHub repository](https://github.com/deepset-ai/hayhooks).
:::
## Overview
Hayhooks simplifies the deployment of Haystack pipelines as REST APIs. It allows you to:
- Expose Haystack pipelines as HTTP endpoints, including OpenAI-compatible chat endpoints,
- Customize logic while keeping minimal boilerplate,
- Deploy pipelines quickly and efficiently.
### Installation
Install Hayhooks using pip:
```shell
pip install hayhooks
```
The `hayhooks` package ships both the server and the client component, and the client is capable of starting the server. From a shell, start the server with:
```shell
$ hayhooks run
INFO: Started server process [44782]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://localhost:1416 (Press CTRL+C to quit)
```
### Check Status
From a different shell, you can query the status of the server with:
```shell
$ hayhooks status
Hayhooks server is up and running.
```
## Configuration
Hayhooks can be configured in three ways:
1. Using an `.env` file in the project root.
2. Passing environment variables when running the command.
3. Using command-line arguments with `hayhooks run`.
For a complete list of environment variables including server settings, CORS, SSL, logging, streaming, and Chainlit UI options, see the [Hayhooks environment variables reference](https://deepset-ai.github.io/hayhooks/reference/environment-variables/).
## Running Hayhooks
To start the server:
```shell
hayhooks run
```
This will launch Hayhooks at `HAYHOOKS_HOST:HAYHOOKS_PORT`.
## Deploying a Pipeline
### Steps
1. Prepare a pipeline definition (`.yml` file) and a `pipeline_wrapper.py` file.
2. Deploy the pipeline:
```shell
hayhooks pipeline deploy-files -n my_pipeline my_pipeline_dir
```
3. Access the pipeline at `{pipeline_name}/run` endpoint.
### Pipeline Wrapper
A `PipelineWrapper` class is required to wrap the pipeline:
```python
from pathlib import Path
from haystack import Pipeline
from hayhooks import BasePipelineWrapper
class PipelineWrapper(BasePipelineWrapper):
def setup(self) -> None:
pipeline_yaml = (Path(__file__).parent / "pipeline.yml").read_text()
self.pipeline = Pipeline.loads(pipeline_yaml)
def run_api(self, input_text: str) -> str:
result = self.pipeline.run({"input": {"text": input_text}})
return result["output"]["text"]
```
## File Uploads
Hayhooks enables handling file uploads in your pipeline wrapper's `run_api` method by including `files: list[UploadFile] | None = None` as an argument.
```python
def run_api(self, files: list[UploadFile] | None = None) -> str:
if files and len(files) > 0:
filenames = [f.filename for f in files if f.filename is not None]
file_contents = [f.file.read() for f in files]
return f"Received files: {', '.join(filenames)}"
return "No files received"
```
Hayhooks automatically processes uploaded files and passes them to the `run_api` method when present. The HTTP request must be a `multipart/form-data` request. For more details on file uploads, including combining files with parameters, see the [official Hayhooks documentation](https://deepset-ai.github.io/hayhooks/features/file-upload-support/).
## Running Pipelines from the CLI
You can execute a pipeline through the command line using the `hayhooks pipeline run` command. Internally, this triggers the `run_api` method of the pipeline wrapper, passing parameters as a JSON payload.
```shell
hayhooks pipeline run --param 'question="Is this recipe vegan?"'
```
You can also upload files when running a pipeline:
```shell
hayhooks pipeline run --file file.pdf --param 'question="Is this recipe vegan?"'
```
For the full CLI reference, see the [Hayhooks CLI documentation](https://deepset-ai.github.io/hayhooks/features/cli-commands/).
## MCP Support
Hayhooks supports the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) and can act as an MCP Server. It automatically lists your deployed pipelines and agents as MCP Tools, over both Streamable HTTP (recommended) and Server-Sent Events (SSE, kept for backward compatibility). Agents are deployed using the same `PipelineWrapper` mechanism as pipelines.
MCP support is an optional extra. Install it and start the Hayhooks MCP server with:
```shell
pip install "hayhooks[mcp]"
hayhooks mcp run
```
For each deployed pipeline, Hayhooks uses the pipeline wrapper name as the MCP Tool name and generates the tool schema from the `run_api` method arguments. For details on configuring MCP tools, see the [Hayhooks MCP documentation](https://deepset-ai.github.io/hayhooks/features/mcp-support/).
## OpenAI Compatibility
Hayhooks supports OpenAI-compatible endpoints through the `run_chat_completion` method.
```python
from hayhooks import BasePipelineWrapper, get_last_user_message
class PipelineWrapper(BasePipelineWrapper):
def run_chat_completion(self, model: str, messages: list, body: dict):
question = get_last_user_message(messages)
return self.pipeline.run({"query": question})
```
This makes Hayhooks pipelines compatible with any tool that supports the OpenAI chat completion API, including streaming responses. For details, see the [Hayhooks OpenAI compatibility documentation](https://deepset-ai.github.io/hayhooks/features/openai-compatibility/).
## Running Programmatically
Hayhooks can be embedded in a FastAPI application:
```python
import uvicorn
from hayhooks.settings import settings
from fastapi import Request
from hayhooks import create_app
# Create the Hayhooks app
hayhooks = create_app()
# Add a custom route
@hayhooks.get("/custom")
async def custom_route():
return {"message": "Hi, this is a custom route!"}
# Add a custom middleware
@hayhooks.middleware("http")
async def custom_middleware(request: Request, call_next):
response = await call_next(request)
response.headers["X-Custom-Header"] = "custom-header-value"
return response
if __name__ == "__main__":
uvicorn.run("app:hayhooks", host=settings.host, port=settings.port)
```
---
// File: development/logging
import ClickableImage from "@site/src/components/ClickableImage";
# Logging
Logging is crucial for monitoring and debugging LLM applications during development as well as in production. Haystack provides different logging solutions out of the box to get you started quickly, depending on your use case.
## Standard Library Logging (default)
Haystack logs through Python’s standard library. This gives you full flexibility and customizability to adjust the log format according to your needs.
### Changing the Log Level
By default, Haystack's logging level is set to `WARNING`. To display more information, you can change it to `INFO`. This way, not only warnings but also information messages are displayed in the console output.
To change the logging level to `INFO`, run:
```python
import logging
logging.basicConfig(
format="%(levelname)s - %(name)s - %(message)s",
level=logging.WARNING,
)
logging.getLogger("haystack").setLevel(logging.INFO)
```
#### Further Configuration
See [Python’s documentation on logging](https://docs.python.org/3/howto/logging.html) for more advanced configuration.
## Real-Time Pipeline Logging
Use Haystack's [`LoggingTracer`](https://github.com/deepset-ai/haystack/blob/main/haystack/tracing/logging_tracer.py) logs to inspect the data that's flowing through your pipeline in real-time.
This feature is particularly helpful during experimentation and prototyping, as you don’t need to set up any tracing backend beforehand.
Here’s how you can enable this tracer. In this example, we are adding color tags (this is optional) to highlight the components' names and inputs:
```python
import logging
from haystack import tracing
from haystack.tracing.logging_tracer import LoggingTracer
logging.basicConfig(
format="%(levelname)s - %(name)s - %(message)s",
level=logging.WARNING,
)
logging.getLogger("haystack").setLevel(logging.DEBUG)
tracing.tracer.is_content_tracing_enabled = (
True # to enable tracing/logging content (inputs/outputs)
)
tracing.enable_tracing(
LoggingTracer(
tags_color_strings={
"haystack.component.input": "\x1b[1;31m",
"haystack.component.name": "\x1b[1;34m",
},
),
)
```
Here’s what the resulting log would look like when a pipeline is run:
## Structured Logging
Haystack leverages the [structlog library](https://www.structlog.org/en/stable/) to provide structured key-value logs. This provides additional metadata with each log message and is especially useful if you archive your logs with tools like [ELK](https://www.elastic.co/de/elastic-stack), [Grafana](https://grafana.com/oss/agent/?plcmt=footer), or [Datadog](https://www.datadoghq.com/).
If Haystack detects a [structlog installation](https://www.structlog.org/en/stable/) on your system, it installs a structlog-based formatting handler on import - but only for Haystack's own logger namespaces (`haystack`, `haystack_integrations`, and `haystack_experimental`). The root logger and the process-global structlog configuration are left untouched, so importing Haystack does not change how your application or other libraries log.
### Scoping and Duplicate Log Lines
You can adjust this behavior with an explicit `configure_logging` call:
- `configure_logging(logger_name="")` attaches the formatting handler to the root logger instead, restoring the legacy behavior of formatting every log record in the process.
- `configure_logging(propagate=False)` stops Haystack's log records from propagating to ancestor loggers. Use this to avoid duplicate log lines when your application also configures a handler on the root logger.
```python
from haystack.logging import configure_logging
# Format all log records in the process (legacy behavior)
configure_logging(logger_name="")
# Avoid duplicate log lines when the host app configures the root logger
configure_logging(propagate=False)
```
### Console Rendering
To make development a more pleasurable experience, Haystack uses [structlog’s `ConsoleRender`](https://www.structlog.org/en/stable/console-output.html) by default to render structured logs as a nicely aligned and colorful output:
:::tip[Rich Formatting]
Install [_rich_](https://rich.readthedocs.io/en/stable/index.html) to beautify your logs even more!
:::
### JSON Rendering
We recommend JSON logging when deploying Haystack to production. Haystack will automatically switch to JSON format if it detects no interactive terminal session. If you want to enforce JSON logging:
- Run Haystack with the environment variable `HAYSTACK_LOGGING_USE_JSON` set to `true`.
- Or, use Python to tell Haystack to log as JSON:
```python
import haystack.logging
haystack.logging.configure_logging(use_json=True)
```
### Disabling Structured Logging
To disable structured logging despite an existing installation of structlog, set the environment variable `HAYSTACK_LOGGING_IGNORE_STRUCTLOG` to `true` when running Haystack.
---
// File: development/tracing/custom-tracer
# Custom Tracer
Learn how to connect Haystack to a custom tracing backend by implementing the `Tracer` interface.
| | |
| --- | --- |
| **Base classes** | `Tracer` and `Span` |
| **How to enable** | Implement the `Tracer` interface, then `tracing.enable_tracing(your_tracer)` |
| **Content tracing** | Optional. Set `HAYSTACK_CONTENT_TRACING_ENABLED` to `true` to trace component inputs and outputs |
| **Package** | Built into Haystack |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/tracing/tracer.py |
## Overview
If your tracing backend isn't supported out of the box, you can connect it to Haystack by implementing the `Tracer` interface. This gives you full control over how spans are created and how tags are recorded.
## Usage
1. Implement the `Tracer` interface. The following code snippet provides an example using the OpenTelemetry package:
```python
import contextlib
from collections.abc import Iterator
from typing import Any
from opentelemetry import trace
from opentelemetry.trace import NonRecordingSpan
from haystack.tracing import Tracer, Span
from haystack.tracing import utils as tracing_utils
import opentelemetry.trace
class OpenTelemetrySpan(Span):
def __init__(self, span: opentelemetry.trace.Span) -> None:
self._span = span
def set_tag(self, key: str, value: Any) -> None:
# Tracing backends usually don't support any tag value
# `coerce_tag_value` forces the value to either be a Python
# primitive (int, float, boolean, str) or tries to dump it as string.
coerced_value = tracing_utils.coerce_tag_value(value)
self._span.set_attribute(key, coerced_value)
class OpenTelemetryTracer(Tracer):
def __init__(self, tracer: opentelemetry.trace.Tracer) -> None:
self._tracer = tracer
@contextlib.contextmanager
def trace(
self,
operation_name: str,
tags: dict[str, Any] | None = None,
parent_span: Span | None = None,
) -> Iterator[Span]:
with self._tracer.start_as_current_span(operation_name) as span:
span = OpenTelemetrySpan(span)
if tags:
span.set_tags(tags)
yield span
def current_span(self) -> Span | None:
current_span = trace.get_current_span()
if isinstance(current_span, NonRecordingSpan):
return None
return OpenTelemetrySpan(current_span)
```
2. Tell Haystack to use your custom tracer:
```python
from haystack import tracing
haystack_tracer = OpenTelemetryTracer(tracer)
tracing.enable_tracing(haystack_tracer)
```
---
// File: development/tracing/datadog
# Datadog
Learn how to trace your Haystack pipelines with Datadog.
| | |
| --- | --- |
| **Tracer class** | `DatadogTracer` |
| **How to enable** | Enable the tracer with `tracing.enable_tracing(DatadogTracer(ddtrace.tracer))`, or add the `DatadogConnector` component to your pipeline |
| **Content tracing** | Set `HAYSTACK_CONTENT_TRACING_ENABLED` to `true` to trace component inputs and outputs |
| **Package** | `datadog-haystack` |
| **API reference** | [datadog](/reference/integrations-datadog) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/datadog |
## Overview
Trace your Haystack pipelines with [Datadog](https://www.datadoghq.com/) through [Datadog's tracing library `ddtrace`](https://ddtrace.readthedocs.io/en/stable/). Haystack captures detailed information about pipeline runs, like API calls, context data, and prompts, so you can see the complete trace of your pipeline execution in Datadog.
## Installation
Install the `datadog-haystack` package:
```shell
pip install datadog-haystack
```
## Prerequisites
1. A way to receive traces, such as a running [Datadog Agent](https://docs.datadoghq.com/agent/). `ddtrace` sends traces to the Datadog Agent at `localhost:8126` by default.
2. Configure `ddtrace` through the standard mechanisms, for example the `DD_SERVICE`, `DD_ENV`, and `DD_VERSION` environment variables, or by running your application with the `ddtrace-run` command. See the [ddtrace documentation](https://ddtrace.readthedocs.io/en/stable/) for more details.
## Usage
Enable the `DatadogTracer` directly to trace any Haystack pipeline, without adding a component to it. Make sure to set the `HAYSTACK_CONTENT_TRACING_ENABLED` environment variable before importing any Haystack components.
```python
import os
os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
import ddtrace
from haystack import Pipeline, tracing
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack_integrations.tracing.datadog import DatadogTracer
# Enable the Datadog tracer
tracing.enable_tracing(DatadogTracer(ddtrace.tracer))
pipe = Pipeline()
pipe.add_component("prompt_builder", ChatPromptBuilder())
pipe.add_component("llm", OpenAIChatGenerator())
pipe.connect("prompt_builder.prompt", "llm.messages")
messages = [
ChatMessage.from_system(
"Always respond in German even if some input data is in other languages.",
),
ChatMessage.from_user("Tell me about {{location}}"),
]
response = pipe.run(
data={
"prompt_builder": {
"template_variables": {"location": "Berlin"},
"template": messages,
},
},
)
print(response["llm"]["replies"][0])
```
Each pipeline run produces a trace that includes the entire execution context, including prompts, completions, and metadata. You can then view the traces in your Datadog dashboard.
## Alternative: the DatadogConnector component
If you prefer to manage tracing as part of your pipeline definition (for example, so it serializes to YAML), you can add the `DatadogConnector` component instead. It enables the same Datadog tracing as soon as it is initialized.
:::info
See the [`DatadogConnector` documentation page](../../pipeline-components/connectors/datadogconnector.mdx) for full usage examples, or check out the [integration page](https://haystack.deepset.ai/integrations/datadog).
:::
---
// File: development/tracing/langfuse
import ClickableImage from "@site/src/components/ClickableImage";
# Langfuse
Learn how to trace your Haystack pipelines with Langfuse.
| | |
| --- | --- |
| **Tracer class** | `LangfuseTracer` |
| **How to enable** | Enable the tracer with `tracing.enable_tracing(LangfuseTracer(langfuse))`, or add the `LangfuseConnector` component to your pipeline |
| **Content tracing** | Required. Set `HAYSTACK_CONTENT_TRACING_ENABLED` to `true` |
| **Package** | `langfuse-haystack` |
| **API reference** | [langfuse](/reference/integrations-langfuse) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/langfuse |
## Overview
Trace your Haystack pipelines with the [Langfuse](https://langfuse.com/) UI. Langfuse captures detailed information about pipeline runs, like API calls, context data, prompts, and more. Use it to monitor model performance such as token usage and cost, find areas for improvement, and create datasets from your pipeline executions.
## Installation
Install the `langfuse-haystack` package:
```shell
pip install langfuse-haystack
```
## Prerequisites
1. An active Langfuse [account](https://cloud.langfuse.com/).
2. Set the `LANGFUSE_SECRET_KEY` and `LANGFUSE_PUBLIC_KEY` environment variables with your Langfuse secret and public keys, found in your account profile.
3. Set the `HAYSTACK_CONTENT_TRACING_ENABLED` environment variable to `true` to enable tracing.
:::info[Usage Notice]
To ensure proper tracing, always set environment variables before importing any Haystack components. This is crucial because Haystack initializes its internal tracing components during import. An even better practice is to set these environment variables in your shell before running the script.
:::
## Usage
Enable the `LangfuseTracer` directly to trace any Haystack pipeline, without adding a component to it.
```python
import os
os.environ["LANGFUSE_HOST"] = "https://cloud.langfuse.com"
os.environ["LANGFUSE_SECRET_KEY"] = ""
os.environ["LANGFUSE_PUBLIC_KEY"] = ""
os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
from langfuse import Langfuse
from haystack import Pipeline, tracing
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack_integrations.tracing.langfuse import LangfuseTracer
# Enable the Langfuse tracer. The client reads your keys from the environment.
langfuse = Langfuse()
langfuse_tracer = LangfuseTracer(langfuse, name="Chat example")
tracing.enable_tracing(langfuse_tracer)
pipe = Pipeline()
pipe.add_component("prompt_builder", ChatPromptBuilder())
pipe.add_component("llm", OpenAIChatGenerator())
pipe.connect("prompt_builder.prompt", "llm.messages")
messages = [
ChatMessage.from_system(
"Always respond in German even if some input data is in other languages.",
),
ChatMessage.from_user("Tell me about {{location}}"),
]
response = pipe.run(
data={
"prompt_builder": {
"template_variables": {"location": "Berlin"},
"template": messages,
},
},
)
print(response["llm"]["replies"][0])
# Flush any pending spans before the program exits
langfuse_tracer.flush()
```
Each pipeline run produces one trace that includes the entire execution context, including prompts, completions, and metadata. You can then view the trace in the Langfuse UI.
## Alternative: the LangfuseConnector component
If you prefer to manage tracing as part of your pipeline definition, you can add the `LangfuseConnector` component instead. It enables the same Langfuse tracing, exposes the `trace_url` as an output, and supports a custom `SpanHandler` for advanced span processing.
:::info
See the [`LangfuseConnector` documentation page](../../pipeline-components/connectors/langfuseconnector.mdx) for full usage examples and advanced span customization, or read the [blog post](https://haystack.deepset.ai/blog/langfuse-integration) for a complete walkthrough.
:::
---
// File: development/tracing/logging-tracer
import ClickableImage from "@site/src/components/ClickableImage";
# LoggingTracer
Learn how to inspect the data flowing through your Haystack pipelines in real time with the `LoggingTracer`.
| | |
| --- | --- |
| **Tracer class** | `LoggingTracer` |
| **How to enable** | `tracing.enable_tracing(LoggingTracer(...))` |
| **Content tracing** | Required to log inputs and outputs. Set `tracing.tracer.is_content_tracing_enabled = True` |
| **Package** | Built into Haystack |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/tracing/logging_tracer.py |
## Overview
Use Haystack's [`LoggingTracer`](https://github.com/deepset-ai/haystack/blob/main/haystack/tracing/logging_tracer.py) logs to inspect the data that's flowing through your pipeline in real time.
This feature is particularly helpful during experimentation and prototyping, as you don’t need to set up any tracing backend beforehand.
## Usage
Here’s how you can enable this tracer. In this example, we are adding color tags (this is optional) to highlight the components' names and inputs:
```python
import logging
from haystack import tracing
from haystack.tracing.logging_tracer import LoggingTracer
logging.basicConfig(
format="%(levelname)s - %(name)s - %(message)s",
level=logging.WARNING,
)
logging.getLogger("haystack").setLevel(logging.DEBUG)
tracing.tracer.is_content_tracing_enabled = (
True # to enable tracing/logging content (inputs/outputs)
)
tracing.enable_tracing(
LoggingTracer(
tags_color_strings={
"haystack.component.input": "\x1b[1;31m",
"haystack.component.name": "\x1b[1;34m",
},
),
)
```
Here’s what the resulting log would look like when a pipeline is run:
---
// File: development/tracing/mlflow
# MLflow
Learn how to trace your Haystack pipelines with MLflow.
## Overview
[MLflow](https://mlflow.org/) is an open-source platform for managing the end-to-end machine learning and AI lifecycle. MLflow provides native tracing support for Haystack, so you can capture traces from all your pipelines and components with a single line of code.
## Installation
Install MLflow:
```shell
pip install mlflow
```
## Usage
Enable automatic tracing for all Haystack pipelines and components:
```python
import mlflow
mlflow.haystack.autolog()
# Optionally set an experiment name
mlflow.set_experiment("Haystack")
```
This automatically captures traces from all Haystack pipelines and components, including latencies, token usage, cost, and any exceptions.
:::info
Check out the [MLflow Haystack integration guide](https://haystack.deepset.ai/integrations/mlflow) for a full walkthrough with examples.
:::
---
// File: development/tracing/opentelemetry
import ClickableImage from "@site/src/components/ClickableImage";
# OpenTelemetry
Learn how to trace your Haystack pipelines with OpenTelemetry.
| | |
| --- | --- |
| **Tracer class** | `OpenTelemetryTracer` |
| **How to enable** | Configure an OpenTelemetry `TracerProvider`, then enable the tracer with `tracing.enable_tracing(OpenTelemetryTracer(trace.get_tracer("my_application")))`, or add the `OpenTelemetryConnector` component to your pipeline |
| **Content tracing** | Set `HAYSTACK_CONTENT_TRACING_ENABLED` to `true` to trace component inputs and outputs |
| **Package** | `opentelemetry-haystack` |
| **API reference** | [opentelemetry](/reference/integrations-opentelemetry) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/opentelemetry |
## Overview
[OpenTelemetry](https://opentelemetry.io/) is an open-source observability framework for collecting traces, metrics, and logs. Haystack integrates with OpenTelemetry, so you can send traces of your pipeline runs to any OpenTelemetry-compatible backend.
:::info[Provided by an integration]
`OpenTelemetryTracer` lives in the `opentelemetry-haystack` package and is not part of Haystack core. Since Haystack 3.0, OpenTelemetry tracing is no longer auto-enabled when `opentelemetry-sdk` is installed. Install the integration and either enable the `OpenTelemetryTracer` directly or add the `OpenTelemetryConnector` component to your pipeline.
:::
## Installation
Install the `opentelemetry-haystack` package:
```shell
pip install opentelemetry-haystack
```
To add traces to even deeper levels of your pipelines, we recommend you check out [OpenTelemetry integrations](https://opentelemetry.io/ecosystem/registry/?s=python), such as:
- [`urllib3` instrumentation](https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-urllib3) for tracing HTTP requests in your pipeline,
- [OpenAI instrumentation](https://github.com/traceloop/openllmetry/tree/main/packages/opentelemetry-instrumentation-openai) for tracing OpenAI requests.
## Prerequisites
A configured OpenTelemetry `TracerProvider` with an exporter, for example an OTLP exporter that sends traces to a collector or a backend. Set up the provider before enabling the tracer.
## Usage
Enable the `OpenTelemetryTracer` directly to trace any Haystack pipeline, without adding a component to it. Configure your `TracerProvider` and set the `HAYSTACK_CONTENT_TRACING_ENABLED` environment variable before importing any Haystack components.
```python
import os
os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.semconv.resource import ResourceAttributes
# Configure the OpenTelemetry SDK. A service name is required for most backends.
resource = Resource(attributes={ResourceAttributes.SERVICE_NAME: "haystack"})
tracer_provider = TracerProvider(resource=resource)
tracer_provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint="http://localhost:4318/v1/traces")),
)
trace.set_tracer_provider(tracer_provider)
from haystack import tracing
from haystack_integrations.tracing.opentelemetry import OpenTelemetryTracer
# Enable the OpenTelemetry tracer
tracing.enable_tracing(OpenTelemetryTracer(trace.get_tracer("my_application")))
```
Each pipeline run then produces a trace that includes the entire execution context, including prompts, completions, and metadata. You can view the traces in your OpenTelemetry-compatible backend.
## Alternative: the OpenTelemetryConnector component
If you prefer to manage tracing as part of your pipeline definition, you can add the `OpenTelemetryConnector` component instead. It enables the same OpenTelemetry tracing as soon as it is initialized.
:::info
See the [`OpenTelemetryConnector` documentation page](../../pipeline-components/connectors/opentelemetryconnector.mdx) for full usage examples, or check out the [integration page](https://haystack.deepset.ai/integrations/opentelemetry).
:::
## Visualizing Traces During Development
Use [Jaeger](https://www.jaegertracing.io/docs/1.6/getting-started/) as a lightweight tracing backend for local pipeline development. This allows you to experiment with tracing without the need for a complex tracing backend.
1. Run the Jaeger container. This creates a tracing backend as well as a UI to visualize the traces:
```shell
docker run --rm -d --name jaeger \
-e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 4317:4317 \
-p 4318:4318 \
-p 14250:14250 \
-p 14268:14268 \
-p 14269:14269 \
-p 9411:9411 \
jaegertracing/all-in-one:latest
```
2. Install the integration and the OTLP exporter:
```shell
pip install opentelemetry-haystack
pip install opentelemetry-exporter-otlp
```
3. Configure `OpenTelemetry` to use the Jaeger backend and enable the tracer:
```python
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.semconv.resource import ResourceAttributes
from haystack import tracing
from haystack_integrations.tracing.opentelemetry import OpenTelemetryTracer
# Service name is required for most backends
resource = Resource(attributes={ResourceAttributes.SERVICE_NAME: "haystack"})
tracer_provider = TracerProvider(resource=resource)
processor = BatchSpanProcessor(
OTLPSpanExporter(endpoint="http://localhost:4318/v1/traces")
)
tracer_provider.add_span_processor(processor)
trace.set_tracer_provider(tracer_provider)
tracing.enable_tracing(OpenTelemetryTracer(trace.get_tracer("my_application")))
```
4. Run your pipeline:
```python
...
pipeline.run(...)
...
```
5. Inspect the traces in the UI provided by Jaeger at [http://localhost:16686](http://localhost:16686/search).
---
// File: development/tracing/weave
# Weights & Biases Weave
Learn how to trace your Haystack pipelines with Weights & Biases Weave.
| | |
| --- | --- |
| **Tracer class** | `WeaveTracer` |
| **How to enable** | Enable the tracer with `tracing.enable_tracing(WeaveTracer(project_name="..."))`, or add the `WeaveConnector` component to your pipeline |
| **Content tracing** | Required. Set `HAYSTACK_CONTENT_TRACING_ENABLED` to `true` |
| **Package** | `weave-haystack` |
| **API reference** | [Weave](/reference/integrations-weave) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/weave |
## Overview
Trace and visualize your pipeline execution in [Weights & Biases](https://wandb.ai/site/). Information captured by the Haystack tracing tool, such as API calls, context data, and prompts, is sent to Weights & Biases, where you can see the complete trace of your pipeline execution.
## Installation
Install the `weave-haystack` package:
```shell
pip install weave-haystack
```
## Prerequisites
1. A Weave account. You can sign up for free on the [Weights & Biases website](https://wandb.ai/site).
2. Set the `WANDB_API_KEY` environment variable with your Weights & Biases API key. Once logged in, you can find your API key on [your home page](https://wandb.ai/home).
3. Set the `HAYSTACK_CONTENT_TRACING_ENABLED` environment variable to `true`.
## Usage
Enable the `WeaveTracer` directly to trace any Haystack pipeline, without adding a component to it. The `project_name` is the name that will appear in your Weave project.
```python
import os
os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
from haystack import Pipeline, tracing
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack_integrations.tracing.weave import WeaveTracer
# Enable the Weave tracer
tracing.enable_tracing(WeaveTracer(project_name="test_pipeline"))
pipe = Pipeline()
pipe.add_component("prompt_builder", ChatPromptBuilder())
pipe.add_component("llm", OpenAIChatGenerator())
pipe.connect("prompt_builder.prompt", "llm.messages")
messages = [
ChatMessage.from_system(
"Always respond in German even if some input data is in other languages.",
),
ChatMessage.from_user("Tell me about {{location}}"),
]
response = pipe.run(
data={
"prompt_builder": {
"template_variables": {"location": "Berlin"},
"template": messages,
},
},
)
print(response["llm"]["replies"][0])
```
You can then see the complete trace for your pipeline at `https://wandb.ai//projects` under the project name you specified.
## Alternative: the WeaveConnector component
If you prefer to manage tracing as part of your pipeline definition, you can add the `WeaveConnector` component instead. It enables the same Weave tracing as soon as it runs.
:::info
See the [`WeaveConnector` documentation page](../../pipeline-components/connectors/weaveconnector.mdx) for full usage examples.
:::
---
// File: development/tracing
# Tracing
Traces document the flow of requests through your application and are vital for monitoring applications in production. This helps you understand the execution order of your pipeline components and analyze where your pipeline spends the most time.
Instrumented applications typically send traces to a trace collector or a tracing backend. Haystack provides out-of-the-box support for several backends, and you can also quickly implement support for additional providers of your choosing.
## Supported Tracers
| Tracer | Description |
| --- | --- |
| [OpenTelemetry](tracing/opentelemetry.mdx) | Send traces to any [OpenTelemetry](https://opentelemetry.io/)-compatible backend using the `OpenTelemetryTracer` or the `OpenTelemetryConnector` component. Includes a Jaeger setup for local development. |
| [MLflow](tracing/mlflow.mdx) | Capture traces with [MLflow](https://mlflow.org/)'s native Haystack tracing support. |
| [Datadog](tracing/datadog.mdx) | Trace your pipelines with [Datadog](https://www.datadoghq.com/) using the `DatadogTracer` or the `DatadogConnector` component. |
| [Langfuse](tracing/langfuse.mdx) | Trace your pipelines with the [Langfuse](https://langfuse.com/) UI using the `LangfuseTracer` or the `LangfuseConnector` component. |
| [Weights & Biases Weave](tracing/weave.mdx) | Trace and visualize pipeline execution in [Weights & Biases](https://wandb.ai/site/) using the `WeaveTracer` or the `WeaveConnector` component. |
| [LoggingTracer](tracing/logging-tracer.mdx) | Inspect the data flowing through your pipeline in real time through logs, with no backend setup. |
| [Custom Tracer](tracing/custom-tracer.mdx) | Connect any tracing backend by implementing the `Tracer` interface. |
## Enabling and Disabling Tracing
Haystack never enables tracing automatically. To enable it, either call `haystack.tracing.enable_tracing(...)` with the tracer of your choice, or add a tracing connector component such as the [`OpenTelemetryConnector`](tracing/opentelemetry.mdx) or the [`DatadogConnector`](tracing/datadog.mdx) to your pipeline.
To disable an enabled tracer:
```python
from haystack.tracing import disable_tracing
disable_tracing()
```
## Content Tracing
Haystack also allows you to trace your pipeline components' input and output values. This is useful for investigating your pipeline execution step by step.
By default, this behavior is disabled to prevent sensitive user information from being sent to your tracing backend.
To enable content tracing, there are two options:
- Set the environment variable `HAYSTACK_CONTENT_TRACING_ENABLED` to `true` when running your Haystack application
— or —
- Explicitly enable content tracing in Python:
```python
from haystack import tracing
tracing.tracer.is_content_tracing_enabled = True
```
---
// File: document-stores/alloydbdocumentstore
# AlloyDBDocumentStore
| | |
| --- | --- |
| API reference | [AlloyDB](/reference/integrations-alloydb) |
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/alloydb |
[AlloyDB](https://cloud.google.com/alloydb) is a fully managed, PostgreSQL-compatible database service on Google Cloud. The `AlloyDBDocumentStore` uses the [pgvector extension](https://cloud.google.com/alloydb/docs/ai/work-with-embeddings) to perform vector similarity search.
Connection is handled securely via the [AlloyDB Python Connector](https://github.com/GoogleCloudPlatform/alloydb-python-connector), which provides TLS encryption and IAM-based authorization without requiring manual SSL certificate management, firewall rules, or IP allowlisting.
The `AlloyDBDocumentStore` supports embedding retrieval, keyword retrieval, and metadata filtering.
## Installation
Install the `alloydb-haystack` integration:
```shell
pip install alloydb-haystack
```
To set up an AlloyDB cluster and instance, follow the [AlloyDB quickstart](https://cloud.google.com/alloydb/docs/quickstart).
## Usage
### Authentication
The `AlloyDBDocumentStore` uses [Secrets](../concepts/secret-management.mdx) and reads connection details from environment variables by default:
- `ALLOYDB_INSTANCE_URI`: the AlloyDB instance URI in the format `projects/PROJECT/locations/REGION/clusters/CLUSTER/instances/INSTANCE`.
- `ALLOYDB_USER`: the database user. When using IAM database authentication, use the service account email (omitting `.gserviceaccount.com`) or the full IAM user email.
- `ALLOYDB_PASSWORD`: the database password. Not required when `enable_iam_auth=True`.
```shell
export ALLOYDB_INSTANCE_URI="projects/MY_PROJECT/locations/MY_REGION/clusters/MY_CLUSTER/instances/MY_INSTANCE"
export ALLOYDB_USER="my-db-user"
export ALLOYDB_PASSWORD="my-db-password"
```
To authenticate with IAM instead of a password, set `enable_iam_auth=True` and grant the IAM principal the AlloyDB Client role. See the [AlloyDB IAM authentication documentation](https://cloud.google.com/alloydb/docs/manage-iam-authn) for details.
## Initialization
Initialize an `AlloyDBDocumentStore` and write Documents to it. Connection to AlloyDB is established lazily on first use, and the table that stores Haystack Documents is created automatically if it doesn't exist:
```python
from haystack import Document
from haystack_integrations.document_stores.alloydb import AlloyDBDocumentStore
document_store = AlloyDBDocumentStore(
db="my-database",
embedding_dimension=768,
vector_function="cosine_similarity",
recreate_table=True,
)
document_store.write_documents(
[
Document(content="This is first", embedding=[0.1] * 768),
Document(content="This is second", embedding=[0.3] * 768),
],
)
print(document_store.count_documents())
```
To learn more about the initialization parameters, see our [API docs](/reference/integrations-alloydb#alloydbdocumentstore).
To compute embeddings for your Documents, you can use a Document Embedder, such as the [`SentenceTransformersDocumentEmbedder`](../pipeline-components/embedders/sentencetransformersdocumentembedder.mdx).
### Search Strategy
The `AlloyDBDocumentStore` supports two search strategies for embedding retrieval:
- `"exact_nearest_neighbor"` (default): provides perfect recall but can be slow on large numbers of documents.
- `"hnsw"`: an approximate nearest neighbor search strategy that trades off some accuracy for speed. Recommended for large numbers of documents.
When using `"hnsw"`, an index is created based on the `vector_function` you choose, so subsequent queries should keep using the same vector similarity function in order to take advantage of the index. You can tune index creation through `hnsw_index_creation_kwargs` (see the [pgvector documentation](https://github.com/pgvector/pgvector?tab=readme-ov-file#hnsw)).
### Metadata Filtering
The `AlloyDBDocumentStore` fully supports comparison operators (`==`, `!=`, `>`, `>=`, `<`, `<=`, `in`, `not in`, `like`, `not like`) and the logical operators `AND` and `OR`. The `like` and `not like` operators are PostgreSQL-specific extensions to the standard Haystack filter syntax and map to the SQL `LIKE` / `NOT LIKE` pattern-matching operators.
The `NOT` logical operator is **not** supported. Because every comparison operator already has a negated counterpart (`==`/`!=`, `in`/`not in`, `like`/`not like`), any filter expressible with `NOT` around a single condition can be rewritten by inverting the comparison operator instead. To negate a nested `AND`/`OR` group, apply De Morgan's laws — for example, `NOT (A AND B)` becomes `(NOT A) OR (NOT B)`, where each `NOT A` / `NOT B` is expressed via the inverted comparison.
For more details on filter syntax, refer to [Metadata Filtering](../concepts/metadata-filtering.mdx).
### Supported Retrievers
- [`AlloyDBEmbeddingRetriever`](../pipeline-components/retrievers/alloydbembeddingretriever.mdx): An embedding-based Retriever that fetches Documents from the Document Store based on a query embedding.
- [`AlloyDBKeywordRetriever`](../pipeline-components/retrievers/alloydbkeywordretriever.mdx): A keyword-based Retriever that fetches Documents matching a query using PostgreSQL full-text search.
---
// File: document-stores/arangodocumentstore
# ArangoDocumentStore
Use the ArangoDB multi-model database with Haystack for embedding retrieval and GraphRAG workloads.
| | |
| --- | --- |
| API reference | [ArangoDB](/reference/integrations-arangodb) |
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/arangodb |
ArangoDB is a multi-model database that combines documents, graphs, and key-value data in a single engine. The `ArangoDocumentStore` stores documents in an ArangoDB collection and runs vector similarity search using AQL (ArangoDB Query Language) vector functions. Because documents and their relationships live in the same database, ArangoDB is a good fit for GraphRAG pipelines that combine semantic search with graph traversal.
Vector search requires **ArangoDB 3.12 or later** with the vector index feature enabled (the `--vector-index` startup flag).
For more information, see the [ArangoDB documentation](https://docs.arangodb.com/).
## Installation
Run ArangoDB with Docker, enabling the vector index and setting a root password:
```shell
docker run -d -p 8529:8529 \
-e ARANGO_ROOT_PASSWORD=test-password \
arangodb:3.12 arangod --vector-index
```
Install the Haystack integration:
```shell
pip install arangodb-haystack
```
## Usage
The store reads its credentials from the `ARANGO_USERNAME` and `ARANGO_PASSWORD` environment variables by default. `ARANGO_USERNAME` falls back to `root` if it is not set, so you typically only need to provide the password:
```shell
export ARANGO_PASSWORD=test-password
```
Initialize the document store and write documents:
```python
from haystack import Document
from haystack_integrations.document_stores.arangodb import ArangoDocumentStore
document_store = ArangoDocumentStore(
host="http://localhost:8529",
database="haystack",
collection_name="documents",
embedding_dimension=768,
recreate_collection=True,
)
document_store.write_documents(
[
Document(
content="There are over 7,000 languages spoken around the world today.",
),
Document(
content="Elephants have been observed to recognize themselves in mirrors.",
),
],
)
print(document_store.count_documents())
```
To learn more about the initialization parameters, see the [API docs](/reference/integrations-arangodb#arangodocumentstore).
To compute real embeddings for your documents, use a Document Embedder such as the [`SentenceTransformersDocumentEmbedder`](../pipeline-components/embedders/sentencetransformersdocumentembedder.mdx). The embedding dimension produced by the embedder must match the `embedding_dimension` configured on the store.
### Authentication
Credentials are passed as Haystack [`Secret`](../concepts/secret-management.mdx) objects. By default they are read from environment variables, but you can also pass them explicitly:
```python
from haystack.utils import Secret
from haystack_integrations.document_stores.arangodb import ArangoDocumentStore
document_store = ArangoDocumentStore(
host="http://localhost:8529",
database="haystack",
username=Secret.from_env_var("ARANGO_USERNAME", strict=False),
password=Secret.from_env_var("ARANGO_PASSWORD"),
)
```
### Similarity Functions
`ArangoDocumentStore` supports three similarity functions for vector search, configured at initialization with the `similarity_function` parameter:
- `"cosine"` (default): cosine similarity, best for normalized embeddings.
- `"dot_product"`: dot product, useful when embedding magnitude carries meaning.
- `"l2"`: Euclidean (L2) distance.
```python
document_store = ArangoDocumentStore(
host="http://localhost:8529",
embedding_dimension=768,
similarity_function="dot_product",
)
```
### Supported Retrievers
- [`ArangoEmbeddingRetriever`](../pipeline-components/retrievers/arangoembeddingretriever.mdx): Retrieves documents from the `ArangoDocumentStore` based on vector similarity using ArangoDB's AQL vector functions.
---
// File: document-stores/arcadedbdocumentstore
# ArcadeDBDocumentStore
| | |
| --- | --- |
| API reference | [ArcadeDB](/reference/integrations-arcadedb) |
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/arcadedb |
ArcadeDB is a multi-model database that supports vector search via its LSM_VECTOR (HNSW) index. The `ArcadeDBDocumentStore` uses ArcadeDB's HTTP/JSON API for all operations—no special drivers required. It supports dense embedding retrieval and SQL-based metadata filtering.
For more information, see the [ArcadeDB documentation](https://docs.arcadedb.com/).
## Installation
Run ArcadeDB with Docker and update the password according to your setup:
```shell
docker run -d -p 2480:2480 \
-e JAVA_OPTS="-Darcadedb.server.rootPassword=arcadedb" \
arcadedata/arcadedb:latest
```
Install the Haystack integration:
```shell
pip install arcadedb-haystack
```
## Usage
Set credentials via environment variables (recommended) or pass them explicitly:
```shell
export ARCADEDB_USERNAME=root
export ARCADEDB_PASSWORD=arcadedb
```
Initialize the document store and write documents:
```python
from haystack import Document
from haystack_integrations.document_stores.arcadedb import ArcadeDBDocumentStore
document_store = ArcadeDBDocumentStore(
url="http://localhost:2480",
database="haystack",
embedding_dimension=768,
recreate_type=True,
)
document_store.write_documents(
[
Document(content="This is first", embedding=[0.0] * 768),
Document(content="This is second", embedding=[0.1, 0.2, 0.3] + [0.0] * 765),
]
)
print(document_store.count_documents())
```
To learn more about the initialization parameters, see the [API docs](/reference/integrations-arcadedb#arcadedbdocumentstore).
Documents without embeddings or with a different dimension are stored with a zero-padded vector so they can be written and filtered; use an [Embedder](../pipeline-components/embedders/sentencetransformersdocumentembedder.mdx) for real embeddings.
### Supported Retrievers
- [ArcadeDBEmbeddingRetriever](../pipeline-components/retrievers/arcadedbembeddingretriever.mdx): An embedding-based Retriever that fetches documents from the Document Store by vector similarity (HNSW).
---
// File: document-stores/astradocumentstore
# AstraDocumentStore
| | |
| --- | --- |
| API reference | [Astra](/reference/integrations-astra) |
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/astra |
DataStax Astra DB is a serverless vector database built on Apache Cassandra, and it supports vector-based search and auto-scaling. You can deploy it on AWS, GCP, or Azure and easily expand to one or more regions within those clouds for multi-region availability, low latency data access, data sovereignty, and to avoid cloud vendor lock-in. For more information, see the [DataStax documentation](https://docs.datastax.com/en/home/docs/index.html).
### Initialization
Once you have an AstraDB account and have created a database, install the `astra-haystack` integration:
```shell
pip install astra-haystack
```
From the configuration in AstraDB’s web UI, you need the database API endpoint and a generated token.
You can additionally set a collection name and a namespace. The collection name defaults to `documents`, and you can set the embedding dimensions and the similarity metric alongside it with `embedding_dimension` and `similarity`. The namespace organizes data in a database and is called a keyspace in Apache Cassandra.
Then, in Haystack, initialize an `AstraDocumentStore` object that’s connected to the AstraDB instance, and write documents to it.
We strongly encourage passing authentication data through environment variables: make sure to populate the environment variables `ASTRA_DB_API_ENDPOINT` and `ASTRA_DB_APPLICATION_TOKEN` before running the following example.
```python
from haystack import Document
from haystack_integrations.document_stores.astra import AstraDocumentStore
document_store = AstraDocumentStore()
document_store.write_documents(
[Document(content="This is first"), Document(content="This is second")],
)
print(document_store.count_documents())
```
### Supported Retrievers
[AstraEmbeddingRetriever](../pipeline-components/retrievers/astraretriever.mdx): An embedding-based Retriever that fetches documents from the Document Store based on a query embedding provided to the Retriever.
### Indexing Warnings
When you create an Astra DB Document Store, you might see one of these warnings:
> Astra DB collection `...` is detected as having indexing turned on for all fields (either created manually or by older versions of this plugin). This implies stricter limitations on the amount of text each string in a document can store. Consider indexing anew on a fresh collection to be able to store longer texts.
Or:
> Astra DB collection `...` is detected as having the following indexing policy: `{...}`. This does not match the requested indexing policy for this object: `{...}`. In particular, there may be stricter limitations on the amount of text each string in a document can store. Consider indexing anew on a fresh collection to be able to store longer texts.
#### Why You See This Warning
The collection already exists and is configured to [index all fields for search](https://docs.datastax.com/en/astra-db-serverless/api-reference/collections.html#the-indexing-option), possibly because you created it earlier or an older plugin did. When Haystack tries to create the collection, it applies an indexing policy optimized for your intended use. This policy lets you store longer texts and avoids indexing fields you won’t filter on, which also reduces write overhead.
#### Common Causes
1. You created the collection outside Haystack (for example, in the Astra UI or with AstraPy’s `Database.create_collection()`).
2. You created the collection with an older version of the plugin.
#### Impact
This is only a warning. Your application keeps running unless you try to store very long text fields. If you do, Astra DB returns an indexing error.
#### Solutions
- **Recommended:** _Drop and recreate the collection_ if you can repopulate it. Then rerun your Haystack application so it creates the collection with the optimized indexing policy.
- _Ignore the warning_ if you’re sure you won’t store very long text fields.
## Additional References
🧑🍳 Cookbook: [Using AstraDB as a data store in your Haystack pipelines](https://haystack.deepset.ai/cookbook/astradb_haystack_integration)
---
// File: document-stores/azureaisearchdocumentstore
# AzureAISearchDocumentStore
A Document Store for storing and retrieval from Azure AI Search Index.
[Azure AI Search](https://learn.microsoft.com/en-us/azure/search/search-what-is-azure-search) is an enterprise-ready search and retrieval system to build RAG-based applications on Azure, with native LLM integrations.
`AzureAISearchDocumentStore` supports semantic reranking and metadata/content filtering. The Document Store is useful for various tasks such as generating knowledge base insights (catalog or document search), information discovery (data exploration), RAG, and automation.
### Initialization
This integration requires you to have an active Azure subscription with a deployed [Azure AI Search](https://azure.microsoft.com/en-us/products/ai-services/ai-search) service.
Once you have the subscription, install the `azure-ai-search-haystack` integration:
```shell
pip install azure-ai-search-haystack
```
To use the `AzureAISearchDocumentStore`, you need to provide a search service endpoint as an `AZURE_AI_SEARCH_ENDPOINT` and an API key as `AZURE_AI_SEARCH_API_KEY` for authentication. If the API key is not provided, the `DefaultAzureCredential` will attempt to authenticate you through the browser.
During initialization the Document Store will either retrieve the existing search index for the given `index_name` or create a new one if it doesn't already exist. Note that one of the limitations of `AzureAISearchDocumentStore` is that the fields of the Azure search index cannot be modified through the API after creation. Therefore, any additional fields beyond the default ones must be provided as `metadata_fields` during the Document Store's initialization. However, if needed, [Azure AI portal](https://azure.microsoft.com/) can be used to modify the fields without deleting the index.
It is recommended to pass authentication data through `AZURE_AI_SEARCH_API_KEY` and `AZURE_AI_SEARCH_ENDPOINT` before running the following example.
```python
from haystack_integrations.document_stores.azure_ai_search import (
AzureAISearchDocumentStore,
)
from haystack import Document
document_store = AzureAISearchDocumentStore(index_name="haystack-docs")
document_store.write_documents(
[
Document(content="This is the first document."),
Document(content="This is the second document."),
],
)
print(document_store.count_documents())
```
:::info[Latency Notice]
Due to Azure search index latency, the document count returned in the example might be zero if executed immediately. To ensure accurate results, be mindful of this latency when retrieving documents from the search index.
:::
You can enable semantic reranking in `AzureAISearchDocumentStore` by providing [SemanticSearch](https://learn.microsoft.com/en-us/python/api/azure-search-documents/azure.search.documents.indexes.models.semanticsearch?view=azure-python) configuration in `index_creation_kwargs` during initialization and calling it from one of the Retrievers. For more information, refer to the [Azure AI tutorial](https://learn.microsoft.com/en-us/azure/search/search-get-started-semantic) on this feature.
### Supported Retrievers
The Haystack Azure AI Search integration includes three Retriever components. Each Retriever leverages the Azure AI Search API and you can select the one that best suits your pipeline:
- [`AzureAISearchEmbeddingRetriever`](../pipeline-components/retrievers/azureaisearchembeddingretriever.mdx): This Retriever accepts the embeddings of a single query as input and returns a list of matching documents. The query must be embedded beforehand, which can be done using an [Embedder](../pipeline-components/embedders.mdx) component.
- [`AzureAISearchBM25Retriever`](../pipeline-components/retrievers/azureaisearchbm25retriever.mdx): A keyword-based Retriever that retrieves documents matching a query from the Azure AI Search index.
- [`AzureAISearchHybridRetriever`](../pipeline-components/retrievers/azureaisearchhybridretriever.mdx): This Retriever combines embedding-based retrieval and keyword search to find matching documents in the search index to get more relevant results.
---
// File: document-stores/chromadocumentstore
# ChromaDocumentStore
| | |
| --- | --- |
| API reference | [Chroma](/reference/integrations-chroma) |
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/chroma |
[Chroma](https://docs.trychroma.com/) is an open source vector database capable of storing collections of documents along with their metadata, creating embeddings for documents and queries, and searching the collections filtering by document metadata or content. Additionally, Chroma supports multi-modal embedding functions.
Chroma can be used in-memory, as an embedded database, or in a client-server fashion. When running in-memory, Chroma can still keep its contents on disk across different sessions. This allows users to quickly put together prototypes using the in-memory version and later move to production, where the client-server version is deployed.
## Initialization
First, install the Chroma integration, which will install Haystack and Chroma if they are not already present. The following command is all you need to start:
```shell
pip install chroma-haystack
```
To store data in Chroma, create a `ChromaDocumentStore` instance and write documents with:
```python
from haystack_integrations.document_stores.chroma import ChromaDocumentStore
from haystack import Document
document_store = ChromaDocumentStore()
document_store.write_documents(
[
Document(content="This is the first document."),
Document(content="This is the second document."),
],
)
print(document_store.count_documents())
```
In this case, since we didn’t pass any embeddings along with our documents, Chroma will create them for us using its [default embedding function](https://docs.trychroma.com/embeddings#default-all-minilm-l6-v2).
### Connection Options
1. **In-Memory Mode (Local)**: Chroma can be set up as a local Document Store for fast and lightweight usage. You can use this option during development or small-scale experiments. Set up a local in-memory instance of `ChromaDocumentStore` like this:
```python
from haystack_integrations.document_stores.chroma import ChromaDocumentStore
document_store = ChromaDocumentStore()
```
2. **Persistent Storage**: If you need to retain the documents between sessions, Chroma supports persistent storage by specifying a path to store data on disk:
```python
from haystack_integrations.document_stores.chroma import ChromaDocumentStore
document_store = ChromaDocumentStore(persist_path="your_directory_path")
```
3. **Remote Connection**: You can connect to a remote Chroma database through HTTP. This is suitable for distributed setups where multiple clients might interact with the same remote Chroma instance.
Note that this option is incompatible with in-memory or persistent storage modes.
First, start a Chroma server:
```shell
chroma run --path /db_path
```
Or using docker:
```shell
docker run -p 8000:8000 chromadb/chroma
```
Then, initialize the Document Store with `host` and `port` parameters:
```python
from haystack_integrations.document_stores.chroma import ChromaDocumentStore
document_store = ChromaDocumentStore(host="localhost", port=8000)
```
## Supported Retrievers
The Haystack Chroma integration comes with two Retriever components. They both rely on the Chroma [query API](https://docs.trychroma.com/reference/Collection#query), but they have different inputs and outputs so that you can pick the one that best fits your pipeline:
- [`ChromaQueryTextRetriever`](../pipeline-components/retrievers/chromaqueryretriever.mdx): This Retriever takes a plain-text query string in input and returns a list of matching documents. Chroma will create the embeddings for the query using its [default embedding function](https://docs.trychroma.com/embeddings#default-all-minilm-l6-v2).
- [`ChromaEmbeddingRetriever`](../pipeline-components/retrievers/chromaembeddingretriever.mdx): This Retriever takes the embeddings of a single query in input and returns a list of matching documents. The query needs to be embedded before being passed to this component. For example, you can use an [embedder](../pipeline-components/embedders.mdx) component.
## Additional References
🧑🍳 Cookbook: [Use Chroma for RAG and Indexing](https://haystack.deepset.ai/cookbook/chroma-indexing-and-rag-examples)
---
// File: document-stores/elasticsearch-document-store
# ElasticsearchDocumentStore
Use an Elasticsearch database with Haystack.
| | |
| --- | --- |
| API reference | [Elasticsearch](/reference/integrations-elasticsearch) |
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/elasticsearch |
ElasticsearchDocumentStore is excellent if you want to evaluate the performance of different retrieval options (dense vs. sparse) and aim for a smooth transition from PoC to production.
It features the approximate nearest neighbours (ANN) search.
### Initialization
[Install](https://www.elastic.co/guide/en/elasticsearch/reference/current/install-elasticsearch.html) Elasticsearch and then [start](https://www.elastic.co/guide/en/elasticsearch/reference/current/starting-elasticsearch.html) an instance. Haystack supports Elasticsearch 8.
If you have Docker set up, we recommend pulling the Docker image and running it.
```shell
docker pull docker.elastic.co/elasticsearch/elasticsearch:8.19.7
docker run -p 9200:9200 -e "discovery.type=single-node" -e "ES_JAVA_OPTS=-Xms1024m -Xmx1024m" -e "xpack.security.enabled=false" docker.elastic.co/elasticsearch/elasticsearch:8.19.7
```
As an alternative, you can go to [Elasticsearch integration GitHub](https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/elasticsearch) and start a Docker container running Elasticsearch using the provided `docker-compose.yml`:
```shell
docker compose up
```
Once you have a running Elasticsearch instance, install the `elasticsearch-haystack` integration:
```shell
pip install elasticsearch-haystack
```
Then, initialize an `ElasticsearchDocumentStore` object that’s connected to the Elasticsearch instance and writes documents to it:
```python
from haystack_integrations.document_stores.elasticsearch import (
ElasticsearchDocumentStore,
)
from haystack import Document
document_store = ElasticsearchDocumentStore(hosts="http://localhost:9200")
document_store.write_documents(
[Document(content="This is first"), Document(content="This is second")],
)
print(document_store.count_documents())
```
### Supported Retrievers
[`ElasticsearchBM25Retriever`](../pipeline-components/retrievers/elasticsearchbm25retriever.mdx): A keyword-based Retriever that fetches documents matching a query from the Document Store.
[`ElasticsearchEmbeddingRetriever`](../pipeline-components/retrievers/elasticsearchembeddingretriever.mdx): Compares the query and document embeddings and fetches the documents most relevant to the query.
---
// File: document-stores/faissdocumentstore
# FAISSDocumentStore
| | |
| --- | --- |
| API reference | [FAISS](/reference/integrations-faiss) |
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/faiss |
`FAISSDocumentStore` is a local Document Store backed by [FAISS](https://github.com/facebookresearch/faiss) for vector similarity search.
It keeps vectors in a FAISS index and stores document data in memory, with optional persistence to disk.
`FAISSDocumentStore` is a good fit for local development and small to medium-sized datasets where you want a lightweight setup without running an external database service.
## Installation
Install the FAISS integration:
```shell
pip install faiss-haystack
```
## Initialization
Create a `FAISSDocumentStore` instance and write embedded documents:
```python
from haystack import Document
from haystack.document_stores.types import DuplicatePolicy
from haystack_integrations.document_stores.faiss import FAISSDocumentStore
document_store = FAISSDocumentStore(
index_path="my_faiss_index", # Optional: enables persistence on disk
index_string="Flat",
embedding_dim=768,
)
document_store.write_documents(
[
Document(content="This is first", embedding=[0.1] * 768),
Document(content="This is second", embedding=[0.2] * 768),
],
policy=DuplicatePolicy.OVERWRITE,
)
print(document_store.count_documents())
# Persist index and metadata files (`.faiss` and `.json`)
document_store.save("my_faiss_index")
```
### Persistence
If you provide `index_path` when initializing `FAISSDocumentStore`, it tries to load existing persisted files (`.faiss` and `.json`) from that path.
You can also explicitly call:
- `save(index_path)` to write index and metadata to disk.
- `load(index_path)` to load them later.
Example of loading from a previously saved folder/path:
```python
from haystack_integrations.document_stores.faiss import FAISSDocumentStore
# This loads `my_faiss_index.faiss` and `my_faiss_index.json` if they exist
document_store = FAISSDocumentStore(index_path="my_faiss_index")
# Alternatively, initialize first and then load explicitly
another_store = FAISSDocumentStore(embedding_dim=768)
another_store.load("my_faiss_index")
```
## Supported Retrievers
[`FAISSEmbeddingRetriever`](../pipeline-components/retrievers/faissembeddingretriever.mdx): Retrieves documents from `FAISSDocumentStore` based on query embeddings.
### Fixing OpenMP Runtime Conflicts on macOS
#### Symptoms
You may encounter one or both of the following errors at runtime:
```
OMP: Error #15: Initializing libomp.dylib, but found libomp.dylib already initialized.
OMP: Hint This means that multiple copies of the OpenMP runtime have been linked into the program.
```
```
resource_tracker: There appear to be 1 leaked semaphore objects to clean up at shutdown
```
If setting `OMP_NUM_THREADS=1` prevents the crash, the root cause is **multiple OpenMP runtimes loaded simultaneously**. Each runtime maintains its own thread pool and thread-local storage (TLS). When two runtimes spin up worker threads at the same time, they corrupt each other's memory — causing segfaults at `N > 1` threads.
---
#### Diagnosis
First, find how many copies of `libomp.dylib` exist in your virtual environment:
```bash
find /path/to/your/.venv -name "libomp.dylib" 2>/dev/null
```
If you see more than one, e.g.:
```
.venv/lib/pythonX.Y/site-packages/torch/lib/libomp.dylib
.venv/lib/pythonX.Y/site-packages/sklearn/.dylibs/libomp.dylib
.venv/lib/pythonX.Y/site-packages/faiss/.dylibs/libomp.dylib
```
you need to consolidate them into a single runtime.
---
#### Fix
The solution is to pick one canonical `libomp.dylib` (torch's is a good choice) and replace all other copies with symlinks pointing to it.
For each duplicate, delete the copy and replace it with a symlink:
```bash
# Delete the duplicate
rm /path/to/.venv/lib/pythonX.Y/site-packages//.dylibs/libomp.dylib
# Replace with a symlink to the canonical copy
ln -s /path/to/.venv/lib/pythonX.Y/site-packages/torch/lib/libomp.dylib \
/path/to/.venv/lib/pythonX.Y/site-packages//.dylibs/libomp.dylib
```
Repeat for every duplicate found. Because these packages use `@loader_path`-relative references to load `libomp.dylib`, the symlink will be transparently resolved to the single canonical runtime at load time.
---
#### Verify
After applying the fix, confirm only one unique `libomp.dylib` is being referenced:
```bash
find /path/to/your/.venv -name "*.so" | xargs otool -L 2>/dev/null | grep libomp | sort -u
```
All entries should resolve to the same canonical path. You should now be able to run without `OMP_NUM_THREADS=1`.
---
// File: document-stores/falkordbdocumentstore
# FalkorDBDocumentStore
Use the FalkorDB graph database with Haystack for GraphRAG workloads.
| | |
| --- | --- |
| API reference | [FalkorDB](/reference/integrations-falkordb) |
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/falkordb |
FalkorDB is a high-performance graph database optimized for GraphRAG workloads. The `FalkorDBDocumentStore` stores documents as graph nodes and supports native vector search — no APOC is required. Documents and their `meta` fields are stored flat on each node, and all bulk writes use `UNWIND` + `MERGE` for safe OpenCypher upserts.
For more information, see the [FalkorDB documentation](https://docs.falkordb.com/).
## Installation
Run FalkorDB with Docker:
```shell
docker run -d -p 6379:6379 falkordb/falkordb:latest
```
Install the Haystack integration:
```shell
pip install falkordb-haystack
```
## Usage
Initialize the document store and write documents:
```python
from haystack import Document
from haystack_integrations.document_stores.falkordb import FalkorDBDocumentStore
document_store = FalkorDBDocumentStore(
host="localhost",
port=6379,
embedding_dim=768,
recreate_graph=True,
)
document_store.write_documents(
[
Document(
content="There are over 7,000 languages spoken around the world today.",
),
Document(
content="Elephants have been observed to recognize themselves in mirrors.",
),
],
)
print(document_store.count_documents())
```
To learn more about the initialization parameters, see the [API docs](/reference/integrations-falkordb#falkordbdocumentstore).
To compute real embeddings for your documents, use a Document Embedder such as the [`SentenceTransformersDocumentEmbedder`](../pipeline-components/embedders/sentencetransformersdocumentembedder.mdx).
### Authentication
To connect to a password-protected FalkorDB instance, pass the password via `Secret`:
```python
from haystack.utils import Secret
from haystack_integrations.document_stores.falkordb import FalkorDBDocumentStore
document_store = FalkorDBDocumentStore(
host="localhost",
port=6379,
password=Secret.from_env_var("FALKORDB_PASSWORD"),
)
```
### Similarity Functions
`FalkorDBDocumentStore` supports two similarity functions for vector search:
- `"cosine"` (default): cosine similarity, best for normalized embeddings.
- `"euclidean"`: Euclidean distance, useful when embedding magnitude matters.
```python
document_store = FalkorDBDocumentStore(
host="localhost",
port=6379,
embedding_dim=768,
similarity="euclidean",
)
```
### Supported Retrievers
- [`FalkorDBEmbeddingRetriever`](../pipeline-components/retrievers/falkordbembeddingretriever.mdx): Retrieves documents from the `FalkorDBDocumentStore` based on vector similarity using FalkorDB's native vector index.
- [`FalkorDBCypherRetriever`](../pipeline-components/retrievers/falkordbcypherretriever.mdx): Retrieves documents by executing arbitrary OpenCypher queries, enabling graph traversal and multi-hop queries for GraphRAG pipelines.
---
// File: document-stores/inmemorydocumentstore
# InMemoryDocumentStore
The `InMemoryDocumentStore` is a very simple document store with no extra services or dependencies.
It is great for experimenting with Haystack, however we do not recommend using it for production.
### Initialization
`InMemoryDocumentStore` requires no external setup. Simply use this code:
```python
from haystack.document_stores.in_memory import InMemoryDocumentStore
document_store = InMemoryDocumentStore()
```
### Supported Retrievers
[`InMemoryBM25Retriever`](../pipeline-components/retrievers/inmemorybm25retriever.mdx): A keyword-based Retriever that fetches documents matching a query from a temporary in-memory database.
[`InMemoryEmbeddingRetriever`](../pipeline-components/retrievers/inmemoryembeddingretriever.mdx): Compares the query and document embeddings and fetches the documents most relevant to the query.
---
// File: document-stores/mongodbatlasdocumentstore
# MongoDBAtlasDocumentStore
`MongoDBAtlasDocumentStore` can be used to manage documents using [MongoDB Atlas](https://www.mongodb.com/atlas), a multi-cloud database service by the same people who build MongoDB. Atlas simplifies deploying and managing your databases while offering the versatility you need to build resilient and performant global applications on the cloud providers of your choice. You can use MongoDB Atlas on cloud providers such as AWS, Azure, or Google Cloud, all without leaving Atlas' web UI.
MongoDB Atlas supports embeddings and can therefore be used for embedding retrieval.
## Installation
To use MongoDB Atlas with Haystack, install the integration first:
```shell
pip install mongodb-atlas-haystack
```
## Initialization
To use MongoDB Atlas with Haystack, you will need to create your MongoDB Atlas account: check the [MongoDB Atlas documentation](https://www.mongodb.com/docs/atlas/getting-started/) for help. You also need to [create a vector search index](https://www.mongodb.com/docs/atlas/atlas-vector-search/create-index/#std-label-avs-create-index) and [a full-text search index](https://www.mongodb.com/docs/atlas/atlas-search/manage-indexes/#create-an-atlas-search-index) for the collection you plan to use.
Once you have your connection string, you should export it in an environment variable called `MONGO_CONNECTION_STRING`. It should look something like this:
```shell
export MONGO_CONNECTION_STRING="mongodb+srv://:@.gwkckbk.mongodb.net/?retryWrites=true&w=majority"
```
At this point, you’re ready to initialize the store:
```python
from haystack_integrations.document_stores.mongodb_atlas import (
MongoDBAtlasDocumentStore,
)
# Initialize the document store
document_store = MongoDBAtlasDocumentStore(
database_name="haystack_test",
collection_name="test_collection",
vector_search_index="embedding_index",
full_text_search_index="search_index",
)
```
## Supported Retrievers
- [`MongoDBAtlasEmbeddingRetriever`](../pipeline-components/retrievers/mongodbatlasembeddingretriever.mdx): Compares the query and document embeddings and fetches the documents most relevant to the query.
- [`MongoDBAtlasFullTextRetriever`](../pipeline-components/retrievers/mongodbatlasfulltextretriever.mdx): A full-text search Retriever.
---
// File: document-stores/opensearch-document-store
# OpenSearchDocumentStore
A Document Store for storing and retrieval from OpenSearch.
| | |
| --- | --- |
| API reference | [OpenSearch](/reference/integrations-opensearch) |
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/opensearch |
OpenSearch is a fully open source search and analytics engine for use cases such as log analytics, real-time application monitoring, and clickstream analysis. For more information, see the [OpenSearch documentation](https://opensearch.org/docs/).
This Document Store is great if you want to evaluate the performance of different retrieval options (dense vs. sparse). It’s compatible with the Amazon OpenSearch Service.
OpenSearch provides support for vector similarity comparisons and approximate nearest neighbors algorithms.
### Initialization
[Install](https://opensearch.org/docs/latest/install-and-configure/install-opensearch/index/) and run an OpenSearch instance.
If you have Docker set up, we recommend pulling the Docker image and running it.
```shell
docker pull opensearchproject/opensearch:3.5.0
docker run \
-p 9200:9200 \
-p 9600:9600 \
-e "discovery.type=single-node" \
-e "ES_JAVA_OPTS=-Xms1024m -Xmx1024m" \
-e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=SecureHaystack*2026" \
opensearchproject/opensearch:3.5.0
```
As an alternative, you can go to [OpenSearch integration GitHub](https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/opensearch) and start a Docker container running OpenSearch using the provided `docker-compose.yml`:
```shell
docker compose up
```
Once you have a running OpenSearch instance, install the `opensearch-haystack` integration:
```shell
pip install opensearch-haystack
```
Then, initialize an `OpenSearchDocumentStore` object that’s connected to the OpenSearch instance and writes documents to it:
```python
from haystack_integrations.document_stores.opensearch import OpenSearchDocumentStore
from haystack import Document
document_store = OpenSearchDocumentStore(
hosts="http://localhost:9200",
use_ssl=True,
verify_certs=False,
http_auth=("admin", "SecureHaystack*2026"),
)
document_store.write_documents(
[Document(content="This is first"), Document(content="This is second")],
)
print(document_store.count_documents())
```
### Supported Retrievers
[`OpenSearchBM25Retriever`](../pipeline-components/retrievers/opensearchbm25retriever.mdx): A keyword-based Retriever that fetches documents matching a query from the Document Store.
[`OpenSearchEmbeddingRetriever`](../pipeline-components/retrievers/opensearchembeddingretriever.mdx): Compares the query and document embeddings and fetches the documents most relevant to the query.
## Additional References
🧑🍳 Cookbook: [PDF-Based Question Answering with Amazon Bedrock and Haystack](https://haystack.deepset.ai/cookbook/amazon_bedrock_for_documentation_qa)
---
// File: document-stores/oracledocumentstore
# OracleDocumentStore
| | |
| --- | --- |
| API reference | [Oracle](/reference/integrations-oracle) |
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/oracle |
`OracleDocumentStore` is a Document Store backed by [Oracle AI Vector Search](https://www.oracle.com/database/ai-vector-search/), available in Oracle Database 23ai and later.
It stores documents alongside dense vector embeddings in a native `VECTOR` column, and supports both vector similarity search and keyword search via an automatically managed DBMS_SEARCH index.
## Installation
```shell
pip install oracle-haystack
```
The examples on this page use Sentence Transformers embedders that have moved to the `sentence-transformers-haystack` package. Install it to run the examples:
```shell
pip install sentence-transformers-haystack
```
## Connection
`OracleDocumentStore` connects to Oracle using the `OracleConnectionConfig` dataclass, which supports two connection modes:
- **Thin mode** (default): connects directly over TCP. No Oracle Instant Client required.
- **Thick mode**: activated automatically when `wallet_location` is provided. Used for Oracle Autonomous Database (ADB-S) connections.
Set the connection parameters as environment variables:
```shell
export ORACLE_USER="haystack"
export ORACLE_PASSWORD="secret"
export ORACLE_DSN="localhost:1521/freepdb1"
```
## Initialization
```python
from haystack.utils import Secret
from haystack_integrations.document_stores.oracle import (
OracleDocumentStore,
OracleConnectionConfig,
)
document_store = OracleDocumentStore(
connection_config=OracleConnectionConfig(
user=Secret.from_env_var("ORACLE_USER"),
password=Secret.from_env_var("ORACLE_PASSWORD"),
dsn=Secret.from_env_var("ORACLE_DSN"),
),
embedding_dim=768,
)
```
To learn more about the initialization parameters, see the [API docs](/reference/integrations-oracle#oracledocumentstore).
### Connecting to Oracle Autonomous Database
For Oracle Autonomous Database (ADB-S), provide a wallet for authentication. The store automatically activates thick mode when `wallet_location` is set:
```python
document_store = OracleDocumentStore(
connection_config=OracleConnectionConfig(
user=Secret.from_env_var("ORACLE_USER"),
password=Secret.from_env_var("ORACLE_PASSWORD"),
dsn=Secret.from_env_var("ORACLE_DSN"),
wallet_location="/path/to/wallet",
wallet_password=Secret.from_env_var("WALLET_PASSWORD"),
),
embedding_dim=1536,
)
```
### HNSW Vector Index
By default, the store performs exact vector search. To enable approximate nearest-neighbor search (faster on large datasets), create an HNSW index:
```python
document_store = OracleDocumentStore(
connection_config=OracleConnectionConfig(
user=Secret.from_env_var("ORACLE_USER"),
password=Secret.from_env_var("ORACLE_PASSWORD"),
dsn=Secret.from_env_var("ORACLE_DSN"),
),
embedding_dim=768,
distance_metric="COSINE",
create_index=True, # creates the HNSW index on startup
hnsw_neighbors=32,
hnsw_ef_construction=200,
hnsw_accuracy=95,
)
```
## Supported Retrievers
- [`OracleEmbeddingRetriever`](../pipeline-components/retrievers/oracleembeddingretriever.mdx): Retrieves documents from `OracleDocumentStore` based on vector similarity to a query embedding.
- [`OracleKeywordRetriever`](../pipeline-components/retrievers/oraclekeywordretriever.mdx): Retrieves documents matching a keyword query using Oracle's DBMS_SEARCH full-text index.
## Example: RAG pipeline
```python
from haystack import Document, Pipeline
from haystack.document_stores.types import DuplicatePolicy
from haystack_integrations.components.embedders.sentence_transformers import (
SentenceTransformersDocumentEmbedder,
SentenceTransformersTextEmbedder,
)
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.utils import Secret
from haystack_integrations.document_stores.oracle import (
OracleDocumentStore,
OracleConnectionConfig,
)
from haystack_integrations.components.retrievers.oracle import OracleEmbeddingRetriever
document_store = OracleDocumentStore(
connection_config=OracleConnectionConfig(
user=Secret.from_env_var("ORACLE_USER"),
password=Secret.from_env_var("ORACLE_PASSWORD"),
dsn=Secret.from_env_var("ORACLE_DSN"),
),
embedding_dim=384,
)
# Index documents
documents = [
Document(content="There are over 7,000 languages spoken around the world today."),
Document(
content="Elephants have been observed to behave in a way that indicates a high level of self-awareness.",
),
Document(
content="In certain places, you can witness the phenomenon of bioluminescent waves.",
),
]
doc_embedder = SentenceTransformersDocumentEmbedder(
model="sentence-transformers/all-MiniLM-L6-v2",
)
embedded_docs = doc_embedder.run(documents)["documents"]
document_store.write_documents(embedded_docs, policy=DuplicatePolicy.OVERWRITE)
# Build a RAG pipeline
template = [
ChatMessage.from_user(
"""
Given the following context, answer the question.
Context: {% for doc in documents %}{{ doc.content }}{% endfor %}
Question: {{ query }}
""",
),
]
pipeline = Pipeline()
pipeline.add_component(
"embedder",
SentenceTransformersTextEmbedder(model="sentence-transformers/all-MiniLM-L6-v2"),
)
pipeline.add_component(
"retriever",
OracleEmbeddingRetriever(document_store=document_store, top_k=3),
)
pipeline.add_component("prompt_builder", ChatPromptBuilder(template=template))
pipeline.add_component(
"llm",
OpenAIChatGenerator(api_key=Secret.from_env_var("OPENAI_API_KEY")),
)
pipeline.connect("embedder.embedding", "retriever.query_embedding")
pipeline.connect("retriever.documents", "prompt_builder.documents")
pipeline.connect("prompt_builder.prompt", "llm.messages")
result = pipeline.run(
{
"embedder": {"text": "How many languages are there?"},
"prompt_builder": {"query": "How many languages are there?"},
},
)
print(result["llm"]["replies"][0].text)
```
---
// File: document-stores/pgvectordocumentstore
# PgvectorDocumentStore
| | |
| --- | --- |
| API reference | [Pgvector](/reference/integrations-pgvector) |
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/pgvector/ |
Pgvector is an extension for PostgreSQL that enhances its capabilities with vector similarity search. It builds upon the classic features of PostgreSQL, such as ACID compliance and point-in-time recovery, and introduces the ability to perform exact and approximate nearest neighbor search using vectors.
For more information, see the [pgvector repository](https://github.com/pgvector/pgvector).
Pgvector Document Store supports embedding retrieval and metadata filtering.
## Installation
To quickly set up a PostgreSQL database with pgvector, you can use Docker:
```shell
docker run -d -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres pgvector/pgvector:pg17
```
For more information on installing pgvector, visit the [pgvector GitHub repository](https://github.com/pgvector/pgvector).
To use pgvector with Haystack, install the `pgvector-haystack` integration:
```shell
pip install pgvector-haystack
```
## Usage
### Connection String
Define the connection string to your PostgreSQL database in the `PG_CONN_STR` environment variable. Two formats are supported:
**URI format:**
```shell
export PG_CONN_STR="postgresql://USER:PASSWORD@HOST:PORT/DB_NAME"
```
**Keyword/value format:**
```shell
export PG_CONN_STR="host=HOST port=PORT dbname=DB_NAME user=USER password=PASSWORD"
```
:::caution[Special Characters in Connection URIs]
When using the URI format, special characters in the password must be [percent-encoded](https://en.wikipedia.org/wiki/Percent-encoding). Otherwise, connection errors may occur. A password like `p=ssword` would cause the error `psycopg.OperationalError: [Errno -2] Name or service not known`.
For example, if your password is `p=ssword`, the connection string should be:
```shell
export PG_CONN_STR="postgresql://postgres:p%3Dssword@localhost:5432/postgres"
```
Alternatively, use the keyword/value format, which does not require percent-encoding:
```shell
export PG_CONN_STR="host=localhost port=5432 dbname=postgres user=postgres password=p=ssword"
```
:::
For more details, see the [PostgreSQL connection string documentation](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING).
## Initialization
Initialize a `PgvectorDocumentStore` object that’s connected to the PostgreSQL database and writes documents to it:
```python
from haystack_integrations.document_stores.pgvector import PgvectorDocumentStore
from haystack import Document
document_store = PgvectorDocumentStore(
embedding_dimension=768,
vector_function="cosine_similarity",
recreate_table=True,
search_strategy="hnsw",
)
document_store.write_documents(
[
Document(content="This is first", embedding=[0.1] * 768),
Document(content="This is second", embedding=[0.3] * 768),
],
)
print(document_store.count_documents())
```
To learn more about the initialization parameters, see our [API docs](/reference/integrations-pgvector#pgvectordocumentstore).
To properly compute embeddings for your documents, you can use a Document Embedder (for instance, the [`SentenceTransformersDocumentEmbedder`](../pipeline-components/embedders/sentencetransformersdocumentembedder.mdx)).
### Supported Retrievers
- [`PgvectorEmbeddingRetriever`](../pipeline-components/retrievers/pgvectorembeddingretriever.mdx): An embedding-based Retriever that fetches documents from the Document Store based on a query embedding provided to the Retriever.
- [`PgvectorKeywordRetriever`](../pipeline-components/retrievers/pgvectorkeywordretriever.mdx): A keyword-based Retriever that fetches documents matching a query from the Pgvector Document Store.
---
// File: document-stores/pinecone-document-store
# PineconeDocumentStore
Use a Pinecone vector database with Haystack.
| | |
| --- | --- |
| API reference | [Pinecone](/reference/integrations-pinecone) |
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/pinecone |
[Pinecone](https://www.pinecone.io/) is a cloud-based vector database. It is fast and easy to use.
Unlike other solutions (such as Qdrant and Weaviate), it can’t run locally on the user's machine but provides a generous free tier.
### Installation
You can simply install the Pinecone Haystack integration with:
```shell
pip install pinecone-haystack
```
### Initialization
- To use Pinecone as a Document Store in Haystack, sign up for a free Pinecone [account](https://app.pinecone.io/) and get your API key.
The Pinecone API key can be explicitly provided or automatically read from the environment variable `PINECONE_API_KEY` (recommended).
- In Haystack, each `PineconeDocumentStore` operates in a specific namespace of an index. If not provided, both index and namespace are `default`.
If the index already exists, the Document Store connects to it. Otherwise, it creates a new index.
- When creating a new index, you can provide a `spec` in the form of a dictionary. This allows choosing between serverless and pod deployment options and setting additional parameters. Refer to the [Pinecone documentation](https://docs.pinecone.io/reference/api/control-plane/create_index) for more details. If not provided, a default spec with serverless deployment in the `us-east-1` region will be used (compatible with the free tier).
- You can provide `dimension` and `metric`, but they are only taken into account if the Pinecone index does not already exist.
Then, you can use the Document Store like this:
```python
from haystack import Document
from haystack_integrations.document_stores.pinecone import PineconeDocumentStore
# Make sure you have the PINECONE_API_KEY environment variable set
document_store = PineconeDocumentStore(
index="default",
namespace="default",
dimension=5,
metric="cosine",
spec={"serverless": {"region": "us-east-1", "cloud": "aws"}},
)
document_store.write_documents(
[
Document(content="This is first", embedding=[0.1] * 5),
Document(content="This is second", embedding=[0.1, 0.2, 0.3, 0.4, 0.5]),
],
)
print(document_store.count_documents())
```
### Supported Retrievers
[`PineconeEmbeddingRetriever`](../pipeline-components/retrievers/pineconedenseretriever.mdx): Retrieves documents from the `PineconeDocumentStore` based on their dense embeddings (vectors).
---
// File: document-stores/qdrant-document-store
# QdrantDocumentStore
Use the Qdrant vector database with Haystack.
| | |
| --- | --- |
| API reference | [Qdrant](/reference/integrations-qdrant) |
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/qdrant |
Qdrant is a powerful high-performance, massive-scale vector database. The `QdrantDocumentStore` can be used with any Qdrant instance, in-memory, locally persisted, hosted, and the official Qdrant Cloud.
### Installation
You can simply install the Qdrant Haystack integration with:
```shell
pip install qdrant-haystack
```
### Initialization
The quickest way to use `QdrantDocumentStore` is to create an in-memory instance of it:
```python
from haystack.dataclasses.document import Document
from haystack_integrations.document_stores.qdrant import QdrantDocumentStore
document_store = QdrantDocumentStore(
":memory:",
recreate_index=True,
return_embedding=True,
wait_result_from_api=True,
)
document_store.write_documents(
[
Document(content="This is first", embedding=[0.0] * 768),
Document(content="This is second", embedding=[0.1] * 768),
],
)
print(document_store.count_documents())
```
:::warning[Collections Created Outside Haystack]
When you create a `QdrantDocumentStore` instance, Haystack takes care of setting up the collection. In general, you cannot use a Qdrant collection created without Haystack with Haystack. If you want to migrate your existing collection, see the sample script at https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/qdrant/src/haystack_integrations/document_stores/qdrant/migrate_to_sparse.py.
:::
You can also connect directly to [Qdrant Cloud](https://cloud.qdrant.io/login). Once you have your API key and your cluster URL from the Qdrant dashboard, you can connect like this:
```python
from haystack.dataclasses.document import Document
from haystack_integrations.document_stores.qdrant import QdrantDocumentStore
from haystack.utils import Secret
document_store = QdrantDocumentStore(
url="https://XXXXXXXXX.us-east4-0.gcp.cloud.qdrant.io:6333",
index="your_index_name",
embedding_dim=5, # based on the embedding model
recreate_index=True, # enable only to recreate the index and not connect to the existing one
api_key=Secret.from_token("YOUR_TOKEN"),
)
document_store.write_documents(
[
Document(content="This is first", embedding=[0.0] * 5),
Document(content="This is second", embedding=[0.1, 0.2, 0.3, 0.4, 0.5]),
],
)
print(document_store.count_documents())
```
:::tip[More information]
You can find more ways to initialize and use QdrantDocumentStore on our [integration page](https://haystack.deepset.ai/integrations/qdrant-document-store).
:::
### Supported Retrievers
- [`QdrantEmbeddingRetriever`](../pipeline-components/retrievers/qdrantembeddingretriever.mdx): Retrieves documents from the `QdrantDocumentStore` based on their dense embeddings (vectors).
- [`QdrantSparseEmbeddingRetriever`](../pipeline-components/retrievers/qdrantsparseembeddingretriever.mdx): Retrieves documents from the `QdrantDocumentStore` based on their sparse embeddings.
- [`QdrantHybridRetriever`](../pipeline-components/retrievers/qdranthybridretriever.mdx): Retrieves documents from the `QdrantDocumentStore` based on both dense and sparse embeddings.
:::note[Sparse Embedding Support]
To use Sparse Embedding support, you need to initialize the `QdrantDocumentStore` with `use_sparse_embeddings=True`, which is `False` by default.
If you want to use Document Store or collection previously created with this feature disabled, you must migrate the existing data. You can do this by taking advantage of the `migrate_to_sparse_embeddings_support` utility function.
:::
## Additional References
🧑🍳 Cookbook: [Sparse Embedding Retrieval with Qdrant and FastEmbed](https://haystack.deepset.ai/cookbook/sparse_embedding_retrieval)
---
// File: document-stores/supabasedocumentstore
# SupabaseDocumentStore
| | |
| --- | --- |
| API reference | [Supabase](/reference/integrations-supabase) |
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/supabase/ |
[Supabase](https://supabase.com/) is an open-source backend platform built on PostgreSQL. The Supabase integration for Haystack provides two document stores:
- **`SupabasePgvectorDocumentStore`** — vector similarity search using the [pgvector](https://github.com/pgvector/pgvector) PostgreSQL extension, which comes pre-installed on Supabase.
- **`SupabaseGroongaDocumentStore`** — multilingual full-text search using the [PGroonga](https://pgroonga.github.io/) PostgreSQL extension. No embeddings required.
## Installation
```shell
pip install supabase-haystack
```
The examples on this page use Sentence Transformers embedders that have moved to the `sentence-transformers-haystack` package. Install it to run the examples:
```shell
pip install sentence-transformers-haystack
```
## SupabasePgvectorDocumentStore
`SupabasePgvectorDocumentStore` is a thin wrapper around [`PgvectorDocumentStore`](./pgvectordocumentstore.mdx) with Supabase-specific defaults:
- Reads the connection string from the `SUPABASE_DB_URL` environment variable.
- Defaults `create_extension` to `False` since pgvector is pre-installed on Supabase.
### Connection
Set the `SUPABASE_DB_URL` environment variable with your Supabase database connection string.
:::tip[Use session mode (port 5432)]
Supabase offers two pooler ports: transaction mode (port 6543) and session mode (port 5432). For best compatibility with pgvector operations, use session mode or a direct connection.
:::
```shell
export SUPABASE_DB_URL="postgresql://postgres.[project-ref]:[password]@aws-0-[region].pooler.supabase.com:5432/postgres"
```
### Initialization
```python
from haystack_integrations.document_stores.supabase import SupabasePgvectorDocumentStore
document_store = SupabasePgvectorDocumentStore(
embedding_dimension=768,
vector_function="cosine_similarity",
recreate_table=True,
)
```
To learn more about the initialization parameters, see the [API docs](/reference/integrations-supabase#supabasepgvectordocumentstore).
### Supported Retrievers
- [`SupabasePgvectorEmbeddingRetriever`](../pipeline-components/retrievers/supabasepgvectorembeddingretriever.mdx): Fetches documents from the store based on a query embedding.
- [`SupabasePgvectorKeywordRetriever`](../pipeline-components/retrievers/supabasepgvectorkeywordretriever.mdx): Fetches documents matching a keyword query using PostgreSQL's `ts_rank_cd` ranking.
### Example: RAG pipeline
```python
from haystack import Document, Pipeline
from haystack.document_stores.types.policy import DuplicatePolicy
from haystack_integrations.components.embedders.sentence_transformers import (
SentenceTransformersTextEmbedder,
SentenceTransformersDocumentEmbedder,
)
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.utils import Secret
from haystack_integrations.document_stores.supabase import SupabasePgvectorDocumentStore
from haystack_integrations.components.retrievers.supabase import (
SupabasePgvectorEmbeddingRetriever,
)
document_store = SupabasePgvectorDocumentStore(
embedding_dimension=768,
vector_function="cosine_similarity",
recreate_table=True,
)
# Index documents
documents = [
Document(content="There are over 7,000 languages spoken around the world today."),
Document(
content="Elephants have been observed to behave in a way that indicates a high level of self-awareness.",
),
Document(
content="In certain places, you can witness the phenomenon of bioluminescent waves.",
),
]
embedder = SentenceTransformersDocumentEmbedder()
documents_with_embeddings = embedder.run(documents)
document_store.write_documents(
documents_with_embeddings["documents"],
policy=DuplicatePolicy.OVERWRITE,
)
# Query pipeline
prompt_template = [
ChatMessage.from_system("Answer the question based on the provided context."),
ChatMessage.from_user(
"Query: {{query}}\nDocuments:\n{% for doc in documents %}{{ doc.content }}\n{% endfor %}\nAnswer:",
),
]
query_pipeline = Pipeline()
query_pipeline.add_component("text_embedder", SentenceTransformersTextEmbedder())
query_pipeline.add_component(
"retriever",
SupabasePgvectorEmbeddingRetriever(document_store=document_store),
)
query_pipeline.add_component(
"prompt_builder",
ChatPromptBuilder(
template=prompt_template,
required_variables=["query", "documents"],
),
)
query_pipeline.add_component("generator", OpenAIChatGenerator(model="gpt-4o"))
query_pipeline.connect("text_embedder.embedding", "retriever.query_embedding")
query_pipeline.connect("retriever.documents", "prompt_builder.documents")
query_pipeline.connect("prompt_builder.prompt", "generator.messages")
result = query_pipeline.run(
{
"text_embedder": {"text": "How many languages are there?"},
"prompt_builder": {"query": "How many languages are there?"},
},
)
```
---
## SupabaseGroongaDocumentStore
`SupabaseGroongaDocumentStore` uses [PGroonga](https://pgroonga.github.io/), a PostgreSQL extension for fast, multilingual full-text search. Unlike the pgvector store, it works with plain text queries and requires no embeddings.
### Prerequisites
PGroonga must be enabled in your Supabase project. Run the following SQL in the Supabase SQL editor:
```sql
CREATE EXTENSION IF NOT EXISTS pgroonga;
```
You also need to create a SQL function that PGroonga uses for search. See the [integration README](https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/supabase/) for the required function definition.
### Initialization
```python
from haystack_integrations.document_stores.supabase import SupabaseGroongaDocumentStore
from haystack.utils import Secret
document_store = SupabaseGroongaDocumentStore(
supabase_url="https://.supabase.co",
supabase_key=Secret.from_env_var("SUPABASE_SERVICE_KEY"),
table_name="haystack_groonga_documents",
)
document_store.warm_up()
```
:::note
`warm_up()` must be called before using the store. It initializes the Supabase client and creates the table and PGroonga index if they don't exist.
:::
To learn more about the initialization parameters, see the [API docs](/reference/integrations-supabase).
### Supported Retrievers
- [`SupabaseGroongaBM25Retriever`](../pipeline-components/retrievers/supabasegroongabm25retriever.mdx): Retrieves documents using PGroonga full-text search. Works without embeddings and can be combined with `SupabasePgvectorEmbeddingRetriever` for hybrid search pipelines.
---
// File: document-stores/valkeydocumentstore
# ValkeyDocumentStore
| | |
| --- | --- |
| API reference | [Valkey](/reference/integrations-valkey) |
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/valkey |
[Valkey](https://valkey.io/) is a high-performance, in-memory data structure store that you can use in Haystack pipelines with the `ValkeyDocumentStore`. Valkey operates in-memory by default for maximum performance, but can be configured with persistence options for data durability.
The `ValkeyDocumentStore` connects to a Valkey server with the search module running and supports vector similarity search for RAG and other retrieval use cases. For a detailed overview of all the available methods and settings, visit the [API Reference](/reference/integrations-valkey#valkeydocumentstore).
## Installation
You can install the Valkey Haystack integration with:
```shell
pip install valkey-haystack
```
The examples on this page use Sentence Transformers embedders that have moved to the `sentence-transformers-haystack` package. Install it to run the examples:
```shell
pip install sentence-transformers-haystack
```
## Initialization
To use Valkey as your data storage for Haystack pipelines, you need a Valkey server with the search module running. Initialize a `ValkeyDocumentStore` like this:
```python
from haystack_integrations.document_stores.valkey import ValkeyDocumentStore
document_store = ValkeyDocumentStore(
nodes_list=[("localhost", 6379)],
index_name="my_documents",
embedding_dim=768,
distance_metric="cosine",
)
```
### Running Valkey locally
For development and testing, you can start a Valkey server with Docker:
```shell
docker run -d -p 6379:6379 valkey/valkey-bundle:latest
```
Then connect with the same initialization code above, using `nodes_list=[("localhost", 6379)]`.
For more advanced configurations and clustering setups, refer to the [Valkey documentation](https://valkey.io/docs/).
## Writing documents
To write documents to your `ValkeyDocumentStore`, create an indexing pipeline or use the `write_documents()` method. You can use [Converters](../pipeline-components/converters.mdx), [PreProcessors](../pipeline-components/preprocessors.mdx), and other integrations to fetch and prepare data. Below is an example that indexes Markdown files into Valkey.
### Indexing pipeline
```python
from haystack import Pipeline
from haystack.components.converters import MarkdownToDocument
from haystack.components.writers import DocumentWriter
from haystack_integrations.components.embedders.sentence_transformers import (
SentenceTransformersDocumentEmbedder,
)
from haystack.components.preprocessors import DocumentSplitter
from haystack_integrations.document_stores.valkey import ValkeyDocumentStore
document_store = ValkeyDocumentStore(
nodes_list=[("localhost", 6379)],
index_name="my_documents",
embedding_dim=768,
distance_metric="cosine",
)
indexing = Pipeline()
indexing.add_component("converter", MarkdownToDocument())
indexing.add_component(
"splitter",
DocumentSplitter(split_by="sentence", split_length=2),
)
indexing.add_component("embedder", SentenceTransformersDocumentEmbedder())
indexing.add_component("writer", DocumentWriter(document_store))
indexing.connect("converter", "splitter")
indexing.connect("splitter", "embedder")
indexing.connect("embedder", "writer")
indexing.run({"converter": {"sources": ["filename.md"]}})
```
## Using Valkey in a RAG pipeline
Once documents are in your `ValkeyDocumentStore`, you can use [`ValkeyEmbeddingRetriever`](../pipeline-components/retrievers/valkeyembeddingretriever.mdx) to retrieve them. The following example builds a RAG pipeline with a custom prompt:
```python
from haystack import Pipeline
from haystack.utils import Secret
from haystack.dataclasses import ChatMessage
from haystack_integrations.components.embedders.sentence_transformers import (
SentenceTransformersTextEmbedder,
)
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack_integrations.document_stores.valkey import ValkeyDocumentStore
from haystack_integrations.components.retrievers.valkey import ValkeyEmbeddingRetriever
document_store = ValkeyDocumentStore(
nodes_list=[("localhost", 6379)],
index_name="my_documents",
embedding_dim=768,
distance_metric="cosine",
)
prompt_template = [
ChatMessage.from_system(
"Answer the question based on the provided context. If the context does not include an answer, reply with 'I don't know'.",
),
ChatMessage.from_user(
"Query: {{query}}\n"
"Documents:\n{% for doc in documents %}{{ doc.content }}\n{% endfor %}\n"
"Answer:",
),
]
query_pipeline = Pipeline()
query_pipeline.add_component("text_embedder", SentenceTransformersTextEmbedder())
query_pipeline.add_component(
"retriever",
ValkeyEmbeddingRetriever(document_store=document_store),
)
query_pipeline.add_component(
"prompt_builder",
ChatPromptBuilder(
template=prompt_template,
required_variables=["query", "documents"],
),
)
query_pipeline.add_component(
"generator",
OpenAIChatGenerator(
api_key=Secret.from_token("YOUR_OPENAI_API_KEY"),
model="gpt-4o",
),
)
query_pipeline.connect("text_embedder.embedding", "retriever.query_embedding")
query_pipeline.connect("retriever.documents", "prompt_builder.documents")
query_pipeline.connect("prompt_builder.prompt", "generator.messages")
query = "What is Valkey?"
results = query_pipeline.run(
{
"text_embedder": {"text": query},
"prompt_builder": {"query": query},
},
)
```
For more examples, see the [examples folder](https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/valkey/examples) in the repository.
## Performance benefits
- **In-memory storage**: Fast read and write operations.
- **High throughput**: Handles many operations per second.
- **Low latency**: Minimal response times for document operations.
- **Scalability**: Supports clustering for horizontal scaling.
## Supported Retrievers
[`ValkeyEmbeddingRetriever`](../pipeline-components/retrievers/valkeyembeddingretriever.mdx): Compares the query and document embeddings and fetches the documents most relevant to the query from the `ValkeyDocumentStore`.
---
// File: document-stores/vespadocumentstore
# VespaDocumentStore
| | |
| --- | --- |
| API reference | [Vespa](/reference/integrations-vespa) |
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/vespa |
[Vespa](https://vespa.ai/) is an open-source big data serving engine that supports structured, text, and vector search at scale. The `VespaDocumentStore` connects Haystack to an existing Vespa application through [pyvespa](https://vespa-engine.github.io/pyvespa/) and supports both lexical and dense vector retrieval as well as metadata filtering.
Unlike most other Haystack Document Stores, the `VespaDocumentStore` does **not** create or deploy the Vespa application or schema for you. You configure Vespa with the fields and rank profiles you need, deploy it (either self-hosted or on [Vespa Cloud](https://cloud.vespa.ai/)), and then point the Document Store at the running endpoint.
## Installation
Install the `vespa-haystack` integration:
```shell
pip install vespa-haystack
```
To run Vespa locally, see the [Vespa quick start](https://docs.vespa.ai/en/vespa-quick-start.html). To deploy a managed Vespa application, see [Vespa Cloud](https://cloud.vespa.ai/en/getting-started).
## Usage
### Prerequisites: Vespa Schema
Before using the `VespaDocumentStore`, you need a deployed Vespa application with a schema compatible with the fields you configure on the Document Store. By default, the integration expects:
- A text field named `content` for the Document body.
- A tensor field named `embedding` for dense vectors (when using embedding retrieval).
- A rank profile named `bm25` for lexical retrieval (used by `VespaKeywordRetriever`).
- A rank profile named `semantic` that ranks with `closeness(field, embedding)` (used by `VespaEmbeddingRetriever`).
Field and rank profile names can be customized via the Document Store and Retriever constructors. See the [Vespa documentation](https://docs.vespa.ai/en/schemas.html) for details on writing schemas and rank profiles.
### Authentication
The `VespaDocumentStore` supports the authentication methods provided by `pyvespa`:
- **No authentication** for local development against an unsecured Vespa endpoint.
- **mTLS** with a data plane certificate and key (via the `cert` and `key` parameters as [Secrets](../concepts/secret-management.mdx)).
- **Bearer token** for Vespa Cloud token endpoints (via `vespa_cloud_secret_token` or the `VESPA_CLOUD_SECRET_TOKEN` environment variable).
The Vespa endpoint URL can be passed via the `url` parameter or the `VESPA_URL` environment variable:
```shell
export VESPA_URL="http://localhost"
```
For Vespa Cloud token authentication:
```shell
export VESPA_URL="https://my-app.my-tenant.aws-us-east-1c.z.vespa-app.cloud"
export VESPA_CLOUD_SECRET_TOKEN="my-secret-token"
```
## Initialization
Point the `VespaDocumentStore` at your deployed Vespa application and write Documents to it. The HTTP client is created lazily on first use:
```python
from haystack import Document
from haystack_integrations.document_stores.vespa import VespaDocumentStore
document_store = VespaDocumentStore(
url="http://localhost",
schema="doc",
namespace="doc",
content_field="content",
embedding_field="embedding",
metadata_fields=["category"],
)
document_store.write_documents(
[
Document(
content="Haystack integrates with Vespa for search.",
meta={"category": "docs"},
),
Document(
content="Vespa supports lexical and vector retrieval.",
meta={"category": "docs"},
),
],
)
print(document_store.count_documents())
```
To learn more about the initialization parameters, see our [API docs](/reference/integrations-vespa#vespadocumentstore).
To compute embeddings for your Documents, you can use a Document Embedder, such as the [`SentenceTransformersDocumentEmbedder`](../pipeline-components/embedders/sentencetransformersdocumentembedder.mdx).
### Metadata Fields
Vespa is strictly schema-bound: every metadata field that you want to feed or read back from Vespa must exist as a field in the deployed schema. Use the `metadata_fields` parameter to declare an allowlist of metadata keys to send to Vespa on write and to request back on read. Metadata keys that are not in this allowlist are kept on Documents in memory but are not stored in Vespa.
### Metadata Filtering
The `VespaDocumentStore` supports comparison operators (`==`, `!=`, `>`, `>=`, `<`, `<=`, `in`, `not in`) and the logical operators `AND`, `OR`, and `NOT`. Filters are translated to Vespa's [YQL](https://docs.vespa.ai/en/query-language.html) where clauses whenever possible.
Filters on date-typed values are evaluated client-side in Python when YQL cannot express the comparison directly. For more details on filter syntax, refer to [Metadata Filtering](../concepts/metadata-filtering.mdx).
### Supported Retrievers
- [`VespaEmbeddingRetriever`](../pipeline-components/retrievers/vespaembeddingretriever.mdx): A dense embedding-based Retriever that fetches Documents from Vespa using nearest-neighbor search and a configurable rank profile.
- [`VespaKeywordRetriever`](../pipeline-components/retrievers/vespakeywordretriever.mdx): A lexical Retriever that fetches Documents from Vespa using a configurable rank profile (BM25 by default).
---
// File: document-stores/weaviatedocumentstore
# WeaviateDocumentStore
| | |
| --- | --- |
| API reference | [Weaviate](/reference/integrations-weaviate) |
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/weaviate |
Weaviate is a multi-purpose vector DB that can store both embeddings and data objects, making it a good choice for multi-modality.
The `WeaviateDocumentStore` can connect to any Weaviate instance, whether it's running on Weaviate Cloud Services, Kubernetes, or a local Docker container.
## Installation
You can simply install the Weaviate Haystack integration with:
```shell
pip install weaviate-haystack
```
## Initialization
### Weaviate Embedded
To use `WeaviateDocumentStore` as a temporary instance, initialize it as ["Embedded"](https://weaviate.io/developers/weaviate/installation/embedded):
```python
from haystack_integrations.document_stores.weaviate import WeaviateDocumentStore
from weaviate.embedded import EmbeddedOptions
document_store = WeaviateDocumentStore(embedded_options=EmbeddedOptions())
```
### Docker
You can use `WeaviateDocumentStore` in a local Docker container. This is what a minimal `docker-compose.yml` could look like:
```yaml
---
services:
weaviate:
command:
- --host
- 0.0.0.0
- --port
- '8080'
- --scheme
- http
image: semitechnologies/weaviate:1.36.2
ports:
- 8080:8080
- 50051:50051
volumes:
- weaviate_data:/var/lib/weaviate
restart: 'no'
environment:
QUERY_DEFAULTS_LIMIT: 25
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true'
PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
DEFAULT_VECTORIZER_MODULE: 'none'
ENABLE_MODULES: ''
CLUSTER_HOSTNAME: 'node1'
volumes:
weaviate_data:
...
```
:::warning
With this example, we explicitly enable access without authentication, so you don't need to set any username, password, or API key to connect to our local instance. That is strongly discouraged for production use. See the [authorization](#authorization) section for detailed information.
:::
Start your container with `docker compose up -d` and then initialize the Document Store with:
```python
from haystack_integrations.document_stores.weaviate.document_store import (
WeaviateDocumentStore,
)
from haystack import Document
document_store = WeaviateDocumentStore(url="http://localhost:8080")
document_store.write_documents(
[Document(content="This is first"), Document(content="This is second")],
)
print(document_store.count_documents())
```
### Weaviate Cloud Service
To use the [Weaviate managed cloud service](https://weaviate.io/developers/wcs), first, create your Weaviate cluster.
Then, initialize the `WeaviateDocumentStore` using the API Key and URL found in your [Weaviate account](https://console.weaviate.cloud/):
```python
from haystack_integrations.document_stores.weaviate import (
WeaviateDocumentStore,
AuthApiKey,
)
from haystack import Document
import os
os.environ["WEAVIATE_API_KEY"] = "YOUR-API-KEY"
auth_client_secret = AuthApiKey()
document_store = WeaviateDocumentStore(
url="YOUR-WEAVIATE-URL",
auth_client_secret=auth_client_secret,
)
```
## Authorization
We provide some utility classes in the `auth` package to handle authorization using different credentials. Every class stores distinct [secrets](../concepts/secret-management.mdx) and retrieves them from the environment variables when required.
The default environment variables for the classes are:
- **`AuthApiKey`**
- `WEAVIATE_API_KEY`
- **`AuthBearerToken`**
- `WEAVIATE_ACCESS_TOKEN`
- `WEAVIATE_REFRESH_TOKEN`
- **`AuthClientCredentials`**
- `WEAVIATE_CLIENT_SECRET`
- `WEAVIATE_SCOPE`
- **`AuthClientPassword`**
- `WEAVIATE_USERNAME`
- `WEAVIATE_PASSWORD`
- `WEAVIATE_SCOPE`
You can easily change environment variables if needed. In the following snippet, we instruct `AuthApiKey` to look for `MY_ENV_VAR`.
```python
from haystack_integrations.document_stores.weaviate.auth import AuthApiKey
from haystack.utils.auth import Secret
AuthApiKey(api_key=Secret.from_env_var("MY_ENV_VAR"))
```
## Supported Retrievers
[`WeaviateBM25Retriever`](../pipeline-components/retrievers/weaviatebm25retriever.mdx): A keyword-based Retriever that fetches documents matching a query from the Document Store.
[`WeaviateEmbeddingRetriever`](../pipeline-components/retrievers/weaviateembeddingretriever.mdx): Compares the query and document embeddings and fetches the documents most relevant to the query.
---
// File: intro
# Introduction to Haystack
Haystack is an **open-source AI framework** for building production-ready **AI Agents**, **powerful RAG applications** and **scalable multimodal search systems**. Build pipelines using reusable components, each responsible for specific tasks. Customize and extend pipelines to match your requirements. Learn more about Haystack and how it works.
:::tip[Welcome to Haystack]
To skip the introductions and go directly to installing and creating a search app, see [Get Started](overview/get-started.mdx).
:::
Haystack is an open-source AI orchestration framework that you can use to build powerful, production-ready applications with Large Language Models (LLMs) for various use cases. Whether you’re creating autonomous agents, multimodal apps, or scalable RAG systems, Haystack provides the tools to move from idea to production easily.
Haystack is designed in a modular way, allowing you to combine the best technology from OpenAI, Google, Anthropic, and open-source projects like Hugging Face's Transformers.
The core foundation of Haystack consists of components and pipelines, along with Document Stores, Agents, Tools, and many integrations. Read more about Haystack concepts in the [Haystack Concepts Overview](concepts/concepts-overview.mdx).
Supported by an engaged community of developers, Haystack has grown into a comprehensive and user-friendly framework for LLM-based development.
:::note[Looking to scale with confidence?]
If your team needs **enterprise-grade support, best practices, and deployment guidance** to run Haystack in production, check out **Haystack Enterprise Starter**.
📜 [Learn more about Haystack Enterprise Starter](https://haystack.deepset.ai/blog/announcing-haystack-enterprise)
🤝 [Get in touch with our team](https://www.deepset.ai/products-and-services/haystack-enterprise-starter)
👉 For platform tooling to **manage data, pipelines, testing, and governance at scale**, explore the [Haystack Enterprise Platform](https://www.deepset.ai/products-and-services/haystack-enterprise-platform).
:::
---
// File: memory-stores/cogneememorystore
# CogneeMemoryStore
`CogneeMemoryStore` is a persistent memory store backed by Cognee's knowledge graph API. It is the shared data layer used by [`CogneeRetriever`](../pipeline-components/retrievers/cogneeretriever.mdx) and [`CogneeWriter`](../pipeline-components/writers/cogneewriter.mdx).
## Overview
`CogneeMemoryStore` wraps Cognee's V2 memory API:
- `add_memories` → `cognee.remember`
- `search_memories` → `cognee.recall`
- `improve` → `cognee.improve`
- `delete_all_memories` → `cognee.forget`
Cognee supports two memory tiers. Set `session_id` to use the **session cache** — fast writes with no LLM extraction, session-aware recall. Leave `session_id` as `None` to write to the **permanent knowledge graph**, which uses LLM extraction during ingestion and supports richer graph-completion queries.
Cognee configuration (LLM provider, database, vector store) is read from environment variables. See the [Cognee documentation](https://docs.cognee.ai) for setup instructions.
### Parameters
- `search_type` is *optional* and defaults to `"GRAPH_COMPLETION"`. Controls which Cognee recall strategy is used. Other useful values include `"CHUNKS"` for raw retrieval and `"SUMMARIES"` for summarized graph nodes.
- `top_k` is *optional* and defaults to `5`. Sets the default maximum number of memories returned per search.
- `dataset_name` is *optional* and defaults to `"haystack_memory"`. Names the Cognee dataset backing this store.
- `session_id` is *optional* and defaults to `None`. When set, reads and writes target the session-cache tier. When `None`, the permanent knowledge graph is used.
- `self_improvement` is *optional* and defaults to `True`. When `True`, Cognee runs graph improvement inline after every write. Set to `False` when you want `improve()` to be the sole improvement trigger.
- `timeout` is *optional* and defaults to `300`. Per-call timeout in seconds for any Cognee operation.
### Installation
Install the Cognee integration:
```bash
pip install cognee-haystack
```
Set your LLM API key (used by Cognee for graph extraction and queries):
```bash
export LLM_API_KEY="your-llm-api-key"
```
Optionally, set a separate embedding API key (defaults to `LLM_API_KEY` when unset):
```bash
export EMBEDDING_API_KEY="your-embedding-api-key"
```
## Usage
### On its own
```python
from haystack.dataclasses import ChatMessage
from haystack_integrations.memory_stores.cognee import CogneeMemoryStore
store = CogneeMemoryStore(search_type="GRAPH_COMPLETION", top_k=5)
store.add_memories(
messages=[ChatMessage.from_user("Alice enjoys hiking and outdoor activities.")],
user_id="a1b2c3d4-e5f6-7890-abcd-ef1234567890",
)
memories = store.search_memories(
query="What does Alice like?",
user_id="a1b2c3d4-e5f6-7890-abcd-ef1234567890",
)
print([msg.text for msg in memories])
```
### Session tier vs permanent graph
Use `session_id` to control which memory tier is targeted. A single store can serve both tiers — the writer's `session_id` overrides the store's `session_id` per call.
```python
from haystack.dataclasses import ChatMessage
from haystack_integrations.memory_stores.cognee import CogneeMemoryStore
store = CogneeMemoryStore(dataset_name="my_agent_memory", self_improvement=False)
# Write long-lived facts to the permanent graph (no session_id).
store.add_memories(
messages=[ChatMessage.from_user("Alice is a senior data scientist at Acme Corp.")],
)
# Write transient session context to the session cache.
store.add_memories(
messages=[
ChatMessage.from_user("Alice is currently debugging a vector store issue.")
],
session_id="alice_session_1",
)
# Promote the session cache into the permanent graph.
store.improve(session_id="alice_session_1")
```
### Delete all memories
```python
# Delete only this store's dataset (session cache is unaffected).
store.delete_all_memories()
# To wipe everything including the session cache, call cognee directly:
import asyncio
import cognee
asyncio.run(cognee.forget(everything=True))
```
---
// File: memory-stores/mem0memorystore
# Mem0MemoryStore
`Mem0MemoryStore` is a memory store backed by the Mem0 cloud API. It is the shared data layer used by [`Mem0MemoryRetriever`](../pipeline-components/retrievers/mem0memoryretriever.mdx), [`Mem0MemoryWriter`](../pipeline-components/writers/mem0memorywriter.mdx), and the [Mem0 Memory Tools](../tools/ready-made-tools/mem0memorytools.mdx).
## Overview
`Mem0MemoryStore` wraps the Mem0 cloud API and provides two core methods:
- `add_memories` — stores a list of `ChatMessage` objects as memories in Mem0.
- `search_memories` — retrieves memories from Mem0 that are relevant to a query.
Scope memories with at least one Mem0 entity ID: `user_id`, `run_id`, `agent_id`, or `app_id`. These are runtime parameters, so a single store instance can serve multiple users or sessions.
The `infer` parameter on `add_memories` controls how Mem0 processes incoming messages:
- `infer=True` lets Mem0 extract memories from the messages automatically. This is useful when storing a full Agent turn.
- `infer=False` stores the supplied message text as-is. This is useful when the exact memory text has already been selected upstream.
### Installation
Install the Mem0 integration:
```bash
pip install mem0-haystack
```
Set your Mem0 API key:
```bash
export MEM0_API_KEY="your-mem0-api-key"
```
## Usage
### On its own
```python
from haystack.dataclasses import ChatMessage
from haystack_integrations.memory_stores.mem0 import Mem0MemoryStore
store = Mem0MemoryStore()
store.add_memories(
messages=[ChatMessage.from_user("Alice prefers concise Python examples.")],
user_id="alice",
infer=False,
)
memories = store.search_memories(
query="What does Alice prefer?",
user_id="alice",
top_k=3,
)
print([msg.text for msg in memories])
```
### Scoping with multiple entity IDs
Mem0 supports narrowing the scope of reads and writes with `user_id`, `run_id`, `agent_id`, and `app_id`. Pass any combination at call time:
```python
store.add_memories(
messages=[
ChatMessage.from_user("Alice is working on a documentation search system.")
],
user_id="alice",
run_id="docs-assistant-session-1",
infer=True,
)
memories = store.search_memories(
query="What project is Alice working on?",
user_id="alice",
run_id="docs-assistant-session-1",
)
print([msg.text for msg in memories])
```
### Retrieving all memories in scope
Pass `query=None` to return all memories matching the provided scope without a relevance search:
```python
all_memories = store.search_memories(query=None, user_id="alice")
print([msg.text for msg in all_memories])
```
---
// File: optimization/advanced-rag-techniques/hypothetical-document-embeddings-hyde
import ClickableImage from "@site/src/components/ClickableImage";
# Hypothetical Document Embeddings (HyDE)
Enhance the retrieval in Haystack using HyDE method by generating a mock-up hypothetical document for an initial query.
## When Is It Helpful?
The HyDE method is highly useful when:
- The performance of the retrieval step in your pipeline is not good enough (for example, low Recall metric).
- Your retrieval step has a query as input and returns documents from a larger document base.
- Particularly worth a try if your data (documents or queries) come from a special domain that is very different from the typical datasets that Retrievers are trained on.
## How Does It Work?
Many embedding retrievers generalize poorly to new, unseen domains. This approach tries to tackle this problem. Given a query, the Hypothetical Document Embeddings (HyDE) first zero-shot prompts an instruction-following language model to generate a “fake” hypothetical document that captures relevant textual patterns from the initial query - in practice, this is done five times. Then, it encodes each hypothetical document into an embedding vector and averages them. The resulting, single embedding can be used to identify a neighbourhood in the document embedding space from which similar actual documents are retrieved based on vector similarity. As with any other retriever, these retrieved documents can then be used downstream in a pipeline (for example, in a Generator for RAG). Refer to the paper “[Precise Zero-Shot Dense Retrieval without Relevance Labels](https://aclanthology.org/2023.acl-long.99/)” for more details.
## How To Build It in Haystack?
First, prepare all the components that you would need:
The examples on this page use Sentence Transformers embedders that have moved to the `sentence-transformers-haystack` package. Install it to run the examples:
```shell
pip install sentence-transformers-haystack
```
```python
import os
from numpy import array, mean
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.builders import ChatPromptBuilder
from haystack import component, Document
from haystack.components.converters import OutputAdapter
from haystack_integrations.components.embedders.sentence_transformers import (
SentenceTransformersDocumentEmbedder,
)
from haystack.dataclasses import ChatMessage
# We need to ensure we have the OpenAI API key in our environment variables
os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_KEY"
# Initializing standard Haystack components
generator = OpenAIChatGenerator(
model="gpt-4o-mini",
generation_kwargs={"n": 5, "temperature": 0.75, "max_tokens": 400},
)
prompt_builder = ChatPromptBuilder(
template=[
ChatMessage.from_user(
"""Given a question, generate a paragraph of text that answers the question. Question: {{question}} Paragraph:""",
),
],
required_variables="*",
)
# The ChatGenerator returns ChatMessage replies, so we read each reply's text.
# unsafe=True lets the adapter return actual Document objects instead of a string.
adapter = OutputAdapter(
template="{{answers | build_doc}}",
output_type=list[Document],
custom_filters={"build_doc": lambda data: [Document(content=d.text) for d in data]},
unsafe=True,
)
embedder = SentenceTransformersDocumentEmbedder(
model="sentence-transformers/all-MiniLM-L6-v2",
)
# Adding one custom component that returns one, "average" embedding from multiple (hypothetical) document embeddings
@component
class HypotheticalDocumentEmbedder:
@component.output_types(hypothetical_embedding=list[float])
def run(self, documents: list[Document]):
stacked_embeddings = array([doc.embedding for doc in documents])
avg_embeddings = mean(stacked_embeddings, axis=0)
hyde_vector = avg_embeddings.reshape((1, len(avg_embeddings)))
return {"hypothetical_embedding": hyde_vector[0].tolist()}
```
Then, assemble them all into a pipeline:
```python
from haystack import Pipeline
pipeline = Pipeline()
pipeline.add_component(name="prompt_builder", instance=prompt_builder)
pipeline.add_component(name="generator", instance=generator)
pipeline.add_component(name="adapter", instance=adapter)
pipeline.add_component(name="embedder", instance=embedder)
pipeline.add_component(name="hyde", instance=HypotheticalDocumentEmbedder())
pipeline.connect("prompt_builder.prompt", "generator.messages")
pipeline.connect("generator.replies", "adapter.answers")
pipeline.connect("adapter.output", "embedder.documents")
pipeline.connect("embedder.documents", "hyde.documents")
query = "What should I do if I have a fever?"
result = pipeline.run(data={"prompt_builder": {"question": query}})
# 'hypothetical_embedding': [0.0990725576877594, -0.017647066991776227, 0.05918873250484467, ...]}
```
Here's the graph of the resulting pipeline:
This pipeline example turns your query into one embedding.
You can continue and feed this embedding to any [Embedding Retriever](../../pipeline-components/retrievers.mdx#dense-embedding-based-retrievers) to find similar documents in your Document Store.
## Additional References
📚 Article: [Optimizing Retrieval with HyDE](https://haystack.deepset.ai/blog/optimizing-retrieval-with-hyde)
🧑🍳 Cookbook: [Using Hypothetical Document Embedding (HyDE) to Improve Retrieval](https://haystack.deepset.ai/cookbook/using_hyde_for_improved_retrieval)
---
// File: optimization/advanced-rag-techniques
# Advanced RAG Techniques
This section of documentation talks about advanced RAG techniques you can implement with Haystack.
Read more about [Hypothetical Document Embeddings (HyDE)](advanced-rag-techniques/hypothetical-document-embeddings-hyde.mdx),
or check out one of our cookbooks 🧑🍳:
- [Using Hypothetical Document Embedding (HyDE) to Improve Retrieval](https://haystack.deepset.ai/cookbook/using_hyde_for_improved_retrieval)
- [Query Decomposition and Reasoning](https://haystack.deepset.ai/cookbook/query_decomposition)
- [Improving Retrieval by Embedding Meaningful Metadata](https://haystack.deepset.ai/cookbook/improve-retrieval-by-embedding-metadata)
- [Query Expansion](https://haystack.deepset.ai/cookbook/query-expansion)
- [Automated Structured Metadata Enrichment](https://haystack.deepset.ai/cookbook/metadata_enrichment)
- [Auto-Merging and Hierarchical Document Retrieval](https://haystack.deepset.ai/cookbook/auto_merging_retriever)
---
// File: optimization/evaluation/model-based-evaluation
# Model-Based Evaluation
Haystack supports various kinds of model-based evaluation. This page explains what model-based evaluation is and discusses the various options available with Haystack.
## What is Model-Based Evaluation
Model-based evaluation in Haystack uses a language model to check the results of a Pipeline. This method is easy to use because it usually doesn't need labels for the outputs. It's often used with Retrieval-Augmented Generative (RAG) Pipelines, but can work with any Pipeline.
Currently, Haystack supports the end-to-end, model-based evaluation of a complete RAG Pipeline.
### Using LLMs for Evaluation
A common strategy for model-based evaluation involves using a Language Model (LLM), such as OpenAI's GPT models, as the evaluator model, often referred to as the _golden_ model. By default, Haystack's LLM-based Evaluators use an `OpenAIChatGenerator` as the golden model. We utilize this model to evaluate a RAG Pipeline by providing it with the Pipeline's results and sometimes additional information, along with a prompt that outlines the evaluation criteria.
This method of using an LLM as the evaluator is very flexible as it exposes a number of metrics to you. Each of these metrics is ultimately a well-crafted prompt describing to the LLM how to evaluate and score results. Common metrics are faithfulness, context relevance, and so on.
### Using Local LLMs
To use the model-based Evaluators with a local model, pass a Chat Generator pointed at your local model through the `chat_generator` parameter when initializing the Evaluator. The Chat Generator must be configured to return a JSON object.
The following example uses [Ollama](https://ollama.com/) through the [`OllamaChatGenerator`](../../pipeline-components/generators/ollamachatgenerator.mdx).
[Download and install Ollama](https://ollama.com/download), then pull the model you want to evaluate with. Ollama serves it on `http://localhost:11434` by default, which is where `OllamaChatGenerator` looks:
```shell
ollama pull qwen3:1.7b
```
Then install the integration:
```shell
pip install ollama-haystack
```
`OllamaChatGenerator` takes a `response_format` parameter, so setting it to `"json"` is all you need to satisfy the Evaluator's JSON requirement:
```python
from haystack.components.evaluators import FaithfulnessEvaluator
from haystack_integrations.components.generators.ollama import OllamaChatGenerator
questions = ["Who created the Python language?"]
contexts = [
[
(
"Python, created by Guido van Rossum in the late 1980s, is a high-level general-purpose programming "
"language. Its design philosophy emphasizes code readability, and its language constructs aim to help "
"programmers write clear, logical code for both small and large-scale software projects."
),
],
]
predicted_answers = [
"Python is a high-level general-purpose programming language that was created by George Lucas.",
]
evaluator = FaithfulnessEvaluator(
chat_generator=OllamaChatGenerator(model="qwen3:1.7b", response_format="json"),
)
result = evaluator.run(
questions=questions,
contexts=contexts,
predicted_answers=predicted_answers,
)
print(result["score"])
print(result["results"][0]["statement_scores"])
```
```text
0.5
[1, 0]
```
The Evaluator splits the answer into two statements, and only the first one is supported by the context, so the answer scores 0.5.
### Using Small Cross-Encoder Models for Evaluation
Alongside LLMs for evaluation, we can also use small cross-encoder models. These models can calculate, for example, semantic answer similarity. In contrast to metrics based on LLMs, the metrics based on smaller models don’t require an API key of a model provider.
This method of using small cross-encoder models as evaluators is faster and cheaper to run but is less flexible in terms of what aspect you can evaluate. You can only evaluate what the small model was trained to evaluate.
## Model-Based Evaluation Pipelines in Haystack
There are two ways of performing model-based evaluation in Haystack, both of which leverage [Pipelines](../../concepts/pipelines.mdx) and [Evaluator](../../pipeline-components/evaluators.mdx) components.
- You can create and run an evaluation Pipeline independently. This means you’ll have to provide the required inputs to the evaluation Pipeline manually. We recommend this way because the separation of your RAG Pipeline and your evaluation Pipeline allows you to store the results of your RAG Pipeline and try out different evaluation metrics afterward without needing to re-run your RAG Pipeline every time.
- As another option, you can add an evaluator component to the end of a RAG Pipeline. This means you run both a RAG Pipeline and evaluation on top of it in a single `pipeline.run()` call.
### Model-based Evaluation of Retrieved Documents
#### [ContextRelevanceEvaluator](../../pipeline-components/evaluators/contextrelevanceevaluator.mdx)
Context relevance refers to how relevant the retrieved documents are to the query. An LLM is used to judge that aspect. It first extracts the statements from the documents that are relevant for answering the query, then scores each question 1 if at least one relevant statement was found and 0 otherwise.
### Model-based Evaluation of Generated or Extracted Answers
#### [FaithfulnessEvaluator](../../pipeline-components/evaluators/faithfulnessevaluator.mdx)
Faithfulness, also called groundedness, evaluates to what extent a generated answer is based on retrieved documents. An LLM is used to extract statements from the answer and check the faithfulness for each separately. If the answer is not based on the documents, the answer, or at least parts of it, is called a hallucination.
#### [SASEvaluator](../../pipeline-components/evaluators/sasevaluator.mdx) (Semantic Answer Similarity)
Semantic answer similarity uses a transformer-based model (either a bi-encoder or a cross-encoder, depending on the `model` you pass) to evaluate the semantic similarity of two answers rather than their lexical overlap. While F1 and EM would both score _one hundred percent_ as sharing zero similarity with _100 %_, SAS is trained to assign a high score to such cases. SAS is particularly useful for seeking out cases where F1 doesn't give a good indication of the validity of a predicted answer. You can read more about SAS in [Semantic Answer Similarity for Evaluating Question-Answering Models paper](https://arxiv.org/abs/2108.06130).
### Evaluation Framework Integrations
Currently, Haystack has integrations with [DeepEval](https://docs.confident-ai.com/docs/metrics-introduction) and [Ragas](https://docs.ragas.io/en/stable/index.html). There is an Evaluator component available for each of these frameworks:
- [RagasEvaluator](../../pipeline-components/evaluators/ragasevaluator.mdx)
- [DeepEvalEvaluator](../../pipeline-components/evaluators/deepevalevaluator.mdx)
| | | |
| --- | --- | --- |
| Feature/Integration | RagasEvaluator | DeepEvalEvaluator |
| Evaluator Models | Any provider supported by Ragas (OpenAI, Anthropic, Google, Groq, Mistral, and more), configured on each metric with `ragas.llms.llm_factory` | All GPT models from OpenAI |
| Supported metrics | Any metric from `ragas.metrics.collections`, for example `Faithfulness`, `AnswerRelevancy`, `ContextPrecision`, `ContextRecall`, `AnswerCorrectness`, `SemanticSimilarity` | ANSWER_RELEVANCY, FAITHFULNESS, CONTEXTUAL_PRECISION, CONTEXTUAL_RECALL, CONTEXTUAL_RELEVANCE |
| Customizable prompt for response evaluation | ✅, with the rubric-based metrics such as `DomainSpecificRubrics` | ❌ |
| Explanations of scores | ❌ | ✅ |
| Monitoring dashboard | ❌ | ❌ |
:::info[Framework Documentation]
You can find more information about the metrics in the documentation of the respective evaluation frameworks:
- Ragas metrics: https://docs.ragas.io/en/latest/concepts/metrics/index.html
- DeepEval metrics: https://docs.confident-ai.com/docs/metrics-introduction
:::
## Additional References
:notebook: Tutorial: [Evaluating RAG Pipelines](https://haystack.deepset.ai/tutorials/35_evaluating_rag_pipelines)
---
// File: optimization/evaluation/statistical-evaluation
# Statistical Evaluation
Haystack supports various statistical evaluation metrics. This page explains what statistical evaluation is and discusses the various options available within Haystack.
## Introduction
Statistical evaluation in Haystack compares ground truth labels with pipeline predictions, typically using metrics such as precision or recall. It's often used to evaluate the Retriever component within Retrieval-Augmented Generative (RAG) pipelines, but this methodology can be adapted for any pipeline if ground truth labels of relevant documents are available.
When evaluating answers, such as those predicted by an extractive question answering pipeline, the ground truth labels of expected answers are compared to the pipeline's predictions.
For assessing answers generated by LLMs with one of Haystack’s Generator components, we recommend model-based evaluation instead. It can incorporate measures of semantic similarity or coherence and is better suited to evaluate predictions that might differ in wording from the ground truth labels.
## Statistical Evaluation Pipelines in Haystack
There are two ways of performing statistical evaluation in Haystack, both of which leverage [pipelines](../../concepts/pipelines.mdx) and [Evaluator](../../pipeline-components/evaluators.mdx) components:
- You can create and run an evaluation pipeline independently. This means you’ll have to provide the required inputs to the evaluation pipeline manually. We recommend this way because the separation of your RAG pipeline and your evaluation pipeline allows you to store the results of your RAG pipeline and try out different evaluation metrics afterward without needing to re-run your pipeline every time.
- As another option, you can add an Evaluator to the end of a RAG pipeline. This means you run both a RAG pipeline and evaluation on top of it in a single `pipeline.run()` call.
## Statistical Evaluation of Retrieved Documents
### [DocumentRecallEvaluator](../../pipeline-components/evaluators/documentrecallevaluator.mdx)
Recall measures how often the correct document was among the retrieved documents over a set of queries. For a single query, the output is binary: either the correct document is contained in the selection, or it is not. Over the entire dataset, the recall score amounts to a number between zero (no query retrieved the right document) and one (all queries retrieved the right documents).
In some scenarios, there can be multiple correct documents for one query. Use the evaluator's `mode` parameter to choose between two behaviors: `single_hit` (the default) considers whether at least one of the correct documents is retrieved, whereas `multi_hit` takes into account how many of the multiple correct documents for one query are retrieved.
Note that recall is affected by the number of documents that the Retriever returns. If the Retriever returns few documents, it means that it is difficult to retrieve the correct documents. Make sure to set the Retriever's `top_k` to an appropriate value in the pipeline that you're evaluating.
### [DocumentMRREvaluator](../../pipeline-components/evaluators/documentmrrevaluator.mdx) (Mean Reciprocal Rank)
In contrast to the recall metric, mean reciprocal rank takes the position of the top correctly retrieved document (the “rank”) into account. It does this to account for the fact that a query elicits multiple responses of varying relevance. Like recall, MRR can be a value between zero (no matches) and one (the system retrieved a correct document for all queries as the top result). For more details, check out [Mean Reciprocal Rank wiki page](https://en.wikipedia.org/wiki/Mean_reciprocal_rank).
### [DocumentMAPEvaluator](../../pipeline-components/evaluators/documentmapevaluator.mdx) (Mean Average Precision)
Mean average precision is similar to mean reciprocal rank but takes into account the position of every correctly retrieved document. Like MRR, mAP can be a value between zero (no matches) and one (the system retrieved correct documents for all top results). mAP is particularly useful in cases where there is more than one correct answer to be retrieved. For more details, check out [Mean Average Precision wiki page](https://en.wikipedia.org/wiki/Evaluation_measures_(information_retrieval)#Mean_average_precision).
## Statistical Evaluation of Extracted or Generated Answers
### [AnswerExactMatchEvaluator](../../pipeline-components/evaluators/answerexactmatchevaluator.mdx)
Exact match measures the proportion of cases where the predicted Answer is identical to the correct Answer. For example, for the annotated question-answer pair “What is Haystack?" + "A question answering library in Python”, even a predicted answer like “A Python question answering library” would yield a zero score because it does not match the expected answer 100%.
---
// File: optimization/evaluation
# Evaluation
Learn all about pipeline or component evaluation in Haystack.
Haystack has all the tools needed to evaluate entire pipelines or individual components like Retrievers, Readers, or Generators. This guide explains how to evaluate your pipeline in different scenarios and how to understand the metrics.
Use evaluation and its results to:
- Judge how well your system is performing on a given domain,
- Compare the performance of different models,
- Identify underperforming components in your pipeline.
## Evaluation Options
**Evaluating individual components or end-to-end pipelines.**
Evaluating individual components can help understand performance bottlenecks and optimize one component at a time, for example, a Retriever or a prompt used with a Generator.
End-to-end evaluation checks how the full pipeline is used and evaluates only the final outputs. The pipeline is approached as a black box.
**Using ground-truth labels or no labels at all.**
Most statistical evaluators require ground truth labels, such as the documents relevant to the query or the expected answer. In contrast, most model-based evaluators work without any labels just by following the prompt instructions. However, few-shot labels included in the prompt can improve the evaluator.
**Model-based evaluation using a language model or statistical evaluation.**
Model-based evaluation uses LLMs with prompt instructions or smaller fine-tuned models to score aspects of a pipeline’s outputs. Statistical evaluation requires no models and is thus a more lightweight way to score pipeline outputs. For more information, see our docs on [model-based](evaluation/model-based-evaluation.mdx) evaluation and [statistical](evaluation/statistical-evaluation.mdx) evaluation.
## Evaluator Components
| | | | |
| --- | --- | --- | --- |
| Evaluator | Evaluates Answers or Documents | Model-based or Statistical | Requires Labels |
| [AnswerExactMatchEvaluator](../pipeline-components/evaluators/answerexactmatchevaluator.mdx) | Answers | Statistical | Yes |
| [ContextRelevanceEvaluator](../pipeline-components/evaluators/contextrelevanceevaluator.mdx) | Documents | Model-based | No |
| [DocumentMRREvaluator](../pipeline-components/evaluators/documentmrrevaluator.mdx) | Documents | Statistical | Yes |
| [DocumentMAPEvaluator](../pipeline-components/evaluators/documentmapevaluator.mdx) | Documents | Statistical | Yes |
| [DocumentNDCGEvaluator](../pipeline-components/evaluators/documentndcgevaluator.mdx) | Documents | Statistical | Yes |
| [DocumentRecallEvaluator](../pipeline-components/evaluators/documentrecallevaluator.mdx) | Documents | Statistical | Yes |
| [FaithfulnessEvaluator](../pipeline-components/evaluators/faithfulnessevaluator.mdx) | Answers | Model-based | No |
| [LLMEvaluator](../pipeline-components/evaluators/llmevaluator.mdx) | User-defined | Model-based | No |
| [SASEvaluator](../pipeline-components/evaluators/sasevaluator.mdx) | Answers | Model-based | Yes |
## Evaluator Integrations
To learn more about our integration with the Ragas and DeepEval evaluation frameworks, head over to the [RagasEvaluator](../pipeline-components/evaluators/ragasevaluator.mdx) and [DeepEvalEvaluator](../pipeline-components/evaluators/deepevalevaluator.mdx) component docs.
To get started using practical examples, check out our evaluation tutorial or the respective cookbooks below.
## Additional References
:notebook: Tutorial: [Evaluating RAG Pipelines](https://haystack.deepset.ai/tutorials/35_evaluating_rag_pipelines)
🧑🍳 Cookbooks:
- [RAG Evaluation with Prometheus 2](https://haystack.deepset.ai/cookbook/prometheus2_evaluation)
- [RAG Pipeline Evaluation Using Ragas](https://haystack.deepset.ai/cookbook/rag_eval_ragas)
- [RAG Pipeline Evaluation Using DeepEval](https://haystack.deepset.ai/cookbook/rag_eval_deep_eval)
---
// File: overview/breaking-change-policy
# Breaking Change Policy
This document outlines the breaking change policy for Haystack, including the definition of breaking changes, versioning conventions, and the deprecation process for existing features.
Haystack is under active development, which means that functionalities are being added, deprecated, or removed rather frequently. This policy aims to minimize the impact of these changes on current users and deployments. It provides a clear schedule and outlines the necessary steps before upgrading to a new Haystack version.
## Breaking Change Definition
A breaking change occurs when:
- A Component is removed, renamed, or the Python import path is changed.
- A parameter is renamed, removed, or changed from optional to mandatory.
- A new mandatory parameter is added.
Existing deployments might break, and the change is deemed a _breaking change_. The decision to declare a change as breaking has nothing to do with its potential impact: while the change might only impact a tiny subset of applications using a specific Haystack feature, it would still be treated as a breaking change.
The following cases are **not** considered a breaking change:
- A new functionality is added (for example, a new Component).
- A component, class, or utility function gets a new optional parameter.
- An existing parameter gets changed from mandatory to optional.
Existing deployments are not impacted, and the change is deemed non-breaking. Release notes will mention the change and possibly provide an upgrade path, but upgrading Haystack won’t break existing applications.
## Versioning
Haystack releases are labeled with a series of three numbers separated by dots, for example, `2.0.1`. Each number has a specific meaning:
- `2` is the Major version
- `0` is the Minor version
- `1` is the Patch version
:::info
Albeit similar, Haystack DOES NOT follow the principles of [Semantic Versioning](https://semver.org). Read on to see the differences.
:::
Given a Haystack release with a version number of type `MAJOR.MINOR.PATCH`, you should expect:
1. **For Major version change:** fundamental, incompatible API changes. In this case, you would most likely need a migration process before being able to update Haystack. Major releases happen no more than once a year, changes are extensively documented, and a migration path is provided.
2. **For Minor version change:** addition or removal of functionalities that might not be backward compatible. Most of the time, you will be able to upgrade your Haystack installation seamlessly, but always refer to the [release notes](https://github.com/deepset-ai/haystack/releases) for guidance. Deprecated components are the most common breaking change shipped in a Minor version release.
3. **For Patch version change:** bugfixes. You can safely upgrade Haystack to the new version without concerns that your program will break.
## Deprecation of Existing Features
Haystack strives for robustness. To achieve this, we clean up our code by removing old features that are no longer used. This helps us maintain the codebase, improve security, and make it easier to keep everything running smoothly. Before we remove a feature, component, class, or utility function, we go through a process called deprecation.
A Major or Minor (but not Patch) version may deprecate certain features from previous releases, and this is what you should expect:
- If a feature is deprecated in Haystack version `X.Y`, it will continue to work but the Python code will raise warnings detailing the steps to take in order to upgrade.
- Features deprecated in Haystack version `X.Y` will be removed in Haystack `X.Y+1`, giving affected users a timeframe of roughly a month to prepare the upgrade.
### Example
To clarify the process, here’s an example:
At some point, we decide to remove a `FooComponent` and declare it deprecated in Haystack version `2.99.0`. This is what will happen:
1. `FooComponent` keeps working as usual In Haystack `2.99.0`, but using the component raises a `FutureWarning` message in the code.
2. In Haystack version `2.100.0`, we remove the `FooComponent` from the codebase. Trying to use it produces an error.
## Discontinuing an Integration
When existing features are changed or removed, integrations go through the same deprecation process as detailed on this page for Haystack. It’s important to note that integrations are independent and distributed with their own packages. In certain cases, a special form of deprecation may occur where the integration is discontinued and subsequently removed from the Core Integrations repository.
To give our community the opportunity to take over the integration and keep it maintained before being discontinued Core Integrations gradually go through different states, as detailed below:
- **Staged**
- The source code of the integration is moved from `main` to a special `staging` branch of the Core Integrations repository.
- The documentation pages are removed from the Haystack documentation website.
- The main README of the Core Integrations repository shows a disclaimer explaining how the integration can be adopted from the community.
- The integration tile is removed (it can be re-added later by the maintainer who adopted the integration).
- The integration package on PyPI remains available.
- A grace period of 3 months starts.
- **Adopted**
- An organization or an individual from the community accepts to take over the ownership of the Staged integration.
- The adopter creates their own repository, and the source code of the discontinued integration is removed from the `staging` branch.
- Ownership of the PyPI package is transferred to the new maintainer.
- The adopter will create a new integration tile in [haystack-integrations](https://github.com/deepset-ai/haystack-integrations).
- **Discontinued**
- If the grace period expires and nobody adopts the Staged Integration, its source code is removed from the `staging` branch.
- The PyPI package of the integration won’t be removed but won’t be further updated.
---
// File: overview/docs-mcp-server
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# Using Haystack Docs in Your Coding Agent
Haystack publishes a public [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that lets coding agents search the official Haystack documentation. Pointing your agent at it means it answers questions from up-to-date docs instead of relying on training data, which can lag behind the framework.
The server exposes a single tool, `search_haystack_docs`, that returns relevant documentation sections with source URLs. No API key or sign-up is needed.
## Server URL
```
https://docs.haystack.deepset.ai/api/mcp
```
The server speaks **HTTP** transport. Most agents auto-detect this. If yours asks you to choose, pick `http`.
## Setup
Add the server with the `claude mcp` CLI:
```shell
claude mcp add --transport http haystack-docs https://docs.haystack.deepset.ai/api/mcp
```
Restart your Claude Code session. Verify it's connected by running `/mcp` — you should see `haystack-docs` listed with the `search_haystack_docs` tool.
For more options (project vs. user scope, SSE transport, headers), see the [Claude Code MCP docs](https://code.claude.com/docs/en/mcp).
Open Cursor settings → **Tools & MCPs** → **Add new MCP server**, or edit `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per-project) directly:
```json
{
"mcpServers": {
"haystack-docs": {
"url": "https://docs.haystack.deepset.ai/api/mcp"
}
}
}
```
Save the file and reload Cursor. The tool appears in the **Available Tools** list inside the chat panel.
See the [Cursor MCP docs](https://cursor.com/docs/context/mcp) for the full configuration reference.
In VS Code, create or edit `.vscode/mcp.json` in your workspace:
```json
{
"servers": {
"haystack-docs": {
"type": "http",
"url": "https://docs.haystack.deepset.ai/api/mcp"
}
}
}
```
Open the Copilot Chat panel, switch to **Agent** mode, then click the tools icon and enable `haystack-docs`. You can also register the server globally from the command palette via **MCP: Add Server**.
See [Add and manage MCP servers in VS Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) for the full configuration reference.
## Verifying it works
Ask your agent a question that requires current Haystack knowledge, for example:
> What are the required methods on a Haystack custom component?
The agent should call `search_haystack_docs` and cite source URLs under `docs.haystack.deepset.ai` in its answer. If it answers without calling the tool, prompt it explicitly: *"Use the haystack-docs MCP server to answer."*
---
// File: overview/faq
# FAQ
Here are the answers to the questions people frequently ask about Haystack.
### How can I make sure that my GPU is being engaged when I use Haystack?
You will want to ensure that a CUDA enabled GPU is being engaged when Haystack is running (you can check by running `nvidia-smi -l` on your command line). Components which can be sped up by GPU have a `device` argument in their constructor. For more details, check the [Device Management](../concepts/device-management.mdx) page.
### Are you tracking my Haystack usage?
We only collect _anonymous_ usage statistics of Haystack pipeline components. Read more about telemetry in Haystack or how you can opt out on the [Telemetry](telemetry.mdx) page.
### How can I ask my questions around Haystack?
For general questions, we recommend joining the [Haystack Discord ](https://discord.com/invite/xYvH6drSmA)or using [GitHub discussions](https://github.com/deepset-ai/haystack/discussions), where the community and maintainers can help. You can also explore [tutorials](https://haystack.deepset.ai/tutorials/40_building_chat_application_with_function_calling) and [examples](https://haystack.deepset.ai/cookbook/tools_support) on website to find more info.
### How can I get expert support for Haystack?
If you’re a team running Haystack in production or want to move faster and scale with confidence, we recommend [Haystack Enterprise Starter](https://haystack.deepset.ai/blog/announcing-haystack-enterprise). It gives you direct access to the Haystack team, proven best practices, and hands-on support to help you go from prototype to production smoothly.
👉 [Get in touch with our team to explore Haystack Enterprise Starter](https://www.deepset.ai/products-and-services/haystack-enterprise)
### Where can I find documentation for older Haystack versions?
The website only hosts documentation for the 5 most recent Haystack versions.
For older versions (up to 2.18), you can access the documentation on GitHub: https://github.com/deepset-ai/haystack/tree/main/docs-website/versioned_docs.
### Where can I find tutorials and documentation for Haystack 1.x?
You can access old tutorials in the [GitHub history](https://github.com/deepset-ai/haystack-tutorials/tree/5917718cbfbb61410aab4121ee6fe754040a5dc7) and download the Haystack 1.x documentation as a [ZIP file](https://core-engineering.s3.eu-central-1.amazonaws.com/public/docs/haystack-v1-docs.zip).
The ZIP file contains documentation for all minor releases from version 1.0 to 1.26.
To download documentation for a specific release, replace the version number in the following URL: `https://core-engineering.s3.eu-central-1.amazonaws.com/public/docs/v1.26.zip`.
Learn how to migrate to Haystack 3.x with our [migration guide](migration.mdx).
---
// File: overview/get-started
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# Get Started
Have a look at this page to learn how to quickly get up and running with Haystack. It contains instructions for installing Haystack, building your first RAG pipeline, and creating a tool-calling Agent.
## Build your first RAG application
Let's build your first Retrieval Augmented Generation (RAG) pipeline and see how Haystack answers questions.
First, install the minimal form of Haystack:
```shell
pip install haystack-ai
```
In the examples below, we show how to set an API key using a Haystack [Secret](../concepts/secret-management.mdx). Choose your preferred LLM provider from the tabs below. For easier use, you can also set the API key as an environment variable.
[OpenAIChatGenerator](../pipeline-components/generators/openaichatgenerator.mdx) is included in the `haystack-ai` package.
```python
from haystack import Pipeline, Document
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.retrievers import InMemoryBM25Retriever
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.builders import ChatPromptBuilder
from haystack.utils import Secret
from haystack.dataclasses import ChatMessage
document_store = InMemoryDocumentStore()
document_store.write_documents(
[
Document(content="My name is Jean and I live in Paris."),
Document(content="My name is Mark and I live in Berlin."),
Document(content="My name is Giorgio and I live in Rome."),
],
)
prompt_template = [
ChatMessage.from_system(
"""
Given these documents, answer the question.
Documents:
{% for doc in documents %}
{{ doc.content }}
{% endfor %}
""",
),
ChatMessage.from_user("{{question}}"),
]
retriever = InMemoryBM25Retriever(document_store=document_store)
prompt_builder = ChatPromptBuilder(template=prompt_template, required_variables="*")
llm = OpenAIChatGenerator(
api_key=Secret.from_env_var("OPENAI_API_KEY"),
model="gpt-4o-mini",
)
rag_pipeline = Pipeline()
rag_pipeline.add_component("retriever", retriever)
rag_pipeline.add_component("prompt_builder", prompt_builder)
rag_pipeline.add_component("llm", llm)
rag_pipeline.connect("retriever", "prompt_builder.documents")
rag_pipeline.connect("prompt_builder", "llm")
question = "Who lives in Paris?"
results = rag_pipeline.run(
{
"retriever": {"query": question},
"prompt_builder": {"question": question},
},
)
print(results["llm"]["replies"])
```
[HuggingFaceAPIChatGenerator](../pipeline-components/generators/huggingfaceapichatgenerator.mdx) is included in the `huggingface-api-haystack` package. You can get a [free Hugging Face token](https://huggingface.co/settings/tokens) to use the Serverless Inference API.
```shell
pip install huggingface-api-haystack
```
```python
from haystack import Pipeline, Document
from haystack_integrations.components.generators.huggingface_api import (
HuggingFaceAPIChatGenerator,
)
from haystack.components.retrievers import InMemoryBM25Retriever
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.builders import ChatPromptBuilder
from haystack.utils import Secret
from haystack.dataclasses import ChatMessage
document_store = InMemoryDocumentStore()
document_store.write_documents(
[
Document(content="My name is Jean and I live in Paris."),
Document(content="My name is Mark and I live in Berlin."),
Document(content="My name is Giorgio and I live in Rome."),
],
)
prompt_template = [
ChatMessage.from_system(
"""
Given these documents, answer the question.
Documents:
{% for doc in documents %}
{{ doc.content }}
{% endfor %}
""",
),
ChatMessage.from_user("{{question}}"),
]
retriever = InMemoryBM25Retriever(document_store=document_store)
prompt_builder = ChatPromptBuilder(template=prompt_template, required_variables="*")
llm = HuggingFaceAPIChatGenerator(
api_type="serverless_inference_api",
api_params={"model": "Qwen/Qwen2.5-72B-Instruct"},
token=Secret.from_env_var("HF_API_TOKEN"),
)
rag_pipeline = Pipeline()
rag_pipeline.add_component("retriever", retriever)
rag_pipeline.add_component("prompt_builder", prompt_builder)
rag_pipeline.add_component("llm", llm)
rag_pipeline.connect("retriever", "prompt_builder.documents")
rag_pipeline.connect("prompt_builder", "llm")
question = "Who lives in Paris?"
results = rag_pipeline.run(
{
"retriever": {"query": question},
"prompt_builder": {"question": question},
},
)
print(results["llm"]["replies"])
```
Install the [Anthropic integration](https://haystack.deepset.ai/integrations/anthropic):
```bash
pip install anthropic-haystack
```
See the [AnthropicChatGenerator](../pipeline-components/generators/anthropicchatgenerator.mdx) docs for more details.
```python
from haystack import Pipeline, Document
from haystack_integrations.components.generators.anthropic import AnthropicChatGenerator
from haystack.components.retrievers import InMemoryBM25Retriever
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.builders import ChatPromptBuilder
from haystack.utils import Secret
from haystack.dataclasses import ChatMessage
document_store = InMemoryDocumentStore()
document_store.write_documents(
[
Document(content="My name is Jean and I live in Paris."),
Document(content="My name is Mark and I live in Berlin."),
Document(content="My name is Giorgio and I live in Rome."),
],
)
prompt_template = [
ChatMessage.from_system(
"""
Given these documents, answer the question.
Documents:
{% for doc in documents %}
{{ doc.content }}
{% endfor %}
""",
),
ChatMessage.from_user("{{question}}"),
]
retriever = InMemoryBM25Retriever(document_store=document_store)
prompt_builder = ChatPromptBuilder(template=prompt_template, required_variables="*")
llm = AnthropicChatGenerator(
api_key=Secret.from_env_var("ANTHROPIC_API_KEY"),
model="claude-sonnet-4-5-20250929",
)
rag_pipeline = Pipeline()
rag_pipeline.add_component("retriever", retriever)
rag_pipeline.add_component("prompt_builder", prompt_builder)
rag_pipeline.add_component("llm", llm)
rag_pipeline.connect("retriever", "prompt_builder.documents")
rag_pipeline.connect("prompt_builder", "llm")
question = "Who lives in Paris?"
results = rag_pipeline.run(
{
"retriever": {"query": question},
"prompt_builder": {"question": question},
},
)
print(results["llm"]["replies"])
```
Install the [Amazon Bedrock integration](https://haystack.deepset.ai/integrations/amazon-bedrock):
```bash
pip install amazon-bedrock-haystack
```
See the [AmazonBedrockChatGenerator](../pipeline-components/generators/amazonbedrockchatgenerator.mdx) docs for more details.
```python
import os
from haystack import Pipeline, Document
from haystack_integrations.components.generators.amazon_bedrock import (
AmazonBedrockChatGenerator,
)
from haystack.components.retrievers import InMemoryBM25Retriever
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.builders import ChatPromptBuilder
from haystack.dataclasses import ChatMessage
os.environ["AWS_ACCESS_KEY_ID"] = "YOUR_AWS_ACCESS_KEY_ID"
os.environ["AWS_SECRET_ACCESS_KEY"] = "YOUR_AWS_SECRET_ACCESS_KEY"
os.environ["AWS_DEFAULT_REGION"] = "YOUR_AWS_REGION"
document_store = InMemoryDocumentStore()
document_store.write_documents(
[
Document(content="My name is Jean and I live in Paris."),
Document(content="My name is Mark and I live in Berlin."),
Document(content="My name is Giorgio and I live in Rome."),
],
)
prompt_template = [
ChatMessage.from_system(
"""
Given these documents, answer the question.
Documents:
{% for doc in documents %}
{{ doc.content }}
{% endfor %}
""",
),
ChatMessage.from_user("{{question}}"),
]
retriever = InMemoryBM25Retriever(document_store=document_store)
prompt_builder = ChatPromptBuilder(template=prompt_template, required_variables="*")
llm = AmazonBedrockChatGenerator(model="anthropic.claude-3-5-sonnet-20240620-v1:0")
rag_pipeline = Pipeline()
rag_pipeline.add_component("retriever", retriever)
rag_pipeline.add_component("prompt_builder", prompt_builder)
rag_pipeline.add_component("llm", llm)
rag_pipeline.connect("retriever", "prompt_builder.documents")
rag_pipeline.connect("prompt_builder", "llm")
question = "Who lives in Paris?"
results = rag_pipeline.run(
{
"retriever": {"query": question},
"prompt_builder": {"question": question},
},
)
print(results["llm"]["replies"])
```
Install the [Google Gen AI integration](https://haystack.deepset.ai/integrations/google-genai):
```bash
pip install google-genai-haystack
```
See the [GoogleGenAIChatGenerator](../pipeline-components/generators/googlegenaichatgenerator.mdx) docs for more details.
```python
from haystack import Pipeline, Document
from haystack_integrations.components.generators.google_genai import (
GoogleGenAIChatGenerator,
)
from haystack.components.retrievers import InMemoryBM25Retriever
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.builders import ChatPromptBuilder
from haystack.utils import Secret
from haystack.dataclasses import ChatMessage
document_store = InMemoryDocumentStore()
document_store.write_documents(
[
Document(content="My name is Jean and I live in Paris."),
Document(content="My name is Mark and I live in Berlin."),
Document(content="My name is Giorgio and I live in Rome."),
],
)
prompt_template = [
ChatMessage.from_system(
"""
Given these documents, answer the question.
Documents:
{% for doc in documents %}
{{ doc.content }}
{% endfor %}
""",
),
ChatMessage.from_user("{{question}}"),
]
retriever = InMemoryBM25Retriever(document_store=document_store)
prompt_builder = ChatPromptBuilder(template=prompt_template, required_variables="*")
llm = GoogleGenAIChatGenerator(
api_key=Secret.from_env_var("GOOGLE_API_KEY"),
model="gemini-2.5-flash",
)
rag_pipeline = Pipeline()
rag_pipeline.add_component("retriever", retriever)
rag_pipeline.add_component("prompt_builder", prompt_builder)
rag_pipeline.add_component("llm", llm)
rag_pipeline.connect("retriever", "prompt_builder.documents")
rag_pipeline.connect("prompt_builder", "llm")
question = "Who lives in Paris?"
results = rag_pipeline.run(
{
"retriever": {"query": question},
"prompt_builder": {"question": question},
},
)
print(results["llm"]["replies"])
```
Haystack supports many more model providers including **Cohere**, **Mistral**, **NVIDIA**, **Ollama**, and others—both cloud-hosted and local options.
Browse the full list of supported models and chat generators in the [Generators documentation](../pipeline-components/generators.mdx).
You can also explore all available integrations on the [Haystack Integrations](https://haystack.deepset.ai/integrations) page.
### Next Steps
Ready to dive deeper? Check out the [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline) tutorial for a step-by-step guide on building a complete RAG pipeline with your own data.
## Build your first Agent
Agents are AI systems that can use tools to gather information, perform actions, and interact with external systems. Let's build an agent that can search the web to answer questions.
All the examples below use `SerperDevWebSearch`, which lives in the `serperdev-haystack` package:
```shell
pip install serperdev-haystack
```
They also require a [SerperDev API key](https://serper.dev/) for web search. Set it as the `SERPERDEV_API_KEY` environment variable.
[OpenAIChatGenerator](../pipeline-components/generators/openaichatgenerator.mdx) is included in the `haystack-ai` package.
```python
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.tools import ComponentTool
from haystack_integrations.components.websearch.serperdev import SerperDevWebSearch
from haystack.utils import Secret
search_tool = ComponentTool(component=SerperDevWebSearch())
agent = Agent(
chat_generator=OpenAIChatGenerator(
api_key=Secret.from_env_var("OPENAI_API_KEY"),
model="gpt-4o-mini",
),
tools=[search_tool],
system_prompt="You are a helpful assistant that can search the web for information.",
)
result = agent.run(messages=[ChatMessage.from_user("What is Haystack AI?")])
print(result["last_message"].text)
```
[HuggingFaceAPIChatGenerator](../pipeline-components/generators/huggingfaceapichatgenerator.mdx) is included in the `huggingface-api-haystack` package. You can get a [free Hugging Face token](https://huggingface.co/settings/tokens) to use the Serverless Inference API.
```python
from haystack.components.agents import Agent
from haystack_integrations.components.generators.huggingface_api import (
HuggingFaceAPIChatGenerator,
)
from haystack.dataclasses import ChatMessage
from haystack.tools import ComponentTool
from haystack_integrations.components.websearch.serperdev import SerperDevWebSearch
from haystack.utils import Secret
search_tool = ComponentTool(component=SerperDevWebSearch())
agent = Agent(
chat_generator=HuggingFaceAPIChatGenerator(
api_type="serverless_inference_api",
api_params={"model": "Qwen/Qwen2.5-72B-Instruct"},
token=Secret.from_env_var("HF_API_TOKEN"),
),
tools=[search_tool],
system_prompt="You are a helpful assistant that can search the web for information.",
)
result = agent.run(messages=[ChatMessage.from_user("What is Haystack AI?")])
print(result["last_message"].text)
```
Install the [Anthropic integration](https://haystack.deepset.ai/integrations/anthropic):
```bash
pip install anthropic-haystack
```
See the [AnthropicChatGenerator](../pipeline-components/generators/anthropicchatgenerator.mdx) docs for more details.
```python
from haystack.components.agents import Agent
from haystack_integrations.components.generators.anthropic import AnthropicChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.tools import ComponentTool
from haystack_integrations.components.websearch.serperdev import SerperDevWebSearch
from haystack.utils import Secret
search_tool = ComponentTool(component=SerperDevWebSearch())
agent = Agent(
chat_generator=AnthropicChatGenerator(
api_key=Secret.from_env_var("ANTHROPIC_API_KEY"),
model="claude-sonnet-4-5-20250929",
),
tools=[search_tool],
system_prompt="You are a helpful assistant that can search the web for information.",
)
result = agent.run(messages=[ChatMessage.from_user("What is Haystack AI?")])
print(result["last_message"].text)
```
Install the [Amazon Bedrock integration](https://haystack.deepset.ai/integrations/amazon-bedrock):
```bash
pip install amazon-bedrock-haystack
```
See the [AmazonBedrockChatGenerator](../pipeline-components/generators/amazonbedrockchatgenerator.mdx) docs for more details.
```python
import os
from haystack.components.agents import Agent
from haystack_integrations.components.generators.amazon_bedrock import (
AmazonBedrockChatGenerator,
)
from haystack.dataclasses import ChatMessage
from haystack.tools import ComponentTool
from haystack_integrations.components.websearch.serperdev import SerperDevWebSearch
os.environ["AWS_ACCESS_KEY_ID"] = "YOUR_AWS_ACCESS_KEY_ID"
os.environ["AWS_SECRET_ACCESS_KEY"] = "YOUR_AWS_SECRET_ACCESS_KEY"
os.environ["AWS_DEFAULT_REGION"] = "YOUR_AWS_REGION"
search_tool = ComponentTool(component=SerperDevWebSearch())
agent = Agent(
chat_generator=AmazonBedrockChatGenerator(
model="anthropic.claude-3-5-sonnet-20240620-v1:0",
),
tools=[search_tool],
system_prompt="You are a helpful assistant that can search the web for information.",
)
result = agent.run(messages=[ChatMessage.from_user("What is Haystack AI?")])
print(result["last_message"].text)
```
Install the [Google Gen AI integration](https://haystack.deepset.ai/integrations/google-genai):
```bash
pip install google-genai-haystack
```
See the [GoogleGenAIChatGenerator](../pipeline-components/generators/googlegenaichatgenerator.mdx) docs for more details.
```python
from haystack.components.agents import Agent
from haystack_integrations.components.generators.google_genai import (
GoogleGenAIChatGenerator,
)
from haystack.dataclasses import ChatMessage
from haystack.tools import ComponentTool
from haystack_integrations.components.websearch.serperdev import SerperDevWebSearch
from haystack.utils import Secret
search_tool = ComponentTool(component=SerperDevWebSearch())
agent = Agent(
chat_generator=GoogleGenAIChatGenerator(
api_key=Secret.from_env_var("GOOGLE_API_KEY"),
model="gemini-2.5-flash",
),
tools=[search_tool],
system_prompt="You are a helpful assistant that can search the web for information.",
)
result = agent.run(messages=[ChatMessage.from_user("What is Haystack AI?")])
print(result["last_message"].text)
```
Haystack supports many more model providers including **Cohere**, **Mistral**, **NVIDIA**, **Ollama**, and others—both cloud-hosted and local options.
Browse the full list of supported models and chat generators in the [Generators documentation](../pipeline-components/generators.mdx).
You can also explore all available integrations on the [Haystack Integrations](https://haystack.deepset.ai/integrations) page.
### Next Steps
For a hands-on guide on creating a tool-calling agent that can use both components and pipelines as tools, check out the [Build a Tool-Calling Agent](https://haystack.deepset.ai/tutorials/43_building_a_tool_calling_agent) tutorial.
---
// File: overview/installation
# Installation
See how to quickly install Haystack with pip, uv, or conda.
## Package Installation
Use [pip](https://github.com/pypa/pip) to install the [Haystack PyPI package](https://pypi.org/project/haystack-ai/):
```shell
pip install haystack-ai
```
Alternatively, you can use [uv](https://docs.astral.sh/uv/) to install Haystack:
```shell
uv pip install haystack-ai
```
Or add it as a dependency to your project:
```shell
uv add haystack-ai
```
You can also use [conda](https://docs.conda.io/projects/conda/en/stable/) to install the [Haystack conda package](https://anaconda.org/conda-forge/haystack-ai):
```shell
conda install conda-forge::haystack-ai
```
### Optional Dependencies
Some components in Haystack rely on additional optional dependencies.
To keep the installation lightweight, these are not included by default – only the essentials are installed.
If you use a feature that requires an optional dependency that hasn't been installed, Haystack will raise an error that instructs you to install missing dependencies, for example:
```shell
ImportError: "Haystack failed to import the optional dependency 'pypdf'. Run 'pip install pypdf'.
```
## Contributing to Haystack
If you would like to contribute to the Haystack, check our [Contributor Guidelines](https://github.com/deepset-ai/haystack/blob/main/CONTRIBUTING.md) first
and follow the instructions [here](https://github.com/deepset-ai/haystack/blob/main/CONTRIBUTING.md#setting-up-your-development-environment) to set up your development environment.
---
// File: overview/migrating-from-langgraphlangchain-to-haystack
import CodeBlock from '@theme/CodeBlock';
# Migrating from LangGraph/LangChain to Haystack
Whether you're planning to migrate to Haystack or just comparing **LangChain/LangGraph** and **Haystack** to choose the proper framework for your AI application, this guide will help you map common patterns between frameworks.
In this guide, you'll learn how to translate core LangGraph concepts, like nodes, edges, and state, into Haystack components, pipelines, and agents. The goal is to preserve your existing logic while leveraging Haystack's flexible, modular ecosystem.
It's most accurate to think of Haystack as covering both **LangChain** and **LangGraph** territory: Haystack provides the building blocks for everything from simple sequential flows to fully agentic workflows with custom logic.
## Why you might explore or migrate to Haystack
You might consider Haystack if you want to build your AI applications on a **stable, actively maintained foundation** with an intuitive developer experience.
* **Unified orchestration framework.** Haystack supports both deterministic pipelines and adaptive agentic flows, letting you combine them with the right level of autonomy in a single system.
* **High-quality codebase and design.** Haystack is engineered for clarity and reliability with well-tested components, predictable APIs, and a modular architecture that simply works.
* **Ease of customization.** Extend core components, add your own logic, or integrate custom tools with minimal friction.
* **Reduced cognitive overhead.** Haystack extends familiar ideas rather than introducing new abstractions, helping you stay focused on applying concepts, not learning them.
* **Comprehensive documentation and learning resources.** Every concept, from components and pipelines to agents and tools, is supported by detailed and well-maintained docs, tutorials, and educational content.
* **Frequent release cycles.** New features, improvements, and bug fixes are shipped regularly, ensuring that the framework evolves quickly while maintaining backward compatibility.
* **Scalable from prototype to production.** Start small and expand easily. The same code you use for a proof of concept can power enterprise-grade deployments through the whole Haystack ecosystem.
## Concept mapping: LangGraph/LangChain → Haystack
Here's a table of key concepts and their approximate equivalents between the two frameworks. Use this when auditing your LangGraph/Langchain architecture and planning the migration.
| LangGraph/LangChain concept | Haystack equivalent | Notes |
| --- | --- | --- |
| Node | Component | A unit of logic in both frameworks. In Haystack, a [Component](../concepts/components.mdx) can run standalone, in a pipeline, or as a tool with agent. You can [create custom components](../concepts/components/custom-components.mdx) or use built-in ones like Generators and Retrievers. |
| Edge / routing logic | Connection / Branching / Looping | [Pipelines](../concepts/pipelines.mdx) connect component inputs and outputs with type-checked links. They support branching, routing, and loops for flexible flow control. |
| Graph / Workflow (nodes + edges) | Pipeline or Agent | LangGraph explicitly defines graphs; Haystack achieves similar orchestration through pipelines or [Agents](../concepts/agents.mdx) when adaptive logic is needed. |
| Subgraphs | SuperComponent | A [SuperComponent](../concepts/components/supercomponents.mdx) wraps a full pipeline and exposes it as a single reusable component |
| Models / LLMs | ChatGenerator Components | Haystack's [ChatGenerators](../pipeline-components/generators.mdx) unify access to open and proprietary models, with support for streaming, structured outputs, and multimodal data. |
| Agent Creation (`create_agent`, multi-agent from LangChain) | Agent Component | Haystack provides a simple, pipeline-based [Agent](../concepts/agents.mdx) abstraction that handles reasoning, tool use, and multi-step execution. |
| Tool (Langchain) | [Tool](../tools/tool.mdx) / [PipelineTool](../tools/pipelinetool.mdx) / [ComponentTool](../tools/componenttool.mdx) / [AgentTool](../tools/agenttool.mdx) / [MCPTool](../tools/mcptool.mdx) | Haystack exposes Python functions, pipelines, components, external APIs and MCP servers as agent tools. |
| Multi-Agent Collaboration (LangChain) | Multi-Agent System | Using [`AgentTool`](../tools/agenttool.mdx), agents can use other agents as tools, enabling [multi-agent architectures](https://haystack.deepset.ai/tutorials/45_creating_a_multi_agent_system) within one framework. |
| Model Context Protocol `load_mcp_tools` `MultiServerMCPClient` | Model Context Protocol - `MCPTool`, `MCPToolset`, `StdioServerInfo`, `StreamableHttpServerInfo` | Haystack provides [various MCP primitives](https://haystack.deepset.ai/integrations/mcp) for connecting multiple MCP servers and organizing MCP toolsets. |
| Memory (State, short-term, long-term) | Memory (Agent State, short-term, long-term) | Agent [State](../pipeline-components/agents-1/state.mdx) provides a structured way to share data between tools and store intermediate results during agent execution. For long-term memory, Haystack offers memory stores such as [Mem0MemoryStore](../memory-stores/mem0memorystore.mdx) and [CogneeMemoryStore](../memory-stores/cogneememorystore.mdx) to persist conversation history across sessions. |
| Time travel (Checkpoints) | Breakpoints (Breakpoint, PipelineSnapshot) | [Breakpoints](../concepts/pipelines/pipeline-breakpoints.mdx) let you pause, inspect, modify, and resume a pipeline for debugging or iterative development. |
| Human-in-the-Loop (Interrupts / Commands) | Human-in-the-loop (`ConfirmationHook` with confirmation strategies) | Haystack applies [confirmation strategies](https://haystack.deepset.ai/tutorials/47_human_in_the_loop_agent) through a `ConfirmationHook` registered under the Agent's `before_tool` [hook point](../pipeline-components/agents-1/hooks.mdx) to pause or block the execution to gather user feedback |
## Ecosystem and Tooling Mapping: LangChain → Haystack
At deepset, we're building the tools to make LLMs truly usable in production, open source and beyond.
* [Haystack, AI Orchestration Framework](https://github.com/deepset-ai/haystack) → Open Source AI framework for building production-ready, AI-powered agents and applications, on your own or with community support.
* [Haystack Enterprise Starter](https://www.deepset.ai/products-and-services/haystack-enterprise) → Private and secure engineering support, advanced pipeline templates, deployment guides, and early access features for teams needing more support and guidance.
* [Haystack Enterprise Platform](https://www.deepset.ai/products-and-services/deepset-ai-platform) → An enterprise-ready platform for teams running Gen AI apps in production, with security, governance, and scalability built in with [a free version](https://www.deepset.ai/deepset-studio).
Here's the product equivalent of two ecosystems:
| **LangChain Ecosystem** | **Haystack Ecosystem** | **Notes** |
| --- | --- | --- |
| **LangChain, LangGraph, Deep Agents** | **Haystack** | **Core AI orchestration framework for components, pipelines, and agents**. Supports deterministic workflows and agentic execution with explicit, modular building blocks. |
| **LangSmith (Observability)** | **Haystack Enterprise Platform** | **Integrated tooling for building, debugging and iterating.** Assemble agents and pipelines visually with the **Builder**, which includes component validation, testing and debugging. The **Prompt Explorer** is used to iterate and evaluate models and prompts. Built-in chat interfaces to enable fast SME and stakeholder feedback. Collaborative building environment for engineers and business. |
| **LangSmith (Deployment)** | **Hayhooks** **Haystack Enterprise Starter** (deployment guides + advanced best practice templates) **Haystack Enterprise Platform** (1-click deployment, on-prem/VPC options) | Multiple deployment paths: lightweight API exposure via [Hayhooks](https://github.com/deepset-ai/hayhooks), structured enterprise deployment patterns through Haystack Enterprise Starter, and full managed or self-hosted deployment through the Haystack Enterprise Platform. |
## Code Comparison
### Agentic Flows with Haystack vs LangGraph
Here's an example **graph-based agent** with access to a list of tools, comparing the LangGraph and Haystack APIs.
**Step 1: Define tools**
Both frameworks use a `@tool` decorator to expose Python functions as tools the LLM can call. The function signature and docstring define the tool's interface, which the LLM uses to understand when and how to invoke each tool.
{`# pip install haystack-ai anthropic-haystack
from haystack.tools import tool
# Define tools
@tool
def multiply(a: int, b: int) -> int:
"""Multiply \`a\` and \`b\`.
Args:
a: First int
b: Second int
"""
return a * b
@tool
def add(a: int, b: int) -> int:
"""Adds \`a\` and \`b\`.
Args:
a: First int
b: Second int
"""
return a + b
@tool
def divide(a: int, b: int) -> float:
"""Divide \`a\` and \`b\`.
Args:
a: First int
b: Second int
"""
return a / b`}
{`# pip install langchain-anthropic langgraph langchain
from langchain.tools import tool
# Define tools
@tool
def multiply(a: int, b: int) -> int:
"""Multiply \`a\` and \`b\`.
Args:
a: First int
b: Second int
"""
return a * b
@tool
def add(a: int, b: int) -> int:
"""Adds \`a\` and \`b\`.
Args:
a: First int
b: Second int
"""
return a + b
@tool
def divide(a: int, b: int) -> float:
"""Divide \`a\` and \`b\`.
Args:
a: First int
b: Second int
"""
return a / b`}
**Step 2: Initialize the LLM**
The frameworks connect tools to the LLM differently. In Haystack, you only initialize the `ChatGenerator` component here: the tools are passed to the `Agent` in Step 3, which forwards them to the LLM. In LangGraph, you first initialize the model, then bind tools using `.bind_tools()` to create a tool-enabled LLM instance.
{`from haystack_integrations.components.generators.anthropic import AnthropicChatGenerator
# Initialize the LLM; the tools are passed to the Agent in Step 3
tools = [add, multiply, divide]
model = AnthropicChatGenerator(
model="claude-sonnet-4-5-20250929",
generation_kwargs={"temperature": 0},
)`}
{`from langchain.chat_models import init_chat_model
# Augment the LLM with tools
model = init_chat_model(
"claude-sonnet-4-5-20250929",
temperature=0,
)
tools = [add, multiply, divide]
tools_by_name = {tool.name: tool for tool in tools}
llm_with_tools = model.bind_tools(tools)`}
**Step 3: Assemble the agent**
This is where the frameworks diverge most. In Haystack, you create the `Agent` component from the chat generator and tools - the agentic loop is built in. The Agent accumulates the conversation (LLM replies and tool results) internally, executes the tool calls prepared by the LLM, and iterates until an exit condition is met. The default `exit_conditions=["text"]` stops the loop as soon as the LLM replies without tool calls; tool names can also be used to exit after a specific tool runs.
In LangGraph, you build the loop explicitly: a node function (`llm_call`) that invokes the LLM on the accumulated `MessagesState`, a node function (`tool_node`) that executes tool calls and wraps the results in `ToolMessage` objects, and a conditional edge function (`should_continue`) that decides whether to continue the loop or finish. You then wire nodes and edges together in a `StateGraph` and compile the graph into an executable agent.
{`from haystack.components.agents import Agent
# Create the agent - the agentic loop (LLM calls,
# tool execution, iteration) is built in
agent = Agent(
chat_generator=model,
tools=tools,
system_prompt="You are a helpful assistant tasked with performing arithmetic on a set of inputs.",
exit_conditions=["text"], # default
)`}
{`from typing import Literal
from langgraph.graph import MessagesState, StateGraph, START, END
from langchain.messages import SystemMessage, ToolMessage
# Node: the LLM decides whether to call a tool or not
def llm_call(state: MessagesState):
return {
"messages": [
llm_with_tools.invoke(
[
SystemMessage(
content="You are a helpful assistant tasked with performing arithmetic on a set of inputs."
)
]
+ state["messages"]
)
]
}
# Node: performs the tool calls
def tool_node(state: dict):
result = []
for tool_call in state["messages"][-1].tool_calls:
tool = tools_by_name[tool_call["name"]]
observation = tool.invoke(tool_call["args"])
result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"]))
return {"messages": result}
# Conditional edge: route to the tool node or end
# based upon whether the LLM made a tool call
def should_continue(state: MessagesState) -> Literal["tool_node", END]:
last_message = state["messages"][-1]
if last_message.tool_calls:
return "tool_node"
return END
# Build workflow
agent_builder = StateGraph(MessagesState)
# Add nodes
agent_builder.add_node("llm_call", llm_call)
agent_builder.add_node("tool_node", tool_node)
# Add edges to connect nodes
agent_builder.add_edge(START, "llm_call")
agent_builder.add_conditional_edges(
"llm_call",
should_continue,
["tool_node", END]
)
agent_builder.add_edge("tool_node", "llm_call")
# Compile the agent
agent = agent_builder.compile()`}
**Step 4: Run the agent**
Finally, we execute the agent with a user message. Haystack calls `.run()` on the Agent with initial messages, while LangGraph calls `.invoke()` on the compiled agent. Both return the conversation history.
{`from haystack.dataclasses import ChatMessage
# Run the agent
result = agent.run(messages=[
ChatMessage.from_user(text="Add 3 and 4.")
])
print(result["last_message"].text)`}
{`from langchain.messages import HumanMessage
# Invoke
messages = [
HumanMessage(content="Add 3 and 4.")
]
messages = agent.invoke({"messages": messages})
for m in messages["messages"]:
m.pretty_print()`}
### Creating Agents
The [Agentic Flows](#agentic-flows-with-haystack-vs-langgraph) walkthrough above stepped through the agent loop piece by piece. In Haystack, the high-level `Agent` class wraps the full loop - LLM calls, tool invocation, and iteration - into a single component. LangGraph offers an equivalent shortcut through `create_react_agent` in `langgraph.prebuilt`. Both produce a ReAct-style agent that handles tool calling and multi-step reasoning automatically. Here are the complete examples side by side:
{`# pip install haystack-ai anthropic-haystack
from haystack.components.agents import Agent
from haystack_integrations.components.generators.anthropic import AnthropicChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.tools import tool
@tool
def multiply(a: int, b: int) -> int:
"""Multiply \`a\` and \`b\`."""
return a * b
@tool
def add(a: int, b: int) -> int:
"""Add \`a\` and \`b\`."""
return a + b
# Create an agent - the agentic loop is handled automatically
agent = Agent(
chat_generator=AnthropicChatGenerator(
model="claude-sonnet-4-5-20250929",
generation_kwargs={"temperature": 0},
),
tools=[multiply, add],
system_prompt="You are a helpful assistant that performs arithmetic.",
)
result = agent.run(messages=[
ChatMessage.from_user("What is 3 multiplied by 7, then add 5?")
])
print(result["messages"][-1].text) # or print(result["last_message"].text)`}
{`# pip install langchain-anthropic langgraph
from langchain_anthropic import ChatAnthropic
from langchain_core.tools import tool
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage, SystemMessage
@tool
def multiply(a: int, b: int) -> int:
"""Multiply \`a\` and \`b\`."""
return a * b
@tool
def add(a: int, b: int) -> int:
"""Add \`a\` and \`b\`."""
return a + b
# Create an agent - the agentic loop is handled automatically
model = ChatAnthropic(
model="claude-sonnet-4-5-20250929",
temperature=0,
)
agent = create_agent(
model,
tools=[multiply, add],
system_prompt=SystemMessage(
content="You are a helpful assistant that performs arithmetic."
),
)
result = agent.invoke({
"messages": [HumanMessage(content="What is 3 multiplied by 7, then add 5?")]
})
print(result["messages"][-1].content)`}
### Connecting to Document Stores
Document stores are the foundation of retrieval-augmented generation (RAG). In Haystack, document stores integrate natively with pipeline components like Retrievers and Prompt Builders via explicit typed connections. LangChain centers retrieval around its vector store abstraction composed using LCEL (LangChain Expression Language).
Both frameworks offer in-memory stores for prototyping and a wide range of production backends (Elasticsearch, Qdrant, Weaviate, Pinecone, and more) via integrations.
**Step 1: Create a document store and add documents**
{`# pip install haystack-ai sentence-transformers-haystack
from haystack import Document
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack_integrations.components.embedders.sentence_transformers import SentenceTransformersDocumentEmbedder
# Embed and write documents to the document store
document_store = InMemoryDocumentStore()
doc_embedder = SentenceTransformersDocumentEmbedder(
model="sentence-transformers/all-MiniLM-L6-v2"
)
docs = [
Document(content="Paris is the capital of France."),
Document(content="Berlin is the capital of Germany."),
Document(content="Tokyo is the capital of Japan."),
]
docs_with_embeddings = doc_embedder.run(docs)["documents"]
document_store.write_documents(docs_with_embeddings)`}
{`# pip install langchain-community langchain-huggingface sentence-transformers
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import InMemoryVectorStore
from langchain_core.documents import Document
# Embed and add documents to the vector store
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2"
)
vectorstore = InMemoryVectorStore(embedding=embeddings)
vectorstore.add_documents([
Document(page_content="Paris is the capital of France."),
Document(page_content="Berlin is the capital of Germany."),
Document(page_content="Tokyo is the capital of Japan."),
])`}
**Step 2: Build a RAG pipeline**
{`from haystack import Pipeline
from haystack_integrations.components.embedders.sentence_transformers import SentenceTransformersTextEmbedder
from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever
from haystack.components.builders import ChatPromptBuilder
from haystack.dataclasses import ChatMessage
from haystack_integrations.components.generators.anthropic import AnthropicChatGenerator
# ChatPromptBuilder expects a list[ChatMessage] as template
template = [ChatMessage.from_user("""
Given the following documents, answer the question.
{% for doc in documents %}{{ doc.content }}{% endfor %}
Question: {{ question }}
""")]
rag_pipeline = Pipeline()
rag_pipeline.add_component(
"text_embedder",
SentenceTransformersTextEmbedder(model="sentence-transformers/all-MiniLM-L6-v2")
)
rag_pipeline.add_component(
"retriever", InMemoryEmbeddingRetriever(document_store=document_store)
)
rag_pipeline.add_component(
"prompt_builder", ChatPromptBuilder(template=template)
)
rag_pipeline.add_component(
"llm", AnthropicChatGenerator(model="claude-sonnet-4-5-20250929")
)
rag_pipeline.connect("text_embedder.embedding", "retriever.query_embedding")
rag_pipeline.connect("retriever.documents", "prompt_builder.documents")
rag_pipeline.connect("prompt_builder.prompt", "llm.messages")
result = rag_pipeline.run({
"text_embedder": {"text": "What is the capital of France?"},
"prompt_builder": {"question": "What is the capital of France?"},
})
print(result["llm"]["replies"][0].text)`}
{`from langchain_anthropic import ChatAnthropic
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
def format_docs(docs):
return "\\n".join(doc.page_content for doc in docs)
retriever = vectorstore.as_retriever()
model = ChatAnthropic(model="claude-sonnet-4-5-20250929")
template = """
Given the following documents, answer the question.
{context}
Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
result = rag_chain.invoke("What is the capital of France?")
print(result)`}
### Using MCP Tools
Both frameworks support the [Model Context Protocol (MCP)](https://modelcontextprotocol.io), letting agents connect to external tools and services exposed by MCP servers. Haystack provides [`MCPTool`](https://docs.haystack.deepset.ai/docs/mcptool) and [`MCPToolset`](https://docs.haystack.deepset.ai/docs/mcptoolset) through the `mcp-haystack` integration package, which plug directly into the `Agent` component. LangChain's MCP support relies on the separate `langchain-mcp-adapters` package and requires an async workflow throughout.
{`# pip install haystack-ai mcp-haystack anthropic-haystack
from haystack_integrations.tools.mcp import MCPToolset, StdioServerInfo
from haystack.components.agents import Agent
from haystack_integrations.components.generators.anthropic import AnthropicChatGenerator
from haystack.dataclasses import ChatMessage
# Connect to an MCP server - tools are auto-discovered
toolset = MCPToolset(
server_info=StdioServerInfo(
command="uvx",
args=["mcp-server-fetch"],
)
)
agent = Agent(
chat_generator=AnthropicChatGenerator(model="claude-sonnet-4-5-20250929"),
tools=toolset,
system_prompt="You are a helpful assistant that can fetch web content.",
)
result = agent.run(messages=[
ChatMessage.from_user("Fetch the content from https://haystack.deepset.ai")
])
print(result["messages"][-1].text) # or print(result["last_message"].text)`}
{`# pip install langchain-mcp-adapters langgraph langchain-anthropic
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage, SystemMessage
model = ChatAnthropic(model="claude-sonnet-4-5-20250929")
async def run():
client = MultiServerMCPClient(
{
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"],
"transport": "stdio",
}
}
)
tools = await client.get_tools()
agent = create_agent(
model,
tools,
system_prompt=SystemMessage(
content="You are a helpful assistant that can fetch web content."
),
)
result = await agent.ainvoke(
{
"messages": [
HumanMessage(content="Fetch the content from https://haystack.deepset.ai")
]
}
)
print(result["messages"][-1].content)
asyncio.run(run())`}
## Hear from Haystack Users
See how teams across industries use Haystack to power their production AI systems, from RAG applications to agentic workflows.
> "_Haystack allows its users a production ready, easy to use framework that covers just about all of your needs, and allows you to write integrations easily for those it doesn't._"
> **- Josh Longenecker, GenAI Specialist at AWS**
>
> _"Haystack's design philosophy significantly accelerates development and improves the robustness of AI applications, especially when heading towards production. The emphasis on explicit, modular components truly pays off in the long run."_
> **- Rima Hajou, Data & AI Technical Lead at Accenture**
### Featured Stories
* [TELUS Agriculture & Consumer Goods Built an Agentic Chatbot with Haystack to Transform Trade Promotions Workflows](https://haystack.deepset.ai/blog/telus-user-story)
* [Lufthansa Industry Solutions Uses Haystack to Power Enterprise RAG](https://haystack.deepset.ai/blog/lufthansa-user-story)
## Start Building with Haystack
**👉 Thinking about migrating or evaluating Haystack?** Jump right in with the [Haystack Get Started guide](https://haystack.deepset.ai/overview/quick-start) or [contact our team](https://www.deepset.ai/products-and-services/haystack-enterprise-starter), we'd love to support you.
---
// File: overview/migration
# Migration Guide
Learn how to make the move to Haystack 3.x from Haystack 2.x.
This guide is designed for those with previous experience with Haystack 2.x who want to upgrade to Haystack 3.x. It walks through every breaking change and shows how to adapt your code. If you're new to Haystack, skip this page and proceed directly to the [Get Started](get-started.mdx) guide.
Haystack 3.x is an evolution of Haystack 2.x, not a rewrite: components, pipelines, and the `Agent` work as before. Most applications only need import updates and small, mechanical changes. The complete list of breaking changes with extended examples is maintained in [MIGRATION.md](https://github.com/deepset-ai/haystack/blob/main/MIGRATION.md) in the Haystack repository.
:::tip[Migrate with a coding agent]
Want help migrating with a coding agent? [Fill out this form](https://landing.deepset.ai/haystack-v3-migration-skill) to get access to the Haystack v3 migration skill.
:::
## Update Your Installation
The package name is unchanged:
```bash
pip install --upgrade haystack-ai
```
Two dependency changes to be aware of:
- **`haystack-experimental` is no longer installed automatically.** The package is now archived and unmaintained: `0.19.0.post1` is its final release. If your code still imports from `haystack_experimental`, install it explicitly and pin it with `pip install "haystack-experimental==0.19.0.post1"`. Most experiments graduated into `haystack-ai` itself, so prefer migrating your imports to the core equivalents.
- **Several components moved to dedicated integration packages** and now require an extra `pip install`. See [Components Moved to Integration Packages](#components-moved-to-integration-packages) below.
## Removed and Renamed Components
### Legacy Generators removed
`OpenAIGenerator`, `AzureOpenAIGenerator`, `HuggingFaceAPIGenerator`, and `HuggingFaceLocalGenerator` have been removed. Their chat counterparts are the replacement: `OpenAIChatGenerator` and `AzureOpenAIChatGenerator` in Haystack core, `HuggingFaceAPIChatGenerator` in the `huggingface-api-haystack` integration, and `TransformersChatGenerator` (the renamed `HuggingFaceLocalChatGenerator`) in the `transformers-haystack` integration (see [Components Moved to Integration Packages](#components-moved-to-integration-packages)). All [ChatGenerators](../pipeline-components/generators.mdx) now also accept a plain `str` as input, so simple text-in/text-out use cases rarely require structural changes.
Before (v2.x):
```python
from haystack.components.generators import OpenAIGenerator
gen = OpenAIGenerator()
result = gen.run("What is NLP?")
text = result["replies"][0] # str
meta = result["meta"][0] # dict with model metadata
```
After (v3.0):
```python
from haystack.components.generators.chat import OpenAIChatGenerator
gen = OpenAIChatGenerator()
result = gen.run("What is NLP?") # str input accepted directly
reply = result["replies"][0] # ChatMessage
text = reply.text # str
meta = reply.meta # dict with model metadata (now on the message)
```
Pipelines that connected a `PromptBuilder` (output: `str`) to a legacy Generator keep working unchanged when you swap in a ChatGenerator: the pipeline type system automatically converts `str` to `list[ChatMessage]` at the connection edge. Two follow-up points:
- The legacy Generators' separate `meta` output socket is gone. Remove any `pipeline.connect("llm.meta", ...)` calls; per-reply metadata now lives in each `ChatMessage.meta`, and `AnswerBuilder` reads it from there automatically.
- To set a system prompt, prepend `ChatMessage.from_system(...)` to the messages instead of using the removed `system_prompt` init parameter.
### `ToolInvoker` removed
Tool execution is now owned by the [`Agent`](../pipeline-components/agents-1/agent.mdx) component. Instead of wiring a ChatGenerator to a `ToolInvoker`, pass the tools to an `Agent`: it forwards the tool definitions to the chat generator, executes requested tool calls, appends tool results to the conversation, and loops until an exit condition is reached.
Before (v2.x):
```python
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.tools import ToolInvoker
from haystack.dataclasses import ChatMessage
chat_generator = OpenAIChatGenerator(tools=[weather])
tool_invoker = ToolInvoker(tools=[weather])
llm_result = chat_generator.run(
messages=[ChatMessage.from_user("What is the weather in Berlin?")],
)
tool_result = tool_invoker.run(messages=llm_result["replies"])
```
After (v3.0):
```python
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
agent = Agent(chat_generator=OpenAIChatGenerator(), tools=[weather])
result = agent.run(messages=[ChatMessage.from_user("What is the weather in Berlin?")])
```
The `tool_invoker_kwargs` parameter is gone from `Agent`; the relevant options are now top-level constructor parameters:
- `max_workers` → `tool_concurrency_limit`
- `enable_streaming_callback_passthrough` → `tool_streaming_callback_passthrough`
- `convert_result_to_json_string` has been removed: non-string tool results are now always serialized with `json.dumps`.
If you need to execute a prepared tool call outside an `Agent`, call `Tool.invoke` directly and send the result back to the model as a `ChatMessage.from_tool` message.
### Other removals and renames
| v2.x | v3.0 replacement |
| --- | --- |
| `TransformersSimilarityRanker` | `SentenceTransformersSimilarityRanker` (accepts the same parameters, adds async support) |
| `DALLEImageGenerator` | [`OpenAIImageGenerator`](../pipeline-components/generators/openaiimagegenerator.mdx) (same API; renamed after OpenAI retired the DALL-E family) |
| `AsyncPipeline` | `Pipeline` (see [Pipeline changes](#asyncpipeline-merged-into-pipeline)) |
## Components Moved to Integration Packages
Some components moved out of Haystack core into dedicated integration packages hosted in the [haystack-core-integrations](https://github.com/deepset-ai/haystack-core-integrations) repository. This keeps the core lean and lets fixes ship independently of the Haystack release cycle.
To migrate, install the new package (`pip install `) and update your imports:
| Old import (`haystack-ai<3.0.0`) | New package | New import |
|---|---|---|
| `from haystack.components.generators.chat import HuggingFaceAPIChatGenerator` | `huggingface-api-haystack` | `from haystack_integrations.components.generators.huggingface_api import HuggingFaceAPIChatGenerator` |
| `from haystack.components.embedders import HuggingFaceAPITextEmbedder` | `huggingface-api-haystack` | `from haystack_integrations.components.embedders.huggingface_api import HuggingFaceAPITextEmbedder` |
| `from haystack.components.embedders import HuggingFaceAPIDocumentEmbedder` | `huggingface-api-haystack` | `from haystack_integrations.components.embedders.huggingface_api import HuggingFaceAPIDocumentEmbedder` |
| `from haystack.components.rankers import HuggingFaceTEIRanker` | `huggingface-api-haystack` | `from haystack_integrations.components.rankers.huggingface_api import HuggingFaceTEIRanker` |
| `from haystack.components.generators.chat import HuggingFaceLocalChatGenerator` | `transformers-haystack` | `from haystack_integrations.components.generators.transformers import TransformersChatGenerator` |
| `from haystack.components.readers import ExtractiveReader` | `transformers-haystack` | `from haystack_integrations.components.readers.transformers import TransformersExtractiveReader` |
| `from haystack.components.classifiers import TransformersZeroShotDocumentClassifier` | `transformers-haystack` | `from haystack_integrations.components.classifiers.transformers import TransformersZeroShotDocumentClassifier` |
| `from haystack.components.routers import TransformersTextRouter` | `transformers-haystack` | `from haystack_integrations.components.routers.transformers import TransformersTextRouter` |
| `from haystack.components.routers import TransformersZeroShotTextRouter` | `transformers-haystack` | `from haystack_integrations.components.routers.transformers import TransformersZeroShotTextRouter` |
| `from haystack.components.extractors import NamedEntityExtractor` (Hugging Face backend) | `transformers-haystack` | `from haystack_integrations.components.extractors.transformers import TransformersNamedEntityExtractor` |
| `from haystack.components.extractors import NamedEntityExtractor` (spaCy backend) | `spacy-haystack` | `from haystack_integrations.components.extractors.spacy import SpacyNamedEntityExtractor` |
| `from haystack.components.embedders import SentenceTransformersTextEmbedder` | `sentence-transformers-haystack` | `from haystack_integrations.components.embedders.sentence_transformers import SentenceTransformersTextEmbedder` |
| `from haystack.components.embedders import SentenceTransformersDocumentEmbedder` | `sentence-transformers-haystack` | `from haystack_integrations.components.embedders.sentence_transformers import SentenceTransformersDocumentEmbedder` |
| `from haystack.components.embedders import SentenceTransformersSparseTextEmbedder` | `sentence-transformers-haystack` | `from haystack_integrations.components.embedders.sentence_transformers import SentenceTransformersSparseTextEmbedder` |
| `from haystack.components.embedders import SentenceTransformersSparseDocumentEmbedder` | `sentence-transformers-haystack` | `from haystack_integrations.components.embedders.sentence_transformers import SentenceTransformersSparseDocumentEmbedder` |
| `from haystack.components.embedders.image import SentenceTransformersDocumentImageEmbedder` | `sentence-transformers-haystack` | `from haystack_integrations.components.embedders.sentence_transformers import SentenceTransformersDocumentImageEmbedder` |
| `from haystack.components.rankers import SentenceTransformersSimilarityRanker` | `sentence-transformers-haystack` | `from haystack_integrations.components.rankers.sentence_transformers import SentenceTransformersSimilarityRanker` |
| `from haystack.components.rankers import SentenceTransformersDiversityRanker` | `sentence-transformers-haystack` | `from haystack_integrations.components.rankers.sentence_transformers import SentenceTransformersDiversityRanker` |
| `from haystack.components.websearch import SerperDevWebSearch` | `serperdev-haystack` | `from haystack_integrations.components.websearch.serperdev import SerperDevWebSearch` |
| `from haystack.components.websearch import SearchApiWebSearch` | `searchapi-haystack` | `from haystack_integrations.components.websearch.searchapi import SearchApiWebSearch` |
| `from haystack.components.classifiers import DocumentLanguageClassifier` | `langdetect-haystack` | `from haystack_integrations.components.classifiers.langdetect import DocumentLanguageClassifier` |
| `from haystack.components.routers import TextLanguageRouter` | `langdetect-haystack` | `from haystack_integrations.components.routers.langdetect import TextLanguageRouter` |
| `from haystack.components.audio import LocalWhisperTranscriber` | `whisper-haystack` | `from haystack_integrations.components.audio.whisper import LocalWhisperTranscriber` |
| `from haystack.components.audio import RemoteWhisperTranscriber` | `whisper-haystack` | `from haystack_integrations.components.audio.whisper import RemoteWhisperTranscriber` |
| `from haystack.components.converters import TikaDocumentConverter` | `tika-haystack` | `from haystack_integrations.components.converters.tika import TikaDocumentConverter` |
| `from haystack.components.converters import AzureOCRDocumentConverter` | `azure-form-recognizer-haystack` | `from haystack_integrations.components.converters.azure_form_recognizer import AzureOCRDocumentConverter` |
| `from haystack.components.connectors import OpenAPIConnector` | `openapi-haystack` | `from haystack_integrations.components.connectors.openapi import OpenAPIConnector` |
| `from haystack.components.connectors import OpenAPIServiceConnector` | `openapi-haystack` | `from haystack_integrations.components.connectors.openapi import OpenAPIServiceConnector` |
| `from haystack.components.converters import OpenAPIServiceToFunctions` | `openapi-haystack` | `from haystack_integrations.components.converters.openapi import OpenAPIServiceToFunctions` |
| `from haystack.tracing.datadog import DatadogTracer` | `datadog-haystack` | `from haystack_integrations.tracing.datadog import DatadogTracer` |
| `from haystack.tracing import OpenTelemetryTracer` | `opentelemetry-haystack` | `from haystack_integrations.tracing.opentelemetry import OpenTelemetryTracer` |
## Pipeline Changes
### `AsyncPipeline` merged into `Pipeline`
The `AsyncPipeline` class has been removed. Its asynchronous methods (`run_async`, `run_async_generator`, `stream`) are now part of the single [`Pipeline`](../concepts/pipelines.mdx) class, alongside the synchronous `run`.
Before (v2.x):
```python
from haystack import AsyncPipeline
pipeline = AsyncPipeline()
result = await pipeline.run_async(data)
```
After (v3.0):
```python
from haystack import Pipeline
pipeline = Pipeline()
result = await pipeline.run_async(data)
```
If you used the **synchronous** `AsyncPipeline.run()`, note that it wrapped the concurrent async engine, so `Pipeline.run()` is not a drop-in replacement. Choose by intent:
```python
# Keep concurrent execution from sync code:
result = asyncio.run(pipeline.run_async(data, concurrency_limit=4))
# Sequential execution is fine:
result = pipeline.run(data) # components run one at a time; no concurrency_limit
```
Keep in mind that `Pipeline.run` executes components sequentially and does not accept `concurrency_limit`; only `run_async` / `run_async_generator` run components concurrently. Only `run` supports [breakpoints](../concepts/pipelines/pipeline-breakpoints.mdx).
### Deserialization is gated by a module allowlist
`Pipeline.load`, `Pipeline.loads`, and `Pipeline.from_dict` now refuse to import classes from modules outside a trusted-module allowlist and raise a `DeserializationError` instead. The default allowlist contains `haystack`, `haystack_integrations`, `haystack_experimental`, `builtins`, `typing`, and `collections`, so pipelines that only reference Haystack's own packages keep loading without changes.
Pipelines that reference custom components, callables, or types in other packages need the extra modules explicitly allowed:
```python
from haystack import Pipeline
# 1. Per-call kwarg — recommended for application code.
with open("pipeline.yaml") as fp:
pipeline = Pipeline.load(fp, allowed_modules=["mypkg.*"])
# 2. Per-call bypass — "I fully trust this YAML; skip the allowlist".
with open("pipeline.yaml") as fp:
pipeline = Pipeline.load(fp, unsafe=True)
# 3. Process-wide — call once at startup.
from haystack.core.serialization import allow_deserialization_module
allow_deserialization_module("mypkg.*")
```
```bash
# 4. Environment variable — useful for deployments where code shouldn't change.
export HAYSTACK_DESERIALIZATION_ALLOWLIST="mypkg.*,otherpkg.*"
```
See the [Serialization](../concepts/pipelines/serialization.mdx) page for details.
## Prompt Builders: Template Variables Are Required by Default
[`PromptBuilder`](../pipeline-components/builders/promptbuilder.mdx) and [`ChatPromptBuilder`](../pipeline-components/builders/chatpromptbuilder.mdx) now treat every Jinja2 template variable as required. Previously, variables were optional by default and missing values were silently rendered as empty strings. The `required_variables` parameter's default changed from `None` (all optional) to `"*"` (all required).
```python
from haystack.components.builders import PromptBuilder
# Option 1: provide every variable (matches the new safe default).
builder = PromptBuilder(template="Hello, {{ name }}! {{ greeting }}")
builder.run(name="John", greeting="Welcome")
# Option 2: declare which variables are required; everything else stays optional.
builder = PromptBuilder(
template="Hello, {{ name }}! {{ greeting }}",
required_variables=["name"],
)
builder.run(name="John") # greeting renders as ""
# Option 3: restore the old "all optional" behavior.
builder = PromptBuilder(
template="Hello, {{ name }}! {{ greeting }}",
required_variables=None,
)
builder.run(name="John") # greeting renders as ""
```
## Agent Changes
Beyond taking over tool execution from the removed `ToolInvoker`, the [`Agent`](../pipeline-components/agents-1/agent.mdx) component has a few more breaking changes:
### Breakpoints and snapshots removed
The agent-specific breakpoint API (`AgentBreakpoint`, `ToolBreakpoint`, `AgentSnapshot`, and the `break_point` / `snapshot` / `snapshot_callback` parameters of `Agent.run`) has been removed. Pausing and resuming execution inside an Agent is no longer supported; [pipeline-level breakpoints](../concepts/pipelines/pipeline-breakpoints.mdx) still cover the common debugging use cases, and [tracing](../development/tracing.mdx) is the recommended way to inspect an Agent's behavior.
### Runtime `system_prompt` and `user_prompt` removed
`Agent.run` and `Agent.run_async` no longer accept `system_prompt` or `user_prompt`; both must be set at initialization time. If a prompt must still be assembled per run, build `ChatMessage` objects before the Agent (for example, with a `ChatPromptBuilder`) and pass them through the `messages` input — a system message at the start of `messages` acts as a runtime system prompt. The same change applies to the `LLM` component.
### Prompt template variables are required by default
`Agent` now treats every Jinja2 template variable in `user_prompt` and `system_prompt` as required, in line with the [prompt builders](#prompt-builders-template-variables-are-required-by-default): the `required_variables` parameter's default changed from `None` (all optional) to `"*"` (all required). Previously, missing variables were silently rendered as empty strings. Pass `required_variables=["var1", "var2"]` to require only a subset, or `required_variables=None` to restore the old "all optional" behavior.
### Tools must declare `inputs_from_state` to read from `State` by name
A tool now reads a value from the Agent's [`State`](../pipeline-components/agents-1/state.mdx) by name only when it declares an explicit `inputs_from_state` mapping. The old implicit behavior — any tool parameter whose name matched a `State` key was silently filled from `State` — has been removed. Add `inputs_from_state={"state_key": "parameter_name"}` to any tool that should read from `State`. Tools that take the full `State` object via a `State`-annotated parameter are unaffected.
### Reserved `state_schema` keys
`Agent` now reserves the names `step_count`, `token_usage`, `tool_call_counts`, `continue_run`, `tools`, and `hook_context` in its `state_schema` and raises a `ValueError` if you pass any of them. Rename clashing entries.
### Human-in-the-Loop confirmation is now a `before_tool` hook
The `confirmation_strategies` and `confirmation_strategy_context` parameters have been removed. Wrap your confirmation strategies in a `ConfirmationHook` registered under the `before_tool` [hook point](../pipeline-components/agents-1/hooks.mdx), and pass request-scoped resources through the generic `hook_context` run argument:
Before (v2.x):
```python
agent = Agent(
chat_generator=...,
tools=[...],
confirmation_strategies={"my_tool": BlockingConfirmationStrategy(...)},
)
agent.run(messages=[...], confirmation_strategy_context={"websocket": ws})
```
After (v3.0):
```python
from haystack.hooks.human_in_the_loop import ConfirmationHook
confirmation_hook = ConfirmationHook(
confirmation_strategies={"my_tool": BlockingConfirmationStrategy(...)},
)
agent = Agent(
chat_generator=...,
tools=[...],
hooks={"before_tool": [confirmation_hook]},
)
agent.run(messages=[...], hook_context={"websocket": ws})
```
See [Human-in-the-Loop](../pipeline-components/agents-1/human-in-the-loop.mdx) for the full walkthrough. Note also that confirmation strategies now see only the tool arguments the model produced; values injected from `State` are applied at execution time and are no longer part of what is presented for confirmation.
## Behavior Changes
### Auto-generated `Document.id` changes for documents with non-empty `meta`
The hash used to auto-generate `Document.id` is now computed from a canonical (key-sorted) JSON serialization of `meta`, so the ID no longer depends on the insertion order of `meta` keys. Documents with empty `meta` keep their v2.x IDs, but documents with non-empty `meta` get different IDs in 3.0.
If you rely on auto-generated IDs matching documents already persisted in a Document Store written by Haystack 2.x, re-ingest the affected documents, pass the previous `id` explicitly, or migrate the stored IDs in place: read the stored documents, regenerate their IDs with Haystack 3.x (`replace(doc, id="")`), write them back, and delete the entries under the old IDs. [MIGRATION.md](https://github.com/deepset-ai/haystack/blob/main/MIGRATION.md) contains a complete example script.
### Logging no longer reconfigures the whole process
Importing Haystack no longer attaches its formatting handler to the root logger or configures `structlog` process-wide; the handler is scoped to the `haystack`, `haystack_integrations`, and `haystack_experimental` namespaces. To restore the old process-wide behavior, call `configure_logging(logger_name="")`; to prevent duplicate log lines when your application also configures the root logger, call `configure_logging(propagate=False)`. See the [Logging](../development/logging.mdx) page.
### Tracing is no longer auto-enabled
Haystack no longer auto-enables Datadog or OpenTelemetry tracing when the respective SDK is installed, and both tracers moved to integration packages (`datadog-haystack`, `opentelemetry-haystack`). Enable tracing explicitly — either by adding the integration's connector component (`DatadogConnector`, `OpenTelemetryConnector`) to your pipeline or by calling `haystack.tracing.enable_tracing(...)` with the tracer. The `HAYSTACK_AUTO_TRACE_ENABLED` environment variable no longer has any effect. See [Tracing](../development/tracing.mdx).
### API keys are resolved at warm-up
Components that use external services (such as OpenAI and Azure OpenAI components) now create their API clients during `warm_up()` instead of in `__init__`. A missing API key is reported at warm-up or first run rather than at construction.
### `GeneratedAnswer` and `ExtractedAnswer` serialization
`GeneratedAnswer.to_dict()` and `ExtractedAnswer.to_dict()` now return a flat dictionary of the object's fields instead of wrapping them in a `{"type": ..., "init_parameters": {...}}` envelope, aligning them with all other Haystack dataclasses. `from_dict()` still accepts the old wrapped format, so existing serialized artifacts keep loading. Update any code that reads the serialized output to access fields at the top level instead of under `init_parameters`.
## Migrating from Haystack 1.x
Haystack 1.x (the `farm-haystack` package) reached its end of life long before 3.0. If you are still on 1.x, migrate to the `haystack-ai` package first: the previous version of this page, covering the 1.x→2.x migration, is preserved [in the repository's v2 branch](https://github.com/deepset-ai/haystack/blob/v2.31.x/docs-website/docs/overview/migration.mdx). The archived 1.x documentation is available as a [ZIP file](https://core-engineering.s3.eu-central-1.amazonaws.com/public/docs/haystack-v1-docs.zip), and old tutorials remain accessible in the [GitHub history](https://github.com/deepset-ai/haystack-tutorials/tree/5917718cbfbb61410aab4121ee6fe754040a5dc7).
---
// File: overview/platform-components
# Haystack Enterprise Components
The Haystack Enterprise Platform currently supports **246 components** and **76 integrations**. The following table lists them grouped by integration partner.
## Core Components
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [Agent](https://docs.haystack.deepset.ai/docs/agent) | Component | ✅ Available |
| [AnswerBuilder](https://docs.haystack.deepset.ai/docs/answerbuilder) | Builder | ✅ Available |
| [AnswerJoiner](https://docs.haystack.deepset.ai/docs/answerjoiner) | Joiner | ✅ Available |
| [AutoMergingRetriever](https://docs.haystack.deepset.ai/docs/automergingretriever) | Retriever | ✅ Available |
| [AzureOpenAIChatGenerator](https://docs.haystack.deepset.ai/docs/azureopenaichatgenerator) | Generator | ✅ Available |
| [AzureOpenAIDocumentEmbedder](https://docs.haystack.deepset.ai/docs/azureopenaidocumentembedder) | Embedder | ✅ Available |
| [AzureOpenAIResponsesChatGenerator](https://docs.haystack.deepset.ai/docs/azureopenairesponseschatgenerator) | Generator | ✅ Available |
| [AzureOpenAITextEmbedder](https://docs.haystack.deepset.ai/docs/azureopenaitextembedder) | Embedder | ✅ Available |
| [BranchJoiner](https://docs.haystack.deepset.ai/docs/branchjoiner) | Joiner | ✅ Available |
| [CacheChecker](https://docs.haystack.deepset.ai/docs/cachechecker) | Component | ✅ Available |
| [ChatPromptBuilder](https://docs.haystack.deepset.ai/docs/chatpromptbuilder) | Builder | ✅ Available |
| [ConditionalRouter](https://docs.haystack.deepset.ai/docs/conditionalrouter) | Router | ✅ Available |
| [CSVDocumentCleaner](https://docs.haystack.deepset.ai/docs/csvdocumentcleaner) | Preprocessor | ✅ Available |
| [CSVDocumentSplitter](https://docs.haystack.deepset.ai/docs/csvdocumentsplitter) | Preprocessor | ✅ Available |
| [CSVToDocument](https://docs.haystack.deepset.ai/docs/csvtodocument) | Converter | ✅ Available |
| [DocumentCleaner](https://docs.haystack.deepset.ai/docs/documentcleaner) | Preprocessor | ✅ Available |
| [DocumentJoiner](https://docs.haystack.deepset.ai/docs/documentjoiner) | Joiner | ✅ Available |
| [DocumentLengthRouter](https://docs.haystack.deepset.ai/docs/documentlengthrouter) | Router | ✅ Available |
| [DocumentPreprocessor](https://docs.haystack.deepset.ai/docs/documentpreprocessor) | Preprocessor | ✅ Available |
| [DocumentSplitter](https://docs.haystack.deepset.ai/docs/documentsplitter) | Preprocessor | ✅ Available |
| [DocumentToImageContent](https://docs.haystack.deepset.ai/docs/documenttoimagecontent) | Converter | ✅ Available |
| [DocumentTypeRouter](https://docs.haystack.deepset.ai/docs/documenttyperouter) | Router | ✅ Available |
| [DocumentWriter](https://docs.haystack.deepset.ai/docs/documentwriter) | Writer | ✅ Available |
| [DOCXToDocument](https://docs.haystack.deepset.ai/docs/docxtodocument) | Converter | ✅ Available |
| [EmbeddingBasedDocumentSplitter](https://docs.haystack.deepset.ai/docs/embeddingbaseddocumentsplitter) | Preprocessor | ✅ Available |
| [FallbackChatGenerator](https://docs.haystack.deepset.ai/docs/fallbackchatgenerator) | Generator | ✅ Available |
| [FileToFileContent](https://docs.haystack.deepset.ai/docs/filetofilecontent) | Converter | ✅ Available |
| [FileTypeRouter](https://docs.haystack.deepset.ai/docs/filetyperouter) | Router | ✅ Available |
| [FilterRetriever](https://docs.haystack.deepset.ai/docs/filterretriever) | Retriever | ✅ Available |
| [HierarchicalDocumentSplitter](https://docs.haystack.deepset.ai/docs/hierarchicaldocumentsplitter) | Preprocessor | ✅ Available |
| [HTMLToDocument](https://docs.haystack.deepset.ai/docs/htmltodocument) | Converter | ✅ Available |
| [ImageFileToDocument](https://docs.haystack.deepset.ai/docs/imagefiletodocument) | Converter | ✅ Available |
| [ImageFileToImageContent](https://docs.haystack.deepset.ai/docs/imagefiletoimagecontent) | Converter | ✅ Available |
| [JSONConverter](https://docs.haystack.deepset.ai/docs/jsonconverter) | Converter | ✅ Available |
| [JsonSchemaValidator](https://docs.haystack.deepset.ai/docs/jsonschemavalidator) | Validator | ✅ Available |
| [LinkContentFetcher](https://docs.haystack.deepset.ai/docs/linkcontentfetcher) | Fetcher | ✅ Available |
| [ListJoiner](https://docs.haystack.deepset.ai/docs/listjoiner) | Joiner | ✅ Available |
| [LLMDocumentContentExtractor](https://docs.haystack.deepset.ai/docs/llmdocumentcontentextractor) | Extractor | ✅ Available |
| [LLMMessagesRouter](https://docs.haystack.deepset.ai/docs/llmmessagesrouter) | Router | ✅ Available |
| [LLMMetadataExtractor](https://docs.haystack.deepset.ai/docs/llmmetadataextractor) | Extractor | ✅ Available |
| [LLMRanker](https://docs.haystack.deepset.ai/docs/llmranker) | Ranker | ✅ Available |
| [LostInTheMiddleRanker](https://docs.haystack.deepset.ai/docs/lostinthemiddleranker) | Ranker | ✅ Available |
| [MarkdownHeaderSplitter](https://docs.haystack.deepset.ai/docs/markdownheadersplitter) | Preprocessor | ✅ Available |
| [MarkdownToDocument](https://docs.haystack.deepset.ai/docs/markdowntodocument) | Converter | ✅ Available |
| [MetadataRouter](https://docs.haystack.deepset.ai/docs/metadatarouter) | Router | ✅ Available |
| [MetaFieldGroupingRanker](https://docs.haystack.deepset.ai/docs/metafieldgroupingranker) | Ranker | ✅ Available |
| [MetaFieldRanker](https://docs.haystack.deepset.ai/docs/metafieldranker) | Ranker | ✅ Available |
| [MSGToDocument](https://docs.haystack.deepset.ai/docs/msgtodocument) | Converter | ✅ Available |
| [MultiFileConverter](https://docs.haystack.deepset.ai/docs/multifileconverter) | Converter | ✅ Available |
| [MultiQueryEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/multiqueryembeddingretriever) | Retriever | ✅ Available |
| [MultiQueryTextRetriever](https://docs.haystack.deepset.ai/docs/multiquerytextretriever) | Retriever | ✅ Available |
| [MultiRetriever](https://docs.haystack.deepset.ai/docs/multiretriever) | Retriever | ✅ Available |
| [OpenAIChatGenerator](https://docs.haystack.deepset.ai/docs/openaichatgenerator) | Generator | ✅ Available |
| [OpenAIDocumentEmbedder](https://docs.haystack.deepset.ai/docs/openaidocumentembedder) | Embedder | ✅ Available |
| [OpenAIResponsesChatGenerator](https://docs.haystack.deepset.ai/docs/openairesponseschatgenerator) | Generator | ✅ Available |
| [OpenAITextEmbedder](https://docs.haystack.deepset.ai/docs/openaitextembedder) | Embedder | ✅ Available |
| [OutputAdapter](https://docs.haystack.deepset.ai/docs/outputadapter) | Converter | ✅ Available |
| [PDFMinerToDocument](https://docs.haystack.deepset.ai/docs/pdfminertodocument) | Converter | ✅ Available |
| [PDFToImageContent](https://docs.haystack.deepset.ai/docs/pdftoimagecontent) | Converter | ✅ Available |
| [PPTXToDocument](https://docs.haystack.deepset.ai/docs/pptxtodocument) | Converter | ✅ Available |
| [PromptBuilder](https://docs.haystack.deepset.ai/docs/promptbuilder) | Builder | ✅ Available |
| [PyPDFToDocument](https://docs.haystack.deepset.ai/docs/pypdftodocument) | Converter | ✅ Available |
| [PythonCodeSplitter](https://docs.haystack.deepset.ai/docs/pythoncodesplitter) | Preprocessor | ✅ Available |
| [QueryExpander](https://docs.haystack.deepset.ai/docs/queryexpander) | Component | ✅ Available |
| [RecursiveDocumentSplitter](https://docs.haystack.deepset.ai/docs/recursivesplitter) | Preprocessor | ✅ Available |
| [RegexTextExtractor](https://docs.haystack.deepset.ai/docs/regextextextractor) | Extractor | ✅ Available |
| [SentenceWindowRetriever](https://docs.haystack.deepset.ai/docs/sentencewindowretriever) | Retriever | ✅ Available |
| [StringJoiner](https://docs.haystack.deepset.ai/docs/stringjoiner) | Joiner | ✅ Available |
| [TextCleaner](https://docs.haystack.deepset.ai/docs/textcleaner) | Preprocessor | ✅ Available |
| [TextEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/textembeddingretriever) | Retriever | ✅ Available |
| [TextFileToDocument](https://docs.haystack.deepset.ai/docs/textfiletodocument) | Converter | ✅ Available |
| [TopPSampler](https://docs.haystack.deepset.ai/docs/toppsampler) | Sampler | ✅ Available |
| [XLSXToDocument](https://docs.haystack.deepset.ai/docs/xlsxtodocument) | Converter | ✅ Available |
## AIML API
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [AIMLAPIChatGenerator](https://docs.haystack.deepset.ai/docs/aimllapichatgenerator) | Generator | ✅ Available |
## AlloyDB
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [AlloyDBEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/alloydbembeddingretriever) | Retriever | ✅ Available |
| [AlloyDBKeywordRetriever](https://docs.haystack.deepset.ai/docs/alloydbkeywordretriever) | Retriever | ✅ Available |
## Amazon Bedrock
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [AmazonBedrockChatGenerator](https://docs.haystack.deepset.ai/docs/amazonbedrockchatgenerator) | Generator | ✅ Available |
| [AmazonBedrockDocumentEmbedder](https://docs.haystack.deepset.ai/docs/amazonbedrockdocumentembedder) | Embedder | ✅ Available |
| [AmazonBedrockDocumentImageEmbedder](https://docs.haystack.deepset.ai/docs/amazonbedrockdocumentimageembedder) | Embedder | ✅ Available |
| [AmazonBedrockRanker](https://docs.haystack.deepset.ai/docs/amazonbedrockranker) | Ranker | ✅ Available |
| [AmazonBedrockTextEmbedder](https://docs.haystack.deepset.ai/docs/amazonbedrocktextembedder) | Embedder | ✅ Available |
## Amazon S3
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [S3Downloader](https://docs.haystack.deepset.ai/docs/s3downloader) | Component | ✅ Available |
## Amazon Textract
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [AmazonTextractConverter](https://docs.haystack.deepset.ai/docs/amazontextractconverter) | Converter | ✅ Available |
## Anthropic
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [AnthropicChatGenerator](https://docs.haystack.deepset.ai/docs/anthropicchatgenerator) | Generator | ✅ Available |
| [AnthropicFoundryChatGenerator](https://docs.haystack.deepset.ai/docs/anthropicfoundrychatgenerator) | Generator | ✅ Available |
| [AnthropicVertexChatGenerator](https://docs.haystack.deepset.ai/docs/anthropicvertexchatgenerator) | Generator | ✅ Available |
## Arangodb
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [ArangoEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/arangoembeddingretriever) | Retriever | ✅ Available |
## ArcadeDB
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [ArcadeDBEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/arcadedbembeddingretriever) | Retriever | ✅ Available |
## Astra
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [AstraEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/astraretriever) | Retriever | ✅ Available |
## Azure AI Search
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [AzureAISearchBM25Retriever](https://docs.haystack.deepset.ai/docs/azureaisearchbm25retriever) | Retriever | ✅ Available |
| [AzureAISearchEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/azureaisearchembeddingretriever) | Retriever | ✅ Available |
| [AzureAISearchHybridRetriever](https://docs.haystack.deepset.ai/docs/azureaisearchhybridretriever) | Retriever | ✅ Available |
## Azure Document Intelligence
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [AzureDocumentIntelligenceConverter](https://docs.haystack.deepset.ai/docs/azuredocumentintelligenceconverter) | Converter | ✅ Available |
## Azure Form Recognizer
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [AzureOCRDocumentConverter](https://docs.haystack.deepset.ai/docs/azureocrdocumentconverter) | Converter | ✅ Available |
## Brave
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [BraveWebSearch](https://docs.haystack.deepset.ai/docs/bravewebsearch) | Component | ✅ Available |
## Chroma
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [ChromaEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/chromaembeddingretriever) | Retriever | ✅ Available |
| [ChromaQueryTextRetriever](https://docs.haystack.deepset.ai/docs/chromaqueryretriever) | Retriever | ✅ Available |
## Cohere
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [CohereChatGenerator](https://docs.haystack.deepset.ai/docs/coherechatgenerator) | Generator | ✅ Available |
| [CohereDocumentEmbedder](https://docs.haystack.deepset.ai/docs/coheredocumentembedder) | Embedder | ✅ Available |
| [CohereDocumentImageEmbedder](https://docs.haystack.deepset.ai/docs/coheredocumentimageembedder) | Embedder | ✅ Available |
| [CohereRanker](https://docs.haystack.deepset.ai/docs/cohereranker) | Ranker | ✅ Available |
| [CohereTextEmbedder](https://docs.haystack.deepset.ai/docs/coheretextembedder) | Embedder | ✅ Available |
## Cometapi
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [CometAPIChatGenerator](https://docs.haystack.deepset.ai/docs/cometapichatgenerator) | Generator | ✅ Available |
## Datadog
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [DatadogConnector](https://docs.haystack.deepset.ai/docs/datadogconnector) | Connector | ✅ Available |
## Ddgs
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [DDGSWebSearch](https://docs.haystack.deepset.ai/docs/ddgswebsearch) | Component | ✅ Available |
## Docling
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [DoclingConverter](https://docs.haystack.deepset.ai/docs/doclingconverter) | Converter | ✅ Available |
| [DoclingServeConverter](https://docs.haystack.deepset.ai/docs/doclingserveconverter) | Converter | ✅ Available |
## Edenai
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [EdenAIChatGenerator](https://docs.haystack.deepset.ai/docs/edenaichatgenerator) | Generator | ✅ Available |
| [EdenAIDocumentEmbedder](https://docs.haystack.deepset.ai/docs/edenaidocumentembedder) | Embedder | ✅ Available |
| [EdenAITextEmbedder](https://docs.haystack.deepset.ai/docs/edenaitextembedder) | Embedder | ✅ Available |
## Elasticsearch
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [ElasticsearchBM25Retriever](https://docs.haystack.deepset.ai/docs/elasticsearchbm25retriever) | Retriever | ✅ Available |
| [ElasticsearchEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/elasticsearchembeddingretriever) | Retriever | ✅ Available |
| [ElasticsearchHybridRetriever](https://docs.haystack.deepset.ai/docs/elasticsearchhybridretriever) | Retriever | ✅ Available |
| ElasticsearchInferenceHybridRetriever | Retriever | ✅ Available |
| ElasticsearchInferenceSparseRetriever | Retriever | ✅ Available |
| ElasticsearchSparseEmbeddingRetriever | Retriever | ✅ Available |
| [ElasticsearchSQLRetriever](https://docs.haystack.deepset.ai/docs/elasticsearchsqlretriever) | Retriever | ✅ Available |
## FalkorDB
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [FalkorDBCypherRetriever](https://docs.haystack.deepset.ai/docs/falkordbcypherretriever) | Retriever | ✅ Available |
| [FalkorDBEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/falkordbembeddingretriever) | Retriever | ✅ Available |
## FastEmbed
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [FastembedDocumentEmbedder](https://docs.haystack.deepset.ai/docs/fastembeddocumentembedder) | Embedder | ✅ Available |
| [FastembedLateInteractionRanker](https://docs.haystack.deepset.ai/docs/fastembedlateinteractionranker) | Ranker | ✅ Available |
| [FastembedRanker](https://docs.haystack.deepset.ai/docs/fastembedranker) | Ranker | ✅ Available |
| [FastembedSparseDocumentEmbedder](https://docs.haystack.deepset.ai/docs/fastembedsparsedocumentembedder) | Embedder | ✅ Available |
| [FastembedSparseTextEmbedder](https://docs.haystack.deepset.ai/docs/fastembedsparsetextembedder) | Embedder | ✅ Available |
| [FastembedTextEmbedder](https://docs.haystack.deepset.ai/docs/fastembedtextembedder) | Embedder | ✅ Available |
## Firecrawl
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [FirecrawlCrawler](https://docs.haystack.deepset.ai/docs/firecrawlcrawler) | Fetcher | ✅ Available |
| [FirecrawlWebSearch](https://docs.haystack.deepset.ai/docs/firecrawlwebsearch) | Component | ✅ Available |
## GitHub
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [GitHubFileEditor](https://docs.haystack.deepset.ai/docs/githubfileeditor) | Connector | ✅ Available |
| [GitHubIssueCommenter](https://docs.haystack.deepset.ai/docs/githubissuecommenter) | Connector | ✅ Available |
| [GitHubIssueViewer](https://docs.haystack.deepset.ai/docs/githubissueviewer) | Connector | ✅ Available |
| [GitHubPRCreator](https://docs.haystack.deepset.ai/docs/githubprcreator) | Connector | ✅ Available |
| [GitHubRepoForker](https://docs.haystack.deepset.ai/docs/githubrepoforker) | Connector | ✅ Available |
| [GitHubRepoViewer](https://docs.haystack.deepset.ai/docs/githubrepoviewer) | Connector | ✅ Available |
## Google Drive
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [GoogleDriveFetcher](https://docs.haystack.deepset.ai/docs/googledrivefetcher) | Fetcher | ✅ Available |
| [GoogleDriveRetriever](https://docs.haystack.deepset.ai/docs/googledriveretriever) | Retriever | ✅ Available |
## Google Generative AI
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [GoogleGenAIChatGenerator](https://docs.haystack.deepset.ai/docs/googlegenaichatgenerator) | Generator | ✅ Available |
| [GoogleGenAIDocumentEmbedder](https://docs.haystack.deepset.ai/docs/googlegenaidocumentembedder) | Embedder | ✅ Available |
| [GoogleGenAIMultimodalDocumentEmbedder](https://docs.haystack.deepset.ai/docs/googlegenaimultimodaldocumentembedder) | Embedder | ✅ Available |
| [GoogleGenAITextEmbedder](https://docs.haystack.deepset.ai/docs/googlegenaitextembedder) | Embedder | ✅ Available |
## Hugging Face API
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [HuggingFaceAPIChatGenerator](https://docs.haystack.deepset.ai/docs/huggingfaceapichatgenerator) | Generator | ✅ Available |
| [HuggingFaceAPIDocumentEmbedder](https://docs.haystack.deepset.ai/docs/huggingfaceapidocumentembedder) | Embedder | ✅ Available |
| [HuggingFaceAPITextEmbedder](https://docs.haystack.deepset.ai/docs/huggingfaceapitextembedder) | Embedder | ✅ Available |
| [HuggingFaceTEIRanker](https://docs.haystack.deepset.ai/docs/huggingfaceteiranker) | Ranker | ✅ Available |
## Jina AI
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [JinaDocumentEmbedder](https://docs.haystack.deepset.ai/docs/jinadocumentembedder) | Embedder | ✅ Available |
| [JinaDocumentImageEmbedder](https://docs.haystack.deepset.ai/docs/jinadocumentimageembedder) | Embedder | ✅ Available |
| [JinaRanker](https://docs.haystack.deepset.ai/docs/jinaranker) | Ranker | ✅ Available |
| [JinaReaderConnector](https://docs.haystack.deepset.ai/docs/jinareaderconnector) | Connector | ✅ Available |
| [JinaTextEmbedder](https://docs.haystack.deepset.ai/docs/jinatextembedder) | Embedder | ✅ Available |
## Kreuzberg
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [KreuzbergConverter](https://docs.haystack.deepset.ai/docs/kreuzbergconverter) | Converter | ✅ Available |
## Langdetect
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [DocumentLanguageClassifier](https://docs.haystack.deepset.ai/docs/documentlanguageclassifier) | Classifier | ✅ Available |
| [TextLanguageRouter](https://docs.haystack.deepset.ai/docs/textlanguagerouter) | Router | ✅ Available |
## Langfuse
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [LangfuseConnector](https://docs.haystack.deepset.ai/docs/langfuseconnector) | Connector | ✅ Available |
## Lara
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [LaraDocumentTranslator](https://docs.haystack.deepset.ai/docs/laradocumenttranslator) | Component | ✅ Available |
## Linkup
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [LinkupWebSearch](https://docs.haystack.deepset.ai/docs/linkupwebsearch) | Component | ✅ Available |
## LiteLLM
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [LiteLLMChatGenerator](https://docs.haystack.deepset.ai/docs/litellmchatgenerator) | Generator | ✅ Available |
## Llama Stack
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [LlamaStackChatGenerator](https://docs.haystack.deepset.ai/docs/llamastackchatgenerator) | Generator | ✅ Available |
## Llama.cpp
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [LlamaCppChatGenerator](https://docs.haystack.deepset.ai/docs/llamacppchatgenerator) | Generator | ✅ Available |
## MarkItDown
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [MarkItDownConverter](https://docs.haystack.deepset.ai/docs/markitdownconverter) | Converter | ✅ Available |
## Mem0
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [Mem0MemoryRetriever](https://docs.haystack.deepset.ai/docs/mem0memoryretriever) | Retriever | ✅ Available |
| [Mem0MemoryWriter](https://docs.haystack.deepset.ai/docs/mem0memorywriter) | Writer | ✅ Available |
## Microsoft Sharepoint
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [MSSharePointFetcher](https://docs.haystack.deepset.ai/docs/mssharepointfetcher) | Fetcher | ✅ Available |
| [MSSharePointRetriever](https://docs.haystack.deepset.ai/docs/mssharepointretriever) | Retriever | ✅ Available |
## Mistral
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [MistralChatGenerator](https://docs.haystack.deepset.ai/docs/mistralchatgenerator) | Generator | ✅ Available |
| [MistralDocumentEmbedder](https://docs.haystack.deepset.ai/docs/mistraldocumentembedder) | Embedder | ✅ Available |
| [MistralOCRDocumentConverter](https://docs.haystack.deepset.ai/docs/mistralocrdocumentconverter) | Converter | ✅ Available |
| [MistralTextEmbedder](https://docs.haystack.deepset.ai/docs/mistraltextembedder) | Embedder | ✅ Available |
## MongoDB Atlas
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [MongoDBAtlasEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/mongodbatlasembeddingretriever) | Retriever | ✅ Available |
| [MongoDBAtlasFullTextRetriever](https://docs.haystack.deepset.ai/docs/mongodbatlasfulltextretriever) | Retriever | ✅ Available |
## NVIDIA
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [NvidiaChatGenerator](https://docs.haystack.deepset.ai/docs/nvidiachatgenerator) | Generator | ✅ Available |
| [NvidiaDocumentEmbedder](https://docs.haystack.deepset.ai/docs/nvidiadocumentembedder) | Embedder | ✅ Available |
| [NvidiaRanker](https://docs.haystack.deepset.ai/docs/nvidiaranker) | Ranker | ✅ Available |
| [NvidiaTextEmbedder](https://docs.haystack.deepset.ai/docs/nvidiatextembedder) | Embedder | ✅ Available |
## Oauth
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [OAuthTokenResolver](https://docs.haystack.deepset.ai/docs/oauthtokenresolver) | Connector | ✅ Available |
## Ollama
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [OllamaChatGenerator](https://docs.haystack.deepset.ai/docs/ollamachatgenerator) | Generator | ✅ Available |
| [OllamaDocumentEmbedder](https://docs.haystack.deepset.ai/docs/ollamadocumentembedder) | Embedder | ✅ Available |
| [OllamaTextEmbedder](https://docs.haystack.deepset.ai/docs/ollamatextembedder) | Embedder | ✅ Available |
## Openapi
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [OpenAPIConnector](https://docs.haystack.deepset.ai/docs/openapiconnector) | Connector | ✅ Available |
| [OpenAPIServiceConnector](https://docs.haystack.deepset.ai/docs/openapiserviceconnector) | Connector | ✅ Available |
| [OpenAPIServiceToFunctions](https://docs.haystack.deepset.ai/docs/openapiservicetofunctions) | Converter | ✅ Available |
## OpenRouter
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [OpenRouterChatGenerator](https://docs.haystack.deepset.ai/docs/openrouterchatgenerator) | Generator | ✅ Available |
## OpenSearch
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [OpenSearchBM25Retriever](https://docs.haystack.deepset.ai/docs/opensearchbm25retriever) | Retriever | ✅ Available |
| [OpenSearchEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/opensearchembeddingretriever) | Retriever | ✅ Available |
| [OpenSearchHybridRetriever](https://docs.haystack.deepset.ai/docs/opensearchhybridretriever) | Retriever | ✅ Available |
| [OpenSearchMetadataRetriever](https://docs.haystack.deepset.ai/docs/opensearchmetadataretriever) | Retriever | ✅ Available |
| [OpenSearchSQLRetriever](https://docs.haystack.deepset.ai/docs/opensearchsqlretriever) | Retriever | ✅ Available |
## Opentelemetry
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [OpenTelemetryConnector](https://docs.haystack.deepset.ai/docs/opentelemetryconnector) | Connector | ✅ Available |
## Oracle
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [OracleEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/oracleembeddingretriever) | Retriever | ✅ Available |
| [OracleKeywordRetriever](https://docs.haystack.deepset.ai/docs/oraclekeywordretriever) | Retriever | ✅ Available |
## Orcarouter
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [OrcaRouterChatGenerator](https://docs.haystack.deepset.ai/docs/orcarouterchatgenerator) | Generator | ✅ Available |
## Perplexity
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [PerplexityChatGenerator](https://docs.haystack.deepset.ai/docs/perplexitychatgenerator) | Generator | ✅ Available |
| [PerplexityDocumentEmbedder](https://docs.haystack.deepset.ai/docs/perplexitydocumentembedder) | Embedder | ✅ Available |
| [PerplexityTextEmbedder](https://docs.haystack.deepset.ai/docs/perplexitytextembedder) | Embedder | ✅ Available |
| [PerplexityWebSearch](https://docs.haystack.deepset.ai/docs/perplexitywebsearch) | Component | ✅ Available |
## pgvector
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [PgvectorEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/pgvectorembeddingretriever) | Retriever | ✅ Available |
| [PgvectorKeywordRetriever](https://docs.haystack.deepset.ai/docs/pgvectorkeywordretriever) | Retriever | ✅ Available |
## Pinecone
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [PineconeEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/pineconedenseretriever) | Retriever | ✅ Available |
## Presidio
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [PresidioDocumentCleaner](https://docs.haystack.deepset.ai/docs/presidiodocumentcleaner) | Preprocessor | ✅ Available |
| [PresidioEntityExtractor](https://docs.haystack.deepset.ai/docs/presidioentityextractor) | Extractor | ✅ Available |
| [PresidioTextCleaner](https://docs.haystack.deepset.ai/docs/presidiotextcleaner) | Preprocessor | ✅ Available |
## Pyversity
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [PyversityRanker](https://docs.haystack.deepset.ai/docs/pyversityranker) | Ranker | ✅ Available |
## Qdrant
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [QdrantEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/qdrantembeddingretriever) | Retriever | ✅ Available |
| [QdrantHybridRetriever](https://docs.haystack.deepset.ai/docs/qdranthybridretriever) | Retriever | ✅ Available |
| [QdrantSparseEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/qdrantsparseembeddingretriever) | Retriever | ✅ Available |
## Searchapi
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [SearchApiWebSearch](https://docs.haystack.deepset.ai/docs/searchapiwebsearch) | Component | ✅ Available |
## Sentence Transformers
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [SentenceTransformersDiversityRanker](https://docs.haystack.deepset.ai/docs/sentencetransformersdiversityranker) | Ranker | ✅ Available |
| [SentenceTransformersDocumentEmbedder](https://docs.haystack.deepset.ai/docs/sentencetransformersdocumentembedder) | Embedder | ✅ Available |
| [SentenceTransformersDocumentImageEmbedder](https://docs.haystack.deepset.ai/docs/sentencetransformersdocumentimageembedder) | Embedder | ✅ Available |
| [SentenceTransformersSimilarityRanker](https://docs.haystack.deepset.ai/docs/sentencetransformerssimilarityranker) | Ranker | ✅ Available |
| [SentenceTransformersSparseDocumentEmbedder](https://docs.haystack.deepset.ai/docs/sentencetransformerssparsedocumentembedder) | Embedder | ✅ Available |
| [SentenceTransformersSparseTextEmbedder](https://docs.haystack.deepset.ai/docs/sentencetransformerssparsetextembedder) | Embedder | ✅ Available |
| [SentenceTransformersTextEmbedder](https://docs.haystack.deepset.ai/docs/sentencetransformerstextembedder) | Embedder | ✅ Available |
## Serperdev
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [SerperDevWebSearch](https://docs.haystack.deepset.ai/docs/serperdevwebsearch) | Component | ✅ Available |
## Snowflake
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [SnowflakeTableRetriever](https://docs.haystack.deepset.ai/docs/snowflaketableretriever) | Retriever | ✅ Available |
## Spacy
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [SpacyNamedEntityExtractor](https://docs.haystack.deepset.ai/docs/spacynamedentityextractor) | Extractor | ✅ Available |
## SQLAlchemy
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [SQLAlchemyTableRetriever](https://docs.haystack.deepset.ai/docs/sqlalchemytableretriever) | Retriever | ✅ Available |
## STACKIT
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [STACKITChatGenerator](https://docs.haystack.deepset.ai/docs/stackitchatgenerator) | Generator | ✅ Available |
| [STACKITDocumentEmbedder](https://docs.haystack.deepset.ai/docs/stackitdocumentembedder) | Embedder | ✅ Available |
| [STACKITTextEmbedder](https://docs.haystack.deepset.ai/docs/stackittextembedder) | Embedder | ✅ Available |
## Supabase
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| SupabaseBucketDownloader | Component | ✅ Available |
| [SupabaseGroongaBM25Retriever](https://docs.haystack.deepset.ai/docs/supabasegroongabm25retriever) | Retriever | ✅ Available |
| [SupabasePgvectorEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/supabasepgvectorembeddingretriever) | Retriever | ✅ Available |
| [SupabasePgvectorKeywordRetriever](https://docs.haystack.deepset.ai/docs/supabasepgvectorkeywordretriever) | Retriever | ✅ Available |
## Tavily
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [TavilyFetcher](https://docs.haystack.deepset.ai/docs/tavilyfetcher) | Fetcher | ✅ Available |
| [TavilyWebSearch](https://docs.haystack.deepset.ai/docs/tavilywebsearch) | Component | ✅ Available |
## Tika
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [TikaDocumentConverter](https://docs.haystack.deepset.ai/docs/tikadocumentconverter) | Converter | ✅ Available |
## TogetherAI
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [TogetherAIChatGenerator](https://docs.haystack.deepset.ai/docs/togetheraichatgenerator) | Generator | ✅ Available |
## Transformers
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [TransformersChatGenerator](https://docs.haystack.deepset.ai/docs/transformerschatgenerator) | Generator | ✅ Available |
| [TransformersExtractiveReader](https://docs.haystack.deepset.ai/docs/transformersextractivereader) | Reader | ✅ Available |
| [TransformersNamedEntityExtractor](https://docs.haystack.deepset.ai/docs/transformersnamedentityextractor) | Extractor | ✅ Available |
| [TransformersTextRouter](https://docs.haystack.deepset.ai/docs/transformerstextrouter) | Router | ✅ Available |
| [TransformersZeroShotDocumentClassifier](https://docs.haystack.deepset.ai/docs/transformerszeroshotdocumentclassifier) | Classifier | ✅ Available |
| [TransformersZeroShotTextRouter](https://docs.haystack.deepset.ai/docs/transformerszeroshottextrouter) | Router | ✅ Available |
## Unstructured
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [UnstructuredFileConverter](https://docs.haystack.deepset.ai/docs/unstructuredfileconverter) | Converter | ✅ Available |
## Valkey
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [ValkeyEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/valkeyembeddingretriever) | Retriever | ✅ Available |
## Vespa
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [VespaEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/vespaembeddingretriever) | Retriever | ✅ Available |
| [VespaKeywordRetriever](https://docs.haystack.deepset.ai/docs/vespakeywordretriever) | Retriever | ✅ Available |
## vLLM
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [VLLMChatGenerator](https://docs.haystack.deepset.ai/docs/vllmchatgenerator) | Generator | ✅ Available |
| [VLLMDocumentEmbedder](https://docs.haystack.deepset.ai/docs/vllmdocumentembedder) | Embedder | ✅ Available |
| [VLLMRanker](https://docs.haystack.deepset.ai/docs/vllmranker) | Ranker | ✅ Available |
| [VLLMTextEmbedder](https://docs.haystack.deepset.ai/docs/vllmtextembedder) | Embedder | ✅ Available |
## Weaviate
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [WeaviateBM25Retriever](https://docs.haystack.deepset.ai/docs/weaviatebm25retriever) | Retriever | ✅ Available |
| [WeaviateEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/weaviateembeddingretriever) | Retriever | ✅ Available |
| [WeaviateHybridRetriever](https://docs.haystack.deepset.ai/docs/weaviatehybridretriever) | Retriever | ✅ Available |
## Weights & Biases (Weave)
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [WeaveConnector](https://docs.haystack.deepset.ai/docs/weaveconnector) | Connector | ✅ Available |
## Whisper
| Component | Type | Haystack Enterprise Platform |
|-----------|------|------------------------------|
| [LocalWhisperTranscriber](https://docs.haystack.deepset.ai/docs/localwhispertranscriber) | Audio | ✅ Available |
| [RemoteWhisperTranscriber](https://docs.haystack.deepset.ai/docs/remotewhispertranscriber) | Audio | ✅ Available |
---
// File: overview/telemetry
# Telemetry
Haystack relies on anonymous usage statistics to continuously improve. That's why some basic information, like the type of Document Store used, is shared automatically.
## What Information Is Shared?
Telemetry in Haystack comprises anonymous usage statistics of base components, such as `DocumentStore`, `Retriever`, `Reader`, or any other pipeline component. We receive an event every time these components are initialized. This way, we know which components are most relevant to our community. For the same reason, an event is also sent when one of the tutorials is executed.
Each event contains an anonymous, randomly generated user ID (`uuid`) and a collection of properties about your execution environment. They **never** contain properties that can be used to identify you, such as:
- IP addresses
- Hostnames
- File paths
- Queries
- Document contents
By taking the above steps, we ensure that only anonymized data is transmitted to our telemetry server.
Here is an exemplary event that is sent when tutorial 1 is executed by running `Tutorial1_Basic_QA_Pipeline.py`:
```json
{
"event": "tutorial 1 executed",
"distinct_id": "9baab867-3bc8-438c-9974-a192c9d53cd1",
"properties": {
"os_family": "Darwin",
"os_machine": "arm64",
"os_version": "21.3.0",
"haystack_version": "1.0.0",
"python_version": "3.9.6",
"torch_version": "1.9.0",
"transformers_version": "4.13.0",
"execution_env": "script",
"n_gpu": 0,
},
}
```
Our telemetry code can be directly inspected on [GitHub](https://github.com/deepset-ai/haystack/blob/5d66d040cc303ab49225587cd61290f1987a5d1f/haystack/telemetry/_telemetry.py).
## How Does Telemetry Help?
Thanks to telemetry, we can understand the needs of the community: _"What pipeline nodes are most popular?", "Should we focus on supporting one specific Document Store?", "How many people use Haystack on Windows?"_ are some of the questions telemetry helps us answer. Metadata about the operating system and installed dependencies allows us to quickly identify and address issues caused by specific setups.
In short, by sharing this information, you enable us to continuously improve Haystack for everyone.
## How Can I Opt Out?
You can disable telemetry with one of the following methods:
### Through an Environment Variable
You can disable telemetry by setting the environment variable `HAYSTACK_TELEMETRY_ENABLED` to `"False"` .
### Using a Bash Shell
If you are using a bash shell, add the following line to the file `~/.bashrc` to disable telemetry: `export HAYSTACK_TELEMETRY_ENABLED=False`.
### Using zsh
If you are using zsh as your shell, for example, on macOS, add the following line to the file `~/.zshrc`: `export HAYSTACK_TELEMETRY_ENABLED=False`.
### On Windows
To disable telemetry on Windows, set a user-level environment variable by running this command in the standard command prompt: `setx HAYSTACK_TELEMETRY_ENABLED "False"`.
Alternatively, run the following command in Windows PowerShell: `[Environment]::SetEnvironmentVariable("HAYSTACK_TELEMETRY_ENABLED","False","User")`.
You might need to restart the operating system for the command to take effect.
---
// File: pipeline-components/agents-1/agent-pack/advanced-rag-agent
# Advanced RAG Agent
A metadata-aware RAG agent: instead of guessing which metadata fields exist, it inspects the document store (fields, values, ranges) and can construct Haystack filters to narrow its retrieval.
| | |
| --- | --- |
| **Mandatory init variables** | `document_store`: The document store to inspect and fetch from `retriever`: A relevance-scoring retriever or retrieval `Pipeline` |
| **Mandatory run variables** | `messages`: A list of [`ChatMessage`](../../../concepts/data-classes/chatmessage.mdx)s |
| **Output variables** | `last_message`: The answer, citing documents as `[doc ]` `documents`: Every document the agent retrieved, deduplicated |
| **API reference** | [Agent Pack](/reference/integrations-agent-pack) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/agent_pack/src/haystack_integrations/agent_pack/advanced_rag |
| **Package name** | `agent-pack-haystack` |
:::warning
Part of [Agent Pack](../agent-pack.mdx), which is experimental for the moment. Its APIs and agent architectures can change in any release, without following the usual deprecation policy.
:::
## When to use this agent
Use the advanced RAG agent when:
- You have a large, heterogeneous corpus (many topics, sources, or document types mixed together) with well-structured metadata. The agent uses that metadata to narrow retrieval, making results more precise than relevance ranking alone.
- You need to retrieve exact or complete subsets of documents by metadata ("all pages of this file", "everything from source X"), not just the most relevant matches.
It's less useful when:
- There's no metadata, or the metadata isn't useful for narrowing retrieval.
- The corpus is small and homogeneous, so plain top-k retrieval already returns the right documents.
## Installation
```shell
pip install agent-pack-haystack arrow
```
`arrow` (required with the default system prompt) renders today's date so the agent can build filters for relative dates like "the last 5 years".
Set `OPENAI_API_KEY` in the environment.
## Usage
Index a corpus with varied metadata and ask a question the agent can only answer well by inspecting the metadata, building a filter, and retrieving with it:
```python
from haystack import Document
from haystack.components.retrievers.in_memory import InMemoryBM25Retriever
from haystack.dataclasses import ChatMessage
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack_integrations.agent_pack import create_advanced_rag_agent
document_store = InMemoryDocumentStore()
document_store.write_documents(
[
Document(
content="CRISPR gene editing corrected a hereditary blindness mutation in a clinical trial.",
meta={"category": "science", "year": 2021, "rating": 4.6},
),
Document(
content="A quantum computer demonstrated error-corrected logical qubits.",
meta={"category": "science", "year": 2023, "rating": 4.8},
),
Document(
content="Dolly the sheep became the first mammal cloned from an adult somatic cell.",
meta={"category": "science", "year": 1996, "rating": 4.2},
),
Document(
content="The Berlin Wall fell, a decisive moment in the end of the Cold War.",
meta={"category": "history", "year": 1989, "rating": 4.7},
),
Document(
content="Argentina won the FIFA World Cup final against France on penalties.",
meta={"category": "sports", "year": 2022, "rating": 4.9},
),
],
)
agent = create_advanced_rag_agent(
document_store=document_store,
retriever=InMemoryBM25Retriever(document_store=document_store, top_k=5),
)
result = agent.run(
messages=[ChatMessage.from_user("What science advances happened after 2015?")],
)
print(result["last_message"].text) # the answer, citing documents as [doc ]
for doc in result["documents"]: # every document the agent retrieved, deduplicated
print(f"[doc {doc.id[:8]}] {doc.meta} :: {doc.content[:60]}")
```
The agent lists the metadata fields, verifies the `category` values and the `year` range, builds a [filter](../../../concepts/metadata-filtering.mdx) like `{"operator": "AND", "conditions": [{"field": "meta.category", "operator": "==", "value": "science"}, {"field": "meta.year", "operator": ">", "value": 2015}]}`, retrieves with it, and answers citing the CRISPR and quantum documents. Filtering is optional: when metadata can't narrow a question, the agent retrieves without one.
:::note
The retrieval you provide should be scoring-based: keyword (BM25), embedding, or hybrid. Direct, unscored fetching by metadata is already covered by the built-in `fetch_documents_by_filter` tool.
:::
### Using a retrieval pipeline instead of a single retriever
To use a multi-component retrieval flow, pass a retrieval `Pipeline` as the `retriever` and provide mappings for the query, filters, and document output. For example, hybrid retrieval with reciprocal rank fusion:
```python
from haystack import Pipeline
from haystack.components.embedders import OpenAITextEmbedder
from haystack.components.joiners import DocumentJoiner
from haystack.components.retrievers.in_memory import (
InMemoryBM25Retriever,
InMemoryEmbeddingRetriever,
)
pipeline = Pipeline()
pipeline.add_component(
"bm25_retriever",
InMemoryBM25Retriever(document_store=document_store),
)
pipeline.add_component("text_embedder", OpenAITextEmbedder())
pipeline.add_component(
"embedding_retriever",
InMemoryEmbeddingRetriever(document_store=document_store),
)
pipeline.add_component("joiner", DocumentJoiner(join_mode="reciprocal_rank_fusion"))
pipeline.connect("text_embedder.embedding", "embedding_retriever.query_embedding")
pipeline.connect("bm25_retriever.documents", "joiner.documents")
pipeline.connect("embedding_retriever.documents", "joiner.documents")
agent = create_advanced_rag_agent(
document_store=document_store,
retriever=pipeline,
retrieval_pipeline_input_mapping={
"query": ["bm25_retriever.query", "text_embedder.text"],
"filters": ["bm25_retriever.filters", "embedding_retriever.filters"],
},
retrieval_pipeline_output_mapping={"joiner.documents": "documents"},
)
```
### Using the tools on their own
The four document-store-backed tools (see [How it works](#how-it-works)) are exported individually and also bundled as `DocumentStoreToolset`, so you can drop them into your own `Agent` with your own prompt:
```python
from haystack_integrations.agent_pack.advanced_rag import DocumentStoreToolset
agent = Agent(
chat_generator=...,
tools=[DocumentStoreToolset(document_store), my_retrieval_tool],
)
```
## Supported document stores
The metadata tools rely on document store methods that are not part of the base `DocumentStore` protocol: `get_metadata_fields_info`, `get_metadata_field_unique_values`, and `get_metadata_field_min_max`. [`InMemoryDocumentStore`](../../../document-stores/inmemorydocumentstore.mdx) and most document store integrations implement them (OpenSearch, Elasticsearch, Weaviate, Chroma, pgvector, Qdrant, Pinecone, MongoDB Atlas, Astra, and more).
Each tool fails fast at construction time with a clear error if the store doesn't support the method it needs, so stores that implement only some of the methods can still use the matching subset of tools.
## Configuration
Everything is configured through keyword arguments to `create_advanced_rag_agent`. All parameters are keyword-only. Only `document_store` and `retriever` are required, the rest are optional.
### Retrieval
- `document_store` is the store the metadata inspection tools and the `fetch_documents_by_filter` tool run against.
- `retriever` becomes the `search_documents` tool. It can be either:
- a standalone retriever component whose `run` method accepts `query` and `filters`, or
- a retrieval `Pipeline`.
Examples of standalone components include [`InMemoryBM25Retriever`](../../retrievers/inmemorybm25retriever.mdx) and embedding retrievers wrapped in `TextEmbeddingRetriever`.
- `retrieval_pipeline_input_mapping` maps the tool inputs to pipeline input sockets, and must have exactly the keys `query` and `filters`, for example `{"query": ["embedder.text"], "filters": ["retriever.filters"]}`. Required when `retriever` is a `Pipeline`.
- `retrieval_pipeline_output_mapping` maps pipeline output sockets to tool outputs, for example `{"retriever.documents": "documents"}`. Only valid when `retriever` is a `Pipeline`.
### Models and prompt
- `llm` is the LLM that drives the agent loop. Defaults to `OpenAIResponsesChatGenerator("gpt-5.4")` with low reasoning effort.
- `backup_answer_llm` is the LLM the built-in `BackupAnswerHook` uses to write a best-effort answer when the run is cut off by `max_agent_steps`. Defaults to a separate `OpenAIResponsesChatGenerator("gpt-5.4")` with low reasoning effort.
- `system_prompt` overrides the pre-made system prompt.
### Limits
- `max_agent_steps` caps the agent loop. Defaults to `20`.
- `max_fetched_docs` sets how many documents `fetch_documents_by_filter` shows per fetch. Defaults to `10`. A filter fetch is not bounded by a retriever's `top_k`, so this caps the tool result instead; the scored `search_documents` tool is bounded by the `top_k` configured on your retrieval components.
- `tool_concurrency_limit` caps the number of tool calls executed in parallel within one agent step. Defaults to `4`.
### Extension
- `extra_tools` takes additional tools or toolsets, appended after the built-in document-store toolset and the retrieval tool.
- `state_schema` merges additional entries into the agent's [`State`](../state.mdx) schema. The built-in `documents` entry always takes precedence.
- `hooks` merges additional [hooks](../hooks.mdx) per hook point with the built-in ones. For `after_run`, the built-in backup-answer hook runs first, so custom hooks see the final answer.
- `raise_on_tool_invocation_failure` makes a failing tool call raise when `True`, instead of returning the error to the LLM as a message it can recover from. Defaults to `False`.
## How it works
The architecture consists of a single Haystack [`Agent`](../agent.mdx) that works through three logical stages using five tools:
- **Inspect metadata.** The agent discovers which metadata fields exist, then inspects their values or ranges.
- **Retrieve documents.** It either runs relevance-based retrieval, optionally narrowed by a metadata filter, or fetches documents directly when metadata uniquely identifies them.
- **Answer.** It answers using only the retrieved documents and cites them as `[doc ]`.
Every retrieved document is accumulated in the agent's [`State`](../state.mdx) under the `documents` key and deduplicated by id. As a result, `agent.run(...)` returns both the answer (in `last_message`) and the complete set of documents retrieved during the run, alongside the standard [`Agent`](../agent.mdx) outputs `messages`, `step_count`, `token_usage`, and `tool_call_counts`. The answer cites each document by the first 8 characters of its id (for example `[doc a1b2c3d4]`); resolve a citation against the returned list with `doc.id.startswith(...)`.
If the run is cut off by `max_agent_steps` before an answer is written, a `BackupAnswerHook` (an `after_run` hook) makes one extra LLM call to produce a best-effort answer from the evidence gathered so far, so `last_message` always carries a text answer.
The agent's tools:
| Tool | What it is | What it does |
| --- | --- | --- |
| `list_metadata_fields` | `ListMetadataFieldsTool` | Lists all metadata fields and their types. The system prompt instructs the agent to call this first. |
| `get_metadata_field_values` | `GetMetadataFieldValuesTool` | Returns the distinct values of a field, so filters use values that actually exist. Listing is capped for high-cardinality fields, and the total count is reported when the store provides one. |
| `get_metadata_field_range` | `GetMetadataFieldRangeTool` | Returns min and max of a numeric or orderable field (for example years, ratings, ISO dates). |
| `fetch_documents_by_filter` | `FetchDocumentsByFilterTool` | Fetches documents directly through a metadata filter, when relevance scoring is unnecessary (for example a known title or file). |
| `search_documents` | [`ComponentTool`](../../../tools/componenttool.mdx) over your retriever, or [`PipelineTool`](../../../tools/pipelinetool.mdx) over your retrieval pipeline | Retrieves documents for a query by relevance, optionally narrowed by a metadata filter. Bounded by the `top_k` of your retrieval components. An empty result nudges the agent to relax the filter. |
`fetch_documents_by_filter` returns its results in reading order, grouping documents by parent file and sorting by split or page. It shows at most `max_docs` per call and reports the total match count, so larger match sets can be paged through with the tool's `offset` input. On stores that can count documents by filter, an over-broad filter is refused before any documents are fetched, and the refusal is returned to the LLM as an error it recovers from by narrowing the filter.
### The filter grammar
To help the LLM construct valid [Haystack filters](../../../concepts/metadata-filtering.mdx) consistently, the filter grammar is included in the description of the `filters` parameter of `search_documents` and `fetch_documents_by_filter`, rather than placed entirely in the system prompt. The model receives it contextually at the point of tool use:
- single condition: `{"field": "meta.category", "operator": "==", "value": "science"}`
- comparison operators: `==, !=, >, >=, <, <=, in, not in`
- logical grouping: `{"operator": "AND"|"OR"|"NOT", "conditions": [...]}` (nestable)
- field names must be prefixed with `meta.`
The system prompt adds the workflow rules: inspect fields first, verify values before filtering, and relax the filter when a search comes back empty.
---
// File: pipeline-components/agents-1/agent-pack/deep-research-agent
# Deep Research Agent
A deep research agent: give it a question, and it researches the web and produces a structured, cited Markdown report.
| | |
| --- | --- |
| **Mandatory run variables** | `messages`: A list of [`ChatMessage`](../../../concepts/data-classes/chatmessage.mdx)s |
| **Output variables** | `report`: The final cited Markdown report `brief`, `notes`: The intermediate research brief and collected summaries |
| **API reference** | [Agent Pack](/reference/integrations-agent-pack) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/agent_pack/src/haystack_integrations/agent_pack/deep_research |
| **Package name** | `agent-pack-haystack` |
:::warning
Part of [Agent Pack](../agent-pack.mdx), which is experimental for the moment. Its APIs and agent architectures can change in any release, without following the usual deprecation policy.
:::
## When to use this agent
Use the deep research agent when you need more than a quick answer. It's designed for questions that require gathering information from many web sources, evaluating them, and producing a structured report with citations.
Typical use cases include:
- Researching a broad topic across many sources.
- Comparing products, companies, technologies, or scientific findings.
- Preparing a literature review or market overview.
- Answering complex questions that benefit from investigating several sub-topics in parallel.
It's less useful when:
- A single web search or RAG lookup is enough. The multi-agent workflow adds latency and cost.
- The information lives in a private knowledge base rather than on the public web. For that, use the [Advanced RAG Agent](./advanced-rag-agent.mdx).
## Installation
```shell
pip install agent-pack-haystack tavily-haystack trafilatura pypdf arrow
```
`tavily-haystack`, `trafilatura`, `pypdf`, and `arrow` are separate installs the deep research agent needs at runtime (web search, HTML and PDF parsing, and date rendering).
Set `OPENAI_API_KEY` and `TAVILY_API_KEY` in the environment.
## Usage
```python
from haystack.dataclasses import ChatMessage
from haystack_integrations.agent_pack import create_deep_research_agent
agent = create_deep_research_agent()
result = agent.run(messages=[ChatMessage.from_user("your research question")])
print(result["report"])
```
`agent.run(...)` returns a dictionary whose main output is `report`, the final Markdown report. The dictionary also carries the intermediate `brief` (a `str`) and `notes` (a `list[str]`), plus the standard [`Agent`](../agent.mdx) outputs `messages`, `last_message`, `step_count`, `token_usage`, and `tool_call_counts`.
## Configuration
Everything is configured through keyword arguments to `create_deep_research_agent`. All parameters are keyword-only and optional.
### Models
Each phase takes its own ChatGenerator, so you can mix models by cost and capability, or swap in a different provider.
- `scope_llm` is the LLM that rewrites the user query into a focused research brief. Defaults to `OpenAIResponsesChatGenerator("gpt-5.4")`.
- `orchestrator_llm` is the LLM that plans the investigation and delegates the sub-questions. Defaults to `OpenAIResponsesChatGenerator("gpt-5.4")`.
- `researcher_llm` is the LLM that drives each sub-researcher's search, read, and think loop. Defaults to `OpenAIResponsesChatGenerator("gpt-5.4-mini")`.
- `summarizer_llm` is the LLM used inside the `read_url` tool to summarize a fetched page toward the question. Defaults to `OpenAIResponsesChatGenerator("gpt-5.4-mini")`.
- `writer_llm` is the LLM that turns the brief plus collected notes into the final report. Defaults to `OpenAIResponsesChatGenerator("gpt-5.4")`.
### Breadth and depth
- `max_subtopics` is the maximum number of sub-questions the orchestrator may delegate (breadth). Defaults to `5`.
- `max_concurrent_researchers` is the maximum number of sub-researchers that run at the same time. Defaults to `5`.
- `max_orchestrator_steps` is the maximum number of steps for the orchestrator's agent loop (reflect and delegate rounds). Defaults to `8`.
- `max_researcher_steps` is the maximum number of steps for each sub-researcher's agent loop. Defaults to `20`.
### Search and reading
- `max_search_results` is the number of results returned per `web_search` call. Defaults to `10`.
- `max_content_length` is the maximum number of raw page characters fed to the summarizer, before summarization. Defaults to `50000`.
## How it works
The architecture is built around a single top-level Haystack [`Agent`](../agent.mdx), which acts as the orchestrator. Two [hooks](../hooks.mdx) run before and after its loop, creating three logical phases: Scope, Research, and Write. During the Research phase, the orchestrator invokes isolated sub-researcher agents, each its own `Agent`, through a tool:
- **Scope.** The user question is rewritten into a focused research brief.
- **Research.** The orchestrator splits the brief into focused sub-questions, delegates each to a sub-researcher, and collects their summaries.
- **Write.** The brief and the collected summaries become the final report: Markdown with inline `[text](url)` citations.
Scope and Write are plain LLM calls (a [`ChatPromptBuilder`](../../builders/chatpromptbuilder.mdx) and an [`OpenAIResponsesChatGenerator`](../../generators/openairesponseschatgenerator.mdx)), wrapped as serializable hook classes (`ScopeHook`, `WriteHook`):
- Scope runs as a `before_run` hook: before the orchestrator's loop starts, it turns the user query into a brief, stored on the agent's [`State`](../state.mdx).
- Write runs as an `after_run` hook: when the orchestrator's loop finishes, it turns the brief plus collected `notes` into the final report.
`brief`, `notes`, and `report` are declared in the agent's `state_schema`, so they come back as outputs of a single `agent.run(...)` call.
### The agents
The Research phase uses two nested agents. Each one is a Haystack `Agent`: an LLM that loops, calling tools, until it decides to answer.
#### Orchestrator
The orchestrator is the lead agent: it receives the research brief and coordinates the whole investigation.
- **Job:** split the brief into a few focused, non-overlapping sub-questions, delegate each one, check coverage, and stop when there's enough.
- **Parallelism:** it emits several delegation calls in a single turn, and they run concurrently (bounded by `max_concurrent_researchers`).
- **Memory:** the summaries returned by sub-researchers are appended to a shared `notes` list (the agent's `State`), which the writer later turns into the report.
- **Stops when:** it replies with plain text (research complete) or hits `max_orchestrator_steps`.
The orchestrator's tools:
| Tool | What it is | What it does |
| --- | --- | --- |
| `research_subtopic` | The sub-researcher agent, exposed as a tool ([`ComponentTool`](../../../tools/componenttool.mdx)) | Researches a single sub-question in an isolated context and returns a compressed, cited summary. Only that summary is shown to the orchestrator; the summary is also appended to `notes`. |
| `think_tool` | A no-op reflection tool | Lets the orchestrator pause to plan sub-questions and assess coverage between rounds. |
#### Sub-researcher
The sub-researcher is a reusable agent that answers a single sub-question. The orchestrator runs it many times in parallel, each in its own isolated context. This is the key idea: each sub-researcher processes the raw search results privately and returns only a concise summary, so the orchestrator's context stays small and the final report stays coherent.
- **Job:** search the web, optionally read promising pages, reflect, then write a compressed summary with inline citations to the exact source URLs.
- **Returns:** its final text message *is* the summary (it exits as soon as it writes plain text).
- **Bounded by:** `max_researcher_steps`.
The sub-researcher's tools:
| Tool | What it is | What it does |
| --- | --- | --- |
| `web_search` | [`TavilyWebSearchTool`](../../../tools/ready-made-tools/tavilywebsearchtool.mdx) from the Tavily integration | Runs a web search and returns the top results as title, exact URL, and snippet. |
| `read_url` | [`PipelineTool`](../../../tools/pipelinetool.mdx) over a fetch, route, convert-to-text, and summarize pipeline | Fetches a page (`LinkContentFetcher`), routes by MIME type (`FileTypeRouter`) to `HTMLToDocument` (Trafilatura) or `PyPDFToDocument` so PDFs are parsed too, and summarizes the page toward a question the agent passes, so only the relevant text enters the agent's context, not the full page. Used only when a search snippet is too shallow. |
| `think_tool` | A no-op reflection tool | "What did I learn? What's missing? Stop or continue?" between searches. |
### Context management
The core challenge in a deep research agent is keeping each context window small and focused. Raw web content (search results, full pages, PDFs) is large and noisy. If it all accumulated in a single context, the model's output quality would degrade. We avoid that with isolation and compression:
- Each sub-researcher runs as its own agent with its own `State`, so all the messy intermediate content (every search result, every fetched page) stays in *its* private context.
- It finishes by writing one short summary (its final message). Only that summary leaves the sub-researcher: the raw content never reaches the orchestrator or the writer.
Two settings on the `research_subtopic` tool decide where that summary goes:
| Setting (on `research_subtopic`) | Controls | Effect |
| --- | --- | --- |
| `outputs_to_string={"source": "last_message"}` | What the orchestrator's LLM sees as the tool result | Only the summary text comes back, not the sub-researcher's full message history. Keeps the orchestrator's context clean. |
| `outputs_to_state={"notes": {...}}` | What gets saved for the writer | The same summary is appended (as text) to the shared `notes` list, which becomes the writer's input. |
So each summary travels two ways, into the orchestrator's reasoning (so it can decide whether to dig further) and into the `notes` accumulator (so the writer can use it), while the bulky raw research stays isolated and is not propagated beyond the sub-researcher:
```
sub-researcher (private context: searches, pages, reflections)
│ writes one short summary
├─ outputs_to_string → orchestrator's LLM (decide: done, or dig more?)
└─ outputs_to_state → notes → writer (final report)
```
---
// File: pipeline-components/agents-1/agent-pack
# Agent Pack
Agent Pack is a collection of complex, pre-configured Haystack agents you can run as they are, customize, or copy as a blueprint for your own architecture.
## Overview
Language Models and Tools are the core building blocks of agents.
However, building a robust agent often requires more: an architecture that splits the work across sub-agents, techniques for keeping context small but focused, and ways to influence and interact with the agent loop.
Agent Pack combines these techniques into working agents. Each one is a complete architecture built from Haystack primitives ([`Agent`](./agent.mdx), [Tools](../../tools/tool.mdx), [hooks](./hooks.mdx), [`State`](./state.mdx), and Pipelines), exposed behind a single `create_*` entry point.
There are three ways to use an agent from the pack:
- **Run it as is.** Each agent has a factory that builds a ready-to-run agent with defaults chosen to work out of the box. For example, `create_deep_research_agent()` returns an agent that takes a question and produces a report.
- **Customize it.** Each entry point exposes parameters for changing models, adding tools, and configuring behavior specific to that type of agent. (*This interface is still being refined to make customization more consistent and flexible.*)
- **Copy it.** Read the implementation and take inspiration for your agents. These agents are built with this use case in mind, so individual parts should be easy to adapt.
In other words, you can use the agents in Agent Pack as either ready-made solutions or reference architectures.
## Installation
```shell
pip install agent-pack-haystack
```
Each agent has its own additional runtime dependencies and may require API keys. See its documentation page for these details.
## Available agents
| Agent | Description |
| --- | --- |
| [Deep Research Agent](agent-pack/deep-research-agent.mdx) | Researches a question on the web and produces a structured Markdown report with citations. |
| [Advanced RAG Agent](agent-pack/advanced-rag-agent.mdx) | Inspects document-store metadata and builds Haystack filters to retrieve precisely, then answers with citations. |
## Experimental
:::warning
Agent Pack is experimental for the moment. Its APIs and agent architectures can change in any release, without following the usual deprecation policy.
:::
Agent Pack is distributed separately from `haystack-ai` and its code lives in `haystack-core-integrations`.
Agentic architectures are evolving fast. Keeping it outside `haystack-ai` allows us to improve these agents rapidly, release updates independently, and avoid committing to stability before these patterns have settled.
Developing complex agents also helps us identify missing capabilities in Haystack. When a gap shows up, we can implement it in the pack first, and then, if it turns out to be generally useful, refine it and move it into Haystack.
## Feedback
If you find a bug or have an idea for a complex agent that could belong in the pack, [open an issue](https://github.com/deepset-ai/haystack-core-integrations/issues).
---
// File: pipeline-components/agents-1/agent
# 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`](../builders/chatpromptbuilder.mdx) or user input |
| **Mandatory init variables** | `chat_generator`: An instance of a Chat Generator that supports tools |
| **Mandatory run variables** | `messages`: A list of [`ChatMessage`](../../concepts/data-classes/chatmessage.mdx)s |
| **Output variables** | `messages`: Chat history with tool and model responses
`last_message`: The final `ChatMessage` of the run
`step_count`, `token_usage`, `tool_call_counts`, `exit_reason`: Run metadata
Plus one output per key defined in `state_schema` |
| **API reference** | [Agents](/reference/agents-api) |
| **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 final `ChatMessage` from the agent,
- `step_count`: the number of steps the agent ran,
- `token_usage`: aggregated token usage summed across every LLM call in the run,
- `tool_call_counts`: how many times each tool was invoked, keyed by tool name,
- `exit_reason`: why the agent stopped, useful for routing its output downstream,
- Additional dynamic keys based on `state_schema`.
### Run Metadata
The `step_count`, `token_usage`, `tool_call_counts`, and `exit_reason` outputs are populated automatically during a run. They are added to the agent's `state_schema` behind the scenes, so tools registered with `inputs_from_state` and [hooks](./hooks.mdx) can read them from the live `State`. They are outputs only — they cannot be passed as inputs to `run()` or `run_async()`, and using them as keys in your own `state_schema` raises a `ValueError`. See [State](./state.mdx#schema-definition) for details.
```python
response = agent.run(messages=[ChatMessage.from_user("What is 7 * (4 + 2)?")])
print(response["step_count"]) # 2
print(
response["token_usage"],
) # {"prompt_tokens": 512, "completion_tokens": 86, ...}
print(response["tool_call_counts"]) # {"calculator": 1}
print(response["exit_reason"]) # "text"
```
### Exit reason
The `exit_reason` output tells you why the agent stopped, which makes it easy to route the agent's output downstream — for example, with a [`ConditionalRouter`](../routers/conditionalrouter.mdx). It is one of:
- `"text"`: the model returned a reply with no tool calls.
- the name of the tool that satisfied a tool exit condition. In this case `last_message` is that tool's result — a tool-result `ChatMessage` whose `text` is empty — so `exit_reason` tells you how to consume it.
- `"max_agent_steps"`: the agent reached `max_agent_steps` before meeting an exit condition.
Because `exit_reason` is available on the live `State`, an `after_run` [hook](./hooks.mdx) can read it to react to how the run ended — for example, appending a fallback answer when the step budget is exhausted before the agent finished:
```python
from haystack.components.agents.state import State
from haystack.dataclasses import ChatMessage
from haystack.hooks import hook
@hook
def fallback_on_max_steps(state: State) -> None:
if state.get("exit_reason") == "max_agent_steps":
state.set(
"messages",
[ChatMessage.from_assistant("Sorry, I ran out of steps before finishing.")],
)
```
## 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`](../../tools/tool.mdx), [`ComponentTool`](../../tools/componenttool.mdx), [`PipelineTool`](../../tools/pipelinetool.mdx), [`AgentTool`](../../tools/agenttool.mdx), [`MCPTool`](../../tools/mcptool.mdx), [`Toolset`](../../tools/toolset.mdx), [`MCPToolset`](../../tools/mcptoolset.mdx), [`SearchableToolset`](../../tools/searchabletoolset.mdx). Tool names must be unique; duplicate names are detected at the start of each agent step, before the chat generator is called.
- `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 to `run()`.
- `user_prompt`: A Jinja2 template appended to the user-provided messages on each run. Template variables become additional inputs to `run()`. Use `required_variables` to 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”]`. Exit conditions are evaluated at runtime rather than validated at initialization, so a condition can name a tool that is only loaded later — for example, a tool passed at runtime via `run(tools=...)` or one discovered by a [`SearchableToolset`](../../tools/searchabletoolset.mdx).
- `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 via `inputs_from_state` and `outputs_to_state`. See [State](./state.mdx) for full details.
- `streaming_callback`: A callback invoked for each streamed token. Use the built-in `print_streaming_chunk` for console output.
- `max_agent_steps`: Maximum number of LLM + tool call iterations before the agent stops. Defaults to `100`.
- `raise_on_tool_invocation_failure`: If `True`, raises an exception when a tool call fails. If `False` (default), the error is passed back to the LLM as a message so it can recover.
- `hooks`: A dict mapping a hook point (`"before_run"`, `"before_llm"`, `"before_tool"`, `"after_tool"`, `"on_exit"`, `"after_run"`) to a list of hooks the agent runs at that point. Hooks receive the live `State` and influence the run by mutating it — for example, to build run-time context or require human confirmation of tool calls. See [Hooks](./hooks.mdx) and [Human in the Loop](./human-in-the-loop.mdx).
- `tool_concurrency_limit`: Maximum number of tool calls to execute at the same time. Defaults to `4`; set to `1` to disable parallel tool execution.
- `tool_streaming_callback_passthrough`: If `True`, passes the streaming callback to tools that accept it.
### Runtime overrides
`run()` also accepts parameters that override the init-time configuration for a single call:
- `tools`: Pass a list of `Tool`/`Toolset` objects, 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}`).
- `hook_context`: A dict of request-scoped resources made available to [hooks](./hooks.mdx) via `state.data["hook_context"]` — for example, a user ID or a WebSocket connection.
:::info
For the full parameter reference, see the [Agents API Documentation](/reference/agents-api).
:::
### Cloning and modifying an agent
Agent attributes are not meant to be reassigned after initialization: several parameters are processed at init time, so setting an attribute on a built agent does not reliably take effect. The recommended way to get a modified version of an existing agent is `clone()`.
`clone()` returns a new agent with the same configuration, optionally replacing some init parameters. This is useful for creating a variant of an agent you did not build yourself, such as one returned by a factory function of the [Agent Pack](./agent-pack.mdx).
```python
variant = agent.clone(system_prompt="Answer in German.", max_agent_steps=20)
```
Overrides replace the original values. To extend a list or dictionary instead, unpack the existing value and add your entries:
```python
extended = agent.clone(
tools=[*agent.tools, my_new_tool],
state_schema={**agent.state_schema, "notes": {"type": str}},
hooks={**agent.hooks, "before_llm": [my_hook]},
)
```
## Usage
### On its own
```python
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.
```python
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
```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
exit_conditions:
- text
hooks: null
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_concurrency_limit: 4
tool_streaming_callback_passthrough: false
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_integrations.components.websearch.serperdev.websearch.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).
```python
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](../generators/guides-to-generators/choosing-the-right-generator.mdx#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.
## Multimodal Inputs
Agents support multimodal inputs when paired with a vision-capable model such as `gpt-5` (OpenAI) or `gemini-2.5-flash` (Google).
Pass images alongside text by including `ImageContent` objects in the `content_parts` of a `ChatMessage`:
```python
from haystack.dataclasses import ChatMessage, ImageContent
image = ImageContent.from_url("https://example.com/chart.png")
result = agent.run(
messages=[
ChatMessage.from_user(content_parts=["What does this chart show?", image]),
],
)
```
Tools can also return `ImageContent` directly, letting the agent fetch and reason about images dynamically during its loop.
Two things are required: set `outputs_to_string={"raw_result": True}` so the Agent's tool execution skips string conversion, and return a `list[ImageContent]` (the tool result type is `str | Sequence[TextContent | ImageContent]`).
The standard Chat Completions API doesn't support images in tool results — use `OpenAIResponsesChatGenerator` (OpenAI's Responses API) instead:
```python
from typing import Annotated
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIResponsesChatGenerator
from haystack.dataclasses import ChatMessage, ImageContent
from haystack.tools import tool
@tool(outputs_to_string={"raw_result": True})
def fetch_image(
url: Annotated[str, "URL of the image to fetch and analyze"],
) -> list[ImageContent]:
"""Fetch an image from a URL so the agent can analyze its contents."""
return [ImageContent.from_url(url)]
agent = Agent(
chat_generator=OpenAIResponsesChatGenerator(model="gpt-5"),
tools=[fetch_image],
system_prompt="You are a helpful assistant that can fetch and analyze images from URLs.",
)
result = agent.run(
messages=[
ChatMessage.from_user(
"Fetch the image at https://picsum.photos/seed/haystack/640/480 and describe what you see.",
),
],
)
print(result["last_message"].text)
```
`ImageContent` can be created from a URL, a local file path, or a PDF page using the `PDFToImageContent` converter.
### In a pipeline
When an `Agent` sits inside a pipeline, use `ChatPromptBuilder` with its string template format and the `| templatize_part` filter to pass images as structured content parts:
```python
from haystack import Pipeline
from haystack.components.agents import Agent
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ImageContent
template = """
{% message role="user" %}
{{ question }}
{{ image | templatize_part }}
{% endmessage %}
"""
agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5"),
system_prompt="You are a helpful assistant that can analyze images.",
)
prompt_builder = ChatPromptBuilder(
template=template,
required_variables=["question", "image"],
)
pipeline = Pipeline()
pipeline.add_component("prompt_builder", prompt_builder)
pipeline.add_component("agent", agent)
pipeline.connect("prompt_builder.prompt", "agent.messages")
# Download or provide your own chart image as "chart.png"
image = ImageContent.from_file_path("chart.png")
result = pipeline.run(
{
"prompt_builder": {"question": "What does this chart show?", "image": image},
},
)
print(result["agent"]["last_message"].text)
```
:::tip
See these cookbooks for complete multimodal agent examples:
- [Multimodal Agents](https://haystack.deepset.ai/cookbook/multimodal_intro#multimodal-agent) — image inputs and tool use with agents
- [Gemma Chat RAG](https://haystack.deepset.ai/cookbook/gemma_chat_rag) — vision model in a RAG pipeline
:::
## Multi-Agent Systems
You can wrap an `Agent` as a tool to build multi-agent systems where specialist agents handle focused subtasks and a coordinator agent plans and delegates.
The simplest way is [`AgentTool`](../../tools/agenttool.mdx), which wraps an `Agent` and delegates a task to it as a single user message, returning only its final reply.
See [Multi-Agent Systems](../../concepts/agents/multi-agent-systems.mdx) for a full guide.
## MCP Integration
Agents work with MCP in two directions:
- **Consuming MCP tools**: Pass `MCPTool` or `MCPToolset` instances in the `tools` list to call tools on any MCP-compatible server (filesystem, browser, databases, and more). See [MCPTool](../../tools/mcptool.mdx) and [MCPToolset](../../tools/mcptoolset.mdx).
- **Exposing as an MCP server**: Use [Hayhooks](../../development/hayhooks.mdx) to deploy your agent and expose it as an MCP server, making it callable from any MCP-compatible client such as Claude Desktop or Cursor.
## Additional References
📖 Related docs:
- [State](./state.mdx) — managing shared data between tools
- [Hooks](./hooks.mdx) — running custom logic at defined points of the run loop
- [Human in the Loop](./human-in-the-loop.mdx) — intercepting tool calls for human review
- [Tool Result Offloading](./tool-result-offloading.mdx) — keeping large tool results out of the context window
📚 Tutorials:
- [Build a Tool-Calling Agent](https://haystack.deepset.ai/tutorials/43_building_a_tool_calling_agent)
- [Creating a Multi-Agent System](https://haystack.deepset.ai/tutorials/45_creating_a_multi_agent_system)
- [Human-in-the-Loop with Haystack Agents](https://haystack.deepset.ai/tutorials/47_human_in_the_loop_agent/)
🧑🍳 Cookbook:
- [Build a GitHub Issue Resolver Agent](https://haystack.deepset.ai/cookbook/github_issue_resolver_agent)
- [Multimodal Agents](https://haystack.deepset.ai/cookbook/multimodal_intro#multimodal-agent)
- [Gemma Chat RAG](https://haystack.deepset.ai/cookbook/gemma_chat_rag)
---
// File: pipeline-components/agents-1/compaction/compaction-hook
# CompactionHook
`CompactionHook` monitors an Agent's conversation before each LLM call. When the estimated context reaches a configured threshold, the hook passes the messages to a `Compactor` and writes the shorter conversation back to the Agent's state.
:::warning[Experimental]
`CompactionHook` is experimental and may change without a deprecation cycle.
:::
| | |
| --- | --- |
| **Configured on** | The [`Agent`](../agent.mdx) component under the `before_llm` [hook point](../hooks.mdx) |
| **Mandatory init variables** | `compactor`: The strategy used to shorten the messages
## Usage
Register the hook under `before_llm` and configure the context window of the Agent's model:
```python
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIResponsesChatGenerator
from haystack.hooks.compaction import CompactionHook, SlidingWindowCompactor
compaction_hook = CompactionHook(
compactor=SlidingWindowCompactor(),
context_window=400_000, # gpt-5.4-nano's context window
compact_at=0.7,
compact_to=0.4,
)
agent = Agent(
chat_generator=OpenAIResponsesChatGenerator(model="gpt-5.4-nano"),
tools=[],
hooks={"before_llm": [compaction_hook]},
max_agent_steps=50,
)
```
`CompactionHook` can only be registered under `before_llm`. The Agent raises a `ValueError` if you register it at another hook point.
## Configuration
| Parameter | Default | Description |
| --- | --- | --- |
| `compactor` | No default | A `Compactor` implementation that decides how to shorten the messages. |
| `context_window` | No default | The model's full context-window size in tokens. It must be greater than zero. |
| `compact_at` | `0.7` | The fraction of the context window at which compaction starts. Leave enough space above it for the next model response and tool results. |
| `compact_to` | `0.4` | The fraction of the context window that compaction targets. A lower value compacts less often but removes more context each time. |
| `token_counter` | `ApproximateTokenCounter()` | The counter used to estimate messages not yet included in provider-reported usage. |
The thresholds must satisfy `0 < compact_to < compact_at <= 1`. A target at or above the trigger would leave the conversation ready to compact again on the next step, so the hook rejects that configuration.
## How the hook measures context
After an LLM call, the Agent stores the generator's reported prompt-plus-completion usage in `state.data["context_tokens"]`. This count includes the system prompt, tool schemas, and provider-specific chat-template overhead. Messages appended since that call, typically tool results, are measured locally with the configured [`TokenCounter`](../../../token-counters.mdx).
If the generator does not report usage and `context_tokens` remains `0`, the hook estimates the complete conversation and tool schemas locally. The default `ApproximateTokenCounter` needs no extra dependency. You can provide another built-in or custom counter:
```python
from haystack.hooks.compaction import CompactionHook, SlidingWindowCompactor
from haystack.token_counters import TiktokenCounter
compaction_hook = CompactionHook(
compactor=SlidingWindowCompactor(),
context_window=128_000,
token_counter=TiktokenCounter(encoding="o200k_base"),
)
```
The hook subtracts estimated non-message overhead from the target passed to the compactor. This prevents the compactor from treating tool schemas or provider formatting as message tokens it can remove.
## Choosing a compactor
The compactor controls what information survives:
| Compactor | Strategy |
| --- | --- |
| [`SlidingWindowCompactor`](sliding-window-compactor.mdx) | Keeps the current task and as much complete recent conversation as fits, removing complete historical turns before it trims the task's own steps. |
| [`ToolResultPruningCompactor`](tool-result-pruning-compactor.mdx) | Replaces older, large tool results with short placeholders while keeping recent results intact. |
You can also implement the `Compactor` protocol for a custom strategy. See [Context Compaction](../compaction.mdx#creating-a-custom-compactor) for its requirements.
## Lifecycle and serialization
The hook warms up its token counter and compactor when they provide a `warm_up` method, and delegates `close` to the compactor when supported. Its asynchronous lifecycle methods prefer the compactor's async implementation when one exists.
`to_dict()` serializes the hook together with its compactor and token counter. `from_dict()` reconstructs both nested objects, so an Agent configured with the hook can be serialized and restored.
---
// File: pipeline-components/agents-1/compaction/sliding-window-compactor
# SlidingWindowCompactor
`SlidingWindowCompactor` removes older conversation history while preserving the Agent's instructions, current task, and as much complete recent conversation as the token target allows. It removes complete historical turns first, and only trims the current task's own Agent steps when removing every historical turn is not enough.
:::warning[Experimental]
`SlidingWindowCompactor` is experimental and may change without a deprecation cycle. Compaction is lossy: messages removed by this strategy cannot be recovered or summarized.
:::
## Usage
Pass the compactor to a `CompactionHook`:
```python
from haystack.hooks.compaction import CompactionHook, SlidingWindowCompactor
compaction_hook = CompactionHook(
compactor=SlidingWindowCompactor(
min_keep_steps=1,
omission_note=(
"[{num_removed} earlier messages were removed to free up context.]"
),
),
context_window=200_000,
compact_at=0.7,
compact_to=0.4,
)
```
`CompactionHook` determines when compaction runs and provides the target token count. `SlidingWindowCompactor` determines which messages to retain.
## How the sliding window is selected
The compactor divides a conversation into protected context, historical turns, and the current task's Agent steps:
1. It preserves all leading system messages as the Agent's instructions.
2. It preserves the latest user message as the current task.
3. It groups the history before that task into complete historical turns, each running from one user message up to the next.
4. It groups each assistant message and all immediately following tool-result messages into one complete Agent step.
5. Working backwards from the newest, it keeps as many complete historical turns as fit within the target.
6. Only when the current task alone still exceeds the target does it begin removing that task's own steps, oldest first.
7. It replaces what it removed with an omission note, unless the note is disabled.
Keeping complete steps ensures that an assistant tool call is not separated from its results, including batches of parallel tool calls. Incomplete tool-call exchanges are rejected by chat-completion providers. Historical turns are kept or removed in full for the same reason: an assistant reply is never retained without the user message it answers.
The target is a goal rather than a guarantee, and the conversation can end up above it rather than below. Leading system messages and the current task are never removed, and `min_keep_steps` holds on to the newest Agent steps whatever their size, so a long system prompt or a single large tool result can leave the conversation well over the target.
## Configuration
| Parameter | Default | Description |
| --- | --- | --- |
| `min_keep_steps` | `1` | The minimum number of complete recent Agent steps to preserve, even if they exceed the target. Set it to `0` to allow all completed steps to be removed. |
| `omission_note` | `"[{num_removed} earlier messages were removed from this conversation to free up context and cannot be recovered.]"` | A user message inserted where history was removed. Use `{num_removed}` to include the number of removed messages, provide custom text without the placeholder, or set it to `None` to remove history silently. |
`min_keep_steps` cannot be negative.
### Omission notes
An omission note tells the model that earlier context is missing. Without one, the shortened conversation can appear complete and the model may repeat work or behave as though it still has the removed information.
The note is left where the removed messages used to sit: directly after the leading system messages when only historical turns were removed, and directly after the latest user message when the current task's own steps were removed. Repeated compactions fold an earlier note into the new one, so the conversation carries at most one.
Compaction metadata is stored on the note, including the strategy name and the numbers of removed and retained messages.
## When the conversation is unchanged
The compactor returns `None` without changing the conversation when:
- The conversation already fits within `target_tokens`.
- There is no removable history outside the protected messages and the history it retained.
---
// File: pipeline-components/agents-1/compaction/tool-result-pruning-compactor
# ToolResultPruningCompactor
`ToolResultPruningCompactor` reduces an Agent's context by replacing older tool results with short placeholders. It keeps every tool call paired with a result, allowing the model to see which tool it called and call it again if needed.
:::warning[Experimental]
`ToolResultPruningCompactor` is experimental and may change without a deprecation cycle. Pruning is lossy: removed tool output cannot be recovered unless it was stored separately.
:::
## Usage
Pass the compactor to a `CompactionHook`:
```python
from haystack.hooks.compaction import CompactionHook, ToolResultPruningCompactor
compaction_hook = CompactionHook(
compactor=ToolResultPruningCompactor(
min_keep_steps=1,
min_tokens=200,
),
context_window=200_000,
compact_at=0.7,
compact_to=0.4,
)
```
`CompactionHook` determines when compaction runs and provides the target token count. `ToolResultPruningCompactor` replaces only as many eligible results as needed to reach that target.
## How pruning works
The compactor processes tool results from oldest to newest:
1. It leaves the conversation unchanged when it already fits within the target.
2. It protects results from at least the configured number of recent tool-calling Agent steps. Since at least one step must be kept, the current result batch remains intact until the model has acted on it.
3. It skips results already marked by context compaction, results below the token threshold, and results carrying protected metadata such as an offloaded-result reference.
4. It replaces eligible results with the configured placeholder until the estimated conversation size reaches the target.
The replacement preserves the originating tool call, error flag, and message metadata. This keeps the tool-call exchange valid for chat-completion providers while discarding the expensive result content.
The target is a goal rather than a guarantee. Protected recent results and results that do not meet the pruning rules can leave the conversation above the requested target.
## Configuration
| Parameter | Default | Description |
| --- | --- | --- |
| `min_keep_steps` | `1` | The minimum number of recent tool-calling Agent steps whose results remain intact regardless of the target. Parallel results from one step are protected together. |
| `min_tokens` | `200` | Only prune a tool-result message when it uses more than this many tokens, as measured by the configured token counter. |
| `placeholder` | ``"[Tool result removed to free up context. Call `{tool_name}` again if you need it.]"`` | Text that replaces a pruned result. Use `{tool_name}` to insert the originating tool's name. Other braces are preserved literally. |
| `skip_meta_keys` | `("tool_result_offloaded",)` | Leave a result unchanged when its metadata contains any listed key. The default protects pointers created by `ToolResultOffloadHook`. |
`min_keep_steps` must be at least `1`, and `min_tokens` cannot be negative.
### Custom placeholders
Keep custom placeholders short so replacing a result saves context. If a placeholder costs at least as many tokens as the original result, the compactor leaves that result unchanged.
```python
compactor = ToolResultPruningCompactor(
placeholder="Previous output from {tool_name} was removed. Call the tool again if needed.",
)
```
The compactor records `context_compaction` metadata on each rewritten result with the strategy name and the original tool-result message's token count.
## Token counting
The compactor uses the [`TokenCounter`](../../../token-counters.mdx) supplied by `CompactionHook` to determine whether a result exceeds `min_tokens` and whether replacing it saves context.
The complete conversation is counted once. For each eligible result, the counter then measures only the original result message and its short replacement. The compactor updates its running total using the difference between those two counts.
## Interaction with tool result offloading
[`ToolResultOffloadHook`](../tool-result-offloading.mdx) stores a tool result outside the conversation and replaces it with a reference. Pruning that reference would prevent the model from retrieving the stored content.
By default, `ToolResultPruningCompactor` skips messages with `tool_result_offloaded` metadata. These pointer messages are already small, so pruning them would save little context while removing the model's only reference to the full stored result. Add other metadata keys to `skip_meta_keys` when another hook or application feature leaves references that must remain available.
## When the conversation is unchanged
The compactor returns `None` without changing the conversation when:
- The conversation already fits within `target_tokens`.
- There are no results older than the protected steps.
- Every older result is already compacted, protected by metadata, or no larger than `min_tokens`.
- A replacement would not reduce the result's measured token count.
---
// File: pipeline-components/agents-1/compaction
# Context Compaction
Context compaction shortens an Agent's conversation so long runs do not exhaust the model's context window. It rewrites older history into a smaller representation while preserving the context the Agent needs to continue working.
Compaction is lossy. After it runs, the Agent works from a shorter record of the conversation. What survives and what is discarded depends on the compaction strategy.
## How context compaction works
Context compaction separates three responsibilities:
| Responsibility | Abstraction | Purpose |
| --- | --- | --- |
| Decide when to compact | [`CompactionHook`](compaction/compaction-hook.mdx) | Monitors the Agent's context before LLM calls and invokes a compactor after a configured threshold is reached. |
| Decide how to compact | `Compactor` protocol | Defines how a conversation is rewritten. Haystack includes [`SlidingWindowCompactor`](compaction/sliding-window-compactor.mdx) and [`ToolResultPruningCompactor`](compaction/tool-result-pruning-compactor.mdx). |
| Measure the conversation | [`TokenCounter`](../../token-counters.mdx) protocol | Estimates the size of messages and tool schemas before they are sent to a model. |
This separation lets you combine a standard trigger with different compaction strategies and token counters. For example, a local sliding window can remove old history without making an additional model call, while a custom compactor could summarize the same history with an LLM.
## Basic setup
The following example registers a `CompactionHook` under the Agent's `before_llm` [hook point](./hooks.mdx). It starts compacting at 70% of the model's context window and asks the compactor to reduce the context to approximately 40%.
```python
from typing import Annotated
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIResponsesChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.hooks.compaction import CompactionHook, SlidingWindowCompactor
from haystack.tools import tool
@tool
def fetch_page(url: Annotated[str, "The URL to fetch"]) -> str:
"""Fetch a web page and return its text."""
return "Fusion startups reported net-energy-gain milestones this year. " * 500
compaction_hook = CompactionHook(
compactor=SlidingWindowCompactor(),
context_window=400_000, # gpt-5.4-nano's context window
compact_at=0.7,
compact_to=0.4,
)
agent = Agent(
chat_generator=OpenAIResponsesChatGenerator(model="gpt-5.4-nano"),
tools=[fetch_page],
system_prompt="You are a research assistant. Fetch pages as needed and cite what you used.",
hooks={"before_llm": [compaction_hook]},
)
result = agent.run(
messages=[ChatMessage.from_user("Summarize recent fusion energy milestones.")],
)
print(result["last_message"].text)
```
See [`CompactionHook`](compaction/compaction-hook.mdx) for threshold configuration, context measurement, lifecycle, and serialization.
## Compaction strategies
Compactors receive the current messages, a target token count, and the same token counter used to measure the context. They return a shorter replacement conversation or `None` when there is nothing useful to change.
| Compactor | Strategy | Trade-off |
| --- | --- | --- |
| [`SlidingWindowCompactor`](compaction/sliding-window-compactor.mdx) | Preserves the Agent's instructions and latest user task, keeps complete historical turns while they fit, and trims the current task's own Agent steps only when that is not enough. | Fast and local, but discarded information is not summarized. |
| [`ToolResultPruningCompactor`](compaction/tool-result-pruning-compactor.mdx) | Replaces older, large tool results with short placeholders while preserving tool-call/result structure. | Retains the shape of the run and recent results, but removes the content of pruned results. |
## Combining compaction strategies
Register multiple `CompactionHook` instances at `before_llm` to apply progressively more aggressive strategies. Hooks run in list order against the same Agent state, so each hook measures the messages left by the previous one.
For example, prune large tool results first and use a sliding window as a fallback:
```python
from haystack.hooks.compaction import (
CompactionHook,
SlidingWindowCompactor,
ToolResultPruningCompactor,
)
prune_tool_results = CompactionHook(
compactor=ToolResultPruningCompactor(),
context_window=400_000,
compact_at=0.7,
compact_to=0.4,
)
drop_old_steps = CompactionHook(
compactor=SlidingWindowCompactor(),
context_window=400_000,
compact_at=0.7,
compact_to=0.4,
)
agent = Agent(
chat_generator=OpenAIResponsesChatGenerator(model="gpt-5.4-nano"),
tools=[fetch_page],
hooks={"before_llm": [prune_tool_results, drop_old_steps]},
)
```
If pruning brings the updated context below `compact_at`, the sliding-window hook does nothing. If pruning returns `None` because no eligible results remain, or it shortens the context without getting below the trigger, the sliding window removes historical turns and then, if needed, the current task's oldest Agent steps. A result the pruning compactor already replaced with a placeholder stays with the historical turn it belongs to, so its tool call keeps an answer. Configure both hooks for the same model context window and compatible token counters so they make decisions from comparable estimates.
### Creating a custom compactor
Implement the `Compactor` protocol when you need a different strategy, such as summarizing older messages or selectively shortening tool results.
```python
from typing import Any
from haystack.core.serialization import default_to_dict
from haystack.dataclasses import ChatMessage
from haystack.hooks.compaction import Compactor
from haystack.token_counters import TokenCounter
class CustomCompactor(Compactor):
def compact(
self,
messages: list[ChatMessage],
target_tokens: int,
token_counter: TokenCounter,
) -> list[ChatMessage] | None:
# Return a shorter, valid conversation or None when nothing should change.
...
def to_dict(self) -> dict[str, Any]:
return default_to_dict(self)
```
A compactor must follow these rules:
1. Return `None` unless the conversation actually gets smaller.
2. Return a new list without modifying the input `messages` list.
3. Keep tool calls together with all their result messages. Chat-completion APIs reject incomplete tool-call exchanges.
The `target_tokens` value is a goal rather than a guarantee. When the target conflicts with context the Agent must retain, preserve the required context and get as close to the target as possible.
`compact_async()` calls `compact()` by default. Override it when compaction performs I/O, such as calling an LLM, so asynchronous Agent runs are not blocked. Use `to_dict()` to serialize constructor settings. The protocol's default `from_dict()` handles plain constructor values; override it when serialized values must be reconstructed first, such as a `Secret` or nested component.
## Token counters
The default `ApproximateTokenCounter` estimates tokens from text length and needs no extra dependency. You can configure another built-in or custom [`TokenCounter`](../../token-counters.mdx) when you need a model- or provider-specific estimate.
Token counters can also include tool schemas and non-text content in the estimate. Consult the page for the counter you use to understand how it handles images and files.
## Context compaction and tool result offloading
[Tool result offloading](./tool-result-offloading.mdx) solves an adjacent problem: it writes large tool results to a store and leaves a pointer in the conversation. The two approaches work well together — offloading keeps individual results small as they arrive, while compaction bounds the conversation as a whole.
An offloaded result is represented by a reference to the stored content. If a compactor removes or rewrites that message, the model loses the reference it needs to read the content again.
`ToolResultPruningCompactor` skips results marked as offloaded by default, preserving their stored-content references.
---
// File: pipeline-components/agents-1/hooks
# Hooks
Hooks let you run custom logic at defined points of an [`Agent`](./agent.mdx)'s run loop — at the start and end of a run, before each LLM call, before and after tool execution, and on exit.
## Overview
Pass `hooks` to the `Agent` as a dictionary mapping a *hook point* to a list of hooks the Agent runs at that point. Each hook receives the live [`State`](./state.mdx) and influences the run by mutating it in place. Hooks for a hook point run in list order, and the same hook can be registered under multiple hook points.
This enables patterns such as building run-time system context, retrieving memories before the first LLM call, auditing or intercepting tool calls, and requiring a condition to hold before the Agent is allowed to finish.
### Hook points
- `before_run`: Runs once per run, after the state is initialized and before the first chat-generator call. Use it to rewrite the initial messages or seed state — for example, to turn the user query into a task brief — without re-running on every step like `before_llm` does.
- `before_llm`: Runs before each chat-generator call.
- `before_tool`: Runs after the model requests tool calls, before any tools run. After these hooks run, the Agent re-reads the current last message from `state.data["messages"]`. If that message contains tool calls, those calls are executed. If it does not, no tools run for that step, no tool-based exit condition is triggered, and the Agent loops back to the next LLM call unless `max_agent_steps` has been reached.
- `after_tool`: Runs after tools execute, once their result messages are in `state.data["messages"]`, before the exit-condition check and the next LLM call. Use it to rewrite the freshly produced tool-result messages — for example, to offload, redact, truncate, or summarize results. It does not run on the plain-text exit step. It does still run when a `before_tool` hook removed the pending tool calls: no tools executed on that step, so don't assume the last message is a fresh tool result.
- `on_exit`: Runs when the Agent is about to stop on an exit condition. An `on_exit` hook can keep the Agent running by setting the `continue_run` control flag (`state.set("continue_run", True)`), usually alongside a message telling the model what to do next. `on_exit` hooks run when the Agent stops on an exit condition, but not when it stops because `max_agent_steps` is reached — use `after_run` for logic that must run however the run ends.
- `after_run`: Runs once per run, after the step loop has ended and before the Agent builds its return value — regardless of whether the run stopped on an exit condition or because `max_agent_steps` was reached (unlike `on_exit`). Mutations to the state, such as appending a final message, are reflected in the returned `messages` / `last_message` and `state_schema` outputs. Setting `continue_run` here has no effect.
Registering a hook under an unknown hook point raises a `ValueError` at construction. A hook class can declare an `allowed_hook_points` attribute listing the hook points it supports; the Agent validates it and fails fast if the hook is registered somewhere it doesn't belong.
### State keys for hooks
The Agent manages a few state keys that hooks interact with. Like the run-metadata keys (`step_count`, `token_usage`, `tool_call_counts`), they are reserved — using any of them in your own `state_schema` raises a `ValueError`. See [State](./state.mdx#schema-definition) for the full list:
- `continue_run`: Set by an `on_exit` hook to keep the Agent running.
- `tools`: The tools available in the current step, for hooks to inspect.
- `hook_context`: Request-scoped resources passed to `Agent.run(hook_context={...})` / `run_async(hook_context={...})`. Hooks read it with `state.data["hook_context"]` or `state.data.get("hook_context")` — use it for per-request resources such as a user ID, a WebSocket, or a database client. Avoid the plain `state.get("hook_context")` here: `State.get` returns a deep copy of the value, which often fails for the kinds of resources stored in this dict (such as a WebSocket or a database client).
- `context_tokens`: An approximate count of the tokens currently in the context window, refreshed after each LLM call with that reply's prompt-plus-completion tokens (read it with `state.get("context_tokens")`). Unlike `token_usage`, which accumulates over the run, this is replaced on every call. It's `0` until the first reply that reports usage and doesn't count messages appended after the latest call. A `before_llm` hook can read it to trigger context compaction once it crosses a threshold.
Hooks can also read the automatically tracked run metadata: `step_count`, `token_usage`, and `tool_call_counts`.
## Creating hooks
### With the `@hook` decorator
The `@hook` decorator wraps a function taking a single `State` argument into a hook. A regular function becomes the hook's sync path, a coroutine function its async path. To give a single hook both paths, construct a `FunctionHook` directly with both `function` and `async_function`.
The example below registers a hook at each of `before_llm`, `before_tool`, and `on_exit` to show what hooks can do:
```python
from datetime import datetime, timezone
from typing import Annotated
from haystack.components.agents import Agent
from haystack.components.agents.state import State, replace_values
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.hooks import hook
from haystack.tools import tool
@tool
def search(query: Annotated[str, "The search query"]) -> str:
"""Search the web."""
# Placeholder: would call a real search API
return "Fusion startups reported net-energy-gain milestones this year."
@hook
def build_context(state: State) -> None:
# before_llm: build run-time system context once, before the first model call.
if state.get("step_count") == 0:
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
system = ChatMessage.from_system(
f"You are a research assistant. The current time is {now}.",
)
state.set(
"messages",
[system, *state.data["messages"]],
handler_override=replace_values,
)
@hook
def audit_tool_calls(state: State) -> None:
# before_tool: see which tools the model is about to run.
pending = state.data["messages"][-1].tool_calls
print(f"about to run: {[tc.tool_name for tc in pending]}")
@hook
def require_search(state: State) -> None:
# on_exit: keep going until the agent has actually searched.
if state.get("tool_call_counts", {}).get("search", 0) == 0:
state.set("messages", [ChatMessage.from_system("Search before answering.")])
state.set("continue_run", True)
agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
tools=[search],
hooks={
"before_llm": [build_context],
"before_tool": [audit_tool_calls],
"on_exit": [require_search],
},
)
result = agent.run(
messages=[
ChatMessage.from_user("What are the latest developments in fusion energy?"),
],
)
print(result["last_message"].text)
```
### Class-based hooks
A hook is any object with a `run(state)` method; it may additionally define `run_async(state)` for true async behavior. Class-based hooks may also implement the optional lifecycle methods `warm_up` / `warm_up_async` and `close` / `close_async`. The Agent calls them from its own `warm_up` / `close`, so a hook can defer opening clients or reading credentials until warm-up and release them on close. Because warm-up runs before every Agent run, a hook should not repeat expensive initialization: return early if the work is already done, as in `if self._client is not None: return`.
When a class-based hook should be serializable (so an Agent using it can be serialized), implement `to_dict` / `from_dict`: store serializable constructor arguments on the hook and rebuild runtime clients from those values.
The example below is an `on_exit` hook that grades the Agent's answer with its own LLM and asks the Agent to improve a weak answer before finishing:
```python
from typing import Any
from haystack.components.agents import Agent
from haystack.components.agents.state import State
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.core.serialization import default_from_dict, default_to_dict
from haystack.dataclasses import ChatMessage
class GradeFinalAnswer:
"""Grade the Agent's answer with an LLM and ask it to improve a weak answer before finishing."""
def __init__(self, model: str = "gpt-5.4-nano"):
self.model = model
self._judge = OpenAIChatGenerator(model=self.model)
def warm_up(self) -> None:
# The Agent calls this before every run, but OpenAIChatGenerator.warm_up
# creates its client only on the first call, so repeating it is safe and cheap.
self._judge.warm_up()
def close(self) -> None:
# Release the judge's client during the Agent's close.
self._judge.close()
def run(self, state: State) -> None:
answer = state.data["messages"][-1].text or ""
verdict = (
self._judge.run(
messages=[
ChatMessage.from_user(
f"Reply with only PASS or FAIL. Is this answer complete?\n\n{answer}",
),
],
)["replies"][0].text
or ""
)
if "FAIL" in verdict.upper():
state.set(
"messages",
[
ChatMessage.from_user(
"Your answer was incomplete. Please improve it.",
),
],
)
state.set("continue_run", True)
def to_dict(self) -> dict[str, Any]:
return default_to_dict(self, model=self.model)
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "GradeFinalAnswer":
return default_from_dict(cls, data)
agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
hooks={"on_exit": [GradeFinalAnswer()]},
)
result = agent.run(messages=[ChatMessage.from_user("Explain how vaccines work.")])
print(result["last_message"].text)
```
## Ready-made hooks
Haystack ships several ready-made hooks, each in its own submodule of `haystack.hooks`:
- `CompactionHook` (from `haystack.hooks.compaction`): A `before_llm` hook that shortens an Agent's conversation when it reaches a configured fraction of the model's context window. A `Compactor` determines how the conversation is shortened. See [Context Compaction](./compaction.mdx).
- `ConfirmationHook` (from `haystack.hooks.human_in_the_loop`): A `before_tool` hook that applies Human-in-the-Loop confirmation strategies to pending tool calls — a human can confirm, modify, or reject the tool calls the model requested before they run. See [Human in the Loop](./human-in-the-loop.mdx).
- `ToolResultOffloadHook` (from `haystack.hooks.tool_result_offloading`): An `after_tool` hook that offloads tool results to a `ToolResultStore` (such as `FileSystemToolResultStore`) and replaces them in the conversation with a compact pointer, so the next LLM call sees a reference instead of the full result. Per-tool policies (`AlwaysOffload`, `NeverOffload`, `OffloadOverChars`) control which results are offloaded. See [Tool Result Offloading](./tool-result-offloading.mdx).
---
// File: pipeline-components/agents-1/human-in-the-loop
# Human in the Loop
Human-in-the-loop (HITL) lets you intercept an agent's tool calls before they are executed.
A human can **confirm**, **reject**, or **modify** the parameters of each tool call in real time.
This is useful for high-stakes operations - such as sending emails, modifying databases, or making API calls - where you want a human to review the action first.
## Overview
HITL is one application of the Agent's general [hooks](./hooks.mdx) mechanism: a `ConfirmationHook` registered under the `before_tool` hook point intercepts the tool calls the model requested before they run, and confirms, modifies, or rejects them by rewriting the conversation in the Agent's `State`.
The HITL system is composed of these layers:
- **`ConfirmationHook`** - the `before_tool` hook that applies your confirmation strategies to pending tool calls. Its `confirmation_strategies` mapping accepts a single tool name, a tuple of tool names, or the wildcard `"*"` that applies to any tool without a more specific entry.
- **Strategy** - decides what to do when a tool is about to be called. The built-in `BlockingConfirmationStrategy` pauses execution and asks a human.
- **Policy** - decides *when* to ask. Built-in policies: `AlwaysAskPolicy`, `NeverAskPolicy`, `AskOncePolicy`.
- **UI** - the interface used to ask the human. Built-in UIs: `RichConsoleUI` (requires `rich`) and `SimpleConsoleUI` (stdlib only).
When the agent is about to invoke a tool, the strategy checks the policy.
If the policy says to ask, the UI prompts the human with the tool name, description, and parameters. The human can:
- **Confirm** (`y`) - execute as-is
- **Reject** (`n`) - skip execution and feed rejection feedback back to the LLM
- **Modify** (`m`) - edit the parameters before execution
The agent then continues with the human's decision.
:::info
Strategies see only the arguments the model produced for a tool call. Values injected from [`State`](./state.mdx) via a tool's `inputs_from_state` mapping are not included in what is presented for confirmation — that injection happens at tool execution time.
:::
## Usage
### Basic setup
```python
from typing import Annotated
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.hooks.human_in_the_loop import (
AlwaysAskPolicy,
BlockingConfirmationStrategy,
ConfirmationHook,
SimpleConsoleUI,
)
from haystack.tools import tool
@tool
def send_email(
to: Annotated[str, "The recipient email address"],
subject: Annotated[str, "The email subject line"],
body: Annotated[str, "The email body"],
) -> str:
"""Send an email to a recipient."""
return f"Email sent to {to}."
strategy = BlockingConfirmationStrategy(
confirmation_policy=AlwaysAskPolicy(),
confirmation_ui=SimpleConsoleUI(),
)
agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5.4-mini"),
tools=[send_email],
hooks={
"before_tool": [
ConfirmationHook(confirmation_strategies={"send_email": strategy}),
],
},
)
result = agent.run(
messages=[ChatMessage.from_user("Send a welcome email to alice@example.com")],
)
```
When the agent calls `send_email`, the terminal will pause and show:
```
--- Tool Execution Request ---
Tool: send_email
Description: Send an email to a recipient.
Arguments:
to: alice@example.com
subject: Welcome!
body: Hi Alice, welcome aboard!
------------------------------
Confirm execution? (y=confirm / n=reject / m=modify):
```
### Using RichConsoleUI
`RichConsoleUI` provides a styled terminal prompt using the [`rich`](https://github.com/Textualize/rich) library:
```shell
pip install rich
```
```python
from haystack.hooks.human_in_the_loop import RichConsoleUI
strategy = BlockingConfirmationStrategy(
confirmation_policy=AlwaysAskPolicy(),
confirmation_ui=RichConsoleUI(),
)
```
### Applying strategies to multiple tools
You can configure different strategies per tool, share one strategy across a group of tools using a tuple key, or set a default for all tools with the wildcard `"*"` (applied to any tool without a more specific entry):
```python
@tool
def delete_record(record_id: Annotated[str, "The ID of the record to delete"]) -> str:
"""Delete a record from the database."""
return f"Record {record_id} deleted."
@tool
def update_record(
record_id: Annotated[str, "The ID of the record to update"],
data: Annotated[str, "The new data as a JSON string"],
) -> str:
"""Update a record in the database."""
return f"Record {record_id} updated."
@tool
def search(query: Annotated[str, "The search query"]) -> str:
"""Search the knowledge base."""
return f"Results for: {query}"
ask_strategy = BlockingConfirmationStrategy(
confirmation_policy=AlwaysAskPolicy(),
confirmation_ui=SimpleConsoleUI(),
)
confirmation_hook = ConfirmationHook(
confirmation_strategies={
# Share one strategy across multiple sensitive tools using a tuple key
("send_email", "delete_record", "update_record"): ask_strategy,
# search has no strategy - always executes without asking
},
)
agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5.4-mini"),
tools=[send_email, delete_record, update_record, search],
hooks={"before_tool": [confirmation_hook]},
)
```
### Customizing feedback messages
When a tool call is rejected or modified, `BlockingConfirmationStrategy` sends a message back to the LLM explaining what happened. Three optional template parameters control these messages — each has a sensible default, so you only need to set them if you want different wording:
- `reject_template`: Sent to the LLM when the user rejects a tool call. Must include a `{tool_name}` placeholder. Default: `"Tool execution for '{tool_name}' was rejected by the user."`
- `modify_template`: Sent when the user modifies the parameters. Must include `{tool_name}` and `{final_tool_params}` placeholders. Default: `"The parameters for tool '{tool_name}' were updated by the user to:\n{final_tool_params}"`
- `user_feedback_template`: Appends the user's optional free-text feedback to either message. Must include a `{feedback}` placeholder. Default: `"With user feedback: {feedback}"`
```python
strategy = BlockingConfirmationStrategy(
confirmation_policy=AlwaysAskPolicy(),
confirmation_ui=SimpleConsoleUI(),
reject_template="Skipping '{tool_name}' — rejected by operator.",
modify_template="Updated parameters for '{tool_name}': {final_tool_params}",
user_feedback_template="Reason: {feedback}",
)
```
## Policies
Policies control *when* the human is asked.
| Policy | Behavior |
| --- | --- |
| `AlwaysAskPolicy` | Ask every time the tool is called |
| `NeverAskPolicy` | Never ask - always proceed (useful for toggling HITL off without removing the strategy) |
| `AskOncePolicy` | Ask once per unique `(tool_name, parameters)` combination. Remembers confirmed calls and skips asking on repeats. |
### Custom policy
You can implement your own policy by subclassing `ConfirmationPolicy` from `haystack.hooks.human_in_the_loop.types`:
```python
from haystack.hooks.human_in_the_loop.types import (
ConfirmationPolicy,
ConfirmationUIResult,
)
from typing import Any
class AskForSensitiveParamsPolicy(ConfirmationPolicy):
"""Only ask when the 'to' parameter looks like an external email domain."""
def should_ask(
self,
tool_name: str,
tool_description: str,
tool_params: dict[str, Any],
) -> bool:
to = tool_params.get("to", "")
return not to.endswith("@mycompany.com")
```
For stateful policies, also implement `update_after_confirmation`.
It is called after the user responds and receives the full `ConfirmationUIResult`, letting you update internal state based on the outcome.
The following policy asks once per tool name and skips re-asking for any tool the user has already confirmed:
```python
from haystack.hooks.human_in_the_loop.types import ConfirmationPolicy
from haystack.hooks.human_in_the_loop import ConfirmationUIResult
from typing import Any
class AskOncePerToolPolicy(ConfirmationPolicy):
"""Ask once per tool name, regardless of parameters. Skip on repeat confirmed calls."""
def __init__(self) -> None:
self._confirmed_tools: set[str] = set()
def should_ask(
self,
tool_name: str,
tool_description: str,
tool_params: dict[str, Any],
) -> bool:
return tool_name not in self._confirmed_tools
def update_after_confirmation(
self,
tool_name: str,
tool_description: str,
tool_params: dict[str, Any],
confirmation_result: ConfirmationUIResult,
) -> None:
if confirmation_result.action == "confirm":
self._confirmed_tools.add(tool_name)
```
## Dataclasses
### `ConfirmationUIResult`
Returned by the UI after the human responds.
| Field | Type | Description |
| --- | --- | --- |
| `action` | `str` | `"confirm"`, `"reject"`, or `"modify"` |
| `feedback` | `str \| None` | Optional free-text feedback from the human |
| `new_tool_params` | `dict \| None` | Replacement parameters when action is `"modify"` |
### `ToolExecutionDecision`
Returned by the strategy to the agent.
| Field | Type | Description |
| --- | --- | --- |
| `tool_name` | `str` | Name of the tool |
| `execute` | `bool` | Whether to execute the tool |
| `tool_call_id` | `str \| None` | ID of the tool call |
| `feedback` | `str \| None` | Feedback message passed back to the LLM on rejection or modification |
| `final_tool_params` | `dict \| None` | Final parameters to use for execution |
## Example: HITL with Hayhooks and Open WebUI
The [hitl-hayhooks-redis-openwebui](https://github.com/deepset-ai/hitl-hayhooks-redis-openwebui) repository shows a full production-style HITL setup using a Haystack Agent served via [Hayhooks](https://github.com/deepset-ai/hayhooks) with approval dialogs rendered in [Open WebUI](https://github.com/open-webui/open-webui).
The key pattern it demonstrates is a custom `RedisConfirmationStrategy` that receives per-request resources - a Redis client and an async event queue - at runtime. Pass such resources via the generic `hook_context` run argument (`agent.run(messages=[...], hook_context={"redis": client})`). `ConfirmationHook` reads this dict from state with `state.data["hook_context"]` (not `state.get`, which returns a deep copy that fails for live resources like clients and queues - see [Hooks](./hooks.mdx)) and passes it to each strategy's `run()` as the `confirmation_strategy_context` keyword argument, which is how a custom strategy receives the Redis client and event queue:
- When a tool call is about to execute, the strategy emits a `tool_call_start` SSE event and blocks on `Redis BLPOP` waiting for an approval decision.
- The Open WebUI Pipe function receives the SSE event, shows the user a confirmation dialog, then writes `approved` or `rejected` to Redis via `LPUSH`.
- Once Redis unblocks, the strategy returns a `ToolExecutionDecision` and the agent continues.
This is a good reference if you need non-blocking HITL in a web or server environment where `SimpleConsoleUI` and `RichConsoleUI` are not suitable.
## Custom UI
Implement `ConfirmationUI` from `haystack.hooks.human_in_the_loop.types` to build your own interface - for example, a web-based approval queue:
```python
from haystack.hooks.human_in_the_loop.types import ConfirmationUI
from haystack.hooks.human_in_the_loop import ConfirmationUIResult
from typing import Any
class WebhookApprovalUI(ConfirmationUI):
"""Sends a webhook and waits for an async approval response."""
def get_user_confirmation(
self,
tool_name: str,
tool_description: str,
tool_params: dict[str, Any],
) -> ConfirmationUIResult:
# Send approval request to your system and wait for response
response = send_approval_request_and_wait(tool_name, tool_params)
return ConfirmationUIResult(
action=response["action"],
feedback=response.get("feedback"),
)
```
---
// File: pipeline-components/agents-1/state
# State
`State` is a container for storing shared information during Agent and Tool execution.
It provides a structured way to share data between tools, accumulate results across multiple tool calls, and surface them alongside the agent's final answer.
## Overview
When building agents that use multiple tools, you often need tools to share information or accumulate results across iterations.
State provides centralized storage that all tools can read from and write to.
For example, a search tool called multiple times can append its results to a shared `documents` list, which is then returned alongside the agent's final answer for source inspection.
State uses a schema-based approach where you define:
- What data can be stored,
- The type of each piece of data,
- How values are merged when updated.
The Agent creates and manages the `State` object internally. You shouldn't need to instantiate it directly.
You interact with it through tool definitions (`inputs_from_state`, `outputs_to_state`, or a `state: State` parameter) and read results from the agent's output dict.
### Supported Types
State supports standard Python types:
- Basic types: `str`, `int`, `float`, `bool`, `dict`
- List types: `list`, `list[str]`, `list[int]`, `list[Document]`
- Union types: `str | int`, `str | None`
- Custom classes and data classes.
### Automatic Message Handling
State automatically includes a `messages` field that stores the full conversation history during execution.
You don't need to define this in your schema.
It uses `list[ChatMessage]` type with the `merge_lists` handler, so new messages are appended on each iteration.
### State API
| Method | Description |
| --- | --- |
| `state.get(key, default=None)` | Read a value; returns `default` if the key doesn't exist |
| `state.set(key, value)` | Write a value, merged using the schema's handler |
| `state.has(key)` | Returns `True` if the key exists in state |
| `state.data` | Returns a snapshot of all current state as a `dict` |
## Schema Definition
The schema defines what data can be stored and how values are updated. Each schema entry consists of:
- `type` (required): The Python type for this field (for example, `str`, `int`, `list`)
- `handler` (optional): A callable that determines how new values are merged when `set()` is called
```python
{
"parameter_name": {
"type": SomeType, # Required: expected Python type
"handler": some_func, # Optional: merge function
},
}
```
If you don't specify a handler, State automatically assigns a default based on the type.
:::info Reserved keys
The `Agent` manages some state keys itself and rejects them in a user-provided `state_schema` with a `ValueError`:
- The run-metadata keys `step_count`, `token_usage`, `tool_call_counts`, and `exit_reason`, which the Agent populates automatically during a run: tools and hooks can read them from the live `State`, and they are returned in the result dictionary. `exit_reason` reports why the Agent stopped (`"text"`, the name of the tool that triggered a tool exit condition, or `"max_agent_steps"`).
- The hook-facing keys `continue_run` (set by an `on_exit` hook to keep the Agent running), `tools` (the tools available in the current step, for hooks to inspect), `hook_context` (the request-scoped resources passed to `Agent.run(hook_context={...})`), and `context_tokens` (an approximate count of the tokens currently in the context window, refreshed after each LLM call for hooks to read — for example, to trigger context compaction). Unlike the run-metadata keys, these are not returned in the result dictionary.
If one of your state keys clashes, rename it (for example, `my_token_usage`).
:::
### Default Handlers
State provides two built-in merge behaviors (importable from `haystack.components.agents.state`):
- **`merge_lists`**: Appends to the existing list (default for list types)
- **`replace_values`**: Overwrites the existing value (default for non-list types)
```python
from haystack.components.agents import State
schema = {
"documents": {"type": list}, # uses merge_lists by default
"user_name": {"type": str}, # uses replace_values by default
}
state = State(schema=schema)
state.set("documents", [1, 2])
state.set("documents", [3, 4])
print(state.get("documents")) # [1, 2, 3, 4]
state.set("user_name", "Alice")
state.set("user_name", "Bob")
print(state.get("user_name")) # "Bob"
```
### Custom Handlers
Custom handlers are useful when the default `merge_lists` or `replace_values` behaviors don't fit your needs.
A handler takes the current state value and the new value and returns the merged result.
The example below uses a deduplication handler, useful when multiple tool calls might return overlapping results and you want to avoid accumulating duplicates in state:
```python
def deduplicate(current_value: list | None, new_value: list) -> list:
"""Append new items, skipping any already in the list."""
existing = set(current_value or [])
return (current_value or []) + [item for item in new_value if item not in existing]
schema = {"doc_ids": {"type": list, "handler": deduplicate}}
state = State(schema=schema)
state.set("doc_ids", ["doc-1", "doc-2"])
state.set("doc_ids", ["doc-2", "doc-3"])
print(state.get("doc_ids")) # ["doc-1", "doc-2", "doc-3"]
```
You can also override the handler for a single `set()` call:
```python
from haystack.components.agents import State
def concatenate_strings(current: str | None, new: str) -> str:
return f"{current}-{new}" if current else new
state = State(schema={"user_name": {"type": str}})
state.set("user_name", "Alice")
state.set("user_name", "Bob", handler_override=concatenate_strings)
print(state.get("user_name")) # "Alice-Bob"
```
## Using State
Define a `state_schema` when creating the Agent.
State keys declared in `state_schema` are exposed as output keys on the agent's result dict alongside `messages` and `last_message`.
Tools interact with State through three mechanisms:
- **`outputs_to_state`**: Write tool results to state keys after the tool runs.
- **`inputs_from_state`**: Inject state values into tool parameters before the tool runs.
- **Direct `State` injection**: Add a `state: State` parameter to your tool function's signature. The Agent detects the `State` annotation and injects the live `State` object automatically, so you can read or write any key defined in the schema. The `State` object is never exposed to the LLM's parameter schema.
### Reading from State: `inputs_from_state`
`inputs_from_state` maps state keys to function parameter names using the format `{"state_key": "param_name"}`.
The value is injected from state before the tool runs, so the LLM never needs to provide it.
Parameters mapped via `inputs_from_state` are automatically excluded from the LLM's parameter schema.
The model never sees or provides them:
```python
from typing import Annotated
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.generators.utils import print_streaming_chunk
from haystack.dataclasses import ChatMessage
from haystack.tools import tool
@tool(inputs_from_state={"user_name": "user_context"})
def search_documents(
query: Annotated[str, "The search query"],
user_context: str, # injected from state; excluded from LLM schema
) -> dict:
"""Search documents using query and user context."""
return {"results": [f"Found results for '{query}' (user: {user_context})"]}
agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
tools=[search_documents],
system_prompt="Use the search_documents tool to find information.",
streaming_callback=print_streaming_chunk,
state_schema={"user_name": {"type": str}},
)
result = agent.run(
messages=[ChatMessage.from_user("Search for Python tutorials")],
user_name="Alice", # state key "user_name" is pre-populated by passing user_name= to agent.run()
)
print(result["last_message"].text)
```
### Writing to State: `outputs_to_state`
The `outputs_to_state` parameter maps tool output keys to state keys. Each entry supports two optional fields:
```python
{
"state_key": {
"source": "tool_output_key", # which key to read from the tool's return dict; omit to store the entire dict
"handler": some_func, # override the schema's merge handler for this mapping only
},
}
```
```python
from typing import Annotated
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.generators.utils import print_streaming_chunk
from haystack.dataclasses import ChatMessage
from haystack.tools import tool
@tool(
outputs_to_state={
"documents": {"source": "documents"},
"result_count": {"source": "count"},
"last_query": {"source": "query"},
},
)
def retrieve_documents(
query: Annotated[str, "The search query"],
) -> dict:
"""Retrieve relevant documents."""
return {
"documents": [
{"title": "Doc 1", "content": "Content about Python"},
{"title": "Doc 2", "content": "More about Python"},
],
"count": 2,
"query": query,
}
agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
tools=[retrieve_documents],
system_prompt="Use the retrieve_documents tool to find information.",
streaming_callback=print_streaming_chunk,
state_schema={
"documents": {"type": list},
"result_count": {"type": int},
"last_query": {"type": str},
},
)
result = agent.run(messages=[ChatMessage.from_user("Find information about Python")])
print(f"Documents: {result['documents']}")
print(f"Result count: {result['result_count']}")
print(f"Last query: {result['last_query']}")
```
If you omit `source`, the entire tool result dict is stored under the state key:
```python
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.generators.utils import print_streaming_chunk
from haystack.dataclasses import ChatMessage
from haystack.tools import tool
@tool(outputs_to_state={"user_info": {}})
def get_user_info() -> dict:
"""Get user information."""
return {"name": "Alice", "email": "alice@example.com", "role": "admin"}
agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
tools=[get_user_info],
system_prompt="Use the get_user_info tool to look up user details.",
streaming_callback=print_streaming_chunk,
state_schema={"user_info": {"type": dict}},
)
result = agent.run(messages=[ChatMessage.from_user("What are the user's details?")])
print(result["last_message"].text)
print(f"User info: {result['user_info']}")
```
### Combining Inputs and Outputs
Tools can both read from and write to State, enabling tool chaining across iterations.
This example builds on `retrieve_documents` from the previous section:
```python
@tool(
inputs_from_state={"documents": "documents"},
outputs_to_state={
"final_docs": {"source": "processed_docs"},
"final_count": {"source": "processed_count"},
},
)
def process_documents(
max_results: Annotated[int, "Maximum number of documents to return"],
documents: list = None, # injected from state; LLM does not provide this
) -> dict:
"""Process retrieved documents and return a filtered subset."""
processed = (documents or [])[:max_results]
return {"processed_docs": processed, "processed_count": len(processed)}
agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
tools=[retrieve_documents, process_documents], # chained through state
system_prompt="Use the available tools to retrieve and process documents.",
streaming_callback=print_streaming_chunk,
state_schema={
"documents": {"type": list},
"result_count": {"type": int},
"last_query": {"type": str},
"final_docs": {"type": list},
"final_count": {"type": int},
},
)
result = agent.run(
messages=[ChatMessage.from_user("Find and process 3 documents about Python")],
)
print(f"Processed {result['final_count']} documents")
```
### Injecting State Directly into Tools
As an alternative to `inputs_from_state` and `outputs_to_state`, a tool can declare a `state: State` parameter to receive the live `State` object at invocation time.
This lets the tool read from and write to any number of state keys without declaring mappings upfront.
The Agent detects the `State` annotation and injects the object automatically.
It is excluded from the LLM-facing schema. The model is never asked to supply it.
Both `State` and `State | None` annotations are supported.
For function-based tools, add the `state` parameter and use the `@tool` decorator:
```python
from typing import Annotated
from haystack.components.agents import Agent, State
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.generators.utils import print_streaming_chunk
from haystack.dataclasses import ChatMessage, Document
from haystack.tools import tool
@tool
def retrieve_and_store(
query: Annotated[str, "The search query"],
state: State,
) -> str:
"""Retrieve documents and store them directly in state."""
documents = [Document(content=f"Result for '{query}'")]
state.set("documents", documents)
user_name = state.get("user_name", "unknown")
return f"Retrieved {len(documents)} document(s) for {user_name}"
agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
tools=[retrieve_and_store],
system_prompt="Use the retrieve_and_store tool to find documents.",
streaming_callback=print_streaming_chunk,
state_schema={"documents": {"type": list[Document]}, "user_name": {"type": str}},
)
result = agent.run(
messages=[ChatMessage.from_user("Find documents about Python")],
user_name="Alice",
)
print(result["last_message"].text)
print(result["documents"])
```
For component-based tools, declare a `State` input socket on the `run` method and wrap it with `ComponentTool`:
```python
from haystack import component
from haystack.components.agents import Agent, State
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.generators.utils import print_streaming_chunk
from haystack.dataclasses import ChatMessage, Document
from haystack.tools import ComponentTool
@component
class DocumentRetriever:
"""Retrieve documents and store them in state."""
@component.output_types(reply=str)
def run(self, query: str, state: State) -> dict[str, str]:
"""
Retrieve documents based on query and store them in state.
:param query: The search query
"""
documents = [Document(content=f"Result for '{query}'")]
state.set("documents", documents)
return {"reply": f"Retrieved {len(documents)} document(s)"}
retriever_tool = ComponentTool(
component=DocumentRetriever(),
name="retrieve",
description="Retrieve documents based on a search query",
)
agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
tools=[retriever_tool],
system_prompt="Use the retrieve tool to find documents.",
streaming_callback=print_streaming_chunk,
state_schema={"documents": {"type": list[Document]}},
)
result = agent.run(messages=[ChatMessage.from_user("Find documents about Python")])
print(result["last_message"].text)
print(result["documents"])
```
---
// File: pipeline-components/agents-1/tool-result-offloading
# Tool Result Offloading
Tool result offloading writes selected tool results to a store and replaces them in the conversation with a compact pointer — a reference plus a short preview — so the next LLM call sees a reference instead of the full result.
This keeps the context window small when tools return large outputs (web pages, file contents, query results), and it is a step towards letting an Agent operate on offloaded results with follow-up tools, such as a file-reading tool that opens the referenced files.
## Overview
Tool result offloading is one application of the Agent's general [hooks](./hooks.mdx) mechanism: a `ToolResultOffloadHook` registered under the `after_tool` hook point runs after each step's tools execute and rewrites the freshly produced tool-result messages in the Agent's [`State`](./state.mdx). It only considers the current step's results; earlier conversation history is left untouched.
The system is composed of these layers:
- **`ToolResultOffloadHook`** - the `after_tool` hook that applies your offload strategies to fresh tool results. Its `offload_strategies` mapping accepts a single tool name, a tuple of tool names, or the wildcard `"*"` that applies to any tool without a more specific entry.
- **Policy** - decides *whether* a given result is offloaded. Built-in policies: `AlwaysOffload`, `NeverOffload`, `OffloadOverChars`.
- **Store** - decides *where* the full result lives. The built-in `FileSystemToolResultStore` writes results to the local file system.
When a result is offloaded, the hook writes the full text to the store and rebuilds the message with a one-line pointer in its place:
```
Tool result offloaded to '/abs/path/tool_results/2_search_call-123.txt' (18234 characters). Preview: Fusion startups reported...
```
The pointer carries the store reference, the original length, and a preview of the first `preview_chars` characters (200 by default, configurable on the hook), so the model knows roughly what was offloaded and where to find it.
## Usage
### Basic setup
The example below offloads any tool result longer than 4,000 characters to files under a local `tool_results` directory:
```python
from typing import Annotated
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.hooks.tool_result_offloading import (
FileSystemToolResultStore,
OffloadOverChars,
ToolResultOffloadHook,
)
from haystack.tools import tool
@tool
def search(query: Annotated[str, "The search query"]) -> str:
"""Search the web and return the (potentially large) results."""
# Placeholder: would call a real search API
return f"... large result for {query} ..."
offload_hook = ToolResultOffloadHook(
store=FileSystemToolResultStore(root="tool_results"),
offload_strategies={"*": OffloadOverChars(4000)},
)
agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
tools=[search],
hooks={"after_tool": [offload_hook]},
)
result = agent.run(messages=[ChatMessage.from_user("Summarize today's tech news")])
```
### Configuring what gets offloaded per tool
Each key in `offload_strategies` may be a single tool name, a tuple of tool names sharing one policy, or the wildcard `"*"`. More specific keys win over `"*"`, and a tool with no matching key (and no `"*"`) is never offloaded:
```python
from haystack.hooks.tool_result_offloading import (
AlwaysOffload,
FileSystemToolResultStore,
NeverOffload,
OffloadOverChars,
ToolResultOffloadHook,
)
offload_hook = ToolResultOffloadHook(
store=FileSystemToolResultStore(root="tool_results"),
offload_strategies={
"web_search": AlwaysOffload(), # force offload
"get_time": NeverOffload(), # opt out of the wildcard default
("read_file", "list_dir"): OffloadOverChars(4000), # tuple key: shared policy
"*": OffloadOverChars(8000), # default for any unlisted tool
},
)
```
### What is offloaded
The hook only offloads **successful, text** tool results:
- Error results — including rejections produced by a `before_tool` [Human-in-the-Loop](./human-in-the-loop.mdx) hook — are always left in context, so the model sees what went wrong.
- Non-text results (image or file content) are left in context; supporting only text is a deliberate choice for now. A warning is logged when a non-text result has a matching offload policy.
- Each result is offloaded at most once, even though the hook runs on every tool step. This also means two offload hooks registered under `after_tool` won't offload each other's pointers.
## Policies
Policies control *whether* a result is offloaded.
| Policy | Behavior |
| --- | --- |
| `AlwaysOffload` | Offload every result of the tool it is assigned to |
| `NeverOffload` | Never offload - keep the full result in context (useful to opt a tool out of a wildcard default) |
| `OffloadOverChars(threshold)` | Offload only when the result is longer than `threshold` characters |
### Custom policy
Subclass the `OffloadPolicy` protocol from `haystack.hooks.tool_result_offloading` for custom conditions. A policy needs a `should_offload` method, which receives the tool name, the result text, and the Agent's live [`State`](./state.mdx), so it can also decide based on run context:
```python
from haystack.components.agents.state import State
from haystack.hooks.tool_result_offloading import OffloadPolicy
class OffloadLateSteps(OffloadPolicy):
"""Offload results only once the run is several steps deep and context pressure builds up."""
def should_offload(self, tool_name: str, result: str, state: State) -> bool:
return state.data.get("step_count", 0) >= 3 and len(result) > 1000
```
The protocol provides default `to_dict` / `from_dict` implementations, so a policy like this one, whose constructor takes no arguments, is serializable as-is. A policy with constructor arguments should implement both methods itself, following `OffloadOverChars` as an example.
## Stores
### `FileSystemToolResultStore`
`FileSystemToolResultStore(root=...)` writes each offloaded result to a file under its root directory and returns the absolute file path as the reference. The directory is created on first write. Store keys are derived from the step count, tool name, and tool call ID (for example `2_search_call-123.txt`), so results from different tools and steps do not collide. A key that would resolve outside the root directory is rejected.
### Custom store
Subclass the `ToolResultStore` protocol to target other backends, such as object storage or an isolated sandbox file system. A store needs two methods: `write(key=..., content=...)` persists the content and returns an opaque reference string, and `read(reference)` resolves that reference back to the content:
```python
from haystack.hooks.tool_result_offloading import ToolResultStore
class InMemoryToolResultStore(ToolResultStore):
"""Keep offloaded results in a dict - useful for tests."""
def __init__(self) -> None:
self._data: dict[str, str] = {}
def write(self, *, key: str, content: str) -> str:
self._data[key] = content
return key
def read(self, reference: str) -> str:
return self._data[reference]
```
Like `OffloadPolicy`, the protocol provides default `to_dict` / `from_dict` implementations covering stores whose constructor takes no arguments; implement both methods for stores with constructor arguments.
### Per-run stores via `hook_context`
The constructor `store` is shared by every run - fine for single-user or local use. In a multi-user server, give each run its own isolated store (for example, a per-session directory) by passing it in the Agent's generic `hook_context` run argument under the key `RESULT_STORE_CONTEXT_KEY`. It overrides the constructor store for that run:
```python
from haystack.hooks.tool_result_offloading import (
RESULT_STORE_CONTEXT_KEY,
FileSystemToolResultStore,
)
per_request_store = FileSystemToolResultStore(root=f"tool_results/{session_id}")
result = agent.run(
messages=[ChatMessage.from_user("...")],
hook_context={RESULT_STORE_CONTEXT_KEY: per_request_store},
)
```
Isolating the store per run keeps concurrent users from colliding on store keys or reading each other's offloaded results — especially important when a file-reading tool is scoped to the store. The hook itself keeps no mutable state, so a single instance is safe to share across concurrent runs.
## Letting the Agent read offloaded results back
The pointer left in the conversation tells the model where the full result lives, but the model can only act on it if the Agent has a tool that can read from the store. With `FileSystemToolResultStore`, that can be a simple file-reading tool:
```python
from typing import Annotated
from haystack.tools import tool
@tool
def read_offloaded_result(
path: Annotated[str, "Absolute path of an offloaded tool result"],
) -> str:
"""Read back the full content of an offloaded tool result."""
return FileSystemToolResultStore(root="tool_results").read(path)
```
With this tool available, the Agent can work with a compact conversation and selectively re-read only the offloaded results it actually needs — instead of carrying every full result in context on every LLM call.
## Serialization
`ToolResultOffloadHook` implements `to_dict` / `from_dict`, so an Agent using it can be serialized as long as the configured store and policies are serializable too. The built-in store and policies all are; for custom ones, see the notes in [Policies](#custom-policy) and [Stores](#custom-store) above.
## Additional References
📖 Related docs:
- [Hooks](./hooks.mdx) — the general mechanism behind this feature, including the `after_tool` hook point
- [Human in the Loop](./human-in-the-loop.mdx) — another ready-made hook, intercepting tool calls for human review
- [State](./state.mdx) — the live run state hooks and policies receive
---
// File: pipeline-components/audio/external-integrations-audio
# External Integrations
External integrations that enable working with audio in Haystack by transcribing files or converting text to audio.
| Name | Description |
| --- | --- |
| [AssemblyAI](https://haystack.deepset.ai/integrations/assemblyai) | Perform speech recognition, speaker diarization and summarization. |
| [Elevenlabs](https://haystack.deepset.ai/integrations/elevenlabs) | Convert text to speech using ElevenLabs’ API. |
---
// File: pipeline-components/audio/funasrtranscriber
# FunASRTranscriber
Transcribe audio files to Haystack Documents using FunASR — a local, open-source speech recognition toolkit supporting 50+ languages.
| | |
| --- | --- |
| **Most common position in a pipeline** | As the first component in an indexing pipeline |
| **Mandatory run variables** | `sources`: A list of audio file paths (`str` or `Path`) or `ByteStream` objects |
| **Output variables** | `documents`: A list of Haystack Documents, one per source, with transcript text in `content` |
| **API reference** | [FunASR integration](/reference/integrations-funasr) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/funasr/src/haystack_integrations/components/audio/funasr/transcriber.py |
## Overview
`FunASRTranscriber` uses [FunASR](https://github.com/modelscope/FunASR), an open-source speech recognition toolkit from Alibaba DAMO Academy, to transcribe audio files into Haystack `Document` objects. It runs entirely locally — no API key required.
The default model is `iic/SenseVoiceSmall`, a multilingual model supporting 50+ languages that is 5–10x faster than Whisper. Models are downloaded from ModelScope on first use and cached in `~/.cache/modelscope`.
The component accepts audio file paths (`str` or `Path`) as well as `ByteStream` objects. The model is loaded into memory automatically the first time the component runs.
## Usage
### On its own
```python
from haystack_integrations.components.audio.funasr import FunASRTranscriber
transcriber = FunASRTranscriber()
result = transcriber.run(sources=["speech.wav"])
print(result["documents"][0].content)
```
### In a pipeline
```python
from haystack import Pipeline
from haystack.components.fetchers import LinkContentFetcher
from haystack_integrations.components.audio.funasr import FunASRTranscriber
pipe = Pipeline()
pipe.add_component("fetcher", LinkContentFetcher())
pipe.add_component("transcriber", FunASRTranscriber())
pipe.connect("fetcher", "transcriber")
result = pipe.run(
data={
"fetcher": {
"urls": ["https://example.com/interview.wav"],
},
},
)
print(result["transcriber"]["documents"][0].content)
```
---
// File: pipeline-components/audio/localwhispertranscriber
# LocalWhisperTranscriber
Use `LocalWhisperTranscriber` to transcribe audio files using OpenAI's Whisper model using your local installation of Whisper.
| | |
| --- | --- |
| **Most common position in a pipeline** | As the first component in an indexing pipeline |
| **Mandatory run variables** | `sources`: A list of paths or binary streams that you want to transcribe |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [Whisper](/reference/integrations-whisper) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/whisper |
| **Package name** | `whisper-haystack` |
## Overview
The component also needs to know which Whisper model to work with. Specify this in the `model` parameter when initializing the component. All transcription is completed on the executing machine, and the audio is never sent to a third-party provider.
See other optional parameters you can specify in our [API documentation](/reference/integrations-whisper).
See the [Whisper API documentation](https://platform.openai.com/docs/guides/speech-to-text) and the official Whisper [GitHub repo](https://github.com/openai/whisper) for the supported audio formats and languages.
The `LocalWhisperTranscriber` is part of the `whisper-haystack` integration package. To work with it, install the package along with [Whisper](https://github.com/openai/whisper) (which also pulls in torch) using the following commands:
```shell
pip install whisper-haystack
pip install -U openai-whisper
```
## Usage
### On its own
Here’s an example of how to use `LocalWhisperTranscriber` on its own:
```python
import requests
from haystack_integrations.components.audio.whisper import LocalWhisperTranscriber
response = requests.get(
"https://ia903102.us.archive.org/19/items/100-Best--Speeches/EK_19690725_64kb.mp3",
)
with open("kennedy_speech.mp3", "wb") as file:
file.write(response.content)
transcriber = LocalWhisperTranscriber(model="tiny")
transcription = transcriber.run(sources=["./kennedy_speech.mp3"])
print(transcription["documents"][0].content)
```
### In a pipeline
The pipeline below fetches an audio file from a specified URL and transcribes it. It first retrieves the audio file using `LinkContentFetcher`, then transcribes the audio into text with `LocalWhisperTranscriber`, and finally outputs the transcription text.
```python
from haystack_integrations.components.audio.whisper import LocalWhisperTranscriber
from haystack.components.fetchers import LinkContentFetcher
from haystack import Pipeline
pipe = Pipeline()
pipe.add_component("fetcher", LinkContentFetcher())
pipe.add_component("transcriber", LocalWhisperTranscriber(model="tiny"))
pipe.connect("fetcher", "transcriber")
result = pipe.run(
data={
"fetcher": {
"urls": [
"https://ia903102.us.archive.org/19/items/100-Best--Speeches/EK_19690725_64kb.mp3",
],
},
},
)
print(result["transcriber"]["documents"][0].content)
```
## Additional References
🧑🍳 Cookbook: [Multilingual RAG from a podcast with Whisper, Qdrant and Mistral](https://haystack.deepset.ai/cookbook/multilingual_rag_podcast)
---
// File: pipeline-components/audio/remotewhispertranscriber
# RemoteWhisperTranscriber
Use `RemoteWhisperTranscriber` to transcribe audio files using OpenAI's Whisper model.
| | |
| --- | --- |
| **Most common position in a pipeline** | As the first component in an indexing pipeline |
| **Mandatory init variables** | `api_key`: An OpenAI API key. Can be set with an environment variable `OPENAI_API_KEY`. |
| **Mandatory run variables** | `sources`: A list of paths or binary streams that you want to transcribe |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [Whisper](/reference/integrations-whisper) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/whisper |
| **Package name** | `whisper-haystack` |
## Overview
The `RemoteWhisperTranscriber` is part of the `whisper-haystack` integration package. Install it with:
```shell
pip install whisper-haystack
```
`RemoteWhisperTranscriber` works with OpenAI-compatible clients and isn't limited to just OpenAI as a provider. For example, [Groq](https://console.groq.com/docs/speech-to-text) offers a drop-in replacement that can be used as well. You can set the API key in one of two ways:
1. Through the `api_key` initialization parameter, where the key is resolved using [Secret API](../../concepts/secret-management.mdx).
2. By setting it in the `OPENAI_API_KEY` environment variable, which the system will use to access the key.
```python
from haystack_integrations.components.audio.whisper import RemoteWhisperTranscriber
transcriber = RemoteWhisperTranscriber()
```
Additionally, the component requires the following parameters to work:
- `model` specifies the Whisper model.
- `api_base_url` specifies the OpenAI base URL and defaults to `"https://api.openai.com/v1"`. If you are using Whisper provider other than OpenAI set this parameter according to provider's documentation.
See other optional parameters in our [API documentation](/reference/integrations-whisper).
See the [Whisper API documentation](https://platform.openai.com/docs/guides/speech-to-text) and the official Whisper [GitHub repo](https://github.com/openai/whisper) for the supported audio formats and languages.
## Usage
### On its own
Here’s an example of how to use `RemoteWhisperTranscriber` to transcribe a local file:
```python
import requests
from haystack_integrations.components.audio.whisper import RemoteWhisperTranscriber
response = requests.get(
"https://ia903102.us.archive.org/19/items/100-Best--Speeches/EK_19690725_64kb.mp3",
)
with open("kennedy_speech.mp3", "wb") as file:
file.write(response.content)
transcriber = RemoteWhisperTranscriber()
transcription = transcriber.run(sources=["./kennedy_speech.mp3"])
print(transcription["documents"][0].content)
```
### In a pipeline
The pipeline below fetches an audio file from a specified URL and transcribes it. It first retrieves the audio file using `LinkContentFetcher`, then transcribes the audio into text with `RemoteWhisperTranscriber`, and finally outputs the transcription text.
```python
from haystack_integrations.components.audio.whisper import RemoteWhisperTranscriber
from haystack.components.fetchers import LinkContentFetcher
from haystack import Pipeline
pipe = Pipeline()
pipe.add_component("fetcher", LinkContentFetcher())
pipe.add_component("transcriber", RemoteWhisperTranscriber())
pipe.connect("fetcher", "transcriber")
result = pipe.run(
data={
"fetcher": {
"urls": [
"https://ia903102.us.archive.org/19/items/100-Best--Speeches/EK_19690725_64kb.mp3",
],
},
},
)
print(result["transcriber"]["documents"][0].content)
```
## Additional References
🧑🍳 Cookbook: [Multilingual RAG from a podcast with Whisper, Qdrant and Mistral](https://haystack.deepset.ai/cookbook/multilingual_rag_podcast)
---
// File: pipeline-components/audio
# Audio
Use these components to work with audio in Haystack by transcribing files or converting text to audio.
| Name | Description |
| --- | --- |
| [FunASRTranscriber](audio/funasrtranscriber.mdx) | Transcribe audio files using FunASR — a local, open-source speech recognition toolkit supporting 50+ languages. |
| [LocalWhisperTranscriber](audio/localwhispertranscriber.mdx) | Transcribe audio files using OpenAI's Whisper model using your local installation of Whisper. |
| [RemoteWhisperTranscriber](audio/remotewhispertranscriber.mdx) | Transcribe audio files using OpenAI's Whisper model. |
---
// File: pipeline-components/builders/answerbuilder
# AnswerBuilder
Use this component in pipelines that contain a Generator to parse its replies.
| | |
| --- | --- |
| **Most common position in a pipeline** | Use in pipelines (such as a RAG pipeline) after a [Generator](../generators.mdx) component to create [`GeneratedAnswer`](../../concepts/data-classes.mdx#generatedanswer) objects from its replies. |
| **Mandatory run variables** | `query`: A query string
`replies`: A list of strings, or a list of [`ChatMessage`](../../concepts/data-classes/chatmessage.mdx) objects that are replies from a Generator |
| **Output variables** | `answers`: A list of `GeneratedAnswer` objects |
| **API reference** | [Builders](/reference/builders-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/builders/answer_builder.py |
| **Package name** | `haystack-ai` |
## Overview
`AnswerBuilder` takes a query and the replies a Generator returns as input and parses them into `GeneratedAnswer` objects. Optionally, it also takes documents and metadata from the Generator as inputs to enrich the `GeneratedAnswer` objects.
The `AnswerBuilder` works with both Chat and non-Chat Generators.
The optional `pattern` parameter defines how to extract answer texts from replies. It needs to be a regular expression with a maximum of one capture group. If a capture group is present, the text matched by the capture group is used as the answer. If no capture group is present, the whole match is used as the answer. If no `pattern` is set, the whole reply is used as the answer text.
The optional `reference_pattern` parameter can be set to a regular expression that parses referenced documents from the replies so that only those referenced documents are listed in the `GeneratedAnswer` objects. Haystack assumes that documents are referenced by their index in the list of input documents and that indices start at 1. For example, if you set the `reference_pattern` to _`\\[(\\d+)\\]`,_ it finds “1” in a string "This is an answer[1]". If `reference_pattern` is not set, all input documents are listed in the `GeneratedAnswer` objects.
## Usage
### On its own
Below is an example where we’re using the `AnswerBuilder` to parse a string that could be the reply received from a Generator using a custom regular expression. Any text other than the answer will not be included in the `GeneratedAnswer` object constructed by the builder.
```python
from haystack.components.builders import AnswerBuilder
builder = AnswerBuilder(pattern="Answer: (.*)")
builder.run(
query="What's the answer?",
replies=["This is an argument. Answer: This is the answer."],
)
```
### In a pipeline
Below is an example of a RAG pipeline where we use an `AnswerBuilder` to create `GeneratedAnswer` objects from the replies returned by a Generator. In addition to the text of the reply, these objects also hold the query, the referenced docs, and metadata returned by the Generator.
```python
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.retrievers.in_memory import InMemoryBM25Retriever
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.builders.chat_prompt_builder import ChatPromptBuilder
from haystack.components.builders.answer_builder import AnswerBuilder
from haystack.utils import Secret
from haystack.dataclasses import ChatMessage
from haystack.dataclasses import Document
prompt_template = [
ChatMessage.from_system("You are a helpful assistant."),
ChatMessage.from_user(
"Given these documents, answer the question.\nDocuments:\n"
"{% for doc in documents %}{{ doc.content }}{% endfor %}\n"
"Question: {{query}}\nAnswer:",
),
]
docs = [
Document(content="The capital of France is Paris"),
Document(content="The capital of England is London"),
]
document_store = InMemoryDocumentStore()
document_store.write_documents(docs)
p = Pipeline()
p.add_component(
instance=InMemoryBM25Retriever(document_store=document_store),
name="retriever",
)
p.add_component(
instance=ChatPromptBuilder(
template=prompt_template,
required_variables={"query", "documents"},
),
name="prompt_builder",
)
p.add_component(
instance=OpenAIChatGenerator(api_key=Secret.from_env_var("OPENAI_API_KEY")),
name="llm",
)
p.add_component(instance=AnswerBuilder(), name="answer_builder")
p.connect("retriever", "prompt_builder.documents")
p.connect("prompt_builder", "llm.messages")
p.connect("llm.replies", "answer_builder.replies")
p.connect("retriever", "answer_builder.documents")
query = "What is the capital of France?"
result = p.run(
{
"retriever": {"query": query},
"prompt_builder": {"query": query},
"answer_builder": {"query": query},
},
)
print(result)
```
---
// File: pipeline-components/builders/chatpromptbuilder
# ChatPromptBuilder
This component constructs prompts dynamically by processing chat messages.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before a [Generator](../generators.mdx) |
| **Mandatory init variables** | `template`: A list of [`ChatMessage`](../../concepts/data-classes/chatmessage.mdx) objects or a special string template. Needs to be provided either during init or run. |
| **Mandatory run variables** | `**kwargs`: Any strings that should be used to render the prompt template. See [Variables](#variables) section for more details. |
| **Output variables** | `prompt`: A dynamically constructed prompt |
| **API reference** | [Builders](/reference/builders-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/builders/chat_prompt_builder.py |
| **Package name** | `haystack-ai` |
## Overview
The `ChatPromptBuilder` component creates prompts using static or dynamic templates written in [Jinja2](https://palletsprojects.com/p/jinja/) syntax, by processing a list of chat messages or a special string template. The templates contain placeholders like `{{ variable }}` that are filled with values provided during runtime. You can use it for static prompts set at initialization or change the templates and variables dynamically while running.
To use it, start by providing a list of `ChatMessage` objects or a special string as the template.
[`ChatMessage`](../../concepts/data-classes/chatmessage.mdx) is a data class that includes message content, a role (who generated the message, such as `user`, `assistant`, `system`, `tool`), and optional metadata.
The builder looks for placeholders in the template and identifies the required variables. You can also list these variables manually. During runtime, the `run` method takes the template and the variables, fills in the placeholders, and returns the completed prompt. If required variables are missing. If the template is invalid, the builder raises an error.
For example, you can create a simple translation prompt:
```python
template = [ChatMessage.from_user("Translate to {{ target_language }}: {{ text }}")]
builder = ChatPromptBuilder(template=template)
result = builder.run(target_language="French", text="Hello, how are you?")
```
Or you can also replace the template at runtime with a new one:
```python
new_template = [
ChatMessage.from_user("Summarize in {{ target_language }}: {{ content }}"),
]
result = builder.run(
template=new_template,
target_language="English",
content="A detailed paragraph.",
)
```
### Variables
The template variables found in the init template are used as input types for the component. By default, `required_variables` is set to `"*"`, so all variables in the template are required: if any of them is missing at runtime, the component raises an error and halts execution.
Use `required_variables` and `variables` to specify the input types and required variables:
- `required_variables`
- Defines which template variables must be provided when the component runs.
- If any required variable is missing, the component raises an error and halts execution.
- You can:
- Use `"*"` (the default) to mark all variables in the template as required, or
- Pass a list of required variable names (such as `["name"]`) to make only those required; the remaining variables are optional, or
- Pass an empty list (`[]`) or `None` to make all variables optional. Setting `None` explicitly logs a warning, since missing variables are then silently replaced with empty strings, which can lead to unintended behavior, especially in complex pipelines.
- `variables`
- Lists all variables that can appear in the template, whether required or optional.
- Optional variables that aren't provided are replaced with an empty string in the rendered prompt.
- This allows partial prompts to be constructed without errors, unless a variable is marked as required.
In the example below, only _name_ is required to run the component, while _topic_ is only an optional variable:
```python
template = [
ChatMessage.from_user("Hello, {{ name }}. How can I assist you with {{ topic }}?"),
]
builder = ChatPromptBuilder(
template=template,
required_variables=["name"],
variables=["name", "topic"],
)
result = builder.run(name="Alice")
# Output: "Hello, Alice. How can I assist you with ?"
```
The component only waits for the required inputs before running.
### Roles
A [`ChatMessage`](../../concepts/data-classes/chatmessage.mdx) represents a single message in the conversation and can have one of three class methods that build the chat messages: `from_user`, `from_system`, or `from_assistant`. `from_user` messages are inputs provided by the user, such as a query or request. `from_system` messages provide context or instructions to guide the LLM’s behavior, such as setting a tone or purpose for the conversation. `from_assistant` defines the expected or actual response from the LLM.
Here’s how the roles work together in a `ChatPromptBuilder`:
```python
system_message = ChatMessage.from_system(
"You are an assistant helping tourists in {{ language }}.",
)
user_message = ChatMessage.from_user("What are the best places to visit in {{ city }}?")
assistant_message = ChatMessage.from_assistant(
"The best places to visit in {{ city }} include the Eiffel Tower, Louvre Museum, and Montmartre.",
)
```
### String Templates
Instead of a list of `ChatMessage` objects, you can also express the template as a special string.
This template format allows you to define `ChatMessage` sequences using Jinja2 syntax. Each `{% message %}` block defines a single message with a specific role, and you can insert dynamic content using `{{ variables }}`.
Compared to using a list of `ChatMessage`s, this format is more flexible and allows including structured parts like images in the templatized `ChatMessage`; to better understand this use case, check out the [multimodal example](#multimodal) in the Usage section below.
#### The `insert` Tag
String templates also support an `{% insert %}` tag. It is a placeholder that evaluates an expression to a `ChatMessage` or a list of `ChatMessage` objects and expands it into the prompt, so messages provided at runtime can be interleaved with literal `{% message %}` blocks. For example, you can wrap the runtime messages with a system message above and a templated user message below, then pass the messages (and any template variables) at run time:
```python
from haystack.components.builders import ChatPromptBuilder
from haystack.dataclasses import ChatMessage
template = """
{% message role="system" %}You are a helpful assistant.{% endmessage %}
{% insert messages %}
{% message role="user" %}{{ query }}{% endmessage %}
"""
builder = ChatPromptBuilder(template=template)
result = builder.run(
messages=[ChatMessage.from_user("Hi"), ChatMessage.from_assistant("Hello!")],
query="What's the weather?",
)
# result["prompt"] -> [system, user "Hi", assistant "Hello!", user "What's the weather?"]
```
All content types (tool calls, tool call results, images, reasoning, `name`, and `meta`) round trip without loss. A missing or empty value expands to nothing.
The expression can be a plain variable (`{% insert messages %}`), a slice or index (`{% insert messages[-1:] %}`, `{% insert messages[-1] %}`), or a combination of variables (`{% insert previous + current %}`). Multiple `{% insert %}` tags can be used in a single template, so the runtime messages can be split, reordered, or repeated across different positions.
### Jinja2 Time Extension
`ChatPromptBuilder` supports the Jinja2 TimeExtension, which allows you to work with datetime formats.
The Time Extension provides two main features:
1. A `now` tag that gives you access to the current time,
2. Date/time formatting capabilities through Python's datetime module.
To use the Jinja2 TimeExtension, you need to install a dependency with:
```shell
pip install arrow>=1.3.0
```
#### The `now` Tag
The `now` tag creates a datetime object representing the current time, which you can then store in a variable:
```jinja2
{% now 'utc' as current_time %}
The current UTC time is: {{ current_time }}
```
You can specify different timezones:
```jinja2
{% now 'America/New_York' as ny_time %}
The time in New York is: {{ ny_time }}
```
If you don't specify a timezone, your system's local timezone will be used:
```jinja2
{% now as local_time %}
Local time: {{ local_time }}
```
#### Date Formatting
You can format the datetime objects using Python's `strftime` syntax:
```jinja2
{% now as current_time %}
Formatted date: {{ current_time.strftime('%Y-%m-%d %H:%M:%S') }}
```
The common format codes are:
- `%Y`: 4-digit year (for example, 2025)
- `%m`: Month as a zero-padded number (01-12)
- `%d`: Day as a zero-padded number (01-31)
- `%H`: Hour (24-hour clock) as a zero-padded number (00-23)
- `%M`: Minute as a zero-padded number (00-59)
- `%S`: Second as a zero-padded number (00-59)
#### Example
```python
from haystack.components.builders.chat_prompt_builder import ChatPromptBuilder
from haystack.dataclasses import ChatMessage
template = [
ChatMessage.from_user("Current date is: {% now 'UTC' %}"),
ChatMessage.from_assistant("Thank you for providing the date"),
ChatMessage.from_user("Yesterday was: {% now 'UTC' - 'days=1' %}"),
]
builder = ChatPromptBuilder(template=template)
result = builder.run()["prompt"]
```
## Usage
### On its own
#### With static template
```python
from haystack.components.builders import ChatPromptBuilder
from haystack.dataclasses import ChatMessage
template = [
ChatMessage.from_user(
"Translate to {{ target_language }}. Context: {{ snippet }}; Translation:",
),
]
builder = ChatPromptBuilder(template=template)
builder.run(target_language="spanish", snippet="I can't speak spanish.")
```
#### With special string template
```python
from haystack.components.builders import ChatPromptBuilder
from haystack.dataclasses import ChatMessage
template = """
{% message role="user" %}
Hello, my name is {{name}}!
{% endmessage %}
"""
builder = ChatPromptBuilder(template=template)
result = builder.run(name="John")
assert result["prompt"] == [ChatMessage.from_user("Hello, my name is John!")]
```
#### Specifying name and meta in a ChatMessage
```python
from haystack.components.builders import ChatPromptBuilder
from haystack.dataclasses import ChatMessage
template = """
{% message role="user" name="John" meta={"key": "value"} %}
Hello from {{country}}!
{% endmessage %}
"""
builder = ChatPromptBuilder(template=template)
result = builder.run(country="Italy")
assert result["prompt"] == [
ChatMessage.from_user("Hello from Italy!", name="John", meta={"key": "value"}),
]
```
#### Multiple ChatMessages with different roles
```python
from haystack.components.builders import ChatPromptBuilder
from haystack.dataclasses import ChatMessage
template = """
{% message role="system" %}
You are a {{adjective}} assistant.
{% endmessage %}
{% message role="user" %}
Hello, my name is {{name}}!
{% endmessage %}
{% message role="assistant" %}
Hello, {{name}}! How can I help you today?
{% endmessage %}
"""
builder = ChatPromptBuilder(template=template)
result = builder.run(name="John", adjective="helpful")
assert result["prompt"] == [
ChatMessage.from_system("You are a helpful assistant."),
ChatMessage.from_user("Hello, my name is John!"),
ChatMessage.from_assistant("Hello, John! How can I help you today?"),
]
```
#### Overriding static template at runtime
```python
from haystack.components.builders import ChatPromptBuilder
from haystack.dataclasses import ChatMessage
template = [
ChatMessage.from_user(
"Translate to {{ target_language }}. Context: {{ snippet }}; Translation:",
),
]
builder = ChatPromptBuilder(template=template)
builder.run(target_language="spanish", snippet="I can't speak spanish.")
summary_template = [
ChatMessage.from_user(
"Translate to {{ target_language }} and summarize. Context: {{ snippet }}; Summary:",
),
]
builder.run(
target_language="spanish",
snippet="I can't speak spanish.",
template=summary_template,
)
```
#### Multimodal
The `| templatize_part` filter in the example below tells the template engine to insert structured (non-text) objects, such as images, into the message content. These are treated differently from plain text and are rendered as special content parts in the final `ChatMessage`.
```python
from haystack.components.builders import ChatPromptBuilder
from haystack.dataclasses import ChatMessage, ImageContent
template = """
{% message role="user" meta={"key": "value"}%}
Hello! I am {{user_name}}. What's the difference between the following images?
{% for image in images %}
{{ image | templatize_part }}
{% endfor %}
{% endmessage %}
"""
builder = ChatPromptBuilder(template=template)
images = [
ImageContent.from_file_path("apple.jpg"),
ImageContent.from_file_path("kiwi.jpg"),
]
result = builder.run(user_name="John", images=images)
assert result["prompt"] == [
ChatMessage.from_user(
content_parts=[
"Hello! I am John. What's the difference between the following images?",
*images,
],
meta={"key": "value"},
),
]
```
### In a pipeline
```python
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack import Pipeline
from haystack.utils import Secret
# no parameter init, we don't use any runtime template variables
prompt_builder = ChatPromptBuilder()
llm = OpenAIChatGenerator()
pipe = Pipeline()
pipe.add_component("prompt_builder", prompt_builder)
pipe.add_component("llm", llm)
pipe.connect("prompt_builder.prompt", "llm.messages")
location = "Berlin"
language = "English"
system_message = ChatMessage.from_system(
"You are an assistant giving information to tourists in {{language}}",
)
messages = [system_message, ChatMessage.from_user("Tell me about {{location}}")]
res = pipe.run(
data={
"prompt_builder": {
"template_variables": {"location": location, "language": language},
"template": messages,
},
},
)
print(res)
```
Then, you could ask about the weather forecast for the said location. The `ChatPromptBuilder` fills in the template with the new `day_count` variable and passes it to an LLM once again:
```python
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack import Pipeline
from haystack.utils import Secret
# no parameter init, we don't use any runtime template variables
prompt_builder = ChatPromptBuilder()
llm = OpenAIChatGenerator()
pipe = Pipeline()
pipe.add_component("prompt_builder", prompt_builder)
pipe.add_component("llm", llm)
pipe.connect("prompt_builder.prompt", "llm.messages")
location = "Berlin"
messages = [
system_message,
ChatMessage.from_user(
"What's the weather forecast for {{location}} in the next {{day_count}} days?",
),
]
res = pipe.run(
data={
"prompt_builder": {
"template_variables": {"location": location, "day_count": "5"},
"template": messages,
},
},
)
print(res)
```
### In YAML
This is the YAML representation of the pipeline shown above. It dynamically constructs a prompt and generates an answer using a chat model.
```yaml
components:
llm:
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-4o-mini
organization: null
streaming_callback: null
timeout: null
tools: null
tools_strict: false
type: haystack.components.generators.chat.openai.OpenAIChatGenerator
prompt_builder:
init_parameters:
required_variables: '*'
template: null
variables: null
type: haystack.components.builders.chat_prompt_builder.ChatPromptBuilder
connection_type_validation: true
connections:
- receiver: llm.messages
sender: prompt_builder.prompt
max_runs_per_component: 100
metadata: {}
```
## Additional References
🧑🍳 Cookbook: [Advanced Prompt Customization for Anthropic](https://haystack.deepset.ai/cookbook/prompt_customization_for_anthropic)
---
// File: pipeline-components/builders/promptbuilder
# PromptBuilder
Use this component in pipelines before a Generator to render a prompt template and fill in variable values.
| | |
| --- | --- |
| **Most common position in a pipeline** | In a querying pipeline, before a [Generator](../generators.mdx) |
| **Mandatory init variables** | `template`: A prompt template string that uses Jinja2 syntax |
| **Mandatory run variables** | `**kwargs`: Any strings that should be used to render the prompt template. See [Variables](#variables) section for more details. |
| **Output variables** | `prompt`: A string that represents the rendered prompt template |
| **API reference** | [Builders](/reference/builders-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/builders/prompt_builder.py |
| **Package name** | `haystack-ai` |
## Overview
`PromptBuilder` is initialized with a prompt template and renders it by filling in parameters passed through keyword arguments, `kwargs`. With `kwargs`, you can pass a variable number of keyword arguments so that any variable used in the prompt template can be specified with the desired value. Values for all variables appearing in the prompt template need to be provided through the `kwargs`.
The template that is provided to the `PromptBuilder` during initialization needs to conform to the [Jinja2](https://palletsprojects.com/p/jinja/) template language.
### Variables
The template variables found in the init template are used as input types for the component. By default, `required_variables` is set to `"*"`, so all variables in the template are required: if any of them is missing at runtime, the component raises an error and halts execution.
Use `required_variables` and `variables` to specify the input types and required variables:
- `required_variables`
- Defines which template variables must be provided when the component runs.
- If any required variable is missing, the component raises an error and halts execution.
- You can:
- Use `"*"` (the default) to mark all variables in the template as required, or
- Pass a list of required variable names (such as `["query"]`) to make only those required; the remaining variables are optional, or
- Pass an empty list (`[]`) or `None` to make all variables optional. Setting `None` explicitly logs a warning, since missing variables are then silently replaced with empty strings, which can lead to unintended behavior, especially in complex pipelines.
- `variables`
- Lists all variables that can appear in the template, whether required or optional.
- Optional variables that aren't provided are replaced with an empty string in the rendered prompt.
- This allows partial prompts to be constructed without errors, unless a variable is marked as required.
```python
from haystack.components.builders import PromptBuilder
# All variables required (the default, equivalent to required_variables="*")
builder = PromptBuilder(
template="Hello {{name}}! {{greeting}}",
)
# Some variables required
builder = PromptBuilder(
template="Hello {{name}}! {{greeting}}",
required_variables=["name"], # 'greeting' becomes optional
)
# All variables optional (missing ones default to empty string)
builder = PromptBuilder(
template="Hello {{name}}! {{greeting}}",
required_variables=[], # explicit None also works but logs a warning
)
```
The component only waits for the required inputs before running.
### Jinja2 Time Extension
`PromptBuilder` supports the Jinja2 TimeExtension, which allows you to work with datetime formats.
The Time Extension provides two main features:
1. A `now` tag that gives you access to the current time,
2. Date/time formatting capabilities through Python's datetime module.
To use the Jinja2 TimeExtension, you need to install a dependency with:
```shell
pip install arrow>=1.3.0
```
#### The `now` Tag
The `now` tag creates a datetime object representing the current time, which you can then store in a variable:
```jinja2
{% now 'utc' as current_time %}
The current UTC time is: {{ current_time }}
```
You can specify different timezones:
```jinja2
{% now 'America/New_York' as ny_time %}
The time in New York is: {{ ny_time }}
```
If you don't specify a timezone, your system's local timezone will be used:
```jinja2
{% now as local_time %}
Local time: {{ local_time }}
```
#### Date Formatting
You can format the datetime objects using Python's `strftime` syntax:
```jinja2
{% now as current_time %}
Formatted date: {{ current_time.strftime('%Y-%m-%d %H:%M:%S') }}
```
The common format codes are:
- `%Y`: 4-digit year (for example, 2025)
- `%m`: Month as a zero-padded number (01-12)
- `%d`: Day as a zero-padded number (01-31)
- `%H`: Hour (24-hour clock) as a zero-padded number (00-23)
- `%M`: Minute as a zero-padded number (00-59)
- `%S`: Second as a zero-padded number (00-59)
#### Example
```python
from haystack.components.builders import PromptBuilder
# Define template using Jinja-style formatting
template = """
Current date is: {% now 'UTC' %}
Thank you for providing the date
Yesterday was: {% now 'UTC' - 'days=1' %}
"""
builder = PromptBuilder(template=template)
result = builder.run()["prompt"]
```
## Usage
### On its own
Below is an example of using the `PromptBuilder` to render a prompt template and fill it with `target_language` and `snippet`. The PromptBuilder returns a prompt with the string `Translate the following context to spanish. Context: I can't speak spanish.; Translation:`.
```python
from haystack.components.builders import PromptBuilder
template = "Translate the following context to {{ target_language }}. Context: {{ snippet }}; Translation:"
builder = PromptBuilder(template=template)
builder.run(target_language="spanish", snippet="I can't speak spanish.")
```
### In a pipeline
Below is an example of a RAG pipeline where we use a `PromptBuilder` to render a custom prompt template and fill it with the contents of retrieved documents and a query. The rendered prompt is then sent to a Generator.
```python
from haystack import Pipeline, Document
from haystack.utils import Secret
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.builders.prompt_builder import PromptBuilder
# in a real world use case documents could come from a retriever, web, or any other source
documents = [
Document(content="Joe lives in Berlin"),
Document(content="Joe is a software engineer"),
]
prompt_template = """
Given these documents, answer the question.\nDocuments:
{% for doc in documents %}
{{ doc.content }}
{% endfor %}
\nQuestion: {{query}}
\nAnswer:
"""
p = Pipeline()
p.add_component(instance=PromptBuilder(template=prompt_template), name="prompt_builder")
p.add_component(
instance=OpenAIChatGenerator(api_key=Secret.from_env_var("OPENAI_API_KEY")),
name="llm",
)
p.connect("prompt_builder", "llm")
question = "Where does Joe live?"
result = p.run({"prompt_builder": {"documents": documents, "query": question}})
print(result)
```
#### Changing the template at runtime (Prompt Engineering)
`PromptBuilder` allows you to switch the prompt template of an existing pipeline. The example below builds on top of the existing pipeline in the previous section. We are invoking the existing pipeline with a new prompt template:
```python
documents = [
Document(content="Joe lives in Berlin", meta={"name": "doc1"}),
Document(content="Joe is a software engineer", meta={"name": "doc1"}),
]
new_template = """
You are a helpful assistant.
Given these documents, answer the question.
Documents:
{% for doc in documents %}
Document {{ loop.index }}:
Document name: {{ doc.meta['name'] }}
{{ doc.content }}
{% endfor %}
Question: {{ query }}
Answer:
"""
p.run(
{
"prompt_builder": {
"documents": documents,
"query": question,
"template": new_template,
},
},
)
```
If you want to use different variables during prompt engineering than in the default template, you can do so by setting `PromptBuilder`'s variables init parameter accordingly.
#### Overwriting variables at runtime
In case you want to overwrite the values of variables, you can use `template_variables` during runtime, as shown below:
```python
language_template = """
You are a helpful assistant.
Given these documents, answer the question.
Documents:
{% for doc in documents %}
Document {{ loop.index }}:
Document name: {{ doc.meta['name'] }}
{{ doc.content }}
{% endfor %}
Question: {{ query }}
Please provide your answer in {{ answer_language | default('English') }}
Answer:
"""
p.run(
{
"prompt_builder": {
"documents": documents,
"query": question,
"template": language_template,
"template_variables": {"answer_language": "German"},
},
},
)
```
Note that `language_template` introduces `answer_language` variable which is not bound to any pipeline variable. If not set otherwise, it would use its default value, "English". In this example, we overwrite its value to "German".
The `template_variables` allows you to overwrite pipeline variables (such as documents) as well.
### In YAML
This is the YAML representation of the RAG pipeline shown above. It renders a custom prompt template by filling it with the contents of retrieved documents and a query, then sends the rendered prompt to a generator.
```yaml
components:
llm:
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-mini
organization: null
streaming_callback: null
timeout: null
tools: null
tools_strict: false
type: haystack.components.generators.chat.openai.OpenAIChatGenerator
prompt_builder:
init_parameters:
required_variables: '*'
template: "\n Given these documents, answer the question.\nDocuments:\n \
\ {% for doc in documents %}\n {{ doc.content }}\n {% endfor %}\n\
\n \nQuestion: {{query}}\n \nAnswer:\n "
variables: null
type: haystack.components.builders.prompt_builder.PromptBuilder
connection_type_validation: true
connections:
- receiver: llm.messages
sender: prompt_builder.prompt
max_runs_per_component: 100
metadata: {}
```
## Additional References
🧑🍳 Cookbooks:
- [Advanced Prompt Customization for Anthropic](https://haystack.deepset.ai/cookbook/prompt_customization_for_anthropic)
- [Prompt Optimization with DSPy](https://haystack.deepset.ai/cookbook/prompt_optimization_with_dspy)
---
// File: pipeline-components/builders
# Builders
| Component | Description |
| --- | --- |
| [AnswerBuilder](builders/answerbuilder.mdx) | Creates `GeneratedAnswer` objects from the query and the answer. |
| [PromptBuilder](builders/promptbuilder.mdx) | Renders prompt templates with given parameters. |
| [ChatPromptBuilder](builders/chatpromptbuilder.mdx) | PromptBuilder for chat messages. |
---
// File: pipeline-components/caching/cachechecker
# CacheChecker
This component checks for the presence of documents in a Document Store based on a specified cache field.
| | |
| --- | --- |
| **Most common position in a pipeline** | Flexible |
| **Mandatory init variables** | `document_store`: A Document Store instance
`cache_field`: Name of the document's metadata field |
| **Mandatory run variables** | `items`: A list of values associated with the `cache_field` in documents |
| **Output variables** | `hits`: A list of documents that were found with the specified value in cache
`misses`: A list of values that could not be found |
| **API reference** | [Caching](/reference/caching-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/caching/cache_checker.py |
| **Package name** | `haystack-ai` |
## Overview
`CacheChecker` checks if a Document Store contains any document with a value in the `cache_field` that matches any of the values provided in the `items` input variable. It returns a dictionary with two keys: `"hits"` and `"misses"`. The values are lists of documents that were found in the cache and items that were not, respectively.
## Usage
### On its own
```python
from haystack.components.caching import CacheChecker
from haystack.document_stores.in_memory import InMemoryDocumentStore
my_doc_store = InMemoryDocumentStore()
# For URL-based caching
cache_checker = CacheChecker(document_store=my_doc_store, cache_field="url")
cache_check_results = cache_checker.run(
items=[
"https://example.com/resource",
"https://another_example.com/other_resources",
],
)
print(
cache_check_results["hits"],
) # List of Documents that were found in the cache: all of these have 'url': in the metadata
print(
cache_check_results["misses"],
) # URLs that were not found in the cache, like ["https://example.com/resource"]
# For caching based on a custom identifier
cache_checker = CacheChecker(document_store=my_doc_store, cache_field="metadata_field")
cache_check_results = cache_checker.run(items=["12345", "ABCDE"])
print(
cache_check_results["hits"],
) # Documents that were found in the cache: all of these have 'metadata_field': in the metadata
print(
cache_check_results["misses"],
) # Values that were not found in the cache, like: ["ABCDE"]
```
### In a pipeline
```python
from haystack import Pipeline
from haystack.components.converters import TextFileToDocument
from haystack.components.preprocessors import DocumentCleaner, DocumentSplitter
from haystack.components.writers import DocumentWriter
from haystack.components.caching import CacheChecker
from haystack.document_stores.in_memory import InMemoryDocumentStore
pipeline = Pipeline()
document_store = InMemoryDocumentStore()
pipeline.add_component(
instance=CacheChecker(document_store, cache_field="meta.file_path"),
name="cache_checker",
)
pipeline.add_component(instance=TextFileToDocument(), name="text_file_converter")
pipeline.add_component(instance=DocumentCleaner(), name="cleaner")
pipeline.add_component(
instance=DocumentSplitter(split_by="sentence", split_length=250, split_overlap=30),
name="splitter",
)
pipeline.add_component(
instance=DocumentWriter(document_store=document_store),
name="writer",
)
pipeline.connect("cache_checker.misses", "text_file_converter.sources")
pipeline.connect("text_file_converter.documents", "cleaner.documents")
pipeline.connect("cleaner.documents", "splitter.documents")
pipeline.connect("splitter.documents", "writer.documents")
pipeline.draw("pipeline.png")
# Take the current directory as input and run the pipeline
result = pipeline.run({"cache_checker": {"items": ["code_of_conduct_1.txt"]}})
print(result)
# The second execution skips the files that were already processed
result = pipeline.run({"cache_checker": {"items": ["code_of_conduct_1.txt"]}})
print(result)
```
---
// File: pipeline-components/classifiers/documentlanguageclassifier
# DocumentLanguageClassifier
Use this component to classify documents by language and add language information to metadata.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [`MetadataRouter`](../routers/metadatarouter.mdx) |
| **Mandatory run variables** | `documents`: A list of documents |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [Langdetect](/reference/integrations-langdetect) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/langdetect |
| **Package name** | `langdetect-haystack` |
## Overview
`DocumentLanguageClassifier` classifies the language of documents and adds the detected language to their metadata. If a document's text does not match any of the languages specified at initialization, it is classified as "unmatched". By default, the classifier classifies for English (”en”) documents, with the rest being classified as “unmatched”.
The set of supported languages can be specified in the init method with the `languages` variable, using ISO codes.
To route your documents to various branches of the pipeline based on the language, use `MetadataRouter` component right after `DocumentLanguageClassifier`.
For classifying and then routing plain text using the same logic, use the `TextLanguageRouter` component instead.
## Usage
Install the `langdetect-haystack` package to use the `DocumentLanguageClassifier` component:
```shell
pip install langdetect-haystack
```
### On its own
Below, we are using the `DocumentLanguageClassifier` to classify English and German documents:
The examples on this page use Sentence Transformers embedders from the `sentence-transformers-haystack` package. Install it to run the examples:
```shell
pip install sentence-transformers-haystack
```
```python
from haystack_integrations.components.classifiers.langdetect import (
DocumentLanguageClassifier,
)
from haystack import Document
documents = [
Document(content="Mein Name ist Jean und ich wohne in Paris."),
Document(content="Mein Name ist Mark und ich wohne in Berlin."),
Document(content="Mein Name ist Giorgio und ich wohne in Rome."),
Document(content="My name is Pierre and I live in Paris"),
Document(content="My name is Paul and I live in Berlin."),
Document(content="My name is Alessia and I live in Rome."),
]
document_classifier = DocumentLanguageClassifier(languages=["en", "de"])
document_classifier.run(documents=documents)
```
### In a pipeline
Below, we are using the `DocumentLanguageClassifier` in an indexing pipeline that indexes English and German documents into two difference indexes in an `InMemoryDocumentStore`, using embedding models for each language.
```python
from haystack import Pipeline
from haystack import Document
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack_integrations.components.classifiers.langdetect import (
DocumentLanguageClassifier,
)
from haystack_integrations.components.embedders.sentence_transformers import (
SentenceTransformersDocumentEmbedder,
)
from haystack.components.writers import DocumentWriter
from haystack.components.routers import MetadataRouter
document_store_en = InMemoryDocumentStore()
document_store_de = InMemoryDocumentStore()
document_classifier = DocumentLanguageClassifier(languages=["en", "de"])
metadata_router = MetadataRouter(
rules={"en": {"language": {"$eq": "en"}}, "de": {"language": {"$eq": "de"}}},
)
english_embedder = SentenceTransformersDocumentEmbedder()
german_embedder = SentenceTransformersDocumentEmbedder(
model="PM-AI/bi-encoder_msmarco_bert-base_german",
)
en_writer = DocumentWriter(document_store=document_store_en)
de_writer = DocumentWriter(document_store=document_store_de)
indexing_pipeline = Pipeline()
indexing_pipeline.add_component(document_classifier, name="document_classifier")
indexing_pipeline.add_component(metadata_router, name="metadata_router")
indexing_pipeline.add_component(english_embedder, name="english_embedder")
indexing_pipeline.add_component(german_embedder, name="german_embedder")
indexing_pipeline.add_component(en_writer, name="en_writer")
indexing_pipeline.add_component(de_writer, name="de_writer")
indexing_pipeline.connect("document_classifier.documents", "metadata_router.documents")
indexing_pipeline.connect("metadata_router.en", "english_embedder.documents")
indexing_pipeline.connect("metadata_router.de", "german_embedder.documents")
indexing_pipeline.connect("english_embedder", "en_writer")
indexing_pipeline.connect("german_embedder", "de_writer")
indexing_pipeline.run(
{
"document_classifier": {
"documents": [
Document(content="This is an English sentence."),
Document(content="Dies ist ein deutscher Satz."),
],
},
},
)
```
---
// File: pipeline-components/classifiers/transformerszeroshotdocumentclassifier
# TransformersZeroShotDocumentClassifier
Classifies the documents based on the provided labels and adds them to their metadata.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before a [MetadataRouter](../routers/metadatarouter.mdx) |
| **Mandatory init variables** | `model`: The name or path of a Hugging Face model for zero shot document classification
`labels`: The set of possible class labels to classify each document into, for example, [`positive`, `negative`]. The labels depend on the selected model. |
| **Mandatory run variables** | `documents`: A list of documents to classify |
| **Output variables** | `documents`: A list of processed documents with an added `classification` metadata field |
| **API reference** | [Transformers](/reference/integrations-transformers) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/transformers |
| **Package name** | `transformers-haystack` |
## Overview
The `TransformersZeroShotDocumentClassifier` component performs zero-shot classification of documents based on the labels that you set and adds the predicted label to their metadata.
The component uses a Hugging Face pipeline for zero-shot classification.
To initialize the component, provide the model and the set of labels to be used for categorization.
You can additionally configure the component to allow multiple labels to be true with the `multi_label` boolean set to True.
Classification is run on the document's content field by default. If you want it to run on another field, set the`classification_field` to one of the document's metadata fields.
The classification results are stored in the `classification` dictionary within each document's metadata. If `multi_label` is set to `True`, you will find the scores for each label under the `details` key within the `classification` dictionary.
Available models for the task of zero-shot-classification are:
- `valhalla/distilbart-mnli-12-3`
- `cross-encoder/nli-distilroberta-base`
- `cross-encoder/nli-deberta-v3-xsmall`
## Usage
Install the `transformers-haystack` package to use the `TransformersZeroShotDocumentClassifier`:
```shell
pip install transformers-haystack
```
### On its own
```python
from haystack import Document
from haystack_integrations.components.classifiers.transformers import (
TransformersZeroShotDocumentClassifier,
)
documents = [
Document(id="0", content="Cats don't get teeth cavities."),
Document(id="1", content="Cucumbers can be grown in water."),
]
document_classifier = TransformersZeroShotDocumentClassifier(
model="cross-encoder/nli-deberta-v3-xsmall",
labels=["animals", "food"],
)
document_classifier.run(documents=documents)
```
### In a pipeline
The following is a pipeline that classifies documents based on predefined classification labels
retrieved from a search pipeline:
```python
from haystack import Document
from haystack.components.retrievers.in_memory import InMemoryBM25Retriever
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.core.pipeline import Pipeline
from haystack_integrations.components.classifiers.transformers import (
TransformersZeroShotDocumentClassifier,
)
documents = [
Document(id="0", content="Today was a nice day!"),
Document(id="1", content="Yesterday was a bad day!"),
]
document_store = InMemoryDocumentStore()
retriever = InMemoryBM25Retriever(document_store=document_store)
document_classifier = TransformersZeroShotDocumentClassifier(
model="cross-encoder/nli-deberta-v3-xsmall",
labels=["positive", "negative"],
)
document_store.write_documents(documents)
pipeline = Pipeline()
pipeline.add_component(name="retriever", instance=retriever)
pipeline.add_component(name="document_classifier", instance=document_classifier)
pipeline.connect("retriever", "document_classifier")
queries = ["How was your day today?", "How was your day yesterday?"]
expected_predictions = ["positive", "negative"]
for idx, query in enumerate(queries):
result = pipeline.run({"retriever": {"query": query, "top_k": 1}})
classified_docs = result["document_classifier"]["documents"]
assert classified_docs[0].id == str(idx)
assert (
classified_docs[0].meta["classification"]["label"] == expected_predictions[idx]
)
```
---
// File: pipeline-components/classifiers
# Classifiers
Use Classifiers to classify your documents by specific traits and update the metadata.
| Classifier | Description |
| --- | --- |
| [DocumentLanguageClassifier](classifiers/documentlanguageclassifier.mdx) | Classify documents by language. |
| [TransformersZeroShotDocumentClassifier](classifiers/transformerszeroshotdocumentclassifier.mdx) | Classify the documents based on the provided labels. |
---
// File: pipeline-components/connectors/datadogconnector
# DatadogConnector
Learn how to work with Datadog in Haystack.
| | |
| --- | --- |
| **Most common position in a pipeline** | Anywhere, as it’s not connected to other components |
| **Mandatory init variables** | None. The connection to the Datadog backend is created at initialization time |
| **Output variables** | `name`: The name of the tracing component |
| **API reference** | [datadog](/reference/integrations-datadog) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/datadog |
| **Package name** | `datadog-haystack` |
## Overview
`DatadogConnector` integrates tracing capabilities into Haystack pipelines using [Datadog](https://www.datadoghq.com/), through [Datadog's tracing library `ddtrace`](https://ddtrace.readthedocs.io/en/stable/). It captures detailed information about pipeline runs, like API calls, context data, prompts, and more, so you can see the complete trace of your pipeline execution in Datadog.
Datadog tracing is enabled as soon as the `DatadogConnector` is initialized, so you only need to add it to your pipeline – it does not need to be connected to other components or to run to take effect.
You can optionally pass a `name` to identify this tracing component (it defaults to `datadog`).
### Prerequisites
These are the things that you need before working with the `DatadogConnector`:
1. A way to receive traces, such as a running [Datadog Agent](https://docs.datadoghq.com/agent/). `ddtrace` sends traces to the Datadog Agent at `localhost:8126` by default.
2. Set the `HAYSTACK_CONTENT_TRACING_ENABLED` environment variable to `true` – this will enable content tracing (inputs and outputs) in your pipelines.
3. Configure `ddtrace` through the standard mechanisms, for example the `DD_SERVICE`, `DD_ENV`, and `DD_VERSION` environment variables, or by running your application with the `ddtrace-run` command. See the [ddtrace documentation](https://ddtrace.readthedocs.io/en/stable/) for more details.
### Installation
First, install the `datadog-haystack` package to use the `DatadogConnector`:
```shell
pip install datadog-haystack
```
:::info[Usage Notice]
To ensure proper tracing, always set environment variables before importing any Haystack components. This is crucial because Haystack initializes its internal tracing components during import. In the example below, we first set the environment variables and then import the relevant Haystack components.
Alternatively, an even better practice is to set these environment variables in your shell before running the script. This approach keeps configuration separate from code and allows for easier management of different environments.
:::
## Usage
In the example below, we are adding `DatadogConnector` to the pipeline as a _tracer_. Each pipeline run will produce a trace that includes the entire execution context, including prompts, completions, and metadata. You can then view the traces in your Datadog dashboard.
```python
import os
os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
from haystack import Pipeline
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack_integrations.components.connectors.datadog import DatadogConnector
pipe = Pipeline()
pipe.add_component("tracer", DatadogConnector("Chat example"))
pipe.add_component("prompt_builder", ChatPromptBuilder())
pipe.add_component("llm", OpenAIChatGenerator())
pipe.connect("prompt_builder.prompt", "llm.messages")
messages = [
ChatMessage.from_system(
"Always respond in German even if some input data is in other languages.",
),
ChatMessage.from_user("Tell me about {{location}}"),
]
response = pipe.run(
data={
"prompt_builder": {
"template_variables": {"location": "Berlin"},
"template": messages,
},
},
)
print(response["llm"]["replies"][0])
```
### With an Agent
```python
import os
os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
from typing import Annotated
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.tools import tool
from haystack import Pipeline
from haystack_integrations.components.connectors.datadog import DatadogConnector
@tool
def get_weather(city: Annotated[str, "The city to get weather for"]) -> str:
"""Get current weather information for a city."""
weather_data = {
"Berlin": "18°C, partly cloudy",
"New York": "22°C, sunny",
"Tokyo": "25°C, clear skies",
}
return weather_data.get(city, f"Weather information for {city} not available")
@tool
def calculate(
operation: Annotated[
str,
"Mathematical operation: add, subtract, multiply, divide",
],
a: Annotated[float, "First number"],
b: Annotated[float, "Second number"],
) -> str:
"""Perform basic mathematical calculations."""
if operation == "add":
result = a + b
elif operation == "subtract":
result = a - b
elif operation == "multiply":
result = a * b
elif operation == "divide":
if b == 0:
return "Error: Division by zero"
result = a / b
else:
return f"Error: Unknown operation '{operation}'"
return f"The result of {a} {operation} {b} is {result}"
# Create the chat generator
chat_generator = OpenAIChatGenerator()
# Create the agent with tools
agent = Agent(
chat_generator=chat_generator,
tools=[get_weather, calculate],
system_prompt="You are a helpful assistant with access to weather and calculator tools. Use them when needed.",
exit_conditions=["text"],
)
# Create the DatadogConnector for tracing
datadog_connector = DatadogConnector("Agent Example")
# Build the pipeline
pipe = Pipeline()
pipe.add_component("tracer", datadog_connector)
pipe.add_component("agent", agent)
# Run the pipeline
response = pipe.run(
data={
"agent": {
"messages": [
ChatMessage.from_user(
"What's the weather in Berlin and calculate 15 + 27?",
),
],
},
"tracer": {},
},
)
# Display results
print("Agent Response:")
print(response["agent"]["last_message"].text)
```
### Configuring the tracing backend directly
Instead of using the `DatadogConnector`, you can configure the Datadog tracing backend directly by enabling a `DatadogTracer`. Make sure to set the `HAYSTACK_CONTENT_TRACING_ENABLED` environment variable before importing any Haystack components.
```python
import ddtrace
from haystack import tracing
from haystack_integrations.tracing.datadog import DatadogTracer
tracing.enable_tracing(DatadogTracer(ddtrace.tracer))
```
---
// File: pipeline-components/connectors/external-integrations-connectors
# External Integrations
External integrations that connect your pipelines to services by external providers.
| Name | Description |
| --- | --- |
| [Arize AI](https://haystack.deepset.ai/integrations/arize) | Trace and evaluate your Haystack pipelines with Arize AI. |
| [Arize Phoenix](https://haystack.deepset.ai/integrations/arize-phoenix) | Trace and evaluate your Haystack pipelines with Arize Phoenix. |
| [Context AI](https://haystack.deepset.ai/integrations/context-ai) | Log conversations for analytics by Context.ai |
| [Opik](https://haystack.deepset.ai/integrations/opik) | Trace and evaluate your Haystack pipelines with Opik platform. |
| [Traceloop](https://haystack.deepset.ai/integrations/traceloop) | Evaluate and monitor the quality of your LLM apps and agents |
---
// File: pipeline-components/connectors/githubfileeditor
# GitHubFileEditor
This is a component for editing files in GitHub repositories through the GitHub API.
| | |
| --- | --- |
| **Most common position in a pipeline** | After a Chat Generator, or right at the beginning of a pipeline |
| **Mandatory init variables** | `github_token`: GitHub personal access token. Can be set with `GITHUB_TOKEN` env var. |
| **Mandatory run variables** | `command`: Operation type (edit, create, delete, undo)
## Overview
`GitHubFileEditor` supports multiple file operations, including editing existing files, creating new files, deleting files, and undoing recent changes.
There are four main commands:
- **EDIT**: Edit an existing file by replacing specific content
- **CREATE**: Create a new file with specified content
- **DELETE**: Delete an existing file
- **UNDO**: Revert the last commit if made by the same user
### Authorization
This component requires GitHub authentication with a personal access token. You can set the token using the `GITHUB_TOKEN` environment variable, or pass it directly during initialization via the `github_token` parameter.
To create a personal access token, visit [GitHub's token settings page](https://github.com/settings/tokens). Make sure to grant the appropriate permissions for repository access and content management.
### Installation
Install the GitHub integration with pip:
```shell
pip install github-haystack
```
## Usage
:::info[Repository Placeholder]
To run the following code snippets, you need to replace the `owner/repo` with your own GitHub repository name.
:::
### On its own
Editing an existing file:
```python
from haystack_integrations.components.connectors.github import GitHubFileEditor, Command
editor = GitHubFileEditor(repo="owner/repo", branch="main")
result = editor.run(
command=Command.EDIT,
payload={
"path": "src/example.py",
"original": "def old_function():",
"replacement": "def new_function():",
"message": "Renamed function for clarity",
},
)
print(result)
```
```bash
{'result': 'Edit successful'}
```
Creating a new file:
```python
from haystack_integrations.components.connectors.github import GitHubFileEditor, Command
editor = GitHubFileEditor(repo="owner/repo")
result = editor.run(
command=Command.CREATE,
payload={
"path": "docs/new_file.md",
"content": "# New Documentation\n\nThis is a new file.",
"message": "Add new documentation file",
},
)
print(result)
```
```bash
{'result': 'File created successfully'}
```
---
// File: pipeline-components/connectors/githubissuecommenter
# GitHubIssueCommenter
This component posts comments to GitHub issues using the GitHub API.
| | |
| --- | --- |
| **Most common position in a pipeline** | After a Chat Generator that provides the comment text to post or right at the beginning of a pipeline |
| **Mandatory init variables** | `github_token`: GitHub personal access token. Can be set with `GITHUB_TOKEN` env var. |
| **Mandatory run variables** | `url`: A GitHub issue URL
`comment`: Comment text to post |
| **Output variables** | `success`: Boolean indicating whether the comment was posted successfully |
| **API reference** | [GitHub](/reference/integrations-github) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/github |
| **Package name** | `github-haystack` |
## Overview
`GitHubIssueCommenter` takes a GitHub issue URL and comment text, then posts the comment to the specified issue.
The component requires authentication with a GitHub personal access token since posting comments is an authenticated operation.
### Authorization
This component requires GitHub authentication with a personal access token. You can set the token using the `GITHUB_TOKEN` environment variable, or pass it directly during initialization via the `github_token` parameter.
To create a personal access token, visit [GitHub's token settings page](https://github.com/settings/tokens). Make sure to grant the appropriate permissions for repository access and issue management.
### Installation
Install the GitHub integration with pip:
```shell
pip install github-haystack
```
## Usage
:::info[Repository Placeholder]
To run the following code snippets, you need to replace the `owner/repo` with your own GitHub repository name.
:::
### On its own
Basic usage with environment variable authentication:
```python
from haystack_integrations.components.connectors.github import GitHubIssueCommenter
commenter = GitHubIssueCommenter()
result = commenter.run(
url="https://github.com/owner/repo/issues/123",
comment="Thanks for reporting this issue! We'll look into it.",
)
print(result)
```
```bash
{'success': True}
```
### In a pipeline
The following pipeline analyzes a GitHub issue and automatically posts a response:
```python
from haystack import Pipeline
from haystack.components.builders.chat_prompt_builder import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack_integrations.components.connectors.github import (
GitHubIssueViewer,
GitHubIssueCommenter,
)
issue_viewer = GitHubIssueViewer()
issue_commenter = GitHubIssueCommenter()
prompt_template = [
ChatMessage.from_system(
"You are a helpful assistant that analyzes GitHub issues and creates appropriate responses.",
),
ChatMessage.from_user(
"Based on the following GitHub issue:\n"
"{% for document in documents %}"
"{% if document.meta.type == 'issue' %}"
"**Issue Title:** {{ document.meta.title }}\n"
"**Issue Description:** {{ document.content }}\n"
"{% endif %}"
"{% endfor %}\n"
"Generate a helpful response comment for this issue. Keep it professional and concise.",
),
]
prompt_builder = ChatPromptBuilder(template=prompt_template, required_variables="*")
llm = OpenAIChatGenerator(model="gpt-4o-mini")
pipeline = Pipeline()
pipeline.add_component("issue_viewer", issue_viewer)
pipeline.add_component("prompt_builder", prompt_builder)
pipeline.add_component("llm", llm)
pipeline.add_component("issue_commenter", issue_commenter)
pipeline.connect("issue_viewer.documents", "prompt_builder.documents")
pipeline.connect("prompt_builder.prompt", "llm.messages")
pipeline.connect("llm.replies", "issue_commenter.comment")
issue_url = "https://github.com/owner/repo/issues/123"
result = pipeline.run(
data={"issue_viewer": {"url": issue_url}, "issue_commenter": {"url": issue_url}},
)
print(f"Comment posted successfully: {result['issue_commenter']['success']}")
```
```
Comment posted successfully: True
```
---
// File: pipeline-components/connectors/githubissueviewer
# GitHubIssueViewer
This component fetches and parses GitHub issues into Haystack documents.
| | |
| --- | --- |
| **Most common position in a pipeline** | Right at the beginning of a pipeline and before a [ChatPromptBuilder](../builders/chatpromptbuilder.mdx) that expects the content of a GitHub issue as input |
| **Mandatory run variables** | `url`: A GitHub issue URL |
| **Output variables** | `documents`: A list of documents containing the main issue and its comments |
| **API reference** | [GitHub](/reference/integrations-github) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/github |
| **Package name** | `github-haystack` |
## Overview
`GitHubIssueViewer` takes a GitHub issue URL and returns a list of documents where:
- The first document contains the main issue content
- Subsequent documents contain the issue comments (if any)
Each document includes rich metadata such as the issue title, number, state, creation date, author, and more.
### Authorization
The component can work without authentication for public repositories, but for private repositories or to avoid rate limiting, you can provide a GitHub personal access token.
Pass the token during initialization via the `github_token` parameter, for example `github_token=Secret.from_env_var("GITHUB_TOKEN")`. This component has no default environment variable for the token.
To create a personal access token, visit [GitHub's token settings page](https://github.com/settings/tokens).
### Installation
Install the GitHub integration with pip:
```shell
pip install github-haystack
```
## Usage
:::info[Repository Placeholder]
To run the following code snippets, you need to replace the `owner/repo` with your own GitHub repository name.
:::
### On its own
Basic usage without authentication:
```python
from haystack_integrations.components.connectors.github import GitHubIssueViewer
viewer = GitHubIssueViewer()
result = viewer.run(url="https://github.com/deepset-ai/haystack/issues/123")
print(result)
```
```bash
{'documents': [Document(id=3989459bbd8c2a8420a9ba7f3cd3cf79bb41d78bd0738882e57d509e1293c67a, content: 'sentence-transformers = 0.2.6.1
haystack = latest
farm = 0.4.3 latest branch
In the call to Emb...', meta: {'type': 'issue', 'title': 'SentenceTransformer no longer accepts \'gpu" as argument', 'number': 123, 'state': 'closed', 'created_at': '2020-05-28T04:49:31Z', 'updated_at': '2020-05-28T07:11:43Z', 'author': 'predoctech', 'url': 'https://github.com/deepset-ai/haystack/issues/123'}), Document(id=a8a56b9ad119244678804d5873b13da0784587773d8f839e07f644c4d02c167a, content: 'Thanks for reporting!
Fixed with #124 ', meta: {'type': 'comment', 'issue_number': 123, 'created_at': '2020-05-28T07:11:42Z', 'updated_at': '2020-05-28T07:11:42Z', 'author': 'tholor', 'url': 'https://github.com/deepset-ai/haystack/issues/123#issuecomment-635153940'})]}
```
### In a pipeline
The following pipeline fetches a GitHub issue, extracts relevant information, and generates a summary:
```python
from haystack import Pipeline
from haystack.components.builders.chat_prompt_builder import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack_integrations.components.connectors.github import GitHubIssueViewer
# Initialize components
issue_viewer = GitHubIssueViewer()
prompt_template = [
ChatMessage.from_system("You are a helpful assistant that analyzes GitHub issues."),
ChatMessage.from_user(
"Based on the following GitHub issue and comments:\n"
"{% for document in documents %}"
"{% if document.meta.type == 'issue' %}"
"**Issue Title:** {{ document.meta.title }}\n"
"**Issue Description:** {{ document.content }}\n"
"{% else %}"
"**Comment by {{ document.meta.author }}:** {{ document.content }}\n"
"{% endif %}"
"{% endfor %}\n"
"Please provide a summary of the issue and suggest potential solutions.",
),
]
prompt_builder = ChatPromptBuilder(template=prompt_template, required_variables="*")
llm = OpenAIChatGenerator(model="gpt-4o-mini")
# Create pipeline
pipeline = Pipeline()
pipeline.add_component("issue_viewer", issue_viewer)
pipeline.add_component("prompt_builder", prompt_builder)
pipeline.add_component("llm", llm)
# Connect components
pipeline.connect("issue_viewer.documents", "prompt_builder.documents")
pipeline.connect("prompt_builder.prompt", "llm.messages")
# Run pipeline
issue_url = "https://github.com/deepset-ai/haystack/issues/123"
result = pipeline.run(data={"issue_viewer": {"url": issue_url}})
print(result["llm"]["replies"][0])
```
---
// File: pipeline-components/connectors/githubprcreator
# GitHubPRCreator
This component creates pull requests from a fork back to the original repository through the GitHub API.
| | |
| --- | --- |
| **Most common position in a pipeline** | At the end of a pipeline, after [GitHubRepoForker](githubrepoforker.mdx), [GitHubFileEditor](githubfileeditor.mdx) and other components that prepare changes for submission |
| **Mandatory init variables** | `github_token`: GitHub personal access token. Can be set with `GITHUB_TOKEN` env var. |
| **Mandatory run variables** | `issue_url`: GitHub issue URL
## Overview
`GitHubPRCreator` takes a GitHub issue URL and creates a pull request from your fork to the original repository, automatically linking it to the specified issue. It's designed to work with existing forks and assumes you have already made changes in a branch.
Key features:
- **Cross-repository PRs**: Creates pull requests from your fork to the original repository
- **Issue linking**: Automatically links the PR to the specified GitHub issue
- **Draft support**: Option to create draft pull requests
- **Fork validation**: Checks that the required fork exists before creating the PR
As optional parameters, you can set `body` to provide a pull request description and the boolean parameter `draft` to open a draft pull request.
### Authorization
This component requires GitHub authentication with a personal access token from the fork owner. You can set the token using the `GITHUB_TOKEN` environment variable, or pass it directly during initialization via the `github_token` parameter.
To create a personal access token, visit [GitHub's token settings page](https://github.com/settings/tokens). Make sure to grant the appropriate permissions for repository access and pull request creation.
### Installation
Install the GitHub integration with pip:
```shell
pip install github-haystack
```
## Usage
:::info[Repository Placeholder]
To run the following code snippets, you need to replace the `owner/repo` with your own GitHub repository name.
:::
### On its own
```python
from haystack_integrations.components.connectors.github import GitHubPRCreator
pr_creator = GitHubPRCreator()
result = pr_creator.run(
issue_url="https://github.com/owner/repo/issues/123",
title="Fix issue #123",
body="This PR addresses issue #123 by implementing the requested changes.",
branch="fix-123", # Branch in your fork with the changes
base="main", # Branch in original repo to merge into
)
print(result)
```
```bash
{'result': 'Pull request #456 created successfully and linked to issue #123'}
```
---
// File: pipeline-components/connectors/githubrepoforker
# GitHubRepoForker
This component forks a GitHub repository from an issue URL through the GitHub API.
| | |
| --- | --- |
| **Most common position in a pipeline** | Right at the beginning of a pipeline and before an [Agent](../agents-1/agent.mdx) component that expects the name of a GitHub branch as input |
| **Mandatory init variables** | `github_token`: GitHub personal access token. Can be set with `GITHUB_TOKEN` env var. |
| **Mandatory run variables** | `url`: The URL of a GitHub issue in the repository that should be forked |
| **Output variables** | `repo`: Fork repository path
## Overview
`GitHubRepoForker` takes a GitHub issue URL, extracts the repository information, creates or syncs a fork of that repository, and optionally creates an issue-specific branch. It's particularly useful for automated workflows that need to create pull requests or work with repository forks.
Key features:
- **Auto-sync**: Automatically syncs existing forks with the upstream repository
- **Branch creation**: Creates issue-specific branches (e.g., "fix-123" for issue #123)
- **Completion waiting**: Optionally waits for fork creation to complete
- **Fork management**: Handles existing forks intelligently
### Authorization
This component requires GitHub authentication with a personal access token. You can set the token using the `GITHUB_TOKEN` environment variable, or pass it directly during initialization via the `github_token` parameter.
To create a personal access token, visit [GitHub's token settings page](https://github.com/settings/tokens). Make sure to grant the appropriate permissions for repository forking and management.
### Installation
Install the GitHub integration with pip:
```shell
pip install github-haystack
```
## Usage
:::info[Repository Placeholder]
To run the following code snippets, you need to replace the `owner/repo` with your own GitHub repository name.
:::
### On its own
```python
from haystack_integrations.components.connectors.github import GitHubRepoForker
forker = GitHubRepoForker()
result = forker.run(url="https://github.com/owner/repo/issues/123")
print(result)
```
```bash
{'repo': 'owner/repo', 'issue_branch': 'fix-123'}
```
---
// File: pipeline-components/connectors/githubrepoviewer
# GitHubRepoViewer
This component navigates and fetches content from GitHub repositories through the GitHub API.
| | |
| --- | --- |
| **Most common position in a pipeline** | Right at the beginning of a pipeline and before a [ChatPromptBuilder](../builders/chatpromptbuilder.mdx) that expects the content of GitHub files as input |
| **Mandatory run variables** | `path`: Repository path to view
`repo`: Repository in owner/repo format |
| **Output variables** | `documents`: A list of documents containing repository contents |
| **API reference** | [GitHub](/reference/integrations-github) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/github |
| **Package name** | `github-haystack` |
## Overview
`GitHubRepoViewer` provides different behavior based on the path type:
- **For directories**: Returns a list of documents, one for each item (files and subdirectories),
- **For files**: Returns a single document containing the file content.
Each document includes rich metadata such as the path, type, size, and URL.
### Authorization
The component can work without authentication for public repositories, but for private repositories or to avoid rate limiting, you can provide a GitHub personal access token.
Pass the token during initialization via the `github_token` parameter, for example `github_token=Secret.from_env_var("GITHUB_TOKEN")`. This component has no default environment variable for the token.
To create a personal access token, visit [GitHub's token settings page](https://github.com/settings/tokens).
### Installation
Install the GitHub integration with pip:
```shell
pip install github-haystack
```
## Usage
:::info[Repository Placeholder]
To run the following code snippets, you need to replace the `owner/repo` with your own GitHub repository name.
:::
### On its own
Viewing a directory listing:
```python
from haystack_integrations.components.connectors.github import GitHubRepoViewer
viewer = GitHubRepoViewer()
result = viewer.run(
repo="deepset-ai/haystack",
path="haystack/components",
branch="main",
)
print(result)
```
```bash
{'documents': [Document(id=..., content: 'agents', meta: {'path': 'haystack/components/agents', 'type': 'dir', 'size': 0, 'url': 'https://github.com/deepset-ai/haystack/tree/main/haystack/components/agents'}), ...]}
```
Viewing a specific file:
```python
from haystack_integrations.components.connectors.github import GitHubRepoViewer
viewer = GitHubRepoViewer(repo="deepset-ai/haystack", branch="main")
result = viewer.run(path="README.md")
print(result)
```
```bash
{'documents': [Document(id=..., content: '
## Overview
`JinaReaderConnector` interacts with Jina AI’s Reader API to process queries and output documents.
You need to select one of the following modes of operations when initializing the component:
- `read`: Processes a URL and extracts the textual content.
- `search`: Searches the web and returns textual content from the most relevant pages.
- `ground`: Performs fact-checking using a grounding engine.
You can find more information on these modes in the [Jina Reader documentation](https://jina.ai/reader/).
You can additionally control the response format from the Jina Reader API using the component’s `json_response` parameter:
- `True` (default) requests a JSON response for documents enriched with structured metadata.
- `False` requests a raw response, resulting in one document with minimal metadata.
### Authorization
The component uses a `JINA_API_KEY` environment variable by default. Otherwise, you can pass a Jina API key at initialization with `api_key` like this:
```python
reader = JinaReaderConnector(mode="read", api_key=Secret.from_token(""))
```
To get your API key, head to Jina AI’s [website](https://jina.ai/reader/).
### Installation
To start using this integration with Haystack, install the package with:
```shell
pip install jina-haystack
```
## Usage
### On its own
Read mode:
```python
from haystack_integrations.components.connectors.jina import JinaReaderConnector
reader = JinaReaderConnector(mode="read")
query = "https://example.com"
result = reader.run(query=query)
print(result)
# {'documents': [Document(id=fa3e51e4ca91828086dca4f359b6e1ea2881e358f83b41b53c84616cb0b2f7cf,
# content: 'This domain is for use in illustrative examples in documents. You may use this domain in literature ...',
# meta: {'title': 'Example Domain', 'description': '', 'url': 'https://example.com/', 'usage': {'tokens': 42}})]}
```
Search mode:
```python
from haystack_integrations.components.connectors.jina import JinaReaderConnector
reader = JinaReaderConnector(mode="search")
query = "UEFA Champions League 2024"
result = reader.run(query=query)
print(result)
# {'documents': [Document(id=6a71abf9955594232037321a476d39a835c0cb7bc575d886ee0087c973c95940,
# content: '2024/25 UEFA Champions League: Matches, draw, final, key dates | UEFA Champions League | UEFA.com...',
# meta: {'title': '2024/25 UEFA Champions League: Matches, draw, final, key dates',
# 'description': 'What are the match dates? Where is the 2025 final? How will the competition work?',
# 'url': 'https://www.uefa.com/uefachampionsleague/news/...',
# 'usage': {'tokens': 5581}}), ...]}
```
Ground mode:
```python
from haystack_integrations.components.connectors.jina import JinaReaderConnector
reader = JinaReaderConnector(mode="ground")
query = "ChatGPT was launched in 2017"
result = reader.run(query=query)
print(result)
# {'documents': [Document(id=f0c964dbc1ebb2d6584c8032b657150b9aa6e421f714cc1b9f8093a159127f0c,
# content: 'The statement that ChatGPT was launched in 2017 is incorrect. Multiple references confirm that ChatG...',
# meta: {'factuality': 0, 'result': False, 'references': [
# {'url': 'https://en.wikipedia.org/wiki/ChatGPT',
# 'keyQuote': 'ChatGPT is a generative artificial intelligence (AI) chatbot developed by OpenAI and launched in 2022.',
# 'isSupportive': False}, ...],
# 'usage': {'tokens': 10188}})]}
```
### In a pipeline
**Query pipeline with search mode**
The following pipeline example, the `JinaReaderConnector` first searches for relevant documents, then feeds them along with a user query into a prompt template, and finally generates a response based on the retrieved context.
```python
from haystack import Pipeline
from haystack.utils import Secret
from haystack.components.builders.chat_prompt_builder import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack_integrations.components.connectors.jina import JinaReaderConnector
from haystack.dataclasses import ChatMessage
reader_connector = JinaReaderConnector(mode="search")
prompt_template = [
ChatMessage.from_system("You are a helpful assistant."),
ChatMessage.from_user(
"Given the information below:\n"
"{% for document in documents %}{{ document.content }}{% endfor %}\n"
"Answer question: {{ query }}.\nAnswer:",
),
]
prompt_builder = ChatPromptBuilder(
template=prompt_template,
required_variables={"query", "documents"},
)
llm = OpenAIChatGenerator(
model="gpt-4o-mini",
api_key=Secret.from_token(""),
)
pipe = Pipeline()
pipe.add_component("reader_connector", reader_connector)
pipe.add_component("prompt_builder", prompt_builder)
pipe.add_component("llm", llm)
pipe.connect("reader_connector.documents", "prompt_builder.documents")
pipe.connect("prompt_builder.prompt", "llm.messages")
query = "What is the most famous landmark in Berlin?"
result = pipe.run(
data={"reader_connector": {"query": query}, "prompt_builder": {"query": query}},
)
print(result)
# {'llm': {'replies': [ChatMessage(_role=, _content=[TextContent(text='The most famous landmark in Berlin is the **Brandenburg Gate**. It is considered the symbol of the city and represents reunification.')], _name=None, _meta={'model': 'gpt-4o-mini-2024-07-18', 'index': 0, 'finish_reason': 'stop', 'usage': {'completion_tokens': 27, 'prompt_tokens': 4479, 'total_tokens': 4506}})]}}
```
The same component in search mode could also be used in an indexing pipeline.
---
// File: pipeline-components/connectors/langfuseconnector
# LangfuseConnector
Learn how to work with Langfuse in Haystack.
| | |
| --- | --- |
| **Most common position in a pipeline** | Anywhere, as it’s not connected to other components |
| **Mandatory init variables** | `name`: The name of the pipeline or component to identify the tracing run |
| **Output variables** | `name`: The name of the tracing component
`trace_url`: A link to the tracing data
`trace_id`: The ID of the trace |
| **API reference** | [langfuse](/reference/integrations-langfuse) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/langfuse |
| **Package name** | `langfuse-haystack` |
## Overview
`LangfuseConnector` integrates tracing capabilities into Haystack pipelines using [Langfuse](https://langfuse.com/). It captures detailed information about pipeline runs, like API calls, context data, prompts, and more. Use this component to:
- Monitor model performance, such as token usage and cost.
- Find areas for pipeline improvement by identifying low-quality outputs and collecting user feedback.
- Create datasets for fine-tuning and testing from your pipeline executions.
To work with the integration, add the `LangfuseConnector` to your pipeline, run the pipeline, and then view the tracing data on the Langfuse website. Don’t connect this component to any other – `LangfuseConnector` will simply run in your pipeline’s background.
You can optionally define two more parameters when working with this component:
- `httpx_client`: An optional custom `httpx.Client` instance for Langfuse API calls. Note that custom clients are discarded when deserializing a pipeline from YAML, as HTTPX clients cannot be serialized. In such cases, Langfuse creates a default client.
- `span_handler`: An optional custom handler for processing spans. If not provided, the `DefaultSpanHandler` is used. The span handler defines how spans are created and processed, enabling customization of span types based on component types and post-processing of spans. See more details in the [Advanced Usage section](#advanced-usage) below.
### Prerequisites
These are the things that you need before working with LangfuseConnector:
1. Make sure you have an active Langfuse [account](https://cloud.langfuse.com/).
2. Set the `HAYSTACK_CONTENT_TRACING_ENABLED` environment variable to `true` – this will enable tracing in your pipelines.
3. Set the `LANGFUSE_SECRET_KEY` and `LANGFUSE_PUBLIC_KEY` environment variables with your Langfuse secret and public keys found in your account profile.
### Installation
First, install `langfuse-haystack` package to use the `LangfuseConnector`:
```shell
pip install langfuse-haystack
```
:::info[Usage Notice]
To ensure proper tracing, always set environment variables before importing any Haystack components. This is crucial because Haystack initializes its internal tracing components during import. In the example below, we first set the environmental variables and then import the relevant Haystack components.
Alternatively, an even better practice is to set these environment variables in your shell before running the script. This approach keeps configuration separate from code and allows for easier management of different environments.
:::
## Usage
In the example below, we are adding `LangfuseConnector` to the pipeline as a _tracer_. Each pipeline run will produce one trace that includes the entire execution context, including prompts, completions, and metadata.
You can then view the trace by following a URL link printed in the output.
```python
import os
os.environ["LANGFUSE_HOST"] = "https://cloud.langfuse.com"
os.environ["TOKENIZERS_PARALLELISM"] = "false"
os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack import Pipeline
from haystack_integrations.components.connectors.langfuse import LangfuseConnector
if __name__ == "__main__":
pipe = Pipeline()
pipe.add_component("tracer", LangfuseConnector("Chat example"))
pipe.add_component("prompt_builder", ChatPromptBuilder())
pipe.add_component("llm", OpenAIChatGenerator())
pipe.connect("prompt_builder.prompt", "llm.messages")
messages = [
ChatMessage.from_system(
"Always respond in German even if some input data is in other languages.",
),
ChatMessage.from_user("Tell me about {{location}}"),
]
response = pipe.run(
data={
"prompt_builder": {
"template_variables": {"location": "Berlin"},
"template": messages,
},
},
)
print(response["llm"]["replies"][0])
print(response["tracer"]["trace_url"])
```
### With an Agent
```python
import os
os.environ["LANGFUSE_HOST"] = "https://cloud.langfuse.com"
os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
from typing import Annotated
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.tools import tool
from haystack import Pipeline
from haystack_integrations.components.connectors.langfuse import LangfuseConnector
@tool
def get_weather(city: Annotated[str, "The city to get weather for"]) -> str:
"""Get current weather information for a city."""
weather_data = {
"Berlin": "18°C, partly cloudy",
"New York": "22°C, sunny",
"Tokyo": "25°C, clear skies",
}
return weather_data.get(city, f"Weather information for {city} not available")
@tool
def calculate(
operation: Annotated[
str,
"Mathematical operation: add, subtract, multiply, divide",
],
a: Annotated[float, "First number"],
b: Annotated[float, "Second number"],
) -> str:
"""Perform basic mathematical calculations."""
if operation == "add":
result = a + b
elif operation == "subtract":
result = a - b
elif operation == "multiply":
result = a * b
elif operation == "divide":
if b == 0:
return "Error: Division by zero"
else:
result = a / b
else:
return f"Error: Unknown operation '{operation}'"
return f"The result of {a} {operation} {b} is {result}"
if __name__ == "__main__":
# Create components
chat_generator = OpenAIChatGenerator()
agent = Agent(
chat_generator=chat_generator,
tools=[get_weather, calculate],
system_prompt="You are a helpful assistant with access to weather and calculator tools. Use them when needed.",
exit_conditions=["text"],
)
langfuse_connector = LangfuseConnector("Agent Example")
# Create and run pipeline
pipe = Pipeline()
pipe.add_component("tracer", langfuse_connector)
pipe.add_component("agent", agent)
response = pipe.run(
data={
"agent": {
"messages": [
ChatMessage.from_user(
"What's the weather in Berlin and calculate 15 + 27?",
),
],
},
"tracer": {"invocation_context": {"test": "agent_with_tools"}},
},
)
print(response["agent"]["last_message"].text)
print(response["tracer"]["trace_url"])
```
## Advanced Usage
### Customizing Langfuse Traces with SpanHandler
The `SpanHandler` interface in Haystack allows you to customize how spans are created and processed for Langfuse trace creation. This enables you to log custom metrics, add tags, or integrate metadata.
By extending `SpanHandler` or its default implementation, `DefaultSpanHandler`, you can define custom logic for span processing, providing precise control over what data is logged to Langfuse for tracking and analyzing pipeline executions.
Here's an example:
```python
from haystack_integrations.components.connectors.langfuse import LangfuseConnector
from haystack_integrations.tracing.langfuse import DefaultSpanHandler, LangfuseSpan
from typing import Optional
class CustomSpanHandler(DefaultSpanHandler):
def handle(self, span: LangfuseSpan, component_type: Optional[str]) -> None:
# Custom logic to add metadata or modify span
if component_type == "OpenAIChatGenerator":
output = span._data.get("haystack.component.output", {})
if len(output.get("text", "")) < 10:
span._span.update(level="WARNING", status_message="Response too short")
# Add the custom handler to the LangfuseConnector
connector = LangfuseConnector(
"Custom Handler Example", span_handler=CustomSpanHandler()
)
```
---
// File: pipeline-components/connectors/oauthtokenresolver
# OAuthTokenResolver
Resolves an OAuth access token at pipeline runtime and emits it for downstream components such as the SharePoint and Google Drive retrievers and fetchers.
| | |
| --- | --- |
| **Most common position in a pipeline** | At the start of a pipeline, feeding `access_token` into downstream components such as [`MSSharePointRetriever`](../retrievers/mssharepointretriever.mdx) or [`GoogleDriveRetriever`](../retrievers/googledriveretriever.mdx) |
| **Mandatory init variables** | `token_source`: The strategy that resolves the access token, for example `OAuthRefreshTokenSource` |
| **Mandatory run variables** | None for config-only sources. `subject_token`: a controller-injected per-request credential, mandatory only when the source requires it (for example `OAuthTokenExchangeSource`) |
| **Output variables** | `access_token`: A bearer token string |
| **API reference** | [OAuth](/reference/integrations-oauth) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/oauth |
| **Package name** | `oauth-haystack` |
## Overview
`OAuthTokenResolver` resolves an OAuth access token when the pipeline runs and emits it on the `access_token` output socket. Downstream components – such as [`MSSharePointRetriever`](../retrievers/mssharepointretriever.mdx), [`MSSharePointFetcher`](../fetchers/mssharepointfetcher.mdx), [`GoogleDriveRetriever`](../retrievers/googledriveretriever.mdx), and [`GoogleDriveFetcher`](../fetchers/googledrivefetcher.mdx) – consume the token through a normal connection and never need to know how it was obtained.
The resolver itself is a thin wrapper. The actual work of getting a token is delegated to a pluggable **token source** that decides *where* the token comes from. This separation lets you swap authentication strategies (refresh-token grant, per-request token exchange, or a static long-lived token) without changing the rest of your pipeline.
### Token sources
You pass a token source to the resolver through the `token_source` parameter. All sources are importable from `haystack_integrations.utils.oauth`.
| Source | Use it when | Per-request input |
| --- | --- | --- |
| `OAuthRefreshTokenSource` | You have a single, fixed identity backed by a stored refresh token and want the source to exchange it for short-lived access tokens and cache them. | None |
| `OAuthTokenExchangeSource` | You serve multiple users (or run multiple replicas) and want to exchange an incoming per-request user assertion for a downstream token, with no persistent storage. Implements RFC 8693 token exchange and Microsoft's on-behalf-of flow. | `subject_token` |
| `OAuthStaticTokenSource` | Your provider issues a non-expiring token that you manage out of band (for example Slack or Notion). | None |
When the configured source needs a per-request credential (`OAuthTokenExchangeSource` sets `requires_subject_token = True`), the resolver declares a **mandatory** `subject_token` run input. This is a controller-injected credential – for example an incoming user assertion – not a value chosen by an end user. For config-only sources (`OAuthRefreshTokenSource`, `OAuthStaticTokenSource`), the resolver declares no run input and acts as a source node.
:::info[Scopes are provider-specific]
The OAuth scopes you request depend on the downstream service. For Microsoft Graph, that means scopes such as `https://graph.microsoft.com/Files.Read.All`; for Google Drive, scopes such as `https://www.googleapis.com/auth/drive.readonly`. Always consult your identity provider's documentation for the exact scope values.
:::
### Installation
Install the OAuth integration with:
```shell
pip install oauth-haystack
```
## Usage
### On its own
Resolve a token with a stored refresh token using `OAuthRefreshTokenSource`. The refresh token is read from an environment variable through the [Secret API](../../concepts/secret-management.mdx):
```python
from haystack.utils import Secret
from haystack_integrations.components.connectors.oauth import OAuthTokenResolver
from haystack_integrations.utils.oauth import OAuthRefreshTokenSource
resolver = OAuthTokenResolver(
token_source=OAuthRefreshTokenSource(
token_url="https://login.microsoftonline.com/common/oauth2/v2.0/token",
client_id="aaa-bbb-ccc",
refresh_token=Secret.from_env_var("MS_REFRESH_TOKEN"),
scopes=[
"https://graph.microsoft.com/Files.Read.All",
"offline_access",
],
),
)
access_token = resolver.run()["access_token"]
```
For a provider that issues long-lived, non-expiring tokens, use `OAuthStaticTokenSource` instead:
```python
from haystack.utils import Secret
from haystack_integrations.components.connectors.oauth import OAuthTokenResolver
from haystack_integrations.utils.oauth import OAuthStaticTokenSource
resolver = OAuthTokenResolver(
token_source=OAuthStaticTokenSource(token=Secret.from_env_var("SERVICE_TOKEN")),
)
access_token = resolver.run()["access_token"]
```
For multi-user backends, use `OAuthTokenExchangeSource`. The resolver then requires a per-request `subject_token`:
```python
from haystack_integrations.components.connectors.oauth import OAuthTokenResolver
from haystack_integrations.utils.oauth import OAuthTokenExchangeSource
resolver = OAuthTokenResolver(
token_source=OAuthTokenExchangeSource(
token_url="https://login.microsoftonline.com//oauth2/v2.0/token",
client_id="aaa-bbb-ccc",
subject_token_param="assertion",
grant_type="urn:ietf:params:oauth:grant-type:jwt-bearer",
scopes=["https://graph.microsoft.com/Files.Read.All"],
extra_token_params={"requested_token_use": "on_behalf_of"},
),
)
# `subject_token` is the incoming per-request user assertion, injected by your application.
access_token = resolver.run(subject_token="")["access_token"]
```
### In a pipeline
In a pipeline, connect the resolver's `access_token` output to the `access_token` input of one or more downstream components. The example below wires the resolver into a [`MSSharePointRetriever`](../retrievers/mssharepointretriever.mdx) so that searching SharePoint requires only a query at runtime:
```python
from haystack import Pipeline
from haystack.utils import Secret
from haystack_integrations.components.connectors.oauth import OAuthTokenResolver
from haystack_integrations.utils.oauth import OAuthRefreshTokenSource
from haystack_integrations.components.retrievers.microsoft_sharepoint import (
MSSharePointRetriever,
)
pipeline = Pipeline()
pipeline.add_component(
"resolver",
OAuthTokenResolver(
token_source=OAuthRefreshTokenSource(
token_url="https://login.microsoftonline.com/common/oauth2/v2.0/token",
client_id="aaa-bbb-ccc",
refresh_token=Secret.from_env_var("MS_REFRESH_TOKEN"),
scopes=[
"https://graph.microsoft.com/Files.Read.All",
"https://graph.microsoft.com/Sites.Read.All",
"offline_access",
],
),
),
)
pipeline.add_component("retriever", MSSharePointRetriever(top_k=5))
pipeline.connect("resolver.access_token", "retriever.access_token")
result = pipeline.run({"retriever": {"query": "quarterly roadmap"}})
documents = result["retriever"]["documents"]
```
A single `access_token` output can be connected to several downstream inputs. For a full retrieve-then-fetch pipeline that feeds the same token to both a retriever and a fetcher, see the [`MSSharePointFetcher`](../fetchers/mssharepointfetcher.mdx) and [`GoogleDriveFetcher`](../fetchers/googledrivefetcher.mdx) pages.
---
// File: pipeline-components/connectors/openapiconnector
# OpenAPIConnector
`OpenAPIConnector` is a component that acts as an interface between the Haystack ecosystem and OpenAPI services.
:::tip[Consider using MCP instead]
These OpenAPI components are a legacy way to connect Haystack to external APIs. For most use cases, we recommend the [`MCPTool`](../../tools/mcptool.mdx) instead: it is the modern, standardized way to give your pipelines and agents access to external tools and services.
:::
| | |
| --- | --- |
| **Most common position in a pipeline** | Anywhere, after components providing input for its run parameters |
| **Mandatory init variables** | `openapi_spec`: The OpenAPI specification for the service. Can be a URL, file path, or raw string. |
| **Mandatory run variables** | `operation_id`: The operationId from the OpenAPI spec to invoke. |
| **Output variables** | `response`: A REST service response |
| **API reference** | [OpenAPI](/reference/integrations-openapi) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/openapi |
| **Package name** | `openapi-haystack` |
## Overview
The `OpenAPIConnector` is a component within the Haystack ecosystem that allows direct invocation of REST endpoints defined in an OpenAPI (formerly Swagger) specification. It acts as a bridge between Haystack pipelines and any REST API that follows the OpenAPI standard, enabling dynamic method calls, authentication, and parameter handling.
To use the `OpenAPIConnector`, ensure that you have the `openapi-haystack` package installed:
```shell
pip install openapi-haystack
```
Unlike [OpenAPIServiceConnector](openapiserviceconnector.mdx), which works with LLMs, `OpenAPIConnector` directly calls REST endpoints using explicit input arguments.
## Usage
### On its own
You can initialize and use the `OpenAPIConnector` on its own by passing an OpenAPI specification and other parameters:
```python
from haystack.utils import Secret
from haystack_integrations.components.connectors.openapi import OpenAPIConnector
connector = OpenAPIConnector(
openapi_spec="https://bit.ly/serperdev_openapi",
credentials=Secret.from_env_var("SERPERDEV_API_KEY"),
service_kwargs={"config_factory": my_custom_config_factory},
)
response = connector.run(
operation_id="search",
arguments={"q": "Who was Nikola Tesla?"},
)
```
#### Output
The `OpenAPIConnector` returns a dictionary containing the service response:
```json
{
"response": { // here goes REST endpoint response JSON
}
}
```
### In a pipeline
The `OpenAPIConnector` can be integrated into a Haystack pipeline to interact with OpenAPI services. For example, here’s how you can link the `OpenAPIConnector` to a pipeline:
```python
from haystack import Pipeline
from haystack_integrations.components.connectors.openapi import OpenAPIConnector
from haystack.dataclasses.chat_message import ChatMessage
from haystack.utils import Secret
# Initialize the OpenAPIConnector
connector = OpenAPIConnector(
openapi_spec="https://bit.ly/serperdev_openapi",
credentials=Secret.from_env_var("SERPERDEV_API_KEY"),
)
# Create a ChatMessage from the user
user_message = ChatMessage.from_user(text="Who was Nikola Tesla?")
# Define the pipeline
pipeline = Pipeline()
pipeline.add_component("openapi_connector", connector)
# Run the pipeline
response = pipeline.run(
data={
"openapi_connector": {
"operation_id": "search",
"arguments": {"q": user_message.text},
},
},
)
# Extract the answer from the response
answer = response.get("openapi_connector", {}).get("response", {})
print(answer)
```
---
// File: pipeline-components/connectors/openapiserviceconnector
# OpenAPIServiceConnector
`OpenAPIServiceConnector` is a component that acts as an interface between the Haystack ecosystem and OpenAPI services.
:::tip[Consider using MCP instead]
These OpenAPI components are a legacy way to connect Haystack to external APIs. For most use cases, we recommend the [`MCPTool`](../../tools/mcptool.mdx) instead: it is the modern, standardized way to give your pipelines and agents access to external tools and services.
:::
| | |
| --- | --- |
| **Most common position in a pipeline** | Flexible |
| **Mandatory run variables** | `messages`: A list of [`ChatMessage`](../../concepts/data-classes/chatmessage.mdx) objects where the last message must be from the assistant and contain tool calls.
`service_openapi_spec`: OpenAPI specification of the service being invoked. It can be YAML/JSON, and all ref values must be resolved.
`service_credentials`: Authentication credentials for the service. We currently support two OpenAPI spec v3 security schemes:
1. http – for Basic, Bearer, and other HTTP authentication schemes; 2. apiKey – for API keys and cookie authentication. |
| **Output variables** | `service_response`: A list of [`ChatMessage`](../../concepts/data-classes/chatmessage.mdx) objects where each message corresponds to a tool call invocation. If a message contains multiple tool calls, there will be multiple responses. |
| **API reference** | [OpenAPI](/reference/integrations-openapi) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/openapi |
| **Package name** | `openapi-haystack` |
## Overview
`OpenAPIServiceConnector` acts as a bridge between Haystack ecosystem and OpenAPI services. This component works by using information from a `ChatMessage` to dynamically invoke service methods. It handles parameter payload parsing from `ChatMessage`, service authentication, method invocation, and response formatting, making it easier to integrate OpenAPI services.
To use `OpenAPIServiceConnector`, you need to install the `openapi-haystack` package with:
```shell
pip install openapi-haystack
```
`OpenAPIServiceConnector` component doesn’t have any init parameters.
## Usage
### On its own
This component is primarily meant to be used in pipelines, as [`OpenAPIServiceToFunctions`](../converters/openapiservicetofunctions.mdx), in tandem with an LLM with tool calling capabilities, resolves the actual tool call parameters that are injected as invocation parameters for `OpenAPIServiceConnector`.
### In a pipeline
Let's say we're linking the Serper search engine to a pipeline. Here, `OpenAPIServiceConnector` uses the abilities of `OpenAPIServiceToFunctions`. `OpenAPIServiceToFunctions` first fetches and changes the [Serper's OpenAPI specification](https://bit.ly/serper_dev_spec) into function definitions that an LLM with tool calling capabilities can understand. Then, `OpenAPIServiceConnector` activates the Serper service using this specification.
More precisely, `OpenAPIServiceConnector` dynamically calls methods defined in the Serper OpenAPI specification. This involves reading chat messages to extract tool call parameters, handling authentication with the Serper service, and making the right API calls. The connector makes sure that the method call follows the Serper API requirements, such as correct formatting requests and handling responses.
Note that we used Serper just as an example here. This could be any OpenAPI-compliant service.
:::info
To run the following code snippet, note that you have to have your own Serper and OpenAI API keys.
:::
```python
import json
import requests
from typing import Any
from haystack import Pipeline
from haystack.components.converters import OutputAdapter
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.dataclasses.byte_stream import ByteStream
from haystack_integrations.components.connectors.openapi import OpenAPIServiceConnector
from haystack_integrations.components.converters.openapi import (
OpenAPIServiceToFunctions,
)
def prepare_fc_params(openai_functions_schema: dict[str, Any]) -> dict[str, Any]:
return {
"tools": [{"type": "function", "function": openai_functions_schema}],
"tool_choice": {
"type": "function",
"function": {"name": openai_functions_schema["name"]},
},
}
serperdev_spec = requests.get("https://bit.ly/serper_dev_spec").json()
system_prompt = requests.get("https://bit.ly/serper_dev_system").text
user_prompt = "Why was Sam Altman ousted from OpenAI?"
pipe = Pipeline()
pipe.add_component("spec_to_functions", OpenAPIServiceToFunctions())
pipe.add_component(
"prepare_fc_adapter",
OutputAdapter(
"{{functions[0] | prepare_fc}}",
dict[str, Any],
{"prepare_fc": prepare_fc_params},
),
)
pipe.add_component("functions_llm", OpenAIChatGenerator())
pipe.add_component("openapi_connector", OpenAPIServiceConnector())
pipe.add_component(
"message_adapter",
OutputAdapter(
"{{system_message + service_response}}",
list[ChatMessage],
unsafe=True,
),
)
pipe.add_component("llm", OpenAIChatGenerator())
pipe.connect("spec_to_functions.functions", "prepare_fc_adapter.functions")
pipe.connect(
"spec_to_functions.openapi_specs",
"openapi_connector.service_openapi_spec",
)
pipe.connect("prepare_fc_adapter", "functions_llm.generation_kwargs")
pipe.connect("functions_llm.replies", "openapi_connector.messages")
pipe.connect("openapi_connector.service_response", "message_adapter.service_response")
pipe.connect("message_adapter", "llm.messages")
result = pipe.run(
data={
"functions_llm": {
"messages": [
ChatMessage.from_system("Only do tool/function calling"),
ChatMessage.from_user(user_prompt),
],
},
"openapi_connector": {
"service_credentials": serper_dev_key,
},
"spec_to_functions": {
"sources": [ByteStream.from_string(json.dumps(serperdev_spec))],
},
"message_adapter": {
"system_message": [ChatMessage.from_system(system_prompt)],
},
},
)
print(result["llm"]["replies"][0].text)
# Sam Altman was ousted from OpenAI on November 17, 2023, following
# a "deliberative review process" by the board of directors. The board concluded
# that he was not "consistently candid in his communications". However, he
# returned as CEO just days after his ouster.
```
---
// File: pipeline-components/connectors/opentelemetryconnector
# OpenTelemetryConnector
Learn how to work with OpenTelemetry in Haystack.
| | |
| --- | --- |
| **Most common position in a pipeline** | Anywhere, as it’s not connected to other components |
| **Mandatory init variables** | None. The tracer is created at initialization time |
| **Output variables** | `name`: The name of the tracing component |
| **API reference** | [opentelemetry](/reference/integrations-opentelemetry) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/opentelemetry |
| **Package name** | `opentelemetry-haystack` |
## Overview
`OpenTelemetryConnector` integrates tracing capabilities into Haystack pipelines using [OpenTelemetry](https://opentelemetry.io/), through the [OpenTelemetry SDK](https://opentelemetry.io/docs/languages/python/). It captures detailed information about pipeline runs, like API calls, context data, prompts, and more, so you can see the complete trace of your pipeline execution in any OpenTelemetry-compatible backend.
OpenTelemetry tracing is enabled as soon as the `OpenTelemetryConnector` is initialized, so you only need to add it to your pipeline – it does not need to be connected to other components or to run to take effect.
You can optionally pass a `name` to identify this tracing component (it defaults to `opentelemetry`).
### Prerequisites
These are the things that you need before working with the `OpenTelemetryConnector`:
1. A configured OpenTelemetry `TracerProvider` with an exporter (for example, an OTLP exporter that sends traces to a collector or a backend). Set up the provider before initializing the connector.
2. Set the `HAYSTACK_CONTENT_TRACING_ENABLED` environment variable to `true` – this will enable content tracing (inputs and outputs) in your pipelines.
3. To add traces at even deeper levels, check out the available [OpenTelemetry instrumentations](https://opentelemetry.io/ecosystem/registry/?s=python), such as `opentelemetry-instrumentation-openai-v2` for tracing OpenAI requests.
### Installation
First, install the `opentelemetry-haystack` package to use the `OpenTelemetryConnector`:
```shell
pip install opentelemetry-haystack
```
:::info[Usage Notice]
To ensure proper tracing, always set environment variables before importing any Haystack components. This is crucial because Haystack initializes its internal tracing components during import. In the example below, we first set the environment variables and then import the relevant Haystack components.
Alternatively, an even better practice is to set these environment variables in your shell before running the script. This approach keeps configuration separate from code and allows for easier management of different environments.
:::
## Usage
In the example below, we are adding `OpenTelemetryConnector` to the pipeline as a _tracer_. Each pipeline run will produce a trace that includes the entire execution context, including prompts, completions, and metadata. You can then view the traces in your OpenTelemetry backend.
```python
import os
os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.semconv.resource import ResourceAttributes
# Configure the OpenTelemetry SDK. A service name is required for most backends.
resource = Resource(attributes={ResourceAttributes.SERVICE_NAME: "haystack"})
tracer_provider = TracerProvider(resource=resource)
tracer_provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint="http://localhost:4318/v1/traces")),
)
trace.set_tracer_provider(tracer_provider)
from haystack import Pipeline
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack_integrations.components.connectors.opentelemetry import (
OpenTelemetryConnector,
)
pipe = Pipeline()
pipe.add_component("tracer", OpenTelemetryConnector("Chat example"))
pipe.add_component("prompt_builder", ChatPromptBuilder())
pipe.add_component("llm", OpenAIChatGenerator())
pipe.connect("prompt_builder.prompt", "llm.messages")
messages = [
ChatMessage.from_system(
"Always respond in German even if some input data is in other languages.",
),
ChatMessage.from_user("Tell me about {{location}}"),
]
response = pipe.run(
data={
"prompt_builder": {
"template_variables": {"location": "Berlin"},
"template": messages,
},
},
)
print(response["llm"]["replies"][0])
```
### Configuring the tracing backend directly
Instead of using the `OpenTelemetryConnector`, you can configure the OpenTelemetry tracing backend directly by enabling an `OpenTelemetryTracer`. Make sure to set the `HAYSTACK_CONTENT_TRACING_ENABLED` environment variable and configure your `TracerProvider` before importing any Haystack components.
```python
from opentelemetry import trace
from haystack import tracing
from haystack_integrations.tracing.opentelemetry import OpenTelemetryTracer
tracing.enable_tracing(OpenTelemetryTracer(trace.get_tracer("my_application")))
```
---
// File: pipeline-components/connectors/weaveconnector
# WeaveConnector
Learn how to use Weights & Biases Weave framework for tracing and monitoring your pipeline components.
| | |
| --- | --- |
| **Most common position in a pipeline** | Anywhere, as it’s not connected to other components |
| **Mandatory init variables** | `pipeline_name`: The name of your pipeline, which will also show up in Weaver dashboard. |
| **Output variables** | `pipeline_name`: The name of the pipeline that just run |
| **API reference** | [Weave](/reference/integrations-weave) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/weave |
| **Package name** | `weave-haystack` |
## Overview
This integration allows you to trace and visualize your pipeline execution in [Weights & Biases](https://wandb.ai/site/).
Information captured by the Haystack tracing tool, such as API calls, context data, and prompts, is sent to Weights & Biases, where you can see the complete trace of your pipeline execution.
### Prerequisites
You need a Weave account to use this feature. You can sign up for free at [Weights & Biases website](https://wandb.ai/site).
You will then need to set the `WANDB_API_KEY` environment variable with your Weights & Biases API key. Once logged in, you can find your API key on [your home page](https://wandb.ai/home).
Then go to `https://wandb.ai//projects` and see the full trace for your pipeline under the pipeline name you specified when creating the `WeaveConnector`.
You will also need to set the `HAYSTACK_CONTENT_TRACING_ENABLED` environment variable set to `true`.
## Usage
First, install the `weave-haystack` package to use this connector:
```shell
pip install weave-haystack
```
Then, add it to your pipeline without any connections, and it will automatically start sending traces to Weights & Biases:
```python
import os
from haystack import Pipeline
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack_integrations.components.connectors.weave import WeaveConnector
pipe = Pipeline()
pipe.add_component("prompt_builder", ChatPromptBuilder())
pipe.add_component("llm", OpenAIChatGenerator())
pipe.connect("prompt_builder.prompt", "llm.messages")
connector = WeaveConnector(pipeline_name="test_pipeline")
pipe.add_component("weave", connector)
messages = [
ChatMessage.from_system(
"Always respond in German even if some input data is in other languages.",
),
ChatMessage.from_user("Tell me about {{location}}"),
]
response = pipe.run(
data={
"prompt_builder": {
"template_variables": {"location": "Berlin"},
"template": messages,
},
},
)
```
You can then see the complete trace for your pipeline at `https://wandb.ai//projects` under the pipeline name you specified when creating the `WeaveConnector`.
### With an Agent
```python
import os
# Enable Haystack content tracing
os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
from typing import Annotated
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.tools import tool
from haystack import Pipeline
from haystack_integrations.components.connectors.weave import WeaveConnector
@tool
def get_weather(city: Annotated[str, "The city to get weather for"]) -> str:
"""Get current weather information for a city."""
weather_data = {
"Berlin": "18°C, partly cloudy",
"New York": "22°C, sunny",
"Tokyo": "25°C, clear skies",
}
return weather_data.get(city, f"Weather information for {city} not available")
@tool
def calculate(
operation: Annotated[
str,
"Mathematical operation: add, subtract, multiply, divide",
],
a: Annotated[float, "First number"],
b: Annotated[float, "Second number"],
) -> str:
"""Perform basic mathematical calculations."""
if operation == "add":
result = a + b
elif operation == "subtract":
result = a - b
elif operation == "multiply":
result = a * b
elif operation == "divide":
if b == 0:
return "Error: Division by zero"
result = a / b
else:
return f"Error: Unknown operation '{operation}'"
return f"The result of {a} {operation} {b} is {result}"
# Create the chat generator
chat_generator = OpenAIChatGenerator()
# Create the agent with tools
agent = Agent(
chat_generator=chat_generator,
tools=[get_weather, calculate],
system_prompt="You are a helpful assistant with access to weather and calculator tools. Use them when needed.",
exit_conditions=["text"],
)
# Create the WeaveConnector for tracing
weave_connector = WeaveConnector(pipeline_name="Agent Example")
# Build the pipeline
pipe = Pipeline()
pipe.add_component("tracer", weave_connector)
pipe.add_component("agent", agent)
# Run the pipeline
response = pipe.run(
data={
"agent": {
"messages": [
ChatMessage.from_user(
"What's the weather in Berlin and calculate 15 + 27?",
),
],
},
"tracer": {},
},
)
# Display results
print("Agent Response:")
print(response["agent"]["last_message"].text)
print(f"\nPipeline Name: {response['tracer']['pipeline_name']}")
print(
"\nCheck your Weights & Biases dashboard at https://wandb.ai//projects to see the traces!",
)
```
---
// File: pipeline-components/connectors
# Connectors
These are Haystack integrations that connect your pipelines to services by external providers.
| Component | Description |
| --- | --- |
| [DatadogConnector](connectors/datadogconnector.mdx) | Enables tracing in Haystack pipelines using Datadog. |
| [GitHubFileEditor](connectors/githubfileeditor.mdx) | Enables editing files in GitHub repositories through the GitHub API. |
| [GitHubIssueCommenter](connectors/githubissuecommenter.mdx) | Enables posting comments to GitHub issues using the GitHub API. |
| [GitHubIssueViewer](connectors/githubissueviewer.mdx) | Enables fetching and parsing GitHub issues into Haystack documents. |
| [GitHubPRCreator](connectors/githubprcreator.mdx) | Enables creating pull requests from a fork back to the original repository through the GitHub API. |
| [GitHubRepoForker](connectors/githubrepoforker.mdx) | Enables forking a GitHub repository from an issue URL through the GitHub API. |
| [GitHubRepoViewer](connectors/githubrepoviewer.mdx) | Enables navigating and fetching content from GitHub repositories through the GitHub API. |
| [JinaReaderConnector](connectors/jinareaderconnector.mdx) | Use Jina AI’s Reader API with Haystack. |
| [LangfuseConnector](connectors/langfuseconnector.mdx) | Enables tracing in Haystack pipelines using Langfuse. |
| [OAuthTokenResolver](connectors/oauthtokenresolver.mdx) | Resolves an OAuth access token at runtime and emits it for downstream components. |
| [OpenAPIConnector](connectors/openapiconnector.mdx) | Acts as an interface between the Haystack ecosystem and OpenAPI services, using explicit input arguments. |
| [OpenAPIServiceConnector](connectors/openapiserviceconnector.mdx) | Acts as an interface between the Haystack ecosystem and OpenAPI services. |
| [OpenTelemetryConnector](connectors/opentelemetryconnector.mdx) | Enables tracing in Haystack pipelines using OpenTelemetry. |
| [WeaveConnector](connectors/weaveconnector.mdx) | Connects you to Weights & Biases Weave framework for tracing and monitoring your pipeline components. |
---
// File: pipeline-components/converters/amazontextractconverter
# AmazonTextractConverter
`AmazonTextractConverter` converts images and single-page PDFs to documents using AWS Textract. It supports plain text OCR, structured analysis of tables, forms, signatures, and layout, as well as natural-language queries over the document.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx), or right at the beginning of an indexing pipeline |
| **Mandatory init variables** | AWS credentials are resolved via `Secret` parameters or the default boto3 credential chain (environment variables, AWS config files, IAM roles). |
| **Mandatory run variables** | `sources`: A list of file paths or `ByteStream` objects |
| **Output variables** | `documents`: A list of documents
`raw_textract_response`: A list of raw responses from the Textract API |
| **API reference** | [Amazon Textract](/reference/integrations-amazon_textract) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/amazon_textract |
| **Package name** | `amazon-textract-haystack` |
## Overview
`AmazonTextractConverter` takes a list of file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects as input and uses AWS Textract to extract text from images and single-page PDFs. Optionally, metadata can be attached to the documents through the `meta` input parameter. You need an active AWS account with access to the Textract service to use this integration. Refer to the [AWS Textract documentation](https://docs.aws.amazon.com/textract/latest/dg/getting-started.html) to set up your AWS credentials and ensure Textract is available in your selected region.
Supported input formats: JPEG, PNG, TIFF, BMP, and single-page PDF (up to 10 MB).
By default, the component uses the standard AWS environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`, `AWS_DEFAULT_REGION`, `AWS_PROFILE`) for authentication. You can also pass these as `Secret` objects at initialization. The component falls back to the default boto3 credential chain if no explicit credentials are provided, which makes it work with IAM roles when running on AWS infrastructure.
### Operation modes
The component switches between two Textract APIs depending on how you configure it:
- **Plain text OCR (`DetectDocumentText`)** – Used when `feature_types` is not set. This is the fastest and cheapest option, extracting raw text from the document.
- **Structured analysis (`AnalyzeDocument`)** – Used when `feature_types` is set. You can pass any combination of `"TABLES"`, `"FORMS"`, `"SIGNATURES"`, and `"LAYOUT"` to extract richer structural information from the document.
### Natural-language queries
You can pass a list of natural-language questions through the `queries` parameter on `run()`. When queries are provided, the `QUERIES` feature type is added automatically and Textract returns the extracted answers in the raw response. This is useful for pulling specific fields out of forms, invoices, or receipts without writing custom parsing logic.
## Usage
You need to install the `amazon-textract-haystack` integration to use `AmazonTextractConverter`:
```shell
pip install amazon-textract-haystack
```
### On its own
Basic usage with plain text OCR:
```python
from haystack_integrations.components.converters.amazon_textract import (
AmazonTextractConverter,
)
converter = AmazonTextractConverter()
result = converter.run(sources=["document.png"])
documents = result["documents"]
```
Extracting tables and forms with `AnalyzeDocument`:
```python
from haystack_integrations.components.converters.amazon_textract import (
AmazonTextractConverter,
)
converter = AmazonTextractConverter(feature_types=["TABLES", "FORMS"])
result = converter.run(sources=["invoice.pdf"])
documents = result["documents"]
raw_responses = result["raw_textract_response"]
```
Using natural-language queries to extract specific fields:
```python
from haystack_integrations.components.converters.amazon_textract import (
AmazonTextractConverter,
)
converter = AmazonTextractConverter()
result = converter.run(
sources=["receipt.png"],
queries=["What is the patient name?", "What is the total due?"],
)
documents = result["documents"]
raw_responses = result["raw_textract_response"]
```
Passing AWS credentials explicitly:
```python
from haystack.utils import Secret
from haystack_integrations.components.converters.amazon_textract import (
AmazonTextractConverter,
)
converter = AmazonTextractConverter(
aws_access_key_id=Secret.from_env_var("AWS_ACCESS_KEY_ID"),
aws_secret_access_key=Secret.from_env_var("AWS_SECRET_ACCESS_KEY"),
aws_region_name=Secret.from_token("us-east-1"),
)
result = converter.run(sources=["document.png"])
```
### In a pipeline
Here's an example of an indexing pipeline that uses Textract to extract text from images and writes the resulting documents to a Document Store:
```python
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.preprocessors import DocumentCleaner, DocumentSplitter
from haystack.components.writers import DocumentWriter
from haystack_integrations.components.converters.amazon_textract import (
AmazonTextractConverter,
)
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component("converter", AmazonTextractConverter())
pipeline.add_component("cleaner", DocumentCleaner())
pipeline.add_component(
"splitter",
DocumentSplitter(split_by="sentence", split_length=5),
)
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "cleaner")
pipeline.connect("cleaner", "splitter")
pipeline.connect("splitter", "writer")
file_names = ["document.png", "invoice.pdf"]
pipeline.run({"converter": {"sources": file_names}})
```
---
// File: pipeline-components/converters/azuredocumentintelligenceconverter
# AzureDocumentIntelligenceConverter
`AzureDocumentIntelligenceConverter` converts files to Documents using Azure's Document Intelligence service with GitHub Flavored Markdown output for better LLM/RAG integration. It supports the following file formats: PDF (both searchable and image-only), JPEG, PNG, BMP, TIFF, DOCX, XLSX, PPTX, and HTML.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx), or right at the beginning of an indexing pipeline |
| **Mandatory init variables** | `endpoint`: The endpoint URL of your Azure Document Intelligence resource
`api_key`: The API key for Azure authentication. Can be set with `AZURE_DI_API_KEY` environment variable. |
| **Mandatory run variables** | `sources`: A list of file paths or ByteStream objects |
| **Output variables** | `documents`: A list of documents
`raw_azure_response`: A list of raw responses from Azure |
| **API reference** | [Azure Document Intelligence](/reference/integrations-azure_doc_intelligence) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/azure_doc_intelligence |
| **Package name** | `azure-doc-intelligence-haystack` |
## Overview
`AzureDocumentIntelligenceConverter` takes a list of file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects as input and uses Azure's Document Intelligence service to convert the files to a list of documents. Optionally, metadata can be attached to the documents through the `meta` input parameter. You need an active Azure account and a Document Intelligence or Cognitive Services resource to use this integration. Follow the steps described in the Azure [documentation](https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/quickstarts/get-started-sdks-rest-api) to set up your resource.
The component uses an `AZURE_DI_API_KEY` environment variable by default. Otherwise, you can pass an `api_key` at initialization — see code examples below.
This component uses the `azure-ai-documentintelligence` package (v1.0.0+) and outputs GitHub Flavored Markdown, preserving document structure such as headings, tables, and lists. Tables are rendered as inline markdown tables rather than being extracted as separate documents.
When you initialize the component, you can optionally set the `model_id`, which refers to the model you want to use. Available options include:
- `"prebuilt-document"`: General document analysis (default)
- `"prebuilt-read"`: Fast OCR for text extraction
- `"prebuilt-layout"`: Enhanced layout analysis with better table and structure detection
- Custom model IDs from your Azure resource
Refer to the [Azure documentation](https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/choose-model-feature) for a full list of available models.
:::info
This component replaces the legacy [`AzureOCRDocumentConverter`](azureocrdocumentconverter.mdx), which uses the older `azure-ai-formrecognizer` package. The `AzureDocumentIntelligenceConverter` uses the newer `azure-ai-documentintelligence` SDK and produces Markdown output instead of plain text, making it better suited for LLM and RAG applications.
:::
:::note
This component returns Markdown content. Avoid piping it through `DocumentCleaner()` with its default settings because `remove_extra_whitespaces=True` and `remove_empty_lines=True` can collapse line breaks and flatten headings, tables, and lists. Connect the converter directly to your next component, or disable those options if you need custom cleanup.
:::
## Usage
You need to install the `azure-doc-intelligence-haystack` integration to use the `AzureDocumentIntelligenceConverter`:
```shell
pip install azure-doc-intelligence-haystack
```
### On its own
```python
from pathlib import Path
from haystack_integrations.components.converters.azure_doc_intelligence import (
AzureDocumentIntelligenceConverter,
)
from haystack.utils import Secret
converter = AzureDocumentIntelligenceConverter(
endpoint="https://YOUR_RESOURCE.cognitiveservices.azure.com/",
api_key=Secret.from_env_var("AZURE_DI_API_KEY"),
)
result = converter.run(sources=[Path("my_file.pdf")])
documents = result["documents"]
```
### In a pipeline
```python
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
from haystack.utils import Secret
from haystack_integrations.components.converters.azure_doc_intelligence import (
AzureDocumentIntelligenceConverter,
)
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component(
"converter",
AzureDocumentIntelligenceConverter(
endpoint="https://YOUR_RESOURCE.cognitiveservices.azure.com/",
api_key=Secret.from_env_var("AZURE_DI_API_KEY"),
),
)
pipeline.add_component(
"splitter",
DocumentSplitter(split_by="sentence", split_length=5),
)
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "splitter")
pipeline.connect("splitter", "writer")
file_names = ["my_file.pdf"]
pipeline.run({"converter": {"sources": file_names}})
```
---
// File: pipeline-components/converters/azureocrdocumentconverter
# AzureOCRDocumentConverter
`AzureOCRDocumentConverter` converts files to documents using Azure's Document Intelligence service. It supports the following file formats: PDF (both searchable and image-only), JPEG, PNG, BMP, TIFF, DOCX, XLSX, PPTX, and HTML.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) , or right at the beginning of an indexing pipeline |
| **Mandatory init variables** | `endpoint`: The endpoint of your Azure resource
`api_key`: The API key of your Azure resource. Can be set with `AZURE_AI_API_KEY` environment variable. |
| **Mandatory run variables** | `sources`: A list of file paths |
| **Output variables** | `documents`: A list of documents
`raw_azure_response`: A list of raw responses from Azure |
| **API reference** | [Azure Form Recognizer](/reference/integrations-azure_form_recognizer) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/azure_form_recognizer |
| **Package name** | `azure-form-recognizer-haystack` |
## Overview
`AzureOCRDocumentConverter` takes a list of file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects as input and uses Azure services to convert the files to a list of documents. Optionally, metadata can be attached to the documents through the `meta` input parameter. You need an active Azure account and a Document Intelligence or Cognitive Services resource to use this integration. Follow the steps described in the Azure [documentation](https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/quickstarts/get-started-sdks-rest-api) to set up your resource.
The component uses an `AZURE_AI_API_KEY` environment variable by default. Otherwise, you can pass an `api_key` at initialization – see code examples below.
When you initialize the component, you can optionally set the `model_id`, which refers to the model you want to use. Please refer to [Azure documentation](https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/choose-model-feature) for a list of available models. The default model is `"prebuilt-read"`.
The `AzureOCRDocumentConverter` doesn’t leave tables inline in the page text. It creates a separate `Document` for each table, with the table rendered as CSV in the document content and `preceding_context`, `following_context`, and `page` added to its metadata.
## Usage
The `AzureOCRDocumentConverter` is part of the `azure-form-recognizer-haystack` integration package. Install it with:
```shell
pip install azure-form-recognizer-haystack
```
### On its own
```python
from pathlib import Path
from haystack_integrations.components.converters.azure_form_recognizer import (
AzureOCRDocumentConverter,
)
from haystack.utils import Secret
converter = AzureOCRDocumentConverter(
endpoint="azure_resource_url",
api_key=Secret.from_token(""),
)
converter.run(sources=[Path("my_file.pdf")])
```
### In a pipeline
```python
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack_integrations.components.converters.azure_form_recognizer import (
AzureOCRDocumentConverter,
)
from haystack.components.preprocessors import DocumentCleaner
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
from haystack.utils import Secret
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component(
"converter",
AzureOCRDocumentConverter(
endpoint="azure_resource_url",
api_key=Secret.from_token(""),
),
)
pipeline.add_component("cleaner", DocumentCleaner())
pipeline.add_component(
"splitter",
DocumentSplitter(split_by="sentence", split_length=5),
)
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "cleaner")
pipeline.connect("cleaner", "splitter")
pipeline.connect("splitter", "writer")
file_names = ["my_file.pdf"]
pipeline.run({"converter": {"sources": file_names}})
```
---
// File: pipeline-components/converters/csvtodocument
# CSVToDocument
Converts CSV files to documents.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) , or right at the beginning of an indexing pipeline |
| **Mandatory run variables** | `sources`: A list of file paths or [ByteStream](../../concepts/data-classes.mdx#bytestream) objects |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [Converters](/reference/converters-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/csv.py |
| **Package name** | `haystack-ai` |
## Overview
`CSVToDocument` converts one or more CSV files into a text document.
The component uses UTF-8 encoding by default, but you may specify a different encoding if needed during initialization.
You can optionally attach metadata to each document with a `meta` parameter when running the component.
## Usage
### On its own
```python
from haystack.components.converters.csv import CSVToDocument
converter = CSVToDocument()
results = converter.run(
sources=["sample.csv"],
meta={"date_added": datetime.now().isoformat()},
)
documents = results["documents"]
print(documents[0].content)
# 'col1,col2\nrow1,row1\nrow2,row2\n'
```
### In a pipeline
```python
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.converters import CSVToDocument
from haystack.components.preprocessors import DocumentCleaner
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component("converter", CSVToDocument())
pipeline.add_component("cleaner", DocumentCleaner())
pipeline.add_component(
"splitter",
DocumentSplitter(split_by="sentence", split_length=5),
)
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "cleaner")
pipeline.connect("cleaner", "splitter")
pipeline.connect("splitter", "writer")
pipeline.run({"converter": {"sources": file_names}})
```
---
// File: pipeline-components/converters/doclingconverter
# DoclingConverter
`DoclingConverter` converts PDF, DOCX, HTML, and other document formats to Haystack Documents using [Docling](https://docling-project.github.io/docling/), a document parsing library that understands document structure including layout, tables, and headings.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx), or right at the beginning of an indexing pipeline |
| **Mandatory run variables** | `sources`: A list of file paths, URLs, or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [Docling](/reference/integrations-docling) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/docling |
| **Package name** | `docling-haystack` |
## Overview
The `DoclingConverter` takes a list of file paths, URLs, or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects and uses Docling to parse them into a rich document representation that captures layout, tables, headings, and other structural elements.
The component supports three export modes, controlled by the `export_type` parameter:
- **`ExportType.MARKDOWN`** (default): Exports each input document as a single Markdown string in one [`Document`](../../concepts/data-classes.mdx#document). Use this mode when you want to preserve the full document content as formatted text.
- **`ExportType.DOC_CHUNKS`**: Chunks each document using Docling's `HybridChunker` and returns one [`Document`](../../concepts/data-classes.mdx#document) per chunk. Chunk metadata includes structural context from Docling. Use this mode for indexing pipelines where downstream retrieval benefits from semantically coherent chunks.
- **`ExportType.JSON`**: Serializes the full Docling document to a JSON string in one [`Document`](../../concepts/data-classes.mdx#document). Use this mode when you need access to the complete structured representation.
You can customize parsing behavior by passing a pre-configured `DocumentConverter` instance via the `converter` parameter, and pass additional keyword arguments to Docling's conversion step via `convert_kwargs`. For `ExportType.MARKDOWN`, use `md_export_kwargs` to control Markdown rendering options (for example, image placeholder text). For `ExportType.DOC_CHUNKS`, provide a custom `BaseChunker` instance via the `chunker` parameter.
Document metadata is populated by a `MetaExtractor` instance. The default `MetaExtractor` adds Docling-specific metadata (chunk structure or document origin) under the `dl_meta` key. You can supply a custom `BaseMetaExtractor` implementation via the `meta_extractor` parameter. Additional metadata can be attached to all output Documents by passing a dictionary to the `meta` run parameter, or per source by passing a list of dictionaries.
## Usage
Install the Docling integration:
```shell
pip install docling-haystack
```
### On its own
```python
from haystack_integrations.components.converters.docling import (
DoclingConverter,
ExportType,
)
# Default: full document as Markdown
converter = DoclingConverter()
result = converter.run(sources=["report.pdf", "notes.docx"])
documents = result["documents"]
print(documents[0].content)
# One document per chunk
converter = DoclingConverter(export_type=ExportType.DOC_CHUNKS)
result = converter.run(sources=["report.pdf"])
documents = result["documents"]
```
### In a pipeline
```python
from haystack import Pipeline
from haystack.components.writers import DocumentWriter
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack_integrations.components.converters.docling import DoclingConverter
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component("converter", DoclingConverter())
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "writer")
pipeline.run({"converter": {"sources": ["report.pdf", "manual.docx"]}})
```
When you set `export_type=ExportType.DOC_CHUNKS`, `DoclingConverter` already chunks the documents, so you typically don't need a separate `DocumentSplitter` in the pipeline.
## Additional Features
### Custom chunking
Provide a custom Docling chunker to control how documents are split. The `chunker` parameter only takes effect with `ExportType.DOC_CHUNKS`:
```python
from docling.chunking import HybridChunker
from haystack_integrations.components.converters.docling import (
DoclingConverter,
ExportType,
)
chunker = HybridChunker(tokenizer="BAAI/bge-small-en-v1.5", max_tokens=256)
converter = DoclingConverter(export_type=ExportType.DOC_CHUNKS, chunker=chunker)
result = converter.run(sources=["report.pdf"])
```
### Attaching metadata
Pass a single dictionary to apply metadata to all output Documents, or a list to set metadata per source:
```python
from haystack_integrations.components.converters.docling import DoclingConverter
converter = DoclingConverter()
# Same metadata for all sources
result = converter.run(
sources=["a.pdf", "b.pdf"],
meta={"project": "research"},
)
# Per-source metadata
result = converter.run(
sources=["a.pdf", "b.pdf"],
meta=[{"title": "Report A"}, {"title": "Report B"}],
)
```
### Processing in-memory files
Pass [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects to convert files loaded into memory. Set `file_path` in the ByteStream metadata so Docling can detect the file format:
```python
from haystack.dataclasses import ByteStream
from haystack_integrations.components.converters.docling import DoclingConverter
with open("report.pdf", "rb") as f:
data = f.read()
source = ByteStream(data=data, meta={"file_path": "report.pdf"})
converter = DoclingConverter()
result = converter.run(sources=[source])
```
---
// File: pipeline-components/converters/doclingserveconverter
# DoclingServeConverter
`DoclingServeConverter` converts PDF, DOCX, HTML, and other document formats to Haystack Documents by calling a [DoclingServe](https://github.com/docling-project/docling-serve) HTTP server. Unlike the local [`DoclingConverter`](doclingconverter.mdx), this component has no heavy ML dependencies — all document parsing happens on the remote server.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx), or right at the beginning of an indexing pipeline |
| **Mandatory run variables** | `sources`: A list of file paths, URLs, or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [Docling Serve](/reference/integrations-docling_serve) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/docling_serve |
| **Package name** | `docling-serve-haystack` |
## Overview
The `DoclingServeConverter` takes a list of file paths, URLs, or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects and sends them to a running DoclingServe instance for parsing. Local files and `ByteStream` objects are uploaded to the `/v1/convert/file` endpoint; URL strings are sent to `/v1/convert/source`.
The component supports three export modes, controlled by the `export_type` parameter:
- **`ExportType.MARKDOWN`** (default): Returns the document content as a Markdown string. Use this mode when you want well-structured text output with formatting preserved.
- **`ExportType.TEXT`**: Returns plain text extracted from the document. Use this mode when you need clean, unformatted text.
- **`ExportType.JSON`**: Returns the full Docling document representation as a JSON string. Use this mode when you need access to the complete structured representation.
Each source produces one [`Document`](../../concepts/data-classes.mdx#document) in the output. Sources that fail to convert are skipped with a warning logged.
You can pass additional conversion options to the DoclingServe API via the `convert_options` parameter (for example, `{"do_ocr": True, "ocr_engine": "tesseract"}`). If the DoclingServe instance requires authentication, pass the API key via the `api_key` parameter or set the `DOCLING_SERVE_API_KEY` environment variable.
The component supports both synchronous (`run`) and asynchronous (`run_async`) execution.
## Usage
Install the Docling Serve integration:
```shell
pip install docling-serve-haystack
```
Start a DoclingServe instance locally (requires Docker):
```shell
docker run -p 5001:5001 ghcr.io/docling-project/docling-serve-cpu:latest
```
### On its own
```python
from haystack_integrations.components.converters.docling_serve import (
DoclingServeConverter,
)
# Default: Markdown output
converter = DoclingServeConverter(base_url="http://localhost:5001")
result = converter.run(sources=["report.pdf", "notes.docx"])
documents = result["documents"]
print(documents[0].content[:200])
# Plain text output
from haystack_integrations.components.converters.docling_serve import ExportType
converter = DoclingServeConverter(
base_url="http://localhost:5001",
export_type=ExportType.TEXT,
)
result = converter.run(sources=["report.pdf"])
print(result["documents"][0].content)
```
### In a pipeline
```python
from haystack import Pipeline
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack_integrations.components.converters.docling_serve import (
DoclingServeConverter,
)
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component(
"converter",
DoclingServeConverter(base_url="http://localhost:5001"),
)
pipeline.add_component("splitter", DocumentSplitter())
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "splitter")
pipeline.connect("splitter", "writer")
pipeline.run({"converter": {"sources": ["report.pdf", "manual.docx"]}})
```
## Additional Features
### Converting URLs directly
Pass URL strings to convert remote documents without downloading them first:
```python
from haystack_integrations.components.converters.docling_serve import (
DoclingServeConverter,
)
converter = DoclingServeConverter(base_url="http://localhost:5001")
result = converter.run(sources=["https://arxiv.org/pdf/2602.17316"])
print(result["documents"][0].content[:200])
```
### Attaching metadata
Pass a single dictionary to apply metadata to all output Documents, or a list to set metadata per source:
```python
from haystack_integrations.components.converters.docling_serve import (
DoclingServeConverter,
)
converter = DoclingServeConverter(base_url="http://localhost:5001")
# Same metadata for all sources
result = converter.run(
sources=["a.pdf", "b.pdf"],
meta={"project": "research"},
)
# Per-source metadata
result = converter.run(
sources=["a.pdf", "b.pdf"],
meta=[{"title": "Report A"}, {"title": "Report B"}],
)
```
### Processing in-memory files
Pass [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects to convert files loaded into memory. Set `file_path` in the ByteStream metadata so DoclingServe can detect the file format:
```python
from haystack.dataclasses import ByteStream
from haystack_integrations.components.converters.docling_serve import (
DoclingServeConverter,
)
with open("report.pdf", "rb") as f:
data = f.read()
source = ByteStream(data=data, meta={"file_path": "report.pdf"})
converter = DoclingServeConverter(base_url="http://localhost:5001")
result = converter.run(sources=[source])
```
---
// File: pipeline-components/converters/documenttoimagecontent
# DocumentToImageContent
`DocumentToImageContent` extracts visual data from image or PDF file-based documents and converts them into `ImageContent` objects. These are ready for multimodal AI pipelines, including tasks like image question-answering and captioning.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before a `ChatPromptBuilder` in a query pipeline |
| **Mandatory run variables** | `documents`: A list of documents to process. Each document should have metadata containing at minimum a 'file_path_meta_field' key. PDF documents additionally require a 'page_number' key to specify which page to convert. |
| **Output variables** | `image_contents`: A list of `ImageContent` objects |
| **API reference** | [Image Converters](/reference/image-converters-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/image/document_to_image.py |
| **Package name** | `haystack-ai` |
## Overview
`DocumentToImageContent` processes a list of documents containing image or PDF file paths and converts them into `ImageContent` objects.
- For images, it reads and encodes the file directly.
- For PDFs, it extracts the specified page (through `page_number` in metadata) and converts it to an image.
By default, it looks for the file path in the `file_path` metadata field. You can customize this with the `file_path_meta_field` parameter. The `root_path` lets you specify a common base directory for file resolution.
This component is typically used in query pipelines right before a `ChatPromptBuilder` when you would like to add Images to your user prompt.
If `size` is provided, the images will be resized while maintaining aspect ratio. This reduces file size, memory usage, and processing time, which is beneficial when working with models that have resolution constraints or when transmitting images to remote services.
## Usage
### On its own
```python
from haystack import Document
from haystack.components.converters.image.document_to_image import (
DocumentToImageContent,
)
converter = DocumentToImageContent(
file_path_meta_field="file_path",
root_path="/data/documents",
detail="high",
size=(800, 600),
)
documents = [
Document(content="Photo of a mountain", meta={"file_path": "mountain.jpg"}),
Document(
content="First page of a report",
meta={"file_path": "report.pdf", "page_number": 1},
),
]
result = converter.run(documents)
image_contents = result["image_contents"]
print(image_contents)
# [
# ImageContent(
# base64_image="/9j/4A...", mime_type="image/jpeg", detail="high",
# meta={"file_path": "mountain.jpg"}
# ),
# ImageContent(
# base64_image="/9j/4A...", mime_type="image/jpeg", detail="high",
# meta={"file_path": "report.pdf", "page_number": 1}
# )
# ]
```
### In a pipeline
You can use `DocumentToImageContent` in multimodal indexing pipelines before passing to an Embedder or captioning model.
```python
from haystack import Document, Pipeline
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.converters.image.document_to_image import (
DocumentToImageContent,
)
# Query pipeline
pipeline = Pipeline()
pipeline.add_component("image_converter", DocumentToImageContent(detail="auto"))
pipeline.add_component(
"chat_prompt_builder",
ChatPromptBuilder(
required_variables=["question"],
template="""{% message role="system" %}
You are a friendly assistant that answers questions based on provided images.
{% endmessage %}
{%- message role="user" -%}
Only provide an answer to the question using the images provided.
Question: {{ question }}
Answer:
{%- for img in image_contents -%}
{{ img | templatize_part }}
{%- endfor -%}
{%- endmessage -%}
""",
),
)
pipeline.add_component("llm", OpenAIChatGenerator(model="gpt-4o-mini"))
pipeline.connect("image_converter", "chat_prompt_builder.image_contents")
pipeline.connect("chat_prompt_builder", "llm")
documents = [
Document(content="Cat image", meta={"file_path": "cat.jpg"}),
Document(content="Doc intro", meta={"file_path": "paper.pdf", "page_number": 1}),
]
result = pipeline.run(
data={
"image_converter": {"documents": documents},
"chat_prompt_builder": {"question": "What color is the cat?"},
},
)
print(result)
# {
# "llm": {
# "replies": [
# ChatMessage(
# _role=,
# _content=[TextContent(text="The cat is orange with some black.")],
# _name=None,
# _meta={
# "model": "gpt-4o-mini-2024-07-18",
# "index": 0,
# "finish_reason": "stop",
# "usage": {...},
# },
# )
# ]
# }
# }
```
## Additional References
🧑🍳 Cookbook: [Introduction to Multimodality](https://haystack.deepset.ai/cookbook/multimodal_intro)
---
// File: pipeline-components/converters/docxtodocument
# DOCXToDocument
Convert DOCX files to documents.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) or right at the beginning of an indexing pipeline |
| **Mandatory run variables** | `sources`: DOCX file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [Converters](/reference/converters-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/docx.py |
| **Package name** | `haystack-ai` |
## Overview
The `DOCXToDocument` component converts DOCX files into documents. It takes a list of file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects as input and outputs the converted result as a list of documents. By defining the table format (CSV or Markdown), you can use this component to extract tables in your DOCX files. Optionally, you can attach metadata to the documents through the `meta` input parameter.
## Usage
First, install the`python-docx` package to start using this converter:
```shell
pip install python-docx
```
### On its own
```python
from haystack.components.converters.docx import DOCXToDocument, DOCXTableFormat
converter = DOCXToDocument()
# or define the table format
converter = DOCXToDocument(table_format=DOCXTableFormat.CSV)
results = converter.run(
sources=["sample.docx"],
meta={"date_added": datetime.now().isoformat()},
)
documents = results["documents"]
print(documents[0].content)
# 'This is the text from the DOCX file.'
```
### In a pipeline
```python
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.converters import DOCXToDocument
from haystack.components.preprocessors import DocumentCleaner
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component("converter", DOCXToDocument())
pipeline.add_component("cleaner", DocumentCleaner())
pipeline.add_component(
"splitter",
DocumentSplitter(split_by="sentence", split_length=5),
)
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "cleaner")
pipeline.connect("cleaner", "splitter")
pipeline.connect("splitter", "writer")
pipeline.run({"converter": {"sources": file_names}})
```
---
// File: pipeline-components/converters/filetofilecontent
# FileToFileContent
`FileToFileContent` reads local files and converts them into `FileContent` objects. These are ready for multimodal AI pipelines that need to pass PDFs and other file types to an LLM.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before a `ChatPromptBuilder` in a query pipeline |
| **Mandatory run variables** | `sources`: A list of file paths or ByteStreams |
| **Output variables** | `file_contents`: A list of `FileContent` objects |
| **API reference** | [Converters](/reference/converters-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/file_to_file_content.py |
| **Package name** | `haystack-ai` |
## Overview
`FileToFileContent` processes a list of file sources and converts them into `FileContent` objects that can be embedded
into a `ChatMessage` and passed to a Language Model.
Each source can be:
- A file path (string or `Path`), or
- A `ByteStream` object.
Optionally, you can provide extra provider-specific information using the `extra` parameter. This can be a single dictionary (applied to all files) or a list matching the length of `sources`.
Support for passing files to LLMs varies by provider. Some providers do not support file inputs, some restrict support
to PDF files, and others accept a wider range of file types.
## Usage
### On its own
```python
from haystack.components.converters import FileToFileContent
converter = FileToFileContent()
sources = ["document.pdf", "recording.mp3"]
result = converter.run(sources=sources)
file_contents = result["file_contents"]
print(file_contents)
# [
# FileContent(
# base64_data='JVBERi0x...', mime_type='application/pdf',
# filename='document.pdf', extra={}
# ),
# FileContent(
# base64_data='SUQzBA...', mime_type='audio/mpeg',
# filename='recording.mp3', extra={}
# )
# ]
```
### In a pipeline
Use `FileToFileContent` together with a `LinkContentFetcher` and a `ChatPromptBuilder` to build a pipeline that fetches a remote file, converts it, and passes it to an LLM.
```python
from haystack.components.converters import FileToFileContent
from haystack.components.fetchers import LinkContentFetcher
from haystack.components.generators.chat.openai import OpenAIChatGenerator
from haystack.components.builders import ChatPromptBuilder
from haystack import Pipeline
template = """
{% message role="user"%}
{% for file in files %}
{{ file | templatize_part }}
{% endfor %}
What's the main takeaway of the following document? Just one sentence.
{% endmessage %}
"""
pipeline = Pipeline()
pipeline.add_component("fetcher", LinkContentFetcher())
pipeline.add_component("converter", FileToFileContent())
pipeline.add_component("prompt_builder", ChatPromptBuilder(template=template))
pipeline.add_component("llm", OpenAIChatGenerator(model="gpt-4.1-mini"))
pipeline.connect("fetcher", "converter")
pipeline.connect("converter", "prompt_builder")
pipeline.connect("prompt_builder", "llm")
results = pipeline.run({"fetcher": {"urls": ["https://arxiv.org/pdf/2309.08632"]}})
print(results["llm"]["replies"][0].text)
# The document is a satirical paper humorously claiming that pretraining a
# small language model exclusively on evaluation benchmark test sets can achieve
# perfect performance, highlighting issues of data contamination in model
# evaluation.
```
---
// File: pipeline-components/converters/htmltodocument
# HTMLToDocument
A component that converts HTML files to documents.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) , or right at the beginning of an indexing pipeline |
| **Mandatory run variables** | `sources`: A list of HTML file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [Converters](/reference/converters-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/html.py |
| **Package name** | `haystack-ai` |
## Overview
The `HTMLToDocument` component converts HTML files into documents. It can be used in an indexing pipeline to index the contents of an HTML file into a Document Store or even in a querying pipeline after the [`LinkContentFetcher`](../fetchers/linkcontentfetcher.mdx). The `HTMLToDocument` component takes a list of HTML file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects as input and converts the files to a list of documents. Optionally, you can attach metadata to the documents through the `meta` input parameter.
When you initialize the component, you can optionally set `extraction_kwargs`, a dictionary containing keyword arguments to customize the extraction process. These are passed to the underlying Trafilatura `extract` function. For the full list of available arguments, see the [Trafilatura documentation](https://trafilatura.readthedocs.io/en/latest/corefunctions.html#extract).
## Usage
### On its own
```python
from pathlib import Path
from haystack.components.converters import HTMLToDocument
converter = HTMLToDocument()
docs = converter.run(sources=[Path("saved_page.html")])
```
### In a pipeline
Here's an example of an indexing pipeline that writes the contents of an HTML file into an `InMemoryDocumentStore`:
```python
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.converters import HTMLToDocument
from haystack.components.preprocessors import DocumentCleaner
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component("converter", HTMLToDocument())
pipeline.add_component("cleaner", DocumentCleaner())
pipeline.add_component(
"splitter",
DocumentSplitter(split_by="sentence", split_length=5),
)
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "cleaner")
pipeline.connect("cleaner", "splitter")
pipeline.connect("splitter", "writer")
pipeline.run({"converter": {"sources": file_names}})
```
---
// File: pipeline-components/converters/imagefiletodocument
# ImageFileToDocument
Converts image file references into empty `Document` objects with associated metadata.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before a component that processes images, like `SentenceTransformersDocumentImageEmbedder` or `LLMDocumentContentExtractor` |
| **Mandatory run variables** | `sources`: A list of image file paths or ByteStreams |
| **Output variables** | `documents`: A list of empty Document objects with associated metadata |
| **API reference** | [Image Converters](/reference/image-converters-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/image/file_to_document.py |
| **Package name** | `haystack-ai` |
## Overview
`ImageFileToDocument` converts image file sources into empty `Document` objects with associated metadata.
This component is useful in pipelines where image file paths need to be wrapped in `Document` objects to be processed by downstream components such as `SentenceTransformersDocumentImageEmbedder` or `LLMDocumentContentExtractor`.
It _does not_ extract any content from the image files, but instead creates `Document` objects with `None` as their content and attaches metadata such as file path and any user-provided values.
Each source can be:
- A file path (string or `Path`), or
- A `ByteStream` object.
Optionally, you can provide metadata using the `meta` parameter. This can be a single dictionary (applied to all documents) or a list matching the length of `sources`.
## Usage
### On its own
This component is primarily meant to be used in pipelines.
```python
from haystack.components.converters.image import ImageFileToDocument
converter = ImageFileToDocument()
sources = ["image.jpg", "another_image.png"]
result = converter.run(sources=sources)
documents = result["documents"]
print(documents)
# [Document(id=..., content=None, meta={'file_path': 'image.jpg'}),
# Document(id=..., content=None, meta={'file_path': 'another_image.png'})]
```
### In a pipeline
In the following Pipeline, image documents are created using the `ImageFileToDocument` component, then they are enriched with image embeddings and saved in the Document Store.
The examples on this page use Sentence Transformers embedders from the `sentence-transformers-haystack` package. Install it to run the examples:
```shell
pip install sentence-transformers-haystack
```
```python
from haystack import Pipeline
from haystack.components.converters.image import ImageFileToDocument
from haystack_integrations.components.embedders.sentence_transformers import (
SentenceTransformersDocumentImageEmbedder,
)
from haystack.components.writers.document_writer import DocumentWriter
from haystack.document_stores.in_memory import InMemoryDocumentStore
# Create our document store
doc_store = InMemoryDocumentStore()
# Define pipeline with components
indexing_pipe = Pipeline()
indexing_pipe.add_component(
"image_converter",
ImageFileToDocument(store_full_path=True),
)
indexing_pipe.add_component(
"image_doc_embedder",
SentenceTransformersDocumentImageEmbedder(),
)
indexing_pipe.add_component("document_writer", DocumentWriter(doc_store))
indexing_pipe.connect("image_converter.documents", "image_doc_embedder.documents")
indexing_pipe.connect("image_doc_embedder.documents", "document_writer.documents")
indexing_result = indexing_pipe.run(
data={"image_converter": {"sources": ["apple.jpg", "kiwi.png"]}},
)
indexed_documents = doc_store.filter_documents()
print(f"Indexed {len(indexed_documents)} documents")
# Indexed 2 documents
```
## Additional References
🧑🍳 Cookbook: [Introduction to Multimodality](https://haystack.deepset.ai/cookbook/multimodal_intro)
---
// File: pipeline-components/converters/imagefiletoimagecontent
# ImageFileToImageContent
`ImageFileToImageContent` reads local image files and converts them into `ImageContent` objects. These are ready for multimodal AI pipelines, including tasks like image captioning, visual QA, or prompt-based generation.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before a `ChatPromptBuilder` in a query pipeline |
| **Mandatory run variables** | `sources`: A list of image file paths or ByteStreams |
| **Output variables** | `image_contents`: A list of ImageContent objects |
| **API reference** | [Image Converters](/reference/image-converters-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/image/file_to_image.py |
| **Package name** | `haystack-ai` |
## Overview
`ImageFileToImageContent` processes a list of image sources and converts them into `ImageContent` objects. These can be used in multimodal pipelines that require base64-encoded image input.
Each source can be:
- A file path (string or `Path`), or
- A `ByteStream` object.
Optionally, you can provide metadata using the `meta` parameter. This can be a single dictionary (applied to all images) or a list matching the length of `sources`.
Use the `size` parameter to resize images while preserving aspect ratio. This reduces memory usage and transmission size, which is helpful when working with remote models or limited-resource environments.
This component is often used in query pipelines just before a `ChatPromptBuilder`.
## Usage
### On its own
```python
from haystack.components.converters.image import ImageFileToImageContent
converter = ImageFileToImageContent(detail="high", size=(800, 600))
sources = ["cat.jpg", "scenery.png"]
result = converter.run(sources=sources)
image_contents = result["image_contents"]
print(image_contents)
# [
# ImageContent(
# base64_image="/9j/4A...", mime_type="image/jpeg", detail="high",
# meta={"file_path": "cat.jpg"}
# ),
# ImageContent(
# base64_image="iVBORw0KGgo...", mime_type="image/png", detail="high",
# meta={"file_path": "scenery.png"}
# )
# ]
```
### In a pipeline
Use `ImageFileToImageContent` to supply image data to a `ChatPromptBuilder` for multimodal QA or captioning with an LLM.
```python
from haystack import Pipeline
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.converters.image import ImageFileToImageContent
# Query pipeline
pipeline = Pipeline()
pipeline.add_component("image_converter", ImageFileToImageContent(detail="auto"))
pipeline.add_component(
"chat_prompt_builder",
ChatPromptBuilder(
required_variables=["question"],
template="""{% message role="system" %}
You are a helpful assistant that answers questions using the provided images.
{% endmessage %}
{% message role="user" %}
Question: {{ question }}
{% for img in image_contents %}
{{ img | templatize_part }}
{% endfor %}
{% endmessage %}
""",
),
)
pipeline.add_component("llm", OpenAIChatGenerator(model="gpt-4o-mini"))
pipeline.connect("image_converter", "chat_prompt_builder.image_contents")
pipeline.connect("chat_prompt_builder", "llm")
sources = ["apple.jpg", "haystack-logo.png"]
result = pipeline.run(
data={
"image_converter": {"sources": sources},
"chat_prompt_builder": {"question": "Describe the Haystack logo."},
},
)
print(result)
# {
# "llm": {
# "replies": [
# ChatMessage(
# _role=,
# _content=[TextContent(text="The Haystack logo features...")],
# ...
# )
# ]
# }
# }
```
## Additional References
🧑🍳 Cookbook: [Introduction to Multimodality](https://haystack.deepset.ai/cookbook/multimodal_intro)
---
// File: pipeline-components/converters/jsonconverter
# JSONConverter
Converts JSON files to text documents.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) , or right at the beginning of an indexing pipeline |
| **Mandatory init variables** | ONE OF, OR BOTH:
`jq_schema`: A jq filter string to extract content
`content_key`: A key string to extract document content |
| **Mandatory run variables** | `sources`: A list of file paths or [ByteStream](../../concepts/data-classes.mdx#bytestream) objects |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [Converters](/reference/converters-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/json.py |
| **Package name** | `haystack-ai` |
## Overview
`JSONConverter` converts one or more JSON files into a text document.
### Parameters Overview
To initialize `JSONConverter`, you must provide either `jq_schema`, or `content_key` parameter, or both.
`jq_schema` parameter filter extracts nested data from JSON files. Refer to the [jq documentation](https://jqlang.github.io/jq/) for filter syntax. If not set, the entire JSON file is used.
The `content_key` parameter lets you specify which key in the extracted data will be the document's content.
- If both `jq_schema` and `content_key` are set, the `content_key` is searched in the data extracted by `jq_schema`. Non-object data will be skipped.
- If only `jq_schema` is set, the extracted value must be scalar; objects or arrays will be skipped.
- If only `content_key` is set, the source must be a JSON object, or it will be skipped.
Check out the [API reference](/reference/converters-api#jsonconverter) for the full list of parameters.
## Usage
You need to install the `jq` package to use this Converter:
```shell
pip install jq
```
### Example
Here is an example of simple component usage:
```python
import json
from haystack.components.converters import JSONConverter
from haystack.dataclasses import ByteStream
source = ByteStream.from_string(
json.dumps({"text": "This is the content of my document"}),
)
converter = JSONConverter(content_key="text")
results = converter.run(sources=[source])
documents = results["documents"]
print(documents[0].content)
# 'This is the content of my document'
```
In the following more complex example, we provide a `jq_schema` string to filter the JSON source files and `extra_meta_fields` to extract from the filtered data:
```python
import json
from haystack.components.converters import JSONConverter
from haystack.dataclasses import ByteStream
data = {
"laureates": [
{
"firstname": "Enrico",
"surname": "Fermi",
"motivation": "for his demonstrations of the existence of new radioactive elements produced "
"by neutron irradiation, and for his related discovery of nuclear reactions brought about by"
" slow neutrons",
},
{
"firstname": "Rita",
"surname": "Levi-Montalcini",
"motivation": "for their discoveries of growth factors",
},
],
}
source = ByteStream.from_string(json.dumps(data))
converter = JSONConverter(
jq_schema=".laureates[]",
content_key="motivation",
extra_meta_fields={"firstname", "surname"},
)
results = converter.run(sources=[source])
documents = results["documents"]
print(documents[0].content)
# 'for his demonstrations of the existence of new radioactive elements produced by
# neutron irradiation, and for his related discovery of nuclear reactions brought
# about by slow neutrons'
print(documents[0].meta)
# {'firstname': 'Enrico', 'surname': 'Fermi'}
print(documents[1].content)
# 'for their discoveries of growth factors'
print(documents[1].meta)
# {'firstname': 'Rita', 'surname': 'Levi-Montalcini'}
```
---
// File: pipeline-components/converters/kreuzbergconverter
# KreuzbergConverter
`KreuzbergConverter` converts files to Haystack Documents using [Kreuzberg](https://docs.kreuzberg.dev/), a document intelligence framework with a Rust core that extracts text from 91+ file formats entirely locally with no external API calls.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx), or right at the beginning of an indexing pipeline |
| **Mandatory run variables** | `sources`: A list of file paths, directory paths, or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [Kreuzberg](/reference/integrations-kreuzberg) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/kreuzberg |
| **Package name** | `kreuzberg-haystack` |
## Overview
The `KreuzbergConverter` takes a list of file paths, directory paths, or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects and uses Kreuzberg to extract text and metadata. All processing is performed locally with no external API calls.
**Supported format categories:**
- **Documents**: PDF, DOCX, DOC, PPTX, PPT, XLSX, XLS, ODT, ODS, ODP, RTF, Pages, Keynote, Numbers, and more
- **Images (via OCR)**: PNG, JPEG, TIFF, GIF, BMP, WebP, JPEG 2000, SVG
- **Text/Markup**: Markdown, HTML, XML, LaTeX, Typst, JSON, YAML, reStructuredText, Jupyter notebooks
- **Email**: EML, MSG (with attachment extraction)
- **Archives**: ZIP, TAR, GZIP, 7Z (extracts and processes contents recursively)
- **eBooks & Academic**: EPUB, BibTeX, DocBook, JATS
The component returns one Haystack [`Document`](../../concepts/data-classes.mdx#document) per source by default. When per-page extraction or chunking is enabled, it returns one Document per page or chunk instead. Documents include rich metadata such as quality scores, detected languages, extracted keywords, table data, and PDF annotations.
By default, batch processing is enabled, leveraging Rust's rayon thread pool for parallel extraction. Set `batch=False` for sequential processing.
You can customize extraction behavior with Kreuzberg's `ExtractionConfig`, either passed directly or loaded from a TOML, YAML, or JSON configuration file via `config_path`. See the [Kreuzberg documentation](https://docs.kreuzberg.dev/) for the full configuration reference.
## Usage
Install the Kreuzberg integration:
```shell
pip install kreuzberg-haystack
```
### On its own
```python
from haystack_integrations.components.converters.kreuzberg import KreuzbergConverter
converter = KreuzbergConverter()
result = converter.run(sources=["report.pdf", "notes.docx"])
documents = result["documents"]
```
### In a pipeline
```python
from haystack import Pipeline
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack_integrations.components.converters.kreuzberg import KreuzbergConverter
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component("converter", KreuzbergConverter())
pipeline.add_component(
"splitter",
DocumentSplitter(split_by="sentence", split_length=5),
)
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "splitter")
pipeline.connect("splitter", "writer")
pipeline.run({"converter": {"sources": ["report.pdf", "presentation.pptx"]}})
```
## Additional Features
### Markdown Output with OCR
Use `ExtractionConfig` to customize the output format and OCR settings:
```python
from haystack_integrations.components.converters.kreuzberg import KreuzbergConverter
from kreuzberg import ExtractionConfig, OcrConfig
converter = KreuzbergConverter(
config=ExtractionConfig(
output_format="markdown",
ocr=OcrConfig(backend="tesseract", language="eng"),
),
)
result = converter.run(sources=["scanned_document.pdf"])
documents = result["documents"]
```
### Per-Page Extraction
Create one Document per page using `PageConfig`:
```python
from haystack_integrations.components.converters.kreuzberg import KreuzbergConverter
from kreuzberg import ExtractionConfig, PageConfig
converter = KreuzbergConverter(
config=ExtractionConfig(
page=PageConfig(extract_pages=True),
),
)
result = converter.run(sources=["multipage.pdf"])
# One Document per page, each with page_number in metadata
```
### Token Reduction
Reduce output size for LLM consumption with `TokenReductionConfig`. Token reduction uses TF-IDF-based extractive summarization to identify and preserve the most important terms and phrases, progressively removing less critical content such as extra whitespace, filler words, and redundant phrases. Five levels are available: `"off"` (no reduction), `"light"` (~15%), `"moderate"` (~30%), `"aggressive"` (~50%), and `"maximum"` (>50% reduction):
```python
from haystack_integrations.components.converters.kreuzberg import KreuzbergConverter
from kreuzberg import ExtractionConfig, TokenReductionConfig
converter = KreuzbergConverter(
config=ExtractionConfig(
token_reduction=TokenReductionConfig(mode="moderate"),
),
)
```
### Config from File
Load extraction settings from a TOML, YAML, or JSON file:
```python
from haystack_integrations.components.converters.kreuzberg import KreuzbergConverter
converter = KreuzbergConverter(config_path="extraction_config.toml")
```
For the full configuration reference and format support matrix, see the [Kreuzberg documentation](https://docs.kreuzberg.dev/).
---
// File: pipeline-components/converters/libreofficefileconverter
# LibreOfficeFileConverter
A component that converts office files between formats using LibreOffice's command line interface (`soffice`).
| | |
| --- | --- |
| **Most common position in a pipeline** | Before a document converter (e.g. [`DOCXToDocument`](./docxtodocument.mdx)) when the source files need to be converted to a format that the converter supports |
| **Mandatory run variables** | `sources`: File paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects; `output_file_type`: The target file format |
| **Output variables** | `output`: A list of [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects |
| **API reference** | [LibreOffice](/reference/integrations-libreoffice) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/libreoffice |
| **Package name** | `libreoffice-haystack` |
## Overview
`LibreOfficeFileConverter` converts office files from one format to another using LibreOffice's `soffice` command line utility. It supports a wide range of document, spreadsheet, and presentation formats and is useful when your pipeline receives files in a format that downstream converters don't support.
Unlike most converters, `LibreOfficeFileConverter` outputs [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects rather than Haystack Documents. This means it's typically chained with a document converter (such as [`DOCXToDocument`](./docxtodocument.mdx) or [`PyPDFToDocument`](./pypdftodocument.mdx)) to produce the final Documents.
**Requires LibreOffice to be installed** and available in `PATH` as `soffice`. See the [LibreOffice installation guide](https://www.libreoffice.org/get-help/install-howto/) for details.
### Supported conversions
| Category | Input formats | Possible output formats |
| --- | --- | --- |
| Documents | `doc`, `docx`, `odt`, `rtf`, `txt`, `html` | `pdf`, `docx`, `doc`, `odt`, `rtf`, `txt`, `html`, `epub` |
| Spreadsheets | `xlsx`, `xls`, `ods`, `csv` | `pdf`, `xlsx`, `xls`, `ods`, `csv`, `html` |
| Presentations | `pptx`, `ppt`, `odp` | `pdf`, `pptx`, `ppt`, `odp`, `html`, `png`, `jpg` |
This is a non-exhaustive list. See the [LibreOffice filter documentation](https://help.libreoffice.org/latest/en-GB/text/shared/guide/convertfilters.html) for all supported conversions.
## Usage
Install the LibreOffice integration:
```shell
pip install libreoffice-haystack
```
### On its own
```python
from pathlib import Path
from haystack_integrations.components.converters.libreoffice import (
LibreOfficeFileConverter,
)
converter = LibreOfficeFileConverter()
result = converter.run(sources=[Path("sample.doc")], output_file_type="docx")
bytestreams = result["output"]
```
You can also set `output_file_type` at initialization to avoid passing it on every `run()` call:
```python
converter = LibreOfficeFileConverter(output_file_type="pdf")
result = converter.run(sources=[Path("report.pptx")])
```
### In a pipeline
A common pattern is to chain `LibreOfficeFileConverter` with a document converter. The example below converts a legacy `.doc` file to `.docx` and then extracts it as a Haystack Document:
```python
from pathlib import Path
from haystack import Pipeline
from haystack.components.converters import DOCXToDocument
from haystack_integrations.components.converters.libreoffice import (
LibreOfficeFileConverter,
)
pipeline = Pipeline()
pipeline.add_component(
"libreoffice_converter",
LibreOfficeFileConverter(output_file_type="docx"),
)
pipeline.add_component("docx_converter", DOCXToDocument())
pipeline.connect("libreoffice_converter.output", "docx_converter.sources")
result = pipeline.run(
{"libreoffice_converter": {"sources": [Path("legacy_report.doc")]}},
)
documents = result["docx_converter"]["documents"]
```
---
// File: pipeline-components/converters/markdowntodocument
# MarkdownToDocument
A component that converts Markdown files to documents.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) , or right at the beginning of an indexing pipeline |
| **Mandatory run variables** | `sources`: Markdown file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [Converters](/reference/converters-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/markdown.py |
| **Package name** | `haystack-ai` |
## Overview
The `MarkdownToDocument` component converts Markdown files into documents. You can use it in an indexing pipeline to index the contents of a Markdown file into a Document Store. It takes a list of file paths or [ByteStream](../../concepts/data-classes.mdx#bytestream) objects as input and outputs the converted result as a list of documents. Optionally, you can attach metadata to the documents through the `meta` input parameter.
When you initialize the component, you can optionally turn off progress bars by setting `progress_bar` to `False`. If you want to convert the contents of tables into a single line, you can enable that through the `table_to_single_line` parameter.
If your Markdown files start with YAML frontmatter, set `extract_frontmatter=True` to move that data into `Document.meta` and remove it from the converted document content. Metadata passed through the `meta` input takes precedence over frontmatter keys.
## Usage
You need to install the `markdown-it-py` and `mdit_plain` packages to use the `MarkdownToDocument` component:
```shell
pip install markdown-it-py mdit_plain
```
### On its own
```python
from haystack.components.converters import MarkdownToDocument
converter = MarkdownToDocument()
docs = converter.run(sources=[Path("my_file.md")])
```
### With YAML frontmatter
Given `equity_note.md`:
```markdown
---
ticker: AAPL
source: earnings_call
date: 2026-06-12
---
# Thesis
Revenue guidance improved.
```
```python
from haystack.components.converters import MarkdownToDocument
converter = MarkdownToDocument(extract_frontmatter=True)
docs = converter.run(sources=["equity_note.md"])["documents"]
print(docs[0].meta["ticker"])
print(docs[0].content)
```
### In a pipeline
```python
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.converters import MarkdownToDocument
from haystack.components.preprocessors import DocumentCleaner
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component("converter", MarkdownToDocument())
pipeline.add_component("cleaner", DocumentCleaner())
pipeline.add_component(
"splitter",
DocumentSplitter(split_by="sentence", split_length=5),
)
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "cleaner")
pipeline.connect("cleaner", "splitter")
pipeline.connect("splitter", "writer")
pipeline.run({"converter": {"sources": file_names}})
```
## Additional References
:notebook: Tutorial: [Preprocessing Different File Types](https://haystack.deepset.ai/tutorials/30_file_type_preprocessing_index_pipeline)
---
// File: pipeline-components/converters/markitdownconverter
# MarkItDownConverter
A component that converts files to Documents using Microsoft's MarkItDown library.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) or right at the beginning of an indexing pipeline |
| **Mandatory run variables** | `sources`: File paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [MarkItDown](/reference/integrations-markitdown) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/markitdown |
| **Package name** | `markitdown-haystack` |
## Overview
`MarkItDownConverter` converts files into Haystack Documents using Microsoft's [MarkItDown](https://github.com/microsoft/markitdown) library. MarkItDown converts many file formats to Markdown, including PDF, Word (.docx), PowerPoint (.pptx), Excel (.xlsx), HTML, and more. All processing is performed locally without relying on external APIs.
The converter accepts file paths or [ByteStream](../../concepts/data-classes.mdx#bytestream) objects as input and outputs the converted result as a list of Documents. You can attach metadata to the Documents through the `meta` input parameter.
:::note
This component returns Markdown content. Avoid piping it through `DocumentCleaner()` with its default settings because `remove_extra_whitespaces=True` and `remove_empty_lines=True` can collapse line breaks and flatten headings, tables, lists, and image tags. Connect the converter directly to your next component, or disable those options if you need custom cleanup.
:::
## Usage
Install the MarkItDown integration:
```shell
pip install markitdown-haystack
```
### On its own
```python
from haystack_integrations.components.converters.markitdown import MarkItDownConverter
converter = MarkItDownConverter()
result = converter.run(sources=["document.pdf", "report.docx"])
documents = result["documents"]
```
### In a pipeline
```python
from haystack import Pipeline
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack_integrations.components.converters.markitdown import MarkItDownConverter
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component("converter", MarkItDownConverter())
pipeline.add_component(
"splitter",
DocumentSplitter(split_by="sentence", split_length=5),
)
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "splitter")
pipeline.connect("splitter", "writer")
pipeline.run({"converter": {"sources": ["document.pdf", "report.docx"]}})
```
---
// File: pipeline-components/converters/mistralocrdocumentconverter
# MistralOCRDocumentConverter
`MistralOCRDocumentConverter` extracts text from documents using Mistral's OCR API, with optional structured annotations for both individual image regions and full documents. It supports various input formats including local files, URLs, and Mistral file IDs.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx), or right at the beginning of an indexing pipeline |
| **Mandatory init variables** | `api_key`: The Mistral API key. Can be set with `MISTRAL_API_KEY` environment variable. |
| **Mandatory run variables** | `sources`: A list of document sources (file paths, ByteStreams, URLs, or Mistral chunks) |
| **Output variables** | `documents`: A list of documents
`raw_mistral_response`: A list of raw OCR responses from Mistral API |
| **API reference** | [Mistral](/reference/integrations-mistral) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/mistral |
| **Package name** | `mistral-haystack` |
## Overview
The `MistralOCRDocumentConverter` takes a list of document sources and uses Mistral's OCR API to extract text from images and PDFs. It supports multiple input formats:
- **Local files**: File paths (str or Path) or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects
- **Remote resources**: Document URLs, image URLs using Mistral's `DocumentURLChunk` and `ImageURLChunk`
- **Mistral storage**: File IDs using Mistral's `FileChunk` for files previously uploaded to Mistral
The component returns one Haystack [`Document`](../../concepts/data-classes.mdx#document) per source, with all pages concatenated using form feed characters (`\f`) as separators. This format ensures compatibility with Haystack's [`DocumentSplitter`](../preprocessors/documentsplitter.mdx) for accurate page-wise splitting and overlap handling. The content is returned in markdown format, with images represented as `` tags.
By default, the component uses the `MISTRAL_API_KEY` environment variable for authentication. You can also pass an `api_key` at initialization. Local files are automatically uploaded to Mistral's storage for processing and deleted afterward (configurable with `cleanup_uploaded_files`).
When you initialize the component, you can optionally specify which pages to process, set limits on image extraction, configure minimum image sizes, or include base64-encoded images in the response. The default model is `"mistral-ocr-2505"`. See the [Mistral models documentation](https://docs.mistral.ai/getting-started/models/models_overview/) for available models.
### Structured Annotations
A unique feature of `MistralOCRDocumentConverter` is its support for structured annotations using Pydantic schemas:
- **Bounding box annotations** (`bbox_annotation_schema`): Annotate individual image regions with structured data (for example, image type, description, summary). These annotations are inserted inline after the corresponding image tags in the markdown content.
- **Document annotations** (`document_annotation_schema`): Annotate the full document with structured data (for example, language, chapter titles, URLs). These annotations are unpacked into the document's metadata with a `source_` prefix (for example, `source_language`, `source_chapter_titles`).
When annotation schemas are provided, the OCR model first extracts text and structure, then a Vision LLM analyzes the content and generates structured annotations according to your defined Pydantic schemas. Note that document annotation is limited to a maximum of 8 pages. For more details, see the [Mistral documentation on annotations](https://docs.mistral.ai/capabilities/document_ai/annotations/).
:::note
This component returns Markdown content. Avoid piping it through `DocumentCleaner()` with its default settings because `remove_extra_whitespaces=True` and `remove_empty_lines=True` can collapse line breaks and flatten headings, tables, and image tags. For page-aware chunking, connect the converter directly to `DocumentSplitter`, or disable those options if you need custom cleanup.
:::
## Usage
You need to install the `mistral-haystack` integration to use `MistralOCRDocumentConverter`:
```shell
pip install mistral-haystack
```
### On its own
Basic usage with a local file:
```python
from pathlib import Path
from haystack.utils import Secret
from haystack_integrations.components.converters.mistral import (
MistralOCRDocumentConverter,
)
converter = MistralOCRDocumentConverter(
api_key=Secret.from_env_var("MISTRAL_API_KEY"),
model="mistral-ocr-2505",
)
result = converter.run(sources=[Path("my_document.pdf")])
documents = result["documents"]
```
Processing multiple sources with different types:
```python
from pathlib import Path
from haystack.utils import Secret
from haystack_integrations.components.converters.mistral import (
MistralOCRDocumentConverter,
)
from mistralai.models import DocumentURLChunk, ImageURLChunk
converter = MistralOCRDocumentConverter(
api_key=Secret.from_env_var("MISTRAL_API_KEY"),
model="mistral-ocr-2505",
)
sources = [
Path("local_document.pdf"),
DocumentURLChunk(document_url="https://example.com/document.pdf"),
ImageURLChunk(image_url="https://example.com/receipt.jpg"),
]
result = converter.run(sources=sources)
documents = result["documents"] # List of 3 Documents
raw_responses = result["raw_mistral_response"] # List of 3 raw responses
```
Using structured annotations:
```python
from pathlib import Path
from typing import List
from pydantic import BaseModel, Field
from haystack.utils import Secret
from haystack_integrations.components.converters.mistral import (
MistralOCRDocumentConverter,
)
from mistralai.models import DocumentURLChunk
# Define schema for image region annotations
class ImageAnnotation(BaseModel):
image_type: str = Field(..., description="The type of image content")
short_description: str = Field(
...,
description="Short natural-language description",
)
summary: str = Field(..., description="Detailed summary of the image content")
# Define schema for document-level annotations
class DocumentAnnotation(BaseModel):
language: str = Field(..., description="Primary language of the document")
chapter_titles: List[str] = Field(
...,
description="Detected chapter or section titles",
)
urls: List[str] = Field(..., description="URLs found in the text")
converter = MistralOCRDocumentConverter(
api_key=Secret.from_env_var("MISTRAL_API_KEY"),
model="mistral-ocr-2505",
)
sources = [DocumentURLChunk(document_url="https://example.com/report.pdf")]
result = converter.run(
sources=sources,
bbox_annotation_schema=ImageAnnotation,
document_annotation_schema=DocumentAnnotation,
)
documents = result["documents"]
# Document metadata will include:
# - source_language: extracted from DocumentAnnotation
# - source_chapter_titles: extracted from DocumentAnnotation
# - source_urls: extracted from DocumentAnnotation
# Document content will include inline image annotations
```
### In a pipeline
Here's an example of an indexing pipeline that processes PDFs with OCR and writes them to a Document Store:
```python
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
from haystack.utils import Secret
from haystack_integrations.components.converters.mistral import (
MistralOCRDocumentConverter,
)
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component(
"converter",
MistralOCRDocumentConverter(
api_key=Secret.from_env_var("MISTRAL_API_KEY"),
model="mistral-ocr-2505",
),
)
pipeline.add_component("splitter", DocumentSplitter(split_by="page", split_length=1))
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "splitter")
pipeline.connect("splitter", "writer")
file_paths = ["invoice.pdf", "receipt.jpg", "contract.pdf"]
pipeline.run({"converter": {"sources": file_paths}})
```
---
// File: pipeline-components/converters/msgtodocument
# MSGToDocument
Converts Microsoft Outlook .msg files to documents.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) , or right at the beginning of an indexing pipeline |
| **Mandatory run variables** | `sources`: A list of .msg file paths or [ByteStream](../../concepts/data-classes.mdx#bytestream) objects |
| **Output variables** | `documents`: A list of documents
`attachments`: A list of ByteStream objects representing file attachments |
| **API reference** | [Converters](/reference/converters-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/msg.py |
| **Package name** | `haystack-ai` |
## Overview
The `MSGToDocument` component converts Microsoft Outlook `.msg` files into documents. This component extracts the email metadata (such as sender, recipients, CC, BCC, subject) and body content. Additionally, any file attachments within the `.msg` file are extracted as `ByteStream` objects.
## Usage
First, install the `python-oxmsg` package to start using this converter:
```
pip install python-oxmsg
```
### On its own
```python
from haystack.components.converters.msg import MSGToDocument
from datetime import datetime
converter = MSGToDocument()
results = converter.run(
sources=["sample.msg"],
meta={"date_added": datetime.now().isoformat()},
)
documents = results["documents"]
attachments = results["attachments"]
print(documents[0].content)
```
### In a pipeline
The following setup enables efficient extraction, preprocessing, and indexing of `.msg` email files within a Haystack pipeline:
```python
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.routers import FileTypeRouter
from haystack.components.converters import MSGToDocument
from haystack.components.writers import DocumentWriter
router = FileTypeRouter(mime_types=["application/vnd.ms-outlook"])
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component("router", router)
pipeline.add_component("converter", MSGToDocument())
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("router.application/vnd.ms-outlook", "converter.sources")
pipeline.connect("converter.documents", "writer.documents")
file_names = ["email1.msg", "email2.msg"]
pipeline.run({"converter": {"sources": file_names}})
```
---
// File: pipeline-components/converters/multifileconverter
# MultiFileConverter
Converts CSV, DOCX, HTML, JSON, MD, PPTX, PDF, TXT, and XSLX files to documents.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before PreProcessors , or right at the beginning of an indexing pipeline |
| **Mandatory run variables** | `sources`: A list of file paths or ByteStream objects |
| **Output variables** | `documents`: A list of converted documents
`unclassified`: A list of file paths or byte streams whose MIME type isn't supported
`failed`: A list of file paths or byte streams that couldn't be processed, for example a path that doesn't exist |
| **API reference** | [Converters](/reference/converters-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/multi_file_converter.py |
| **Package name** | `haystack-ai` |
## Overview
`MultiFileConverter` converts input files of various file types into documents.
It is a SuperComponent that combines a [`FileTypeRouter`](../routers/filetyperouter.mdx), nine converters and a [`DocumentJoiner`](../joiners/documentjoiner.mdx) into a single component.
### Parameters
To initialize `MultiFileConverter`, there are no mandatory parameters. Optionally, you can provide `encoding` and `json_content_key` parameters.
The `json_content_key` parameter lets you specify for the JSON files which key in the extracted data will be the document's content. The parameter is passed on to the underlying [`JSONConverter`](jsonconverter.mdx) component.
The `encoding` parameter lets you specify the default encoding of the TXT, CSV, and MD files. If you don't provide any value, the component uses `utf-8` by default. Note that if the encoding is specified in the metadata of an input ByteStream, it will override this parameter's setting. The parameter is passed on to the underlying [`TextFileToDocument`](textfiletodocument.mdx) and [`CSVToDocument`](csvtodocument.mdx) components.
## Usage
Install dependencies for all supported file types to use the `MultiFileConverter`:
```shell
pip install pypdf trafilatura python-pptx python-docx jq openpyxl tabulate pandas
```
### On its own
```python
from haystack.components.converters import MultiFileConverter
converter = MultiFileConverter()
converter.run(sources=["test.txt", "test.pdf"], meta={})
```
### In a pipeline
You can also use `MultiFileConverter` in your indexing pipeline.
```python
from haystack import Pipeline
from haystack.components.converters import MultiFileConverter
from haystack.components.preprocessors import DocumentPreprocessor
from haystack.components.writers import DocumentWriter
from haystack.document_stores.in_memory import InMemoryDocumentStore
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component("converter", MultiFileConverter())
pipeline.add_component("preprocessor", DocumentPreprocessor())
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "preprocessor")
pipeline.connect("preprocessor", "writer")
result = pipeline.run(data={"sources": ["test.txt", "test.pdf"]})
print(result)
# {'writer': {'documents_written': 3}}
```
---
// File: pipeline-components/converters/openapiservicetofunctions
# OpenAPIServiceToFunctions
`OpenAPIServiceToFunctions` is a component that transforms OpenAPI service specifications into a format compatible with LLM tool calling.
:::tip[Consider using MCP instead]
These OpenAPI components are a legacy way to connect Haystack to external APIs. For most use cases, we recommend the [`MCPTool`](../../tools/mcptool.mdx) instead: it is the modern, standardized way to give your pipelines and agents access to external tools and services.
:::
| | |
| --- | --- |
| **Most common position in a pipeline** | Flexible |
| **Mandatory run variables** | `sources`: A list of OpenAPI specification sources, which can be file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects |
| **Output variables** | `functions`: A list of JSON function definitions objects. For each path definition in OpenAPI specification, a corresponding function definition is generated.
`openapi_specs`: A list of JSON/YAML objects with references resolved. Such OpenAPI spec (with references resolved) can, in turn, be used as input to OpenAPIServiceConnector. |
| **API reference** | [OpenAPI](/reference/integrations-openapi) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/openapi |
| **Package name** | `openapi-haystack` |
## Overview
`OpenAPIServiceToFunctions` transforms OpenAPI service specifications into a function calling format suitable for LLM tool calling. It takes an OpenAPI specification, processes it to extract function definitions, and formats these definitions to be compatible with LLM tool calling.
`OpenAPIServiceToFunctions` is valuable when used together with [`OpenAPIServiceConnector`](../connectors/openapiserviceconnector.mdx) component. It converts OpenAPI specifications into function definitions, allowing `OpenAPIServiceConnector` to handle input parameters for the OpenAPI specification and facilitate their use in REST API calls through `OpenAPIServiceConnector`.
To use `OpenAPIServiceToFunctions`, you need to install the `openapi-haystack` package with:
```shell
pip install openapi-haystack
```
`OpenAPIServiceToFunctions` component doesn’t have any init parameters.
## Usage
### On its own
This component is primarily meant to be used in pipelines. Using this component alone is useful when you want to convert OpenAPI specification into function definitions and then perhaps save them in a file and subsequently use them for tool calling.
### In a pipeline
In a pipeline context, `OpenAPIServiceToFunctions` is most valuable when used alongside `OpenAPIServiceConnector`. For instance, let’s consider integrating [serper.dev](http://serper.dev/) search engine bridge into a pipeline. `OpenAPIServiceToFunctions` retrieves the OpenAPI specification of Serper from https://bit.ly/serper_dev_spec, converts this specification into function definitions that an LLM with tool calling capabilities can understand, and then seamlessly passes these definitions as `generation_kwargs` to the Chat Generator component.
:::info
To run the following code snippet, note that you have to have your own Serper and OpenAI API keys.
:::
```python
import json
import requests
from typing import Any
from haystack import Pipeline
from haystack.components.converters import OutputAdapter
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.dataclasses.byte_stream import ByteStream
from haystack_integrations.components.connectors.openapi import OpenAPIServiceConnector
from haystack_integrations.components.converters.openapi import (
OpenAPIServiceToFunctions,
)
def prepare_fc_params(openai_functions_schema: dict[str, Any]) -> dict[str, Any]:
return {
"tools": [{"type": "function", "function": openai_functions_schema}],
"tool_choice": {
"type": "function",
"function": {"name": openai_functions_schema["name"]},
},
}
serperdev_spec = requests.get("https://bit.ly/serper_dev_spec").json()
system_prompt = requests.get("https://bit.ly/serper_dev_system").text
user_prompt = "Why was Sam Altman ousted from OpenAI?"
pipe = Pipeline()
pipe.add_component("spec_to_functions", OpenAPIServiceToFunctions())
pipe.add_component(
"prepare_fc_adapter",
OutputAdapter(
"{{functions[0] | prepare_fc}}",
dict[str, Any],
{"prepare_fc": prepare_fc_params},
),
)
pipe.add_component("functions_llm", OpenAIChatGenerator())
pipe.add_component("openapi_connector", OpenAPIServiceConnector())
pipe.add_component(
"message_adapter",
OutputAdapter(
"{{system_message + service_response}}",
list[ChatMessage],
unsafe=True,
),
)
pipe.add_component("llm", OpenAIChatGenerator())
pipe.connect("spec_to_functions.functions", "prepare_fc_adapter.functions")
pipe.connect(
"spec_to_functions.openapi_specs",
"openapi_connector.service_openapi_spec",
)
pipe.connect("prepare_fc_adapter", "functions_llm.generation_kwargs")
pipe.connect("functions_llm.replies", "openapi_connector.messages")
pipe.connect("openapi_connector.service_response", "message_adapter.service_response")
pipe.connect("message_adapter", "llm.messages")
result = pipe.run(
data={
"functions_llm": {
"messages": [
ChatMessage.from_system("Only do tool/function calling"),
ChatMessage.from_user(user_prompt),
],
},
"openapi_connector": {
"service_credentials": serper_dev_key,
},
"spec_to_functions": {
"sources": [ByteStream.from_string(json.dumps(serperdev_spec))],
},
"message_adapter": {
"system_message": [ChatMessage.from_system(system_prompt)],
},
},
)
print(result["llm"]["replies"][0].text)
# Sam Altman was ousted from OpenAI on November 17, 2023, following
# a "deliberative review process" by the board of directors. The board concluded
# that he was not "consistently candid in his communications". However, he
# returned as CEO just days after his ouster.
```
---
// File: pipeline-components/converters/outputadapter
# OutputAdapter
This component helps the output of one component fit smoothly into the input of another. It uses Jinja expressions to define how this adaptation occurs.
| | |
| --- | --- |
| **Most common position in a pipeline** | Flexible |
| **Mandatory init variables** | `template`: A Jinja template string that defines how to adapt the data
`output_type`: Type alias that this instance will return |
| **Mandatory run variables** | `**kwargs`: Input variables to be used in Jinja expression. See [Variables](#variables) section for more details. |
| **Output variables** | The output is specified under the `output` key dictionary |
| **API reference** | [Converters](/reference/converters-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/output_adapter.py |
| **Package name** | `haystack-ai` |
## Overview
To use `OutputAdapter`, you need to specify the adaptation rule that includes:
- `template`: A Jinja template string that defines how to adapt the input data.
- `output_type`: The type of the output data (such as `str`, `List[int]`..). This doesn't change the actual output type and is only needed to validate connection with other components.
- `custom_filters`: An optional dictionary of custom Jinja filters to be used in the template.
### Variables
The `OutputAdapter` requires all template variables to be present before running and raises an error if any template variable is missing at pipeline connect time.
```python
from haystack.components.converters import OutputAdapter
adapter = OutputAdapter(template="Hello {{name}}!", output_type=str)
```
### Unsafe behavior
The `OutputAdapter` internally renders the `template` using Jinja, and by default, this is safe behavior. However, it limits the output types to strings, bytes, numbers, tuples, lists, dicts, sets, booleans, `None`, and `Ellipsis` (`...`), as well as any combination of these structures.
If you want to use other types such as `ChatMessage`, `Document`, or `Answer`, you must enable unsafe template rendering by setting the `unsafe` init argument to `True`.
Be cautious, as enabling this can be unsafe and may lead to remote code execution if the `template` is a string customizable by the end user.
## Usage
### On its own
This component is primarily meant to be used in pipelines.
In this example, `OutputAdapter` simply outputs the content field of the first document in the arrays of documents:
```python
from haystack import Document
from haystack.components.converters import OutputAdapter
adapter = OutputAdapter(template="{{ documents[0].content }}", output_type=str)
input_data = {"documents": [Document(content="Test content")]}
expected_output = {"output": "Test content"}
assert adapter.run(**input_data) == expected_output
```
### In a pipeline
The example below demonstrates a straightforward pipeline that uses the `OutputAdapter` to capitalize the first document in the list. If needed, you can also utilize the predefined Jinja [filters](https://jinja.palletsprojects.com/en/3.1.x/templates/#builtin-filters).
```python
from haystack import Pipeline, component, Document
from haystack.components.converters import OutputAdapter
@component
class DocumentProducer:
@component.output_types(documents=dict)
def run(self):
return {"documents": [Document(content="haystack")]}
pipe = Pipeline()
pipe.add_component(
name="output_adapter",
instance=OutputAdapter(
template="{{ documents[0].content | capitalize}}",
output_type=str,
),
)
pipe.add_component(name="document_producer", instance=DocumentProducer())
pipe.connect("document_producer", "output_adapter")
result = pipe.run(data={})
assert result["output_adapter"]["output"] == "Haystack"
```
You can also define your own custom filters, which can then be added to an `OutputAdapter` instance through its init method and used in templates. Here’s an example of this approach:
```python
from haystack import Pipeline, component, Document
from haystack.components.converters import OutputAdapter
def reverse_string(s):
return s[::-1]
@component
class DocumentProducer:
@component.output_types(documents=dict)
def run(self):
return {"documents": [Document(content="haystack")]}
pipe = Pipeline()
pipe.add_component(
name="output_adapter",
instance=OutputAdapter(
template="{{ documents[0].content | reverse_string}}",
output_type=str,
custom_filters={"reverse_string": reverse_string},
),
)
pipe.add_component(name="document_producer", instance=DocumentProducer())
pipe.connect("document_producer", "output_adapter")
result = pipe.run(data={})
assert result["output_adapter"]["output"] == "kcatsyah"
```
---
// File: pipeline-components/converters/paddleocrvldocumentconverter
# PaddleOCRVLDocumentConverter
`PaddleOCRVLDocumentConverter` extracts text from documents using PaddleOCR's large model document parsing API. PaddleOCR-VL is used behind the scenes. For more information, please refer to the [PaddleOCR-VL documentation](https://www.paddleocr.ai/latest/en/version3.x/algorithm/PaddleOCR-VL/PaddleOCR-VL.html).
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx), or right at the beginning of an indexing pipeline |
| **Mandatory init variables** | `api_url`: The URL of the PaddleOCR-VL API.
`access_token`: The AI Studio access token. Can be set with `AISTUDIO_ACCESS_TOKEN` environment variable. |
| **Mandatory run variables** | `sources`: A list of image or PDF file paths or ByteStream objects. |
| **Output variables** | `documents`: A list of documents.
`raw_paddleocr_responses`: A list of raw OCR responses from PaddleOCR API. |
| **API reference** | [PaddleOCR](/reference/integrations-paddleocr) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/paddleocr |
| **Package name** | `paddleocr-haystack` |
## Overview
The `PaddleOCRVLDocumentConverter` takes a list of document sources and uses PaddleOCR's large model document parsing API to extract text from images and PDFs. It supports both images and PDF files.
The component returns one Haystack [`Document`](../../concepts/data-classes.mdx#document) per source, with all pages concatenated using form feed characters (`\f`) as separators. This format ensures compatibility with Haystack's [`DocumentSplitter`](../preprocessors/documentsplitter.mdx) for accurate page-wise splitting and overlap handling. The content is returned in markdown format, with images represented as `` tags.
The component takes `api_url` as a required parameter. To obtain the API URL, visit the [PaddleOCR official website](https://aistudio.baidu.com/paddleocr), click the **API** button, choose the example code for PaddleOCR-VL, and copy the `API_URL`.
By default, the component uses the `AISTUDIO_ACCESS_TOKEN` environment variable for authentication. You can also pass an `access_token` at initialization. The AI Studio access token can be obtained from [this page](https://aistudio.baidu.com/account/accessToken).
`raw_paddleocr_responses` can be useful while tuning layout thresholds, prompt settings, or Markdown post-processing options because it gives you access to the original API output alongside the converted Haystack documents.
:::note
This component returns Markdown content. Avoid piping it through `DocumentCleaner()` with its default settings because `remove_extra_whitespaces=True` and `remove_empty_lines=True` can collapse line breaks and flatten headings, tables, and image tags. For page-aware chunking, connect the converter directly to `DocumentSplitter`, or disable those options if you need custom cleanup.
:::
## When to use it
`PaddleOCRVLDocumentConverter` is a strong fit when you need more than plain OCR text:
- **Scanned PDFs and camera-captured documents** where page orientation and warped text can reduce extraction quality.
- **Layout-sensitive documents** such as invoices, reports, forms, and multi-column PDFs where preserving structure matters for downstream chunking and retrieval.
- **Tables, formulas, charts, or seals** where you want more targeted extraction behavior than plain text OCR.
- **RAG ingestion pipelines** where Markdown output is useful because headings, lists, tables, and page breaks can be preserved for later splitting.
## Useful configuration areas
The full parameter list is available in the [API reference](/reference/integrations-paddleocr). In practice, the most useful options tend to fall into these groups:
- **Input handling and image cleanup**: `file_type`, `use_doc_orientation_classify`, and `use_doc_unwarping` help when you mix PDFs and images or work with skewed scans and mobile photos.
- **Layout-aware extraction**: `use_layout_detection`, `layout_threshold`, `layout_nms`, `layout_unclip_ratio`, `layout_merge_bboxes_mode`, `layout_shape_mode`, and `merge_layout_blocks` help you tune how regions are detected and merged before Markdown is generated.
- **Content focus**: `prompt_label`, `use_ocr_for_image_block`, `use_chart_recognition`, and `use_seal_recognition` let you bias extraction toward a particular type of content, such as plain OCR, formulas, tables, charts, or seals.
- **Markdown output shaping**: `format_block_content`, `markdown_ignore_labels`, `prettify_markdown`, `show_formula_number`, `restructure_pages`, `merge_tables`, and `relevel_titles` help you control how much cleanup and restructuring happens before the result becomes a Haystack document.
- **VLM generation controls**: `repetition_penalty`, `temperature`, `top_p`, `min_pixels`, `max_pixels`, `max_new_tokens`, `vlm_extra_args`, and `additional_params` are useful when you need to trade off output quality, determinism, and cost.
- **Debugging and inspection**: `visualize=True` and the returned `raw_paddleocr_responses` are helpful when you are tuning extraction quality for a new document type.
## Typical scenarios
These settings are especially useful in a few common workflows:
- **Scanned contracts or receipts from phones**: start with `use_doc_orientation_classify=True` and `use_doc_unwarping=True`.
- **Table-heavy financial or operations PDFs**: consider `use_layout_detection=True`, `merge_tables=True`, and `restructure_pages=True`.
- **Formula-heavy documents**: use `prompt_label="formula"` together with `show_formula_number=True` if formula numbering matters in the final Markdown.
- **Mixed business documents with figures or seals**: enable `use_chart_recognition=True`, `use_seal_recognition=True`, or `use_ocr_for_image_block=True` depending on the content you want to preserve.
## Usage
You need to install the `paddleocr-haystack` integration to use `PaddleOCRVLDocumentConverter`:
```shell
pip install paddleocr-haystack
```
### On its own
Basic usage with a local file:
```python
from pathlib import Path
from haystack.utils import Secret
from haystack_integrations.components.converters.paddleocr import (
PaddleOCRVLDocumentConverter,
)
converter = PaddleOCRVLDocumentConverter(
api_url="",
access_token=Secret.from_env_var("AISTUDIO_ACCESS_TOKEN"),
)
result = converter.run(sources=[Path("my_document.pdf")])
documents = result["documents"]
```
Advanced configuration for structure-heavy PDFs:
```python
from pathlib import Path
from haystack.utils import Secret
from haystack_integrations.components.converters.paddleocr import (
PaddleOCRVLDocumentConverter,
)
converter = PaddleOCRVLDocumentConverter(
api_url="",
access_token=Secret.from_env_var("AISTUDIO_ACCESS_TOKEN"),
use_doc_orientation_classify=True,
use_doc_unwarping=True,
use_layout_detection=True,
use_ocr_for_image_block=True,
merge_tables=True,
restructure_pages=True,
prettify_markdown=True,
)
result = converter.run(sources=[Path("quarterly_report.pdf")])
documents = result["documents"]
raw_responses = result["raw_paddleocr_responses"]
```
### In a pipeline
Here's an example of an indexing pipeline that processes PDFs with OCR and writes them to a Document Store:
```python
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
from haystack.utils import Secret
from haystack_integrations.components.converters.paddleocr import (
PaddleOCRVLDocumentConverter,
)
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component(
"converter",
PaddleOCRVLDocumentConverter(
api_url="",
access_token=Secret.from_env_var("AISTUDIO_ACCESS_TOKEN"),
),
)
pipeline.add_component("splitter", DocumentSplitter(split_by="page", split_length=1))
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "splitter")
pipeline.connect("splitter", "writer")
file_paths = ["invoice.pdf", "receipt.jpg", "contract.pdf"]
pipeline.run({"converter": {"sources": file_paths}})
```
---
// File: pipeline-components/converters/pdfminertodocument
# PDFMinerToDocument
A component that converts complex PDF files to documents using pdfminer arguments.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) or right at the beginning of an indexing pipeline |
| **Mandatory run variables** | `sources`: PDF file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [Converters](/reference/converters-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/pdfminer.py |
| **Package name** | `haystack-ai` |
## Overview
The `PDFMinerToDocument` component converts PDF files into documents using [PDFMiner](https://pdfminersix.readthedocs.io/en/latest/) extraction tool arguments.
You can use it in an indexing pipeline to index the contents of a PDF file in a Document Store. It takes a list of file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream)objects as input and outputs the converted result as a list of documents. Optionally, you can attach metadata to the documents through the `meta` input parameter.
When initializing the component, you can adjust several parameters to fit your PDF. See the full parameter list and descriptions in our [API reference](/reference/converters-api#pdfminertodocument).
## Usage
First, install `pdfminer` package to start using this converter:
```shell
pip install pdfminer.six
```
### On its own
```python
from haystack.components.converters import PDFMinerToDocument
converter = PDFMinerToDocument()
results = converter.run(
sources=["sample.pdf"],
meta={"date_added": datetime.now().isoformat()},
)
documents = results["documents"]
print(documents[0].content)
# 'This is a text from the PDF file.'
```
### In a pipeline
```python
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.converters import PDFMinerToDocument
from haystack.components.preprocessors import DocumentCleaner
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component("converter", PDFMinerToDocument())
pipeline.add_component("cleaner", DocumentCleaner())
pipeline.add_component(
"splitter",
DocumentSplitter(split_by="sentence", split_length=5),
)
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "cleaner")
pipeline.connect("cleaner", "splitter")
pipeline.connect("splitter", "writer")
pipeline.run({"converter": {"sources": file_names}})
```
---
// File: pipeline-components/converters/pdftoimagecontent
# PDFToImageContent
`PDFToImageContent` reads local PDF files and converts them into `ImageContent` objects. These are ready for multimodal AI pipelines, including tasks like image captioning, visual QA, or prompt-based generation.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before a `ChatPromptBuilder` in a query pipeline |
| **Mandatory run variables** | `sources`: A list of PDF file paths or ByteStreams |
| **Output variables** | `image_contents`: A list of ImageContent objects |
| **API reference** | [Image Converters](/reference/image-converters-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/image/pdf_to_image.py |
| **Package name** | `haystack-ai` |
## Overview
`PDFToImageContent` processes a list of PDF sources and converts them into `ImageContent` objects, one for each page of the PDF. These can be used in multimodal pipelines that require base64-encoded image input.
Each source can be:
- A file path (string or `Path`), or
- A `ByteStream` object.
Optionally, you can provide metadata using the `meta` parameter. This can be a single dictionary (applied to all images) or a list matching the length of `sources`.
Use the `size` parameter to resize images while preserving aspect ratio. This reduces memory usage and transmission size, which is helpful when working with remote models or limited-resource environments.
This component is often used in query pipelines just before a `ChatPromptBuilder`.
## Usage
### On its own
```python
from haystack.components.converters.image import PDFToImageContent
converter = PDFToImageContent()
sources = ["file.pdf", "another_file.pdf"]
image_contents = converter.run(sources=sources)["image_contents"]
print(image_contents)
# [ImageContent(base64_image='...',
# mime_type='image/jpeg',
# detail=None,
# meta={'file_path': 'file.pdf', 'page_number': 1}),
# ...]
```
### In a pipeline
Use `PDFToImageContent` to supply page images to a `ChatPromptBuilder` for multimodal QA or captioning with an LLM.
```python
from haystack import Pipeline
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.converters.image import PDFToImageContent
# Query pipeline
pipeline = Pipeline()
pipeline.add_component("image_converter", PDFToImageContent(detail="auto"))
pipeline.add_component(
"chat_prompt_builder",
ChatPromptBuilder(
required_variables=["question"],
template="""{% message role="system" %}
You are a helpful assistant that answers questions using the provided images.
{% endmessage %}
{% message role="user" %}
Question: {{ question }}
{% for img in image_contents %}
{{ img | templatize_part }}
{% endfor %}
{% endmessage %}
""",
),
)
pipeline.add_component("llm", OpenAIChatGenerator(model="gpt-4o-mini"))
pipeline.connect("image_converter", "chat_prompt_builder.image_contents")
pipeline.connect("chat_prompt_builder", "llm")
sources = ["flan_paper.pdf"]
result = pipeline.run(
data={
"image_converter": {"sources": ["flan_paper.pdf"], "page_range": "9"},
"chat_prompt_builder": {"question": "What is the main takeaway of Figure 6?"},
},
)
print(result["llm"]["replies"][0].text)
# ('The main takeaway of Figure 6 is that Flan-PaLM demonstrates improved '
# 'performance in zero-shot reasoning tasks when utilizing chain-of-thought '
# '(CoT) reasoning, as indicated by higher accuracy across different model '
# 'sizes compared to PaLM without finetuning. This highlights the importance of '
# 'instruction finetuning combined with CoT for enhancing reasoning '
# 'capabilities in models.')
```
## Additional References
🧑🍳 Cookbook: [Introduction to Multimodality](https://haystack.deepset.ai/cookbook/multimodal_intro)
---
// File: pipeline-components/converters/pptxtodocument
# PPTXToDocument
Convert PPTX files to documents.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) or right at the beginning of an indexing pipeline |
| **Mandatory run variables** | `sources`: PPTX file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [Converters](/reference/converters-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/pptx.py |
| **Package name** | `haystack-ai` |
## Overview
The `PPTXToDocument` component converts PPTX files into documents. It takes a list of file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects as input and outputs the converted result as a list of documents. Optionally, you can attach metadata to the documents through the `meta` input parameter.
## Usage
First, install the`python-pptx` package to start using this converter:
```shell
pip install python-pptx
```
### On its own
```python
from haystack.components.converters import PPTXToDocument
converter = PPTXToDocument()
results = converter.run(
sources=["sample.pptx"],
meta={"date_added": datetime.now().isoformat()},
)
documents = results["documents"]
print(documents[0].content)
# 'This is the text from the PPTX file.'
```
### In a pipeline
```python
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.converters import PPTXToDocument
from haystack.components.preprocessors import DocumentCleaner
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component("converter", PPTXToDocument())
pipeline.add_component("cleaner", DocumentCleaner())
pipeline.add_component(
"splitter",
DocumentSplitter(split_by="sentence", split_length=5),
)
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "cleaner")
pipeline.connect("cleaner", "splitter")
pipeline.connect("splitter", "writer")
pipeline.run({"converter": {"sources": file_names}})
```
---
// File: pipeline-components/converters/pypdftodocument
# PyPDFToDocument
A component that converts PDF files to Documents.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) , or right at the beginning of an indexing pipeline |
| **Mandatory run variables** | `sources`: PDF file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [Converters](/reference/converters-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/pypdf.py |
| **Package name** | `haystack-ai` |
## Overview
The `PyPDFToDocument` component converts PDF files into documents. You can use it in an indexing pipeline to index the contents of a PDF file into a Document Store. It takes a list of file paths or [ByteStream](../../concepts/data-classes.mdx#bytestream) objects as input and outputs the converted result as a list of documents. Optionally, you can attach metadata to the documents through the `meta` input parameter.
## Usage
You need to install `pypdf` package to use the `PyPDFToDocument` converter:
```shell
pip install pypdf
```
### On its own
```python
from pathlib import Path
from haystack.components.converters import PyPDFToDocument
converter = PyPDFToDocument()
docs = converter.run(sources=[Path("my_file.pdf")])
```
### In a pipeline
```python
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.converters import PyPDFToDocument
from haystack.components.preprocessors import DocumentCleaner
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component("converter", PyPDFToDocument())
pipeline.add_component("cleaner", DocumentCleaner())
pipeline.add_component(
"splitter",
DocumentSplitter(split_by="sentence", split_length=5),
)
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "cleaner")
pipeline.connect("cleaner", "splitter")
pipeline.connect("splitter", "writer")
pipeline.run({"converter": {"sources": file_names}})
```
## Additional References
🧑🍳 Cookbook: [PDF-Based Question Answering with Amazon Bedrock and Haystack](https://haystack.deepset.ai/cookbook/amazon_bedrock_for_documentation_qa)
📓 Tutorial: [Preprocessing Different File Types](https://haystack.deepset.ai/tutorials/30_file_type_preprocessing_index_pipeline)
---
// File: pipeline-components/converters/textfiletodocument
# TextFileToDocument
Converts text files to documents.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) or right at the beginning of an indexing pipeline |
| **Mandatory run variables** | `sources`: A list of paths to text files you want to convert |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [Converters](/reference/converters-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/txt.py |
| **Package name** | `haystack-ai` |
## Overview
The `TextFileToDocument` component converts text files into documents. You can use it in an indexing pipeline to index the contents of text files into a Document Store. It takes a list of file paths or [ByteStream](../../concepts/data-classes.mdx#bytestream) objects as input and outputs the converted result as a list of documents. Optionally, you can attach metadata to the documents through the `meta` input parameter.
When you initialize the component, you can optionally set the default encoding of the text files through the `encoding` parameter. If you don't provide any value, the component uses `"utf-8"` by default. Note that if the encoding is specified in the metadata of an input ByteStream, it will override this parameter's setting.
## Usage
### On its own
```python
from pathlib import Path
from haystack.components.converters import TextFileToDocument
converter = TextFileToDocument()
docs = converter.run(sources=[Path("my_file.txt")])
```
### In a pipeline
```python
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.converters import TextFileToDocument
from haystack.components.preprocessors import DocumentCleaner
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component("converter", TextFileToDocument())
pipeline.add_component("cleaner", DocumentCleaner())
pipeline.add_component(
"splitter",
DocumentSplitter(split_by="sentence", split_length=5),
)
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "cleaner")
pipeline.connect("cleaner", "splitter")
pipeline.connect("splitter", "writer")
pipeline.run({"converter": {"sources": file_names}})
```
## Additional References
:notebook: Tutorial: [Preprocessing Different File Types](https://haystack.deepset.ai/tutorials/30_file_type_preprocessing_index_pipeline)
---
// File: pipeline-components/converters/tikadocumentconverter
# TikaDocumentConverter
An integration for converting files of different types (PDF, DOCX, HTML, and more) to documents.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) , or right at the beginning of an indexing pipeline |
| **Mandatory run variables** | `sources`: File paths |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [Tika](/reference/integrations-tika) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/tika |
| **Package name** | `tika-haystack` |
## Overview
The `TikaDocumentConverter` component converts files of different types (pdf, docx, html, and others) into documents. You can use it in an indexing pipeline to index the contents of files into a Document Store. It takes a list of file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects as input and outputs the converted result as a list of documents. Optionally, you can attach metadata to the documents through the `meta` input parameter.
This integration uses [Apache Tika](https://tika.apache.org/) to parse the files and requires a running Tika server.
The easiest way to run Tika is by using Docker: `docker run -d -p 127.0.0.1:9998:9998 apache/tika:latest`.
For more options on running Tika on Docker, see the [Tika documentation](https://github.com/apache/tika-docker/blob/main/README.md#usage).
When you initialize the `TikaDocumentConverter` component, you can specify a custom URL of the Tika server you are using through the parameter `tika_url`. The default URL is `"http://localhost:9998/tika"`.
## Usage
Install the `tika-haystack` package to use the `TikaDocumentConverter` component:
```shell
pip install tika-haystack
```
### On its own
```python
from haystack_integrations.components.converters.tika import TikaDocumentConverter
from pathlib import Path
converter = TikaDocumentConverter()
converter.run(sources=[Path("my_file.pdf")])
```
### In a pipeline
```python
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack_integrations.components.converters.tika import TikaDocumentConverter
from haystack.components.preprocessors import DocumentCleaner
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component("converter", TikaDocumentConverter())
pipeline.add_component("cleaner", DocumentCleaner())
pipeline.add_component(
"splitter",
DocumentSplitter(split_by="sentence", split_length=5),
)
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "cleaner")
pipeline.connect("cleaner", "splitter")
pipeline.connect("splitter", "writer")
pipeline.run({"converter": {"sources": file_paths}})
```
---
// File: pipeline-components/converters/twelvelabsvideoconverter
# TwelveLabsVideoConverter
`TwelveLabsVideoConverter` converts videos to Haystack Documents using the TwelveLabs Pegasus video-language model. Pegasus analyzes each video on the fly — its visuals **and** its own audio (via ASR) — and returns text, so each source video becomes one Document whose content is Pegasus's analysis (for example, a description plus a transcript). There is no frame extraction or separate transcription step.
| | |
| --- | --- |
| **Most common position in a pipeline** | At the beginning of an indexing pipeline, before [PreProcessors](../preprocessors.mdx) or an embedder |
| **Mandatory init variables** | `api_key`: The TwelveLabs API key. Can be set with `TWELVELABS_API_KEY` env var. |
| **Mandatory run variables** | `sources`: A list of video URLs or local file paths |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [TwelveLabs](/reference/integrations-twelvelabs) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/twelvelabs |
| **Package name** | `twelvelabs-haystack` |
## Overview
The `TwelveLabsVideoConverter` takes a list of video sources and produces one [`Document`](../../concepts/data-classes.mdx#document) per source, with the Document content set to Pegasus's text analysis. Sources may be publicly accessible direct video URLs or local file paths (uploaded to TwelveLabs, up to 200 MB). Sources that fail to process are skipped with a warning logged, so one bad source does not fail the whole batch.
Each produced Document carries metadata about the request, including `source`, `asset_id`, `analysis_id`, `model`, and `provider`. The default model is `pegasus1.5`.
You can steer the analysis with a custom `prompt` and tune `temperature` and `max_tokens`.
To start using this integration with Haystack, install the package with:
```shell
pip install twelvelabs-haystack
```
The component uses a `TWELVELABS_API_KEY` environment variable by default. Otherwise, you can pass an API key at initialization with `api_key`. To get an API key, head to [playground.twelvelabs.io](https://playground.twelvelabs.io).
## Usage
### On its own
```python
from haystack_integrations.components.converters.twelvelabs import (
TwelveLabsVideoConverter,
)
converter = TwelveLabsVideoConverter()
result = converter.run(sources=["https://example.com/clip.mp4"])
document = result["documents"][0]
print(document.content) # Pegasus's description + transcript of the video
print(document.meta) # includes source, asset_id, analysis_id, model, provider
```
:::info
We recommend setting `TWELVELABS_API_KEY` as an environment variable instead of setting it as a parameter.
:::
### With a custom prompt
```python
from haystack_integrations.components.converters.twelvelabs import (
TwelveLabsVideoConverter,
)
converter = TwelveLabsVideoConverter(
prompt="Summarize this video in three bullet points and list any products shown.",
temperature=0.2,
max_tokens=1024,
)
result = converter.run(sources=["https://example.com/clip.mp4"])
print(result["documents"][0].content)
```
### In a pipeline
This indexing pipeline analyzes videos with Pegasus, embeds the resulting analysis with the [`TwelveLabsDocumentEmbedder`](../embedders/twelvelabsdocumentembedder.mdx), and writes the documents to a document store:
```python
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.writers import DocumentWriter
from haystack_integrations.components.converters.twelvelabs import (
TwelveLabsVideoConverter,
)
from haystack_integrations.components.embedders.twelvelabs import (
TwelveLabsDocumentEmbedder,
)
document_store = InMemoryDocumentStore(embedding_similarity_function="cosine")
indexing_pipeline = Pipeline()
indexing_pipeline.add_component("converter", TwelveLabsVideoConverter())
indexing_pipeline.add_component("embedder", TwelveLabsDocumentEmbedder())
indexing_pipeline.add_component("writer", DocumentWriter(document_store=document_store))
indexing_pipeline.connect("converter", "embedder")
indexing_pipeline.connect("embedder", "writer")
indexing_pipeline.run({"converter": {"sources": ["https://example.com/clip.mp4"]}})
```
### Attaching metadata
Pass a single dictionary to apply metadata to all output Documents, or a list to set metadata per source:
```python
from haystack_integrations.components.converters.twelvelabs import (
TwelveLabsVideoConverter,
)
converter = TwelveLabsVideoConverter()
# Same metadata for all sources
result = converter.run(
sources=["https://example.com/a.mp4", "https://example.com/b.mp4"],
meta={"campaign": "demo"},
)
# Per-source metadata
result = converter.run(
sources=["https://example.com/a.mp4", "https://example.com/b.mp4"],
meta=[{"title": "Clip A"}, {"title": "Clip B"}],
)
```
---
// File: pipeline-components/converters/unstructuredfileconverter
# UnstructuredFileConverter
Use this component to convert text files and directories to a document.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) or right at the beginning of an indexing pipeline |
| **Mandatory run variables** | `paths`: A union of lists of paths |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [Unstructured](/reference/integrations-unstructured) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/unstructured |
| **Package name** | `unstructured-fileconverter-haystack` |
## Overview
`UnstructuredFileConverter` converts files and directories into documents using the Unstructured API.
[Unstructured](https://docs.unstructured.io/) provides a series of tools to do ETL for LLMs. The `UnstructuredFileConverter` calls the Unstructured API that extracts text and other information from a vast range of file [formats](https://docs.unstructured.io/api-reference/api-services/overview#supported-file-types).
This Converter supports different modes for creating documents from the elements returned by Unstructured:
- `"one-doc-per-file"`: One Haystack document per file. All elements are concatenated into one text field.
- `"one-doc-per-page"`: One Haystack document per page. All elements on a page are concatenated into one text field.
- `"one-doc-per-element"`: One Haystack document per element. Each element is converted to a Haystack document.
## Usage
Install the Unstructured integration to use `UnstructuredFileConverter`component:
```shell
pip install unstructured-fileconverter-haystack
```
There are free and paid versions of Unstructured API: **Free Unstructured API** and **Unstructured Serverless API**.
1. **Free Unstructured API**:
- API URL: `https://api.unstructured.io/general/v0/general`
- This version is free, but comes with certain limitations.
2. **Unstructured Serverless API**:
- You'll find your unique API URL in your Unstructured account after signing up for the paid version.
- This is a full-tier paid version of Unstructured.
For more details about the two tiers refer to Unstructured [FAQ](https://docs.unstructured.io/faq/faq).
> ❗️ The API keys for the free and paid versions are different and cannot be used interchangeably.
Regardless of the chosen tier, we recommend to set the Unstructured API key as an environment variable `UNSTRUCTURED_API_KEY`:
```shell
export UNSTRUCTURED_API_KEY=your_api_key
```
### On its own
```python
import os
from haystack_integrations.components.converters.unstructured import (
UnstructuredFileConverter,
)
converter = UnstructuredFileConverter()
documents = converter.run(paths=["a/file/path.pdf", "a/directory/path"])["documents"]
```
### In a pipeline
```python
import os
from haystack import Pipeline
from haystack.components.writers import DocumentWriter
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack_integrations.components.converters.unstructured import (
UnstructuredFileConverter,
)
document_store = InMemoryDocumentStore()
indexing = Pipeline()
indexing.add_component("converter", UnstructuredFileConverter())
indexing.add_component("writer", DocumentWriter(document_store))
indexing.connect("converter", "writer")
indexing.run({"converter": {"paths": ["a/file/path.pdf", "a/directory/path"]}})
```
### With Docker
To use `UnstructuredFileConverter` through Docker, first, set up an Unstructured Docker container:
```
docker run -p 8000:8000 -d --rm --name unstructured-api quay.io/unstructured-io/unstructured-api:latest --port 8000 --host 0.0.0.0
```
When initializing the component, specify the localhost URL:
```python
from haystack_integrations.components.converters.unstructured import (
UnstructuredFileConverter,
)
converter = UnstructuredFileConverter(
api_url="http://localhost:8000/general/v0/general",
)
```
---
// File: pipeline-components/converters/xlsxtodocument
# XLSXToDocument
Converts Excel files into documents.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) or right at the beginning of an indexing pipeline |
| **Mandatory run variables** | `sources`: File paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects |
| **Output variables** | `documents`: A list of documents |
| **API reference** | [Converters](/reference/converters-api) |
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/xlsx.py |
| **Package name** | `haystack-ai` |
## Overview
The `XLSXToDocument` component converts XLSX files into Haystack Documents with a CSV (default) or Markdown format. It takes a list of file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects as input and outputs the converted result as a list of documents. Optionally, you can attach metadata to the documents through the `meta` input parameter.
To see the additional parameters that you can specify with the component initialization, check out the [API Reference](/reference/converters-api#xlsxtodocument).
## Usage
First, install the pandas, openpyxl, and tabulate packages to start using this converter:
```shell
pip install pandas openpyxl
pip install tabulate
```
### On its own
```python
from haystack.components.converters import XLSXToDocument
converter = XLSXToDocument()
results = converter.run(
sources=["sample.xlsx"],
meta={"date_added": datetime.now().isoformat()},
)
documents = results["documents"]
print(documents[0].content)
# ",A,B\n1,col_a,col_b\n2,1.5,test\n"
```
### In a pipeline
```python
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.converters import XLSXToDocument
from haystack.components.preprocessors import DocumentCleaner
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
document_store = InMemoryDocumentStore()
pipeline = Pipeline()
pipeline.add_component("converter", XLSXToDocument())
pipeline.add_component("cleaner", DocumentCleaner())
pipeline.add_component(
"splitter",
DocumentSplitter(split_by="sentence", split_length=5),
)
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
pipeline.connect("converter", "cleaner")
pipeline.connect("cleaner", "splitter")
pipeline.connect("splitter", "writer")
pipeline.run({"converter": {"sources": file_names}})
```
---
// File: pipeline-components/converters
# Converters
Use various Converters to extract data from files in different formats and cast it into the unified document format. There are several converters available for converting PDFs, images, DOCX files, and more.
| Converter | Description |
| --- | --- |
| [AmazonTextractConverter](converters/amazontextractconverter.mdx) | Converts images and single-page PDFs to documents using AWS Textract, with optional structured analysis of tables, forms, signatures, and layout, plus natural-language queries. |
| [AzureDocumentIntelligenceConverter](converters/azuredocumentintelligenceconverter.mdx) | Converts PDF, JPEG, PNG, BMP, TIFF, DOCX, XLSX, PPTX, and HTML to documents using Azure's Document Intelligence service with GitHub Flavored Markdown output. |
| [AzureOCRDocumentConverter](converters/azureocrdocumentconverter.mdx) | Converts PDF (both searchable and image-only), JPEG, PNG, BMP, TIFF, DOCX, XLSX, PPTX, and HTML to documents. |
| [CSVToDocument](converters/csvtodocument.mdx) | Converts CSV files to documents. |
| [DoclingConverter](converters/doclingconverter.mdx) | Converts PDF, DOCX, HTML, and other document formats to documents with layout-aware chunking, Markdown, and JSON export. |
| [DoclingServeConverter](converters/doclingserveconverter.mdx) | Converts PDF, DOCX, HTML, and other document formats to documents using a remote DoclingServe HTTP server, with no local ML dependencies. |
| [DocumentToImageContent](converters/documenttoimagecontent.mdx) | Extracts visual data from image or PDF file-based documents and converts them into `ImageContent` objects. |
| [DOCXToDocument](converters/docxtodocument.mdx) | Convert DOCX files to documents. |
| [FileToFileContent](converters/filetofilecontent.mdx) | Reads files and converts them into `FileContent` objects. |
| [HTMLToDocument](converters/htmltodocument.mdx) | Converts HTML files to documents. |
| [ImageFileToDocument](converters/imagefiletodocument.mdx) | Converts image file references into empty `Document` objects with associated metadata. |
| [ImageFileToImageContent](converters/imagefiletoimagecontent.mdx) | Reads local image files and converts them into `ImageContent` objects. |
| [JSONConverter](converters/jsonconverter.mdx) | Converts JSON files to text documents. |
| [KreuzbergConverter](converters/kreuzbergconverter.mdx) | Converts 91+ file formats to documents locally using Kreuzberg's Rust-core engine. |
| [LibreOfficeFileConverter](converters/libreofficefileconverter.mdx) | Converts office files (documents, spreadsheets, presentations) between formats using LibreOffice's command line interface. |
| [MarkdownToDocument](converters/markdowntodocument.mdx) | Converts markdown files to documents. |
| [MarkItDownConverter](converters/markitdownconverter.mdx) | Converts PDF, Word, PowerPoint, Excel, HTML, images, and more to documents using Microsoft's MarkItDown library. |
| [MistralOCRDocumentConverter](converters/mistralocrdocumentconverter.mdx) | Extracts text from documents using Mistral's OCR API, with optional structured annotations. |
| [MSGToDocument](converters/msgtodocument.mdx) | Converts Microsoft Outlook .msg files to documents. |
| [MultiFileConverter](converters/multifileconverter.mdx) | Converts CSV, DOCX, HTML, JSON, MD, PPTX, PDF, TXT, and XSLX files to documents. |
| [OpenAPIServiceToFunctions](converters/openapiservicetofunctions.mdx) | Transforms OpenAPI service specifications into a format compatible with OpenAI's function calling mechanism. |
| [OutputAdapter](converters/outputadapter.mdx) | Helps the output of one component fit into the input of another. |
| [PaddleOCRVLDocumentConverter](converters/paddleocrvldocumentconverter.mdx) | Extracts text from documents using PaddleOCR's large model document parsing API. |
| [PDFMinerToDocument](converters/pdfminertodocument.mdx) | Converts complex PDF files to documents using pdfminer arguments. |
| [PDFToImageContent](converters/pdftoimagecontent.mdx) | Reads local PDF files and converts them into `ImageContent` objects. |
| [PPTXToDocument](converters/pptxtodocument.mdx) | Converts PPTX files to documents. |
| [PyPDFToDocument](converters/pypdftodocument.mdx) | Converts PDF files to documents. |
| [TikaDocumentConverter](converters/tikadocumentconverter.mdx) | Converts various file types to documents using Apache Tika. |
| [TextFileToDocument](converters/textfiletodocument.mdx) | Converts text files to documents. |
| [TwelveLabsVideoConverter](converters/twelvelabsvideoconverter.mdx) | Converts videos to documents using the TwelveLabs Pegasus video-language model. |
| [UnstructuredFileConverter](converters/unstructuredfileconverter.mdx) | Converts text files and directories to a document. |
| [XLSXToDocument](converters/xlsxtodocument.mdx) | Converts Excel files into documents. |
---
// File: pipeline-components/downloaders/s3downloader
# S3Downloader
`S3Downloader` downloads files from AWS S3 buckets to the local filesystem and enriches documents with the local file path.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before File Converters or Routers that need local file paths |
| **Mandatory init variables** | `file_root_path`: Path where files will be downloaded. Can be set with `FILE_ROOT_PATH` env var.
`aws_access_key_id`: AWS access key ID. Can be set with AWS_ACCESS_KEY_ID env var.
`aws_secret_access_key`: AWS secret access key. Can be set with AWS_SECRET_ACCESS_KEY env var.
`aws_region_name`: AWS region name. Can be set with AWS_DEFAULT_REGION env var. |
| **Mandatory run variables** | `documents`: A list of documents containing name of the file to download in metadata. |
| **Output variables** | `documents`: A list of documents enriched with the local file path in `meta['file_path']` |
| **API reference** | [S3Downloader](/reference/integrations-amazon-bedrock) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/amazon_bedrock |
| **Package name** | `amazon-bedrock-haystack` |
## Overview
`S3Downloader` downloads files from AWS S3 buckets to your local filesystem and enriches Document objects with the local file path. This component is useful for pipelines that need to process files stored in S3, such as PDFs, images, or text files.
The component supports AWS authentication through environment variables by default. You can set `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_DEFAULT_REGION` environment variables. Alternatively, you can pass credentials directly at initialization using the [Secret API](../../concepts/secret-management.mdx):
```python
from haystack.utils import Secret
from haystack_integrations.components.downloaders.s3 import S3Downloader
downloader = S3Downloader(
aws_access_key_id=Secret.from_token(""),
aws_secret_access_key=Secret.from_token(""),
aws_region_name=Secret.from_token(""),
file_root_path="/path/to/download/directory",
)
```
The component downloads multiple files in parallel using the `max_workers` parameter (default is 32 workers) to speed up processing of large document sets. Downloaded files are cached locally, and when the cache exceeds `max_cache_size` (default is 100 files), least recently accessed files are automatically removed. Already downloaded files are touched to update their access time without re-downloading.
:::info[Required Configuration]
The component requires two critical configurations:
1. `file_root_path` parameter or `FILE_ROOT_PATH` environment variable: Specifies where files will be downloaded. This directory will be created if it doesn't exist.
2. `S3_DOWNLOADER_BUCKET` environment variable: Specifies which S3 bucket to download files from.
:::
The optional environment variable `S3_DOWNLOADER_PREFIX` can be set to add a prefix of the files to all generated S3 keys.
### File Extension Filtering
You can use the `file_extensions` parameter to download only specific file types, reducing unnecessary downloads and processing time. For example, `file_extensions=[".pdf", ".txt"]` downloads only PDF and TXT files while skipping others.
### Custom S3 Key Generation
By default, the component uses the `file_name` from Document metadata as the S3 key. If your S3 file structure doesn't match the file names in metadata, you can provide an optional `s3_key_generation_function` to customize how S3 keys are generated from Document metadata.
## Usage
You need to install the `amazon-bedrock-haystack` package to use `S3Downloader`:
```shell
pip install amazon-bedrock-haystack
```
### On its own
Before running the examples, ensure you have set the required environment variables:
```shell
export AWS_ACCESS_KEY_ID=""
export AWS_SECRET_ACCESS_KEY=""
export AWS_DEFAULT_REGION=""
export S3_DOWNLOADER_BUCKET=""
```
Here's how to use `S3Downloader` to download files from S3:
```python
from haystack.dataclasses import Document
from haystack_integrations.components.downloaders.s3 import S3Downloader
# Create documents with file names in metadata
documents = [
Document(meta={"file_name": "report.pdf"}),
Document(meta={"file_name": "data.txt"}),
]
# Initialize the downloader
downloader = S3Downloader(file_root_path="/tmp/s3_downloads")
# Download the files
result = downloader.run(documents=documents)
# Access the downloaded files
for doc in result["documents"]:
print(f"File downloaded to: {doc.meta['file_path']}")
```
With file extension filtering:
```python
from haystack.dataclasses import Document
from haystack_integrations.components.downloaders.s3 import S3Downloader
documents = [
Document(meta={"file_name": "report.pdf"}),
Document(meta={"file_name": "image.png"}),
Document(meta={"file_name": "data.txt"}),
]
# Only download PDF files
downloader = S3Downloader(file_root_path="/tmp/s3_downloads", file_extensions=[".pdf"])
result = downloader.run(documents=documents)
# Only report.pdf is downloaded
print(f"Downloaded {len(result['documents'])} file(s)")
# Output: Downloaded 1 file(s)
```
With custom S3 key generation:
```python
from haystack.dataclasses import Document
from haystack_integrations.components.downloaders.s3 import S3Downloader
def custom_s3_key_function(document: Document) -> str:
"""Generate S3 key from custom metadata."""
folder = document.meta.get("folder", "default")
file_name = document.meta.get("file_name")
if not file_name:
raise ValueError("Document must have 'file_name' in metadata")
return f"{folder}/{file_name}"
documents = [
Document(meta={"file_name": "report.pdf", "folder": "reports/2025"}),
]
downloader = S3Downloader(
file_root_path="/tmp/s3_downloads",
s3_key_generation_function=custom_s3_key_function,
)
result = downloader.run(documents=documents)
```
### In a pipeline
Here's an example of using `S3Downloader` in a document processing pipeline:
```python
from haystack import Pipeline
from haystack.components.converters import PDFMinerToDocument
from haystack.components.routers import DocumentTypeRouter
from haystack.dataclasses import Document
from haystack_integrations.components.downloaders.s3 import S3Downloader
# Create a pipeline
pipe = Pipeline()
# Add S3Downloader to download files from S3
pipe.add_component(
"downloader",
S3Downloader(file_root_path="/tmp/s3_downloads", file_extensions=[".pdf", ".txt"]),
)
# Route documents by file type
pipe.add_component(
"router",
DocumentTypeRouter(
file_path_meta_field="file_path",
mime_types=["application/pdf", "text/plain"],
),
)
# Convert PDFs to documents
pipe.add_component("pdf_converter", PDFMinerToDocument())
# Connect components
pipe.connect("downloader.documents", "router.documents")
pipe.connect("router.application/pdf", "pdf_converter.documents")
# Create documents with S3 file names
documents = [
Document(meta={"file_name": "report.pdf"}),
Document(meta={"file_name": "summary.txt"}),
]
# Run the pipeline
result = pipe.run({"downloader": {"documents": documents}})
```
For a more complex example with image processing and LLM:
```python
from haystack import Pipeline
from haystack.components.builders import ChatPromptBuilder
from haystack.components.converters.image import DocumentToImageContent
from haystack.components.routers import DocumentTypeRouter
from haystack.dataclasses import Document
from haystack_integrations.components.downloaders.s3 import S3Downloader
from haystack_integrations.components.generators.amazon_bedrock import (
AmazonBedrockChatGenerator,
)
# Create documents with file names
documents = [
Document(meta={"file_name": "chart.png"}),
Document(meta={"file_name": "report.pdf"}),
]
# Create pipeline
pipe = Pipeline()
# Download files from S3
pipe.add_component("downloader", S3Downloader(file_root_path="/tmp/s3_downloads"))
# Route by document type
pipe.add_component(
"router",
DocumentTypeRouter(
file_path_meta_field="file_path",
mime_types=["image/png", "application/pdf"],
),
)
# Convert images for LLM
pipe.add_component("image_converter", DocumentToImageContent(detail="auto"))
# Create chat prompt with template
template = """{% message role="user" %}
Answer the question based on the provided images.
Question: {{ question }}
{% for image in image_contents %}
{{ image | templatize_part }}
{% endfor %}
{% endmessage %}"""
pipe.add_component("prompt_builder", ChatPromptBuilder(template=template))
# Generate response
pipe.add_component(
"llm",
AmazonBedrockChatGenerator(model="anthropic.claude-3-haiku-20240307-v1:0"),
)
# Connect components
pipe.connect("downloader.documents", "router.documents")
pipe.connect("router.image/png", "image_converter.documents")
pipe.connect("image_converter.image_contents", "prompt_builder.image_contents")
pipe.connect("prompt_builder.prompt", "llm.messages")
# Run pipeline
result = pipe.run(
{
"downloader": {"documents": documents},
"prompt_builder": {"question": "What information is shown in the chart?"},
},
)
```
---
// File: pipeline-components/embedders/amazonbedrockdocumentembedder
# AmazonBedrockDocumentEmbedder
This component computes embeddings for documents using models through Amazon Bedrock API.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before a [`DocumentWriter`](../writers/documentwriter.mdx) in an indexing pipeline |
| **Mandatory init variables** | `model`: The embedding model to use
`aws_access_key_id`: AWS access key ID. Can be set with `AWS_ACCESS_KEY_ID` env var.
`aws_secret_access_key`: AWS secret access key. Can be set with `AWS_SECRET_ACCESS_KEY` env var.
`aws_region_name`: AWS region name. Can be set with `AWS_DEFAULT_REGION` env var. |
| **Mandatory run variables** | `documents`: A list of documents to be embedded |
| **Output variables** | `documents`: A list of documents (enriched with embeddings) |
| **API reference** | [Amazon Bedrock](/reference/integrations-amazon-bedrock) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/amazon_bedrock |
| **Package name** | `amazon-bedrock-haystack` |
## Overview
[Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html) is a fully managed service that makes language models from leading AI startups and Amazon available for your use through a unified API.
Amazon Titan and Cohere embedding models are supported, for example `amazon.titan-embed-text-v1`, `amazon.titan-embed-text-v2:0`, `amazon.titan-embed-image-v1`, `cohere.embed-english-v3`, `cohere.embed-multilingual-v3`, and `cohere.embed-v4:0`. To find all supported models, see the [Amazon Bedrock documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html), filter for "embedding", and select models from the Amazon Titan and Cohere series.
:::info[Batch Inference]
Note that only Cohere models support batch inference – computing embeddings for more documents with the same request.
:::
This component should be used to embed a list of documents. To embed a string, you should use the [`AmazonBedrockTextEmbedder`](amazonbedrocktextembedder.mdx).
### Authentication
`AmazonBedrockDocumentEmbedder` uses AWS for authentication. You can either provide credentials as parameters directly to the component or use the AWS CLI and authenticate through your IAM. For more information on how to set up an IAM identity-based policy, see the [official documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/security_iam_id-based-policy-examples.html).
To initialize `AmazonBedrockDocumentEmbedder` and authenticate by providing credentials, provide the `model` name, as well as `aws_access_key_id`, `aws_secret_access_key` and `aws_region_name`. Other parameters are optional. You can check them out in our [API reference](/reference/integrations-amazon-bedrock#amazonbedrockdocumentembedder).
### Model-specific parameters
Even if Haystack provides a unified interface, each model offered by Bedrock can accept specific parameters. You can pass these parameters at initialization.
For example, Cohere models support `input_type` and `truncate`, as seen in [Bedrock documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html).
```python
from haystack_integrations.components.embedders.amazon_bedrock import (
AmazonBedrockDocumentEmbedder,
)
embedder = AmazonBedrockDocumentEmbedder(
model="cohere.embed-english-v3",
input_type="search_document",
truncate="LEFT",
)
```
### Embedding Metadata
Text documents often come with a set of metadata. If they are distinctive and semantically meaningful, you can embed them along with the text of the document to improve retrieval.
You can do this easily by using the Document Embedder:
```python
from haystack import Document
from haystack_integrations.components.embedders.amazon_bedrock import (
AmazonBedrockDocumentEmbedder,
)
doc = Document(content="some text", meta={"title": "relevant title", "page number": 18})
embedder = AmazonBedrockDocumentEmbedder(
model="cohere.embed-english-v3",
meta_fields_to_embed=["title"],
)
docs_w_embeddings = embedder.run(documents=[doc])["documents"]
```
## Usage
### Installation
You need to install `amazon-bedrock-haystack` package to use the `AmazonBedrockDocumentEmbedder`:
```shell
pip install amazon-bedrock-haystack
```
### On its own
Basic usage:
```python
import os
from haystack import Document
from haystack_integrations.components.embedders.amazon_bedrock import (
AmazonBedrockDocumentEmbedder,
)
os.environ["AWS_ACCESS_KEY_ID"] = "..."
os.environ["AWS_SECRET_ACCESS_KEY"] = "..."
os.environ["AWS_DEFAULT_REGION"] = "us-east-1" # just an example
doc = Document(content="I love pizza!")
embedder = AmazonBedrockDocumentEmbedder(
model="cohere.embed-english-v3",
input_type="search_document",
)
result = embedder.run(documents=[doc])
print(result["documents"][0].embedding)
# [0.017020374536514282, -0.023255806416273117, ...]
```
### In a pipeline
In a RAG pipeline:
```python
from haystack import Document, Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack_integrations.components.embedders.amazon_bedrock import (
AmazonBedrockDocumentEmbedder,
AmazonBedrockTextEmbedder,
)
from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever
from haystack.components.writers import DocumentWriter
document_store = InMemoryDocumentStore(embedding_similarity_function="cosine")
documents = [
Document(content="My name is Wolfgang and I live in Berlin"),
Document(content="I saw a black horse running"),
Document(content="Germany has many big cities"),
]
indexing_pipeline = Pipeline()
indexing_pipeline.add_component(
"embedder",
AmazonBedrockDocumentEmbedder(model="cohere.embed-english-v3"),
)
indexing_pipeline.add_component("writer", DocumentWriter(document_store=document_store))
indexing_pipeline.connect("embedder", "writer")
indexing_pipeline.run({"embedder": {"documents": documents}})
query_pipeline = Pipeline()
query_pipeline.add_component(
"text_embedder",
AmazonBedrockTextEmbedder(model="cohere.embed-english-v3"),
)
query_pipeline.add_component(
"retriever",
InMemoryEmbeddingRetriever(document_store=document_store),
)
query_pipeline.connect("text_embedder.embedding", "retriever.query_embedding")
query = "Who lives in Berlin?"
result = query_pipeline.run({"text_embedder": {"text": query}})
print(result["retriever"]["documents"][0])
# Document(id=..., content: 'My name is Wolfgang and I live in Berlin')
```
## Additional References
🧑🍳 Cookbook: [PDF-Based Question Answering with Amazon Bedrock and Haystack](https://haystack.deepset.ai/cookbook/amazon_bedrock_for_documentation_qa)
---
// File: pipeline-components/embedders/amazonbedrockdocumentimageembedder
# AmazonBedrockDocumentImageEmbedder
`AmazonBedrockDocumentImageEmbedder` computes image embeddings for documents using models exposed through the Amazon Bedrock API. It stores the obtained vectors in the embedding field of each document.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before a [`DocumentWriter`](../writers/documentwriter.mdx) in an indexing pipeline |
| **Mandatory init variables** | `model`: The multimodal embedding model to use.
`aws_access_key_id`: AWS access key ID. Can be set with `AWS_ACCESS_KEY_ID` env var.
`aws_secret_access_key`: AWS secret access key. Can be set with `AWS_SECRET_ACCESS_KEY` env var.
`aws_region_name`: AWS region name. Can be set with `AWS_DEFAULT_REGION` env var. |
| **Mandatory run variables** | `documents`: A list of documents, with a meta field containing an image file path |
| **Output variables** | `documents`: A list of documents (enriched with embeddings) |
| **API reference** | [Amazon Bedrock](/reference/integrations-amazon-bedrock) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/amazon_bedrock |
| **Package name** | `amazon-bedrock-haystack` |
## Overview
Amazon Bedrock is a fully managed service that provides access to foundation models through a unified API.
`AmazonBedrockDocumentImageEmbedder` expects a list of documents containing an image or a PDF file path in a meta field. The meta field can be specified with the `file_path_meta_field` init parameter of this component.
The embedder efficiently loads the images, computes the embeddings using selected Bedrock model, and stores each of them in the `embedding` field of the document.
Amazon Titan and Cohere multimodal embedding models are supported, for example `amazon.titan-embed-image-v1`, `cohere.embed-english-v3`, `cohere.embed-multilingual-v3`, and `cohere.embed-v4:0`.
`AmazonBedrockDocumentImageEmbedder` is commonly used in indexing pipelines. At retrieval time, you need to use the same model with `AmazonBedrockTextEmbedder` to embed the query, before using an Embedding Retriever.
### Installation
To start using this integration with Haystack, install the package with:
```shell
pip install amazon-bedrock-haystack
```
### Authentication
`AmazonBedrockDocumentImageEmbedder` uses AWS for authentication. You can either provide credentials as parameters directly to the component or use the AWS CLI and authenticate through your IAM. For more information on how to set up an IAM identity-based policy, see the [official documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/security_iam_id-based-policy-examples.html).
To initialize `AmazonBedrockDocumentImageEmbedder` and authenticate by providing credentials, provide the `model` name, as well as `aws_access_key_id`, `aws_secret_access_key`, and `aws_region_name`. Other parameters are optional, you can check them out in our [API reference](/reference/integrations-amazon-bedrock#amazonbedrocktextembedder).
### Model-specific parameters
Even if Haystack provides a unified interface, each model offered by Bedrock can accept specific parameters. You can pass these parameters at initialization.
- **Amazon Titan**: Use `embeddingConfig` to control embedding behavior.
- **Cohere v3**: Use `embedding_types` to select a single embedding type for images.
```python
from haystack_integrations.components.embedders.amazon_bedrock import (
AmazonBedrockDocumentImageEmbedder,
)
embedder = AmazonBedrockDocumentImageEmbedder(
model="cohere.embed-english-v3",
embedding_types=["float"], # single value only
)
```
Note that only _one_ value in `embedding_types` is supported by this component. Passing multiple values raises an error.
## Usage
### On its own
```python
import os
from haystack import Document
from haystack_integrations.components.embedders.amazon_bedrock import (
AmazonBedrockDocumentImageEmbedder,
)
os.environ["AWS_ACCESS_KEY_ID"] = "..."
os.environ["AWS_SECRET_ACCESS_KEY"] = "..."
os.environ["AWS_DEFAULT_REGION"] = "us-east-1" # example
# Point Documents to image/PDF files via metadata (default key: "file_path")
documents = [
Document(content="A photo of a cat", meta={"file_path": "cat.jpg"}),
Document(
content="Invoice page",
meta={
"file_path": "invoice.pdf",
"mime_type": "application/pdf",
"page_number": 1,
},
),
]
embedder = AmazonBedrockDocumentImageEmbedder(
model="amazon.titan-embed-image-v1",
image_size=(1024, 1024), # optional downscaling
)
result = embedder.run(documents=documents)
embedded_docs = result["documents"]
```
### In a pipeline
In this example, we can see an indexing pipeline with 3 components:
- `ImageFileToDocument` Converter that creates empty documents with a reference to an image in the `meta.file_path` field;
- `AmazonBedrockDocumentImageEmbedder` that loads the images, computes embeddings and stores them in documents;
- `DocumentWriter` that write the documents in the `InMemoryDocumentStore`.
There is also a multimodal retrieval pipeline, composed of an `AmazonBedrockTextEmbedder` (using the same model as before) and an `InMemoryEmbeddingRetriever`.
```python
from haystack import Document, Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.writers import DocumentWriter
from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever
from haystack_integrations.components.embedders.amazon_bedrock import (
AmazonBedrockDocumentImageEmbedder,
AmazonBedrockTextEmbedder,
)
# Document store using vector similarity for retrieval
document_store = InMemoryDocumentStore(embedding_similarity_function="cosine")
# Sample corpus with file paths in metadata
documents = [
Document(content="A sketch of a horse", meta={"file_path": "horse.png"}),
Document(content="A city map", meta={"file_path": "map.jpg"}),
]
# Indexing pipeline: image embeddings -> write to store
indexing = Pipeline()
indexing.add_component(
"image_embedder",
AmazonBedrockDocumentImageEmbedder(model="cohere.embed-english-v3"),
)
indexing.add_component("writer", DocumentWriter(document_store=document_store))
indexing.connect("image_embedder", "writer")
indexing.run({"image_embedder": {"documents": documents}})
# Query pipeline: text -> embedding -> vector retriever
query = Pipeline()
query.add_component(
"text_embedder",
AmazonBedrockTextEmbedder(model="cohere.embed-english-v3"),
)
query.add_component(
"retriever",
InMemoryEmbeddingRetriever(document_store=document_store),
)
query.connect("text_embedder.embedding", "retriever.query_embedding")
res = query.run({"text_embedder": {"text": "Which document shows a horse?"}})
```
## Additional References
:notebook: Tutorial: [Creating Vision+Text RAG Pipelines](https://haystack.deepset.ai/tutorials/46_multimodal_rag)
---
// File: pipeline-components/embedders/amazonbedrocktextembedder
# AmazonBedrockTextEmbedder
This component computes embeddings for text (such as a query) using models through Amazon Bedrock API.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before an embedding [Retriever](../retrievers.mdx) in a query/RAG pipeline |
| **Mandatory init variables** | `model`: The embedding model to use
`aws_access_key_id`: AWS access key ID. Can be set with `AWS_ACCESS_KEY_ID` env var.
`aws_secret_access_key`: AWS secret access key. Can be set with `AWS_SECRET_ACCESS_KEY` env var.
`aws_region_name`: AWS region name. Can be set with `AWS_DEFAULT_REGION` env var. |
| **Mandatory run variables** | `text`: A string |
| **Output variables** | `embedding`: A list of float numbers (vector) |
| **API reference** | [Amazon Bedrock](/reference/integrations-amazon-bedrock) |
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/amazon_bedrock |
| **Package name** | `amazon-bedrock-haystack` |
## Overview
[Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html) is a fully managed service that makes language models from leading AI startups and Amazon available for your use through a unified API.
Amazon Titan and Cohere embedding models are supported, for example `amazon.titan-embed-text-v1`, `amazon.titan-embed-text-v2:0`, `amazon.titan-embed-image-v1`, `cohere.embed-english-v3`, `cohere.embed-multilingual-v3`, and `cohere.embed-v4:0`. To find all supported models, see the [Amazon Bedrock documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html), filter for "embedding", and select models from the Amazon Titan and Cohere series.
Use `AmazonBedrockTextEmbedder` to embed a simple string (such as a query) into a vector. Use the [`AmazonBedrockDocumentEmbedder`](amazonbedrockdocumentembedder.mdx) to enrich the documents with the computed embedding, also known as vector.
### Authentication
`AmazonBedrockTextEmbedder` uses AWS for authentication. You can either provide credentials as parameters directly to the component or use the AWS CLI and authenticate through your IAM. For more information on how to set up an IAM identity-based policy, see the [official documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/security_iam_id-based-policy-examples.html).
To initialize `AmazonBedrockTextEmbedder` and authenticate by providing credentials, provide the `model` name, as well as `aws_access_key_id`, `aws_secret_access_key`, and `aws_region_name`. Other parameters are optional, you can check them out in our [API reference](/reference/integrations-amazon-bedrock#amazonbedrocktextembedder).
### Model-specific parameters
Even if Haystack provides a unified interface, each model offered by Bedrock can accept specific parameters. You can pass these parameters at initialization.
For example, the Cohere models support `input_type` and `truncate`, as seen in [Bedrock documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html).
```python
from haystack_integrations.components.embedders.amazon_bedrock import (
AmazonBedrockTextEmbedder,
)
embedder = AmazonBedrockTextEmbedder(
model="cohere.embed-english-v3",
input_type="search_query",
truncate="LEFT",
)
```
## Usage
### Installation
You need to install `amazon-bedrock-haystack` package to use the `AmazonBedrockTextEmbedder`:
```shell
pip install amazon-bedrock-haystack
```
### On its own
Basic usage:
```python
import os
from haystack_integrations.components.embedders.amazon_bedrock import (
AmazonBedrockTextEmbedder,
)
os.environ["AWS_ACCESS_KEY_ID"] = "..."
os.environ["AWS_SECRET_ACCESS_KEY"] = "..."
os.environ["AWS_DEFAULT_REGION"] = "us-east-1" # just an example
text_to_embed = "I love pizza!"
text_embedder = AmazonBedrockTextEmbedder(
model="cohere.embed-english-v3",
input_type="search_query",
)
print(text_embedder.run(text_to_embed))
# {'embedding': [-0.453125, 1.2236328, 2.0058594, 0.67871094...]}
```
### In a pipeline
In a RAG pipeline:
```python
from haystack import Document
from haystack import Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack_integrations.components.embedders.amazon_bedrock import (
AmazonBedrockDocumentEmbedder,
AmazonBedrockTextEmbedder,
)
from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever
document_store = InMemoryDocumentStore(embedding_similarity_function="cosine")
documents = [
Document(content="My name is Wolfgang and I live in Berlin"),
Document(content="I saw a black horse running"),
Document(content="Germany has many big cities"),
]
document_embedder = AmazonBedrockDocumentEmbedder(model="cohere.embed-english-v3")
documents_with_embeddings = document_embedder.run(documents)["documents"]
document_store.write_documents(documents_with_embeddings)
query_pipeline = Pipeline()
query_pipeline.add_component(
"text_embedder",
AmazonBedrockTextEmbedder(model="cohere.embed-english-v3"),
)
query_pipeline.add_component(
"retriever",
InMemoryEmbeddingRetriever(document_store=document_store),
)
query_pipeline.connect("text_embedder.embedding", "retriever.query_embedding")
query = "Who lives in Berlin?"
result = query_pipeline.run({"text_embedder": {"text": query}})
print(result["retriever"]["documents"][0])
# Document(id=..., content: 'My name is Wolfgang and I live in Berlin')
```
## Additional References
🧑🍳 Cookbook: [PDF-Based Question Answering with Amazon Bedrock and Haystack](https://haystack.deepset.ai/cookbook/amazon_bedrock_for_documentation_qa)
---
// File: pipeline-components/embedders/azureopenaidocumentembedder
# AzureOpenAIDocumentEmbedder
This component computes the embeddings of a list of documents and stores the obtained vectors in the embedding field of each document. It uses Azure cognitive services for text and document embedding with models deployed on Azure.
| | |
| --- | --- |
| **Most common position in a pipeline** | Before a [`DocumentWriter`](../writers/documentwriter.mdx) |
| **Mandatory init variables** | `api_key`: The Azure OpenAI API key. Can be set with `AZURE_OPENAI_API_KEY` env var. `azure_endpoint`: The endpoint of the model deployed on Azure. |
| **Mandatory run variables** | `documents`: A list of documents |
| **Output variables** | `documents`: A list of documents (enriched with embeddings)