Releases: deepset-ai/haystack
v2.25.2
v2.25.2-rc1
v2.25.2-rc1
v2.25.1
Release Notes
v2.25.1
⚡️ Enhancement Notes
- Auto variadic sockets now also support
Optional[list[...]]input types, in addition to plainlist[...].
🐛 Bug Fixes
- Fixed smart connection logic to support connecting multiple outputs to a socket whose type is
Optional[list[...]](e.g.list[ChatMessage] | None). Previously, connecting twolist[ChatMessage]outputs toAgent.messageswould fail after its type was updated fromlist[ChatMessage]tolist[ChatMessage] | None.
v2.25.1-rc1
v2.25.1-rc1
v2.25.0
⭐️ Highlights
🛠️ Dynamic Tool Discovery with SearchableToolset
For applications with large tool catalogs, we’ve added the SearchableToolset. Instead of exposing all tools upfront, agents start with a single search_tools function and dynamically discover relevant tools using BM25-based keyword search.
This is particularly useful when connecting MCP servers via MCPToolset, where many tools may be available. By combining the two, agents can load only the tools they actually need at runtime, reducing context usage and improving tool selection.
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.tools import Tool, SearchableToolset
# Create a catalog of tools
catalog = [
Tool(name="get_weather", description="Get weather for a city", ...),
Tool(name="search_web", description="Search the web", ...),
# ... 100s more tools
]
toolset = SearchableToolset(catalog=catalog)
agent = Agent(chat_generator=OpenAIChatGenerator(), tools=toolset)
# The agent is initially provided only with the search_tools tool and will use it to find relevant tools.
result = agent.run(messages=[ChatMessage.from_user("What's the weather in Milan?")])📝 Reusable Prompt Templates for Agents
Agents now natively support Jinja2-templated user prompts. By defining a user_prompt and required_variables during initialization or at runtime, you can easily invoke the Agent with dynamic variables without having to manually build ChatMessage objects for every invocation. Plus, you can seamlessly append rendered prompts directly to prior conversation messages.
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
agent = Agent(
chat_generator=OpenAIChatGenerator(),
tools=tools,
system_prompt="You are a helpful translation assistant.",
user_prompt="""{% message role="user"%}
Now summarize the conversation in {{ language }}.
{% endmessage %}""",
required_variables=["language"],
)
result = agent.run(
messages=[
ChatMessage.from_user("What are the main benefits of renewable energy?"),
ChatMessage.from_assistant("Renewable energy reduces greenhouse gas emissions, decreases dependence on fossil fuels, and can lower long-term energy costs."),
],
language="Spanish",
)⬆️ Upgrade Notes
-
Removed the deprecated
PipelineTemplateandPredefinedPipelineclasses, along with thePipeline.from_template()method. Users should migrate to Pipeline YAML files for similar functionality. See the [Serialization documentation](https://docs.haystack.deepset.ai/docs/serialization) for details on using YAML-based pipeline definitions. -
Default Hugging Face pipeline task updated to ``text-generation``
The default task used by
HuggingFaceLocalGeneratorhas been changed fromtext2text-generationtotext-generationand the default model has been changed from "google/flan-t5-base" to "Qwen/Qwen3-0.6B".In
transformersv5+,text2text-generationis no longer available as a valid pipeline task (see: huggingface/transformers#43256). While parts of the implementation still exist internally, it is no longer supported as a straightforward pipeline option.How to know if you are affected
- You are using
transformers>=5.0.0. - You explicitly set
task="text2text-generation"inHuggingFaceLocalGeneratororHuggingFaceLocalChatGenerator.
How to handle this change
- Replace
task="text2text-generation"withtask="text-generation". - Ensure that the selected model is compatible with the
text-generationpipeline (for example, causal language models). - If you rely on older behavior, pin
transformers<5. text2text-generationis now considered deprecated in Haystack and may be removed in a future release.
- You are using
🚀 New Features
-
Added
link_formatparameter toPPTXToDocumentandXLSXToDocumentconverters, allowing extraction of hyperlink addresses from PPTX and XLSX files.Supported formats:
"markdown":[text](url)"plain":text (url)"none"(default): Only text is extracted, link addresses are ignored.
This follows the same pattern already available in
DOCXToDocument. -
Added a new
LLMcomponent (haystack.components.generators.chat.LLM) that provides a simplified interface for text generation powered by a large language model. TheLLMcomponent is a streamlined version of theAgentthat focuses solely on single-turn text generation without tool usage. It supports system prompts, templated user prompts with required variables, streaming callbacks, and both synchronous (run) and asynchronous (run_async) execution.Usage example:
from haystack.components.generators.chat import LLM from haystack.components.generators.chat import OpenAIChatGenerator from haystack.dataclasses import ChatMessage llm = LLM( chat_generator=OpenAIChatGenerator(), system_prompt="You are a helpful translation assistant.", user_prompt="""{% message role="user"%} Summarize the following document: {{ document }} {% endmessage %}""", required_variables=["document"], ) result = llm.run(document="The weather is lovely today and the sun is shining. ") print(result["last_message"].text)
-
Added
SearchableToolsettohaystack.toolsmodule. This new toolset enables agents to dynamically discover tools from large catalogs using keyword-based (BM25) search. Instead of exposing all tools upfront (which can overwhelm LLMs with large tool definitions), agents start with a singlesearch_toolsfunction and progressively discover relevant tools as needed. For smaller catalogs, it operates in passthrough mode exposing all tools directly.Key features include configurable search threshold for automatic passthrough mode and top-k result limiting.
-
Added
user_promptandrequired_variablesparameters to theAgentcomponent. You can now define a reusable Jinja2-templated user prompt at initialization or at runtime, so the Agent can be invoked with different inputs without manually constructingChatMessageobjects each time.from haystack.components.agents import Agent from haystack.components.generators.chat import OpenAIChatGenerator agent = Agent( chat_generator=OpenAIChatGenerator(), tools=tools, system_prompt="You are a helpful translation assistant.", user_prompt="""{% message role="user"%} Translate the following document to {{ language }}: {{ document }} {% endmessage %}""", required_variables=["language", "document"], ) result = agent.run(language="French", document="The weather is lovely today.")
When you combine
messageswithuser_prompt, the rendered user prompt is appended to the provided messages. This is useful for passing prior conversation context alongside a new templated query. -
Added the
FileToFileContentcomponent, which converts local files intoFileContentobjects. These can be embedded intoChatMessageto pass to an LLM. -
Added
document_comparison_fieldparameter toDocumentMRREvaluator,DocumentMAPEvaluator, andDocumentRecallEvaluator.This allows users to compare documents using fields other than
content, such asidor metadata keys (viameta.<key>syntax).Previously, all three evaluators hardcoded
doc.contentfor comparison, which did not work well when documents were chunked or when ground truth was identified by custom metadata fields.
⚡️Enhancement Notes
-
Added support for
transformersv5. Haystack remains fully compatible withtransformersv4, but upgrading to v5 unlocks several benefits, including faster model loading times, improved model quantization support, faster inference for selected models, and other underlying improvements. Read more in the transformers v5 release blog post. -
The
LLMDocumentContentExtractornow extracts both content and metadata from image-based documents. When the LLM returns JSON,document_contentfills the document body and other keys are merged into metadata; plain text is still used as content. The fieldcontent_extraction_erroris no longer used and when an error occurs the fieldextraction_erroris added to metadata with the error message. -
Improved the deserialization error message for pipeline components to be more actionable and human-readable. The component data dictionary is now pretty-printed as formatted JSON, and the underlying error that caused the failure is explicitly surfaced, making it easier to quickly diagnose deserialization issues.
-
EmbeddingBasedDocumentSplitterandMultiQueryEmbeddingRetrievernow automatically invokewarm_up()whenrun()is called if they have not been warmed up yet. -
Improved
ComponentToolto correctly handle components whoserunmethod parameters are declared as top-levelOptionaltypes such aslist[ChatMessage] | None. The optional wrapper is now unwrapped before checking for afrom_dictmethod on the underlying type. As a result, when a parameter is typed aslist[ChatMessage] | Noneand receives a list of dictionaries,ComponentToolwill automatically coerce the input into a list ofChatMessageobjects usingChatMessage.from_dict. If the provided value isNone, the parameter is preserved asNone. -
Haystack now emits a
Warningwhen dataclass instances (e.g.Document,ChatMessage,...
v2.25.0-rc1
v2.25.0-rc1
v2.24.1
🐛 Bug Fixes
- Fixed a bug in flexible Pipeline connections that prevented automatic value conversion when the receiving component expects a Union type. For example, connecting a component returning
ChatMessageto a receiver expectinglist[str] | list[ChatMessage]should have worked but did not. The conversion strategy now correctly evaluates each branch of a Union receiver and picks the best match.
v2.24.1-rc1
v2.24.1-rc1
v2.24.0
⭐️ Highlights
🔌 Pipelines got simpler
With the updated logic, Pipelines can now:
- connect multiple
list[T]component outputs directly to a singlelist[T]input of the next component, simplifying pipeline definitions when multiple components produce compatible outputs. E.g., you can directly connect multiple converters to a writer component without aDocumentJoinercomponent in ingestion pipelines. - support the automatic conversion between
ChatMessageandstrtypes, enabling simpler connections between various components. E.g., you can easily connect an Agent component (which returnsChatMessageaslast_message) to a text embedder component (which expects astras query) without anOutputAdaptercomponent. - automatically convert
list[ChatMessage]toChatMessageandlist[str]tostrby taking the first element of the list. Another supported conversion islist[ChatMessage]tostr, enabling the connection between a chat generator (which returnslist[ChatMessage]asmessages) and a BM25 retriever (which expects astras query). - perform list wrapping: a component returning type
Tcan be connected to a component expecting typelist[T].
Together, these changes eliminate the need for OutputAdapter and some joiners (ListJoiner, DocumentJoiner) in many common setups such as query rewriting and hybrid search.
🏁 Rankers handles duplicate documents
Rankers now deduplicate documents by id before ranking, preventing the same document from being scored multiple times in hybrid retrieval setups and eliminating the need for a DocumentJoiner after recent pipeline updates.
MultiQueryEmbeddingRetriever and MultiQueryTextRetriever now also deduplicate by id (not by content). Documents are only considered duplicates if they share the same id.
📃 Extended PDF support in Chat Generators
You can now include files (e.g. PDFs) in ChatMessage objects using the new FileContent dataclass when working with OpenAI and Azure chat generators, including Responses API. Support for more providers is coming soon (see #10474)
from haystack.components.generators.chat.openai import OpenAIChatGenerator
from haystack.dataclasses.chat_message import ChatMessage
from haystack.dataclasses.file_content import FileContent
file_content = FileContent.from_url("https://arxiv.org/pdf/2309.08632")
chat_message = ChatMessage.from_user(content_parts=[file_content, "Summarize this paper in 100 words."])
llm = OpenAIChatGenerator(model="gpt-4.1-mini")
response = llm.run(messages=[chat_message])⬆️ Upgrade Notes
-
Deduplication in Rankers is a breaking change for users who rely on keeping duplicate documents with the same user-defined
idin the ranking output. This change only affects users with custom document ids who want duplicates preserved. To keep the previous behavior, ensure that your user-defined document ids are unique across retriever outputs.Affected Rankers:
HuggingFaceTEIRanker,LostInTheMiddleRanker,MetaFieldRanker,MetaFieldGroupingRanker,SentenceTransformersDiversityRanker,SentenceTransformersSimilarityRanker,TransformersSimilarityRanker. -
Deduplication behavior in
MultiQueryEmbeddingRetrieverandMultiQueryTextRetrieverhas changed and may be breaking for users who relied on deduplication based on document content rather than document id.Documents are now considered duplicates only if they share the same
id. Document ids can be user-defined or are automatically generated as a hash of the document’s attributes (e.g. content, metadata, etc.).This change affects setups where multiple documents have identical content but different ids (for example, due to differing metadata). To preserve the previous behavior, ensure that documents with identical content are assigned the same
idacross retriever outputs. -
Removed the deprecated
deserialize_document_store_in_init_params_inplacefunction. This function was deprecated in Haystack 2.23.0 and is no longer used.
🚀 New Features
-
Pipelines now natively support connecting multiple outputs directly to a single component input without requiring an explicit Joiner component. This only works when the connected outputs and inputs are of compatible list types, such as
list[Document].This simplifies pipeline definitions when multiple components produce compatible outputs. For example, multiple outputs from a
FileTypeRoutercan now be connected directly to a single converter or writer, without defining an intermediateListJoinerorDocumentJoiner.from haystack import Pipeline from haystack.components.converters import HTMLToDocument, TextFileToDocument from haystack.components.routers import FileTypeRouter from haystack.components.writers import DocumentWriter from haystack.dataclasses import ByteStream from haystack.document_stores.in_memory import InMemoryDocumentStore sources = [ ByteStream.from_string(text="Text file content", mime_type="text/plain", meta={"file_type": "txt"}), ByteStream.from_string( text="\n<html><body>Some content</body></html>\n", mime_type="text/html", meta={"file_type": "html"}, ), ] doc_store = InMemoryDocumentStore() pipe = Pipeline() pipe.add_component("router", FileTypeRouter(mime_types=["text/plain", "text/html"])) pipe.add_component("txt_converter", TextFileToDocument()) pipe.add_component("html_converter", HTMLToDocument()) pipe.add_component("writer", DocumentWriter(doc_store)) pipe.connect("router.text/plain", "txt_converter.sources") pipe.connect("router.text/html", "html_converter.sources") # The DocumentWriter accepts documents from both converters without needing a DocumentJoiner pipe.connect("txt_converter.documents", "writer.documents") pipe.connect("html_converter.documents", "writer.documents") result = pipe.run({"router": {"sources": sources}}) # result["writer"]["documents_written"] == 2
-
Pipelines now support connection and automatic conversion between
ChatMessageandstrtypes.- When a
stroutput is connected to aChatMessageinput, it is automatically converted to a userChatMessage.- When a
ChatMessageoutput is connected to astrinput, itstextattribute is automatically extracted. IftextisNone, an informativePipelineRuntimeErroris raised. - To maintain backward compatibility, when multiple connections are available, strict type matching is prioritized over conversion.
- When a
-
Pipelines now support list wrapping: a component returning type T can be connected to a component expecting type list[T]. The output will be wrapped automatically.
In addition, Pipelines support automatic conversion between list[T] and T, for str and ChatMessage types only. When converting from list[T] to T, the first element of the list is used. If the list is empty, an informative
PipelineRuntimeErroris raised.With other recent changes, this makes pipelines more flexible and removes the need for explicit adapter components in many cases. For example, the following pipeline automatically converts a
list[ChatMessage]produced by the LLM into astrexpected by the retriever, which previously required an Output Adapter component.from haystack.document_stores.in_memory import InMemoryDocumentStore from haystack.dataclasses import Document from haystack.components.retrievers import InMemoryBM25Retriever from haystack import Pipeline from haystack.components.builders import ChatPromptBuilder from haystack.components.generators.chat import OpenAIChatGenerator document_store = InMemoryDocumentStore() documents = [ Document(content="Bob lives in Paris."), Document(content="Alice lives in London."), Document(content="Ivy lives in Melbourne."), Document(content="Kate lives in Brisbane."), Document(content="Liam lives in Adelaide."), ] document_store.write_documents(documents) template ="""{% message role="user" %} Rewrite the following query to be used for keyword search. {{ query }} {% endmessage %} """ p = Pipeline() p.add_component("prompt_builder", ChatPromptBuilder(template=template)) p.add_component("llm", OpenAIChatGenerator(model="gpt-4.1-mini")) p.add_component("retriever", InMemoryBM25Retriever(document_store=document_store, top_k=3)) p.connect("prompt_builder", "llm") p.connect("llm", "retriever") query = """Someday I'd love to visit Brisbane, but for now I just want to know the names of the people who live there.""" results = p.run(data={"prompt_builder": {"query": query}})
-
Introduced the
MarkdownHeaderSplittercomponent:- Splits documents into chunks at Markdown headers (
#,##, etc.), preserving header hierarchy as metadata. - Supports secondary splitting (by word, passage, period, or line) for further chunking after header-based splitting using Haystack's
DocumentSplitter. - Preserves and propagates metadata such as parent headers and page numbers.
- Handles edge cases such as documents with no headers, empty content, and non-text documents.
- Splits documents into chunks at Markdown headers (
-
Added support for Chat Messages that include files using the
FileContentdataclass toOpenAIResponsesChatGeneratorandAzureOpenAIResponsesChatGenerator.Users can now pass files such as PDFs when using Haystack Chat Generators based on the Responses API.
-
User Chat Messages can now include files using the new
FileContentdataclass.Most API-based LLMs support file inputs such as PDFs (and, for some models, additional file types).
For now, this feature is implemented for
OpenAIChatGeneratorandAzureOpenAIChatGenerator, with support for more model providers coming soon.For advanced PDF handling, such as...
v2.24.0-rc2
v2.24.0-rc2