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,209 @@
|
||||
---
|
||||
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).
|
||||
|
||||
### Understanding AI Agents
|
||||
|
||||
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.
|
||||
|
||||
### What Makes an AI Agent
|
||||
|
||||
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.
|
||||
|
||||
### How AI Agents Work
|
||||
|
||||
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. It evaluates progress and adjusts its strategy to improve results.
|
||||
|
||||
For example, a customer service agent answers queries using a database. If it lacks an answer, it fetches real-time data, summarizes it, and provides a response. A coding assistant understands project requirements, suggests solutions, and even writes code.
|
||||
|
||||
## Key Components
|
||||
|
||||
### Agents
|
||||
|
||||
Haystack has a universal [Agent](../pipeline-components/agents-1/agent.mdx) component that interacts with chat-based LLMs and tools to solve complex queries. It requires a Chat Generator that supports tools to work and can be customizable according to your needs. Check out the [Agent](../pipeline-components/agents-1/agent.mdx) documentation, or the [example](#tool-calling-agent) below to see how it works.
|
||||
|
||||
### Additional Components
|
||||
|
||||
You can build an AI agent in Haystack yourself, using the three main elements in a pipeline:
|
||||
|
||||
- [Chat Generators](../pipeline-components/generators.mdx) to generate tool calls (with tool name and arguments) or assistant responses with an LLM,
|
||||
- [`Tool`](../tools/tool.mdx) class that allows the LLM to perform actions such as running a pipeline or calling an external API, connecting to the external world,
|
||||
- [`ToolInvoker`](../pipeline-components/tools/toolinvoker.mdx) component to execute tool calls generated by an LLM. It parses the LLM's tool-calling responses and invokes the appropriate tool with the correct arguments from the pipeline.
|
||||
|
||||
There are three ways of creating a tool in Haystack:
|
||||
|
||||
- [`Tool`](../tools/tool.mdx) class – Creates a tool representation for a consistent tool-calling experience across all Generators. It allows for most customization, as you can define its own name and description.
|
||||
- [`ComponentTool`](../tools/componenttool.mdx) class – Wraps a Haystack component as a callable tool.
|
||||
- [`@tool`](../tools/tool.mdx#tool-decorator) decorator – Creates tools from Python functions and automatically uses their function name and docstring.
|
||||
- [Toolset](../tools/toolset.mdx) – A container for grouping multiple tools that can be passed directly to Agents or Generators.
|
||||
|
||||
## Example Agents
|
||||
|
||||
### Tool-Calling Agent
|
||||
|
||||
You can create a similar tool-calling agent with the `Agent` component:
|
||||
|
||||
```python
|
||||
from haystack.components.agents import Agent
|
||||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||||
from haystack.components.websearch import SerperDevWebSearch
|
||||
from haystack.dataclasses import Document, ChatMessage
|
||||
from haystack.tools.component_tool import ComponentTool
|
||||
|
||||
from typing import List
|
||||
|
||||
## Create the web search component
|
||||
web_search = SerperDevWebSearch(top_k=3)
|
||||
|
||||
## Create the ComponentTool with simpler parameters
|
||||
web_tool = ComponentTool(
|
||||
component=web_search,
|
||||
name="web_search",
|
||||
description="Search the web for current information like weather, news, or facts.",
|
||||
)
|
||||
|
||||
## Create the agent with the web tool
|
||||
tool_calling_agent = Agent(
|
||||
chat_generator=OpenAIChatGenerator(model="gpt-4o-mini"),
|
||||
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.
|
||||
When you get web search results, extract the relevant information and present it in a clear,
|
||||
concise manner.""",
|
||||
tools=[web_tool],
|
||||
)
|
||||
|
||||
## Run the agent with the user message
|
||||
user_message = ChatMessage.from_user("How is the weather in Berlin?")
|
||||
result = tool_calling_agent.run(messages=[user_message])
|
||||
|
||||
## Print the result - using .text instead of .content
|
||||
print(result["messages"][-1].text)
|
||||
```
|
||||
|
||||
Resulting in:
|
||||
|
||||
```python
|
||||
|
||||
- **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).
|
||||
```
|
||||
|
||||
### Pipeline With Tools
|
||||
|
||||
Here’s an example of how you would build a tool-calling agent with the help of `ToolInvoker`.
|
||||
|
||||
This is what’s happening in this code example:
|
||||
|
||||
1. `OpenAIChatGenerator` uses an LLM to analyze the user's message and determines whether to provide an assistant response or initiate a tool call.
|
||||
2. `ConditionalRouter` directs the output from the `OpenAIChatGenerator` to `there_are_tool_calls` branch if it’s a tool call or to `final_replies` to return to the user directly.
|
||||
3. `ToolInvoker` executes the tool call generated by the LLM. `ComponentTool` wraps the `SerperDevWebSearch` component that fetches real-time search results, making it accessible for `ToolInvoker` to execute it as a tool.
|
||||
4. After the tool provides its output, the `ToolInvoker` sends this information back to the `OpenAIChatGenerator`, along with the original user question stored by the `MessageCollector`.
|
||||
|
||||
```python
|
||||
from haystack import component, Pipeline
|
||||
from haystack.components.tools import ToolInvoker
|
||||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||||
from haystack.components.routers import ConditionalRouter
|
||||
from haystack.components.websearch import SerperDevWebSearch
|
||||
from haystack.core.component.types import Variadic
|
||||
from haystack.dataclasses import ChatMessage
|
||||
from haystack.tools import ComponentTool
|
||||
|
||||
from typing import Any, Dict, List
|
||||
|
||||
|
||||
## helper component to temporarily store last user query before the tool call
|
||||
@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 = []
|
||||
|
||||
|
||||
## Create a tool from a component
|
||||
web_tool = ComponentTool(component=SerperDevWebSearch(top_k=3))
|
||||
|
||||
## Define routing conditions
|
||||
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],
|
||||
},
|
||||
]
|
||||
|
||||
## Create the pipeline
|
||||
tool_agent = Pipeline()
|
||||
tool_agent.add_component("message_collector", MessageCollector())
|
||||
tool_agent.add_component(
|
||||
"generator",
|
||||
OpenAIChatGenerator(model="gpt-4o-mini", tools=[web_tool]),
|
||||
)
|
||||
tool_agent.add_component("router", ConditionalRouter(routes, unsafe=True))
|
||||
tool_agent.add_component("tool_invoker", ToolInvoker(tools=[web_tool]))
|
||||
|
||||
tool_agent.connect("generator.replies", "router")
|
||||
tool_agent.connect("router.there_are_tool_calls", "tool_invoker")
|
||||
tool_agent.connect("router.there_are_tool_calls", "message_collector")
|
||||
tool_agent.connect("tool_invoker.tool_messages", "message_collector")
|
||||
tool_agent.connect("message_collector", "generator.messages")
|
||||
|
||||
messages = [
|
||||
ChatMessage.from_system(
|
||||
"You're a helpful agent choosing the right tool when necessary",
|
||||
),
|
||||
ChatMessage.from_user("How is the weather in Berlin?"),
|
||||
]
|
||||
result = tool_agent.run({"messages": messages})
|
||||
|
||||
print(result["router"]["final_replies"][0].text)
|
||||
```
|
||||
|
||||
Resulting in:
|
||||
|
||||
```python
|
||||
|
||||
For more detailed weather updates, you can check the following links:
|
||||
- [AccuWeather](https://www.accuweather.com/en/de/berlin/10178/weather-forecast/178087)
|
||||
- [Weather.com](https://weather.com/weather/today/l/5ca23443513a0fdc1d37ae2ffaf5586162c6fe592a66acc9320a0d0536be1bb9)
|
||||
```
|
||||
@@ -0,0 +1,513 @@
|
||||
---
|
||||
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 maintain conversation history, share data between tools, and store intermediate results throughout an agent's workflow."
|
||||
---
|
||||
|
||||
# State
|
||||
|
||||
`State` is a container for storing shared information during Agent and Tool execution. It provides a structured way to maintain conversation history, share data between tools, and store intermediate results throughout an agent's workflow.
|
||||
|
||||
## Overview
|
||||
|
||||
When building agents that use multiple tools, you often need tools to share information with each other. State solves this problem by providing centralized storage that all tools can read from and write to. For example, one tool might retrieve documents while another tool uses those documents to generate an answer.
|
||||
|
||||
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.
|
||||
|
||||
### 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: `Union[str, int]`, `Optional[str]`,
|
||||
- Custom classes and data classes.
|
||||
|
||||
### Automatic Message Handling
|
||||
|
||||
State automatically includes a `messages` field to store conversation history. You don't need to define this in your schema.
|
||||
|
||||
```python
|
||||
## State automatically adds messages field
|
||||
state = State(schema={"user_id": {"type": str}})
|
||||
|
||||
## The messages field is available
|
||||
print("messages" in state.schema) # True
|
||||
print(state.schema["messages"]["type"]) # list[ChatMessage]
|
||||
|
||||
## Access conversation history
|
||||
messages = state.get("messages", [])
|
||||
```
|
||||
|
||||
The `messages` field uses `list[ChatMessage]` type and `merge_lists` handler by default, which means new messages are appended to the conversation history.
|
||||
|
||||
## Usage
|
||||
|
||||
### Creating State
|
||||
|
||||
Create State by defining a schema that specifies what data can be stored and their types:
|
||||
|
||||
```python
|
||||
from haystack.components.agents.state import State
|
||||
|
||||
## Define the schema
|
||||
schema = {
|
||||
"user_name": {"type": str},
|
||||
"documents": {"type": list},
|
||||
"count": {"type": int},
|
||||
}
|
||||
|
||||
## Create State with initial data
|
||||
state = State(schema=schema, data={"user_name": "Alice", "documents": [], "count": 0})
|
||||
```
|
||||
|
||||
### Reading from State
|
||||
|
||||
Use the `get()` method to retrieve values:
|
||||
|
||||
```python
|
||||
## Get a value
|
||||
user_name = state.get("user_name")
|
||||
|
||||
## Get a value with a default if key doesn't exist
|
||||
documents = state.get("documents", [])
|
||||
|
||||
## Check if a key exists
|
||||
if state.has("user_name"):
|
||||
print(f"User: {state.get('user_name')}")
|
||||
```
|
||||
|
||||
### Writing to State
|
||||
|
||||
Use the `set()` method to store or merge values:
|
||||
|
||||
```python
|
||||
## Set a value
|
||||
state.set("user_name", "Bob")
|
||||
|
||||
## Set list values (these are merged by default)
|
||||
state.set("documents", [{"title": "Doc 1", "content": "Content 1"}])
|
||||
```
|
||||
|
||||
## 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 that defines what kind of data can be stored (for example, `str`, `int`, `list`)
|
||||
- `handler` (optional): A function that determines how new values are merged with existing values when you call `set()`
|
||||
|
||||
```python
|
||||
{
|
||||
"parameter_name": {
|
||||
"type": SomeType, # Required: Expected Python type for this field
|
||||
"handler": Optional[
|
||||
Callable[[Any, Any], Any]
|
||||
], # Optional: Function to merge values
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
If you don't specify a handler, State automatically assigns a default handler based on the type.
|
||||
|
||||
### Default Handlers
|
||||
|
||||
Handlers control how values are merged when you call `set()` on an existing key. State provides two default handlers:
|
||||
|
||||
- `merge_lists`: Combines the lists together (default for list types)
|
||||
- `replace_values`: Overwrites the existing value (default for non-list types)
|
||||
|
||||
```python
|
||||
from haystack.components.agents.state.state_utils import merge_lists, replace_values
|
||||
|
||||
schema = {
|
||||
"documents": {"type": list}, # Uses merge_lists by default
|
||||
"user_name": {"type": str}, # Uses replace_values by default
|
||||
"count": {"type": int}, # Uses replace_values by default
|
||||
}
|
||||
|
||||
state = State(schema=schema)
|
||||
|
||||
## Lists are merged by default
|
||||
state.set("documents", [1, 2])
|
||||
state.set("documents", [3, 4])
|
||||
print(state.get("documents")) # Output: [1, 2, 3, 4]
|
||||
|
||||
## Other values are replaced
|
||||
state.set("user_name", "Alice")
|
||||
state.set("user_name", "Bob")
|
||||
print(state.get("user_name")) # Output: "Bob"
|
||||
```
|
||||
|
||||
### Custom Handlers
|
||||
|
||||
You can define custom handlers for specific merge behavior:
|
||||
|
||||
```python
|
||||
def custom_merge(current_value, new_value):
|
||||
"""Custom handler that merges and sorts lists."""
|
||||
current_list = current_value or []
|
||||
new_list = new_value if isinstance(new_value, list) else [new_value]
|
||||
return sorted(current_list + new_list)
|
||||
|
||||
|
||||
schema = {"numbers": {"type": list, "handler": custom_merge}}
|
||||
|
||||
state = State(schema=schema)
|
||||
state.set("numbers", [3, 1])
|
||||
state.set("numbers", [2, 4])
|
||||
print(state.get("numbers")) # Output: [1, 2, 3, 4]
|
||||
```
|
||||
|
||||
You can also override handlers for individual operations:
|
||||
|
||||
```python
|
||||
def concatenate_strings(current, new):
|
||||
return f"{current}-{new}" if current else new
|
||||
|
||||
|
||||
schema = {"user_name": {"type": str}}
|
||||
state = State(schema=schema)
|
||||
|
||||
state.set("user_name", "Alice")
|
||||
state.set("user_name", "Bob", handler_override=concatenate_strings)
|
||||
print(state.get("user_name")) # Output: "Alice-Bob"
|
||||
```
|
||||
|
||||
## Using State with Agents
|
||||
|
||||
To use State with an Agent, define a state schema when creating the Agent. The Agent automatically manages State throughout its execution.
|
||||
|
||||
```python
|
||||
from haystack.components.agents import Agent
|
||||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||||
from haystack.dataclasses import ChatMessage
|
||||
from haystack.tools import Tool
|
||||
|
||||
|
||||
## Define a simple calculation tool
|
||||
def calculate(expression: str) -> dict:
|
||||
"""Evaluate a mathematical expression."""
|
||||
result = eval(expression, {"__builtins__": {}})
|
||||
return {"result": result}
|
||||
|
||||
|
||||
## Create a tool that writes to state
|
||||
calculator_tool = Tool(
|
||||
name="calculator",
|
||||
description="Evaluate basic math expressions",
|
||||
parameters={
|
||||
"type": "object",
|
||||
"properties": {"expression": {"type": "string"}},
|
||||
"required": ["expression"],
|
||||
},
|
||||
function=calculate,
|
||||
outputs_to_state={"calc_result": {"source": "result"}},
|
||||
)
|
||||
|
||||
## Create agent with state schema
|
||||
agent = Agent(
|
||||
chat_generator=OpenAIChatGenerator(),
|
||||
tools=[calculator_tool],
|
||||
state_schema={"calc_result": {"type": int}},
|
||||
)
|
||||
|
||||
## Run the agent
|
||||
result = agent.run(messages=[ChatMessage.from_user("Calculate 15 + 27")])
|
||||
|
||||
## Access the state from results
|
||||
calc_result = result["calc_result"]
|
||||
print(calc_result) # Output: 42
|
||||
```
|
||||
|
||||
## Tools and State
|
||||
|
||||
Tools interact with State through two mechanisms: `inputs_from_state` and `outputs_to_state`.
|
||||
|
||||
### Reading from State: `inputs_from_state`
|
||||
|
||||
Tools can automatically read values from State and use them as parameters. The `inputs_from_state` parameter maps state keys to tool parameter names.
|
||||
|
||||
```python
|
||||
def search_documents(query: str, user_context: str) -> dict:
|
||||
"""Search documents using query and user context."""
|
||||
return {"results": [f"Found results for '{query}' (user: {user_context})"]}
|
||||
|
||||
|
||||
## Create tool that reads from state
|
||||
search_tool = Tool(
|
||||
name="search",
|
||||
description="Search documents",
|
||||
parameters={
|
||||
"type": "object",
|
||||
"properties": {"query": {"type": "string"}, "user_context": {"type": "string"}},
|
||||
"required": ["query"],
|
||||
},
|
||||
function=search_documents,
|
||||
inputs_from_state={
|
||||
"user_name": "user_context",
|
||||
}, # Maps state's "user_name" to the tool’s input parameter “user_context”
|
||||
)
|
||||
|
||||
## Define agent with state schema including user_name
|
||||
agent = Agent(
|
||||
chat_generator=OpenAIChatGenerator(),
|
||||
tools=[search_tool],
|
||||
state_schema={"user_name": {"type": str}, "search_results": {"type": list}},
|
||||
)
|
||||
|
||||
## Initialize agent with user context
|
||||
result = agent.run(
|
||||
messages=[ChatMessage.from_user("Search for Python tutorials")],
|
||||
user_name="Alice", # All additional kwargs passed to Agent at runtime are put into State
|
||||
)
|
||||
```
|
||||
|
||||
When the tool is invoked, the Agent automatically retrieves the value from State and passes it to the tool function.
|
||||
|
||||
### Writing to State: `outputs_to_state`
|
||||
|
||||
Tools can write their results back to State. The `outputs_to_state` parameter defines mappings from tool outputs to state keys.
|
||||
|
||||
The structure of the output is: `{”state_key”: {”source”: “tool_result_key”}}`.
|
||||
|
||||
```python
|
||||
def retrieve_documents(query: str) -> dict:
|
||||
"""Retrieve documents based on query."""
|
||||
return {
|
||||
"documents": [
|
||||
{"title": "Doc 1", "content": "Content about Python"},
|
||||
{"title": "Doc 2", "content": "More about Python"},
|
||||
],
|
||||
"count": 2,
|
||||
"query": query,
|
||||
}
|
||||
|
||||
|
||||
## Create tool that writes to state
|
||||
retrieval_tool = Tool(
|
||||
name="retrieve",
|
||||
description="Retrieve relevant documents",
|
||||
parameters={
|
||||
"type": "object",
|
||||
"properties": {"query": {"type": "string"}},
|
||||
"required": ["query"],
|
||||
},
|
||||
function=retrieve_documents,
|
||||
outputs_to_state={
|
||||
"documents": {
|
||||
"source": "documents",
|
||||
}, # Maps tool's "documents" output to state's "documents"
|
||||
"result_count": {
|
||||
"source": "count",
|
||||
}, # Maps tool's "count" output to state's "result_count"
|
||||
"last_query": {
|
||||
"source": "query",
|
||||
}, # Maps tool's "query" output to state's "last_query"
|
||||
},
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
chat_generator=OpenAIChatGenerator(),
|
||||
tools=[retrieval_tool],
|
||||
state_schema={
|
||||
"documents": {"type": list},
|
||||
"result_count": {"type": int},
|
||||
"last_query": {"type": str},
|
||||
},
|
||||
)
|
||||
|
||||
result = agent.run(messages=[ChatMessage.from_user("Find information about Python")])
|
||||
|
||||
## Access state values from result
|
||||
documents = result["documents"]
|
||||
result_count = result["result_count"]
|
||||
last_query = result["last_query"]
|
||||
print(documents) # List of retrieved documents
|
||||
print(result_count) # 2
|
||||
print(last_query) # "Find information about Python"
|
||||
```
|
||||
|
||||
Each mapping can specify:
|
||||
|
||||
- `source`: Which field from the tool's output to use
|
||||
- `handler`: Optional custom function for merging values
|
||||
|
||||
If you omit the `source`, the entire tool result is stored:
|
||||
|
||||
```python
|
||||
from haystack.components.agents import Agent
|
||||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||||
from haystack.dataclasses import ChatMessage
|
||||
from haystack.tools import Tool
|
||||
|
||||
|
||||
def get_user_info() -> dict:
|
||||
"""Get user information."""
|
||||
return {"name": "Alice", "email": "alice@example.com", "role": "admin"}
|
||||
|
||||
|
||||
## Tool that stores entire result
|
||||
info_tool = Tool(
|
||||
name="get_info",
|
||||
description="Get user information",
|
||||
parameters={"type": "object", "properties": {}},
|
||||
function=get_user_info,
|
||||
outputs_to_state={
|
||||
"user_info": {}, # Stores entire result dict in state's "user_info"
|
||||
},
|
||||
)
|
||||
|
||||
## Create agent with matching state schema
|
||||
agent = Agent(
|
||||
chat_generator=OpenAIChatGenerator(),
|
||||
tools=[info_tool],
|
||||
state_schema={
|
||||
"user_info": {"type": dict}, # Schema must match the tool's output type
|
||||
},
|
||||
)
|
||||
|
||||
## Run the agent
|
||||
result = agent.run(messages=[ChatMessage.from_user("Get the user information")])
|
||||
|
||||
## Access the complete result from state
|
||||
user_info = result["user_info"]
|
||||
print(
|
||||
user_info,
|
||||
) # Output: {"name": "Alice", "email": "alice@example.com", "role": "admin"}
|
||||
print(user_info["name"]) # Output: "Alice"
|
||||
print(user_info["email"]) # Output: "alice@example.com"
|
||||
```
|
||||
|
||||
### Combining Inputs and Outputs
|
||||
|
||||
Tools can both read from and write to State, enabling tool chaining:
|
||||
|
||||
```python
|
||||
from haystack.components.agents import Agent
|
||||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||||
from haystack.dataclasses import ChatMessage
|
||||
from haystack.tools import Tool
|
||||
|
||||
|
||||
def process_documents(documents: list, max_results: int) -> dict:
|
||||
"""Process documents and return filtered results."""
|
||||
processed = documents[:max_results]
|
||||
return {"processed_docs": processed, "processed_count": len(processed)}
|
||||
|
||||
|
||||
processing_tool = Tool(
|
||||
name="process",
|
||||
description="Process retrieved documents",
|
||||
parameters={
|
||||
"type": "object",
|
||||
"properties": {"max_results": {"type": "integer"}},
|
||||
"required": ["max_results"],
|
||||
},
|
||||
function=process_documents,
|
||||
inputs_from_state={"documents": "documents"}, # Reads documents from state
|
||||
outputs_to_state={
|
||||
"final_docs": {"source": "processed_docs"},
|
||||
"final_count": {"source": "processed_count"},
|
||||
},
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
chat_generator=OpenAIChatGenerator(),
|
||||
tools=[retrieval_tool, processing_tool], # Chain tools using state
|
||||
state_schema={
|
||||
"documents": {"type": list},
|
||||
"final_docs": {"type": list},
|
||||
"final_count": {"type": int},
|
||||
},
|
||||
)
|
||||
|
||||
## Run the agent - tools will chain through state
|
||||
result = agent.run(
|
||||
messages=[ChatMessage.from_user("Find and process 3 documents about Python")],
|
||||
)
|
||||
|
||||
## Access the final processed results
|
||||
final_docs = result["final_docs"]
|
||||
final_count = result["final_count"]
|
||||
print(f"Processed {final_count} documents")
|
||||
print(final_docs)
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
This example shows a multi-tool agent workflow where tools share data through State:
|
||||
|
||||
```python
|
||||
import math
|
||||
from haystack.components.agents import Agent
|
||||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||||
from haystack.dataclasses import ChatMessage
|
||||
from haystack.tools import Tool
|
||||
|
||||
|
||||
## Tool 1: Calculate factorial
|
||||
def factorial(n: int) -> dict:
|
||||
"""Calculate the factorial of a number."""
|
||||
result = math.factorial(n)
|
||||
return {"result": result}
|
||||
|
||||
|
||||
factorial_tool = Tool(
|
||||
name="factorial",
|
||||
description="Calculate the factorial of a number",
|
||||
parameters={
|
||||
"type": "object",
|
||||
"properties": {"n": {"type": "integer"}},
|
||||
"required": ["n"],
|
||||
},
|
||||
function=factorial,
|
||||
outputs_to_state={"factorial_result": {"source": "result"}},
|
||||
)
|
||||
|
||||
|
||||
## Tool 2: Perform calculation
|
||||
def calculate(expression: str) -> dict:
|
||||
"""Evaluate a mathematical expression."""
|
||||
result = eval(expression, {"__builtins__": {}})
|
||||
return {"result": result}
|
||||
|
||||
|
||||
calculator_tool = Tool(
|
||||
name="calculator",
|
||||
description="Evaluate basic math expressions",
|
||||
parameters={
|
||||
"type": "object",
|
||||
"properties": {"expression": {"type": "string"}},
|
||||
"required": ["expression"],
|
||||
},
|
||||
function=calculate,
|
||||
outputs_to_state={"calc_result": {"source": "result"}},
|
||||
)
|
||||
|
||||
## Create agent with both tools
|
||||
agent = Agent(
|
||||
chat_generator=OpenAIChatGenerator(),
|
||||
tools=[calculator_tool, factorial_tool],
|
||||
state_schema={"calc_result": {"type": int}, "factorial_result": {"type": int}},
|
||||
)
|
||||
|
||||
## Run the agent
|
||||
result = agent.run(
|
||||
messages=[
|
||||
ChatMessage.from_user("Calculate the factorial of 5, then multiply it by 2"),
|
||||
],
|
||||
)
|
||||
|
||||
## Access state values from result
|
||||
factorial_result = result["factorial_result"]
|
||||
calc_result = result["calc_result"]
|
||||
|
||||
## Access conversation messages
|
||||
for message in result["messages"]:
|
||||
print(f"{message.role}: {message.text}")
|
||||
```
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
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, additionally have a `warm_up()` method. When you run a component like this on its own, you must run `warm_up()` after initializing it, but before running it, like this:
|
||||
|
||||
```python
|
||||
from haystack import Document
|
||||
from haystack.components.embedders import SentenceTransformersDocumentEmbedder
|
||||
|
||||
doc = Document(content="I love pizza!")
|
||||
doc_embedder = SentenceTransformersDocumentEmbedder() # First, initialize the component
|
||||
doc_embedder.warm_up() # Then, warm it up to load the model
|
||||
|
||||
result = doc_embedder.run([doc]) # And finally, run it
|
||||
print(result["documents"][0].embedding)
|
||||
```
|
||||
|
||||
If you're using a component that has the `warm_up()` method in a pipeline, you don't have to do anything additionally. The pipeline takes care of warming it up before running.
|
||||
|
||||
The `warm_up()` method is a nice way to keep the `init()` methods lightweight and the validation fast. (Validation in the pipeline happens when connecting the components but before warming them up and running.)
|
||||
@@ -0,0 +1,146 @@
|
||||
---
|
||||
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.
|
||||
|
||||
### 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 typing import List
|
||||
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.
|
||||
|
||||
## 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 @supercomponent 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,57 @@
|
||||
---
|
||||
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,289 @@
|
||||
---
|
||||
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, 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(frozen=True)
|
||||
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(frozen=True)
|
||||
class ByteStream:
|
||||
data: bytes
|
||||
metadata: 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.
|
||||
|
||||
### 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)
|
||||
```
|
||||
|
||||
#### Example
|
||||
|
||||
```python
|
||||
from haystack.dataclasses.streaming_chunk import StreamingChunk, ComponentInfo
|
||||
|
||||
## Basic text chunk
|
||||
chunk = StreamingChunk(
|
||||
content="Hello world",
|
||||
start=True,
|
||||
meta={"model": "gpt-3.5-turbo"},
|
||||
)
|
||||
|
||||
## Tool call chunk
|
||||
tool_chunk = StreamingChunk(
|
||||
tool_calls=[
|
||||
ToolCallDelta(
|
||||
index=0,
|
||||
tool_name="calculator",
|
||||
arguments='{"operation": "add", "a": 2, "b": 3}',
|
||||
),
|
||||
],
|
||||
index=0,
|
||||
start=False,
|
||||
finish_reason="tool_calls",
|
||||
)
|
||||
```
|
||||
|
||||
### 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)
|
||||
```
|
||||
|
||||
### 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,258 @@
|
||||
---
|
||||
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 and tool calls results."
|
||||
---
|
||||
|
||||
# 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 and tool calls results.
|
||||
|
||||
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`, `tool_call`, `tool_calls`, `tool_call_result`, and `tool_call_results` 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).
|
||||
|
||||
## Types of Content
|
||||
|
||||
`ChatMessage` currently supports `TextContent`, `ImageContent`, `ToolCall` and `ToolCallResult` 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.
|
||||
"""
|
||||
|
||||
tool_name: str
|
||||
arguments: Dict[str, Any]
|
||||
id: Optional[str] = None # noqa: A003
|
||||
|
||||
|
||||
@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
|
||||
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
|
||||
```
|
||||
|
||||
The `ImageContent` dataclass also provides 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)
|
||||
|
||||
print(user_message.text)
|
||||
|
||||
print(user_message.texts)
|
||||
```
|
||||
|
||||
### from_user with TextContent and ImageContent
|
||||
|
||||
```python
|
||||
from haystack.dataclasses import ChatMessage, ImageContent
|
||||
|
||||
capybara_image_url = (
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/"
|
||||
"Cattle_tyrant_%28Machetornis_rixosa%29_on_Capybara.jpg/"
|
||||
"960px-Cattle_tyrant_%28Machetornis_rixosa%29_on_Capybara.jpg?download"
|
||||
)
|
||||
|
||||
image_content = ImageContent.from_url(capybara_image_url, detail="low")
|
||||
|
||||
user_message = ChatMessage.from_user(
|
||||
content_parts=["What does the image show?", image_content],
|
||||
)
|
||||
|
||||
print(user_message)
|
||||
|
||||
print(user_message.text)
|
||||
|
||||
print(user_message.texts)
|
||||
|
||||
print(user_message.image)
|
||||
```
|
||||
|
||||
### from_assistant with TextContent
|
||||
|
||||
```python
|
||||
from haystack.dataclasses import ChatMessage
|
||||
|
||||
assistant_message = ChatMessage.from_assistant("How can I assist you today?")
|
||||
|
||||
print(assistant_message)
|
||||
|
||||
print(assistant_message.text)
|
||||
|
||||
print(assistant_message.texts)
|
||||
```
|
||||
|
||||
### 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)
|
||||
|
||||
print(assistant_message_w_tool_call.text)
|
||||
|
||||
print(assistant_message_w_tool_call.texts)
|
||||
|
||||
print(assistant_message_w_tool_call.tool_call)
|
||||
|
||||
print(assistant_message_w_tool_call.tool_calls)
|
||||
|
||||
print(assistant_message_w_tool_call.tool_call_result)
|
||||
|
||||
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)
|
||||
|
||||
print(tool_message.text)
|
||||
|
||||
print(tool_message.texts)
|
||||
|
||||
print(tool_message.tool_call)
|
||||
|
||||
print(tool_message.tool_calls)
|
||||
|
||||
print(tool_message.tool_call_result)
|
||||
|
||||
print(tool_message.tool_call_results)
|
||||
```
|
||||
|
||||
## 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_calls_results`
|
||||
|
||||
```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)
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
```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,126 @@
|
||||
---
|
||||
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.
|
||||
|
||||
:::note
|
||||
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,108 @@
|
||||
---
|
||||
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.
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| 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. |
|
||||
|
||||
#### 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.
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
---
|
||||
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 will make 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/document-store) that provides the structure you need to host a custom Document Store in a dedicated repository.
|
||||
|
||||
See the instructions about [how to use the template](https://github.com/deepset-ai/document-store?tab=readme-ov-file#how-to-use-this-repo) to get you started.
|
||||
|
||||
### 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/document-store)), 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
|
||||
Tip
|
||||
|
||||
Our [GitHub template](https://github.com/deepset-ai/document-store) 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.
|
||||
|
||||
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.
|
||||
|
||||
## 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,69 @@
|
||||
---
|
||||
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
|
||||
```
|
||||
|
||||
:::note
|
||||
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)
|
||||
- [Evaluating RAG Pipelines with EvaluationHarness](https://haystack.deepset.ai/cookbook/rag_eval_harness)
|
||||
- [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,58 @@
|
||||
---
|
||||
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)
|
||||
|
||||
## 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,157 @@
|
||||
---
|
||||
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`
|
||||
```
|
||||
|
||||
:::note
|
||||
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`
|
||||
```
|
||||
|
||||
:::note
|
||||
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,107 @@
|
||||
---
|
||||
title: "Pipelines"
|
||||
id: pipelines
|
||||
sidebar_class_name: hidden-sidebar-item
|
||||
sidebar_position: 0
|
||||
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.
|
||||
|
||||
<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.
|
||||
|
||||
<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.
|
||||
|
||||
:::note
|
||||
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,105 @@
|
||||
---
|
||||
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
|
||||
from haystack.components.embedders import SentenceTransformersTextEmbedder
|
||||
from haystack.components.retrievers import (
|
||||
InMemoryEmbeddingRetriever,
|
||||
InMemoryBM25Retriever,
|
||||
)
|
||||
from haystack.components.joiners import DocumentJoiner
|
||||
from haystack.components.builders import ChatPromptBuilder
|
||||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||||
|
||||
hybrid_rag_retrieval = AsyncPipeline()
|
||||
hybrid_rag_retrieval.add_component("text_embedder", SentenceTransformersTextEmbedder())
|
||||
hybrid_rag_retrieval.add_component(
|
||||
"embedding_retriever",
|
||||
InMemoryEmbeddingRetriever(document_store=document_store),
|
||||
)
|
||||
hybrid_rag_retrieval.add_component(
|
||||
"bm25_retriever",
|
||||
InMemoryBM25Retriever(document_store=document_store),
|
||||
)
|
||||
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_retriever")
|
||||
hybrid_rag_retrieval.connect("bm25_retriever", "document_joiner")
|
||||
hybrid_rag_retrieval.connect("embedding_retriever", "document_joiner")
|
||||
hybrid_rag_retrieval.connect("document_joiner", "prompt_builder.documents")
|
||||
hybrid_rag_retrieval.connect("prompt_builder", "llm")
|
||||
|
||||
|
||||
async def process_results():
|
||||
async for partial_output in hybrid_rag_retrieval.run_async_generator(
|
||||
data=data,
|
||||
include_outputs_from={"document_joiner", "llm"},
|
||||
):
|
||||
# Each partial_output contains the results from a completed component
|
||||
if "retriever" 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,126 @@
|
||||
---
|
||||
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,166 @@
|
||||
---
|
||||
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 executions of the `Pipeline` and creates a JSON file containing a snapshot of the current pipeline state. 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`, the system saves a JSON snapshot with the same information as in the `BreakpointException` .
|
||||
|
||||
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.
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
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,97 @@
|
||||
---
|
||||
title: "Pipeline Templates"
|
||||
id: pipeline-templates
|
||||
slug: "/pipeline-templates"
|
||||
description: ""
|
||||
---
|
||||
|
||||
# Pipeline Templates
|
||||
|
||||
Haystack provides templates to create ready-made pipelines for common use cases.
|
||||
|
||||
To create a pipeline, the method `from_template`of the `Pipeline` class can be called passing a template identifier in the form `PredefinedPipeline.TEMPLATE_IDENTIFIER`.
|
||||
|
||||
For example, to create and run a pipeline using the `INDEXING` template you would use `Pipeline.from_template(PredefinedPipeline.INDEXING)`
|
||||
|
||||
In this section we detail the available templates and how they can be used.
|
||||
|
||||
### Chat with website
|
||||
|
||||
Generates a pipeline to read a web page and ask questions about its content.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Template identifier | `CHAT_WITH_WEBSITE` |
|
||||
| Template params | \- |
|
||||
| Inputs (**\*** means mandatory) | `'converter': {'meta': {}} ` `'fetcher': {'urls': ["https://example.com"]}`**\*** `'llm': {'generation_kwargs': {}}` `'prompt': {'query': 'the question to ask'}` |
|
||||
|
||||
Example code:
|
||||
|
||||
```python
|
||||
from haystack import Pipeline, PredefinedPipeline
|
||||
|
||||
pipeline = Pipeline.from_template(PredefinedPipeline.CHAT_WITH_WEBSITE)
|
||||
pipeline.run(
|
||||
{
|
||||
"fetcher": {"urls": ["https://haystack.deepset.ai:"]},
|
||||
"prompt": {"query": "what is Haystack?"},
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### Generative QA
|
||||
|
||||
Generates a simple pipeline to ask a generic query using an `OpenAIGenerator`.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Template identifier | `GENERATIVE_QA` |
|
||||
| Template params | \- |
|
||||
| Inputs (**\*** means mandatory) | `'generator': {'generation_kwargs': {}}` `'prompt_builder': {'question': "" }` |
|
||||
|
||||
Example code:
|
||||
|
||||
```python
|
||||
from haystack import Pipeline, PredefinedPipeline
|
||||
|
||||
pipeline = Pipeline.from_template(PredefinedPipeline.GENERATIVE_QA)
|
||||
pipeline.run({"prompt_builder": {"question": "Where is Rome?"}})
|
||||
```
|
||||
|
||||
### Indexing
|
||||
|
||||
Generates a pipeline that imports documents from one or more text files, creates the embeddings for each of them, and finally stores them in an [`InMemoryDocumentStore`](../../document-stores/inmemorydocumentstore.mdx).
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Template identifier | `INDEXING` |
|
||||
| Template params | \- |
|
||||
| Inputs (**\*** means mandatory) | `'llm': {'generation_kwargs': {}}` `'prompt_builder': {'query': ''}` `'retriever': {'filters': {}, 'top_k': None}` `'text_embedder': {'text': ''}}`\* |
|
||||
|
||||
Example code:
|
||||
|
||||
```python
|
||||
from haystack import Pipeline, PredefinedPipeline
|
||||
|
||||
pipeline = Pipeline.from_template(PredefinedPipeline.INDEXING)
|
||||
result = pipeline.run({"converter": {"sources": ["some_file.txt"]}})
|
||||
```
|
||||
|
||||
### RAG
|
||||
|
||||
Generates a RAG pipeline using data that was previously indexed (you can use the Indexing template).
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Template identifier | `RAG` |
|
||||
| Template params | \- |
|
||||
| Inputs (**\*** means mandatory) | `'llm': {'generation_kwargs': {}}` `'prompt_builder': {'query': ''}` `'retriever': {'filters': {}, 'top_k': None}` `'text_embedder': {'text': ''}}`\* |
|
||||
|
||||
Example code:
|
||||
|
||||
```python
|
||||
from haystack import Pipeline, PredefinedPipeline
|
||||
|
||||
pipeline = Pipeline.from_template(PredefinedPipeline.RAG)
|
||||
pipeline.run({"text_embedder": {"text": "A question about your documents"}})
|
||||
```
|
||||
@@ -0,0 +1,197 @@
|
||||
---
|
||||
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.
|
||||
|
||||
:::note
|
||||
Serialization formats
|
||||
|
||||
Haystack 2.0 only supports YAML format at this time. We will be rolling out more formats gradually.
|
||||
:::
|
||||
|
||||
## 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_loops_allowed: 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.
|
||||
|
||||
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_loops_allowed: 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),
|
||||
)
|
||||
```
|
||||
|
||||
## 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_loops_allowed = 100\nconnections = []\n\n[metadata]\n\n[components]\n'
|
||||
```
|
||||
|
||||
## Additional References
|
||||
|
||||
:notebook: Tutorial: [Serializing LLM Pipelines](https://haystack.deepset.ai/tutorials/29_serializing_pipelines)
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: "Visualizing Pipelines"
|
||||
id: visualizing-pipelines
|
||||
slug: "/visualizing-pipelines"
|
||||
description: "You can visualize your pipelines as graphs to better understand how the components are connected."
|
||||
---
|
||||
|
||||
import ClickableImage from "@site/src/components/ClickableImage";
|
||||
|
||||
# Visualizing 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 deepset Studio
|
||||
|
||||
YYou can import your Haystack pipeline into deepset Studio and continue visually building your pipeline
|
||||
|
||||
To do that, follow the steps described in our deepset AI Platform [documentation](https://docs.cloud.deepset.ai/docs/import-a-pipeline#import-your-pipeline).
|
||||
@@ -0,0 +1,192 @@
|
||||
---
|
||||
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 `OpenAIGenerator` 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 = OpenAIGenerator(model="gpt-4", 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 = OpenAIGenerator(model="gpt-4", 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 = OpenAIGenerator(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 OpenAIGenerator) have a default value for `Secret.from_env_var` (in this case, `OPENAI_API_KEY`). This means that the `OpenAIGenerator` 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 = (
|
||||
OpenAIGenerator()
|
||||
) # 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 = OpenAIGenerator(api_key=Secret.from_env_var("YOUR_ENV_VAR"))
|
||||
```
|
||||
|
||||
When `OpenAIGenerator` 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: {}
|
||||
model: gpt-4o-mini
|
||||
organization: null
|
||||
streaming_callback: null
|
||||
system_prompt: null
|
||||
type: haystack.components.generators.openai.OpenAIGenerator
|
||||
...
|
||||
```
|
||||
|
||||
### 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 [documentation](hayhooks.mdx) to get details about how to run the server and deploy your pipelines.
|
||||
|
||||
:::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**.
|
||||
|
||||
📜 [Learn more about Haystack Enterprise](https://haystack.deepset.ai/blog/announcing-haystack-enterprise)
|
||||
|
||||
👉 [Get in touch with our team](https://www.deepset.ai/products-and-services/haystack-enterprise)
|
||||
:::
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
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.
|
||||
|
||||
:::note
|
||||
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 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:v0.6.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 [“Qdrant Indexing”](https://github.com/deepset-ai/haystack-demos/tree/main/qdrant_indexing) demo from GitHub.
|
||||
@@ -0,0 +1,271 @@
|
||||
---
|
||||
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:v0.6.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="None" />
|
||||
|
||||
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 docs](../hayhooks.mdx) 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:v0.6.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 `trafilaura` 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="None" />
|
||||
|
||||
## 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:v0.6.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 are installed. Run `pip install hayhooks` and make sure it works by running `hayhooks --version`. Read more about Hayhooks in our [docs](../hayhooks.mdx).
|
||||
- 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:main -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 --server http://hayhooks-XXX.openshiftapps.com status
|
||||
```
|
||||
|
||||
8. Lastly, deploy your pipeline as usual:
|
||||
```
|
||||
hayhooks --server http://hayhooks-XXX.openshiftapps.com deploy your_pipeline.yaml
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
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,312 @@
|
||||
---
|
||||
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.
|
||||
|
||||
:::note
|
||||
Hayhooks GitHub
|
||||
|
||||
You can find the code and an in-depth explanation of the features 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`.
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Description |
|
||||
| --------------------------------- | ---------------------------------- |
|
||||
| `HAYHOOKS_HOST` | Host address for the server |
|
||||
| `HAYHOOKS_PORT` | Port for the server |
|
||||
| `HAYHOOKS_PIPELINES_DIR` | Directory containing pipelines |
|
||||
| `HAYHOOKS_ROOT_PATH` | Root path of the server |
|
||||
| `HAYHOOKS_ADDITIONAL_PYTHON_PATH` | Additional Python paths to include |
|
||||
| `HAYHOOKS_DISABLE_SSL` | Disable SSL verification (boolean) |
|
||||
| `HAYHOOKS_SHOW_TRACEBACKS` | Show error tracebacks (boolean) |
|
||||
|
||||
### CORS Settings
|
||||
|
||||
| Variable | Description |
|
||||
| ---------------------------------- | --------------------------------------------------- |
|
||||
| `HAYHOOKS_CORS_ALLOW_ORIGINS` | List of allowed origins (default: `[*]`) |
|
||||
| `HAYHOOKS_CORS_ALLOW_METHODS` | List of allowed HTTP methods (default: `[*]`) |
|
||||
| `HAYHOOKS_CORS_ALLOW_HEADERS` | List of allowed headers (default: `[*]`) |
|
||||
| `HAYHOOKS_CORS_ALLOW_CREDENTIALS` | Allow credentials (default: `false`) |
|
||||
| `HAYHOOKS_CORS_ALLOW_ORIGIN_REGEX` | Regex pattern for allowed origins (default: `null`) |
|
||||
| `HAYHOOKS_CORS_EXPOSE_HEADERS` | Headers to expose in response (default: `[]`) |
|
||||
| `HAYHOOKS_CORS_MAX_AGE` | Max age for preflight responses (default: `600`) |
|
||||
|
||||
## 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: Optional[List[UploadFile]] = None` as an argument.
|
||||
|
||||
```python
|
||||
def run_api(self, files: Optional[List[UploadFile]] = 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.
|
||||
|
||||
### Combining Files and Parameters
|
||||
|
||||
Hayhooks also supports handling both files and additional parameters in the same request by including them as arguments in `run_api`:
|
||||
|
||||
```python
|
||||
def run_api(
|
||||
self,
|
||||
files: Optional[List[UploadFile]] = None,
|
||||
additional_param: str = "default",
|
||||
) -> str: ...
|
||||
```
|
||||
|
||||
## Running Pipelines from the CLI
|
||||
|
||||
### With JSON-Compatible Parameters
|
||||
|
||||
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.
|
||||
|
||||
This method is ideal for testing deployed pipelines from the CLI without writing additional code.
|
||||
|
||||
```shell
|
||||
hayhooks pipeline run <pipeline_name> --param 'question="Is this recipe vegan?"'
|
||||
```
|
||||
|
||||
### With File Uploads
|
||||
|
||||
To execute a pipeline that requires a file input, use a `multipart/form-data` request. You can submit both files and parameters in the same request.
|
||||
|
||||
Ensure the deployed pipeline supports file handling.
|
||||
|
||||
```shell
|
||||
## Upload a directory
|
||||
hayhooks pipeline run <pipeline_name> --dir files_to_index
|
||||
|
||||
## Upload a single file
|
||||
hayhooks pipeline run <pipeline_name> --file file.pdf
|
||||
|
||||
## Upload multiple files
|
||||
hayhooks pipeline run <pipeline_name> --dir files_to_index --file file1.pdf --file file2.pdf
|
||||
|
||||
## Upload a file with an additional parameter
|
||||
hayhooks pipeline run <pipeline_name> --file file.pdf --param 'question="Is this recipe vegan?"'
|
||||
```
|
||||
|
||||
## MCP Support
|
||||
|
||||
### MCP Server
|
||||
|
||||
Hayhooks supports the Model Context Protocol (MCP) and can act as an MCP Server. It automatically lists your deployed pipelines as MCP Tools using Server-Sent Events (SSE) as the transport method.
|
||||
|
||||
To start the Hayhooks MCP server, run:
|
||||
|
||||
```shell
|
||||
hayhooks mcp run
|
||||
```
|
||||
|
||||
This starts the server at `HAYHOOKS_MCP_HOST:HAYHOOKS_MCP_PORT`.
|
||||
|
||||
### Creating a PipelineWrapper
|
||||
|
||||
To expose a Haystack pipeline as an MCP Tool, you need a `PipelineWrapper` with the following properties:
|
||||
|
||||
- **name**: The tool's name
|
||||
- **description**: The tool's description
|
||||
- **inputSchema**: A JSON Schema object for the tool's input parameters
|
||||
|
||||
For each deployed pipeline, Hayhooks will:
|
||||
|
||||
1. Use the pipeline wrapper name as the MCP Tool name,
|
||||
2. Use the `run_api` method's docstring as the MCP Tool description (if present),
|
||||
3. Generate a Pydantic model from the `run_api` method arguments.
|
||||
|
||||
#### PipelineWrapper Example
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
from haystack import Pipeline
|
||||
from hayhooks import BasePipelineWrapper
|
||||
|
||||
|
||||
class PipelineWrapper(BasePipelineWrapper):
|
||||
def setup(self) -> None:
|
||||
pipeline_yaml = (Path(__file__).parent / "chat_with_website.yml").read_text()
|
||||
self.pipeline = Pipeline.loads(pipeline_yaml)
|
||||
|
||||
def run_api(self, urls: List[str], question: str) -> str:
|
||||
"""
|
||||
Ask a question about one or more websites using a Haystack pipeline.
|
||||
"""
|
||||
result = self.pipeline.run(
|
||||
{"fetcher": {"urls": urls}, "prompt": {"query": question}},
|
||||
)
|
||||
return result["llm"]["replies"][0]
|
||||
```
|
||||
|
||||
### Skipping MCP Tool Listing
|
||||
|
||||
To deploy a pipeline without listing it as an MCP Tool, set `skip_mcp = True` in your class:
|
||||
|
||||
```python
|
||||
class PipelineWrapper(BasePipelineWrapper):
|
||||
# This will skip the MCP Tool listing
|
||||
skip_mcp = True
|
||||
|
||||
def setup(self) -> None: ...
|
||||
|
||||
def run_api(self, urls: List[str], question: str) -> str: ...
|
||||
```
|
||||
|
||||
## 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})
|
||||
```
|
||||
|
||||
### Streaming Responses
|
||||
|
||||
Hayhooks provides a `streaming_generator` utility to stream pipeline output to the client:
|
||||
|
||||
```python
|
||||
from hayhooks import streaming_generator
|
||||
|
||||
|
||||
def run_chat_completion(self, model: str, messages: list, body: dict):
|
||||
question = get_last_user_message(messages)
|
||||
return streaming_generator(
|
||||
pipeline=self.pipeline,
|
||||
pipeline_run_args={"query": question},
|
||||
)
|
||||
```
|
||||
|
||||
## 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,109 @@
|
||||
---
|
||||
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,341 @@
|
||||
---
|
||||
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.
|
||||
|
||||
:::note
|
||||
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" />
|
||||
|
||||
### 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`.
|
||||
|
||||
:::note
|
||||
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,85 @@
|
||||
---
|
||||
title: "AstraDocumentStore"
|
||||
id: astradocumentstore
|
||||
slug: "/astradocumentstore"
|
||||
description: ""
|
||||
---
|
||||
|
||||
# AstraDocumentStore
|
||||
|
||||
| | |
|
||||
| :------------ | :-------------------------------------------------------------------------------------- |
|
||||
| API reference | [Astra](/reference/integrations-astra) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/astra |
|
||||
|
||||
DataStax Astra DB is a serverless vector database built on Apache Cassandra, and it supports vector-based search and auto-scaling. You can deploy it on AWS, GCP, or Azure and easily expand to one or more regions within those clouds for multi-region availability, low latency data access, data sovereignty, and to avoid cloud vendor lock-in. For more information, see the [DataStax documentation](https://docs.datastax.com/en/home/docs/index.html).
|
||||
|
||||
### Initialization
|
||||
|
||||
Once you have an AstraDB account and have created a database, install the `astra-haystack` integration:
|
||||
|
||||
```shell
|
||||
pip install astra-haystack
|
||||
```
|
||||
|
||||
From the configuration in AstraDB’s web UI, you need the database 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:
|
||||
|
||||
:::note
|
||||
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:
|
||||
|
||||
:::note
|
||||
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)
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| **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 |
|
||||
|
||||
[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())
|
||||
```
|
||||
|
||||
:::note
|
||||
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,94 @@
|
||||
---
|
||||
title: "ChromaDocumentStore"
|
||||
id: chromadocumentstore
|
||||
slug: "/chromadocumentstore"
|
||||
description: ""
|
||||
---
|
||||
|
||||
# ChromaDocumentStore
|
||||
|
||||
| | |
|
||||
| :------------ | :--------------------------------------------------------------------------------------- |
|
||||
| API reference | [Chroma](/reference/integrations-chroma) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/chroma |
|
||||
|
||||
[Chroma](https://docs.trychroma.com/) is an open source vector database capable of storing collections of documents along with their metadata, creating embeddings for documents and queries, and searching the collections filtering by document metadata or content. Additionally, Chroma supports multi-modal embedding functions.
|
||||
|
||||
Chroma can be used in-memory, as an embedded database, or in a client-server fashion. When running in-memory, Chroma can still keep its contents on disk across different sessions. This allows users to quickly put together prototypes using the in-memory version and later move to production, where the client-server version is deployed.
|
||||
|
||||
## Initialization
|
||||
|
||||
First, install the Chroma integration, which will install Haystack and Chroma if they are not already present. The following command is all you need to start:
|
||||
|
||||
```shell
|
||||
pip install chroma-haystack
|
||||
```
|
||||
|
||||
To store data in Chroma, create a `ChromaDocumentStore` instance and write documents with:
|
||||
|
||||
```python
|
||||
from haystack_integrations.document_stores.chroma import ChromaDocumentStore
|
||||
from haystack import Document
|
||||
|
||||
document_store = ChromaDocumentStore()
|
||||
document_store.write_documents(
|
||||
[
|
||||
Document(content="This is the first document."),
|
||||
Document(content="This is the second document."),
|
||||
],
|
||||
)
|
||||
print(document_store.count_documents())
|
||||
```
|
||||
|
||||
In this case, since we didn’t pass any embeddings along with our documents, Chroma will create them for us using its [default embedding function](https://docs.trychroma.com/embeddings#default-all-minilm-l6-v2).
|
||||
|
||||
### Connection Options
|
||||
|
||||
1. **In-Memory Mode (Local)**: Chroma can be set up as a local Document Store for fast and lightweight usage. You can use this option during development or small-scale experiments. Set up a local in-memory instance of `ChromaDocumentStore` like this:
|
||||
|
||||
```python
|
||||
from haystack_integrations.document_stores.chroma import ChromaDocumentStore
|
||||
|
||||
document_store = ChromaDocumentStore()
|
||||
```
|
||||
2. **Persistent Storage**: If you need to retain the documents between sessions, Chroma supports persistent storage by specifying a path to store data on disk:
|
||||
|
||||
```python
|
||||
from haystack_integrations.document_stores.chroma import ChromaDocumentStore
|
||||
|
||||
document_store = ChromaDocumentStore(persist_path="your_directory_path")
|
||||
```
|
||||
3. **Remote Connection**: You can connect to a remote Chroma database through HTTP. This is suitable for distributed setups where multiple clients might interact with the same remote Chroma instance.
|
||||
|
||||
Note that this option is incompatible with in-memory or persistent storage modes.
|
||||
|
||||
First, start a Chroma server:
|
||||
|
||||
```shell
|
||||
chroma run --path /db_path
|
||||
```
|
||||
|
||||
Or using docker:
|
||||
|
||||
```shell
|
||||
docker run -p 8000:8000 chromadb/chroma
|
||||
```
|
||||
|
||||
Then, initialize the Document Store with `host` and `port` parameters:
|
||||
|
||||
```python
|
||||
from haystack_integrations.document_stores.chroma import ChromaDocumentStore
|
||||
|
||||
document_store = ChromaDocumentStore(host="localhost", port="8000")
|
||||
```
|
||||
|
||||
## Supported Retrievers
|
||||
|
||||
The Haystack Chroma integration comes with 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)
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: "ElasticsearchDocumentStore"
|
||||
id: elasticsearch-document-store
|
||||
slug: "/elasticsearch-document-store"
|
||||
description: "Use an Elasticsearch database with Haystack."
|
||||
---
|
||||
|
||||
# ElasticsearchDocumentStore
|
||||
|
||||
Use an Elasticsearch database with Haystack.
|
||||
|
||||
| | |
|
||||
| :------------ | :---------------------------------------------------------------------------------------------- |
|
||||
| API reference | [Elasticsearch](/reference/integrations-elasticsearch) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/elasticsearch |
|
||||
|
||||
ElasticsearchDocumentStore is excellent if you want to evaluate the performance of different retrieval options (dense vs. sparse) and aim for a smooth transition from PoC to production.
|
||||
|
||||
It features the approximate nearest neighbours (ANN) search.
|
||||
|
||||
### Initialization
|
||||
|
||||
[Install](https://www.elastic.co/guide/en/elasticsearch/reference/current/install-elasticsearch.html) Elasticsearch and then [start](https://www.elastic.co/guide/en/elasticsearch/reference/current/starting-elasticsearch.html) an instance. Haystack supports Elasticsearch 8.
|
||||
|
||||
If you have Docker set up, we recommend pulling the Docker image and running it.
|
||||
|
||||
```shell
|
||||
docker pull docker.elastic.co/elasticsearch/elasticsearch:8.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,28 @@
|
||||
---
|
||||
title: "InMemoryDocumentStore"
|
||||
id: inmemorydocumentstore
|
||||
slug: "/inmemorydocumentstore"
|
||||
description: ""
|
||||
---
|
||||
|
||||
# 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.
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: "MongoDBAtlasDocumentStore"
|
||||
id: mongodbatlasdocumentstore
|
||||
slug: "/mongodbatlasdocumentstore"
|
||||
description: ""
|
||||
---
|
||||
|
||||
# MongoDBAtlasDocumentStore
|
||||
|
||||
| | |
|
||||
| :------------ | :---------------------------------------------------------------------------------------------- |
|
||||
| API reference | [MongoDB Atlas](/reference/integrations-mongodb-atlas) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/mongodb_atlas |
|
||||
|
||||
`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.
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| :------------ | :------------------------------------------------------------------------------------------- |
|
||||
| API reference | [OpenSearch](/reference/integrations-opensearch) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/opensearch |
|
||||
|
||||
OpenSearch is a fully open source search and analytics engine for use cases such as log analytics, real-time application monitoring, and clickstream analysis. For more information, see the [OpenSearch documentation](https://opensearch.org/docs/).
|
||||
|
||||
This Document Store is great if you want to evaluate the performance of different retrieval options (dense vs. sparse). It’s compatible with the Amazon OpenSearch Service.
|
||||
|
||||
OpenSearch provides support for vector similarity comparisons and approximate nearest neighbors algorithms.
|
||||
|
||||
### Initialization
|
||||
|
||||
[Install](https://opensearch.org/docs/latest/install-and-configure/install-opensearch/index/) and run an OpenSearch instance.
|
||||
|
||||
If you have Docker set up, we recommend pulling the Docker image and running it.
|
||||
|
||||
```shell
|
||||
docker pull opensearchproject/opensearch:2.11.0
|
||||
docker run -p 9200:9200 -p 9600:9600 -e "discovery.type=single-node" -e "ES_JAVA_OPTS=-Xms1024m -Xmx1024m" opensearchproject/opensearch:2.11.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", "admin"),
|
||||
)
|
||||
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,76 @@
|
||||
---
|
||||
title: "PgvectorDocumentStore"
|
||||
id: pgvectordocumentstore
|
||||
slug: "/pgvectordocumentstore"
|
||||
description: ""
|
||||
---
|
||||
|
||||
# PgvectorDocumentStore
|
||||
|
||||
| | |
|
||||
| :------------ | :------------------------------------------------------------------------------------------ |
|
||||
| API reference | [Pgvector](/reference/integrations-pgvector) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/pgvector/ |
|
||||
|
||||
Pgvector is an extension for PostgreSQL that enhances its capabilities with vector similarity search. It builds upon the classic features of PostgreSQL, such as ACID compliance and point-in-time recovery, and introduces the ability to perform exact and approximate nearest neighbor search using vectors.
|
||||
|
||||
For more information, see the [pgvector repository](https://github.com/pgvector/pgvector).
|
||||
|
||||
Pgvector Document Store supports embedding retrieval and metadata filtering.
|
||||
|
||||
## Installation
|
||||
|
||||
To quickly set up a PostgreSQL database with pgvector, you can use Docker:
|
||||
|
||||
```shell
|
||||
docker run -d -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres 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
|
||||
|
||||
Define the connection string to your PostgreSQL database in the `PG_CONN_STR` environment variable. For example:
|
||||
|
||||
```shell Shell
|
||||
export PG_CONN_STR="postgresql://postgres:postgres@localhost:5432/postgres"
|
||||
```
|
||||
|
||||
## 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,63 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| :------------ | :----------------------------------------------------------------------------------------- |
|
||||
| API reference | [Pinecone](/reference/integrations-pinecone) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/pinecone |
|
||||
|
||||
[Pinecone](https://www.pinecone.io/) is a cloud-based vector database. It is fast and easy to use.
|
||||
Unlike other solutions (such as Qdrant and Weaviate), it can’t run locally on the user's machine but provides a generous free tier.
|
||||
|
||||
### Installation
|
||||
|
||||
You can simply install the Pinecone Haystack integration with:
|
||||
|
||||
```shell
|
||||
pip install pinecone-haystack
|
||||
```
|
||||
|
||||
### Initialization
|
||||
|
||||
- To use Pinecone as a Document Store in Haystack, sign up for a free Pinecone [account](https://app.pinecone.io/) and get your API key.
|
||||
The Pinecone API key can be explicitly provided or automatically read from the environment variable `PINECONE_API_KEY` (recommended).
|
||||
- In Haystack, each `PineconeDocumentStore` operates in a specific namespace of an index. If not provided, both index and namespace are `default`.
|
||||
If the index already exists, the Document Store connects to it. Otherwise, it creates a new index.
|
||||
- When creating a new index, you can provide a `spec` in the form of a dictionary. This allows choosing between serverless and pod deployment options and setting additional parameters. Refer to the [Pinecone documentation](https://docs.pinecone.io/reference/api/control-plane/create_index) for more details. If not provided, a default spec with serverless deployment in the `us-east-1` region will be used (compatible with the free tier).
|
||||
- You can provide `dimension` and `metric`, but they are only taken into account if the Pinecone index does not already exist.
|
||||
|
||||
Then, you can use the Document Store like this:
|
||||
|
||||
```python
|
||||
from haystack import Document
|
||||
from haystack_integrations.document_stores.pinecone import PineconeDocumentStore
|
||||
|
||||
## Make sure you have the PINECONE_API_KEY environment variable set
|
||||
document_store = PineconeDocumentStore(
|
||||
index="default",
|
||||
namespace="default",
|
||||
dimension=5,
|
||||
metric="cosine",
|
||||
spec={"serverless": {"region": "us-east-1", "cloud": "aws"}},
|
||||
)
|
||||
|
||||
document_store.write_documents(
|
||||
[
|
||||
Document(content="This is first", embedding=[0.0] * 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,102 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| :------------ | :--------------------------------------------------------------------------------------- |
|
||||
| API reference | [Qdrant](/reference/integrations-qdrant) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/qdrant |
|
||||
|
||||
Qdrant is a powerful high-performance, massive-scale vector database. The `QdrantDocumentStore` can be used with any Qdrant instance, in-memory, locally persisted, hosted, and the official Qdrant Cloud.
|
||||
|
||||
### Installation
|
||||
|
||||
You can simply install the Qdrant Haystack integration with:
|
||||
|
||||
```shell
|
||||
pip install qdrant-haystack
|
||||
```
|
||||
|
||||
### Initialization
|
||||
|
||||
The quickest way to use `QdrantDocumentStore` is to create an in-memory instance of it:
|
||||
|
||||
```python
|
||||
from haystack.dataclasses.document import Document
|
||||
from haystack_integrations.document_stores.qdrant import QdrantDocumentStore
|
||||
|
||||
document_store = QdrantDocumentStore(
|
||||
":memory:",
|
||||
recreate_index=True,
|
||||
return_embedding=True,
|
||||
wait_result_from_api=True,
|
||||
)
|
||||
document_store.write_documents(
|
||||
[
|
||||
Document(content="This is first", embedding=[0.0] * 5),
|
||||
Document(content="This is second", embedding=[0.1, 0.2, 0.3, 0.4, 0.5]),
|
||||
],
|
||||
)
|
||||
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,152 @@
|
||||
---
|
||||
title: "WeaviateDocumentStore"
|
||||
id: weaviatedocumentstore
|
||||
slug: "/weaviatedocumentstore"
|
||||
description: ""
|
||||
---
|
||||
|
||||
# WeaviateDocumentStore
|
||||
|
||||
| | |
|
||||
| :------------ | :----------------------------------------------------------------------------------------- |
|
||||
| API reference | [Weaviate](/reference/integrations-weaviate) |
|
||||
| GitHub link | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/weaviate |
|
||||
|
||||
Weaviate is a multi-purpose vector DB that can store both embeddings and data objects, making it a good choice for multi-modality.
|
||||
|
||||
The `WeaviateDocumentStore` can connect to any Weaviate instance, whether it's running on Weaviate Cloud Services, Kubernetes, or a local Docker container.
|
||||
|
||||
## Installation
|
||||
|
||||
You can simply install the Weaviate Haystack integration with:
|
||||
|
||||
```shell
|
||||
pip install weaviate-haystack
|
||||
```
|
||||
|
||||
## Initialization
|
||||
|
||||
### Weaviate Embedded
|
||||
|
||||
To use `WeaviateDocumentStore` as a temporary instance, initialize it as ["Embedded"](https://weaviate.io/developers/weaviate/installation/embedded):
|
||||
|
||||
```python
|
||||
from haystack_integrations.document_stores.weaviate import WeaviateDocumentStore
|
||||
from weaviate.embedded import EmbeddedOptions
|
||||
|
||||
document_store = WeaviateDocumentStore(embedded_options=EmbeddedOptions())
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
You can use `WeaviateDocumentStore` in a local Docker container. This is what a minimal `docker-compose.yml` could look like:
|
||||
|
||||
```yaml
|
||||
---
|
||||
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,33 @@
|
||||
---
|
||||
title: "Introduction to Haystack"
|
||||
id: intro
|
||||
description: "Haystack is an **open-source AI framework** for building production-ready **AI Agents**, **retrieval-augmented generative pipelines** and **state-of-the-art 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**, **retrieval-augmented generative pipelines** and **state-of-the-art multimodal search systems**. 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 or Elasticsearch.
|
||||
|
||||
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**.
|
||||
|
||||
📜 [Learn more about Haystack Enterprise](https://haystack.deepset.ai/blog/announcing-haystack-enterprise)
|
||||
|
||||
👉 [Get in touch with our team](https://www.deepset.ai/products-and-services/haystack-enterprise)
|
||||
:::
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: "Advanced RAG Techniques"
|
||||
id: advanced-rag-techniques
|
||||
slug: "/advanced-rag-techniques"
|
||||
description: ""
|
||||
---
|
||||
|
||||
# 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)
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
---
|
||||
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.openai import OpenAIGenerator
|
||||
from haystack.components.builders import PromptBuilder
|
||||
from haystack import component, Document
|
||||
from haystack.components.converters import OutputAdapter
|
||||
from haystack.components.embedders import SentenceTransformersDocumentEmbedder
|
||||
|
||||
## 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 = OpenAIGenerator(
|
||||
model="gpt-3.5-turbo",
|
||||
generation_kwargs={"n": 5, "temperature": 0.75, "max_tokens": 400},
|
||||
)
|
||||
prompt_builder = PromptBuilder(
|
||||
template="""Given a question, generate a paragraph of text that answers the question. Question: {{question}} Paragraph:""",
|
||||
)
|
||||
|
||||
adapter = OutputAdapter(
|
||||
template="{{answers | build_doc}}",
|
||||
output_type=List[Document],
|
||||
custom_filters={"build_doc": lambda data: [Document(content=d) for d in data]},
|
||||
)
|
||||
|
||||
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", "generator")
|
||||
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,63 @@
|
||||
---
|
||||
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)
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Evaluator Models | All GPT models from OpenAI Google VertexAI Models Azure OpenAI Models Amazon Bedrock Models |
|
||||
| Supported metrics | ANSWER_CORRECTNESS, FAITHFULNESS, ANSWER_SIMILARITY, CONTEXT_PRECISION, CONTEXT_UTILIZATION,CONTEXT_RECALL, ASPECT_CRITIQUE, CONTEXT_RELEVANCY, ANSWER_RELEVANCY |
|
||||
| Customizable prompt for response evaluation | ✅, with ASPECT_CRITIQUE metric |
|
||||
| Explanations of scores | ❌ |
|
||||
| Monitoring dashboard | ❌ |
|
||||
|
||||
:::note
|
||||
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,89 @@
|
||||
---
|
||||
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
|
||||
|
||||
:::note
|
||||
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,38 @@
|
||||
---
|
||||
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 our 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](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](https://www.deepset.ai/products-and-services/haystack-enterprise)
|
||||
|
||||
### 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,113 @@
|
||||
---
|
||||
title: "Get Started"
|
||||
id: get-started
|
||||
slug: "/get-started"
|
||||
description: "Have a look at this page to learn how to quickly get up and running with Haystack. It contains instructions for installing, running your first RAG pipeline, adding data and further resources."
|
||||
---
|
||||
|
||||
# Get Started
|
||||
|
||||
Have a look at this page to learn how to quickly get up and running with Haystack. It contains instructions for installing, running your first RAG pipeline, adding data and further resources.
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Were you already using Haystack 1.x?</summary>
|
||||
|
||||
:::warning
|
||||
|
||||
Installing `farm-haystack` and `haystack-ai` in the same Python environment (virtualenv, Colab, or system) causes problems.
|
||||
|
||||
Installing both packages in the same environment can somehow work or fail in obscure ways. We suggest installing only one of these packages per Python environment. Make sure that you 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
|
||||
```
|
||||
|
||||
If you have any questions, please reach out to us on the [GitHub Discussion](https://github.com/deepset-ai/haystack/discussions) or [Discord](https://discord.com/invite/VBpFzsgRVF).
|
||||
:::
|
||||
|
||||
</details>
|
||||
|
||||
In the example below, we show how to set an API key using a Haystack [Secret](../concepts/secret-management.mdx). However, for easier use, you can also set an OpenAI key as an `OPENAI_API_KEY` environment variable.
|
||||
|
||||
```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"])
|
||||
```
|
||||
|
||||
### Adding Your Data
|
||||
|
||||
Instead of running the RAG pipeline on example data, learn how you can add your own custom data using [Document Stores](../concepts/document-store.mdx).
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: "Installation"
|
||||
id: installation
|
||||
slug: "/installation"
|
||||
description: "See how to quickly install Haystack with pip or conda."
|
||||
---
|
||||
|
||||
# Installation
|
||||
|
||||
See how to quickly install Haystack with pip or conda.
|
||||
|
||||
## Package Installation
|
||||
|
||||
Use [pip](https://github.com/pypa/pip) to install only the Haystack code:
|
||||
|
||||
```shell
|
||||
pip install haystack-ai
|
||||
```
|
||||
|
||||
Alternatively, you can use [conda](https://docs.conda.io/projects/conda/en/stable/):
|
||||
|
||||
```shell
|
||||
conda config --add channels conda-forge/label/haystack-ai_rc
|
||||
conda install haystack-ai
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Were you already using Haystack 1.x?</summary>
|
||||
|
||||
:::warning
|
||||
Warning
|
||||
|
||||
Installing `farm-haystack` and `haystack-ai` in the same Python environment (virtualenv, Colab, or system) causes problems.
|
||||
|
||||
Installing both packages in the same environment can somehow work or fail in obscure ways. We suggest installing only one of these packages per Python environment. Make sure that you 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
|
||||
```
|
||||
|
||||
If you have any questions, please reach out to us on the [GitHub Discussion](https://github.com/deepset-ai/haystack/discussions) or [Discord](https://discord.com/invite/VBpFzsgRVF).
|
||||
:::
|
||||
|
||||
</details>
|
||||
|
||||
### 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.
|
||||
|
||||
To be able to make changes to Haystack code, install with the following commands:
|
||||
|
||||
```shell
|
||||
## Clone the repo
|
||||
git clone https://github.com/deepset-ai/haystack.git
|
||||
|
||||
## Move into the cloned folder
|
||||
cd haystack
|
||||
|
||||
## Upgrade pip
|
||||
pip install --upgrade pip
|
||||
|
||||
## Install Haystack in editable mode
|
||||
pip install -e '.[dev]'
|
||||
```
|
||||
@@ -0,0 +1,512 @@
|
||||
---
|
||||
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.
|
||||
|
||||
:::note
|
||||
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
|
||||
|
||||
:::note
|
||||
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,224 @@
|
||||
---
|
||||
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`.
|
||||
|
||||
| | |
|
||||
| -------------------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| **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 |
|
||||
|
||||
## 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,
|
||||
- Additional dynamic keys based on `state_schema`.
|
||||
|
||||
### Parameters
|
||||
|
||||
To initialize the `Agent` component, you need to provide it with an instance of a Chat Generator that supports tools. You can pass a list of [tools](../../tools/tool.mdx) or [`ComponentTool`](../../tools/componenttool.mdx) instances, or wrap them in a [`Toolset`](../../tools/toolset.mdx) to manage them as a group.
|
||||
|
||||
You can additionally configure:
|
||||
|
||||
- A `system_prompt` for your Agent,
|
||||
- A list of `exit_conditions` strings that will cause the agent to return. Can be either:
|
||||
- “text”, which means that the Agent will exit as soon as the LLM replies only with a text response,
|
||||
- or specific tool names.
|
||||
- A `state_schema` for one agent invocation run. It defines extra information – such as documents or context – that tools can read from or write to during execution. You can use this schema to pass parameters that tools can both produce and consume.
|
||||
- `streaming_callback` to stream the tokens from the LLM directly in output.
|
||||
|
||||
:::note
|
||||
For a complete list of available parameters, refer to the [Agents API Documentation](/reference/agents-api).
|
||||
|
||||
:::
|
||||
|
||||
### 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
|
||||
|
||||
## Configure any `Generator` or `ChatGenerator` with a streaming callback
|
||||
component = SomeGeneratorOrChatGenerator(streaming_callback=print_streaming_chunk)
|
||||
|
||||
## If this is a `ChatGenerator`, pass a list of messages:
|
||||
## from haystack.dataclasses import ChatMessage
|
||||
## component.run([ChatMessage.from_user("Your question here")])
|
||||
|
||||
## If this is a (non-chat) `Generator`, pass a prompt:
|
||||
## component.run({"prompt": "Your prompt here"})
|
||||
```
|
||||
|
||||
:::note
|
||||
Streaming works only with a single response. If a provider supports multiple candidates, set `n=1`.
|
||||
|
||||
:::
|
||||
|
||||
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.
|
||||
|
||||
## Usage
|
||||
|
||||
### On its own
|
||||
|
||||
```python
|
||||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||||
from haystack.dataclasses import ChatMessage
|
||||
from haystack.tools.tool import Tool
|
||||
from haystack.components.agents import Agent
|
||||
from typing import List
|
||||
|
||||
|
||||
## Tool Function
|
||||
def calculate(expression: str) -> dict:
|
||||
try:
|
||||
result = eval(expression, {"__builtins__": {}})
|
||||
return {"result": result}
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
## Tool Definition
|
||||
calculator_tool = Tool(
|
||||
name="calculator",
|
||||
description="Evaluate basic math expressions.",
|
||||
parameters={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"expression": {
|
||||
"type": "string",
|
||||
"description": "Math expression to evaluate",
|
||||
},
|
||||
},
|
||||
"required": ["expression"],
|
||||
},
|
||||
function=calculate,
|
||||
outputs_to_state={"calc_result": {"source": "result"}},
|
||||
)
|
||||
|
||||
## Agent Setup
|
||||
agent = Agent(
|
||||
chat_generator=OpenAIChatGenerator(),
|
||||
tools=[calculator_tool],
|
||||
exit_conditions=["calculator"],
|
||||
state_schema={
|
||||
"calc_result": {"type": int},
|
||||
},
|
||||
)
|
||||
|
||||
## Run the Agent
|
||||
agent.warm_up()
|
||||
response = agent.run(messages=[ChatMessage.from_user("What is 7 * (4 + 2)?")])
|
||||
|
||||
## Output
|
||||
print(response["messages"])
|
||||
print("Calc Result:", response.get("calc_result"))
|
||||
```
|
||||
|
||||
### 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.core.pipeline import Pipeline
|
||||
from haystack.tools import tool
|
||||
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
||||
from typing import Optional
|
||||
from haystack.dataclasses import ChatMessage, Document
|
||||
|
||||
document_store = InMemoryDocumentStore() # create a document store or an SQL database
|
||||
|
||||
|
||||
@tool
|
||||
def add_database_tool(
|
||||
name: str,
|
||||
surname: str,
|
||||
job_title: Optional[str],
|
||||
other: Optional[str],
|
||||
):
|
||||
"""Use this tool to add names to the database with information about them"""
|
||||
document_store.write_documents(
|
||||
[
|
||||
Document(
|
||||
content=name + " " + surname + " " + (job_title or ""),
|
||||
meta={"other": other},
|
||||
),
|
||||
],
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
database_asistant = Agent(
|
||||
chat_generator=OpenAIChatGenerator(model="gpt-4o-mini"),
|
||||
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 you 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.
|
||||
""",
|
||||
exit_conditions=["text"],
|
||||
max_agent_steps=100,
|
||||
raise_on_tool_invocation_failure=False,
|
||||
)
|
||||
|
||||
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_asistant)
|
||||
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://en.wikipedia.org/wiki/Deepset"]}},
|
||||
)
|
||||
|
||||
print(agent_output["database_agent"]["messages"][-1].text)
|
||||
```
|
||||
|
||||
## Additional References
|
||||
|
||||
🧑🍳 Cookbook: [Build a GitHub Issue Resolver Agent](https://haystack.deepset.ai/cookbook/github_issue_resolver_agent)
|
||||
|
||||
📓 Tutorial: [Build a Tool-Calling Agent](https://haystack.deepset.ai/tutorials/43_building_a_tool_calling_agent)
|
||||
@@ -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. |
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| :------------------------------------- | :-------------------------------------------------------------------------------------------- |
|
||||
| **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 |
|
||||
|
||||
## 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")
|
||||
transcriber.warm_up()
|
||||
|
||||
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)
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| :------------------------------------- | :--------------------------------------------------------------------------------------------- |
|
||||
| **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 |
|
||||
|
||||
## 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,14 @@
|
||||
---
|
||||
title: "Builders"
|
||||
id: builders
|
||||
slug: "/builders"
|
||||
description: ""
|
||||
---
|
||||
|
||||
# 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. |
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 |
|
||||
|
||||
## 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
|
||||
|
||||
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:",
|
||||
),
|
||||
]
|
||||
|
||||
p = Pipeline()
|
||||
p.add_component(
|
||||
instance=InMemoryBM25Retriever(document_store=InMemoryDocumentStore()),
|
||||
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)
|
||||
```
|
||||
+412
@@ -0,0 +1,412 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| :------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **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 |
|
||||
|
||||
## 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 components 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"]
|
||||
|
||||
now = f"Current date is: {arrow.now('UTC').strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
yesterday = (
|
||||
f"Yesterday was: {(arrow.now('UTC').shift(days=-1)).strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
)
|
||||
```
|
||||
|
||||
## 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" %}
|
||||
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,
|
||||
],
|
||||
),
|
||||
]
|
||||
```
|
||||
|
||||
### 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)
|
||||
```
|
||||
|
||||
## Additional References
|
||||
|
||||
🧑🍳 Cookbook: [Advanced Prompt Customization for Anthropic](https://haystack.deepset.ai/cookbook/prompt_customization_for_anthropic)
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| :------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **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 |
|
||||
|
||||
## 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"]
|
||||
|
||||
now = f"Current date is: {arrow.now('UTC').strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
yesterday = (
|
||||
f"Yesterday was: {(arrow.now('UTC').shift(days=-1)).strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
)
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
## 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)
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 |
|
||||
|
||||
## 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. |
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| :------------------------------------- | :----------------------------------------------------------------------------------------------------------------- |
|
||||
| **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 |
|
||||
|
||||
## 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(
|
||||
instance=document_classifier,
|
||||
name="document_classifier",
|
||||
)
|
||||
indexing_pipeline.add_component(instance=metadata_router, name="metadata_router")
|
||||
indexing_pipeline.add_component(instance=english_embedder, name="english_embedder")
|
||||
indexing_pipeline.add_component(instance=german_embedder, name="german_embedder")
|
||||
indexing_pipeline.add_component(instance=en_writer, name="en_writer")
|
||||
indexing_pipeline.add_component(instance=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."),
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
```
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 |
|
||||
|
||||
## 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.warm_up()
|
||||
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(instance=retriever, name="retriever")
|
||||
pipeline.add_component(instance=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}})
|
||||
assert result["document_classifier"]["documents"][0].to_dict()["id"] == str(idx)
|
||||
assert (
|
||||
result["document_classifier"]["documents"][0].to_dict()["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 |
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 |
|
||||
|
||||
## 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
|
||||
|
||||
:::note
|
||||
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.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 |
|
||||
|
||||
## 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
|
||||
|
||||
:::note
|
||||
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.converters import OutputAdapter
|
||||
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")
|
||||
adapter = OutputAdapter(template="{{ replies[-1].text }}", output_type=str)
|
||||
|
||||
pipeline = Pipeline()
|
||||
pipeline.add_component("issue_viewer", issue_viewer)
|
||||
pipeline.add_component("prompt_builder", prompt_builder)
|
||||
pipeline.add_component("llm", llm)
|
||||
pipeline.add_component("adapter", adapter)
|
||||
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", "adapter.replies")
|
||||
pipeline.connect("adapter", "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
|
||||
```
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **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 |
|
||||
|
||||
## 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
|
||||
|
||||
:::note
|
||||
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])
|
||||
```
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 |
|
||||
|
||||
## 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
|
||||
|
||||
:::note
|
||||
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'}
|
||||
```
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 |
|
||||
|
||||
## 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
|
||||
|
||||
:::note
|
||||
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'}
|
||||
```
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 |
|
||||
|
||||
## 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
|
||||
|
||||
:::note
|
||||
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'})]}
|
||||
```
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
---
|
||||
title: "JinaReaderConnector"
|
||||
id: jinareaderconnector
|
||||
slug: "/jinareaderconnector"
|
||||
description: "Use Jina AI’s Reader API with Haystack."
|
||||
---
|
||||
|
||||
# JinaReaderConnector
|
||||
|
||||
Use Jina AI’s Reader API with Haystack.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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** | “document”: A list of documents |
|
||||
| **API reference** | [Jina](/reference/integrations-jina) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/jina |
|
||||
|
||||
## 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.messages", "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.
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
---
|
||||
title: "LangfuseConnector"
|
||||
id: langfuseconnector
|
||||
slug: "/langfuseconnector"
|
||||
description: "Learn how to work with Langfuse in Haystack."
|
||||
---
|
||||
|
||||
# LangfuseConnector
|
||||
|
||||
Learn how to work with Langfuse in Haystack.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Most common position in a pipeline** | Anywhere, as it’s not connected to other components |
|
||||
| **Mandatory init variables** | "name": The name of the pipeline or component to identify the tracing run |
|
||||
| **Output variables** | “name”: The name of the tracing component <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 |
|
||||
|
||||
## 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 />
|
||||
|
||||
:::note
|
||||
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())
|
||||
```
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| -------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
||||
| **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 |
|
||||
|
||||
## 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)
|
||||
```
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **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 is expected to carry parameter invocation payload. <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; 2. apiKey – for API keys and cookie authentication. |
|
||||
| **Output variables** | “service_response”: A dictionary that is a list of [`ChatMessage`](../../concepts/data-classes/chatmessage.mdx) objects where each message corresponds to a function invocation. If a user specifies multiple function calling requests, 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 |
|
||||
|
||||
## 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 the function calling model, resolves the actual function calling 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 a format that OpenAI's function calling mechanism 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 or other inputs to extract function 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.
|
||||
|
||||
:::note
|
||||
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 Dict, Any, List
|
||||
from haystack import Pipeline
|
||||
from haystack.components.generators.utils import print_streaming_chunk
|
||||
from haystack.components.converters import OpenAPIServiceToFunctions, OutputAdapter
|
||||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||||
from haystack.components.connectors import OpenAPIServiceConnector
|
||||
from haystack.components.fetchers import LinkContentFetcher
|
||||
from haystack.dataclasses import ChatMessage, ByteStream
|
||||
from haystack.utils import Secret
|
||||
|
||||
|
||||
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"]},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
system_prompt = requests.get("https://bit.ly/serper_dev_system_prompt").text
|
||||
serper_spec = requests.get("https://bit.ly/serper_dev_spec").text
|
||||
|
||||
pipe = Pipeline()
|
||||
pipe.add_component("spec_to_functions", OpenAPIServiceToFunctions())
|
||||
pipe.add_component(
|
||||
"functions_llm",
|
||||
OpenAIChatGenerator(
|
||||
api_key=Secret.from_token(llm_api_key),
|
||||
model="gpt-3.5-turbo-0613",
|
||||
),
|
||||
)
|
||||
pipe.add_component("openapi_container", OpenAPIServiceConnector())
|
||||
pipe.add_component(
|
||||
"a1",
|
||||
OutputAdapter(
|
||||
"{{functions[0] | prepare_fc}}",
|
||||
Dict[str, Any],
|
||||
{"prepare_fc": prepare_fc_params},
|
||||
),
|
||||
)
|
||||
pipe.add_component("a2", OutputAdapter("{{specs[0]}}", Dict[str, Any]))
|
||||
pipe.add_component(
|
||||
"a3",
|
||||
OutputAdapter("{{system_message + service_response}}", List[ChatMessage]),
|
||||
)
|
||||
pipe.add_component(
|
||||
"llm",
|
||||
OpenAIChatGenerator(
|
||||
api_key=Secret.from_token(llm_api_key),
|
||||
model="gpt-4-1106-preview",
|
||||
streaming_callback=print_streaming_chunk,
|
||||
),
|
||||
)
|
||||
|
||||
pipe.connect("spec_to_functions.functions", "a1.functions")
|
||||
pipe.connect("spec_to_functions.openapi_specs", "a2.specs")
|
||||
pipe.connect("a1", "functions_llm.generation_kwargs")
|
||||
pipe.connect("functions_llm.replies", "openapi_container.messages")
|
||||
pipe.connect("a2", "openapi_container.service_openapi_spec")
|
||||
pipe.connect("openapi_container.service_response", "a3.service_response")
|
||||
pipe.connect("a3", "llm.messages")
|
||||
|
||||
user_prompt = "Why was Sam Altman ousted from OpenAI?"
|
||||
|
||||
result = pipe.run(
|
||||
data={
|
||||
"functions_llm": {
|
||||
"messages": [
|
||||
ChatMessage.from_system("Only do function calling"),
|
||||
ChatMessage.from_user(user_prompt),
|
||||
],
|
||||
},
|
||||
"openapi_container": {"service_credentials": serper_dev_key},
|
||||
"spec_to_functions": {"sources": [ByteStream.from_string(serper_spec)]},
|
||||
"a3": {"system_message": [ChatMessage.from_system(system_prompt)]},
|
||||
},
|
||||
)
|
||||
```
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
---
|
||||
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.
|
||||
|
||||
| | |
|
||||
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **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 |
|
||||
|
||||
## 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(model="gpt-3.5-turbo"))
|
||||
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,34 @@
|
||||
---
|
||||
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 |
|
||||
| :----------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ |
|
||||
| [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. |
|
||||
| [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. |
|
||||
| [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. |
|
||||
| [MarkdownToDocument](converters/markdowntodocument.mdx) | Converts markdown files to documents. |
|
||||
| [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. |
|
||||
| [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. |
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: "AzureOCRDocumentConverter"
|
||||
id: azureocrdocumentconverter
|
||||
slug: "/azureocrdocumentconverter"
|
||||
description: "`AzureOCRDocumentConverter` converts files to documents using Azure's Document Intelligence service. It supports the following file formats: PDF (both searchable and image-only), JPEG, PNG, BMP, TIFF, DOCX, XLSX, PPTX, and HTML."
|
||||
---
|
||||
|
||||
# AzureOCRDocumentConverter
|
||||
|
||||
`AzureOCRDocumentConverter` converts files to documents using Azure's Document Intelligence service. It supports the following file formats: PDF (both searchable and image-only), JPEG, PNG, BMP, TIFF, DOCX, XLSX, PPTX, and HTML.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) , or right at the beginning of an indexing pipeline |
|
||||
| **Mandatory init variables** | "endpoint": The endpoint of your Azure resource <br /> <br />"api_key": The API key of your Azure resource. Can be set with `AZURE_AI_API_KEY` environment variable. |
|
||||
| **Mandatory run variables** | "sources": A list of file paths |
|
||||
| **Output variables** | "documents": A list of documents <br /> <br />"raw_azure_response": A list of raw responses from Azure |
|
||||
| **API reference** | [Converters](/reference/converters-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/azure.py |
|
||||
|
||||
## Overview
|
||||
|
||||
`AzureOCRDocumentConverter` takes a list of file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects as input and uses Azure services to convert the files to a list of documents. Optionally, metadata can be attached to the documents through the `meta` input parameter. You need an active Azure account and a Document Intelligence or Cognitive Services resource to use this integration. Follow the steps described in the Azure [documentation](https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/quickstarts/get-started-sdks-rest-api) to set up your resource.
|
||||
|
||||
The component uses an `AZURE_AI_API_KEY` environment variable by default. Otherwise, you can pass an `api_key` at initialization – see code examples below.
|
||||
|
||||
When you initialize the component, you can optionally set the `model_id`, which refers to the model you want to use. Please refer to [Azure documentation](https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/choose-model-feature) for a list of available models. The default model is `"prebuilt-read"`.
|
||||
|
||||
The `AzureOCRDocumentConverter` doesn’t extract the tables from a file as plain text but generates separate `Document` objects of type `table` that maintain the two-dimensional structure of the tables.
|
||||
|
||||
## Usage
|
||||
|
||||
You need to install `azure-ai-formrecognizer` package to use the `AzureOCRDocumentConverter`:
|
||||
|
||||
```shell
|
||||
pip install "azure-ai-formrecognizer>=3.2.0b2"
|
||||
```
|
||||
|
||||
### On its own
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
from haystack.components.converters import AzureOCRDocumentConverter
|
||||
from haystack.utils import Secret
|
||||
|
||||
converter = AzureOCRDocumentConverter(
|
||||
endpoint="azure_resource_url",
|
||||
api_key=Secret.from_token("<your-api-key>"),
|
||||
)
|
||||
|
||||
converter.run(sources=[Path("my_file.pdf")])
|
||||
```
|
||||
|
||||
### In a pipeline
|
||||
|
||||
```python
|
||||
from haystack import Pipeline
|
||||
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
||||
from haystack.components.converters import AzureOCRDocumentConverter
|
||||
from haystack.components.preprocessors import DocumentCleaner
|
||||
from haystack.components.preprocessors import DocumentSplitter
|
||||
from haystack.components.writers import DocumentWriter
|
||||
from haystack.utils import Secret
|
||||
|
||||
document_store = InMemoryDocumentStore()
|
||||
|
||||
pipeline = Pipeline()
|
||||
pipeline.add_component(
|
||||
"converter",
|
||||
AzureOCRDocumentConverter(
|
||||
endpoint="azure_resource_url",
|
||||
api_key=Secret.from_token("<your-api-key>"),
|
||||
),
|
||||
)
|
||||
pipeline.add_component("cleaner", DocumentCleaner())
|
||||
pipeline.add_component(
|
||||
"splitter",
|
||||
DocumentSplitter(split_by="sentence", split_length=5),
|
||||
)
|
||||
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
|
||||
pipeline.connect("converter", "cleaner")
|
||||
pipeline.connect("cleaner", "splitter")
|
||||
pipeline.connect("splitter", "writer")
|
||||
|
||||
file_names = ["my_file.pdf"]
|
||||
pipeline.run({"converter": {"sources": file_names}})
|
||||
```
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
---
|
||||
title: "CSVToDocument"
|
||||
id: csvtodocument
|
||||
slug: "/csvtodocument"
|
||||
description: "Converts CSV files to documents."
|
||||
---
|
||||
|
||||
# CSVToDocument
|
||||
|
||||
Converts CSV files to documents.
|
||||
|
||||
| | |
|
||||
| :------------------------------------- | :---------------------------------------------------------------------------------------------- |
|
||||
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) , or right at the beginning of an indexing pipeline |
|
||||
| **Mandatory run variables** | "sources": A list of file paths or [ByteStream](../../concepts/data-classes.mdx#bytestream) objects |
|
||||
| **Output variables** | "documents": A list of documents |
|
||||
| **API reference** | [Converters](/reference/converters-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/csv.py |
|
||||
|
||||
## Overview
|
||||
|
||||
`CSVToDocument` converts one or more CSV files into a text document.
|
||||
|
||||
The component uses UTF-8 encoding by default, but you may specify a different encoding if needed during initialization.
|
||||
You can optionally attach metadata to each document with a `meta` parameter when running the component.
|
||||
|
||||
## Usage
|
||||
|
||||
### On its own
|
||||
|
||||
```python
|
||||
from haystack.components.converters.csv import CSVToDocument
|
||||
|
||||
converter = CSVToDocument()
|
||||
results = converter.run(
|
||||
sources=["sample.csv"],
|
||||
meta={"date_added": datetime.now().isoformat()},
|
||||
)
|
||||
documents = results["documents"]
|
||||
|
||||
print(documents[0].content)
|
||||
## 'col1,col2\now1,row1\nrow2row2\n'
|
||||
```
|
||||
|
||||
### In a pipeline
|
||||
|
||||
```python
|
||||
from haystack import Pipeline
|
||||
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
||||
from haystack.components.converters import CSVToDocument
|
||||
from haystack.components.preprocessors import DocumentCleaner
|
||||
from haystack.components.preprocessors import DocumentSplitter
|
||||
from haystack.components.writers import DocumentWriter
|
||||
|
||||
document_store = InMemoryDocumentStore()
|
||||
|
||||
pipeline = Pipeline()
|
||||
pipeline.add_component("converter", CSVToDocument())
|
||||
pipeline.add_component("cleaner", DocumentCleaner())
|
||||
pipeline.add_component(
|
||||
"splitter",
|
||||
DocumentSplitter(split_by="sentence", split_length=5),
|
||||
)
|
||||
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
|
||||
pipeline.connect("converter", "cleaner")
|
||||
pipeline.connect("cleaner", "splitter")
|
||||
pipeline.connect("splitter", "writer")
|
||||
|
||||
pipeline.run({"converter": {"sources": file_names}})
|
||||
```
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
---
|
||||
title: "DocumentToImageContent"
|
||||
id: documenttoimagecontent
|
||||
slug: "/documenttoimagecontent"
|
||||
description: "`DocumentToImageContent` extracts visual data from image or PDF file-based documents and converts them into `ImageContent` objects. These are ready for multimodal AI pipelines, including tasks like image question-answering and captioning."
|
||||
---
|
||||
|
||||
# DocumentToImageContent
|
||||
|
||||
`DocumentToImageContent` extracts visual data from image or PDF file-based documents and converts them into `ImageContent` objects. These are ready for multimodal AI pipelines, including tasks like image question-answering and captioning.
|
||||
|
||||
| | |
|
||||
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Most common position in a pipeline** | Before a `ChatPromptBuilder` in a query pipeline |
|
||||
| **Mandatory run variables** | "documents": A list of documents to process. Each document should have metadata containing at minimum a 'file_path_meta_field' key. PDF documents additionally require a 'page_number' key to specify which page to convert. |
|
||||
| **Output variables** | "image_contents": A list of `ImageContent` objects |
|
||||
| **API reference** | [Image Converters](/reference/image-converters-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/image/document_to_image.py |
|
||||
|
||||
## Overview
|
||||
|
||||
`DocumentToImageContent` processes a list of documents containing image or PDF file paths and converts them into `ImageContent` objects.
|
||||
|
||||
- For images, it reads and encodes the file directly.
|
||||
- For PDFs, it extracts the specified page (through `page_number` in metadata) and converts it to an image.
|
||||
|
||||
By default, it looks for the file path in the `file_path` metadata field. You can customize this with the `file_path_meta_field` parameter. The `root_path` lets you specify a common base directory for file resolution.
|
||||
|
||||
This component is typically used in query pipelines right before a `ChatPromptBuilder` when you would like to add Images to your user prompt.
|
||||
|
||||
If `size` is provided, the images will be resized while maintaining aspect ratio. This reduces file size, memory usage, and processing time, which is beneficial when working with models that have resolution constraints or when transmitting images to remote services.
|
||||
|
||||
## Usage
|
||||
|
||||
### On its own
|
||||
|
||||
```python
|
||||
from haystack import Document
|
||||
from haystack.components.converters.image.document_to_image import (
|
||||
DocumentToImageContent,
|
||||
)
|
||||
|
||||
converter = DocumentToImageContent(
|
||||
file_path_meta_field="file_path",
|
||||
root_path="/data/documents",
|
||||
detail="high",
|
||||
size=(800, 600),
|
||||
)
|
||||
|
||||
documents = [
|
||||
Document(content="Photo of a mountain", meta={"file_path": "mountain.jpg"}),
|
||||
Document(
|
||||
content="First page of a report",
|
||||
meta={"file_path": "report.pdf", "page_number": 1},
|
||||
),
|
||||
]
|
||||
|
||||
result = converter.run(documents)
|
||||
image_contents = result["image_contents"]
|
||||
print(image_contents)
|
||||
|
||||
## [
|
||||
## ImageContent(
|
||||
## base64_image="/9j/4A...", mime_type="image/jpeg", detail="high",
|
||||
## meta={"file_path": "mountain.jpg"}
|
||||
## ),
|
||||
## ImageContent(
|
||||
## base64_image="/9j/4A...", mime_type="image/jpeg", detail="high",
|
||||
## meta={"file_path": "report.pdf", "page_number": 1}
|
||||
## )
|
||||
## ]
|
||||
```
|
||||
|
||||
### In a pipeline
|
||||
|
||||
You can use `DocumentToImageContent` in multimodal indexing pipelines before passing to an Embedder or captioning model.
|
||||
|
||||
```python
|
||||
from haystack import Document, Pipeline
|
||||
from haystack.components.builders import ChatPromptBuilder
|
||||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||||
from haystack.components.converters.image.document_to_image import (
|
||||
DocumentToImageContent,
|
||||
)
|
||||
|
||||
## Query pipeline
|
||||
pipeline = Pipeline()
|
||||
pipeline.add_component("image_converter", DocumentToImageContent(detail="auto"))
|
||||
pipeline.add_component(
|
||||
"chat_prompt_builder",
|
||||
ChatPromptBuilder(
|
||||
required_variables=["question"],
|
||||
template="""{% message role="system" %}
|
||||
You are a friendly assistant that answers questions based on provided images.
|
||||
{% endmessage %}
|
||||
|
||||
{%- message role="user" -%}
|
||||
Only provide an answer to the question using the images provided.
|
||||
|
||||
Question: {{ question }}
|
||||
Answer:
|
||||
|
||||
{%- for img in image_contents -%}
|
||||
{{ img | templatize_part }}
|
||||
{%- endfor -%}
|
||||
{%- endmessage -%}
|
||||
""",
|
||||
),
|
||||
)
|
||||
pipeline.add_component("llm", OpenAIChatGenerator(model="gpt-4o-mini"))
|
||||
|
||||
pipeline.connect("image_converter", "chat_prompt_builder.image_contents")
|
||||
pipeline.connect("chat_prompt_builder", "llm")
|
||||
|
||||
documents = [
|
||||
Document(content="Cat image", meta={"file_path": "cat.jpg"}),
|
||||
Document(content="Doc intro", meta={"file_path": "paper.pdf", "page_number": 1}),
|
||||
]
|
||||
|
||||
result = pipeline.run(
|
||||
data={
|
||||
"image_converter": {"documents": documents},
|
||||
"chat_prompt_builder": {"question": "What color is the cat?"},
|
||||
},
|
||||
)
|
||||
print(result)
|
||||
|
||||
## {
|
||||
## "llm": {
|
||||
## "replies": [
|
||||
## ChatMessage(
|
||||
## _role=<ChatRole.ASSISTANT: 'assistant'>,
|
||||
## _content=[TextContent(text="The cat is orange with some black.")],
|
||||
## _name=None,
|
||||
## _meta={
|
||||
## "model": "gpt-4o-mini-2024-07-18",
|
||||
## "index": 0,
|
||||
## "finish_reason": "stop",
|
||||
## "usage": {...},
|
||||
## },
|
||||
## )
|
||||
## ]
|
||||
## }
|
||||
## }
|
||||
```
|
||||
|
||||
## Additional References
|
||||
|
||||
🧑🍳 Cookbook: [Introduction to Multimodality](https://haystack.deepset.ai/cookbook/multimodal_intro)
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
---
|
||||
title: "DOCXToDocument"
|
||||
id: docxtodocument
|
||||
slug: "/docxtodocument"
|
||||
description: "Convert DOCX files to documents."
|
||||
---
|
||||
|
||||
# DOCXToDocument
|
||||
|
||||
Convert DOCX files to documents.
|
||||
|
||||
| | |
|
||||
| :------------------------------------- | :--------------------------------------------------------------------------------------------- |
|
||||
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) or right at the beginning of an indexing pipeline |
|
||||
| **Mandatory run variables** | "sources": DOCX file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects |
|
||||
| **Output variables** | "documents": A list of documents |
|
||||
| **API reference** | [Converters](/reference/converters-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/docx.py |
|
||||
|
||||
## Overview
|
||||
|
||||
The `DOCXToDocument` component converts DOCX files into documents. It takes a list of file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects as input and outputs the converted result as a list of documents. By defining the table format (CSV or Markdown), you can use this component to extract tables in your DOCX files. Optionally, you can attach metadata to the documents through the `meta` input parameter.
|
||||
|
||||
## Usage
|
||||
|
||||
First, install the`python-docx` package to start using this converter:
|
||||
|
||||
```shell
|
||||
pip install python-docx
|
||||
```
|
||||
|
||||
### On its own
|
||||
|
||||
```python
|
||||
from haystack.components.converters.docx import DOCXToDocument, DOCXTableFormat
|
||||
|
||||
converter = DOCXToDocument()
|
||||
## or define the table format
|
||||
converter = DOCXToDocument(table_format=DOCXTableFormat.CSV)
|
||||
|
||||
results = converter.run(
|
||||
sources=["sample.docx"],
|
||||
meta={"date_added": datetime.now().isoformat()},
|
||||
)
|
||||
documents = results["documents"]
|
||||
|
||||
print(documents[0].content)
|
||||
|
||||
## 'This is the text from the DOCX file.'
|
||||
```
|
||||
|
||||
### In a pipeline
|
||||
|
||||
```python
|
||||
from haystack import Pipeline
|
||||
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
||||
from haystack.components.converters import DOCXToDocument
|
||||
from haystack.components.preprocessors import DocumentCleaner
|
||||
from haystack.components.preprocessors import DocumentSplitter
|
||||
from haystack.components.writers import DocumentWriter
|
||||
|
||||
document_store = InMemoryDocumentStore()
|
||||
|
||||
pipeline = Pipeline()
|
||||
pipeline.add_component("converter", DOCXToDocument())
|
||||
pipeline.add_component("cleaner", DocumentCleaner())
|
||||
pipeline.add_component(
|
||||
"splitter",
|
||||
DocumentSplitter(split_by="sentence", split_length=5),
|
||||
)
|
||||
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
|
||||
pipeline.connect("converter", "cleaner")
|
||||
pipeline.connect("cleaner", "splitter")
|
||||
pipeline.connect("splitter", "writer")
|
||||
|
||||
pipeline.run({"converter": {"sources": file_names}})
|
||||
```
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: "External Integrations"
|
||||
id: external-integrations-converters
|
||||
slug: "/external-integrations-converters"
|
||||
description: "External integrations that enable extracting data from files in different formats and cast it into the unified document format."
|
||||
---
|
||||
|
||||
# External Integrations
|
||||
|
||||
External integrations that enable extracting data from files in different formats and cast it into the unified document format.
|
||||
|
||||
| Name | Description |
|
||||
| :----------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Docling](https://haystack.deepset.ai/integrations/docling/) | Parse PDF, DOCX, HTML, and other document formats into a rich standardized representation (such as layout, tables..), which it can then export to Markdown, JSON, and other formats. |
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
---
|
||||
title: "HTMLToDocument"
|
||||
id: htmltodocument
|
||||
slug: "/htmltodocument"
|
||||
description: "A component that converts HTML files to documents."
|
||||
---
|
||||
|
||||
# HTMLToDocument
|
||||
|
||||
A component that converts HTML files to documents.
|
||||
|
||||
| | |
|
||||
| :------------------------------------- | :---------------------------------------------------------------------------------------------- |
|
||||
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) , or right at the beginning of an indexing pipeline |
|
||||
| **Mandatory run variables** | "sources": A list of HTML file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects |
|
||||
| **Output variables** | "documents": A list of documents |
|
||||
| **API reference** | [Converters](/reference/converters-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/html.py |
|
||||
|
||||
## Overview
|
||||
|
||||
The `HTMLToDocument` component converts HTML files into documents. It can be used in an indexing pipeline to index the contents of an HTML file into a Document Store or even in a querying pipeline after the [`LinkContentFetcher`](../fetchers/linkcontentfetcher.mdx). The `HTMLToDocument` component takes a list of HTML file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects as input and converts the files to a list of documents. Optionally, you can attach metadata to the documents through the `meta` input parameter.
|
||||
|
||||
When you initialize the component, you can optionally set `extraction_kwargs`, a dictionary containing keyword arguments to customize the extraction process. These are passed to the underlying Trafilatura `extract` function. For the full list of available arguments, see the [Trafilatura documentation](https://trafilatura.readthedocs.io/en/latest/corefunctions.html#extract).
|
||||
|
||||
## Usage
|
||||
|
||||
### On its own
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
from haystack.components.converters import HTMLToDocument
|
||||
|
||||
converter = HTMLToDocument()
|
||||
|
||||
docs = converter.run(sources=[Path("saved_page.html")])
|
||||
```
|
||||
|
||||
### In a pipeline
|
||||
|
||||
Here's an example of an indexing pipeline that writes the contents of an HTML file into an `InMemoryDocumentStore`:
|
||||
|
||||
```python
|
||||
from haystack import Pipeline
|
||||
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
||||
from haystack.components.converters import HTMLToDocument
|
||||
from haystack.components.preprocessors import DocumentCleaner
|
||||
from haystack.components.preprocessors import DocumentSplitter
|
||||
from haystack.components.writers import DocumentWriter
|
||||
|
||||
document_store = InMemoryDocumentStore()
|
||||
|
||||
pipeline = Pipeline()
|
||||
pipeline.add_component("converter", HTMLToDocument())
|
||||
pipeline.add_component("cleaner", DocumentCleaner())
|
||||
pipeline.add_component(
|
||||
"splitter",
|
||||
DocumentSplitter(split_by="sentence", split_length=5),
|
||||
)
|
||||
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
|
||||
pipeline.connect("converter", "cleaner")
|
||||
pipeline.connect("cleaner", "splitter")
|
||||
pipeline.connect("splitter", "writer")
|
||||
|
||||
pipeline.run({"converter": {"sources": file_names}})
|
||||
```
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: "ImageFileToDocument"
|
||||
id: imagefiletodocument
|
||||
slug: "/imagefiletodocument"
|
||||
description: "Converts image file references into empty `Document` objects with associated metadata."
|
||||
---
|
||||
|
||||
# ImageFileToDocument
|
||||
|
||||
Converts image file references into empty `Document` objects with associated metadata.
|
||||
|
||||
| | |
|
||||
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Most common position in a pipeline** | Before a component that processes images, like `SentenceTransformersImageDocumentEmbedder` or `LLMDocumentContentExtractor` |
|
||||
| **Mandatory run variables** | "sources": A list of image file paths or ByteStreams |
|
||||
| **Output variables** | "documents": A list of empty Document objects with associated metadata |
|
||||
| **API reference** | [Image Converters](/reference/image-converters-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/image/file_to_document.py |
|
||||
|
||||
## Overview
|
||||
|
||||
`ImageFileToDocument` converts image file sources into empty `Document` objects with associated metadata.
|
||||
|
||||
This component is useful in pipelines where image file paths need to be wrapped in `Document` objects to be processed by downstream components such as `SentenceTransformersImageDocumentEmbedder` or `LLMDocumentContentExtractor`.
|
||||
|
||||
It _does not_ extract any content from the image files, but instead creates `Document` objects with `None` as their content and attaches metadata such as file path and any user-provided values.
|
||||
|
||||
Each source can be:
|
||||
|
||||
- A file path (string or `Path`), or
|
||||
- A `ByteStream` object.
|
||||
|
||||
Optionally, you can provide metadata using the `meta` parameter. This can be a single dictionary (applied to all documents) or a list matching the length of `sources`.
|
||||
|
||||
## Usage
|
||||
|
||||
### On its own
|
||||
|
||||
This component is primarily meant to be used in pipelines.
|
||||
|
||||
```python
|
||||
|
||||
from haystack.components.converters.image import ImageFileToDocument
|
||||
|
||||
converter = ImageFileToDocument()
|
||||
|
||||
sources = ["image.jpg", "another_image.png"]
|
||||
|
||||
result = converter.run(sources=sources)
|
||||
documents = result["documents"]
|
||||
|
||||
print(documents)
|
||||
|
||||
## [Document(id=..., content=None, meta={'file_path': 'image.jpg'}),
|
||||
## Document(id=..., content=None, meta={'file_path': 'another_image.png'})]
|
||||
```
|
||||
|
||||
### In a pipeline
|
||||
|
||||
In the following Pipeline, image documents are created using the `ImageFileToDocument` component, then they are enriched with image embeddings and saved in the Document Store.
|
||||
|
||||
```python
|
||||
from haystack import Pipeline
|
||||
from haystack.components.converters.image import ImageFileToDocument
|
||||
from haystack.components.embedders.image import (
|
||||
SentenceTransformersDocumentImageEmbedder,
|
||||
)
|
||||
from haystack.components.writers.document_writer import DocumentWriter
|
||||
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
||||
|
||||
## Create our document store
|
||||
doc_store = InMemoryDocumentStore()
|
||||
|
||||
## Define pipeline with components
|
||||
indexing_pipe = Pipeline()
|
||||
indexing_pipe.add_component(
|
||||
"image_converter",
|
||||
ImageFileToDocument(store_full_path=True),
|
||||
)
|
||||
indexing_pipe.add_component(
|
||||
"image_doc_embedder",
|
||||
SentenceTransformersDocumentImageEmbedder(),
|
||||
)
|
||||
indexing_pipe.add_component("document_writer", DocumentWriter(doc_store))
|
||||
|
||||
indexing_pipe.connect("image_converter.documents", "image_doc_embedder.documents")
|
||||
indexing_pipe.connect("image_doc_embedder.documents", "document_writer.documents")
|
||||
|
||||
indexing_result = indexing_pipe.run(
|
||||
data={"image_converter": {"sources": ["apple.jpg", "kiwi.png"]}},
|
||||
)
|
||||
|
||||
indexed_documents = doc_store.filter_documents()
|
||||
print(f"Indexed {len(indexed_documents)} documents")
|
||||
## Indexed 2 documents
|
||||
```
|
||||
|
||||
## Additional References
|
||||
|
||||
🧑🍳 Cookbook: [Introduction to Multimodality](https://haystack.deepset.ai/cookbook/multimodal_intro)
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
---
|
||||
title: "ImageFileToImageContent"
|
||||
id: imagefiletoimagecontent
|
||||
slug: "/imagefiletoimagecontent"
|
||||
description: "`ImageFileToImageContent` reads local image files and converts them into `ImageContent` objects. These are ready for multimodal AI pipelines, including tasks like image captioning, visual QA, or prompt-based generation."
|
||||
---
|
||||
|
||||
# ImageFileToImageContent
|
||||
|
||||
`ImageFileToImageContent` reads local image files and converts them into `ImageContent` objects. These are ready for multimodal AI pipelines, including tasks like image captioning, visual QA, or prompt-based generation.
|
||||
|
||||
| | |
|
||||
| -------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| **Most common position in a pipeline** | Before a `ChatPromptBuilder` in a query pipeline |
|
||||
| **Mandatory run variables** | "sources": A list of image file paths or ByteStreams |
|
||||
| **Output variables** | "image_contents": A list of ImageContent objects |
|
||||
| **API reference** | [Image Converters](/reference/image-converters-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/image/file_to_image.py |
|
||||
|
||||
## Overview
|
||||
|
||||
`ImageFileToImageContent` processes a list of image sources and converts them into `ImageContent` objects. These can be used in multimodal pipelines that require base64-encoded image input.
|
||||
|
||||
Each source can be:
|
||||
|
||||
- A file path (string or `Path`), or
|
||||
- A `ByteStream` object.
|
||||
|
||||
Optionally, you can provide metadata using the `meta` parameter. This can be a single dictionary (applied to all images) or a list matching the length of `sources`.
|
||||
|
||||
Use the `size` parameter to resize images while preserving aspect ratio. This reduces memory usage and transmission size, which is helpful when working with remote models or limited-resource environments.
|
||||
|
||||
This component is often used in query pipelines just before a `ChatPromptBuilder`.
|
||||
|
||||
## Usage
|
||||
|
||||
### On its own
|
||||
|
||||
```python
|
||||
|
||||
from haystack.components.converters.image import ImageFileToImageContent
|
||||
|
||||
converter = ImageFileToImageContent(detail="high", size=(800, 600))
|
||||
|
||||
sources = ["cat.jpg", "scenery.png"]
|
||||
|
||||
result = converter.run(sources=sources)
|
||||
image_contents = result["image_contents"]
|
||||
print(image_contents)
|
||||
|
||||
## [
|
||||
## ImageContent(
|
||||
## base64_image="/9j/4A...", mime_type="image/jpeg", detail="high",
|
||||
## meta={"file_path": "cat.jpg"}
|
||||
## ),
|
||||
## ImageContent(
|
||||
## base64_image="/9j/4A...", mime_type="image/png", detail="high",
|
||||
## meta={"file_path": "scenery.png"}
|
||||
## )
|
||||
## ]
|
||||
```
|
||||
|
||||
### In a pipeline
|
||||
|
||||
Use `ImageFileToImageContent` to supply image data to a `ChatPromptBuilder` for multimodal QA or captioning with an LLM.
|
||||
|
||||
```python
|
||||
from haystack import Pipeline
|
||||
from haystack.components.builders import ChatPromptBuilder
|
||||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||||
from haystack.components.converters.image import ImageFileToImageContent
|
||||
|
||||
## Query pipeline
|
||||
pipeline = Pipeline()
|
||||
pipeline.add_component("image_converter", ImageFileToImageContent(detail="auto"))
|
||||
pipeline.add_component(
|
||||
"chat_prompt_builder",
|
||||
ChatPromptBuilder(
|
||||
required_variables=["question"],
|
||||
template="""{% message role="system" %}
|
||||
You are a helpful assistant that answers questions using the provided images.
|
||||
{% endmessage %}
|
||||
|
||||
{% message role="user" %}
|
||||
Question: {{ question }}
|
||||
|
||||
{% for img in image_contents %}
|
||||
{{ img | templatize_part }}
|
||||
{% endfor %}
|
||||
{% endmessage %}
|
||||
""",
|
||||
),
|
||||
)
|
||||
pipeline.add_component("llm", OpenAIChatGenerator(model="gpt-4o-mini"))
|
||||
|
||||
pipeline.connect("image_converter", "chat_prompt_builder.image_contents")
|
||||
pipeline.connect("chat_prompt_builder", "llm")
|
||||
|
||||
sources = ["apple.jpg", "haystack-logo.png"]
|
||||
|
||||
result = pipeline.run(
|
||||
data={
|
||||
"image_converter": {"sources": sources},
|
||||
"chat_prompt_builder": {"question": "Describe the Haystack logo."},
|
||||
},
|
||||
)
|
||||
print(result)
|
||||
|
||||
## {
|
||||
## "llm": {
|
||||
## "replies": [
|
||||
## ChatMessage(
|
||||
## _role=<ChatRole.ASSISTANT: 'assistant'>,
|
||||
## _content=[TextContent(text="The Haystack logo features...")],
|
||||
## ...
|
||||
## )
|
||||
## ]
|
||||
## }
|
||||
## }
|
||||
```
|
||||
|
||||
## Additional References
|
||||
|
||||
🧑🍳 Cookbook: [Introduction to Multimodality](https://haystack.deepset.ai/cookbook/multimodal_intro)
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: "JSONConverter"
|
||||
id: jsonconverter
|
||||
slug: "/jsonconverter"
|
||||
description: "Converts JSON files to text documents."
|
||||
---
|
||||
|
||||
# JSONConverter
|
||||
|
||||
Converts JSON files to text documents.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) , or right at the beginning of an indexing pipeline |
|
||||
| **Mandatory init variables** | ONE OF, OR BOTH: <br /> <br />"jq_schema": A jq filter string to extract content <br /> <br />"content_key": A key string to extract document content |
|
||||
| **Mandatory run variables** | "sources": A list of file paths or [ByteStream](../../concepts/data-classes.mdx#bytestream) objects |
|
||||
| **Output variables** | "documents": A list of documents |
|
||||
| **API reference** | [Converters](/reference/converters-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/json.py |
|
||||
|
||||
## Overview
|
||||
|
||||
`JSONConverter` converts one or more JSON files into a text document.
|
||||
|
||||
### Parameters Overview
|
||||
|
||||
To initialize `JSONConverter`, you must provide either `jq_schema`, or `content_key` parameter, or both.
|
||||
|
||||
`jq_schema` parameter filter extracts nested data from JSON files. Refer to the [jq documentation](https://jqlang.github.io/jq/) for filter syntax. If not set, the entire JSON file is used.
|
||||
|
||||
The `content_key` parameter lets you specify which key in the extracted data will be the document's content.
|
||||
|
||||
- If both `jq_schema` and `content_key` are set, the `content_key` is searched in the data extracted by `jq_schema`. Non-object data will be skipped.
|
||||
- If only `jq_schema` is set, the extracted value must be scalar; objects or arrays will be skipped.
|
||||
- If only `content_key` is set, the source must be a JSON object, or it will be skipped.
|
||||
|
||||
Check out the [API reference](../converters.mdx) for the full list of parameters.
|
||||
|
||||
## Usage
|
||||
|
||||
You need to install the `jq` package to use this Converter:
|
||||
|
||||
```shell
|
||||
pip install jq
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
Here is an example of simple component usage:
|
||||
|
||||
```python
|
||||
import json
|
||||
|
||||
from haystack.components.converters import JSONConverter
|
||||
from haystack.dataclasses import ByteStream
|
||||
|
||||
source = ByteStream.from_string(
|
||||
json.dumps({"text": "This is the content of my document"}),
|
||||
)
|
||||
|
||||
converter = JSONConverter(content_key="text")
|
||||
results = converter.run(sources=[source])
|
||||
documents = results["documents"]
|
||||
print(documents[0].content)
|
||||
## 'This is the content of my document'
|
||||
```
|
||||
|
||||
In the following more complex example, we provide a `jq_schema` string to filter the JSON source files and `extra_meta_fields` to extract from the filtered data:
|
||||
|
||||
```python
|
||||
import json
|
||||
|
||||
from haystack.components.converters import JSONConverter
|
||||
from haystack.dataclasses import ByteStream
|
||||
|
||||
data = {
|
||||
"laureates": [
|
||||
{
|
||||
"firstname": "Enrico",
|
||||
"surname": "Fermi",
|
||||
"motivation": "for his demonstrations of the existence of new radioactive elements produced "
|
||||
"by neutron irradiation, and for his related discovery of nuclear reactions brought about by"
|
||||
" slow neutrons",
|
||||
},
|
||||
{
|
||||
"firstname": "Rita",
|
||||
"surname": "Levi-Montalcini",
|
||||
"motivation": "for their discoveries of growth factors",
|
||||
},
|
||||
],
|
||||
}
|
||||
source = ByteStream.from_string(json.dumps(data))
|
||||
converter = JSONConverter(
|
||||
jq_schema=".laureates[]",
|
||||
content_key="motivation",
|
||||
extra_meta_fields={"firstname", "surname"},
|
||||
)
|
||||
|
||||
results = converter.run(sources=[source])
|
||||
documents = results["documents"]
|
||||
print(documents[0].content)
|
||||
## 'for his demonstrations of the existence of new radioactive elements produced by
|
||||
## neutron irradiation, and for his related discovery of nuclear reactions brought
|
||||
## about by slow neutrons'
|
||||
|
||||
print(documents[0].meta)
|
||||
## {'firstname': 'Enrico', 'surname': 'Fermi'}
|
||||
|
||||
print(documents[1].content)
|
||||
## 'for their discoveries of growth factors'
|
||||
|
||||
print(documents[1].meta)
|
||||
## {'firstname': 'Rita', 'surname': 'Levi-Montalcini'}
|
||||
```
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: "MarkdownToDocument"
|
||||
id: markdowntodocument
|
||||
slug: "/markdowntodocument"
|
||||
description: "A component that converts Markdown files to documents."
|
||||
---
|
||||
|
||||
# MarkdownToDocument
|
||||
|
||||
A component that converts Markdown files to documents.
|
||||
|
||||
| | |
|
||||
| :------------------------------------- | :---------------------------------------------------------------------------------------------- |
|
||||
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) , or right at the beginning of an indexing pipeline |
|
||||
| **Mandatory run variables** | "sources": Markdown file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects |
|
||||
| **Output variables** | "documents": A list of documents |
|
||||
| **API reference** | [Converters](/reference/converters-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/markdown.py |
|
||||
|
||||
## Overview
|
||||
|
||||
The `MarkdownToDocument` component converts Markdown files into documents. You can use it in an indexing pipeline to index the contents of a Markdown file into a Document Store. It takes a list of file paths or [ByteStream](../../concepts/data-classes.mdx#bytestream) objects as input and outputs the converted result as a list of documents. Optionally, you can attach metadata to the documents through the `meta` input parameter.
|
||||
|
||||
When you initialize the component, you can optionally turn off progress bars by setting `progress_bar` to `False`. If you want to convert the contents of tables into a single line, you can enable that through the `table_to_single_line` parameter.
|
||||
|
||||
## Usage
|
||||
|
||||
You need to install `markdown-it-py` and `mdit_plain packages` to use the `MarkdownToDocument` component:
|
||||
|
||||
```shell
|
||||
pip install markdown-it-py mdit_plain
|
||||
```
|
||||
|
||||
### On its own
|
||||
|
||||
```python
|
||||
from haystack.components.converters import MarkdownToDocument
|
||||
|
||||
converter = MarkdownToDocument()
|
||||
|
||||
docs = converter.run(sources=Path("my_file.md"))
|
||||
```
|
||||
|
||||
### In a pipeline
|
||||
|
||||
```python
|
||||
from haystack import Pipeline
|
||||
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
||||
from haystack.components.converters import MarkdownToDocument
|
||||
from haystack.components.preprocessors import DocumentCleaner
|
||||
from haystack.components.preprocessors import DocumentSplitter
|
||||
from haystack.components.writers import DocumentWriter
|
||||
|
||||
document_store = InMemoryDocumentStore()
|
||||
|
||||
pipeline = Pipeline()
|
||||
pipeline.add_component("converter", MarkdownToDocument())
|
||||
pipeline.add_component("cleaner", DocumentCleaner())
|
||||
pipeline.add_component(
|
||||
"splitter",
|
||||
DocumentSplitter(split_by="sentence", split_length=5),
|
||||
)
|
||||
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
|
||||
pipeline.connect("converter", "cleaner")
|
||||
pipeline.connect("cleaner", "splitter")
|
||||
pipeline.connect("splitter", "writer")
|
||||
|
||||
pipeline.run({"converter": {"sources": file_names}})
|
||||
```
|
||||
|
||||
## Additional References
|
||||
|
||||
:notebook: Tutorial: [Preprocessing Different File Types](https://haystack.deepset.ai/tutorials/30_file_type_preprocessing_index_pipeline)
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: "MSGToDocument"
|
||||
id: msgtodocument
|
||||
slug: "/msgtodocument"
|
||||
description: "Converts Microsoft Outlook .msg files to documents."
|
||||
---
|
||||
|
||||
# MSGToDocument
|
||||
|
||||
Converts Microsoft Outlook .msg files to documents.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) , or right at the beginning of an indexing pipeline |
|
||||
| **Mandatory run variables** | "sources": A list of .msg file paths or [ByteStream](../../concepts/data-classes.mdx#bytestream) objects |
|
||||
| **Output variables** | "documents": A list of documents <br /> <br />"attachments": A list of ByteStream objects representing file attachments |
|
||||
| **API reference** | [Converters](/reference/converters-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/msg.py |
|
||||
|
||||
## Overview
|
||||
|
||||
The `MSGToDocument` component converts Microsoft Outlook `.msg` files into documents. This component extracts the email metadata (such as sender, recipients, CC, BCC, subject) and body content. Additionally, any file attachments within the `.msg` file are extracted as `ByteStream` objects.
|
||||
|
||||
## Usage
|
||||
|
||||
First, install the `python-oxmsg` package to start using this converter:
|
||||
|
||||
```
|
||||
pip install python-oxmsg
|
||||
```
|
||||
|
||||
### On its own
|
||||
|
||||
```python
|
||||
from haystack.components.converters.msg import MSGToDocument
|
||||
from datetime import datetime
|
||||
|
||||
converter = MSGToDocument()
|
||||
results = converter.run(
|
||||
sources=["sample.msg"],
|
||||
meta={"date_added": datetime.now().isoformat()},
|
||||
)
|
||||
documents = results["documents"]
|
||||
attachments = results["attachments"]
|
||||
|
||||
print(documents[0].content)
|
||||
```
|
||||
|
||||
### In a pipeline
|
||||
|
||||
The following setup enables efficient extraction, preprocessing, and indexing of `.msg` email files within a Haystack pipeline:
|
||||
|
||||
```python
|
||||
from haystack import Pipeline
|
||||
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
||||
from haystack.components.routers import FileTypeRouter
|
||||
from haystack.components.converters import MSGToDocument
|
||||
from haystack.components.writers import DocumentWriter
|
||||
|
||||
router = FileTypeRouter(mime_types=["application/vnd.ms-outlook"])
|
||||
document_store = InMemoryDocumentStore()
|
||||
|
||||
pipeline = Pipeline()
|
||||
pipeline.add_component("router", router)
|
||||
pipeline.add_component("converter", MSGToDocument())
|
||||
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
|
||||
|
||||
pipeline.connect("router.application/vnd.ms-outlook", "converter.sources")
|
||||
pipeline.connect("converter.documents", "writer.documents")
|
||||
|
||||
file_names = ["email1.msg", "email2.msg"]
|
||||
pipeline.run({"converter": {"sources": file_names}})
|
||||
```
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
---
|
||||
title: "MultiFileConverter"
|
||||
id: multifileconverter
|
||||
slug: "/multifileconverter"
|
||||
description: "Converts CSV, DOCX, HTML, JSON, MD, PPTX, PDF, TXT, and XSLX files to documents."
|
||||
---
|
||||
|
||||
# MultiFileConverter
|
||||
|
||||
Converts CSV, DOCX, HTML, JSON, MD, PPTX, PDF, TXT, and XSLX files to documents.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Most common position in a pipeline** | Before PreProcessors , or right at the beginning of an indexing pipeline |
|
||||
| **Mandatory run variables** | "sources": A list of file paths or ByteStream objects |
|
||||
| **Output variables** | "documents": A list of converted documents <br /> <br />"unclassified": A list of uncategorized file paths or byte streams |
|
||||
| **API reference** | [Converters](/reference/converters-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/multi_file_converter.py |
|
||||
|
||||
## Overview
|
||||
|
||||
`MultiFileConverter` converts input files of various file types into documents.
|
||||
|
||||
It is a SuperComponent that combines a [`FileTypeRouter`](../routers/filetyperouter.mdx), nine converters and a [`DocumentJoiner`](../joiners/documentjoiner.mdx) into a single component.
|
||||
|
||||
### Parameters
|
||||
|
||||
To initialize `MultiFileConverter`, there are no mandatory parameters. Optionally, you can provide `encoding` and `json_content_key` parameters.
|
||||
|
||||
The `json_content_key` parameter lets you specify for the JSON files which key in the extracted data will be the document's content. The parameter is passed on to the underlying [`JSONConverter`](jsonconverter.mdx) component.
|
||||
|
||||
The `encoding` parameter lets you specify the default encoding of the TXT, CSV, and MD files. If you don't provide any value, the component uses `utf-8` by default. Note that if the encoding is specified in the metadata of an input ByteStream, it will override this parameter's setting. The parameter is passed on to the underlying [`TextFileToDocument`](textfiletodocument.mdx) and [`CSVToDocument`](csvtodocument.mdx) components.
|
||||
|
||||
## Usage
|
||||
|
||||
Install dependencies for all supported file types to use the `MultiFileConverter`:
|
||||
|
||||
```shell
|
||||
pip install pypdf markdown-it-py mdit_plain trafilatura python-pptx python-docx jq openpyxl tabulate pandas
|
||||
```
|
||||
|
||||
### On its own
|
||||
|
||||
```python
|
||||
from haystack.components.converters import MultiFileConverter
|
||||
|
||||
converter = MultiFileConverter()
|
||||
converter.run(sources=["test.txt", "test.pdf"], meta={})
|
||||
```
|
||||
|
||||
### In a pipeline
|
||||
|
||||
You can also use `MultiFileConverter` in your indexing pipeline.
|
||||
|
||||
```python
|
||||
from haystack import Pipeline
|
||||
from haystack.components.converters import MultiFileConverter
|
||||
from haystack.components.preprocessors import DocumentPreprocessor
|
||||
from haystack.components.writers import DocumentWriter
|
||||
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
||||
|
||||
document_store = InMemoryDocumentStore()
|
||||
|
||||
pipeline = Pipeline()
|
||||
pipeline.add_component("converter", MultiFileConverter())
|
||||
pipeline.add_component("preprocessor", DocumentPreprocessor())
|
||||
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
|
||||
pipeline.connect("converter", "preprocessor")
|
||||
pipeline.connect("preprocessor", "writer")
|
||||
|
||||
result = pipeline.run(data={"sources": ["test.txt", "test.pdf"]})
|
||||
|
||||
print(result)
|
||||
## {'writer': {'documents_written': 3}}
|
||||
```
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
---
|
||||
title: "OpenAPIServiceToFunctions"
|
||||
id: openapiservicetofunctions
|
||||
slug: "/openapiservicetofunctions"
|
||||
description: "`OpenAPIServiceToFunctions` is a component that transforms OpenAPI service specifications into a format compatible with OpenAI's function calling mechanism."
|
||||
---
|
||||
|
||||
# OpenAPIServiceToFunctions
|
||||
|
||||
`OpenAPIServiceToFunctions` is a component that transforms OpenAPI service specifications into a format compatible with OpenAI's function calling mechanism.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Most common position in a pipeline** | Flexible |
|
||||
| **Mandatory run variables** | “sources”: A list of OpenAPI specification sources, which can be file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects |
|
||||
| **Output variables** | “functions”: A list of JSON OpenAI function calling definitions objects. For each path definition in OpenAPI specification, a corresponding OpenAI function calling definitions is generated. <br /> <br />“openapi_specs”: A list of JSON/YAML objects with references resolved. Such OpenAPI spec (with references resolved) can, in turn, be used as input to OpenAPIServiceConnector. |
|
||||
| **API reference** | [Converters](/reference/converters-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/openapi_functions.py |
|
||||
|
||||
## Overview
|
||||
|
||||
`OpenAPIServiceToFunctions` transforms OpenAPI service specifications into an OpenAI function calling format. It takes an OpenAPI specification, processes it to extract function definitions, and formats these definitions to be compatible with OpenAI's function calling JSON format.
|
||||
|
||||
`OpenAPIServiceToFunctions` is valuable when used together with [`OpenAPIServiceConnector`](../connectors/openapiserviceconnector.mdx) component. It converts OpenAPI specifications into definitions suitable for OpenAI's function calls, allowing `OpenAPIServiceConnector` to handle input parameters for the OpenAPI specification and facilitate their use in REST API calls through `OpenAPIServiceConnector`.
|
||||
|
||||
To use `OpenAPIServiceToFunctions`, you need to install an optional `jsonref` dependency with:
|
||||
|
||||
```shell
|
||||
pip install jsonref
|
||||
```
|
||||
|
||||
`OpenAPIServiceToFunctions` component doesn’t have any init parameters.
|
||||
|
||||
## Usage
|
||||
|
||||
### On its own
|
||||
|
||||
This component is primarily meant to be used in pipelines. Using this component alone is useful when you want to convert OpenAPI specification into OpenAI's function call specification and then perhaps save it in a file and subsequently use it in function calling.
|
||||
|
||||
### In a pipeline
|
||||
|
||||
In a pipeline context, `OpenAPIServiceToFunctions` is most valuable when used alongside `OpenAPIServiceConnector`. For instance, let’s consider integrating [serper.dev](http://serper.dev/) search engine bridge into a pipeline. `OpenAPIServiceToFunctions` retrieves the OpenAPI specification of Serper from https://bit.ly/serper_dev_spec, converts this specification into a format that OpenAI's function calling mechanism can understand, and then seamlessly passes this translated specification as `generation_kwargs` for LLM function calling invocation.
|
||||
|
||||
:::note
|
||||
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 Dict, Any, List
|
||||
from haystack import Pipeline
|
||||
from haystack.components.generators.utils import print_streaming_chunk
|
||||
from haystack.components.converters import OpenAPIServiceToFunctions, OutputAdapter
|
||||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||||
from haystack.components.connectors import OpenAPIServiceConnector
|
||||
from haystack.components.fetchers import LinkContentFetcher
|
||||
from haystack.dataclasses import ChatMessage, ByteStream
|
||||
from haystack.utils import Secret
|
||||
|
||||
|
||||
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"]},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
system_prompt = requests.get("https://bit.ly/serper_dev_system_prompt").text
|
||||
serper_spec = requests.get("https://bit.ly/serper_dev_spec").text
|
||||
|
||||
pipe = Pipeline()
|
||||
pipe.add_component("spec_to_functions", OpenAPIServiceToFunctions())
|
||||
pipe.add_component(
|
||||
"functions_llm",
|
||||
OpenAIChatGenerator(
|
||||
api_key=Secret.from_token(llm_api_key),
|
||||
model="gpt-3.5-turbo-0613",
|
||||
),
|
||||
)
|
||||
pipe.add_component("openapi_container", OpenAPIServiceConnector())
|
||||
pipe.add_component(
|
||||
"a1",
|
||||
OutputAdapter(
|
||||
"{{functions[0] | prepare_fc}}",
|
||||
Dict[str, Any],
|
||||
{"prepare_fc": prepare_fc_params},
|
||||
),
|
||||
)
|
||||
pipe.add_component("a2", OutputAdapter("{{specs[0]}}", Dict[str, Any]))
|
||||
pipe.add_component(
|
||||
"a3",
|
||||
OutputAdapter("{{system_message + service_response}}", List[ChatMessage]),
|
||||
)
|
||||
pipe.add_component(
|
||||
"llm",
|
||||
OpenAIChatGenerator(
|
||||
api_key=Secret.from_token(llm_api_key),
|
||||
model="gpt-4-1106-preview",
|
||||
streaming_callback=print_streaming_chunk,
|
||||
),
|
||||
)
|
||||
|
||||
pipe.connect("spec_to_functions.functions", "a1.functions")
|
||||
pipe.connect("spec_to_functions.openapi_specs", "a2.specs")
|
||||
pipe.connect("a1", "functions_llm.generation_kwargs")
|
||||
pipe.connect("functions_llm.replies", "openapi_container.messages")
|
||||
pipe.connect("a2", "openapi_container.service_openapi_spec")
|
||||
pipe.connect("openapi_container.service_response", "a3.service_response")
|
||||
pipe.connect("a3", "llm.messages")
|
||||
|
||||
user_prompt = "Why was Sam Altman ousted from OpenAI?"
|
||||
|
||||
result = pipe.run(
|
||||
data={
|
||||
"functions_llm": {
|
||||
"messages": [
|
||||
ChatMessage.from_system("Only do function calling"),
|
||||
ChatMessage.from_user(user_prompt),
|
||||
],
|
||||
},
|
||||
"openapi_container": {"service_credentials": serper_dev_key},
|
||||
"spec_to_functions": {"sources": [ByteStream.from_string(serper_spec)]},
|
||||
"a3": {"system_message": [ChatMessage.from_system(system_prompt)]},
|
||||
},
|
||||
)
|
||||
```
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
---
|
||||
title: "OutputAdapter"
|
||||
id: outputadapter
|
||||
slug: "/outputadapter"
|
||||
description: "This component helps the output of one component fit smoothly into the input of another. It uses Jinja expressions to define how this adaptation occurs."
|
||||
---
|
||||
|
||||
# OutputAdapter
|
||||
|
||||
This component helps the output of one component fit smoothly into the input of another. It uses Jinja expressions to define how this adaptation occurs.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Most common position in a pipeline** | Flexible |
|
||||
| **Mandatory init variables** | "template": A Jinja template string that defines how to adapt the data <br /> <br />"output_type": Type alias that this instance will return |
|
||||
| **Mandatory run variables** | “\*\*kwargs”: Input variables to be used in Jinja expression. See [Variables](#variables) section for more details. |
|
||||
| **Output variables** | The output is specified under the “output” key dictionary |
|
||||
| **API reference** | [Converters](/reference/converters-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/output_adapter.py |
|
||||
|
||||
## Overview
|
||||
|
||||
To use `OutputAdapter`, you need to specify the adaptation rule that includes:
|
||||
|
||||
- `template`: A Jinja template string that defines how to adapt the input data.
|
||||
- `output_type`: The type of the output data (such as `str`, `List[int]`..). This doesn't change the actual output type and is only needed to validate connection with other components.
|
||||
- `custom_filters`: An optional dictionary of custom Jinja filters to be used in the template.
|
||||
|
||||
### Variables
|
||||
|
||||
The `OutputAdapter` requires all template variables to be present before running and raises an error if any template variable is missing at pipeline connect time.
|
||||
|
||||
```python
|
||||
from haystack.components.converters import OutputAdapter
|
||||
|
||||
adapter = OutputAdapter(template="Hello {{name}}!", output_type=str)
|
||||
```
|
||||
|
||||
### Unsafe behavior
|
||||
|
||||
The `OutputAdapter` internally renders the `template` using Jinja, and by default, this is safe behavior. However, it limits the output types to strings, bytes, numbers, tuples, lists, dicts, sets, booleans, `None`, and `Ellipsis` (`...`), as well as any combination of these structures.
|
||||
|
||||
If you want to use other types such as `ChatMessage`, `Document`, or `Answer`, you must enable unsafe template rendering by setting the `unsafe` init argument to `True`.
|
||||
|
||||
Be cautious, as enabling this can be unsafe and may lead to remote code execution if the `template` is a string customizable by the end user.
|
||||
|
||||
## Usage
|
||||
|
||||
### On its own
|
||||
|
||||
This component is primarily meant to be used in pipelines.
|
||||
|
||||
In this example, `OutputAdapter` simply outputs the content field of the first document in the arrays of documents:
|
||||
|
||||
```python
|
||||
from haystack import Document
|
||||
from haystack.components.converters import OutputAdapter
|
||||
|
||||
adapter = OutputAdapter(template="{{ documents[0].content }}", output_type=str)
|
||||
input_data = {"documents": [Document(content="Test content")]}
|
||||
expected_output = {"output": "Test content"}
|
||||
assert adapter.run(**input_data) == expected_output
|
||||
```
|
||||
|
||||
### In a pipeline
|
||||
|
||||
The example below demonstrates a straightforward pipeline that uses the `OutputAdapter` to capitalize the first document in the list. If needed, you can also utilize the predefined Jinja [filters](https://jinja.palletsprojects.com/en/3.1.x/templates/#builtin-filters).
|
||||
|
||||
```python
|
||||
from haystack import Pipeline, component, Document
|
||||
from haystack.components.converters import OutputAdapter
|
||||
|
||||
|
||||
@component
|
||||
class DocumentProducer:
|
||||
@component.output_types(documents=dict)
|
||||
def run(self):
|
||||
return {"documents": [Document(content="haystack")]}
|
||||
|
||||
|
||||
pipe = Pipeline()
|
||||
pipe.add_component(
|
||||
name="output_adapter",
|
||||
instance=OutputAdapter(
|
||||
template="{{ documents[0].content | capitalize}}",
|
||||
output_type=str,
|
||||
),
|
||||
)
|
||||
pipe.add_component(name="document_producer", instance=DocumentProducer())
|
||||
pipe.connect("document_producer", "output_adapter")
|
||||
result = pipe.run(data={})
|
||||
|
||||
assert result["output_adapter"]["output"] == "Haystack"
|
||||
```
|
||||
|
||||
You can also define your own custom filters, which can then be added to an `OutputAdapter` instance through its init method and used in templates. Here’s an example of this approach:
|
||||
|
||||
```python
|
||||
|
||||
from haystack import Pipeline, component, Document
|
||||
from haystack.components.converters import OutputAdapter
|
||||
|
||||
|
||||
def reverse_string(s):
|
||||
return s[::-1]
|
||||
|
||||
|
||||
@component
|
||||
class DocumentProducer:
|
||||
@component.output_types(documents=dict)
|
||||
def run(self):
|
||||
return {"documents": [Document(content="haystack")]}
|
||||
|
||||
|
||||
pipe = Pipeline()
|
||||
pipe.add_component(
|
||||
name="output_adapter",
|
||||
instance=OutputAdapter(
|
||||
template="{{ documents[0].content | reverse_string}}",
|
||||
output_type=str,
|
||||
custom_filters={"reverse_string": reverse_string},
|
||||
),
|
||||
)
|
||||
|
||||
pipe.add_component(name="document_producer", instance=DocumentProducer())
|
||||
pipe.connect("document_producer", "output_adapter")
|
||||
result = pipe.run(data={})
|
||||
|
||||
assert result["output_adapter"]["output"] == "kcatsyah"
|
||||
```
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: "PDFMinerToDocument"
|
||||
id: pdfminertodocument
|
||||
slug: "/pdfminertodocument"
|
||||
description: "A component that converts complex PDF files to documents using pdfminer arguments."
|
||||
---
|
||||
|
||||
# PDFMinerToDocument
|
||||
|
||||
A component that converts complex PDF files to documents using pdfminer arguments.
|
||||
|
||||
| | |
|
||||
| :------------------------------------- | :--------------------------------------------------------------------------------------------- |
|
||||
| **Most common position in a pipeline** | Before [PreProcessors](../preprocessors.mdx) or right at the beginning of an indexing pipeline |
|
||||
| **Mandatory run variables** | "sources": PDF file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream) objects |
|
||||
| **Output variables** | "documents": A list of documents |
|
||||
| **API reference** | [Converters](/reference/converters-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/pdfminer.py |
|
||||
|
||||
## Overview
|
||||
|
||||
The `PDFMinerToDocument` component converts PDF files into documents using [PDFMiner](https://pdfminersix.readthedocs.io/en/latest/) extraction tool arguments.
|
||||
|
||||
You can use it in an indexing pipeline to index the contents of a PDF file in a Document Store. It takes a list of file paths or [`ByteStream`](../../concepts/data-classes.mdx#bytestream)objects as input and outputs the converted result as a list of documents. Optionally, you can attach metadata to the documents through the `meta` input parameter.
|
||||
|
||||
When initializing the component, you can adjust several parameters to fit your PDF. See the full parameter list and descriptions in our [API reference](/reference/converters-api#pdfminertodocument).
|
||||
|
||||
## Usage
|
||||
|
||||
First, install `pdfminer` package to start using this converter:
|
||||
|
||||
```shell
|
||||
pip install pdfminer.six
|
||||
```
|
||||
|
||||
### On its own
|
||||
|
||||
```python
|
||||
from haystack.components.converters import PDFMinerToDocument
|
||||
|
||||
converter = PDFMinerToDocument()
|
||||
results = converter.run(
|
||||
sources=["sample.pdf"],
|
||||
meta={"date_added": datetime.now().isoformat()},
|
||||
)
|
||||
documents = results["documents"]
|
||||
|
||||
print(documents[0].content)
|
||||
|
||||
## 'This is a text from the PDF file.'
|
||||
```
|
||||
|
||||
### In a pipeline
|
||||
|
||||
```python
|
||||
from haystack import Pipeline
|
||||
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
||||
from haystack.components.converters import PDFMinerToDocument
|
||||
from haystack.components.preprocessors import DocumentCleaner
|
||||
from haystack.components.preprocessors import DocumentSplitter
|
||||
from haystack.components.writers import DocumentWriter
|
||||
|
||||
document_store = InMemoryDocumentStore()
|
||||
|
||||
pipeline = Pipeline()
|
||||
pipeline.add_component("converter", PDFMinerToDocument())
|
||||
pipeline.add_component("cleaner", DocumentCleaner())
|
||||
pipeline.add_component(
|
||||
"splitter",
|
||||
DocumentSplitter(split_by="sentence", split_length=5),
|
||||
)
|
||||
pipeline.add_component("writer", DocumentWriter(document_store=document_store))
|
||||
pipeline.connect("converter", "cleaner")
|
||||
pipeline.connect("cleaner", "splitter")
|
||||
pipeline.connect("splitter", "writer")
|
||||
|
||||
pipeline.run({"converter": {"sources": file_names}})
|
||||
```
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
---
|
||||
title: "PDFToImageContent"
|
||||
id: pdftoimagecontent
|
||||
slug: "/pdftoimagecontent"
|
||||
description: "`PDFToImageContent` reads local PDF files and converts them into `ImageContent` objects. These are ready for multimodal AI pipelines, including tasks like image captioning, visual QA, or prompt-based generation."
|
||||
---
|
||||
|
||||
# PDFToImageContent
|
||||
|
||||
`PDFToImageContent` reads local PDF files and converts them into `ImageContent` objects. These are ready for multimodal AI pipelines, including tasks like image captioning, visual QA, or prompt-based generation.
|
||||
|
||||
| | |
|
||||
| -------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| **Most common position in a pipeline** | Before a `ChatPromptBuilder` in a query pipeline |
|
||||
| **Mandatory run variables** | "sources": A list of PDF file paths or ByteStreams |
|
||||
| **Output variables** | "image_contents": A list of ImageContent objects |
|
||||
| **API reference** | [Image Converters](/reference/image-converters-api) |
|
||||
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/converters/image/pdf_to_image.py |
|
||||
|
||||
## Overview
|
||||
|
||||
`PDFToImageContent` processes a list of PDF sources and converts them into `ImageContent` objects, one for each page of the PDF. These can be used in multimodal pipelines that require base64-encoded image input.
|
||||
|
||||
Each source can be:
|
||||
|
||||
- A file path (string or `Path`), or
|
||||
- A `ByteStream` object.
|
||||
|
||||
Optionally, you can provide metadata using the `meta` parameter. This can be a single dictionary (applied to all images) or a list matching the length of `sources`.
|
||||
|
||||
Use the `size` parameter to resize images while preserving aspect ratio. This reduces memory usage and transmission size, which is helpful when working with remote models or limited-resource environments.
|
||||
|
||||
This component is often used in query pipelines just before a `ChatPromptBuilder`.
|
||||
|
||||
## Usage
|
||||
|
||||
### On its own
|
||||
|
||||
```python
|
||||
from haystack.components.converters.image import PDFToImageContent
|
||||
|
||||
converter = PDFToImageContent()
|
||||
|
||||
sources = ["file.pdf", "another_file.pdf"]
|
||||
|
||||
image_contents = converter.run(sources=sources)["image_contents"]
|
||||
print(image_contents)
|
||||
|
||||
## [ImageContent(base64_image='...',
|
||||
## mime_type='application/pdf',
|
||||
## detail=None,
|
||||
## meta={'file_path': 'file.pdf', 'page_number': 1}),
|
||||
## ...]
|
||||
```
|
||||
|
||||
### In a pipeline
|
||||
|
||||
Use `ImageFileToImageContent` to supply image data to a `ChatPromptBuilder` for multimodal QA or captioning with an LLM.
|
||||
|
||||
```python
|
||||
from haystack import Pipeline
|
||||
from haystack.components.builders import ChatPromptBuilder
|
||||
from haystack.components.generators.chat import OpenAIChatGenerator
|
||||
from haystack.components.converters.image import PDFToImageContent
|
||||
|
||||
## Query pipeline
|
||||
pipeline = Pipeline()
|
||||
pipeline.add_component("image_converter", PDFToImageContent(detail="auto"))
|
||||
pipeline.add_component(
|
||||
"chat_prompt_builder",
|
||||
ChatPromptBuilder(
|
||||
required_variables=["question"],
|
||||
template="""{% message role="system" %}
|
||||
You are a helpful assistant that answers questions using the provided images.
|
||||
{% endmessage %}
|
||||
|
||||
{% message role="user" %}
|
||||
Question: {{ question }}
|
||||
|
||||
{% for img in image_contents %}
|
||||
{{ img | templatize_part }}
|
||||
{% endfor %}
|
||||
{% endmessage %}
|
||||
""",
|
||||
),
|
||||
)
|
||||
pipeline.add_component("llm", OpenAIChatGenerator(model="gpt-4o-mini"))
|
||||
|
||||
pipeline.connect("image_converter", "chat_prompt_builder.image_contents")
|
||||
pipeline.connect("chat_prompt_builder", "llm")
|
||||
|
||||
sources = ["flan_paper.pdf"]
|
||||
|
||||
result = pipeline.run(
|
||||
data={
|
||||
"image_converter": {"sources": ["flan_paper.pdf"], "page_range": "9"},
|
||||
"chat_prompt_builder": {"question": "What is the main takeaway of Figure 6?"},
|
||||
},
|
||||
)
|
||||
print(result["replies"][0].text)
|
||||
|
||||
## ('The main takeaway of Figure 6 is that Flan-PaLM demonstrates improved '
|
||||
## 'performance in zero-shot reasoning tasks when utilizing chain-of-thought '
|
||||
## '(CoT) reasoning, as indicated by higher accuracy across different model '
|
||||
## 'sizes compared to PaLM without finetuning. This highlights the importance of '
|
||||
## 'instruction finetuning combined with CoT for enhancing reasoning '
|
||||
## 'capabilities in models.')
|
||||
```
|
||||
|
||||
## Additional References
|
||||
|
||||
🧑🍳 Cookbook: [Introduction to Multimodality](https://haystack.deepset.ai/cookbook/multimodal_intro)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user