Visitar URL original
Releases · deepset-ai/haystack · GitHub
Skip to content

Releases

v3.5.0

Choose a tag to compare

@github-actions github-actions released this 10 Nov 12:34
Immutable release. Only release title and notes can be modified.

⭐️ Highlights

🧭 Classify and Route with the OpenAI Decisions API

Three new components use the OpenAI Decisions API to choose how to handle your data without instructions. OpenAIDecisionsTextRouter sends text to a named output per label, OpenAIDecisionsImageRouter does the same for images and can send low-confidence decisions to a fallback branch, and OpenAIDecisionsDocumentClassifier answers choice, score, and yes/no questions about documents and stores the answers in their metadata.

from haystack.components.routers import OpenAIDecisionsTextRouter

router = OpenAIDecisionsTextRouter(
    labels={
        "billing": "Payments, invoices, refunds and charges",
        "technical": "Bugs, crashes and errors in the product",
    },
    instructions="Which team should handle this support request?",
    min_confidence=0.6,
)

print(router.run(text="I was charged twice for my subscription."))
# >> {'billing': 'I was charged twice for my subscription.'}

🧱 Structured Output for Agents

The new StructuredOutputHook validates the Agent's final reply against a Pydantic model or JSON schema and returns it as a dict in result["structured_output"]. If the reply doesn't validate, the hook sends the error back to the model and lets it try again, up to max_retries times.

from pydantic import BaseModel

from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIResponsesChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.hooks.structured_output import StructuredOutputHook

class CityInfo(BaseModel):
    city: str
    country: str

agent = Agent(
    chat_generator=OpenAIResponsesChatGenerator(generation_kwargs={"text_format": CityInfo}),
    state_schema={"structured_output": {"type": dict}},
    hooks={"on_exit": [StructuredOutputHook(schema=CityInfo)]},
)

result = agent.run(messages=[ChatMessage.from_user("Which city is the Eiffel Tower in?")])
print(result["structured_output"])
# >> {'city': 'Paris', 'country': 'France'}

Also new for Agents: the experimental StepBudgetReminderHook tells the model how many steps it has left as it approaches max_agent_steps, so it wraps up with an answer instead of being cut off.

🎯 Multivector Embeddings and MaxSimRanker

Document has a new multivector_embedding field for late-interaction models such as ColBERT, and the new MaxSimRanker reranks candidates by comparing each query vector to the document vectors and summing the best matches. DocumentJoiner(join_mode="merge_embeddings") can also combine dense, sparse, and multivector embeddings computed in parallel for the same documents.

