Skip to content

Releases: deepset-ai/haystack

v2.25.2

05 Mar 11:28

Choose a tag to compare

Release Notes

v2.25.2

🐛 Bug Fixes

  • Reverts the change that made Agent messages optional as it caused issues with pipeline execution. As a consequence, the LLM component now defaults to an empty messages list unless provided at runtime.

v2.25.2-rc1

05 Mar 09:37

Choose a tag to compare

v2.25.2-rc1 Pre-release
Pre-release
v2.25.2-rc1

v2.25.1

27 Feb 17:52

Choose a tag to compare

Release Notes

v2.25.1

⚡️ Enhancement Notes

  • Auto variadic sockets now also support Optional[list[...]] input types, in addition to plain list[...].

🐛 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 two list[ChatMessage] outputs to Agent.messages would fail after its type was updated from list[ChatMessage] to list[ChatMessage] | None.

v2.25.1-rc1

27 Feb 11:17

Choose a tag to compare

v2.25.1-rc1 Pre-release
Pre-release
v2.25.1-rc1

v2.25.0

26 Feb 12:58

Choose a tag to compare

⭐️ 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 PipelineTemplate and PredefinedPipeline classes, along with the Pipeline.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 HuggingFaceLocalGenerator has been changed from text2text-generation to text-generation and the default model has been changed from "google/flan-t5-base" to "Qwen/Qwen3-0.6B".

    In transformers v5+, text2text-generation is 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" in HuggingFaceLocalGenerator or HuggingFaceLocalChatGenerator.

    How to handle this change

    • Replace task="text2text-generation" with task="text-generation".
    • Ensure that the selected model is compatible with the text-generation pipeline (for example, causal language models).
    • If you rely on older behavior, pin transformers<5.
    • text2text-generation is now considered deprecated in Haystack and may be removed in a future release.

🚀 New Features

  • Added link_format parameter to PPTXToDocument and XLSXToDocument converters, 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 LLM component (haystack.components.generators.chat.LLM) that provides a simplified interface for text generation powered by a large language model. The LLM component is a streamlined version of the Agent that 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 SearchableToolset to haystack.tools module. 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 single search_tools function 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_prompt and required_variables parameters to the Agent component. 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 constructing ChatMessage objects 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 messages with user_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 FileToFileContent component, which converts local files into FileContent objects. These can be embedded into ChatMessage to pass to an LLM.

  • Added document_comparison_field parameter to DocumentMRREvaluator, DocumentMAPEvaluator, and DocumentRecallEvaluator.

    This allows users to compare documents using fields other than content, such as id or metadata keys (via meta.<key> syntax).

    Previously, all three evaluators hardcoded doc.content for 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 transformers v5. Haystack remains fully compatible with transformers v4, 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 LLMDocumentContentExtractor now extracts both content and metadata from image-based documents. When the LLM returns JSON, document_content fills the document body and other keys are merged into metadata; plain text is still used as content. The field content_extraction_error is no longer used and when an error occurs the field extraction_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.

  • EmbeddingBasedDocumentSplitter and MultiQueryEmbeddingRetriever now automatically invoke warm_up() when run() is called if they have not been warmed up yet.

  • Improved ComponentTool to correctly handle components whose run method parameters are declared as top-level Optional types such as list[ChatMessage] | None. The optional wrapper is now unwrapped before checking for a from_dict method on the underlying type. As a result, when a parameter is typed as list[ChatMessage] | None and receives a list of dictionaries, ComponentTool will automatically coerce the input into a list of ChatMessage objects using ChatMessage.from_dict. If the provided value is None, the parameter is preserved as None.

  • Haystack now emits a Warning when dataclass instances (e.g. Document, ChatMessage,...

Read more

v2.25.0-rc1

25 Feb 10:49

Choose a tag to compare

v2.25.0-rc1 Pre-release
Pre-release
v2.25.0-rc1

v2.24.1

12 Feb 13:56

Choose a tag to compare

🐛 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 ChatMessage to a receiver expecting list[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

12 Feb 13:25

Choose a tag to compare

v2.24.1-rc1 Pre-release
Pre-release
v2.24.1-rc1

v2.24.0

12 Feb 11:14

Choose a tag to compare

⭐️ Highlights

🔌 Pipelines got simpler

With the updated logic, Pipelines can now:

  • connect multiple list[T] component outputs directly to a single list[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 a DocumentJoiner component in ingestion pipelines.
  • support the automatic conversion between ChatMessage and str types, enabling simpler connections between various components. E.g., you can easily connect an Agent component (which returns ChatMessage as last_message) to a text embedder component (which expects a str as query) without an OutputAdapter component.
  • automatically convert list[ChatMessage] to ChatMessage and list[str] to str by taking the first element of the list. Another supported conversion is list[ChatMessage] to str, enabling the connection between a chat generator (which returns list[ChatMessage] as messages) and a BM25 retriever (which expects a str as query).
  • perform list wrapping: a component returning type T can be connected to a component expecting type list[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.

⚠️ Breaking change: 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 id in 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 MultiQueryEmbeddingRetriever and MultiQueryTextRetriever has 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 id across retriever outputs.

  • Removed the deprecated deserialize_document_store_in_init_params_inplace function. 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 FileTypeRouter can now be connected directly to a single converter or writer, without defining an intermediate ListJoiner or DocumentJoiner.

    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 ChatMessage and str types.

    - When a str output is connected to a ChatMessage input, it is automatically converted to a user ChatMessage.

    • When a ChatMessage output is connected to a str input, its text attribute is automatically extracted. If text is None, an informative PipelineRuntimeError is raised.
    • To maintain backward compatibility, when multiple connections are available, strict type matching is prioritized over conversion.
  • 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 PipelineRuntimeError is 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 a str expected 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 MarkdownHeaderSplitter component:

    • 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.
  • Added support for Chat Messages that include files using the FileContent dataclass to OpenAIResponsesChatGenerator and AzureOpenAIResponsesChatGenerator.

    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 FileContent dataclass.

    Most API-based LLMs support file inputs such as PDFs (and, for some models, additional file types).

    For now, this feature is implemented for OpenAIChatGenerator and AzureOpenAIChatGenerator, with support for more model providers coming soon.

    For advanced PDF handling, such as...

Read more

v2.24.0-rc2

11 Feb 14:20

Choose a tag to compare

v2.24.0-rc2 Pre-release
Pre-release
v2.24.0-rc2