⭐️ Highlights
✂️ Smarter Document Chunking with Embedding-Based Splitting
Introducing the new EmbeddingBasedDocumentSplitter, a component that takes an embedder and splits documents based on semantic similarity rather than fixed sizes or rules.
from haystack.components.embedders import SentenceTransformersDocumentEmbedder
from haystack.components.preprocessors import EmbeddingBasedDocumentSplitter
# Initialize an embedder to calculate semantic similarities
embedder = SentenceTransformersDocumentEmbedder()
# Configure the splitter with parameters that control splitting behavior
splitter = EmbeddingBasedDocumentSplitter(
document_embedder=embedder,
sentences_per_group=2, # Group 2 sentences before calculating embeddings
percentile=0.95, # Split when cosine distance exceeds 95th percentile
min_length=50, # Merge splits shorter than 50 characters
max_length=1000 # Further split chunks longer than 1000 characters
)
result = splitter.run(documents=[doc])🔥 warm_up Runs Automatically on First Use
Components that define awarm_up method now run it automatically on first execution, removing the need for manual calls and preventing errors in standalone usage.
from haystack.components.embedders import SentenceTransformersTextEmbedder
text_embedder = SentenceTransformersTextEmbedder()
# text_embedder.warm_up() # ❌ Don't need this step anymore
print(text_embedder.run("I love pizza!"))
## {'embedding': [-0.07804739475250244, 0.1498992145061493,, ...]}🛠️ Multiple Tool String Outputs with outputs_to_string
Tools can now expose multiple string outputs via the new outputs_to_string configuration, giving you fine-grained control over how tool results are surfaced to the LLM, without changing the underlying tool logic.
def format_documents(documents):
return "\n".join(f"{i+1}. Document: {doc.content}" for i, doc in enumerate(documents))
def format_summary(metadata):
return f"Found {metadata['count']} results"
tool = Tool(
name="search",
description="Search for documents",
parameters={...},
function=search_func, # Returns {"documents": [Document(...)], "metadata": {"count": 5}, "debug_info": {...}}
outputs_to_string={
"formatted_docs": {"source": "documents", "handler": format_documents},
"summary": {"source": "metadata", "handler": format_summary}
# Note: "debug_info" is not included, so it won't be converted to a string
}
)
# After the tool invocation, the tool result includes:
# {
# "formatted_docs": "1. Document Title\n Content...\n2. ...",
# "summary": "Found 5 results"
# }🐍 Python 3.10+ Only
Haystack now requires Python 3.10 or later, as Python 3.9 reached End of Life (EOL) in October 2025.
⬆️ Upgrade Notes
HuggingFaceLocalChatGeneratornow usesQwen/Qwen3-0.6Bas the default model, replacing the previous default.
⚡️Enhancement Notes
-
The parameters
query_suffixanddocument_suffixhave been added toSentenceTransformersSimilarityRankerto support the Qwen3 reranker model family.Here is an example of how to use these new parameters to use the Qwen3-Reranker-0.6B:
from haystack import Document from haystack.components.rankers.sentence_transformers_similarity import SentenceTransformersSimilarityRanker ranker = SentenceTransformersSimilarityRanker( model="tomaarsen/Qwen3-Reranker-0.6B-seq-cls", query_prefix='<|im_start|>system\nJudge whether the Document meets the requirements based on the Query and the Instruct provided. Note that the answer can only be "yes" or "no".<|im_end|>\n<|im_start|>user\n<Instruct>: Given a web search query, retrieve relevant passages that answer the query\n<Query>: ', query_suffix="\n", document_prefix="<Document>: ", document_suffix="<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n", ) result = ranker.run( query="Which planet is known as the Red Planet?", documents=[ Document(content="Venus is often called Earth's twin because of its similar size and proximity."), Document(content="Mars, known for its reddish appearance, is often referred to as the Red Planet."), Document(content="Jupiter, the largest planet in our solar system, has a prominent red spot."), Document(content="Saturn, famous for its rings, is sometimes mistaken for the Red Planet."), ], ) print(result)
NOTE: This only works with the Qwen3 reranker models that use the sequence classification architecture. For example, you can find some on
tomaarsen's Hugging Face profile. -
Added reasoning content support to
HuggingFaceAPIChatGenerator. The component now extracts reasoning content from models that support chain-of-thought reasoning (e.g., DeepSeek R1). Both streaming and non-streaming modes are supported. Access viareply.reasoning.reasoning_text. -
When an Agent runs as part of a Pipeline, the agent's tracing span now uses the component span as its parent. This enables proper nested trace visualization in tracing tools like Datadog, Braintrust, or OpenTelemetry backends.
-
The
_handle_async_stream_response()method inOpenAIChatGeneratornow handlesasyncio.CancelledErrorexceptions. When a streaming task is cancelled mid-stream, the async for loop gracefully closes the stream usingasyncio.shield()to ensure the cleanup operation completes even during cancellation. -
A new
enable_thinkingparameter has been added to enable thinking mode in chat templates for thinking-capable models, allowing them to generate intermediate reasoning steps before producing final responses. -
Add support for PEP 604 type syntax. This means that when defining types in components, you can use
X | Yinstead ofUnion[X, Y]andX | Noneinstead ofOptional[X]. The codebase has been migrated to the new syntax, but both syntaxes are fully supported. -
Support Multiple Tool String Outputs
Added support for tools to define multiple string outputs using the
outputs_to_stringconfiguration. This allows users to specify how different parts of a tool's output should be converted to strings, enhancing flexibility in handling tool results.- Updated
ToolInvokerto handle multiple output configurations. - Updated
Toolto validate and store multiple output configurations. - Added tests to verify the functionality of multiple string outputs.
This enables tools to provide rich, varied context to language models or downstream components without requiring multiple tool calls, while keeping full control over which outputs are stringified.
- Updated
-
Added validation for
inputs_from_stateandoutputs_to_stateparameters in theToolclass. Tools now validate at construction time that state mappings reference valid tool parameters and outputs, catching configuration errors early instead of at runtime. The validation uses function introspection and JSON schema to ensure parameter names exist, and subclasses likeComponentToolvalidate against component input/output sockets.
🐛 Bug Fixes
- Improved error messages in ConditionalRouter when non-string values are provided as route outputs. Users now receive clear guidance (e.g., "use '2' instead of 2") instead of the cryptic "Can't compile non template nodes" error.
- Fixes jinja2 variable detection in
ConditionalRouter,ChatPromptBuilder,PromptBuilderandOutputAdapterby properly skipping variables that are assigned within the template. Previously under specific scenarios variables assigned within a template would falsely be picked up as input variables to the component. For more information you can check out the parent issue in the Jinja2 library here: pallets/jinja#2069 - Fixes deserializing an instance of
NamedEntityExtractorwhenpipeline_kwargsis stored in the deserialization dict with the value ofNone. - When creating an HTTP client object from a dictionary, we now convert the
limitsparameter to anhttpx.Limitsobject to avoid AttributeError. - Raise a
ValueErrorwhen an async function is passed to theToolclass. Async functions are not supported as tools. This change provides a clear error message instead of silent failures where coroutines are never awaited.
⚠️ Deprecation Notes
- The
return_empty_on_no_matchparameter has been removed from theRegexTextExtractorcomponent. This component now always returns a dictionary with the key "captured_text"; the value can be an empty string if no match is found or the captured text. Currently, thereturn_empty_on_no_matchparameter is ignored. Starting from Haystack 2.23.0, initializing the component with this parameter will raise an error.
💙 Big thank you to everyone who contributed to this release!
@anakin87, @ArzelaAscoIi, @bilgeyucel, @Bobholamovic, @davidsbatista, @dfokina, @GunaPalanivel, @majiayu000, @OliverZhangA, @sjrl, @TaMaN2031A, @tommasocerruti, @tstadel, @vblagoje, @YassineGabsi