github deepset-ai/haystack v2.31.0-rc2

pre-release5 hours ago

⬆️ Upgrade Notes

  • DocumentNDCGEvaluator now matches documents by their content field by default instead of their auto-generated id. Previously, ground truth and retrieved documents were matched only if they had identical id values, which rarely happened in practice since IDs are generated independently for each Document instance. As a result, NDCG scores computed with this evaluator may change for existing pipelines. To keep the previous id-based matching behavior, pass document_comparison_field="id" when constructing the evaluator.

🚀 New Features

  • Added native asynchronous support (run_async) to LLMEvaluator, FaithfulnessEvaluator, and ContextRelevanceEvaluator. This allows concurrent evaluation loops inside async applications like FastMCP or FastAPI without blocking the main event loop, while automatically falling back to thread workers for synchronous chat generators.
  • Added optional YAML frontmatter extraction to MarkdownToDocument. When initialized with extract_frontmatter=True, YAML frontmatter at the beginning of a Markdown file is removed from the converted content and added to Document.meta.

⚡️ Enhancement Notes

  • Added document_comparison_field parameter to DocumentNDCGEvaluator, consistent with DocumentMAPEvaluator, DocumentMRREvaluator, and DocumentRecallEvaluator. Users can now match documents by "content", "id", or any "meta.<key>" field when calculating NDCG scores.

  • Add output_passthrough option to ConditionalRouter. When output_passthrough: True is set in a route, the output field is treated as a plain variable name instead of a Jinja2 template, and the value is passed directly from the pipeline inputs to the route output. This allows routing of complex non-basic types such as dataclasses and Pydantic models without unwanted Jinja2 template processing.

    Without output_passthrough, the router renders output as a Jinja2 template, which converts the value to its string representation. Custom types cannot survive that round-trip:

    # Without output_passthrough — the object is silently converted to a string
    routes = [
        {
            "condition": "{{True}}",
            "output": "{{query}}",
            "output_name": "out",
            "output_type": ParsedQuery,
        }
    ]
    router = ConditionalRouter(routes)
    result = router.run(query=ParsedQuery(text="hello", intent="search", entities=[]))
    # result["out"] == "ParsedQuery(text='hello', intent='search', entities=[])"
    # ^^^ str, not ParsedQuery — the object was destroyed

    Set output_passthrough: True to skip Jinja2 entirely and pass the value directly from kwargs:

    from haystack.components.routers import ConditionalRouter
    from dataclasses import dataclass, field
    
    @dataclass
    class ParsedQuery:
        text: str
        intent: str        # "search" | "chat"
        entities: list[str] = field(default_factory=list)
    
    routes = [
        {
            "condition": "{{query.intent == 'search'}}",
            "output": "query",           # variable name, not a Jinja2 template
            "output_name": "search_query",
            "output_type": ParsedQuery,
            "output_passthrough": True,
        },
        {
            "condition": "{{query.intent == 'chat'}}",
            "output": "query",
            "output_name": "chat_query",
            "output_type": ParsedQuery,
            "output_passthrough": True,
        },
    ]
    
    router = ConditionalRouter(routes)
    query = ParsedQuery(text="What is Haystack?", intent="search", entities=["Haystack"])
    result = router.run(query=query)
    
    assert isinstance(result["search_query"], ParsedQuery)  # type preserved
    assert result["search_query"] is query                  # same object, no copying
  • Added an opt-in expand_reference_ranges parameter to AnswerBuilder. When enabled, reference ranges like [6-10] and comma-separated ranges like [1-3,7-9] are expanded to the corresponding document indices in RAG answers. The feature is disabled by default to preserve existing parsing behavior.

