chore: import upstream snapshot with attribution
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
Docker image release / Build base image (push) Has been cancelled
Sync docs with Docusaurus / sync (push) Has been cancelled
Tests / Check if changed (push) Has been cancelled
Tests / format (push) Has been cancelled
Tests / check-imports (push) Has been cancelled
Tests / Unit / macos-latest (push) Has been cancelled
Tests / Unit / ubuntu-latest (push) Has been cancelled
Tests / Unit / windows-latest (push) Has been cancelled
Tests / mypy (push) Has been cancelled
Tests / Integration / ubuntu-latest (push) Has been cancelled
Tests / Integration / macos-latest (push) Has been cancelled
Tests / Integration / windows-latest (push) Has been cancelled
Tests / notify-slack-on-failure (push) Has been cancelled
Tests / Mark tests as completed (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
Docker image release / Build base image (push) Has been cancelled
Sync docs with Docusaurus / sync (push) Has been cancelled
Tests / Check if changed (push) Has been cancelled
Tests / format (push) Has been cancelled
Tests / check-imports (push) Has been cancelled
Tests / Unit / macos-latest (push) Has been cancelled
Tests / Unit / ubuntu-latest (push) Has been cancelled
Tests / Unit / windows-latest (push) Has been cancelled
Tests / mypy (push) Has been cancelled
Tests / Integration / ubuntu-latest (push) Has been cancelled
Tests / Integration / macos-latest (push) Has been cancelled
Tests / Integration / windows-latest (push) Has been cancelled
Tests / notify-slack-on-failure (push) Has been cancelled
Tests / Mark tests as completed (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "Component Name"
|
||||
id: "component-name"
|
||||
description: "A short description of the component"
|
||||
slug: "/component-name"
|
||||
---
|
||||
|
||||
# Component Name
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Most common position in a pipeline** | |
|
||||
| **Mandatory init variables** | |
|
||||
| **Mandatory run variables** | |
|
||||
| **Output variables** | |
|
||||
| **API reference** | |
|
||||
| **GitHub link** | |
|
||||
| **Package name** | |
|
||||
|
||||
</div>
|
||||
|
||||
## 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.*
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
title: "Document Store Name"
|
||||
id: "document-store-name"
|
||||
description: "A short description of the document store"
|
||||
slug: "/document-store-name"
|
||||
---
|
||||
|
||||
# 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)*
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
title: "Agents"
|
||||
id: agents
|
||||
slug: "/agents"
|
||||
description: "This page explains how to create an AI agent in Haystack capable of retrieving information, generating responses, and taking actions using various Haystack components."
|
||||
---
|
||||
|
||||
# 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 a `ComponentTool` 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.
|
||||
- [`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=<your-openai-key>
|
||||
export SERPERDEV_API_KEY=<your-serperdev-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.components.websearch 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).
|
||||
```
|
||||
@@ -0,0 +1,338 @@
|
||||
---
|
||||
title: "Multi-Agent Systems"
|
||||
id: multi-agent-systems
|
||||
slug: "/multi-agent-systems"
|
||||
description: "Learn how to build multi-agent systems in Haystack by spawning agents as tools. Use the @tool decorator or ComponentTool to connect specialist agents to a coordinator."
|
||||
---
|
||||
|
||||
# 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 using either the `@tool` decorator (recommended) or `ComponentTool`.
|
||||
|
||||
## Converting an Agent to a Tool
|
||||
|
||||
### `@tool` Decorator (Recommended)
|
||||
|
||||
Wrapping an agent inside a `@tool` function gives you full control over what the coordinator LLM sees:
|
||||
|
||||
- **Simplified parameters**: define explicit `Annotated` arguments instead of exposing `agent.run()`'s full interface
|
||||
- **Formatted output**: extract and return only what the coordinator needs, rather than the full result dict
|
||||
- **Error handling**: catch exceptions and return a clean message so the coordinator can recover
|
||||
|
||||
This approach works better with smaller LLMs because the tool has a clean, minimal signature.
|
||||
The coordinator only needs to provide a query string - all the `ChatMessage` construction and result unpacking is hidden inside the function.
|
||||
|
||||
```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 ComponentTool, tool
|
||||
from haystack.components.websearch import SerperDevWebSearch
|
||||
from haystack.utils import Secret
|
||||
|
||||
|
||||
research_agent = Agent(
|
||||
chat_generator=OpenAIChatGenerator(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.",
|
||||
)
|
||||
|
||||
|
||||
@tool
|
||||
def research(query: Annotated[str, "The research question to investigate"]) -> str:
|
||||
"""Research a topic and return a summary of findings."""
|
||||
try:
|
||||
result = research_agent.run(messages=[ChatMessage.from_user(query)])
|
||||
return result["last_message"].text
|
||||
except Exception as e:
|
||||
return f"Research failed: {e}"
|
||||
|
||||
|
||||
coordinator = Agent(
|
||||
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
|
||||
tools=[research],
|
||||
system_prompt="You are a coordinator. Delegate research tasks to the research tool.",
|
||||
streaming_callback=print_streaming_chunk,
|
||||
)
|
||||
|
||||
result = coordinator.run(
|
||||
messages=[
|
||||
ChatMessage.from_user("What are the latest developments in Haystack AI?"),
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
### `ComponentTool`
|
||||
|
||||
`ComponentTool` wraps an agent directly without a wrapper function.
|
||||
Choose it when you want **declarative configuration**: the full specialist setup (model, tools, system prompt) lives in one serializable object alongside the coordinator.
|
||||
|
||||
Use `outputs_to_string={"source": "last_message"}` to surface only the specialist's final reply to the coordinator rather than the full result dict.
|
||||
|
||||
```python
|
||||
from haystack.tools import ComponentTool
|
||||
|
||||
research_tool = ComponentTool(
|
||||
component=research_agent,
|
||||
name="research_specialist",
|
||||
description="A specialist that researches topics on the web",
|
||||
outputs_to_string={"source": "last_message"},
|
||||
)
|
||||
|
||||
coordinator = Agent(
|
||||
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
|
||||
tools=[research_tool],
|
||||
system_prompt="You are a coordinator. Delegate research tasks to the research specialist.",
|
||||
streaming_callback=print_streaming_chunk,
|
||||
)
|
||||
|
||||
result = coordinator.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()`.
|
||||
|
||||
<details>
|
||||
<summary>View YAML</summary>
|
||||
|
||||
```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.OpenAIChatGenerator
|
||||
confirmation_strategies: null
|
||||
exit_conditions:
|
||||
- text
|
||||
max_agent_steps: 100
|
||||
raise_on_tool_invocation_failure: false
|
||||
required_variables: null
|
||||
state_schema: {}
|
||||
streaming_callback: null
|
||||
system_prompt: You are a coordinator. Delegate research tasks to the research
|
||||
specialist. Keep your final answer concise.
|
||||
tool_invoker_kwargs: null
|
||||
tools:
|
||||
- data:
|
||||
component:
|
||||
init_parameters:
|
||||
chat_generator:
|
||||
init_parameters:
|
||||
api_base_url: null
|
||||
api_key:
|
||||
env_vars:
|
||||
- OPENAI_API_KEY
|
||||
strict: true
|
||||
type: env_var
|
||||
generation_kwargs: {}
|
||||
http_client_kwargs: null
|
||||
max_retries: null
|
||||
model: gpt-5.4-nano
|
||||
organization: null
|
||||
streaming_callback: null
|
||||
timeout: null
|
||||
tools: null
|
||||
tools_strict: false
|
||||
type: haystack.components.generators.chat.openai.OpenAIChatGenerator
|
||||
confirmation_strategies: null
|
||||
exit_conditions:
|
||||
- text
|
||||
max_agent_steps: 100
|
||||
raise_on_tool_invocation_failure: false
|
||||
required_variables: null
|
||||
state_schema: {}
|
||||
streaming_callback: null
|
||||
system_prompt: You are a research specialist. Search the web to find
|
||||
information. Return a concise summary of your findings in 3-5 sentences.
|
||||
tool_invoker_kwargs: null
|
||||
tools:
|
||||
- data:
|
||||
component:
|
||||
init_parameters:
|
||||
allowed_domains: null
|
||||
api_key:
|
||||
env_vars:
|
||||
- SERPERDEV_API_KEY
|
||||
strict: true
|
||||
type: env_var
|
||||
exclude_subdomains: false
|
||||
search_params: {}
|
||||
top_k: 3
|
||||
type: haystack.components.websearch.serper_dev.SerperDevWebSearch
|
||||
description: Search the web for current information on any topic
|
||||
inputs_from_state: null
|
||||
name: web_search
|
||||
outputs_to_state: null
|
||||
outputs_to_string: null
|
||||
parameters: null
|
||||
type: haystack.tools.component_tool.ComponentTool
|
||||
user_prompt: null
|
||||
type: haystack.components.agents.agent.Agent
|
||||
description: A specialist that researches topics on the web
|
||||
inputs_from_state: null
|
||||
name: research_specialist
|
||||
outputs_to_state: null
|
||||
outputs_to_string:
|
||||
source: last_message
|
||||
parameters: null
|
||||
type: haystack.tools.component_tool.ComponentTool
|
||||
user_prompt: null
|
||||
type: haystack.components.agents.agent.Agent
|
||||
connection_type_validation: true
|
||||
connections: []
|
||||
max_runs_per_component: 100
|
||||
metadata: {}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## 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.
|
||||
Returning just `result["last_message"].text` (with `@tool`) or using `outputs_to_string` (with `ComponentTool`) 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 `max_workers` in `tool_invoker_kwargs` (default: `4`).
|
||||
|
||||
The example below asks the coordinator about two topics: it calls `research` 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 OpenAIChatGenerator
|
||||
from haystack.components.generators.utils import print_streaming_chunk
|
||||
from haystack.components.websearch import SerperDevWebSearch
|
||||
from haystack.dataclasses import ChatMessage
|
||||
from haystack.tools import ComponentTool, tool
|
||||
from haystack.utils import Secret
|
||||
|
||||
|
||||
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=OpenAIChatGenerator(model="gpt-5.4-nano"),
|
||||
tools=[search_tool, fetch_page],
|
||||
system_prompt=(
|
||||
"You are a research specialist. Search the web to find relevant pages, "
|
||||
"then fetch their full content for detailed information. "
|
||||
"Return a concise summary of your findings in 3-5 sentences."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@tool
|
||||
def research(query: Annotated[str, "The research question to investigate"]) -> str:
|
||||
"""Research a topic and return a summary of findings."""
|
||||
try:
|
||||
result = research_agent.run(messages=[ChatMessage.from_user(query)])
|
||||
return result["last_message"].text
|
||||
except Exception as e:
|
||||
return f"Research failed: {e}"
|
||||
|
||||
|
||||
coordinator = Agent(
|
||||
chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
|
||||
tools=[research],
|
||||
system_prompt=(
|
||||
"You are a coordinator. Delegate research tasks to the research tool. "
|
||||
"For questions covering multiple topics, research each one independently. "
|
||||
"Keep your final answer concise."
|
||||
),
|
||||
streaming_callback=print_streaming_chunk,
|
||||
tool_invoker_kwargs={"max_workers": 4}, # run up to 4 specialist calls in parallel
|
||||
)
|
||||
|
||||
result = coordinator.run(
|
||||
messages=[
|
||||
ChatMessage.from_user(
|
||||
"What are the latest developments in large language models and retrieval-augmented generation?",
|
||||
),
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
## Additional References
|
||||
|
||||
📖 Related docs:
|
||||
|
||||
- [Agent](../../pipeline-components/agents-1/agent.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)
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
title: "Components"
|
||||
id: components
|
||||
slug: "/components"
|
||||
description: "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."
|
||||
---
|
||||
|
||||
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:
|
||||
<ClickableImage src="/img/3a53f3e-inputs_and_outputs.png" alt="DocumentWriter component specification table showing Name, Folder Path, Position in Pipeline, Inputs (documents list), and Outputs (documents_written integer)" />
|
||||
|
||||
You can also look them up in the code in the component`run()` method. Here's an example of the inputs and outputs of `TransformerSimilarityRanker`:
|
||||
|
||||
```python
|
||||
@component.output_types(documents=List[Document]) # "documents" is the output name you need when connecting components in a pipeline
|
||||
def run(self, query: str, documents: List[Document], top_k: Optional[int] = None):# "query" and "documents" are the mandatory inputs, additionally you can also specify the optional top_k parameter
|
||||
"""
|
||||
Returns a list of Documents ranked by their similarity to the given query.
|
||||
|
||||
:param query: Query string.
|
||||
: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 their similarity to the query with the most similar Documents appearing first.
|
||||
"""
|
||||
```
|
||||
|
||||
## 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()`:
|
||||
|
||||
```python
|
||||
from haystack import Document
|
||||
from haystack.components.embedders 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.
|
||||
@@ -0,0 +1,182 @@
|
||||
---
|
||||
title: "Creating Custom Components"
|
||||
id: custom-components
|
||||
slug: "/custom-components"
|
||||
description: "Create your own components and use them standalone or in pipelines."
|
||||
---
|
||||
|
||||
# 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/)
|
||||
@@ -0,0 +1,187 @@
|
||||
---
|
||||
title: "SuperComponents"
|
||||
id: supercomponents
|
||||
slug: "/supercomponents"
|
||||
description: "`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."
|
||||
---
|
||||
|
||||
# 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.
|
||||
|
||||
```python
|
||||
# pip install haystack-ai datasets "sentence-transformers>=3.0.0"
|
||||
|
||||
from haystack import Document, Pipeline, super_component
|
||||
from haystack.components.joiners import DocumentJoiner
|
||||
from haystack.components.embedders 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=<ChatRole.ASSISTANT: 'assistant'>,
|
||||
_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)
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: "Haystack Concepts Overview"
|
||||
id: concepts-overview
|
||||
slug: "/concepts-overview"
|
||||
description: "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."
|
||||
---
|
||||
|
||||
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). There are two types of Generators: chat and non-chat:
|
||||
|
||||
- The chat ones enable chat completion and are designed for conversational contexts. It expects a list of messages to interact with the user.
|
||||
- The non-chat Generators use LLMs for simpler text generation (for example, translating or summarizing text).
|
||||
|
||||
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, tables, or binary data. 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 into a convenient format (YAML, TOML, and more) on a disk or share them around using the [serialization](pipelines/serialization.mdx) process.
|
||||
|
||||
Here is a short Haystack pipeline, illustrated:
|
||||
<ClickableImage src="/img/00f5fe8-Pipeline_Illustrations_2.png" alt="RAG architecture overview showing query flow through retrieval and generation stages, with document stores providing context for the language model" />
|
||||
@@ -0,0 +1,309 @@
|
||||
---
|
||||
title: "Data Classes"
|
||||
id: data-classes
|
||||
slug: "/data-classes"
|
||||
description: "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."
|
||||
---
|
||||
|
||||
# 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, 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.
|
||||
|
||||
### 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.
|
||||
@@ -0,0 +1,413 @@
|
||||
---
|
||||
title: "ChatMessage"
|
||||
id: chatmessage
|
||||
slug: "/chatmessage"
|
||||
description: "`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."
|
||||
---
|
||||
|
||||
# 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=<ChatRole.USER: 'user'>,
|
||||
>>> _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=<ChatRole.USER: 'user'>,
|
||||
>>> _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=<ChatRole.USER: 'user'>,
|
||||
>>> _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=<ChatRole.ASSISTANT: 'assistant'>,
|
||||
>>> _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=<ChatRole.ASSISTANT: 'assistant'>,
|
||||
>>> _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=<ChatRole.TOOL: 'tool'>,
|
||||
>>> _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)
|
||||
```
|
||||
@@ -0,0 +1,119 @@
|
||||
---
|
||||
title: "FileContent"
|
||||
id: filecontent
|
||||
slug: "/filecontent"
|
||||
description: "`FileContent` represents file payloads in chat messages, including base64 data, MIME type, filename, and provider-specific metadata."
|
||||
---
|
||||
|
||||
# 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.
|
||||
@@ -0,0 +1,127 @@
|
||||
---
|
||||
title: "Device Management"
|
||||
id: device-management
|
||||
slug: "/device-management"
|
||||
description: "This page discusses the concept of device management in the context of Haystack."
|
||||
---
|
||||
|
||||
# Device Management
|
||||
|
||||
This page discusses the concept of device management in the context of Haystack.
|
||||
|
||||
Many Haystack components, such as `HuggingFaceLocalGenerator` , `AzureOpenAIGenerator`, 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
|
||||
|
||||
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
|
||||
|
||||
device = ComponentDevice.from_single(Device.gpu(id=1))
|
||||
# Alternatively, use a PyTorch device string
|
||||
device = ComponentDevice.from_str("cuda:1")
|
||||
generator = HuggingFaceLocalGenerator(model="llama2", device=device)
|
||||
```
|
||||
|
||||
To use multiple devices, use the `ComponentDevice.from_multiple` class method:
|
||||
|
||||
```python
|
||||
from haystack.utils import ComponentDevice, Device, DeviceMap
|
||||
|
||||
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 = HuggingFaceLocalGenerator(model="llama2", 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, `HuggingFaceLocalGenerator` exposes a `huggingface_pipeline_kwargs` parameter through which Hugging Face-specific `device_map` arguments can be passed:
|
||||
|
||||
```python
|
||||
generator = HuggingFaceLocalGenerator(
|
||||
model="llama2",
|
||||
huggingface_pipeline_kwargs={"device_map": "balanced"},
|
||||
)
|
||||
```
|
||||
|
||||
In such cases, ensure that the parameter precedence and selection behavior is clearly documented. In the case of `HuggingFaceLocalGenerator`, the device map passed through the `huggingface_pipeline_kwargs` parameter overrides the explicit `device` parameter and is documented as such.
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
title: "Document Store"
|
||||
id: document-store
|
||||
slug: "/document-store"
|
||||
description: "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
|
||||
|
||||
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 three possible values:
|
||||
|
||||
- **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.
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
---
|
||||
title: "Choosing a Document Store"
|
||||
id: choosing-a-document-store
|
||||
slug: "/choosing-a-document-store"
|
||||
description: "This article goes through different types of Document Stores and explains their advantages and disadvantages."
|
||||
---
|
||||
|
||||
import ClickableImage from "@site/src/components/ClickableImage";
|
||||
|
||||
# Choosing a Document Store
|
||||
|
||||
This article goes through different types of Document Stores and explains their advantages and disadvantages.
|
||||
|
||||
### Introduction
|
||||
|
||||
Whether you are developing a chatbot, a RAG system, or an image captioner, at some point, it’ll be likely for your AI application to compare the input it gets with the information it already knows. Most of the time, this comparison is performed through vector similarity search.
|
||||
|
||||
If you’re unfamiliar with vectors, think about them as a way to represent text, images, or audio/video in a numerical form called vector embeddings. Vector databases are specifically designed to store such vectors efficiently, providing all the functionalities an AI application needs to implement data retrieval and similarity search.
|
||||
|
||||
Document Stores are special objects in Haystack that abstract all the different vector databases into a common interface that can be easily integrated into a pipeline, most commonly through a Retriever component. Normally, you will find specialized Document Store and Retriever objects for each vector database Haystack supports.
|
||||
|
||||
### Types of vector databases
|
||||
|
||||
But why are vector databases so different, and which one should you use in your Haystack pipeline?
|
||||
|
||||
We can group vector databases into five categories, from more specialized to general purpose:
|
||||
|
||||
- Vector libraries
|
||||
- Pure vector databases
|
||||
- Vector-capable SQL databases
|
||||
- Vector-capable NoSQL databases
|
||||
- Full-text search databases
|
||||
|
||||
We are working on supporting all these types in Haystack.
|
||||
|
||||
In the meantime, here’s the most recent overview of available integrations:
|
||||
<ClickableImage src="/img/2c188e9-2.0_Document_Stores_6.png" alt="Document store categories diagram showing four types: pure vector databases (Chroma, Milvus, Pinecone, Weaviate, Qdrant), full-text search databases (Elasticsearch, OpenSearch), vector-capable SQL databases (Pgvector for PostgreSQL), and vector-capable NoSQL databases (DataStax Astra, MongoDB, neo4j)" className="img-light-bg" />
|
||||
|
||||
#### Summary
|
||||
|
||||
Here is a quick summary of different Document Stores available in Haystack.
|
||||
|
||||
Continue further down the article for a more complex explanation of the strengths and disadvantages of each type.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Type | Best for |
|
||||
| Vector libraries | Managing hardware resources effectively. |
|
||||
| Pure vector DBs | Managing lots of high-dimensional data. |
|
||||
| Vector-capable SQL DBs | Lower maintenance costs with focus on structured data and less on vectors. |
|
||||
| Vector-capable NoSQL DBs | Combining vectors with structured data without the limitations of the traditional relational model. |
|
||||
| Full-text search DBs | Superior full-text search, reliable for production. |
|
||||
| In-memory | Fast, minimal prototypes on small datasets. |
|
||||
|
||||
</div>
|
||||
|
||||
#### Vector libraries
|
||||
|
||||
Vector libraries are often included in the “vector database” category improperly, as they are limited to handling only vectors, are designed to work in-memory, and normally don’t have a clean way to store data on disk. Still, they are the way to go every time performance and speed are the top requirements for your AI application, as these libraries can use hardware resources very effectively.
|
||||
|
||||
:::warning[In progress]
|
||||
|
||||
We are currently developing the support for vector libraries in Haystack.
|
||||
:::
|
||||
|
||||
#### Pure vector databases
|
||||
|
||||
Pure vector databases, also known as just “vector databases”, offer efficient similarity search capabilities through advanced indexing techniques. Most of them support metadata, and despite a recent trend to add more text-search features on top of it, you should consider pure vector databases closer to vector libraries than a regular database. Pick a pure vector database when your application needs to manage huge amounts of high-dimensional data effectively: they are designed to be highly scalable and highly available. Most are open source, but companies usually provide them “as a service” through paid subscriptions.
|
||||
|
||||
- [Chroma](../../document-stores/chromadocumentstore.mdx)
|
||||
- [Pinecone](../../document-stores/pinecone-document-store.mdx)
|
||||
- [Qdrant](../../document-stores/qdrant-document-store.mdx)
|
||||
- [Weaviate](../../document-stores/weaviatedocumentstore.mdx)
|
||||
- [Milvus](https://haystack.deepset.ai/integrations/milvus-document-store) (external integration)
|
||||
|
||||
#### Vector-capable SQL databases
|
||||
|
||||
This category is relatively small but growing fast and includes well-known relational databases where vector capabilities were added through plugins or extensions. They are not as performant as the previous categories, but the main advantage of these databases is the opportunity to easily combine vectors with structured data, having a one-stop data shop for your application. You should pick a vector-capable SQL database when the performance trade-off is paid off by the lower cost of maintaining a single database instance for your application or when the structured data plays a more fundamental role in your business logic, with vectors being more of a nice-to-have.
|
||||
|
||||
- [Pgvector](../../document-stores/pgvectordocumentstore.mdx)
|
||||
|
||||
#### Vector-capable NoSQL databases
|
||||
|
||||
Historically, the killer features of NoSQL databases were the ability to scale horizontally and the adoption of a flexible data model to overcome certain limitations of the traditional relational model. This stays true for databases in this category, where the vector capabilities are added on top of the existing features. Similarly to the previous category, vector support might not be as good as pure vector databases, but once again, there is a tradeoff that might be convenient to bear depending on the use case. For example, if a certain NoSQL database is already part of the stack of your application and a lower performance is not a show-stopper, you might give it a shot.
|
||||
|
||||
- [Astra](../../document-stores/astradocumentstore.mdx)
|
||||
- [MongoDB](../../document-stores/mongodbatlasdocumentstore.mdx)
|
||||
- [Neo4j](https://haystack.deepset.ai/integrations/neo4j-document-store) (external)
|
||||
|
||||
#### Full-text search databases
|
||||
|
||||
The main advantage of full-text search databases is they are already designed to work with text, so you can expect a high level of support for text data along with good performance and the opportunity to scale both horizontally and vertically. Initially, vector capabilities were subpar and provided through plugins or extensions, but this is rapidly changing. You can see how the market leaders in this category have recently added first-class support for vectors. Pick a full-text search database if text data plays a central role in your business logic so that you can easily and effectively implement techniques like hybrid search with a good level of support for similarity search and state-of-the-art support for full-text search.
|
||||
|
||||
- [Elasticsearch](../../document-stores/elasticsearch-document-store.mdx)
|
||||
- [OpenSearch](../../document-stores/opensearch-document-store.mdx)
|
||||
|
||||
#### 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.
|
||||
|
||||
- [InMemory](../../document-stores/inmemorydocumentstore.mdx)
|
||||
|
||||
### Final considerations
|
||||
|
||||
It can be very challenging to pick one vector database 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.
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
---
|
||||
title: "Creating Custom Document Stores"
|
||||
id: creating-custom-document-stores
|
||||
slug: "/creating-custom-document-stores"
|
||||
description: "Create your own Document Stores to manage your documents."
|
||||
---
|
||||
|
||||
# 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 `<TECHNOLOGY>-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.
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: "Experimental Package"
|
||||
id: experimental-package
|
||||
slug: "/experimental-package"
|
||||
description: "Try out new experimental features with Haystack."
|
||||
---
|
||||
|
||||
# Experimental Package
|
||||
|
||||
Try out new experimental features with Haystack.
|
||||
|
||||
The `haystack-experimental` package allows you to test new experimental features without committing to their official release. Its main goal is to gather user feedback and iterate on new features quickly.
|
||||
|
||||
Check out the `haystack-experimental` [GitHub repository](https://github.com/deepset-ai/haystack-experimental) for the latest catalog of available features, or take a look at our [Experiments API Reference](/reference).
|
||||
|
||||
### Installation
|
||||
|
||||
For simplicity, every release of `haystack-experimental` includes all the available experiments at that time. To install the latest features, run:
|
||||
|
||||
```shell
|
||||
pip install -U haystack-experimental
|
||||
```
|
||||
|
||||
:::info
|
||||
The latest version of the experimental package is only tested against the latest version of Haystack. Compatibility with older versions of Haystack is not guaranteed.
|
||||
:::
|
||||
|
||||
### Lifecycle
|
||||
|
||||
Each experimental feature has a default lifespan of 3 months starting from the date of the first non-pre-release build that includes it. Once it reaches the end of its lifespan, we will remove it from `haystack-experimental` and either:
|
||||
|
||||
- Merge the feature into Haystack and publish it with the next minor release,
|
||||
- Release the feature as an integration, or
|
||||
- Drop the feature.
|
||||
|
||||
### Usage
|
||||
|
||||
You can import the experimental new features like any other Haystack integration package:
|
||||
|
||||
```python
|
||||
from haystack.dataclasses import ChatMessage
|
||||
from haystack_experimental.components.generators import FoobarGenerator
|
||||
|
||||
c = FoobarGenerator()
|
||||
c.run([ChatMessage.from_user("What's an experiment? Be brief.")])
|
||||
```
|
||||
|
||||
Experiments can also override existing Haystack features. For example, you can opt into an experimental type of `Pipeline` by changing the usual import:
|
||||
|
||||
```python
|
||||
# from haystack import Pipeline
|
||||
from haystack_experimental import Pipeline
|
||||
|
||||
pipe = Pipeline()
|
||||
# ...
|
||||
pipe.run(...)
|
||||
```
|
||||
|
||||
## Additional References
|
||||
|
||||
🧑🍳 Cookbooks:
|
||||
|
||||
- [Improving Retrieval with Auto-Merging and Hierarchical Document Retrieval](https://haystack.deepset.ai/cookbook/auto_merging_retriever)
|
||||
- [Invoking APIs with OpenAPITool](https://haystack.deepset.ai/cookbook/openapitool)
|
||||
- [Conversational RAG using Memory](https://haystack.deepset.ai/cookbook/conversational_rag_using_memory)
|
||||
- [Define & Run Tools](https://haystack.deepset.ai/cookbook/tools_support)
|
||||
- [Newsletter Sending Agent with Experimental Haystack Tools](https://haystack.deepset.ai/cookbook/newsletter-agent)
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
title: "Introduction to Integrations"
|
||||
id: integrations
|
||||
slug: "/integrations"
|
||||
description: "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."
|
||||
---
|
||||
|
||||
# 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
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: "Jinja Templates"
|
||||
id: jinja-templates
|
||||
slug: "/jinja-templates"
|
||||
description: "Learn how Jinja templates work with Haystack components."
|
||||
---
|
||||
|
||||
# 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`.
|
||||
@@ -0,0 +1,155 @@
|
||||
---
|
||||
title: "Metadata Filtering"
|
||||
id: metadata-filtering
|
||||
slug: "/metadata-filtering"
|
||||
description: "This page provides a detailed explanation of how to apply metadata filters at query time."
|
||||
---
|
||||
|
||||
# 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)
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
title: "Pipelines"
|
||||
id: pipelines
|
||||
slug: "/pipelines"
|
||||
description: "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."
|
||||
---
|
||||
|
||||
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.
|
||||
<ClickableImage src="/img/83f686b-Pipeline_Illustrations_1_1.png" alt="Pipeline architecture diagram showing components arranged in parallel branches that converge into a single pipeline flow" size="large" />
|
||||
|
||||
### 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.
|
||||
|
||||
<ClickableImage src="/img/2390eea-Pipeline_Illustrations_1_2.png" alt="Pipeline architecture diagram illustrating a feedback loop where output from later components loops back to earlier components" size="large" />
|
||||
|
||||
### Async Pipelines
|
||||
|
||||
The AsyncPipeline enables parallel execution of Haystack components when their dependencies allow it. This improves performance in complex pipelines with independent operations. For example, it 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. Through concurrent execution, the AsyncPipeline significantly reduces total processing time compared to sequential execution.
|
||||
|
||||
Find out more in our [AsyncPipeline](pipelines/asyncpipeline.mdx) documentation.
|
||||
|
||||
## 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.
|
||||
|
||||
<YoutubeEmbed videoId="SxAwyeCkguc" title="Introduction to Haystack Pipelines" />
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,158 @@
|
||||
---
|
||||
title: "AsyncPipeline"
|
||||
id: asyncpipeline
|
||||
slug: "/asyncpipeline"
|
||||
description: "Use AsyncPipeline to run multiple Haystack components at the same time for faster processing."
|
||||
---
|
||||
|
||||
# AsyncPipeline
|
||||
|
||||
Use AsyncPipeline to run multiple Haystack components at the same time for faster processing.
|
||||
|
||||
The `AsyncPipeline` in Haystack introduces asynchronous execution capabilities, enabling concurrent component execution when dependencies allow. This optimizes performance, particularly in complex pipelines where multiple independent components can run in parallel.
|
||||
|
||||
The `AsyncPipeline` provides significant performance improvements in scenarios such as:
|
||||
|
||||
- Hybrid retrieval pipelines, where multiple Retrievers can run in parallel,
|
||||
- Multiple LLM calls that can be executed concurrently,
|
||||
- Complex pipelines with independent branches of execution,
|
||||
- I/O-bound operations that benefit from asynchronous execution.
|
||||
|
||||
## Key Features
|
||||
|
||||
### Concurrent Execution
|
||||
|
||||
The `AsyncPipeline` schedules components based on input readiness and dependency resolution, ensuring efficient parallel execution when possible. For example, in a hybrid retrieval scenario, multiple Retrievers can run simultaneously if they do not depend on each other.
|
||||
|
||||
### Execution Methods
|
||||
|
||||
The `AsyncPipeline` offers three ways to run your pipeline:
|
||||
|
||||
#### Synchronous Run (`run`)
|
||||
|
||||
Executes the pipeline synchronously with the provided input data. This method is blocking, making it suitable for environments where asynchronous execution is not possible or desired. Although components execute concurrently internally, the method blocks until the pipeline completes.
|
||||
|
||||
#### Asynchronous Run (`run_async`)
|
||||
|
||||
Executes the pipeline in an asynchronous manner, allowing non-blocking execution. This method is ideal when integrating the pipeline into an async workflow, enabling smooth operation within larger async applications or services.
|
||||
|
||||
#### Asynchronous Generator (`run_async_generator`)
|
||||
|
||||
Allows step-by-step execution by yielding partial outputs as components complete their tasks. This is particularly useful for monitoring progress, debugging, and handling outputs incrementally. It differs from `run_async`, which executes the pipeline in a single async call.
|
||||
|
||||
In an `AsyncPipeline`, components such as A and B will run in parallel _only if they have no shared dependencies_ and can process inputs independently.
|
||||
|
||||
### Concurrency Control
|
||||
|
||||
You can control the maximum number of components that run simultaneously using the `concurrency_limit` parameter to ensure controlled resource usage.
|
||||
|
||||
You can find more details in our [API Reference](/reference/pipeline-api#asyncpipeline), or directly in the pipeline's [GitHub code](https://github.com/deepset-ai/haystack/blob/main/haystack/core/pipeline/async_pipeline.py).
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from haystack import AsyncPipeline, Document
|
||||
from haystack.components.builders import ChatPromptBuilder
|
||||
from haystack.components.embedders import (
|
||||
SentenceTransformersDocumentEmbedder,
|
||||
SentenceTransformersTextEmbedder,
|
||||
)
|
||||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||||
from haystack.components.joiners import DocumentJoiner
|
||||
from haystack.components.retrievers import (
|
||||
InMemoryBM25Retriever,
|
||||
InMemoryEmbeddingRetriever,
|
||||
)
|
||||
from haystack.dataclasses import ChatMessage
|
||||
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
||||
|
||||
documents = [
|
||||
Document(content="Khufu is the largest pyramid."),
|
||||
Document(content="Khafre is the middle pyramid."),
|
||||
Document(content="Menkaure is the smallest pyramid."),
|
||||
]
|
||||
|
||||
docs_embedder = SentenceTransformersDocumentEmbedder()
|
||||
docs_embedder.warm_up()
|
||||
|
||||
document_store = InMemoryDocumentStore()
|
||||
document_store.write_documents(docs_embedder.run(documents=documents)["documents"])
|
||||
|
||||
prompt_template = [
|
||||
ChatMessage.from_system(
|
||||
"""
|
||||
You are a precise, factual QA assistant.
|
||||
According to the following documents:
|
||||
{% for document in documents %}
|
||||
{{document.content}}
|
||||
{% endfor %}
|
||||
|
||||
If an answer cannot be deduced from the documents, say "I don't know based on these documents".
|
||||
|
||||
When answering:
|
||||
- be concise
|
||||
- list the documents that support your answer
|
||||
|
||||
Answer the given question.
|
||||
""",
|
||||
),
|
||||
ChatMessage.from_user("{{query}}"),
|
||||
ChatMessage.from_system("Answer:"),
|
||||
]
|
||||
|
||||
hybrid_rag_retrieval = AsyncPipeline()
|
||||
hybrid_rag_retrieval.add_component("text_embedder", SentenceTransformersTextEmbedder())
|
||||
hybrid_rag_retrieval.add_component(
|
||||
"embedding_retriever",
|
||||
InMemoryEmbeddingRetriever(document_store=document_store, top_k=3),
|
||||
)
|
||||
hybrid_rag_retrieval.add_component(
|
||||
"bm25_retriever",
|
||||
InMemoryBM25Retriever(document_store=document_store, top_k=3),
|
||||
)
|
||||
hybrid_rag_retrieval.add_component("document_joiner", DocumentJoiner())
|
||||
hybrid_rag_retrieval.add_component(
|
||||
"prompt_builder",
|
||||
ChatPromptBuilder(template=prompt_template),
|
||||
)
|
||||
hybrid_rag_retrieval.add_component("llm", OpenAIChatGenerator())
|
||||
|
||||
hybrid_rag_retrieval.connect(
|
||||
"text_embedder.embedding",
|
||||
"embedding_retriever.query_embedding",
|
||||
)
|
||||
hybrid_rag_retrieval.connect("bm25_retriever.documents", "document_joiner.documents")
|
||||
hybrid_rag_retrieval.connect(
|
||||
"embedding_retriever.documents",
|
||||
"document_joiner.documents",
|
||||
)
|
||||
hybrid_rag_retrieval.connect("document_joiner.documents", "prompt_builder.documents")
|
||||
hybrid_rag_retrieval.connect("prompt_builder.prompt", "llm.messages")
|
||||
|
||||
question = "Which pyramid is neither the smallest nor the biggest?"
|
||||
|
||||
data = {
|
||||
"prompt_builder": {"query": question},
|
||||
"text_embedder": {"text": question},
|
||||
"bm25_retriever": {"query": question},
|
||||
}
|
||||
|
||||
|
||||
async def process_results():
|
||||
async for partial_output in hybrid_rag_retrieval.run_async_generator(
|
||||
data=data,
|
||||
include_outputs_from={"document_joiner", "llm"},
|
||||
):
|
||||
if "document_joiner" in partial_output:
|
||||
print(
|
||||
"Retrieved documents:",
|
||||
len(partial_output["document_joiner"]["documents"]),
|
||||
)
|
||||
if "llm" in partial_output:
|
||||
print("Generated answer:", partial_output["llm"]["replies"][0])
|
||||
|
||||
|
||||
asyncio.run(process_results())
|
||||
```
|
||||
@@ -0,0 +1,250 @@
|
||||
---
|
||||
title: "Creating Pipelines"
|
||||
id: creating-pipelines
|
||||
slug: "/creating-pipelines"
|
||||
description: "Learn the general principles of creating a pipeline."
|
||||
---
|
||||
|
||||
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:
|
||||
|
||||
```python
|
||||
from haystack import Document, Pipeline
|
||||
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
||||
from haystack.components.embedders 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:
|
||||
|
||||
<br />
|
||||
<ClickableImage src="/img/vizualised-rag-pipeline.png" alt="RAG pipeline diagram with three connected components: InMemoryBM25Retriever receives a query string and outputs documents, ChatPromptBuilder combines the documents with a question input to create prompt messages, and OpenAIChatGenerator processes the messages to produce replies. Each component box displays its class name and optional input parameters." size="large" />
|
||||
@@ -0,0 +1,125 @@
|
||||
---
|
||||
title: "Debugging Pipelines"
|
||||
id: debugging-pipelines
|
||||
slug: "/debugging-pipelines"
|
||||
description: "Learn how to debug and troubleshoot your Haystack 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:
|
||||
<ClickableImage src="/img/55c3d5c84282d726c95fb3350ec36be49a354edca8a6164f5dffdab7121cec58-image_2.png" alt="Console output showing Haystack pipeline execution with DEBUG level tracing logs including component names, types, and input/output specifications" />
|
||||
|
||||
## Tracing
|
||||
|
||||
To get a bigger picture of the pipeline’s performance, try tracing it with [Langfuse](../../development/tracing.mdx#langfuse).
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,214 @@
|
||||
---
|
||||
title: "Pipeline Breakpoints"
|
||||
id: pipeline-breakpoints
|
||||
slug: "/pipeline-breakpoints"
|
||||
description: "Learn how to pause and resume Haystack pipeline or Agent execution using breakpoints to debug, inspect, and continue workflows from saved snapshots."
|
||||
---
|
||||
|
||||
# Pipeline Breakpoints
|
||||
|
||||
Learn how to pause and resume Haystack pipeline or Agent 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.
|
||||
|
||||
You can also set breakpoints on an Agent, specifically on the `ChatGenerator` component or on any of the `Tool` specified in the `ToolInvoker` component .
|
||||
|
||||
## 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()` or `Agent.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. The same parameter is available on `Agent.run()` for agent breakpoints.
|
||||
|
||||
## 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"])
|
||||
```
|
||||
|
||||
## Setting a Breakpoint on an Agent
|
||||
|
||||
You can also set breakpoints in an Agent component. An Agent supports two types of breakpoints:
|
||||
|
||||
1. **Chat Generator Breakpoint**: Pauses before LLM calls.
|
||||
2. **Tool Invoker Breakpoint**: Pauses before any tool execution.
|
||||
|
||||
A `ChatGenerator` breakpoint is defined as shown below. You need to define a `Breakpoint` as for a pipeline breakpoint and then an `AgentBreakpoint` where you pass the breakpoint defined before and the name of Agent component.
|
||||
|
||||
```python
|
||||
from haystack.dataclasses.breakpoints import AgentBreakpoint, Breakpoint, ToolBreakpoint
|
||||
|
||||
# Break at chat generator (LLM calls)
|
||||
chat_bp = Breakpoint(component_name="chat_generator", visit_count=0)
|
||||
agent_breakpoint = AgentBreakpoint(break_point=chat_bp, agent_name="my_agent")
|
||||
```
|
||||
|
||||
To set a breakpoint on a Tool in an Agent, do the following:
|
||||
|
||||
First, define a `ToolBreakpoint` specifying the `ToolInvoker` component whose name is `tool_invoker` and then the tool associated with the breakpoint, in this case – a `weather_tool` .
|
||||
|
||||
Then, define an `AgentBreakpoint` passing the `ToolBreakpoint` defined before as the breakpoint.
|
||||
|
||||
```python
|
||||
from haystack.dataclasses.breakpoints import AgentBreakpoint, Breakpoint, ToolBreakpoint
|
||||
|
||||
# Break at tool invoker (tool calls)
|
||||
tool_bp = ToolBreakpoint(
|
||||
component_name="tool_invoker",
|
||||
visit_count=0,
|
||||
tool_name="weather_tool", # Specific tool, or None for any tool
|
||||
)
|
||||
agent_breakpoint = AgentBreakpoint(break_point=tool_bp, agent_name="my_agent")
|
||||
```
|
||||
|
||||
### Resuming Agent Execution
|
||||
|
||||
When an Agent breakpoint is triggered, you can resume execution using the saved snapshot. Similar to the regular component in a pipeline, pass the JSON file with the snapshot to the `run()` method of the pipeline.
|
||||
|
||||
```python
|
||||
from haystack.core.pipeline.breakpoint import load_pipeline_snapshot
|
||||
|
||||
# Load the snapshot
|
||||
snapshot_file = "./agent_debug/agent_chat_generator_2025_07_11_23_23.json"
|
||||
snapshot = load_pipeline_snapshot(snapshot_file)
|
||||
|
||||
# Resume pipeline execution
|
||||
result = pipeline.run(data={}, pipeline_snapshot=snapshot)
|
||||
print("Pipeline resumed successfully")
|
||||
print(f"Final result: {result}")
|
||||
```
|
||||
|
||||
## 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)
|
||||
```
|
||||
@@ -0,0 +1,253 @@
|
||||
---
|
||||
title: "Pipeline Loops"
|
||||
id: pipeline-loops
|
||||
slug: "/pipeline-loops"
|
||||
description: "Understand how loops work in Haystack pipelines, how they terminate, and how to use them safely for feedback and self-correction."
|
||||
---
|
||||
|
||||
# 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` (or `AsyncPipeline`) 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.
|
||||
@@ -0,0 +1,233 @@
|
||||
---
|
||||
title: "Serializing Pipelines"
|
||||
id: serialization
|
||||
slug: "/serialization"
|
||||
description: "Save your pipelines into a custom format and explore the serialization options."
|
||||
---
|
||||
|
||||
# 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. Therefore, loading a serialized pipeline or component assumes that the serialized definition originates from a trusted source and has been reviewed by the user.
|
||||
|
||||
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),
|
||||
)
|
||||
```
|
||||
|
||||
## 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)
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
---
|
||||
title: "Smart Pipeline Connections"
|
||||
id: smart-pipeline-connections
|
||||
slug: "/smart-pipeline-connections"
|
||||
description: "Learn how Haystack pipelines simplify connections through implicit joining and flexible type adaptation, reducing the need for glue components."
|
||||
---
|
||||
|
||||
# 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`:
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Expand to see the pipeline graph</summary>
|
||||
<ClickableImage src="/img/pipeline-illustration-auto-joiner.png" alt="Pipeline architecture diagram showing a DocumentWriter receiving inputs from multiple converters without a Joiner" size="large" />
|
||||
|
||||
</details>
|
||||
|
||||
```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="<html><body>Some content</body></html>",
|
||||
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.
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: "Visualizing Haystack Pipelines"
|
||||
id: visualizing-pipelines
|
||||
slug: "/visualizing-pipelines"
|
||||
description: "You can visualize your Haystack AI pipelines as graphs to better understand how the components are connected."
|
||||
---
|
||||
|
||||
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:
|
||||
<ClickableImage src="/img/46a8989-Untitled.png" alt="RAG pipeline flowchart showing the data flow from query through retriever, prompt builder, language model, and answer builder components" size="large" />
|
||||
|
||||
<br />
|
||||
|
||||
## 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).
|
||||
@@ -0,0 +1,202 @@
|
||||
---
|
||||
title: "Secret Management"
|
||||
id: secret-management
|
||||
slug: "/secret-management"
|
||||
description: "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."
|
||||
---
|
||||
|
||||
# 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).
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Example Use Case - Problem Statement</summary>
|
||||
|
||||
### 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.
|
||||
|
||||
</details>
|
||||
|
||||
### 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
|
||||
```
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: "Deployment"
|
||||
id: deployment
|
||||
slug: "/deployment"
|
||||
description: "Deploy your Haystack pipelines through various services such as Docker, Kubernetes, Ray, or a variety of Serverless options."
|
||||
---
|
||||
|
||||
# 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).
|
||||
:::
|
||||
@@ -0,0 +1,117 @@
|
||||
---
|
||||
title: "Docker"
|
||||
id: docker
|
||||
slug: "/docker"
|
||||
description: "Learn how to deploy your Haystack pipelines through Docker starting from the basic Docker container to a complex application using Hayhooks."
|
||||
---
|
||||
|
||||
# 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 `2.12.1`, you can run the command:
|
||||
|
||||
```shell
|
||||
docker pull deepset/haystack:base-v2.12.1
|
||||
```
|
||||
|
||||
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-v2.12.1 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-v2.12.1
|
||||
|
||||
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.16.0
|
||||
|
||||
RUN pip install qdrant-haystack sentence-transformers
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,269 @@
|
||||
---
|
||||
title: "Kubernetes"
|
||||
id: kubernetes
|
||||
slug: "/kubernetes"
|
||||
description: "Learn how to deploy your Haystack pipelines through 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.16.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`.
|
||||
<ClickableImage src="/img/6eb9fb0c7b00367bfbe8182ffc7c3746f3f3d03b720e963df045e28160362d7f-Screenshot_2025-04-15_at_16.15.28.png" alt="Kubernetes Lens interface showing the hayhooks Pod running in the default namespace with status Running" />
|
||||
|
||||
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.16.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.
|
||||
<ClickableImage src="/img/2dbf42dd2db1cb355ee7222d7f8e96c45b611200d83ca289be3456264a854c38-Screenshot_2025-04-16_at_09.19.14.png" alt="Kubernetes Lens interface displaying pod logs with application startup messages and deployed pipeline confirmation" />
|
||||
|
||||
## 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.16.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.
|
||||
|
||||
<ClickableImage src="/img/f3f0ac4b22a37039f0837c22b0cb8b640937bbb0db4acfcbdf7bd016b545d84a-Screenshot_2025-04-16_at_09.32.07.png" alt="Kubernetes Lens interface showing three haystack-deployment pods in Running status with their resource configurations" />
|
||||
|
||||
Note that the `NodePort` you created before will now act as a load balancer and will distribute incoming requests to the three Hayhooks Pods.
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: "OpenShift"
|
||||
id: openshift
|
||||
slug: "/openshift"
|
||||
description: "Learn how to deploy your applications running Haystack pipelines using 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=<your-token> --server=https://<your-server-url>: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.16.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 <your-project-name> on server https://<your-server-url>: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
|
||||
```
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: "Enabling GPU Acceleration"
|
||||
id: enabling-gpu-acceleration
|
||||
slug: "/enabling-gpu-acceleration"
|
||||
description: "Speed up your Haystack application by engaging the GPU."
|
||||
---
|
||||
|
||||
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 `HuggingFaceLocalGenerator`, 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:
|
||||
<ClickableImage src="/img/b44c7f4-gpu_enabled_cropped.png" alt="A screenshot of the command output with the name of the GPU device and its memory usage highlighted." />
|
||||
|
||||
### Enabling the GPU in Colab
|
||||
|
||||
1. In your Colab environment, select **Runtime>Change Runtime type**.
|
||||
<ClickableImage src="/img/85079c7-68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f646565707365742d61692f686179737461636b2f6d61696e2f646f63732f696d672f636f6c61625f6770755f72756e74696d652e6a7067.jpeg" alt="Google Colab Runtime menu with Change runtime type option highlighted for selecting GPU acceleration" size="large" />
|
||||
|
||||
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.
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: "External Integrations"
|
||||
id: external-integrations-development
|
||||
slug: "/external-integrations-development"
|
||||
description: "External integrations that enable tracing, monitoring, and deploying your pipelines."
|
||||
---
|
||||
|
||||
# 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. |
|
||||
@@ -0,0 +1,196 @@
|
||||
---
|
||||
title: "Hayhooks"
|
||||
id: hayhooks
|
||||
slug: "/hayhooks"
|
||||
description: "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."
|
||||
---
|
||||
|
||||
# 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 <pipeline_name> --param 'question="Is this recipe vegan?"'
|
||||
```
|
||||
|
||||
You can also upload files when running a pipeline:
|
||||
|
||||
```shell
|
||||
hayhooks pipeline run <pipeline_name> --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 using Server-Sent Events (SSE) as the transport method. Agents are deployed using the same `PipelineWrapper` mechanism as pipelines.
|
||||
|
||||
To start the Hayhooks MCP server, run:
|
||||
|
||||
```shell
|
||||
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)
|
||||
```
|
||||
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: "Logging"
|
||||
id: logging
|
||||
slug: "/logging"
|
||||
description: "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."
|
||||
---
|
||||
|
||||
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:
|
||||
<ClickableImage src="/img/55c3d5c84282d726c95fb3350ec36be49a354edca8a6164f5dffdab7121cec58-image_2.png" alt="Console output showing Haystack pipeline execution with DEBUG level tracing logs including component names, types, and input/output specifications" />
|
||||
|
||||
## 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 will automatically switch to structlog for logging.
|
||||
|
||||
### 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:
|
||||
<ClickableImage src="/img/e49a1f2-Screenshot_2024-02-27_at_16.13.51.png" alt="Python code snippet demonstrating basic logging setup with getLogger and a warning level log message 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)
|
||||
```
|
||||
<ClickableImage src="/img/bff93d4-Screenshot_2024-02-27_at_16.15.35.png" alt="Python code snippet showing structured JSON logging configuration with example JSON formatted log output including event, level, and timestamp fields" />
|
||||
|
||||
### Disabling Structured Logging
|
||||
|
||||
To disable structured logging despite an existing installation of structlog, set the environment variable `HAYSTACK_LOGGING_IGNORE_STRUCTLOG_ENV_VAR` to `true` when running Haystack.
|
||||
@@ -0,0 +1,358 @@
|
||||
---
|
||||
title: "Tracing"
|
||||
id: tracing
|
||||
slug: "/tracing"
|
||||
description: "This page explains how to use tracing in Haystack. It describes how to set up a tracing backend with OpenTelemetry, Datadog, or your own solution. This can help you monitor your app's performance and optimize it."
|
||||
---
|
||||
|
||||
import ClickableImage from "@site/src/components/ClickableImage";
|
||||
|
||||
# Tracing
|
||||
|
||||
This page explains how to use tracing in Haystack. It describes how to set up a tracing backend with OpenTelemetry, Datadog, or your own solution. This can help you monitor your app's performance and optimize it.
|
||||
|
||||
Traces document the flow of requests through your application and are vital for monitoring applications in production. This helps to understand the execution order of your pipeline components and analyze where your pipeline spends the most time.
|
||||
|
||||
## Configuring a Tracing Backend
|
||||
|
||||
Instrumented applications typically send traces to a trace collector or a tracing backend. Haystack provides out-of-the-box support for [OpenTelemetry](https://opentelemetry.io/) and [Datadog](https://app.datadoghq.eu/dashboard/lists). You can also quickly implement support for additional providers of your choosing.
|
||||
|
||||
### OpenTelemetry
|
||||
|
||||
To use OpenTelemetry as your tracing backend, follow these steps:
|
||||
|
||||
1. Install the [OpenTelemetry SDK](https://opentelemetry.io/docs/languages/python/):
|
||||
|
||||
```shell
|
||||
pip install opentelemetry-sdk
|
||||
pip install opentelemetry-exporter-otlp
|
||||
```
|
||||
2. 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.
|
||||
3. There are two options for how to hook Haystack to the OpenTelemetry SDK.
|
||||
|
||||
- Run your Haystack applications using OpenTelemetry’s [automated instrumentation](https://opentelemetry.io/docs/languages/python/getting-started/#instrumentation). Haystack will automatically detect the configured tracing backend and use it to send traces.
|
||||
|
||||
First, install the `OpenTelemetry` CLI:
|
||||
|
||||
```shell
|
||||
pip install opentelemetry-distro
|
||||
```
|
||||
|
||||
Then, run your Haystack application using the OpenTelemetry SDK:
|
||||
|
||||
```shell
|
||||
opentelemetry-instrument \
|
||||
--traces_exporter console \
|
||||
--metrics_exporter console \
|
||||
--logs_exporter console \
|
||||
--service_name my-haystack-app \
|
||||
<command to run your Haystack pipeline>
|
||||
```
|
||||
|
||||
— or —
|
||||
|
||||
- Configure the tracing backend in your Python code:
|
||||
|
||||
```python
|
||||
from haystack import tracing
|
||||
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
||||
from opentelemetry.sdk.trace import TracerProvider
|
||||
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
||||
from opentelemetry.sdk.resources import Resource
|
||||
from opentelemetry.semconv.resource import ResourceAttributes
|
||||
|
||||
# Service name is required for most backends
|
||||
resource = Resource(attributes={
|
||||
ResourceAttributes.SERVICE_NAME: "haystack" # Correct constant
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
# Tell Haystack to auto-detect the configured tracer
|
||||
import haystack.tracing
|
||||
haystack.tracing.auto_enable_tracing()
|
||||
|
||||
# Explicitly tell Haystack to use your tracer
|
||||
from haystack.tracing import OpenTelemetryTracer
|
||||
|
||||
tracer = tracer_provider.get_tracer("my_application")
|
||||
tracing.enable_tracing(OpenTelemetryTracer(tracer))
|
||||
```
|
||||
|
||||
### Datadog
|
||||
|
||||
To use Datadog as your tracing backend, follow these steps:
|
||||
|
||||
1. Install [Datadog’s tracing library ddtrace](https://ddtrace.readthedocs.io/en/stable/#).
|
||||
|
||||
```shell
|
||||
pip install ddtrace
|
||||
```
|
||||
2. There are two options for how to hook Haystack to ddtrace.
|
||||
|
||||
- Run your Haystack application using the `ddtrace`:
|
||||
```shell
|
||||
ddtrace <command to run your Haystack pipeline
|
||||
```
|
||||
|
||||
— or —
|
||||
|
||||
- Configure the Datadog tracing backend in your Python code:
|
||||
|
||||
```python
|
||||
from haystack.tracing import DatadogTracer
|
||||
from haystack import tracing
|
||||
import ddtrace
|
||||
|
||||
tracer = ddtrace.tracer
|
||||
tracing.enable_tracing(DatadogTracer(tracer))
|
||||
```
|
||||
|
||||
### Langfuse
|
||||
|
||||
`LangfuseConnector` component allows you to easily trace your Haystack pipelines with the Langfuse UI.
|
||||
|
||||
Simply install the component with `pip install langfuse-haystack`, then add it to your pipeline.
|
||||
|
||||
:::info
|
||||
Check out the component's [documentation page](../pipeline-components/connectors/langfuseconnector.mdx) for more details and example usage, or our [blog post](https://haystack.deepset.ai/blog/langfuse-integration) for the complete walkthrough.
|
||||
:::
|
||||
<ClickableImage src="/img/11cec4f-langfuse-generation-span.png" alt="Langfuse trace detail view showing generation span with input prompt, output, metadata, latency, and cost information for a language model call" />
|
||||
|
||||
### MLflow
|
||||
|
||||
[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. Simply install MLflow and enable automatic tracing with a single line of code.
|
||||
|
||||
```shell
|
||||
pip install mlflow
|
||||
```
|
||||
|
||||
```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.
|
||||
:::
|
||||
|
||||
### Weights & Biases Weave
|
||||
|
||||
The `WeaveConnector` component allows you to trace and visualize your pipeline execution in [Weights & Biases](https://wandb.ai/site/) framework.
|
||||
|
||||
You will first need to create a free account on Weights & Biases website and get your API key, as well as install the integration with `pip install weights_biases-haystack`.
|
||||
|
||||
:::info
|
||||
Check out the component's [documentation page](../pipeline-components/connectors/weaveconnector.mdx) for more details and example usage.
|
||||
:::
|
||||
|
||||
### Custom Tracing Backend
|
||||
|
||||
To use your custom tracing backend with Haystack, follow these steps:
|
||||
|
||||
1. Implement the `Tracer` interface. The following code snippet provides an example using the OpenTelemetry package:
|
||||
|
||||
```python
|
||||
import contextlib
|
||||
from typing import Optional, Dict, Any, Iterator
|
||||
|
||||
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: Optional[Dict[str, Any]] = 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) -> Optional[Span]:
|
||||
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)
|
||||
```
|
||||
|
||||
## Disabling Auto Tracing
|
||||
|
||||
Haystack automatically detects and enables tracing under the following circumstances:
|
||||
|
||||
- If `opentelemetry-sdk` is installed and configured for OpenTelemetry.
|
||||
- If `ddtrace` is installed for Datadog.
|
||||
|
||||
To disable this behavior, there are two options:
|
||||
|
||||
- Set the environment variable `HAYSTACK_AUTO_TRACE_ENABLED` to `false` when running your Haystack application
|
||||
|
||||
— or —
|
||||
|
||||
- Disable tracing in Python:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## 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.
|
||||
<ClickableImage src="/img/dd906d7-Screenshot_2024-02-22_at_16.51.01.png" alt="Jaeger UI trace timeline displaying haystack pipeline execution with component spans showing duration and nesting of operations" />
|
||||
|
||||
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 OpenTelemetry SDK:
|
||||
|
||||
```shell
|
||||
pip install opentelemetry-sdk
|
||||
pip install opentelemetry-exporter-otlp
|
||||
```
|
||||
3. Configure `OpenTelemetry` to use the Jaeger backend:
|
||||
|
||||
```python
|
||||
from opentelemetry.sdk.resources import Resource
|
||||
from opentelemetry.semconv.resource import ResourceAttributes
|
||||
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
||||
from opentelemetry.sdk.trace import TracerProvider
|
||||
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
||||
|
||||
# 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)
|
||||
```
|
||||
4. Tell Haystack to use OpenTelemetry for tracing:
|
||||
|
||||
```python
|
||||
import haystack.tracing
|
||||
|
||||
haystack.tracing.auto_enable_tracing()
|
||||
```
|
||||
5. Run your pipeline:
|
||||
|
||||
```python
|
||||
...
|
||||
pipeline.run(...)
|
||||
...
|
||||
```
|
||||
6. Inspect the traces in the UI provided by Jaeger at [http://localhost:16686](http://localhost:16686/search).
|
||||
|
||||
## 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:
|
||||
<ClickableImage src="/img/55c3d5c84282d726c95fb3350ec36be49a354edca8a6164f5dffdab7121cec58-image_2.png" alt="Console output showing Haystack pipeline execution with DEBUG level tracing logs including component names, types, and input/output specifications" />
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: "AlloyDBDocumentStore"
|
||||
id: alloydbdocumentstore
|
||||
slug: "/alloydbdocumentstore"
|
||||
---
|
||||
|
||||
# AlloyDBDocumentStore
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| API reference | [AlloyDB](/reference/integrations-alloydb) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/alloydb |
|
||||
|
||||
</div>
|
||||
|
||||
[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.
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: "ArcadeDBDocumentStore"
|
||||
id: arcadedbdocumentstore
|
||||
slug: "/arcadedbdocumentstore"
|
||||
---
|
||||
|
||||
# ArcadeDBDocumentStore
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| API reference | [ArcadeDB](/reference/integrations-arcadedb) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/arcadedb |
|
||||
|
||||
</div>
|
||||
|
||||
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).
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
title: "AstraDocumentStore"
|
||||
id: astradocumentstore
|
||||
slug: "/astradocumentstore"
|
||||
---
|
||||
|
||||
# AstraDocumentStore
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| API reference | [Astra](/reference/integrations-astra) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/astra |
|
||||
|
||||
</div>
|
||||
|
||||
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 ID and a generated token.
|
||||
|
||||
You will additionally need a collection name and a namespace. When you create the collection name, you also need to set the embedding dimensions and the similarity metric. 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)
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
---
|
||||
title: "AzureAISearchDocumentStore"
|
||||
id: azureaisearchdocumentstore
|
||||
slug: "/azureaisearchdocumentstore"
|
||||
description: "A Document Store for storing and retrieval from Azure AI Search Index."
|
||||
---
|
||||
|
||||
# AzureAISearchDocumentStore
|
||||
|
||||
A Document Store for storing and retrieval from Azure AI Search Index.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **API reference** | [Azure AI Search](/reference/integrations-azure_ai_search) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/azure_ai_search |
|
||||
|
||||
</div>
|
||||
|
||||
[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:
|
||||
|
||||
```python
|
||||
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.
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
title: "ChromaDocumentStore"
|
||||
id: chromadocumentstore
|
||||
slug: "/chromadocumentstore"
|
||||
---
|
||||
|
||||
# ChromaDocumentStore
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| API reference | [Chroma](/reference/integrations-chroma) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/chroma |
|
||||
|
||||
</div>
|
||||
|
||||
[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 three Retriever components. They all 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)
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: "ElasticsearchDocumentStore"
|
||||
id: elasticsearch-document-store
|
||||
slug: "/elasticsearch-document-store"
|
||||
description: "Use an Elasticsearch database with Haystack."
|
||||
---
|
||||
|
||||
# ElasticsearchDocumentStore
|
||||
|
||||
Use an Elasticsearch database with Haystack.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| API reference | [Elasticsearch](/reference/integrations-elasticsearch) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/elasticsearch |
|
||||
|
||||
</div>
|
||||
|
||||
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.11.1
|
||||
docker run -p 9200:9200 -e "discovery.type=single-node" -e "ES_JAVA_OPTS=-Xms1024m -Xmx1024m" -e "xpack.security.enabled=false" elasticsearch:8.11.1
|
||||
```
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,152 @@
|
||||
---
|
||||
title: "FAISSDocumentStore"
|
||||
id: faissdocumentstore
|
||||
slug: "/faissdocumentstore"
|
||||
---
|
||||
|
||||
# FAISSDocumentStore
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| API reference | [FAISS](/reference/integrations-faiss) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/faiss |
|
||||
|
||||
</div>
|
||||
|
||||
`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/<package>/.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/<package>/.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`.
|
||||
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: "FalkorDBDocumentStore"
|
||||
id: falkordbdocumentstore
|
||||
slug: "/falkordbdocumentstore"
|
||||
description: "Use the FalkorDB graph database with Haystack for GraphRAG workloads."
|
||||
---
|
||||
|
||||
# FalkorDBDocumentStore
|
||||
|
||||
Use the FalkorDB graph database with Haystack for GraphRAG workloads.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| API reference | [FalkorDB](/reference/integrations-falkordb) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/falkordb |
|
||||
|
||||
</div>
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "InMemoryDocumentStore"
|
||||
id: inmemorydocumentstore
|
||||
slug: "/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.
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
---
|
||||
title: "MongoDBAtlasDocumentStore"
|
||||
id: mongodbatlasdocumentstore
|
||||
slug: "/mongodbatlasdocumentstore"
|
||||
---
|
||||
|
||||
# MongoDBAtlasDocumentStore
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| API reference | [MongoDB Atlas](/reference/integrations-mongodb-atlas) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/mongodb_atlas |
|
||||
|
||||
</div>
|
||||
|
||||
`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:
|
||||
|
||||
```python
|
||||
export MONGO_CONNECTION_STRING="mongodb+srv://<username>:<password>@<cluster_name>.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.
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
---
|
||||
title: "OpenSearchDocumentStore"
|
||||
id: opensearch-document-store
|
||||
slug: "/opensearch-document-store"
|
||||
description: "A Document Store for storing and retrieval from OpenSearch."
|
||||
---
|
||||
|
||||
# OpenSearchDocumentStore
|
||||
|
||||
A Document Store for storing and retrieval from OpenSearch.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| API reference | [OpenSearch](/reference/integrations-opensearch) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/opensearch |
|
||||
|
||||
</div>
|
||||
|
||||
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)
|
||||
@@ -0,0 +1,109 @@
|
||||
---
|
||||
title: "PgvectorDocumentStore"
|
||||
id: pgvectordocumentstore
|
||||
slug: "/pgvectordocumentstore"
|
||||
---
|
||||
|
||||
# PgvectorDocumentStore
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| API reference | [Pgvector](/reference/integrations-pgvector) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/pgvector/ |
|
||||
|
||||
</div>
|
||||
|
||||
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 ankane/pgvector
|
||||
```
|
||||
|
||||
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/pgvectorembeddingretriever.mdx): A keyword-based Retriever that fetches documents matching a query from the Pgvector Document Store.
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: "PineconeDocumentStore"
|
||||
id: pinecone-document-store
|
||||
slug: "/pinecone-document-store"
|
||||
description: "Use a Pinecone vector database with Haystack."
|
||||
---
|
||||
|
||||
# PineconeDocumentStore
|
||||
|
||||
Use a Pinecone vector database with Haystack.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| API reference | [Pinecone](/reference/integrations-pinecone) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/pinecone |
|
||||
|
||||
</div>
|
||||
|
||||
[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).
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
title: "QdrantDocumentStore"
|
||||
id: qdrant-document-store
|
||||
slug: "/qdrant-document-store"
|
||||
description: "Use the Qdrant vector database with Haystack."
|
||||
---
|
||||
|
||||
# QdrantDocumentStore
|
||||
|
||||
Use the Qdrant vector database with Haystack.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| API reference | [Qdrant](/reference/integrations-qdrant) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/qdrant |
|
||||
|
||||
</div>
|
||||
|
||||
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) directly. 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=1024, # 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)
|
||||
@@ -0,0 +1,170 @@
|
||||
---
|
||||
title: "ValkeyDocumentStore"
|
||||
id: valkeydocumentstore
|
||||
slug: "/valkeydocumentstore"
|
||||
description: "Use a Valkey database with Haystack."
|
||||
---
|
||||
|
||||
# ValkeyDocumentStore
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| API reference | [Valkey](/reference/integrations-valkey) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/valkey |
|
||||
|
||||
</div>
|
||||
|
||||
[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
|
||||
```
|
||||
|
||||
## 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.components.embedders 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.components.embedders 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`.
|
||||
@@ -0,0 +1,115 @@
|
||||
---
|
||||
title: "VespaDocumentStore"
|
||||
id: vespadocumentstore
|
||||
slug: "/vespadocumentstore"
|
||||
---
|
||||
|
||||
# VespaDocumentStore
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| API reference | [Vespa](/reference/integrations-vespa) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/vespa |
|
||||
|
||||
</div>
|
||||
|
||||
[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).
|
||||
@@ -0,0 +1,155 @@
|
||||
---
|
||||
title: "WeaviateDocumentStore"
|
||||
id: weaviatedocumentstore
|
||||
slug: "/weaviatedocumentstore"
|
||||
---
|
||||
|
||||
# WeaviateDocumentStore
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| API reference | [Weaviate](/reference/integrations-weaviate) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/weaviate |
|
||||
|
||||
</div>
|
||||
|
||||
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
|
||||
---
|
||||
version: '3.4'
|
||||
services:
|
||||
weaviate:
|
||||
command:
|
||||
- --host
|
||||
- 0.0.0.0
|
||||
- --port
|
||||
- '8080'
|
||||
- --scheme
|
||||
- http
|
||||
image: semitechnologies/weaviate:1.30.17
|
||||
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.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: "Introduction to Haystack"
|
||||
id: intro
|
||||
description: "Haystack is an open-source AI framework to build production-ready LLM applications such as AI Agents, powerful RAG applications and scalable multimodal search systems. Learn more about Haystack and how it works."
|
||||
---
|
||||
|
||||
# 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).
|
||||
:::
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: "Advanced RAG Techniques"
|
||||
id: advanced-rag-techniques
|
||||
slug: "/advanced-rag-techniques"
|
||||
---
|
||||
|
||||
# Advanced RAG Techniques
|
||||
|
||||
This section of documentation talks about advanced RAQ 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)
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
---
|
||||
title: "Hypothetical Document Embeddings (HyDE)"
|
||||
id: hypothetical-document-embeddings-hyde
|
||||
slug: "/hypothetical-document-embeddings-hyde"
|
||||
description: "Enhance the retrieval in Haystack using HyDE method by generating a mock-up hypothetical document for an initial query."
|
||||
---
|
||||
|
||||
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.
|
||||
<ClickableImage src="/img/2d00628-Untitled_2.png" alt="HyDE model architecture diagram showing how GPT generates hypothetical documents from queries in multiple languages, which are then matched with real documents via a Contriever model" size="large" />
|
||||
|
||||
## How To Build It in Haystack?
|
||||
|
||||
First, prepare all the components that you would need:
|
||||
|
||||
```python
|
||||
import os
|
||||
from numpy import array, mean
|
||||
from typing import List
|
||||
|
||||
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.components.embedders 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-3.5-turbo",
|
||||
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",
|
||||
)
|
||||
embedder.warm_up()
|
||||
|
||||
|
||||
# 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:
|
||||
<ClickableImage src="/img/74f3daa-hyde.png" alt="HyDE pipeline implementation flowchart showing prompt builder, generator, adapter, embedder, and hypothetical document embedder components" size="large"/>
|
||||
|
||||
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)
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
title: "Evaluation"
|
||||
id: evaluation
|
||||
slug: "/evaluation"
|
||||
description: "Learn all about pipeline or component evaluation in Haystack."
|
||||
---
|
||||
|
||||
# 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 |
|
||||
| [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)
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
---
|
||||
title: "Model-Based Evaluation"
|
||||
id: model-based-evaluation
|
||||
slug: "/model-based-evaluation"
|
||||
description: "Haystack supports various kinds of model-based evaluation. This page explains what model-based evaluation is and discusses the various options available with Haystack."
|
||||
---
|
||||
|
||||
# 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. The most frequently used golden model is GPT-4. 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, you need to pass the `api_base_url` and `model` in the `api_params` parameter when initializing the Evaluator.
|
||||
|
||||
The following example shows how this would work with an Ollama model.
|
||||
|
||||
First, make sure that Ollama is running locally:
|
||||
|
||||
```curl
|
||||
curl http://localhost:11434/api/generate -d '{
|
||||
"model": "llama3",
|
||||
"prompt":"Why is the sky blue?"
|
||||
}'
|
||||
```
|
||||
|
||||
Then, your pipeline would look like this:
|
||||
|
||||
```python
|
||||
from haystack.components.evaluators import FaithfulnessEvaluator
|
||||
from haystack.utils import Secret
|
||||
|
||||
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.",
|
||||
]
|
||||
local_endpoint = "http://localhost:11434/v1"
|
||||
|
||||
evaluator = FaithfulnessEvaluator(
|
||||
api_key=Secret.from_token("just-a-placeholder"),
|
||||
api_params={"api_base_url": local_endpoint, "model": "llama3"},
|
||||
)
|
||||
|
||||
result = evaluator.run(
|
||||
questions=questions,
|
||||
contexts=contexts,
|
||||
predicted_answers=predicted_answers,
|
||||
)
|
||||
```
|
||||
|
||||
### 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 statements from the documents and then checks how many of them are relevant for answering the query.
|
||||
|
||||
### 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, cross-encoder architecture 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 | All GPT models from OpenAI <br />Google VertexAI Models <br />Azure OpenAI Models <br />Amazon Bedrock Models | All GPT models from OpenAI |
|
||||
| Supported metrics | ANSWER_CORRECTNESS, FAITHFULNESS, ANSWER_SIMILARITY, CONTEXT_PRECISION, CONTEXT_UTILIZATION,CONTEXT_RECALL, ASPECT_CRITIQUE, CONTEXT_RELEVANCY, ANSWER_RELEVANCY | ANSWER_RELEVANCY, FAITHFULNESS, CONTEXTUAL_PRECISION, CONTEXTUAL_RECALL, CONTEXTUAL_RELEVANCE |
|
||||
| Customizable prompt for response evaluation | ✅, with ASPECT_CRITIQUE metric | ❌ |
|
||||
| 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)
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: "Statistical Evaluation"
|
||||
id: statistical-evaluation
|
||||
slug: "/statistical-evaluation"
|
||||
description: "Haystack supports various statistical evaluation metrics. This page explains what statistical evaluation is and discusses the various options available within Haystack."
|
||||
---
|
||||
|
||||
# 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 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 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. The metric `recall_single_hit` considers whether at least one of the correct documents is retrieved, whereas `recall_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%.
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: "Breaking Change Policy"
|
||||
id: breaking-change-policy
|
||||
slug: "/breaking-change-policy"
|
||||
description: "This document outlines the breaking change policy for Haystack, including the definition of breaking changes, versioning conventions, and the deprecation process for existing features."
|
||||
---
|
||||
|
||||
# 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.
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: "Using Haystack Docs in Your Coding Agent"
|
||||
id: docs-mcp-server
|
||||
slug: "/docs-mcp-server"
|
||||
description: "Connect your coding agent to the Haystack documentation through the public MCP server. Includes setup instructions for Claude Code, Cursor, and GitHub Copilot."
|
||||
---
|
||||
|
||||
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
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="claude-code" label="Claude Code" default>
|
||||
|
||||
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).
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="cursor" label="Cursor">
|
||||
|
||||
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.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="copilot" label="GitHub Copilot">
|
||||
|
||||
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.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## 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."*
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "FAQ"
|
||||
id: faq
|
||||
slug: "/faq"
|
||||
description: "Here are the answers to the questions people frequently ask about Haystack."
|
||||
---
|
||||
|
||||
# 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 version 2.x with our [migration guide](migration.mdx).
|
||||
@@ -0,0 +1,571 @@
|
||||
---
|
||||
title: "Get Started"
|
||||
id: get-started
|
||||
slug: "/get-started"
|
||||
description: "Learn how to quickly get up and running with Haystack. Build your first RAG pipeline and tool-calling Agent with step-by-step examples for multiple LLM providers."
|
||||
---
|
||||
|
||||
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.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="openai" label="OpenAI" default>
|
||||
|
||||
[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"])
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="huggingface" label="Hugging Face">
|
||||
|
||||
[HuggingFaceAPIChatGenerator](../pipeline-components/generators/huggingfaceapichatgenerator.mdx) is included in the `haystack-ai` package. You can get a [free Hugging Face token](https://huggingface.co/settings/tokens) to use the Serverless Inference API.
|
||||
|
||||
```python
|
||||
from haystack import Pipeline, Document
|
||||
from haystack.components.generators.chat 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"])
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="anthropic" label="Anthropic">
|
||||
|
||||
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"])
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="amazon-bedrock" label="Amazon Bedrock">
|
||||
|
||||
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"])
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="google-gemini" label="Google Gemini">
|
||||
|
||||
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"])
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="more-providers" label="More Providers">
|
||||
|
||||
<div style={{backgroundColor: 'var(--ifm-color-emphasis-100)', padding: '1.5rem', borderRadius: '8px'}}>
|
||||
|
||||
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.
|
||||
|
||||
</div>
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### 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.
|
||||
|
||||
This example requires a [SerperDev API key](https://serper.dev/) for web search. Set it as the `SERPERDEV_API_KEY` environment variable.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="openai" label="OpenAI" default>
|
||||
|
||||
[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.components.websearch 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)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="huggingface" label="Hugging Face">
|
||||
|
||||
[HuggingFaceAPIChatGenerator](../pipeline-components/generators/huggingfaceapichatgenerator.mdx) is included in the `haystack-ai` 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.components.generators.chat import HuggingFaceAPIChatGenerator
|
||||
from haystack.dataclasses import ChatMessage
|
||||
from haystack.tools import ComponentTool
|
||||
from haystack.components.websearch 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)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="anthropic" label="Anthropic">
|
||||
|
||||
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.components.websearch 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)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="amazon-bedrock" label="Amazon Bedrock">
|
||||
|
||||
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.components.websearch 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)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="google-gemini" label="Google Gemini">
|
||||
|
||||
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.components.websearch 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)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="more-providers" label="More Providers">
|
||||
|
||||
<div style={{backgroundColor: 'var(--ifm-color-emphasis-100)', padding: '1.5rem', borderRadius: '8px'}}>
|
||||
|
||||
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.
|
||||
|
||||
</div>
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### 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.
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
title: "Installation"
|
||||
id: installation
|
||||
slug: "/installation"
|
||||
description: "See how to quickly install Haystack with pip, uv, or conda."
|
||||
---
|
||||
|
||||
# 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.
|
||||
+669
@@ -0,0 +1,669 @@
|
||||
---
|
||||
title: "Migrating from LangGraph/LangChain to Haystack"
|
||||
id: migrating-from-langgraphlangchain-to-haystack
|
||||
slug: "/migrating-from-langgraphlangchain-to-haystack"
|
||||
description: "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."
|
||||
---
|
||||
|
||||
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) / [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 [`ComponentTool`](../tools/componenttool.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 short-term memory, Haystack offers a [ChatMessage Store](/reference/experimental-chatmessage-store-api) to persist chat history. More memory options are coming soon. |
|
||||
| Time travel (Checkpoints) | Breakpoints (Breakpoint, AgentBreakpoint, ToolBreakpoint, Snapshot) | [Breakpoints](../concepts/pipelines/pipeline-breakpoints.mdx) let you pause, inspect, modify, and resume a pipeline, agent, or tool for debugging or iterative development. |
|
||||
| Human-in-the-Loop (Interrupts / Commands) | Human-in-the-loop ( ConfirmationStrategy / ConfirmationPolicy) | Haystack uses [confirmation strategies](https://haystack.deepset.ai/tutorials/47_human_in_the_loop_agent) 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.
|
||||
|
||||
<div className="code-comparison">
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="Haystack">{`# 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`}</CodeBlock>
|
||||
</div>
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="LangGraph + LangChain">{`# 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`}</CodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
**Step 2: Initialize the LLM with tools**
|
||||
|
||||
Both frameworks connect tools to the LLM, but with different APIs. In Haystack, tools are passed directly to the `ChatGenerator` component during initialization. In LangGraph, you first initialize the model, then bind tools using `.bind_tools()` to create a tool-enabled LLM instance.
|
||||
|
||||
<div className="code-comparison">
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="Haystack">{`from haystack_integrations.components.generators.anthropic import AnthropicChatGenerator
|
||||
|
||||
# Augment the LLM with tools
|
||||
tools = [add, multiply, divide]
|
||||
model = AnthropicChatGenerator(
|
||||
model="claude-sonnet-4-5-20250929",
|
||||
generation_kwargs={"temperature": 0},
|
||||
tools=tools,
|
||||
)`}</CodeBlock>
|
||||
</div>
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="LangGraph + LangChain">{`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)`}</CodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
**Step 3: Set up message handling and LLM invocation**
|
||||
|
||||
This is where the frameworks diverge more significantly. In Haystack you'll use a custom component (`MessageCollector`) to accumulate conversation history across the agentic loop. LangGraph instead defines a node function (`llm_call`) that operates on `MessagesState` - a built-in state container that automatically manages message history.
|
||||
|
||||
<div className="code-comparison">
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="Haystack">{`from typing import Any, Dict, List
|
||||
|
||||
from haystack import component
|
||||
from haystack.core.component.types import Variadic
|
||||
from haystack.dataclasses import ChatMessage
|
||||
|
||||
# Components
|
||||
|
||||
# Custom component to temporarily store the messages
|
||||
@component()
|
||||
class MessageCollector:
|
||||
def __init__(self):
|
||||
self._messages = []
|
||||
|
||||
@component.output_types(messages=List[ChatMessage])
|
||||
def run(self, messages: Variadic[List[ChatMessage]]) -> Dict[str, Any]:
|
||||
self._messages.extend([msg for inner in messages for msg in inner])
|
||||
return {"messages": self._messages}
|
||||
|
||||
def clear(self):
|
||||
self._messages = []
|
||||
|
||||
message_collector = MessageCollector()`}</CodeBlock>
|
||||
</div>
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="LangGraph + LangChain">{`from langgraph.graph import MessagesState
|
||||
from langchain.messages import SystemMessage, ToolMessage
|
||||
from typing import Literal
|
||||
|
||||
# Nodes
|
||||
def llm_call(state: MessagesState):
|
||||
# LLM decides whether to call a tool or not
|
||||
|
||||
return {
|
||||
"messages": [
|
||||
llm_with_tools.invoke(
|
||||
[
|
||||
SystemMessage(
|
||||
content="You are a helpful assistant tasked with performing arithmetic on a set of inputs."
|
||||
)
|
||||
]
|
||||
+ state["messages"]
|
||||
)
|
||||
]
|
||||
}`}</CodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
**Step 4: Execute tool calls**
|
||||
|
||||
When the LLM decides to use a tool, it must be invoked and its result returned. Haystack provides a built-in `ToolInvoker` component that handles this automatically. LangGraph requires you to define a custom node function that iterates over tool calls, invokes each tool, and wraps the results in `ToolMessage` objects.
|
||||
|
||||
<div className="code-comparison">
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="Haystack">{`from haystack.components.tools import ToolInvoker
|
||||
|
||||
# Tool invoker component to execute a tool call
|
||||
tool_invoker = ToolInvoker(tools=tools)`}</CodeBlock>
|
||||
</div>
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="LangGraph + LangChain">{`def tool_node(state: dict):
|
||||
# Performs the tool call
|
||||
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}`}</CodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
**Step 5: Implement conditional routing logic**
|
||||
|
||||
After the LLM responds, we need to decide whether to continue the loop (if tools were called) or finish (if the LLM provided a final answer). Haystack uses a `ConditionalRouter` component with declarative route conditions written in Jinja2 templates. LangGraph uses a conditional edge function (`should_continue`) that inspects the state and returns the next node or `END`.
|
||||
|
||||
<div className="code-comparison">
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="Haystack">{`from haystack.components.routers import ConditionalRouter
|
||||
|
||||
# ConditionalRouter component to route to the tool invoker or end user based upon whether the LLM made a tool call
|
||||
routes = [
|
||||
{
|
||||
"condition": "{{replies[0].tool_calls | length > 0}}",
|
||||
"output": "{{replies}}",
|
||||
"output_name": "there_are_tool_calls",
|
||||
"output_type": List[ChatMessage],
|
||||
},
|
||||
{
|
||||
"condition": "{{replies[0].tool_calls | length == 0}}",
|
||||
"output": "{{replies}}",
|
||||
"output_name": "final_replies",
|
||||
"output_type": List[ChatMessage],
|
||||
},
|
||||
]
|
||||
router = ConditionalRouter(routes, unsafe=True)`}</CodeBlock>
|
||||
</div>
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="LangGraph + LangChain">{`from langgraph.graph import END
|
||||
|
||||
# Conditional edge function to 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]:
|
||||
# Decide if we should continue the loop or stop based upon whether the LLM made a tool call
|
||||
|
||||
messages = state["messages"]
|
||||
last_message = messages[-1]
|
||||
|
||||
# If the LLM makes a tool call, then perform an action
|
||||
if last_message.tool_calls:
|
||||
return "tool_node"
|
||||
|
||||
# Otherwise, we stop (reply to the user)
|
||||
return END`}</CodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
**Step 6: Assemble the workflow**
|
||||
|
||||
This is where you wire together all the components or nodes. Haystack uses a `Pipeline` where you explicitly add components and connect their inputs and outputs, creating a directed graph with loops. LangGraph uses a `StateGraph` where you add nodes and edges, then compile the graph into an executable agent. Both approaches achieve the same agentic loop, but with different levels of explicitness.
|
||||
|
||||
<div className="code-comparison">
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="Haystack">{`from haystack import Pipeline
|
||||
|
||||
# Build pipeline
|
||||
agent_pipe = Pipeline()
|
||||
|
||||
# Add components
|
||||
agent_pipe.add_component("message_collector", message_collector)
|
||||
agent_pipe.add_component("llm", model)
|
||||
agent_pipe.add_component("router", router)
|
||||
agent_pipe.add_component("tool_invoker", tool_invoker)
|
||||
|
||||
# Add connections
|
||||
agent_pipe.connect("message_collector", "llm.messages")
|
||||
agent_pipe.connect("llm.replies", "router")
|
||||
agent_pipe.connect("router.there_are_tool_calls", "tool_invoker") # If there are tool calls, send them to the ToolInvoker
|
||||
agent_pipe.connect("router.there_are_tool_calls", "message_collector")
|
||||
agent_pipe.connect("tool_invoker.tool_messages", "message_collector")`}</CodeBlock>
|
||||
</div>
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="LangGraph + LangChain">{`from langgraph.graph import StateGraph, START
|
||||
|
||||
# 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()`}</CodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
**Step 7: Run the agent**
|
||||
|
||||
Finally, we execute the agent with a user message. Haystack calls `.run()` on the pipeline with initial messages, while LangGraph calls `.invoke()` on the compiled agent. Both return the conversation history.
|
||||
|
||||
<div className="code-comparison">
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="Haystack">{`# Run the pipeline
|
||||
messages = [
|
||||
ChatMessage.from_system(text="You are a helpful assistant tasked with performing arithmetic on a set of inputs."),
|
||||
ChatMessage.from_user(text="Add 3 and 4.")
|
||||
]
|
||||
result = agent_pipe.run({"messages": messages})
|
||||
result`}</CodeBlock>
|
||||
</div>
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="LangGraph + LangChain">{`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()`}</CodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
### Creating Agents
|
||||
|
||||
The [Agentic Flows](#agentic-flows-with-haystack-vs-langgraph) walkthrough above showed how to assemble an agent loop manually from pipeline primitives. Haystack also provides a high-level `Agent` class that 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.
|
||||
|
||||
<div className="code-comparison">
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="Haystack">{`# 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)`}</CodeBlock>
|
||||
</div>
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="LangGraph + LangChain">{`# 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)`}</CodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
### 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**
|
||||
|
||||
<div className="code-comparison">
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="Haystack">{`# pip install haystack-ai sentence-transformers
|
||||
|
||||
from haystack import Document
|
||||
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
||||
from haystack.components.embedders 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)`}</CodeBlock>
|
||||
</div>
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="LangChain">{`# 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."),
|
||||
])`}</CodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
**Step 2: Build a RAG pipeline**
|
||||
|
||||
<div className="code-comparison">
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="Haystack">{`from haystack import Pipeline
|
||||
from haystack.components.embedders 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)`}</CodeBlock>
|
||||
</div>
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="LangChain">{`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)`}</CodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
### 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.
|
||||
|
||||
<div className="code-comparison">
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="Haystack">{`# 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)`}</CodeBlock>
|
||||
</div>
|
||||
<div className="code-comparison__column">
|
||||
<CodeBlock language="python" title="LangGraph + LangChain">{`# 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())`}</CodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,508 @@
|
||||
---
|
||||
title: "Migration Guide"
|
||||
id: migration
|
||||
slug: "/migration"
|
||||
description: "Learn how to make the move to Haystack 2.x from Haystack 1.x."
|
||||
---
|
||||
|
||||
# Migration Guide
|
||||
|
||||
Learn how to make the move to Haystack 2.x from Haystack 1.x.
|
||||
|
||||
This guide is designed for those with previous experience with Haystack and who are interested in understanding the differences between Haystack 1.x and Haystack 2.x. If you're new to Haystack, skip this page and proceed directly to Haystack 2.x [documentation](get-started.mdx).
|
||||
|
||||
## Major Changes
|
||||
|
||||
Haystack 2.x represents a significant overhaul of Haystack 1.x, and it's important to note that certain key concepts outlined in this section don't have a direct correlation between the two versions.
|
||||
|
||||
### Package Name
|
||||
|
||||
Haystack 1.x was distributed with a package called `farm-haystack`. To migrate your application, you must uninstall `farm-haystack` and install the new `haystack-ai` package for Haystack 2.x.
|
||||
|
||||
:::warning
|
||||
Two versions of the project cannot coexist in the same Python environment.
|
||||
|
||||
One of the options is to remove both packages if they are installed in the same environment, followed by installing only one of them:
|
||||
|
||||
```bash
|
||||
pip uninstall -y farm-haystack haystack-ai
|
||||
pip install haystack-ai
|
||||
```
|
||||
:::
|
||||
|
||||
### Nodes
|
||||
|
||||
While Haystack 2.x continues to rely on the `Pipeline` abstraction, the elements linked in a pipeline graph are now referred to as just _components_, replacing the terms _nodes_ and _pipeline components_ used in the previous versions. The [_Migrating Components_](#migrating-components) paragraph below outlines which component in Haystack 2.x can be used as a replacement for a specific 1.x node.
|
||||
|
||||
### Pipelines
|
||||
|
||||
Pipelines continue to serve as the fundamental structure of all Haystack applications. While the concept of `Pipeline` abstraction remains consistent, Haystack 2.x introduces significant enhancements that address various limitations of its predecessor. For instance, the pipelines now support loops. Pipelines also offer greater flexibility in their input, which is no longer restricted to queries. The pipeline now allows to route the output of a component to multiple recipients. This increases flexibility, however, comes with notable differences in the pipeline definition process in Haystack 2.x compared to the previous version.
|
||||
|
||||
In Haystack 1.x, a pipeline was built by adding one node after the other. In the resulting pipeline graph, edges are automatically added to connect those nodes in the order they were added.
|
||||
|
||||
Building a pipeline in Haystack 2.x is a two-step process:
|
||||
|
||||
1. Initially, components are added to the pipeline without any specific order by calling the `add_component` method.
|
||||
2. Subsequently, the components must be explicitly connected by calling the `connect` method to define the final graph.
|
||||
|
||||
To migrate an existing pipeline, the first step is to go through the nodes and identify their counterparts in Haystack 2.x (see the following section, [_Migrating Components_](#migrating-components), for guidance). If all the nodes can be replaced by corresponding components, they have to be added to the pipeline with `add_component` and explicitly connected with the appropriate calls to `connect`. Here is an example:
|
||||
|
||||
**Haystack 1.x**
|
||||
|
||||
```python
|
||||
pipeline = Pipeline()
|
||||
|
||||
node_1 = SomeNode()
|
||||
node_2 = AnotherNode()
|
||||
|
||||
pipeline.add_node(node_1, name="Node_1", inputs=["Query"])
|
||||
pipeline.add_node(node_2, name="Node_2", inputs=["Node_1"])
|
||||
```
|
||||
|
||||
**Haystack 2.x**
|
||||
|
||||
```python
|
||||
pipeline = Pipeline()
|
||||
|
||||
component_1 = SomeComponent()
|
||||
component_2 = AnotherComponent()
|
||||
|
||||
pipeline.add_component("Comp_1", component_1)
|
||||
pipeline.add_component("Comp_2", component_2)
|
||||
|
||||
pipeline.connect("Comp_1", "Comp_2")
|
||||
```
|
||||
|
||||
In case a specific replacement component is not available for one of your nodes, migrating the pipeline might still be possible by:
|
||||
|
||||
- Either [creating a custom component](../concepts/components/custom-components.mdx), or
|
||||
- Changing the pipeline logic, as the last resort.
|
||||
|
||||
:::info
|
||||
Check out the [Pipelines](../concepts/pipelines.mdx) section of our 2.x documentation to understand how new pipelines work more granularly.
|
||||
:::
|
||||
|
||||
### Document Stores
|
||||
|
||||
The fundamental concept of Document Stores as gateways to access text and metadata stored in a database didn’t change in Haystack 2.x, but there are significant differences against Haystack 1.x.
|
||||
|
||||
In Haystack 1.x, Document Stores were a special type of node that you can use in two ways:
|
||||
|
||||
- As the last node in an indexing pipeline (such as a pipeline whose ultimate goal is storing data in a database).
|
||||
- As a normal Python instance passed to a Retriever node.
|
||||
|
||||
In Haystack 2.x, the Document Store is not a component, so to migrate the two use cases above to version 2.x, you can respectively:
|
||||
|
||||
- Replace the Document Store at the end of the pipeline with a [`DocumentWriter`](../pipeline-components/writers/documentwriter.mdx) component.
|
||||
- Identify the right Retriever component and create it passing the Document Store instance, same as it is in Haystack 1.x.
|
||||
|
||||
### Retrievers
|
||||
|
||||
Haystack 1.x provided a set of nodes that filter relevant documents from different data sources according to a given query. Each of those nodes implements a certain retrieval algorithm and supports one or more types of Document Stores. For example, the `BM25Retriever` node in Haystack 1.x can work seamlessly with OpenSearch and Elasticsearch but not with Qdrant; the `EmbeddingRetriever`, on the contrary, can work with all the three databases.
|
||||
|
||||
In Haystack 2.x, the concept is flipped, and each Document Store provides one or more retriever components, depending on which retrieval methods the underlying vector database supports. For example, the `OpenSearchDocumentStore` comes with [two Retriever components](../document-stores/opensearch-document-store.mdx#supported-retrievers), one relying on BM25, and the other on vector similarity.
|
||||
|
||||
To migrate a 1.x retrieval pipeline to 2.x, the first step is to identify the Document Store being used and replace the Retriever node with the corresponding Retriever component from Haystack 2.x with the Document Store of choice. For example, a `BM25Retriever` node using Elasticsearch in a Haystack 1.x pipeline should be replaced with the [`ElasticsearchBM25Retriever`](../pipeline-components/retrievers/elasticsearchbm25retriever.mdx) component.
|
||||
|
||||
### PromptNode
|
||||
|
||||
The `PromptNode` in Haystack 1.x represented the gateway to any Large Language Model (LLM) inference provider, whether it is locally available or remote. Based on the name of the model, Haystack infers the right provider to call and forward the query.
|
||||
|
||||
In Haystack 2.x, the task of using LLMs is assigned to [Generators](../pipeline-components/generators.mdx). These are a set of components that are highly specialized and tailored for each inference provider.
|
||||
|
||||
The first step when migrating a pipeline with a `PromptNode` is to identify the model provider used and to replace the node with two components:
|
||||
|
||||
- A Generator component for the model provider of choice,
|
||||
- A `PromptBuilder` or `ChatPromptBuilder` component to build the prompt to be used.
|
||||
|
||||
The [_Migration examples_](#migration-examples) section below shows how to port a `PromptNode` using OpenAI with a prompt template to a corresponding Haystack 2.x pipeline using the `OpenAIGenerator` in conjunction with a `PromptBuilder` component.
|
||||
|
||||
### Agents
|
||||
|
||||
The agentic approach facilitates the answering of questions that are significantly more complex than those typically addressed by extractive or generative question answering techniques.
|
||||
|
||||
Haystack 1.x provided Agents, enabling the use of LLMs in a loop.
|
||||
|
||||
Currently in Haystack 2.x, you can build Agents using three main elements in a pipeline: Chat Generators, ToolInvoker component, and Tools. A standalone Agent abstraction in Haystack 2.x is in an experimental phase.
|
||||
|
||||
:::note[Agents Documentation Page]
|
||||
|
||||
Take a look at our 2.x [Agents](../concepts/agents.mdx) documentation page for more information and detailed examples.
|
||||
:::
|
||||
|
||||
### REST API
|
||||
|
||||
Haystack 1.x enabled the deployment of pipelines through a RESTful API over HTTP. This feature is facilitated by a separate application named `rest_api` which is exclusively accessible in the form of a [source code on GitHub](https://github.com/deepset-ai/haystack/tree/v1.x/rest_api).
|
||||
|
||||
Haystack 2.x takes the same RESTful approach, but in this case, the application to be used to deploy pipelines is called [Hayhooks](../development/hayhooks.mdx) and can be installed with `pip install hayhooks`.
|
||||
|
||||
At the moment, porting an existing Haystack 1.x deployment using the `rest_api` project to Hayhooks would require a complete rewrite of the application.
|
||||
|
||||
## Dependencies
|
||||
|
||||
In order to minimize runtime errors, Haystack 1.x was distributed in a package that’s quite large, as it tries to set up the Python environment with as many dependencies as possible.
|
||||
|
||||
In contrast, Haystack 2.x strives for a more streamlined approach, offering a minimal set of dependencies right out of the box. It features a system that issues a warning when an additional dependency is required, thereby providing the user with the necessary instructions.
|
||||
|
||||
To make sure all the dependencies are satisfied when migrating a Haystack 1.x application to version 2.x, a good strategy is to run end-to-end tests and cover all the execution paths to ensure all the required dependencies are available in the target Python environment.
|
||||
|
||||
## Migrating Components
|
||||
|
||||
This table outlines which component (or a group of components) can be used to replace a certain node when porting a Haystack 1.x pipeline to the latest 2.x version. It’s important to note that when a Haystack 2.x replacement is not available, this doesn’t necessarily mean we are planning this feature.
|
||||
|
||||
If you need help migrating a 1.x node without a 2.x counterpart, open an [issue](https://github.com/deepset-ai/haystack/issues) in Haystack GitHub repository.
|
||||
|
||||
### Data Handling
|
||||
|
||||
| Haystack 1.x | Description | Haystack 2.x |
|
||||
| --- | --- | --- |
|
||||
| Crawler | Scrapes text from websites. **Example usage:** To run searches on your website content. | Not Available |
|
||||
| DocumentClassifier | Classifies documents by attaching metadata to them. **Example usage:** Labeling documents by their characteristic (for example, sentiment). | [TransformersZeroShotDocumentClassifier](../pipeline-components/classifiers/transformerszeroshotdocumentclassifier.mdx) |
|
||||
| DocumentLanguageClassifier | Detects the language of the documents you pass to it and adds it to the document metadata. | [DocumentLanguageClassifier](../pipeline-components/classifiers/documentlanguageclassifier.mdx) |
|
||||
| EntityExtractor | Extracts predefined entities out of a piece of text. **Example usage:** Named entity extraction (NER). | [NamedEntityExtractor](../pipeline-components/extractors/namedentityextractor.mdx) |
|
||||
| FileClassifier | Distinguishes between text, PDF, Markdown, Docx, and HTML files. **Example usage:** Routing files to appropriate converters (for example, it routes PDF files to `PDFToTextConverter`). | [FileTypeRouter](../pipeline-components/routers/filetyperouter.mdx) |
|
||||
| FileConverter | Cleans and splits documents in different formats. **Example usage:** In indexing pipelines, extracting text from a file and casting it into the Document class format. | [Converters](../pipeline-components/converters.mdx) |
|
||||
| PreProcessor | Cleans and splits documents. **Example usage:** Normalizing white spaces, getting rid of headers and footers, splitting documents into smaller ones. | [PreProcessors](../pipeline-components/preprocessors.mdx) |
|
||||
|
||||
### Semantic Search
|
||||
|
||||
| Haystack 1.x | Description | Haystack 2.x |
|
||||
| --- | --- | --- |
|
||||
| Ranker | Orders documents based on how relevant they are to the query. **Example usage:** In a query pipeline, after a keyword-based Retriever to rank the documents it returns. | [Rankers](../pipeline-components/rankers.mdx) |
|
||||
| Reader | Finds an answer by selecting a text span in documents. **Example usage:** In a query pipeline when you want to know the location of the answer. | [ExtractiveReader](../pipeline-components/readers/extractivereader.mdx) |
|
||||
| Retriever | Fetches relevant documents from the Document Store. **Example usage:** Coupling Retriever with a Reader in a query pipeline to speed up the search (the Reader only goes through the documents it gets from the Retriever). | [Retrievers](../pipeline-components/retrievers.mdx) |
|
||||
| QuestionGenerator | When given a document, it generates questions this document can answer. **Example usage:** Auto-suggested questions in your search app. | Prompt [Builders](../pipeline-components/builders.mdx) with dedicated prompt, [Generators](../pipeline-components/generators.mdx) |
|
||||
|
||||
### Prompts and LLMs
|
||||
|
||||
| Haystack 1.x | Description | Haystack 2.x |
|
||||
| --- | --- | --- |
|
||||
| PromptNode | Uses large language models to perform various NLP tasks in a pipeline or on its own. **Example usage:** It's a very versatile component that can perform tasks like summarization, question answering, translation, and more. | Prompt [Builders](../pipeline-components/builders.mdx),[Generators](../pipeline-components/generators.mdx) |
|
||||
|
||||
### Routing
|
||||
|
||||
| Haystack 1.x | Description | Haystack 2.x |
|
||||
| --- | --- | --- |
|
||||
| QueryClassifier | Categorizes queries. **Example usage:** Distinguishing between keyword queries and natural language questions and routing them to the Retrievers that can handle them best. | [TransformersZeroShotTextRouter](../pipeline-components/routers/transformerszeroshottextrouter.mdx) <br />[TransformersTextRouter](../pipeline-components/routers/transformerstextrouter.mdx) |
|
||||
| RouteDocuments | Routes documents to different branches of your pipeline based on their content type or metadata field. **Example usage:** Routing table data to `TableReader` and text data to `TransfomersReader` for better handling. | [Routers](../pipeline-components/routers.mdx) |
|
||||
|
||||
### Utility Components
|
||||
|
||||
| Haystack 1.x | Description | Haystack 2.x |
|
||||
| --- | --- | --- |
|
||||
| DocumentMerger | Concatenates multiple documents into a single one. **Example usage: **Merge the documents to summarize in a summarization pipeline. | Prompt [Builders](../pipeline-components/builders.mdx) |
|
||||
| Docs2Answers | Converts Documents into Answers. **Example usage:** When using REST API for document retrieval. REST API expects Answer as output, you can use `Doc2Answer` as the last node to convert the retrieved documents to answers. | [AnswerBuilder](../pipeline-components/builders/answerbuilder.mdx) |
|
||||
| JoinAnswers | Takes answers returned by multiple components and joins them in a single list of answers. **Example usage:** For running queries on different document types (for example, tables and text), where the documents are routed to different readers, and each reader returns a separate list of answers. | [AnswerJoiner](../pipeline-components/joiners/answerjoiner.mdx) |
|
||||
| JoinDocuments | Takes documents returned by different components and joins them to form one list of documents. **Example usage:** In document retrieval pipelines, where there are different types of documents, each routed to a different Retriever. Each Retriever returns a separate list of documents, and you can join them into one list using `JoinDocuments`. | [DocumentJoiner](../pipeline-components/joiners/documentjoiner.mdx) |
|
||||
| Shaper | Currently functions mostly as `PromptNode` helper making sure the `PromptNode` input or output is correct. **Example usage:** In a question answering pipeline using `PromptNode`, where the `PromptTemplate` expects questions as input, while Haystack pipelines use query. You can use Shaper to rename queries to questions. | Prompt [Builders](../pipeline-components/builders.mdx) |
|
||||
| Summarizer | Creates an overview of a document. **Example usage:** To get a glimpse of the documents the Retriever is returning. | Prompt [Builders](../pipeline-components/builders.mdx) with dedicated prompt, [Generators](../pipeline-components/generators.mdx) |
|
||||
| TransformersImageToText | Generates captions for images. **Example usage:** Automatically generate captions for a list of images that you can later use in your knowledge base. | [VertexAIImageQA](../pipeline-components/generators/vertexaiimageqa.mdx) |
|
||||
| Translator | Translates text from one language into another. **Example usage:** Running searches on documents in other languages. | Prompt [Builders](../pipeline-components/builders.mdx) with dedicated prompt, [Generators](../pipeline-components/generators.mdx) |
|
||||
|
||||
### Extras
|
||||
|
||||
| Haystack 1.x | Description | Haystack 2.x |
|
||||
| --- | --- | --- |
|
||||
| AnswerToSpeech | Converts text answers into speech answers. **Example usage:** Improving accessibility of your search system by providing a way to have the answer and its context read out loud. | [ElevenLabs](https://haystack.deepset.ai/integrations/elevenlabs) Integration |
|
||||
| DocumentToSpeech | Converts text documents to speech documents. **Example usage:** Improving accessibility of a document retrieval pipeline by providing the option to read documents out loud. | [ElevenLabs](https://haystack.deepset.ai/integrations/elevenlabs) Integration |
|
||||
|
||||
## Migration examples
|
||||
|
||||
:::info
|
||||
This section might grow as we assist users with their use cases.
|
||||
:::
|
||||
|
||||
### Indexing Pipeline
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Haystack 1.x</summary>
|
||||
|
||||
```python
|
||||
from haystack.document_stores import InMemoryDocumentStore
|
||||
from haystack.nodes.file_classifier import FileTypeClassifier
|
||||
from haystack.nodes.file_converter import TextConverter
|
||||
from haystack.nodes.preprocessor import PreProcessor
|
||||
from haystack.pipelines import Pipeline
|
||||
|
||||
# Initialize a DocumentStore
|
||||
document_store = InMemoryDocumentStore()
|
||||
|
||||
# Indexing Pipeline
|
||||
indexing_pipeline = Pipeline()
|
||||
|
||||
# Makes sure the file is a TXT file (FileTypeClassifier node)
|
||||
classifier = FileTypeClassifier()
|
||||
indexing_pipeline.add_node(classifier, name="Classifier", inputs=["File"])
|
||||
|
||||
# Converts a file into text and performs basic cleaning (TextConverter node)
|
||||
text_converter = TextConverter(remove_numeric_tables=True)
|
||||
indexing_pipeline.add_node(
|
||||
text_converter,
|
||||
name="Text_converter",
|
||||
inputs=["Classifier.output_1"],
|
||||
)
|
||||
|
||||
# Pre-processes the text by performing splits and adding metadata to the text (Preprocessor node)
|
||||
preprocessor = PreProcessor(
|
||||
clean_whitespace=True,
|
||||
clean_empty_lines=True,
|
||||
split_length=100,
|
||||
split_overlap=50,
|
||||
split_respect_sentence_boundary=True,
|
||||
)
|
||||
indexing_pipeline.add_node(preprocessor, name="Preprocessor", inputs=["Text_converter"])
|
||||
|
||||
# - Writes the resulting documents into the document store
|
||||
indexing_pipeline.add_node(
|
||||
document_store,
|
||||
name="Document_Store",
|
||||
inputs=["Preprocessor"],
|
||||
)
|
||||
|
||||
# Then we run it with the documents and their metadata as input
|
||||
result = indexing_pipeline.run(file_paths=file_paths, meta=files_metadata)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Haystack 2.x</summary>
|
||||
|
||||
```python
|
||||
from haystack import Pipeline
|
||||
from haystack.components.routers import FileTypeRouter
|
||||
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
||||
from haystack.components.converters import TextFileToDocument
|
||||
from haystack.components.preprocessors import DocumentCleaner, DocumentSplitter
|
||||
from haystack.components.writers import DocumentWriter
|
||||
|
||||
# Initialize a DocumentStore
|
||||
document_store = InMemoryDocumentStore()
|
||||
|
||||
# Indexing Pipeline
|
||||
indexing_pipeline = Pipeline()
|
||||
|
||||
# Makes sure the file is a TXT file (FileTypeRouter component)
|
||||
classifier = FileTypeRouter(mime_types=["text/plain"])
|
||||
indexing_pipeline.add_component("file_type_router", classifier)
|
||||
|
||||
# Converts a file into a Document (TextFileToDocument component)
|
||||
text_converter = TextFileToDocument()
|
||||
indexing_pipeline.add_component("text_converter", text_converter)
|
||||
|
||||
# Performs basic cleaning (DocumentCleaner component)
|
||||
cleaner = DocumentCleaner(
|
||||
remove_empty_lines=True,
|
||||
remove_extra_whitespaces=True,
|
||||
)
|
||||
indexing_pipeline.add_component("cleaner", cleaner)
|
||||
|
||||
# Pre-processes the text by performing splits and adding metadata to the text (DocumentSplitter component)
|
||||
preprocessor = DocumentSplitter(split_by="passage", split_length=100, split_overlap=50)
|
||||
indexing_pipeline.add_component("preprocessor", preprocessor)
|
||||
|
||||
# - Writes the resulting documents into the document store
|
||||
indexing_pipeline.add_component("writer", DocumentWriter(document_store))
|
||||
|
||||
# Connect all the components
|
||||
indexing_pipeline.connect("file_type_router.text/plain", "text_converter")
|
||||
indexing_pipeline.connect("text_converter", "cleaner")
|
||||
indexing_pipeline.connect("cleaner", "preprocessor")
|
||||
indexing_pipeline.connect("preprocessor", "writer")
|
||||
|
||||
# Then we run it with the documents and their metadata as input
|
||||
result = indexing_pipeline.run({"file_type_router": {"sources": file_paths}})
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Query Pipeline
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Haystack 1.x</summary>
|
||||
|
||||
```python
|
||||
from haystack.document_stores import InMemoryDocumentStore
|
||||
from haystack.pipelines import ExtractiveQAPipeline
|
||||
from haystack import Document
|
||||
from haystack.nodes import BM25Retriever
|
||||
from haystack.nodes import FARMReader
|
||||
|
||||
document_store = InMemoryDocumentStore(use_bm25=True)
|
||||
document_store.write_documents(
|
||||
[
|
||||
Document(content="Paris is the capital of France."),
|
||||
Document(content="Berlin is the capital of Germany."),
|
||||
Document(content="Rome is the capital of Italy."),
|
||||
Document(content="Madrid is the capital of Spain."),
|
||||
],
|
||||
)
|
||||
|
||||
retriever = BM25Retriever(document_store=document_store)
|
||||
reader = FARMReader(model_name_or_path="deepset/roberta-base-squad2")
|
||||
extractive_qa_pipeline = ExtractiveQAPipeline(reader, retriever)
|
||||
|
||||
query = "What is the capital of France?"
|
||||
result = extractive_qa_pipeline.run(
|
||||
query=query,
|
||||
params={"Retriever": {"top_k": 10}, "Reader": {"top_k": 5}},
|
||||
)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Haystack 2.x</summary>
|
||||
|
||||
```python
|
||||
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
||||
from haystack import Document, Pipeline
|
||||
from haystack.components.retrievers.in_memory import InMemoryBM25Retriever
|
||||
from haystack.components.readers import ExtractiveReader
|
||||
|
||||
document_store = InMemoryDocumentStore()
|
||||
document_store.write_documents(
|
||||
[
|
||||
Document(content="Paris is the capital of France."),
|
||||
Document(content="Berlin is the capital of Germany."),
|
||||
Document(content="Rome is the capital of Italy."),
|
||||
Document(content="Madrid is the capital of Spain."),
|
||||
],
|
||||
)
|
||||
|
||||
retriever = InMemoryBM25Retriever(document_store)
|
||||
reader = ExtractiveReader(model="deepset/roberta-base-squad2")
|
||||
extractive_qa_pipeline = Pipeline()
|
||||
extractive_qa_pipeline.add_component("retriever", retriever)
|
||||
extractive_qa_pipeline.add_component("reader", reader)
|
||||
extractive_qa_pipeline.connect("retriever", "reader")
|
||||
|
||||
query = "What is the capital of France?"
|
||||
result = extractive_qa_pipeline.run(
|
||||
data={
|
||||
"retriever": {"query": query, "top_k": 3},
|
||||
"reader": {"query": query, "top_k": 2},
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### RAG Pipeline
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Haystack 1.x</summary>
|
||||
|
||||
```python
|
||||
from datasets import load_dataset
|
||||
|
||||
from haystack.pipelines import Pipeline
|
||||
from haystack.document_stores import InMemoryDocumentStore
|
||||
from haystack.nodes import EmbeddingRetriever, PromptNode, PromptTemplate, AnswerParser
|
||||
|
||||
document_store = InMemoryDocumentStore(embedding_dim=384)
|
||||
dataset = load_dataset("bilgeyucel/seven-wonders", split="train")
|
||||
document_store.write_documents(dataset)
|
||||
retriever = EmbeddingRetriever(
|
||||
embedding_model="sentence-transformers/all-MiniLM-L6-v2",
|
||||
document_store=document_store,
|
||||
top_k=2,
|
||||
)
|
||||
document_store.update_embeddings(retriever)
|
||||
|
||||
rag_prompt = PromptTemplate(
|
||||
prompt="""Synthesize a comprehensive answer from the following text for the given question.
|
||||
Provide a clear and concise response that summarizes the key points and information presented in the text.
|
||||
Your answer should be in your own words and be no longer than 50 words.
|
||||
\n\n Related text: {join(documents)} \n\n Question: {query} \n\n Answer:""",
|
||||
output_parser=AnswerParser(),
|
||||
)
|
||||
|
||||
prompt_node = PromptNode(
|
||||
model_name_or_path="gpt-3.5-turbo",
|
||||
api_key=OPENAI_API_KEY,
|
||||
default_prompt_template=rag_prompt,
|
||||
)
|
||||
|
||||
pipe = Pipeline()
|
||||
pipe.add_node(component=retriever, name="retriever", inputs=["Query"])
|
||||
pipe.add_node(component=prompt_node, name="prompt_node", inputs=["retriever"])
|
||||
|
||||
output = pipe.run(query="What does Rhodes Statue look like?")
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Haystack 2.x</summary>
|
||||
|
||||
```python
|
||||
from datasets import load_dataset
|
||||
|
||||
from haystack import Document, Pipeline
|
||||
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
||||
from haystack.components.builders import PromptBuilder
|
||||
from haystack.components.generators import OpenAIGenerator
|
||||
from haystack.components.embedders import SentenceTransformersDocumentEmbedder
|
||||
from haystack.components.embedders import SentenceTransformersTextEmbedder
|
||||
from haystack.components.retrievers import InMemoryEmbeddingRetriever
|
||||
|
||||
document_store = InMemoryDocumentStore()
|
||||
dataset = load_dataset("bilgeyucel/seven-wonders", split="train")
|
||||
embedder = SentenceTransformersDocumentEmbedder(
|
||||
"sentence-transformers/all-MiniLM-L6-v2",
|
||||
)
|
||||
embedder.warm_up()
|
||||
output = embedder.run([Document(**ds) for ds in dataset])
|
||||
document_store.write_documents(output.get("documents"))
|
||||
|
||||
template = """
|
||||
Given the following information, answer the question.
|
||||
|
||||
Context:
|
||||
{% for document in documents %}
|
||||
{{ document.content }}
|
||||
{% endfor %}
|
||||
|
||||
Question: {{question}}
|
||||
Answer:
|
||||
"""
|
||||
prompt_builder = PromptBuilder(template=template)
|
||||
|
||||
retriever = InMemoryEmbeddingRetriever(document_store=document_store, top_k=2)
|
||||
generator = OpenAIGenerator(model="gpt-3.5-turbo")
|
||||
query_embedder = SentenceTransformersTextEmbedder(
|
||||
model="sentence-transformers/all-MiniLM-L6-v2",
|
||||
)
|
||||
|
||||
basic_rag_pipeline = Pipeline()
|
||||
basic_rag_pipeline.add_component("text_embedder", query_embedder)
|
||||
basic_rag_pipeline.add_component("retriever", retriever)
|
||||
basic_rag_pipeline.add_component("prompt_builder", prompt_builder)
|
||||
basic_rag_pipeline.add_component("llm", generator)
|
||||
|
||||
basic_rag_pipeline.connect("text_embedder.embedding", "retriever.query_embedding")
|
||||
basic_rag_pipeline.connect("retriever", "prompt_builder.documents")
|
||||
basic_rag_pipeline.connect("prompt_builder", "llm")
|
||||
|
||||
query = "What does Rhodes Statue look like?"
|
||||
output = basic_rag_pipeline.run(
|
||||
{"text_embedder": {"text": query}, "prompt_builder": {"question": query}},
|
||||
)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Documentation and Tutorials 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`.
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
title: "Telemetry"
|
||||
id: telemetry
|
||||
slug: "/telemetry"
|
||||
description: "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."
|
||||
---
|
||||
|
||||
# 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.
|
||||
@@ -0,0 +1,470 @@
|
||||
---
|
||||
title: "Agent"
|
||||
id: agent
|
||||
slug: "/agent"
|
||||
description: "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`."
|
||||
---
|
||||
|
||||
# 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`.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 |
|
||||
| **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` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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,
|
||||
- Additional dynamic keys based on `state_schema`.
|
||||
|
||||
## Parameters
|
||||
|
||||
`chat_generator` is the only mandatory parameter — an instance of a Chat Generator that supports tools. All other parameters are optional.
|
||||
|
||||
- `tools`: A list of tool or toolset instances the agent can call. Supported types: [`Tool`](../../tools/tool.mdx), [`ComponentTool`](../../tools/componenttool.mdx), [`PipelineTool`](../../tools/pipelinetool.mdx), [`MCPTool`](../../tools/mcptool.mdx), [`Toolset`](../../tools/toolset.mdx), [`MCPToolset`](../../tools/mcptoolset.mdx), [`SearchableToolset`](../../tools/searchabletoolset.mdx).
|
||||
- `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”]`.
|
||||
- `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.
|
||||
- `confirmation_strategies`: A dict mapping tool names (or tuples of tool names) to a `ConfirmationStrategy`, enabling human review of tool calls before execution. See [Human in the Loop](./human-in-the-loop.mdx).
|
||||
- `tool_invoker_kwargs`: Additional keyword arguments forwarded to the internal `ToolInvoker`.
|
||||
|
||||
### 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}`).
|
||||
|
||||
:::info
|
||||
For the full parameter reference, see the [Agents API Documentation](/reference/agents-api).
|
||||
:::
|
||||
|
||||
## 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.
|
||||
|
||||
<details>
|
||||
<summary>View YAML</summary>
|
||||
|
||||
```yaml
|
||||
components:
|
||||
agent:
|
||||
init_parameters:
|
||||
chat_generator:
|
||||
init_parameters:
|
||||
api_base_url: null
|
||||
api_key:
|
||||
env_vars:
|
||||
- OPENAI_API_KEY
|
||||
strict: true
|
||||
type: env_var
|
||||
generation_kwargs: {}
|
||||
http_client_kwargs: null
|
||||
max_retries: null
|
||||
model: gpt-5.4-nano
|
||||
organization: null
|
||||
streaming_callback: null
|
||||
timeout: null
|
||||
tools: null
|
||||
tools_strict: false
|
||||
type: haystack.components.generators.chat.openai.OpenAIChatGenerator
|
||||
confirmation_strategies: null
|
||||
exit_conditions:
|
||||
- text
|
||||
max_agent_steps: 5
|
||||
raise_on_tool_invocation_failure: false
|
||||
required_variables: null
|
||||
state_schema: {}
|
||||
streaming_callback: null
|
||||
system_prompt: You are a helpful assistant. Use the web search tool to find
|
||||
information when needed.
|
||||
tool_invoker_kwargs: null
|
||||
tools:
|
||||
- data:
|
||||
component:
|
||||
init_parameters:
|
||||
allowed_domains: null
|
||||
api_key:
|
||||
env_vars:
|
||||
- SERPERDEV_API_KEY
|
||||
strict: true
|
||||
type: env_var
|
||||
exclude_subdomains: false
|
||||
search_params: {}
|
||||
top_k: 3
|
||||
type: haystack.components.websearch.serper_dev.SerperDevWebSearch
|
||||
description: Search the web for current information on any topic
|
||||
inputs_from_state: null
|
||||
name: web_search
|
||||
outputs_to_state: null
|
||||
outputs_to_string: null
|
||||
parameters: null
|
||||
type: haystack.tools.component_tool.ComponentTool
|
||||
user_prompt: null
|
||||
type: haystack.components.agents.agent.Agent
|
||||
converter:
|
||||
init_parameters:
|
||||
extraction_kwargs: {}
|
||||
store_full_path: false
|
||||
type: haystack.components.converters.html.HTMLToDocument
|
||||
fetcher:
|
||||
init_parameters:
|
||||
client_kwargs:
|
||||
follow_redirects: true
|
||||
timeout: 3
|
||||
http2: false
|
||||
raise_on_failure: true
|
||||
request_headers: {}
|
||||
retry_attempts: 2
|
||||
timeout: 3
|
||||
user_agents:
|
||||
- haystack/LinkContentFetcher/2.27.0rc0
|
||||
type: haystack.components.fetchers.link_content.LinkContentFetcher
|
||||
prompt_builder:
|
||||
init_parameters:
|
||||
required_variables:
|
||||
- docs
|
||||
- query
|
||||
template:
|
||||
- content:
|
||||
- text: 'Based on the following content:
|
||||
|
||||
{% for doc in docs %}
|
||||
|
||||
{{ doc.content }}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
Answer this question: {{ query }}'
|
||||
meta: {}
|
||||
name: null
|
||||
role: user
|
||||
variables: null
|
||||
type: haystack.components.builders.chat_prompt_builder.ChatPromptBuilder
|
||||
connection_type_validation: true
|
||||
connections:
|
||||
- receiver: converter.sources
|
||||
sender: fetcher.streams
|
||||
- receiver: prompt_builder.docs
|
||||
sender: converter.documents
|
||||
- receiver: agent.messages
|
||||
sender: prompt_builder.prompt
|
||||
max_runs_per_component: 100
|
||||
metadata: {}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## 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 `ToolInvoker` 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.
|
||||
|
||||
See [Multi-Agent Systems](../../concepts/agents/multi-agent-systems.mdx) for a full guide, including the recommended `@tool` decorator approach for full interface control and `ComponentTool` for declarative configuration.
|
||||
|
||||
## 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
|
||||
- [Human in the Loop](./human-in-the-loop.mdx) — intercepting tool calls for human review
|
||||
|
||||
📚 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)
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
---
|
||||
title: "Human in the Loop"
|
||||
id: human-in-the-loop
|
||||
slug: "/human-in-the-loop"
|
||||
description: "Human-in-the-loop allows you to intercept agent tool calls before execution, letting a human confirm, reject, or modify the tool parameters."
|
||||
---
|
||||
|
||||
# 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.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Configured on** | The [`Agent`](./agent.mdx) component via `confirmation_strategies` |
|
||||
| **Key classes** | `BlockingConfirmationStrategy`, `AlwaysAskPolicy`, `AskOncePolicy`, `NeverAskPolicy`, `RichConsoleUI`, `SimpleConsoleUI` |
|
||||
| **Import path** | `haystack.human_in_the_loop` |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/human_in_the_loop/ |
|
||||
| **Package name** | `haystack-ai` |
|
||||
|
||||
</div>
|
||||
|
||||
## Overview
|
||||
|
||||
The HITL system is composed of three layers:
|
||||
|
||||
- **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.
|
||||
|
||||
## 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.human_in_the_loop import (
|
||||
AlwaysAskPolicy,
|
||||
BlockingConfirmationStrategy,
|
||||
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],
|
||||
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.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, or share one strategy across a group of tools using a tuple key:
|
||||
|
||||
```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(),
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
chat_generator=OpenAIChatGenerator(model="gpt-5.4-mini"),
|
||||
tools=[send_email, delete_record, update_record, search],
|
||||
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
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### 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.human_in_the_loop.types`:
|
||||
|
||||
```python
|
||||
from haystack.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.human_in_the_loop.types import ConfirmationPolicy
|
||||
from haystack.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 uses `confirmation_strategy_context` to pass per-request resources - a Redis client and an async event queue - into the strategy at runtime:
|
||||
|
||||
- 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.human_in_the_loop.types` to build your own interface - for example, a web-based approval queue:
|
||||
|
||||
```python
|
||||
from haystack.human_in_the_loop.types import ConfirmationUI
|
||||
from haystack.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"),
|
||||
)
|
||||
```
|
||||
@@ -0,0 +1,415 @@
|
||||
---
|
||||
title: "State"
|
||||
id: state
|
||||
slug: "/state"
|
||||
description: "`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."
|
||||
---
|
||||
|
||||
# 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.
|
||||
|
||||
### 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 ToolInvoker 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"])
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "Audio"
|
||||
id: audio
|
||||
slug: "/audio"
|
||||
description: "Use these components to work with audio in Haystack by transcribing files or converting text to audio."
|
||||
---
|
||||
|
||||
# Audio
|
||||
|
||||
Use these components to work with audio in Haystack by transcribing files or converting text to audio.
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| [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. |
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "External Integrations"
|
||||
id: external-integrations-audio
|
||||
slug: "/external-integrations-audio"
|
||||
description: "External integrations that enable working with audio in Haystack by transcribing files or converting text to 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. |
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: "LocalWhisperTranscriber"
|
||||
id: localwhispertranscriber
|
||||
slug: "/localwhispertranscriber"
|
||||
description: "Use `LocalWhisperTranscriber` to transcribe audio files using OpenAI's Whisper model using your local installation of Whisper."
|
||||
---
|
||||
|
||||
# LocalWhisperTranscriber
|
||||
|
||||
Use `LocalWhisperTranscriber` to transcribe audio files using OpenAI's Whisper model using your local installation of Whisper.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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** | [Audio](/reference/audio-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/audio/whisper_local.py |
|
||||
| **Package name** | `haystack-ai` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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/audio-api).
|
||||
|
||||
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.
|
||||
|
||||
To work with the `LocalWhisperTranscriber`, install torch and [Whisper](https://github.com/openai/whisper) first with the following commands:
|
||||
|
||||
```python
|
||||
pip install 'transformers[torch]'
|
||||
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.components.audio 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.components.audio 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)
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
---
|
||||
title: "RemoteWhisperTranscriber"
|
||||
id: remotewhispertranscriber
|
||||
slug: "/remotewhispertranscriber"
|
||||
description: "Use `RemoteWhisperTranscriber` to transcribe audio files using OpenAI's Whisper model."
|
||||
---
|
||||
|
||||
# RemoteWhisperTranscriber
|
||||
|
||||
Use `RemoteWhisperTranscriber` to transcribe audio files using OpenAI's Whisper model.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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** | [Audio](/reference/audio-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/audio/whisper_remote.py |
|
||||
| **Package name** | `haystack-ai` |
|
||||
|
||||
</div>
|
||||
|
||||
## Overview
|
||||
|
||||
`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-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.components.audio 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/audio-api).
|
||||
|
||||
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.components.audio 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.components.audio 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)
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: "Builders"
|
||||
id: builders
|
||||
slug: "/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. |
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: "AnswerBuilder"
|
||||
id: answerbuilder
|
||||
slug: "/answerbuilder"
|
||||
description: "Use this component in pipelines that contain a Generator to parse its replies."
|
||||
---
|
||||
|
||||
# AnswerBuilder
|
||||
|
||||
Use this component in pipelines that contain a Generator to parse its replies.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 <br /> <br />`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` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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)
|
||||
```
|
||||
+451
@@ -0,0 +1,451 @@
|
||||
---
|
||||
title: "ChatPromptBuilder"
|
||||
id: chatpromptbuilder
|
||||
slug: "/chatpromptbuilder"
|
||||
description: "This component constructs prompts dynamically by processing chat messages."
|
||||
---
|
||||
|
||||
# ChatPromptBuilder
|
||||
|
||||
This component constructs prompts dynamically by processing chat messages.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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. If there are no `required_vairables` set, all variables are considered optional by default. In this case, any missing variables are replaced with empty strings, which can lead to unintended behavior, especially in complex pipelines.
|
||||
|
||||
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:
|
||||
- Pass a list of required variable names (such as `["name"]`), or
|
||||
- Use `"*"` to mark all variables in the template as required.
|
||||
|
||||
- `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.
|
||||
|
||||
### 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.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: null
|
||||
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)
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
---
|
||||
title: "PromptBuilder"
|
||||
id: promptbuilder
|
||||
slug: "/promptbuilder"
|
||||
description: "Use this component in pipelines before a Generator to render a prompt template and fill in variable values."
|
||||
---
|
||||
|
||||
# PromptBuilder
|
||||
|
||||
Use this component in pipelines before a Generator to render a prompt template and fill in variable values.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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. If there are no `required_variables` set, all variables are considered optional by default. In this case, any missing variables are replaced with empty strings, which can lead to unintended behavior, especially in complex pipelines.
|
||||
|
||||
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:
|
||||
- Pass a list of required variable names (such as `["query"]`), or
|
||||
- Use `"*"` to mark all variables in the template as required.
|
||||
|
||||
- `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 optional (default to empty string)
|
||||
builder = PromptBuilder(
|
||||
template="Hello {{name}}! {{greeting}}",
|
||||
required_variables=[], # or omit this parameter entirely
|
||||
)
|
||||
|
||||
# Some variables required
|
||||
builder = PromptBuilder(
|
||||
template="Hello {{name}}! {{greeting}}",
|
||||
required_variables=["name"], # 'greeting' remains optional
|
||||
)
|
||||
```
|
||||
|
||||
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 import OpenAIGenerator
|
||||
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=OpenAIGenerator(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
|
||||
system_prompt: null
|
||||
timeout: null
|
||||
type: haystack.components.generators.openai.OpenAIGenerator
|
||||
prompt_builder:
|
||||
init_parameters:
|
||||
required_variables: null
|
||||
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.prompt
|
||||
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)
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
---
|
||||
title: "CacheChecker"
|
||||
id: cachechecker
|
||||
slug: "/cachechecker"
|
||||
description: "This component checks for the presence of documents in a Document Store based on a specified cache field."
|
||||
---
|
||||
|
||||
# CacheChecker
|
||||
|
||||
This component checks for the presence of documents in a Document Store based on a specified cache field.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Most common position in a pipeline** | Flexible |
|
||||
| **Mandatory init variables** | `document_store`: A Document Store instance <br /> <br />`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 <br /> <br />`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` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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': <one of the above> 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': <one of the above> 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)
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "Classifiers"
|
||||
id: classifiers
|
||||
slug: "/classifiers"
|
||||
description: "Use Classifiers to classify your documents by specific traits and update the metadata."
|
||||
---
|
||||
|
||||
# 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. |
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
---
|
||||
title: "DocumentLanguageClassifier"
|
||||
id: documentlanguageclassifier
|
||||
slug: "/documentlanguageclassifier"
|
||||
description: "Use this component to classify documents by language and add language information to metadata."
|
||||
---
|
||||
|
||||
# DocumentLanguageClassifier
|
||||
|
||||
Use this component to classify documents by language and add language information to metadata.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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** | [Classifiers](/reference/classifiers-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/classifiers/document_language_classifier.py |
|
||||
| **Package name** | `haystack-ai` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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`package to use the `DocumentLanguageClassifier`component:
|
||||
|
||||
```shell shell
|
||||
pip install langdetect
|
||||
```
|
||||
|
||||
### On its own
|
||||
|
||||
Below, we are using the `DocumentLanguageClassifier` to classify English and German documents:
|
||||
|
||||
```python
|
||||
from haystack.components.classifiers 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.components.classifiers import DocumentLanguageClassifier
|
||||
from haystack.components.embedders 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."),
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
```
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: "TransformersZeroShotDocumentClassifier"
|
||||
id: transformerszeroshotdocumentclassifier
|
||||
slug: "/transformerszeroshotdocumentclassifier"
|
||||
description: "Classifies the documents based on the provided labels and adds them to their metadata."
|
||||
---
|
||||
|
||||
# TransformersZeroShotDocumentClassifier
|
||||
|
||||
Classifies the documents based on the provided labels and adds them to their metadata.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 <br /> <br />`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** | [Classifiers](/reference/classifiers-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/classifiers/zero_shot_document_classifier.py |
|
||||
| **Package name** | `haystack-ai` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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
|
||||
|
||||
### On its own
|
||||
|
||||
```python
|
||||
from haystack import Document
|
||||
from haystack.components.classifiers 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.components.classifiers 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(retriever, name="retriever")
|
||||
pipeline.add_component(document_classifier, name="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]
|
||||
)
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: "Connectors"
|
||||
id: connectors
|
||||
slug: "/connectors"
|
||||
description: "These are Haystack integrations that connect your pipelines to services by external providers."
|
||||
---
|
||||
|
||||
# Connectors
|
||||
|
||||
These are Haystack integrations that connect your pipelines to services by external providers.
|
||||
|
||||
| Component | Description |
|
||||
| --- | --- |
|
||||
| [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. |
|
||||
| [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. |
|
||||
| [WeaveConnector](connectors/weaveconnector.mdx) | Connects you to Weights & Biases Weave framework for tracing and monitoring your pipeline components. |
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: "External Integrations"
|
||||
id: external-integrations-connectors
|
||||
slug: "/external-integrations-connectors"
|
||||
description: "External integrations that connect your pipelines to services by external providers."
|
||||
---
|
||||
|
||||
# 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 |
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: "GitHubFileEditor"
|
||||
id: githubfileeditor
|
||||
slug: "/githubfileeditor"
|
||||
description: "This is a component for editing files in GitHub repositories through the GitHub API."
|
||||
---
|
||||
|
||||
# GitHubFileEditor
|
||||
|
||||
This is a component for editing files in GitHub repositories through the GitHub API.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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) <br /> <br />`payload`: Command-specific parameters |
|
||||
| **Output variables** | `result`: String that indicates the operation result |
|
||||
| **API reference** | [GitHub](/reference/integrations-github) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/github |
|
||||
| **Package name** | `github-haystack` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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'}
|
||||
```
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
---
|
||||
title: "GitHubIssueCommenter"
|
||||
id: githubissuecommenter
|
||||
slug: "/githubissuecommenter"
|
||||
description: "This component posts comments to GitHub issues using the GitHub API."
|
||||
---
|
||||
|
||||
# GitHubIssueCommenter
|
||||
|
||||
This component posts comments to GitHub issues using the GitHub API.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 <br /> <br />`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` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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
|
||||
```
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
---
|
||||
title: "GitHubIssueViewer"
|
||||
id: githubissueviewer
|
||||
slug: "/githubissueviewer"
|
||||
description: "This component fetches and parses GitHub issues into Haystack documents."
|
||||
---
|
||||
|
||||
# GitHubIssueViewer
|
||||
|
||||
This component fetches and parses GitHub issues into Haystack documents.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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.
|
||||
|
||||
You can set the token using the `GITHUB_API_KEY` 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).
|
||||
|
||||
### 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])
|
||||
```
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: "GitHubPRCreator"
|
||||
id: githubprcreator
|
||||
slug: "/githubprcreator"
|
||||
description: "This component creates pull requests from a fork back to the original repository through the GitHub API."
|
||||
---
|
||||
|
||||
# GitHubPRCreator
|
||||
|
||||
This component creates pull requests from a fork back to the original repository through the GitHub API.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 <br /> <br />`title`: PR title <br /> <br />`branch`: Source branch <br /> <br />`base`: Target branch |
|
||||
| **Output variables** | `result`: String indicating the pull request creation result |
|
||||
| **API reference** | [GitHub](/reference/integrations-github) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/github |
|
||||
| **Package name** | `github-haystack` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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'}
|
||||
```
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: "GitHubRepoForker"
|
||||
id: githubrepoforker
|
||||
slug: "/githubrepoforker"
|
||||
description: "This component forks a GitHub repository from an issue URL through the GitHub API."
|
||||
---
|
||||
|
||||
# GitHubRepoForker
|
||||
|
||||
This component forks a GitHub repository from an issue URL through the GitHub API.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 <br /> <br />`issue_branch`: Issue-specific branch name (if created) |
|
||||
| **API reference** | [GitHub](/reference/integrations-github) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/github |
|
||||
| **Package name** | `github-haystack` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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'}
|
||||
```
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
---
|
||||
title: "GitHubRepoViewer"
|
||||
id: githubrepoviewer
|
||||
slug: "/githubrepoviewer"
|
||||
description: "This component navigates and fetches content from GitHub repositories through the GitHub API."
|
||||
---
|
||||
|
||||
# GitHubRepoViewer
|
||||
|
||||
This component navigates and fetches content from GitHub repositories through the GitHub API.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 <br /> <br />`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` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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.
|
||||
|
||||
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).
|
||||
|
||||
### 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: '<div align="center">
|
||||
<a href="https://haystack.deepset.ai/"><img src="https://raw.githubuserconten...', meta: {'path': 'README.md', 'type': 'file_content', 'size': 11979, 'url': 'https://github.com/deepset-ai/haystack/blob/main/README.md'})]}
|
||||
```
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
---
|
||||
title: "JinaReaderConnector"
|
||||
id: jinareaderconnector
|
||||
slug: "/jinareaderconnector"
|
||||
description: "Use Jina AI’s Reader API with Haystack."
|
||||
---
|
||||
|
||||
# JinaReaderConnector
|
||||
|
||||
Use Jina AI’s Reader API with Haystack.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Most common position in a pipeline** | As the first component in a pipeline that passes the resulting document downstream |
|
||||
| **Mandatory init variables** | `mode`: The operation mode for the reader (`read`, `search`, or `ground`) <br /> <br />`api_key`: The Jina API key. Can be set with `JINA_API_KEY` env var. |
|
||||
| **Mandatory run variables** | `query`: A query string |
|
||||
| **Output variables** | `documents`: A list of documents |
|
||||
| **API reference** | [Jina](/reference/integrations-jina) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/jina |
|
||||
| **Package name** | `jina-haystack` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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
|
||||
ranker = JinaRanker(api_key=Secret.from_token("<your-api-key>"))
|
||||
```
|
||||
|
||||
To get your API key, head to Jina AI’s [website](https://jina.ai/reranker/).
|
||||
|
||||
### 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("<your-api-key>"),
|
||||
)
|
||||
|
||||
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': ['The most famous landmark in Berlin is the **Brandenburg Gate**. It is considered the symbol of the city and represents reunification.'], 'meta': [{'model': 'gpt-4o-mini-2024-07-18', 'index': 0, 'finish_reason': 'stop', 'usage': {'completion_tokens': 27, 'prompt_tokens': 4479, 'total_tokens': 4506, 'completion_tokens_details': CompletionTokensDetails(accepted_prediction_tokens=0, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=0), 'prompt_tokens_details': PromptTokensDetails(audio_tokens=0, cached_tokens=0)}}]}}
|
||||
```
|
||||
|
||||
The same component in search mode could also be used in an indexing pipeline.
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
---
|
||||
title: "LangfuseConnector"
|
||||
id: langfuseconnector
|
||||
slug: "/langfuseconnector"
|
||||
description: "Learn how to work with Langfuse in Haystack."
|
||||
---
|
||||
|
||||
# LangfuseConnector
|
||||
|
||||
Learn how to work with Langfuse in Haystack.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 <br /> <br />`trace_url`: A link to the tracing data |
|
||||
| **API reference** | [langfuse](/reference/integrations-langfuse) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/langfuse |
|
||||
| **Package name** | `langfuse-haystack` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
:::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.tracing.langfuse import (
|
||||
LangfuseConnector,
|
||||
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(span_handler=CustomSpanHandler())
|
||||
```
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
---
|
||||
title: "OpenAPIConnector"
|
||||
id: openapiconnector
|
||||
slug: "/openapiconnector"
|
||||
description: "`OpenAPIConnector` is a component that acts as an interface between the Haystack ecosystem and OpenAPI services."
|
||||
---
|
||||
|
||||
# OpenAPIConnector
|
||||
|
||||
`OpenAPIConnector` is a component that acts as an interface between the Haystack ecosystem and OpenAPI services.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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** | [Connectors](/reference/connectors-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/connectors/openapi.py |
|
||||
| **Package name** | `haystack-ai` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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-llm` dependency installed:
|
||||
|
||||
```shell
|
||||
pip install openapi-llm
|
||||
```
|
||||
|
||||
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.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.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)
|
||||
```
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
---
|
||||
title: "OpenAPIServiceConnector"
|
||||
id: openapiserviceconnector
|
||||
slug: "/openapiserviceconnector"
|
||||
description: "`OpenAPIServiceConnector` is a component that acts as an interface between the Haystack ecosystem and OpenAPI services."
|
||||
---
|
||||
|
||||
# OpenAPIServiceConnector
|
||||
|
||||
`OpenAPIServiceConnector` is a component that acts as an interface between the Haystack ecosystem and OpenAPI services.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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. <br /> <br />`service_openapi_spec`: OpenAPI specification of the service being invoked. It can be YAML/JSON, and all ref values must be resolved. <br /> <br />`service_credentials`: Authentication credentials for the service. We currently support two OpenAPI spec v3 security schemes: <br /> <br />1. http – for Basic, Bearer, and other HTTP authentication schemes; <br />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. <br />If a message contains multiple tool calls, there will be multiple responses. |
|
||||
| **API reference** | [Connectors](/reference/connectors-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/connectors/openapi_service.py |
|
||||
| **Package name** | `haystack-ai` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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 optional `openapi3` dependency with:
|
||||
|
||||
```shell
|
||||
pip install openapi3
|
||||
```
|
||||
|
||||
`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.connectors import OpenAPIServiceConnector
|
||||
from haystack.components.converters import OpenAPIServiceToFunctions, OutputAdapter
|
||||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||||
from haystack.dataclasses import ChatMessage
|
||||
from haystack.dataclasses.byte_stream import ByteStream
|
||||
|
||||
|
||||
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.
|
||||
```
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
---
|
||||
title: "WeaveConnector"
|
||||
id: weaveconnector
|
||||
slug: "/weaveconnector"
|
||||
description: "Learn how to use Weights & Biases Weave framework for tracing and monitoring your pipeline components."
|
||||
---
|
||||
|
||||
# WeaveConnector
|
||||
|
||||
Learn how to use Weights & Biases Weave framework for tracing and monitoring your pipeline components.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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/<user_name>/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 `weights_biases-haystack` package to use this connector:
|
||||
|
||||
```shell
|
||||
pip install weights_biases-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/<user_name>/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/<user_name>/projects to see the traces!",
|
||||
)
|
||||
```
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: "Converters"
|
||||
id: converters
|
||||
slug: "/converters"
|
||||
description: "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."
|
||||
---
|
||||
|
||||
# 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. |
|
||||
| [MarkdownToDocument](converters/markdowntodocument.mdx) | Converts markdown files to documents. |
|
||||
| [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. |
|
||||
| [UnstructuredFileConverter](converters/unstructuredfileconverter.mdx) | Converts text files and directories to a document. |
|
||||
| [XLSXToDocument](converters/xlsxtodocument.mdx) | Converts Excel files into documents. |
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
---
|
||||
title: "AmazonTextractConverter"
|
||||
id: amazontextractconverter
|
||||
slug: "/amazontextractconverter"
|
||||
description: "`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."
|
||||
---
|
||||
|
||||
# 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.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 <br /> <br />`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` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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"])
|
||||
converter.warm_up()
|
||||
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}})
|
||||
```
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
---
|
||||
title: "AzureDocumentIntelligenceConverter"
|
||||
id: azuredocumentintelligenceconverter
|
||||
slug: "/azuredocumentintelligenceconverter"
|
||||
description: "`AzureDocumentIntelligenceConverter` converts files to Documents using Azure's Document Intelligence service with GitHub Flavored Markdown output for better LLM/RAG integration. It supports PDF, JPEG, PNG, BMP, TIFF, DOCX, XLSX, PPTX, and HTML."
|
||||
---
|
||||
|
||||
# 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.
|
||||
|
||||
<div className="key-value-table">
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 <br /> <br />`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 <br /> <br />`raw_azure_response`: A list of raw responses from Azure |
|
||||
| **API reference** | [Azure Document Intelligence](https://github.com/deepset-ai/haystack-core-integrations/tree/main/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` |
|
||||
|
||||
</div>
|
||||
|
||||
## 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}})
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user