Releases · deepset-ai/haystack · GitHub
Skip to content

Releases: deepset-ai/haystack

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

v3.3.0-rc1

v3.3.0-rc1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 30 Sep 13:33
Immutable release. Only release title and notes can be modified.
v3.3.0-rc1

v3.2.0

Choose a tag to compare

@github-actions github-actions released this 24 Sep 10:49
Immutable release. Only release title and notes can be modified.

⭐️ Highlights

🧠 SummarizationCompactor: Compact Agent Context Without Losing It

The experimental SummarizationCompactor is a new compaction strategy for CompactionHook. It replaces older turns and Agent steps with LLM-generated summaries. Long-running Agents keep a condensed record of earlier goals, decisions, and open work. It summarizes as little as needed to reach the target size, starting with the oldest historical turns and only then moving to the current task's older steps. The min_keep_steps newest steps always stay as they are. You can use a small, cheap model to write the summaries.

from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIResponsesChatGenerator
from haystack.hooks.compaction import CompactionHook, SummarizationCompactor

compaction_hook = CompactionHook(
    compactor=SummarizationCompactor(
        chat_generator=OpenAIResponsesChatGenerator(model="gpt-5.4-nano"),
        min_keep_steps=2,
    ),
    context_window=400_000,
    compact_at=0.7,
    compact_to=0.4,
)

agent = Agent(
    chat_generator=OpenAIResponsesChatGenerator(model="gpt-5.4"),
    tools=[web_search],
    hooks={"before_llm": [compaction_hook]},
)

💰 Token Budgets and Clean Stops for Agent Runs

The experimental TokenBudgetHook caps how many tokens an Agent run can spend. Once the budget is used up, the Agent stops before its next LLM call, sets exit_reason to "token_budget_exceeded", and returns the messages collected so far. Set add_final_message=True to add an assistant message explaining why the run ended.

The hook is built on the new stop_run state key, which any hook can set to end a run cleanly with a custom reason instead of raising an error. For example, you can stop after too many calls to one tool:

from haystack.hooks import hook
from haystack.hooks.budget import TokenBudgetHook

@hook
def limit_searches(state):
    if state.get("tool_call_counts", {}).get("web_search", 0) >= 5:
        state.set("stop_run", "search_limit_reached")

agent = Agent(
    chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
    tools=[web_search],
    hooks={"before_llm": [TokenBudgetHook(max_total_tokens=100_000), limit_searches]},
)
result = agent.run(messages)
print(result["exit_reason"])  # "text", "token_budget_exceeded", or "search_limit_reached"

🔗 Fluent Pipeline Building

Building a pipeline takes less code with Haystack version 3.2: Pipeline.add_components() adds several components in one call and Pipeline.connect_many() creates several connections in one call. Both add_component() and add_components() now return the pipeline, so you can chain the calls.

from haystack import Pipeline

pipeline = (
    Pipeline()
    .add_components({"retriever": retriever, "prompt_builder": prompt_builder, "llm": llm})
    .connect_many([("retriever", "prompt_builder.documents"), ("prompt_builder", "llm")])
)

⬆️ Upgrade Notes

  • Serialized OutputAdapter and ConditionalRouter components containing Jinja custom_filters must now be loaded with Pipeline.load(..., unsafe=True) (or the equivalent Pipeline.loads / Pipeline.from_dict option).

  • The tool_result_offloaded meta key that the hook sets on an offloaded message now always holds a list of store references rather than a single reference string, since a result can span several entries. Code reading that key (for example to re-read an offloaded result) should index into the list.

    ToolResultStore.write and read are typed str | bytes. Nothing changes at runtime for a text-only store, but one that subclasses the protocol and annotates content: str may need a type-checker fix.

  • The + operator for combining Toolsets has been removed. Pass Toolsets as a list wherever tools are accepted instead: Agent(..., tools=[toolset_a, toolset_b]). Pipelines serialized with a +-combined Toolset cannot be loaded anymore (their YAML references the removed internal _ToolsetWrapper class): recreate them with the list form and serialize them again.

  • Toolset.add() now only accepts a single Tool, not another Toolset. To combine Toolsets, pass them as a list: Agent(..., tools=[toolset_a, toolset_b]).