This is the first step toward multivector retrieval in Haystack (#13257). Embedders that produce multivector embeddings are in progress in Haystack Core Integrations: Sentence Transformers and vLLM.

from haystack import Document
from haystack.components.rankers import MaxSimRanker

# Representative multivector embedding for "How do solar panels work?"
query_multivector_embedding = [[1.0, 0.0], [0.0, 1.0]]

documents = [
    Document(content="Wind turbines generate electricity.", multivector_embedding=[[1.0, 0.0], [0.0, 0.2]]),
    Document(content="Solar panels turn sunlight into power.", multivector_embedding=[[0.95, 0.05], [0.15, 0.9]]),
    Document(content="Solar cells absorb light.", multivector_embedding=[[0.9, 0.1], [0.1, 0.9]]),
]

ranker = MaxSimRanker(top_k=2)
result = ranker.run(query_multivector_embedding=query_multivector_embedding, documents=documents)
for document in result["documents"]:
    print(f"{document.score:.2f}: {document.content}")
# >> 1.85: Solar panels turn sunlight into power.
# >> 1.80: Solar cells absorb light.

⚠️ New Default OpenAI Model

OpenAIChatGenerator, OpenAIResponsesChatGenerator, and the LLM-based evaluators now default to gpt-5.6-terra, the replacement OpenAI recommends for gpt-5-mini. OpenAI shuts down gpt-5-mini on December 11, 2026. Pipelines saved with model: gpt-5-mini keep using it, so update their YAML before then.

⬆️ Upgrade Notes

  • Raise the minimum supported openai SDK version from 2.6.0 to 3.26.0 to support the Decisions API.

  • Declared pydantic>=2.12 (>=2.14 on Python 3.15) as the minimum supported version. pydantic releases before 2.11 generate different JSON schemas for tool parameters in create_tool_from_function/@tool, ComponentTool, and PipelineTool.

  • If you don't set keep_default_na or na_values in read_excel_kwargs, XLSXToDocument now keeps cell text such as "NA", "N/A", "NULL", and "None" instead of treating it as missing. A numeric column that contains such text is therefore no longer converted to floats, so in CSV output its whole numbers render as 1 instead of 1.0. To restore the previous behavior, pass read_excel_kwargs={"keep_default_na": True}.

  • Components with a Variadic input, or a list input connected to several senders, now receive the values in the order the senders were connected with Pipeline.connect(). Before, Pipeline.run() passed them in the order the senders ran, so the results of order-dependent components such as DocumentJoiner with weights can change: connect the senders in the order you want the values to arrive.

    If you load pipelines from YAML, check the order of your connections entries. Pipelines saved before this release list connections grouped by sender, not in the order they were made, and the order of the entries now decides the order in which a socket receives its values. For example, if embedding_retriever is listed before bm25_retriever in connections, DocumentJoiner now applies weights[0] to the embedding retriever, where before it could apply it to the BM25 retriever. Reorder the entries that feed a Variadic or multi-sender list socket so the senders come in the order you expect.

  • The default model of OpenAIChatGenerator and OpenAIResponsesChatGenerator changed from gpt-5-mini to gpt-5.6-terra, the replacement OpenAI recommends. OpenAI shuts down gpt-5-mini on December 11, 2026. LLMEvaluator, ContextRelevanceEvaluator, and FaithfulnessEvaluator use the new default too.

    Pipelines serialized before this change store model: gpt-5-mini and keep using it. Update the model value in their YAML before December 11, 2026. To keep gpt-5-mini until then, pass it explicitly: OpenAIChatGenerator(model="gpt-5-mini").

  • If you read id, encrypted_content, or status directly from reply.reasoning.extra in OpenAIResponsesChatGenerator replies, read them from entries in reply.reasoning.extra["reasoning_items"] instead. For example, replace reply.reasoning.extra["id"] with reply.reasoning.extra["reasoning_items"][0]["id"] for the first item, or iterate over the list to handle all reasoning items.

  • OpenAIChatGenerator and AzureOpenAIChatGenerator now request token usage metadata by default when streaming. To restore the previous behavior, pass generation_kwargs={"stream_options": {"include_usage": False}}.

  • max_agent_steps is now a reserved state key on Agent. If you defined a custom state_schema key named max_agent_steps, rename it (e.g. my_max_agent_steps): the Agent now raises a ValueError at initialization when a reserved key is redefined.

🚀 New Features

  • Add the optional multivector_embedding field to Document to store multi-vector representation such as those generated by late-interaction models like ColBERT. This is a first step toward supporting multivector embedders, retrievers, and rankers.

  • Add MaxSimRanker to rank documents by their similarity to a query using precomputed multivector embeddings. In two-stage retrieval, an embedding retriever can quickly select candidates using dense similarity, then MaxSimRanker performs a more fine-grained comparison of the query and each candidate's content to return the most relevant documents.

  • Add OpenAIDecisionsDocumentClassifier to answer choice, score, and predicate questions about documents using the OpenAI Decisions API. Answers are stored in document metadata.

  • Add OpenAIDecisionsImageRouter to route images to different pipeline branches based on their content, with optional text context. Images with low-confidence decisions can be sent to a fallback branch.

  • Add OpenAIDecisionsTextRouter to route text to named categories using the OpenAI Decisions API.

  • Added StructuredOutputHook to haystack.hooks.structured_output. Registered under on_exit, it parses the Agent's final reply as JSON, validates it against a Pydantic model or a JSON schema, and stores the result as a dict under the structured_output key by default. The key must be declared in the Agent's state_schema, otherwise the hook raises an error at runtime.

    When a reply can't be parsed, the hook sends the validation error back to the model and keeps the Agent running so it can correct its reply, up to max_retries times (2 by default). When the reply still can't be parsed, or the Agent stops on another exit condition, None is stored.

    from pydantic import BaseModel
    
    from haystack.components.agents import Agent
    from haystack.components.generators.chat import OpenAIResponsesChatGenerator
    from haystack.dataclasses import ChatMessage
    from haystack.hooks.structured_output import StructuredOutputHook
    
    
    class CityInfo(BaseModel):
        ci...
Read more

v3.5.0-rc1

v3.5.0-rc1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 09 Nov 12:38
Immutable release. Only release title and notes can be modified.
v3.5.0-rc1

v2.31.1

Choose a tag to compare

@github-actions github-actions released this 03 Nov 09:27
Immutable release. Only release title and notes can be modified.

🔒 Security Notes

  • Haystack now requires anyio>=4.14.2. anyio is installed transitively through httpx and openai; versions before 4.14.2 are affected by CVE-2026-63374 (GHSA-82r6-8w77-94w6).
  • Haystack now requires certifi>=2026.10.19. certifi is installed transitively through httpx and posthog; earlier versions trust eMudhra/emSign certificate authorities whose private keys were compromised (GHSA-6q48-6c7w-9gw2). If you pin certifi, raise the pin to at least 2026.10.19.

v2.31.1-rc1

v2.31.1-rc1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 02 Nov 16:51
Immutable release. Only release title and notes can be modified.
v2.31.1-rc1

v3.4.1

Choose a tag to compare

@github-actions github-actions released this 21 Oct 19:10
Immutable release. Only release title and notes can be modified.

🔒 Security Notes

  • Haystack now requires certifi>=2026.10.19. certifi is installed transitively through httpx and posthog; earlier versions trust eMudhra/emSign certificate authorities whose private keys were compromised (GHSA-6q48-6c7w-9gw2). If you pin certifi, raise the pin to at least 2026.10.19.

v3.4.1-rc2

v3.4.1-rc2 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 21 Oct 18:54
Immutable release. Only release title and notes can be modified.
v3.4.1-rc2

v3.4.1-rc1

v3.4.1-rc1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 21 Oct 18:22
Immutable release. Only release title and notes can be modified.

Release Notes

v3.4.0

Choose a tag to compare

@github-actions github-actions released this 15 Oct 09:26
Immutable release. Only release title and notes can be modified.

⭐️ Highlights

🐍 Python 3.15 Support, Python 3.10 Dropped

Haystack now officially supports Python 3.15. Python 3.10 reaches end of life in October 2026, so the minimum supported version is now 3.11. ⚠️

📦 ToolResultOffloadCompactor: Compact Agent Context Without Losing Tool Results

The experimental ToolResultOffloadCompactor is a new compaction strategy for CompactionHook. When compaction is triggered, it doesn't drop or summarize older tool results. It writes them to a ToolResultStore and leaves a reference in their place. The Agent can read the full output back on demand with a read tool scoped to the same store. Fresh results stay in context until compaction kicks in, so short runs pay nothing extra.

from typing import Annotated

from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIResponsesChatGenerator
from haystack.hooks.compaction import CompactionHook, ToolResultOffloadCompactor
from haystack.hooks.tool_result_offloading import FileSystemToolResultStore
from haystack.tools import tool

store = FileSystemToolResultStore(root="tool_results")

@tool
def read_offloaded_result(path: Annotated[str, "Path of an offloaded tool result"]) -> str:
    """Read back the full content of an offloaded tool result."""
    content = store.read(path)
    if isinstance(content, bytes):
        return f"'{path}' holds {len(content)} bytes of binary content and cannot be read as text."
    return content

agent = Agent(
    chat_generator=OpenAIResponsesChatGenerator(model="gpt-5.4-nano"),
    tools=[web_search, read_offloaded_result],
    hooks={"before_llm": [CompactionHook(compactor=ToolResultOffloadCompactor(store=store), context_window=400_000)]},
)

⬆️ Upgrade Notes

  • The page_number metadata field now means the same thing in every splitter: the page the chunk's text starts on. Two cases change:

    • RecursiveDocumentSplitter reported the page a chunk ended on. A chunk spanning a page break now reports the page it starts on, matching DocumentSplitter and MarkdownHeaderSplitter.
    • DocumentSplitter, MarkdownHeaderSplitter and EmbeddingBasedDocumentSplitter reported the previous page for a chunk that began with one or more form feeds (\f), even though all of the chunk's text was on the following page. Such chunks now report the page their text is on. A chunk made up entirely of page breaks is an empty page and keeps the page it starts on, so blank pages emitted by split_by="page" still get their own number.

    If you display page_number to users or store it alongside embeddings, re-index documents that contain form feeds to pick up the corrected values.

  • RecursiveDocumentSplitter with split_unit="word" and split_overlap greater than 0 now returns chunks copied from the document as they are. Previously it rebuilt them from their words joined by single spaces, which added spaces the document does not have, collapsed line breaks and repeated spaces, and could drop form feeds (\f). The overlap also counts words the way split_length does, as text separated by spaces, so where words are separated only by line or page breaks, chunks can end at different words than before. Re-index documents split this way if you need their chunks to match the source text.

  • RecursiveDocumentSplitter now keeps the whitespace between sentences when sentence_splitter_params sets keep_white_spaces to False. That is the sentence tokenizer's default, so this applies whenever sentence_splitter_params is passed without "keep_white_spaces": True. The tokenizer leaves that whitespace out, and the chunks used to glue their sentences together, as in "One two.Three four.": the glued words counted as one toward split_length, page breaks (\f) between sentences were lost, and split_idx_start and page_number fell behind the chunk's real start. Chunks are now copied from the document whatever keep_white_spaces is set to; it only changes the rules the tokenizer uses to decide where a sentence ends. Re-index documents split with this setting to pick up the corrected chunks.

  • Dropped support for Python 3.10 since it reaches End of Life (EOL) in October 2026. The minimum supported Python version is now 3.11. If you are on Python 3.10, upgrade your Python version before upgrading Haystack.

  • FilterPolicy.MERGE now intersects initialization and runtime logical filters when both use OR or NOT. Callers that relied on the previous broader result should construct a single explicit OR filter containing all alternatives and pass it with FilterPolicy.REPLACE instead.

  • ChatMessage.from_openai_dict_format now raises a ValueError for content parts it can't convert: images referenced by a URL such as https://example.com/image.png (only images embedded as base64 data URLs, like data:image/png;base64,..., are supported), files referenced by file_id, and other part types such as input_audio. Previously, such messages were accepted because the whole list was stored as a single text, and ChatMessage.to_openai_dict_format sent that list back unchanged, so forwarding them to OpenAI worked by accident. You're affected if the OpenAI messages you convert contain any of these parts. To keep sending images by URL, download them and build the message yourself, for example ChatMessage.from_user(content_parts=["Describe this image", ImageContent.from_url(url)]). Send files inline as base64 file_data instead of referencing them by file_id. Extra keys on content parts, such as cache_control, are no longer passed through, so prompt caching through OpenRouter or LiteLLM stops for these messages.

  • HTMLToDocument now defaults to encoding=None, delegating HTML encoding detection to Trafilatura. Set encoding="utf-8" to retain explicit UTF-8 decoding.

  • Previously, HTMLToDocument used the encoding value in a source ByteStream's metadata to decode its content, overriding the converter's encoding setting. This metadata no longer affects decoding. If you relied on it, pass the encoding to the converter instead, for example HTMLToDocument(encoding="latin-1").

  • The default model for OpenAITextEmbedder, OpenAIDocumentEmbedder, AzureOpenAITextEmbedder, and AzureOpenAIDocumentEmbedder has been changed from text-embedding-ada-002 to text-embedding-3-small.

    text-embedding-3-small is roughly 5x cheaper per token than the previous generation text-embedding-ada-002 and scores higher on the MTEB benchmark (see OpenAI's announcement).

    To preserve previous behavior, pass model="text-embedding-ada-002" explicitly (or, for the Azure variants, azure_deployment="text-embedding-ada-002").

    Note for Azure users: azure_deployment refers to the deployment name in your Azure OpenAI resource, not a model identifier. If you do not already have a text-embedding-3-small deployment in Azure, you will need to create one or pass the name of an existing deployment explicitly.

    Embeddings generated with the new default model are not compatible with embeddings produced by text-embedding-ada-002. If you have a document store populated with previously-generated embeddings, either keep using the old model explicitly or re-embed your corpus.

  • The base Tool.warm_up() and Toolset.warm_up() methods were removed. OpenAI/Azure Chat Generators no longer warm up tools.

    If you use tools through an Agent, no changes are needed. For manual preparation, use warm_up_tools or warm_up_tools_async. Lazy toolsets such as MCPToolset must be warmed up before passing them to a Chat Generator.

🚀 New Features

  • Added the experimental ToolResultOffloadCompactor for CompactionHook. Once compaction is triggered, it writes older tool results to a ToolResultStore and leaves a reference in their place, so the Agent can read the full output back with a read tool you scope to the same store. Fresh results stay in context until then.

    from typing import Annotated
    
    from haystack.components.agents import Agent
    from haystack.components.generators.chat import OpenAIResponsesChatGenerator
    from haystack.hooks.compaction import CompactionHook, ToolResultOffloadCompactor
    from haystack.hooks.tool_result_offloading import FileSystemToolResultStore
    from haystack.tools import tool
    
    store = FileSystemToolResultStore(root="tool_results")
    
    
    @tool
    def read_offloaded_result(path: Annotated[str, "Path of an offloaded tool result"]) -> str:
        """Read back the full content of an offloaded tool result."""
        content = store.read(path)
        if isinstance(content, bytes):
            return f"'{path}' holds {len(content)} bytes of binary content and cannot be read as text."
        return content
    
    
    # web_search stands in for your own tool that returns long results.
    hook = CompactionHook(compactor=ToolResultOffloadCompactor(store=store), context_window=400_000)
    agent = Agent(
        chat_generator=OpenAIResponsesChatGenerator(model="gpt-5.4-nano"),
        tools=[web_search, read_offloaded_result],
        hooks={"before_llm": [hook]},
    )

⚡️ Enhancement Notes

  • When an Agent runs inside a Pipeline, the anonymous pipeline telemetry event now also reports the chat generator class and its tools: the number of tools, the class and name of each tool, the component wrapped by each ComponentTool, and the classes of any Toolset objects.

  • Haystack now officially supports Python 3.15.

  • Removed the Sockets.__getattribute__ override. It looked up a _sockets attribute that no instance has, so on every attribute access it r...

Read more

v3.4.0-rc1

v3.4.0-rc1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 14 Oct 11:29
Immutable release. Only release title and notes can be modified.
v3.4.0-rc1

v3.3.0

Choose a tag to compare

@github-actions github-actions released this 01 Oct 10:39
Immutable release. Only release title and notes can be modified.

⭐️ Highlight

⚡️ Faster SentenceWindowRetriever

SentenceWindowRetriever now queries the Document Store once per run or run_async call instead of once per retrieved document. Fewer calls mean lower latency and less load on the Document Store and outputs have not changed, so existing pipelines get faster without code changes.

⬆️ Upgrade Notes

  • InMemoryDocumentStore.bm25_retrieval and InMemoryBM25Retriever with BM25L (the default) or BM25Plus now return only documents that contain at least one query term, with or without scale_score. Previously, documents without any query term were returned with a positive score and filled up top_k. As a result, retrieval can now return fewer than top_k documents, or none at all. The delta lower bound now applies only to query terms that occur in the document, as in the original BM25L/BM25+ definitions. So scores of matching documents are lower than before whenever the query has terms that a document does not contain. If you filter results with a fixed score threshold, re-check the threshold. BM25Okapi is not affected.

⚡️ Enhancement Notes

  • TextCleaner.run now raises a clear TypeError when texts is not a list or any element is not a str, instead of failing later or producing unexpected results.

🔒 Security Notes

  • Haystack now requires anyio>=4.14.2. anyio is installed transitively through httpx and openai; versions before 4.14.2 are affected by CVE-2026-63374 (GHSA-82r6-8w77-94w6).

🐛 Bug Fixes

  • Fixed BM25 tokenization in InMemoryDocumentStore for Chinese, Japanese and Korean text. The default bm25_tokenization_regex now splits CJK characters into one token each, so bare-term queries can match words inside longer unspaced runs. Text is also NFC-normalized before tokenization, so composed and decomposed spellings produce the same tokens.

  • Fix Pipeline.connect() raising TypeError: object of type 'ellipsis' has no len() when one of the sockets is annotated with Callable[..., T]. A Callable[..., T] now matches callables with any parameters, in both directions, as long as the return types are compatible.

  • Fixed ChatPromptBuilder dropping content parts when the template is a list of ChatMessage objects. Only the first TextContent part was rendered, and every other part - additional texts, images, files -was removed from the rendered prompt without a warning. Template variables used in those dropped parts were not detected either, so they were not exposed as inputs. Now all TextContent parts are rendered and the remaining content parts are passed through unchanged.

  • Fixed CompactionHook.close() and close_async() to release the token counter's resources as well as the compactor's. Previously, resources such as OpenAITokenCounter's HTTP client remained open after the hook or Agent was closed.

  • ConfirmationHook no longer drops the messages that come after the last user or tool message when it rewrites the conversation, such as a system message added by another hook or an assistant answer followed by an on_exit reminder. Before, those messages were removed from the history the Agent sends to the model.

  • DocumentToImageContent no longer raises a KeyError for the whole batch when a document points to a PDF page that cannot be converted, because the page is out of range or the PDF cannot be read. That document now gets None in image_contents, like other invalid documents, and the rest of the batch is still converted. The logged warnings now include the path of the PDF file.

  • Fixed DOCXToDocument breaking a Markdown table when a cell contains a pipe or spans several paragraphs. A pipe was emitted as a column separator, and a cell's line break ended the row in the middle of it. Pipes are now escaped and line breaks inside a cell are collapsed to a space. The csv table format is unchanged: it already kept such cells intact by quoting them.

  • Fixed DOCXToDocument dropping hyperlink addresses inside tables. With link_format set to markdown or plain, links in table cells were written as their display text only, while links in body paragraphs kept their address. Links in table cells are now formatted the same way, in both the markdown and csv table formats. The default link_format="none" output is unchanged.

  • Fixed ConditionalRouter.from_dict mutating the caller's routes data in place: serialized output_type strings were deserialized directly inside the caller's dictionaries, so reusing the same serialized pipeline dict afterwards yielded already-deserialized type objects. from_dict now works on a copy and the caller's data is left untouched. BranchJoiner.from_dict received the same treatment.

  • LinkContentFetcher no longer adds its timeout and follow_redirects defaults to the client_kwargs dictionary passed by the caller. The dictionary is now copied before the defaults are applied, so reusing one HTTP client configuration across components no longer leaks these defaults.

  • Fix MetaFieldRanker to return no documents when missing_meta="drop" and all documents lack the ranking field or have a None value.

  • MetaFieldRanker now treats a Document whose meta_field value is None the same as a Document that is missing the field, applying the missing_meta setting to it. Previously a single None value made sorting fail, so the ranker logged a warning and returned all Documents in their original order, ignoring missing_meta="drop" as well.

  • MockTextEmbedder and MockDocumentEmbedder now accept non-positive dimension values when embedding or embedding_fn is provided, matching the documented behavior. The default deterministic embedding mode still requires a positive dimension.

  • ChatMessage.from_openai_dict_format now accepts tool-call arguments that are already a dictionary instead of raising a TypeError. Some OpenAI-compatible servers send a parsed object rather than a JSON string.

  • MetaFieldGroupingRanker now treats a group_by or subgroup_by value of None as missing, the same way it already treats sort_docs_by. Documents with None go to the end with the other ungrouped documents, instead of forming a group named "None" that also absorbed documents whose value was the string "None".

  • Fixed JSONConverter failing with ValueError when content_key contains numeric or boolean scalar values. These values are now converted to strings before creating the Document, while null values remain unchanged.

  • Fixed SentenceSplitter losing the whitespace between two sentences when the first one ends with a closing quote, as in He said "Hi." Bye.. Those characters were missing from the chunk text and shifted the split_idx_start offset of every following chunk, so chunks could no longer be mapped back onto the original text. This affects all components that split on sentences, such as DocumentSplitter, RecursiveDocumentSplitter, MarkdownHeaderSplitter and EmbeddingBasedDocumentSplitter. Chunk boundaries change for text that contains quoted sentences, so re-indexing an existing corpus produces different chunks than before.

  • Fixed LLMMetadataExtractor incorrectly treating a chat generator output that carries its own error field as a failed LLM call, which sent every document to failed_documents with metadata_extraction_error set to None. Such documents are now processed normally.

  • Retrievers now handle top_k consistently. A negative top_k passed at runtime to InMemoryBM25Retriever, InMemoryEmbeddingRetriever or MultiRetriever (top_k and top_k_per_retriever) now raises a ValueError. Previously it was applied as a negative slice, silently dropping the last documents. A runtime top_k=0 returns no documents.

    MultiRetriever now also validates top_k and top_k_per_retriever at initialization, raising a ValueError when they are set and not greater than 0, matching the in-memory retrievers. None still means no limit.

  • Fixed Pipeline.run(), Pipeline.run_async(), and Pipeline.stream() treating a flat input with a dictionary value as a component name. For example, {"payload": {"x": 1}} now reaches components with a payload input without requiring the component name in the input data.

  • Pipeline.run_async now lets a BreakpointException or PipelineRuntimeError raised by a nested component propagate unchanged, instead of wrapping it in another PipelineRuntimeError. This matches the synchronous Pipeline.run and preserves the original error context (for example an agent snapshot) when a component internally runs a pipeline.

  • XLSXToDocument now escapes pipe characters and replaces in-cell line breaks with spaces in the default Markdown pipe table output. This keeps cell content from being interpreted as additional table columns or rows.

💙 Big thank you to everyone who contributed to this release!

@alanhuangyoo, @anakin87, @bilgeyucel, @carey-bk, @Cha-Imaa, @chrikrah, @dakjdakd, @gauravch-code, @Harsh23Kashyap, @Jayanth-reflex, @JbravoI, @julian-risch, @L4XB, @Lesereingrape, @lets-order-some-fries, @mnm-matin, @MohammadHijjawi97, @nanhanq1, @pcbeingused333, @PeterSmith0127-lcm, @Rainmemery, @sclfcz, @serhiizghama, @shivsin25, @ShousenZHANG, @simpleqt, @winklemad