⚠️ Deprecation Notes

  • AzureOCRDocumentConverter is deprecated and will be removed from Haystack in version 3.0. It is moving to the azure-form-recognizer-haystack package. To continue using it, install the package with pip install azure-form-recognizer-haystack and update your import as follows:

    from haystack_integrations.components.converters.azure_form_recognizer import AzureOCRDocumentConverter
  • DatadogTracer is deprecated and will be removed from Haystack in version 3.0. It is moving to the datadog-haystack package. To continue using it, install the package with pip install datadog-haystack and add the DatadogConnector component to your pipeline to enable tracing, or update your import as follows:

    from haystack_integrations.tracing.datadog import DatadogTracer

    Note that, starting with Haystack 3.0, Datadog tracing is no longer auto-enabled when ddtrace is installed. Use the DatadogConnector component to enable it.

  • OpenAIGenerator, AzureOpenAIGenerator, HuggingFaceAPIGenerator, and HuggingFaceLocalGenerator have been deprecated and will be removed in Haystack 3.0. Generators living in Haystack Core Integrations will also be removed in Haystack 3.0.

    Their chat counterparts (OpenAIChatGenerator, AzureOpenAIChatGenerator, HuggingFaceAPIChatGenerator, HuggingFaceLocalChatGenerator) are the replacement. Starting from Haystack 2.30.0, all ChatGenerators also accept a plain str as input to make the transition easier.

    How to migrate:

    Direct usage (running a generator from Python code)

    Before:

    from haystack.components.generators import OpenAIGenerator
    
    gen = OpenAIGenerator()
    result = gen.run("What is NLP?")
    text = result["replies"][0]   # str
    meta = result["meta"][0]      # dict with model metadata

    After:

    from haystack.components.generators.chat import OpenAIChatGenerator
    
    gen = OpenAIChatGenerator()
    result = gen.run("What is NLP?")   # str input accepted directly
    reply = result["replies"][0]       # ChatMessage
    text = reply.text                  # str
    meta = reply.meta                  # dict with model metadata (now on the message)

    System prompt

    Before:

    from haystack.components.generators import OpenAIGenerator
    
    gen = OpenAIGenerator(system_prompt="You are concise.")
    result = gen.run("What is NLP?")

    After:

    from haystack.components.generators.chat import OpenAIChatGenerator
    from haystack.dataclasses import ChatMessage
    
    gen = OpenAIChatGenerator()
    result = gen.run([
        ChatMessage.from_system("You are concise."),
        ChatMessage.from_user("What is NLP?"),
    ])

    Pipeline usage

    Pipelines that connected PromptBuilder (output: str) to a legacy Generator continue to work unchanged when you swap in a ChatGenerator. The Haystack pipeline type system automatically converts str to list[ChatMessage] at the connection edge.

    Before:

    from haystack.components.generators import OpenAIGenerator
    from haystack.components.builders import PromptBuilder
    
    pipeline.add_component("prompt_builder", PromptBuilder(template=prompt_template))
    pipeline.add_component("llm", OpenAIGenerator())
    pipeline.connect("prompt_builder", "llm")   # str -> str

    After, minimal change (smart connection still works):

    from haystack.components.generators.chat import OpenAIChatGenerator
    from haystack.components.builders import PromptBuilder
    
    pipeline.add_component("prompt_builder", PromptBuilder(template=prompt_template))
    pipeline.add_component("llm", OpenAIChatGenerator())
    pipeline.connect("prompt_builder", "llm")   # str -> list[ChatMessage], auto-converted

    Alternatively, use ChatPromptBuilder:

    from haystack.components.generators.chat import OpenAIChatGenerator
    from haystack.components.builders import ChatPromptBuilder
    from haystack.dataclasses import ChatMessage
    
    template = [ChatMessage.from_user(prompt_template)]
    pipeline.add_component("prompt_builder", ChatPromptBuilder(template=template))
    pipeline.add_component("llm", OpenAIChatGenerator())
    pipeline.connect("prompt_builder.prompt", "llm.messages")
  • HuggingFaceAPIChatGenerator, HuggingFaceAPITextEmbedder, HuggingFaceAPIDocumentEmbedder, and HuggingFaceTEIRanker are deprecated and will be removed from Haystack in version 3.0. They are moving to the huggingface-api-haystack package. To continue using them, install the package with pip install huggingface-api-haystack and update your imports as follows:

    from haystack_integrations.components.generators.huggingface_api import HuggingFaceAPIChatGenerator
    from haystack_integrations.components.embedders.huggingface_api import HuggingFaceAPIDocumentEmbedder
    from haystack_integrations.components.embedders.huggingface_api import HuggingFaceAPITextEmbedder
    from haystack_integrations.components.rankers.huggingface_api import HuggingFaceTEIRanker
  • DocumentLanguageClassifier and TextLanguageRouter are deprecated and will be removed from Haystack in version 3.0. They are moving to the langdetect-haystack package. To continue using them, install the package with pip install langdetect-haystack and update your imports as follows:

    from haystack_integrations.components.classifiers.langdetect import DocumentLanguageClassifier
    from haystack_integrations.components.routers.langdetect import TextLanguageRouter
  • OpenAPIConnector, OpenAPIServiceConnector and OpenAPIServiceToFunctions are deprecated and will be removed from Haystack in version 3.0. They are moving to the openapi-haystack package. To continue using them, install the package with pip install openapi-haystack and update your imports as follows:

    from haystack_integrations.components.connectors.openapi import OpenAPIConnector
    from haystack_integrations.components.connectors.openapi import OpenAPIServiceConnector
    from haystack_integrations.components.converters.openapi import OpenAPIServiceToFunctions

    These components are a legacy way to connect Haystack to external APIs. For most use cases, consider using the MCPTool instead, which is the modern, standardized way to give pipelines and agents access to external tools and services.

  • OpenTelemetryTracer is deprecated and will be removed from Haystack in version 3.0. It is moving to the opentelemetry-haystack package. To continue using it, install the package with pip install opentelemetry-haystack and add the OpenTelemetryConnector component to your pipeline to enable tracing, or update your import as follows:

    from haystack_integrations.tracing.opentelemetry import OpenTelemetryTracer

    Note that, starting with Haystack 3.0, OpenTelemetry tracing is no longer auto-enabled when opentelemetry-sdk is installed. Use the OpenTelemetryConnector component to enable it.

  • SearchApiWebSearch is deprecated and will be removed from Haystack in version 3.0. It is moving to the searchapi-haystack package. To continue using it, install the package with pip install searchapi-haystack and update your import as follows:

    from haystack_integrations.components.websearch.searchapi import SearchApiWebSearch
  • SentenceTransformersTextEmbedder, SentenceTransformersDocumentEmbedder, SentenceTransformersDocumentImageEmbedder, SentenceTransformersSparseTextEmbedder, SentenceTransformersSparseDocumentEmbedder, SentenceTransformersDiversityRanker, and SentenceTransformersSimilarityRanker are deprecated and will be removed from Haystack in version 3.0. They are moving to the sentence-transformers-haystack package. To continue using them, install the package with pip install sentence-transformers-haystack and update your imports as follows:

    from haystack_integrations.components.embedders.sentence_transformers import SentenceTransformersTextEmbedder
    from haystack_integrations.components.embedders.sentence_transformers import SentenceTransformersDocumentEmbedder
    from haystack_integrations.components.embedders.sentence_transformers import SentenceTransformersDocumentImageEmbedder
    from haystack_integrations.components.embedders.sentence_transformers import SentenceTransformersSparseTextEmbedder
    from haystack_integrations.components.embedders.sentence_transformers import SentenceTransformersSparseDocumentEmbedder
    from haystack_integrations.components.rankers.sentence_transformers import SentenceTransformersDiversityRanker
    from haystack_integrations.components.rankers.sentence_transformers import SentenceTransformersSimilarityRanker
  • SerperDevWebSearch is deprecated and will be removed from Haystack in version 3.0. It is moving to the serperdev-haystack package. To continue using it, install the package with pip install serperdev-haystack and update your import as follows:

    from haystack_integrations.components.websearch.serperdev import SerperDevWebSearch
  • The spacy backend of NamedEntityExtractor is deprecated and will be removed from Haystack in version 3.0. It is moving to the spacy-haystack package and being renamed to SpacyNamedEntityExtractor. To continue using it, install the package with pip install spacy-haystack and update your import as follows:

    from haystack_integrations.components.extractors.spacy import SpacyNamedEntityExtractor
  • TikaDocumentConverter is deprecated and will be removed from Haystack in version 3.0. It is moving to the tika-haystack package. To continue using it, install the package with pip install tika-haystack and update your import as follows:

    from haystack_integrations.components.converters.tika import TikaDocumentConverter
  • TransformersZeroShotDocumentClassifier, TransformersTextRouter, TransformersZeroShotTextRouter, HuggingFaceLocalChatGenerator, NamedEntityExtractor, and ExtractiveReader are deprecated and will be removed from Haystack in version 3.0. They are moving to the transformers-haystack package. Some of them are also being renamed. To continue using them, install the package with pip install transformers-haystack and update your imports as follows:

    from haystack_integrations.components.classifiers.transformers import TransformersZeroShotDocumentClassifier
    from haystack_integrations.components.routers.transformers import TransformersTextRouter
    from haystack_integrations.components.routers.transformers import TransformersZeroShotTextRouter
    # HuggingFaceLocalChatGenerator was renamed to TransformersChatGenerator
    from haystack_integrations.components.generators.transformers import TransformersChatGenerator
    # NamedEntityExtractor (Hugging Face backend) was renamed to TransformersNamedEntityExtractor
    from haystack_integrations.components.extractors.transformers import TransformersNamedEntityExtractor
    # ExtractiveReader was renamed to TransformersExtractiveReader
    from haystack_integrations.components.readers.transformers import TransformersExtractiveReader
  • LocalWhisperTranscriber and RemoteWhisperTranscriber are deprecated and will be removed from Haystack in version 3.0. They are moving to the whisper-haystack package. To continue using them, install the package with pip install whisper-haystack and update your imports as follows:

    from haystack_integrations.components.audio.whisper import LocalWhisperTranscriber
    from haystack_integrations.components.audio.whisper import RemoteWhisperTranscriber