🚀 New Features

  • Add Pipeline.add_components() to add a mapping of component names to component instances in one call. Re-adding the same component instance under the same name with Pipeline.add_component() or Pipeline.add_components() is now a no-op.

    pipeline.add_components(
        {
            "retriever": retriever,
            "prompt_builder": prompt_builder,
            "llm": llm,
        }
    )
  • Added the experimental SummarizationCompactor (use with CompactionHook), which progressively summarizes a conversation until it fits a target token budget, preserving useful context from long-running Agents instead of dropping older messages.

    It uses four summarization tiers in order until the target token budget is reached:

    1. historical_turns: Starting with the oldest, summarize as few complete historical turns as needed.
    2. historical_summaries: When no complete historical turns remain, combine as few of the oldest historical summaries as needed.
    3. current_task_steps: Summarize the fewest oldest steps needed to reach the target while preserving the min_keep_steps newest steps.
    4. current_task_summaries: When no more steps can be summarized, combine as few of the current task's oldest summaries as needed to reach the target.
    from typing import Annotated
    from haystack.components.agents import Agent
    from haystack.components.generators.chat import OpenAIResponsesChatGenerator
    from haystack.hooks.compaction import CompactionHook, SummarizationCompactor
    from haystack.tools import tool
    
    @tool
    def web_search(query: Annotated[str, "The search query"]) -> str:
        """Search the web for current information."""
        return f"Search results for: {query}"
    
    agent_generator = OpenAIResponsesChatGenerator(model="gpt-5.4")
    summary_generator = OpenAIResponsesChatGenerator(model="gpt-5.4-nano")
    
    compaction_hook = CompactionHook(
        compactor=SummarizationCompactor(
            chat_generator=summary_generator,
            min_keep_steps=2,
            approximate_summary_tokens=1_024,
        ),
        context_window=400_000,
        compact_at=0.7,
        compact_to=0.4,
    )
    
    agent = Agent(
        chat_generator=agent_generator,
        tools=[web_search],
        hooks={"before_llm": [compaction_hook]},
    )
  • Added stop_run, a control flag that lets an Agent hook end a run cleanly instead of raising an error. The hook sets it to a reason of your choice (state.set("stop_run", "my_reason")), and the Agent stops without another model call and reports that reason in exit_reason.

  • Added TokenBudgetHook (experimental), a ready-made hook that caps how many tokens an Agent run may spend. The run ends with exit_reason set to "token_budget_exceeded", keeping the messages collected so far.

    from haystack.hooks.budget import TokenBudgetHook
    
    agent = Agent(
        chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"),
        tools=[search],
        hooks={"before_llm": [TokenBudgetHook(max_total_tokens=100_000)]},
    )
  • Add Pipeline.connect_many() to connect multiple (sender, receiver) pairs in one call. Pipeline.add_component() and Pipeline.add_components() now return the pipeline, allowing pipeline-building methods to be chained.

    pipeline = (
        Pipeline()
        .add_components(
            {
                "retriever": retriever,
                "prompt_builder": prompt_builder,
                "llm": llm,
            }
        )
        .connect_many(
            [
                ("retriever", "prompt_builder.documents"),
                ("prompt_builder", "llm"),
            ]
        )
    )
  • Added the HAYSTACK_UNSAFE_DESERIALIZATION environment variable as a process-wide equivalent of loading with unsafe=True. When set to a truthy value (1 or true), every Pipeline.load / Pipeline.loads / Pipeline.from_dict call skips all deserialization safety checks — the module allowlist, the builtin/import-primitive and control-plane denylists, the object-internals traversal guard, and the refusal to honor a component's own unsafe: true flag. This is intended for deployments that only ever load fully trusted pipelines and cannot pass unsafe=True at every call site. A warning is logged the first time it takes effect. Only enable it when every pipeline the process loads is trusted: a single untrusted pipeline then leads to arbitrary code execution.

    The switch is not limited to pipeline loading: it disables the same checks for every deserialization path in the process, including ones that take no unsafe argument of their own — Tool.from_dict, State.from_dict (agent snapshot resume), and the ConditionalRouter and OutputAdapter Jinja sandbox flags. A deployment that loads trusted pipelines at startup but accepts serialized tools or agent state at request time is therefore exposed on the request path as well, not only at load time.

    The variable is read once, on the first deserialization in the process, and the result is then frozen for the process lifetime: writes to it afterwards are ignored, in either direction. Set it before the first pipeline is loaded. Freezing keeps the safety mode of a process from changing under a caller's feet and, because the first read happens before any deserialized data can run, stop...

Read more

v3.2.0-rc1

v3.2.0-rc1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 23 Sep 12:24
Immutable release. Only release title and notes can be modified.
v3.2.0-rc1

v3.1.1

Choose a tag to compare

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

Release Notes

v3.1.1

Bug Fixes

  • The OpenAI Chat Completions and Responses converters no longer raise on an assistant message with no content parts, which a Chat Generator returns when it discards a malformed tool call. It is sent with empty content, which the APIs accept, so the next LLM call goes through. ChatMessage.from_openai_dict_format accepts the same empty content, so such a message round-trips.
  • The Hugging Face message converter no longer raises on an assistant message with no content parts, and sends it with empty content instead.