🔒 Security Notes

  • Strengthened the template-variable sanitization in ChatPromptBuilder to cover cases where specially crafted template variables could still be interpreted as structured content (e.g., images, tool calls) instead of plain text. Protection now relies on two independent mechanisms.
  • Fixed a path-traversal issue in DocumentToImageContent and LLMDocumentContentExtractor. When root_path is set, a file_path in document metadata that escapes the root (via ../ or an absolute path) now raises a ValueError before any file is read, preventing attacker-controlled metadata from reading arbitrary files on the host.
  • The repr() of a Secret built via Secret.from_token(...) no longer includes the raw token. Previously the dataclass-generated repr printed the token verbatim, so any path that formats a component's locals — exception tracebacks, print(), Jupyter echo, log aggregators — could leak the API key. The token is now rendered as <redacted>.

🐛 Bug Fixes

  • Six retriever components (CogneeRetriever, FAISSEmbeddingRetriever, FalkorDBCypherRetriever, FalkorDBEmbeddingRetriever, MongoDBAtlasFullTextRetriever, ValkeyEmbeddingRetriever) were missing from the retriever index table in both current and versioned docs.
  • DocumentJoiner now raises a clear ValueError when the provided weights sum to zero, instead of failing with an opaque ZeroDivisionError while normalizing them.
  • Fixed document_matches_filter raising a FilterError when comparing a string date in Document.meta against a datetime object using ordering operators (>, >=, <, <=).
  • Metadata filters that use an unsupported operator now raise a clear FilterError that lists the valid operators, instead of a cryptic KeyError. For example, a typo such as {"field": "meta.year", "operator": "gt", "value": 2000} (instead of ">") previously surfaced as KeyError: 'gt'. This applies to both comparison operators and logical operators (AND, OR, NOT).
  • Fixed a TypeError in AnswerJoiner when running with sort_by_score=True and at least one answer had a score of None. As documented, an answer without a score is now treated as if its score were negative infinity and sorted last, instead of raising TypeError: '<' not supported between instances of 'float' and 'NoneType'.
  • Fixed a ZeroDivisionError in InMemoryDocumentStore.bm25_retrieval that occurred at query time when every stored document had empty (but not None) content. Such a tokenless corpus has no vocabulary and an average document length of zero, which made all three BM25 algorithms divide by zero. Retrieval now scores every candidate as 0.0 in this case: unscaled BM25Okapi returns the documents with score 0.0, while BM25L and BM25Plus return an empty list (non-positive scores are filtered). Corpora with at least one non-empty document are unaffected.
  • GeneratedAnswer and ExtractedAnswer now serialize to the same flat format as all other Haystack dataclasses (Document, ChatMessage, etc.), removing an inconsistency that caused redundant type metadata to be embedded in pipeline snapshots and State objects. Deserialization is backward compatible: from_dict() still accepts the old {"type": "...", "init_parameters": {...}} format.
  • Fixed the >= and < metadata filter operators returning wrong results for datetime strings that represent the same moment in time but are formatted differently (for example "2025-01-01" vs "2025-01-01T00:00:00", or the same instant expressed with different timezone offsets). Equality was previously checked by raw string comparison instead of comparing the parsed datetimes, so >= evaluated to False and < evaluated to True for equal datetimes. This caused boundary documents to be silently dropped from date range queries in InMemoryDocumentStore and any other component relying on document_matches_filter.
  • Fixed a crash in DocumentJoiner when using join_mode="distribution_based_rank_fusion" with documents that have score=None. The score normalization now treats a missing score as 0 (consistent with how the mean/standard-deviation are computed), instead of raising TypeError: unsupported operand type(s) for -: 'NoneType' and 'float'.
  • DocumentSplitter now raises a clear ValueError at initialization when split_overlap is greater than or equal to split_length. Previously this configuration was accepted and only failed later at run time with an opaque error from the underlying windowing helper (the window step became zero or negative).
  • Fixed FaithfulnessEvaluator and ContextRelevanceEvaluator to exclude failed queries (score nan) from the aggregate mean score instead of silently returning nan. A warning is now logged when one or more queries are skipped.
  • Fixed FileTypeRouter silently routing MIME types containing + (such as image/svg+xml, application/ld+json, and every application/*+xml / application/*+json) into the unclassified bucket. Entries in mime_types were being compiled as regex, so the + was interpreted as a quantifier and the type never matched itself. Each source's MIME type is now checked against mime_types by exact equality first, falling back to the existing regex match — so literal MIMEs route correctly and patterns like audio/.* keep working unchanged.
  • Fixed GeneratedAnswer.from_dict raising an IndexError when the serialized meta contained an empty all_messages list. Deserialization now guards against the empty list (consistent with to_dict) and round-trips correctly.
  • Fixes HuggingFaceLocalGenerator so that when multiple stop_words are configured the generator returns exactly N replies instead of N×M replies. Previously the list comprehension created a cross-product, duplicating replies and leaving some stop words unstripped.
  • Fixed human_in_the_loop confirmation strategies (_run_confirmation_strategies and _run_confirmation_strategies_async) raising an unhandled KeyError when a tool call references a tool name that isn't in the tools list (e.g. the chat model hallucinated it). The call now passes through unmodified to ToolInvoker, which already raises a clean ToolNotFoundException (respecting raise_on_failure) for this case.
  • HTMLToDocument now skips ByteStream objects with empty content instead of passing them to trafilatura. This prevents noisy lxml parse error logs (such as "Document is empty") when a fetcher emits an empty stream.
  • Fixed a silent data-loss bug in Pipeline.connect() where any list[T] -> T connection was accepted without warning (e.g. list[Document] -> Document) and only the first list element was forwarded at runtime, dropping the rest. The conversion is now restricted to inner types str and ChatMessage, matching the original intent; any other list[T] -> T wiring now raises PipelineConnectError at connect() time. As a defense-in-depth measure, the runtime UNWRAP conversion also raises ValueError (wrapped in PipelineRuntimeError) when given a multi-element list, so any future regression of the connect-time gate cannot silently drop items. This also affects e.g. a generator using n>1 whose replies are wired into a single-str or single-ChatMessage consumer — that pipeline now raises instead of silently using replies[0].
  • Fixed LLMEvaluator.to_dict not serializing the raise_on_failure parameter, causing it to silently reset to True after a pipeline round-trip.
  • Fixed InMemoryDocumentStore.load_from_disk reconstructing documents with the plain Document constructor instead of Document.from_dict. Documents containing a blob (ByteStream) or a sparse_embedding (SparseEmbedding) were loaded with those fields as raw dictionaries, which later crashed repr(), to_dict(), equality comparison, save_to_disk of the reloaded store, and any component accessing document.blob.data.
  • Fixed MetaFieldGroupingRanker raising a TypeError when sort_docs_by points to a non-numeric metadata field (for example ISO date strings) and some documents are missing that field. Documents that miss the field are now placed at the end of their group, regardless of the field's type.
  • Add EmbeddingBasedDocumentSplitter to the PreProcessors index page, and add MarkdownHeaderSplitter and SpacyNamedEntityExtractor to the sidebar navigation. These components had documentation pages but were missing from navigation, making them undiscoverable by users browsing the docs.
  • Update parameters in MultiRetriever to allow for more flexible retrieval of documents. Changes include:
    • top_k_per_retriever: Allows specifying the maximum number of documents to return per retriever
    • top_k: Allows specifying the maximum number of documents to be retrieved from combined results of all retrievers
  • Fixed a TypeError when filtering documents on a nested metadata field (e.g. meta.user.age) where an intermediate value is not a dictionary (for example None or a scalar). Such intermediate values are now treated as a missing field (None) instead of raising, which previously crashed filter_documents for stores like InMemoryDocumentStore.
  • Fixed OpenAIResponsesChatGenerator dropping encrypted_content from reasoning items in streaming mode. Two issues were addressed: _convert_streaming_chunks_to_chat_message was discarding all reasoning extras except id and type; and _convert_response_chunk_to_streaming_chunk had no handler for the response.output_item.done event for reasoning items, which is the only event that carries encrypted_content. Both are now fixed, so encrypted_content is preserved end-to-end and multi-turn conversations with store=False and include=["reasoning.encrypted_content"] work correctly.
  • Fixed RecursiveDocumentSplitter silently ignoring split_overlap when none of the configured separators match the input text. In that code path the component fell back to fixed-size chunking but returned the result directly without calling _apply_overlap, so every chunk after the first was missing the expected overlap prefix. The fix applies _apply_overlap to the fallback chunks before returning them, making behaviour consistent with the separator-matched code path. All three split units ("char", "word", and "token") are affected and are now covered by regression tests.
  • Fixed RecursiveDocumentSplitter producing incorrect split_idx_start metadata values when split_unit="word" (or "token") is used together with split_overlap > 0. Previously, self.split_overlap (a word/token count) was subtracted directly from a character-offset counter, causing every chunk after the first to report a wrong start position in the original document. The fix uses the actual character length of the overlap suffix, obtained from _get_overlap(), so the offset is always correct regardless of split_unit.
  • Fixed RecursiveDocumentSplitter not populating the _split_overlap metadata when split_unit is "word" or "token" and split_overlap is greater than 0. The overlap length was computed with a word/token count while the offsets it is compared against are character positions, so it became negative and the overlap metadata was silently dropped. The overlap is now measured in characters, matching the ranges it records.
  • Fixed serialize_type dropping NoneType when it appears as a regular argument of a typing generic. Types such as typing.Dict[str, None], typing.Tuple[int, None], typing.List[None] and unions with more than two members like typing.Union[str, int, None] were serialized incorrectly (for example to typing.Dict[str]), which produced malformed type strings that could not be deserialized again. typing.Optional[X] is still serialized as typing.Optional[X] without a redundant trailing None.
  • Fixed SuperComponent.run and SuperComponent.run_async raising ValueError: The truth value of ... is ambiguous whenever a non-scalar input (numpy arrays, pandas DataFrames/Series, torch tensors, or any object whose __ne__ returns a non-scalar) was passed. The internal sentinel filter is now an identity check (is not _delegate_default) instead of value equality (!= _delegate_default), so it no longer invokes __ne__ on user-provided values.
  • Fixed two bugs in TopPSampler that silently disabled filtering:
    • Documents with integer scores were treated as if they had no score at all, so the sampler logged a warning and returned all documents unfiltered. Integer scores are now handled like float scores (booleans are still rejected).
    • Passing top_p=0.0 to run() was silently replaced by the value set in the constructor because of a falsy-value check. The override is now honored, returning the single highest-scoring document, consistent with TopPSampler(top_p=0.0).
  • Fix InputSocket.__post_init__ crashing with IndexError when a Variadic input uses a bare Iterable without a type argument (e.g. Annotated[Iterable, HAYSTACK_VARIADIC_ANNOTATION]). Now raises ComponentError with a clear message instead of an unhelpful IndexError.
  • Fixed serialization of FallbackChatGenerator to use the standard component_to_dict serialization path. This ensures custom chat generator components without an explicit to_dict method are no longer silently omitted from the serialized chat_generators list.
  • HierarchicalDocumentSplitter now validates its block_sizes and split_overlap parameters at construction time. Previously, an empty block_sizes set silently produced no splits, and a split_overlap too large relative to the smallest block size raised a confusing error referencing split_length, a parameter that doesn't exist on this component. HierarchicalDocumentSplitter now raises a clear ValueError in both cases, using its own parameter names and identifying the offending value.
  • HTMLToDocument and MarkdownToDocument now accept an encoding parameter (default "utf-8") and honour ByteStream.meta["encoding"] at run time, mirroring the behaviour of TextFileToDocument. Previously both converters hardcoded decode("utf-8"), causing UnicodeDecodeError for non-UTF-8 sources whose encoding was supplied via ByteStream metadata.
  • Fixed InMemoryDocumentStore.embedding_retrieval producing NaN cosine similarity scores when a document (or the query) had a zero-norm embedding. Zero-norm vectors are now handled without dividing by zero, so such documents receive a score of 0.0 instead of NaN (which silently corrupted ranking).
  • Preserve falsy metadata values such as 0, False, and empty strings when meta_fields_to_embed is used by sentence-transformer embedders and rankers.
  • QueryExpander now ignores malformed responses where queries is not a list, so a string response is no longer split into single-character queries.

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

@Aarkin7, @Amnah199, @anakin87, @ATOM00blue, @axelray-dev, @Ayushhgit, @bilgeyucel, @bogdankostic, @camgrimsec, @charliesheh, @davidsbatista, @ErenAta16, @Goodnight77, @GovindhKishore, @greymoth-jp, @gyx09212214-prog, @hoangsonww, @HotThoughts, @i-anubhav-anand, @immuhammadfurqan, @jdoughty04, @jialiuyang, @julian-risch, @Kunal-Somani, @milljer, @mustafakhokhar, @NIK-TIGER-BILL, @Osamaali313, @oyinkanchekwas, @rautaditya2606, @ritikraj2425, @RitwijParmar, @santino18727-debug, @shr8769, @sjrl, @spideyashith, @Srivatsa03, @SyedShahmeerAli12, @vedjaw, @vidigoat

Don't miss a new haystack release

NewReleases is sending notifications on new releases.