⭐️ Highlights
🧭 Classify and Route with the OpenAI Decisions API
Three new components use the OpenAI Decisions API to make choices about your data without writing a prompt. 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
openaiSDK version from2.6.0to3.26.0to support the Decisions API. -
Declared
pydantic>=2.12(>=2.14on Python 3.15) as the minimum supported version. pydantic releases before 2.11 generate different JSON schemas for tool parameters increate_tool_from_function/@tool,ComponentTool, andPipelineTool. -
If you don't set
keep_default_naorna_valuesinread_excel_kwargs,XLSXToDocumentnow 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 as1instead of1.0. To restore the previous behavior, passread_excel_kwargs={"keep_default_na": True}. -
Components with a
Variadicinput, or alistinput connected to several senders, now receive the values in the order the senders were connected withPipeline.connect(). Before,Pipeline.run()passed them in the order the senders ran, so the results of order-dependent components such asDocumentJoinerwithweightscan change: connect the senders in the order you want the values to arrive.If you load pipelines from YAML, check the order of your
connectionsentries. Pipelines saved before this release listconnectionsgrouped 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, ifembedding_retrieveris listed beforebm25_retrieverinconnections,DocumentJoinernow appliesweights[0]to the embedding retriever, where before it could apply it to the BM25 retriever. Reorder the entries that feed aVariadicor multi-senderlistsocket so the senders come in the order you expect. -
The default model of
OpenAIChatGeneratorandOpenAIResponsesChatGeneratorchanged fromgpt-5-minitogpt-5.6-terra, the replacement OpenAI recommends. OpenAI shuts downgpt-5-minion December 11, 2026.LLMEvaluator,ContextRelevanceEvaluator, andFaithfulnessEvaluatoruse the new default too.Pipelines serialized before this change store
model: gpt-5-miniand keep using it. Update themodelvalue in their YAML before December 11, 2026. To keepgpt-5-miniuntil then, pass it explicitly:OpenAIChatGenerator(model="gpt-5-mini"). -
If you read
id,encrypted_content, orstatusdirectly fromreply.reasoning.extrainOpenAIResponsesChatGeneratorreplies, read them from entries inreply.reasoning.extra["reasoning_items"]instead. For example, replacereply.reasoning.extra["id"]withreply.reasoning.extra["reasoning_items"][0]["id"]for the first item, or iterate over the list to handle all reasoning items. -
OpenAIChatGeneratorandAzureOpenAIChatGeneratornow request token usage metadata by default when streaming. To restore the previous behavior, passgeneration_kwargs={"stream_options": {"include_usage": False}}. -
max_agent_stepsis now a reserved state key onAgent. If you defined a customstate_schemakey namedmax_agent_steps, rename it (e.g.my_max_agent_steps): the Agent now raises aValueErrorat initialization when a reserved key is redefined.
🚀 New Features
-
Add the optional
multivector_embeddingfield toDocumentto 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
MaxSimRankerto 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, thenMaxSimRankerperforms a more fine-grained comparison of the query and each candidate's content to return the most relevant documents. -
Add
OpenAIDecisionsDocumentClassifierto answer choice, score, and predicate questions about documents using the OpenAI Decisions API. Answers are stored in document metadata. -
Add
OpenAIDecisionsImageRouterto 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
OpenAIDecisionsTextRouterto route text to named categories using the OpenAI Decisions API. -
Added
StructuredOutputHooktohaystack.hooks.structured_output. Registered underon_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 thestructured_outputkey by default. The key must be declared in the Agent'sstate_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_retriestimes (2 by default). When the reply still can't be parsed, or the Agent stops on another exit condition,Noneis 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): 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'}
-
DocumentJoinernow supportsjoin_mode="merge_embeddings"to combine theembeddingandsparse_embeddingfields of documents with the same ID. This allows dense and sparse document embedders to run in parallel and their outputs to be combined before passing them toDocumentWriter. -
Added
StepBudgetReminderHook(experimental), abefore_llmhook that tells the model how many steps it has left once an Agent run gets close tomax_agent_steps. Before each of the lastremind_within_stepsLLM calls, it adds a user message wrapped in<system-reminder>tags, such as "You have 2 of 10 steps remaining", and on the last step asks for the final answer. This lets the Agent wrap up instead of being cut off mid-investigation.from haystack.hooks.budget import StepBudgetReminderHook agent = Agent( chat_generator=OpenAIChatGenerator(model="gpt-5.4-nano"), tools=[search], max_agent_steps=20, hooks={"before_llm": [StepBudgetReminderHook(remind_within_steps=3)]}, )
Reminder messages are marked with
meta={"system_reminder": True}. Context compaction recognizes the marker and never treats a reminder as the user's task.
⚡️ Enhancement Notes
- Added the missing
delete_by_filter_async()method toInMemoryDocumentStore. Previously, only the syncdelete_by_filter()was available, unlike the other methods of the store, which all have an async counterpart. The new method runsdelete_by_filter()in the store's thread-pool executor and returns the number of deleted documents. - The default
modify_templateofBlockingConfirmationStrategynow tells the LLM that the user wants the result of the modified tool call used and that it should not call the tool again with the original arguments. With the previous wording, models often repeated the original tool call after a user modified its parameters. Passmodify_templateexplicitly to keep the previous message. DocumentJoiner(join_mode="merge_embeddings")now supports multivector embeddings in addition to dense and sparse embeddings when combining documents with the same ID.OpenAIChatGeneratornow includes several methods to make it easier for subclasses to customize the component's behavior while reducing code duplication.- Deep copies of
Documentobjects and pipeline inputs are now faster, with the same results as before.Documentnow implements__deepcopy__, which copies theembeddinglist in one step instead of element by element.Pipelinealso returns immutable values such as strings and numbers unchanged when copying component inputs, without running the more expensive checks meant for other objects. Pipelines that pass embeddings or many embedded documents between components benefit most. EvaluationRunResult.comparative_detailed_reporthas a newalign_onparameter to match the rows of the two runs by an input column, such as a question ID or the question itself, instead of by position. This compares runs correctly even if they evaluated the same questions in a different order, or not all of the same questions. Rows without a match are kept withNonefor the other run.HierarchicalDocumentSplitternow supportssplit_by="token"(requirespip install tiktoken), with the encoding set by the newtokenizer_encodingparameter (default"o200k_base"). A newwarm_up()method loads the tokenizer up front instead of on the firstrun().PPTXToDocumentnow extracts text from grouped shapes and tables, which were previously skipped. Tables are included in the document content as CSV.OpenAIChatGeneratorandOpenAIResponsesChatGeneratornow preserve OpenAI API refusals inChatMessage.meta["refusal"]. Applications can inspect this metadata to detect and handle refusals.OpenAIResponsesChatGeneratornow preserves text annotations inreply.meta["annotations"], with alignedannotation_offsetsfor locating citations in the concatenated reply text.- Add a
tokenizer_encodingparameter toRecursiveDocumentSplitterto choose the tiktoken encoding used whensplit_unit="token", as inDocumentSplitter. It defaults to"o200k_base", the encoding used until now. Set it to match your model, for example"cl100k_base"for OpenAI'stext-embedding-3models, so that chunks fit the model's token limit. RecursiveDocumentSplitternow validatessplit_unitat initialization, raising aValueErrorfor values other than"word","char", or"token"instead of failing later duringrun().- Simplify ChatMessage serialization and OpenAI format conversion for improved readability and maintainability.
- Hooks can now read the Agent's step limit from the internal
max_agent_stepsstate key, for example withstate.get("max_agent_steps"). It is not returned as an Agent output.
🐛 Bug Fixes
-
Fixed
Agentlosing token counts intoken_usagewhen a later LLM call reports a usage field asnull, for exampleprompt_tokens_detailsreported on one call andnullon the next. Anullvalue now counts as not reported, so the totals no longer depend on the order of the calls. -
FilterRetriever,AutoMergingRetriever,SentenceWindowRetriever,CacheCheckerandDocumentWriternow work with document stores that implement only the synchronous API. Theirrun_asyncmethods use the store's async method when it exists and otherwise run the synchronous one in a worker thread, instead of failing on the missing async method. -
Fix
AutoMergingRetriever.run()andrun_async()when retrieved leaves fromHierarchicalDocumentSplitterhave different depths. Shallower leaves now merge together with siblings promoted from deeper branches, preventing missed parent merges and duplicate parent documents in the retrieved context. -
ComponentToolnow skips unknown invocation arguments with a warning instead of raisingKeyError, including extra properties in custom schemas and arguments added by the LLM. Custom schemas with unknown properties also produce a warning during construction. AValueErroris raised when a custom schema omits mandatory component inputs not provided throughinputs_from_state. Automatically injectedStateinputs are excluded. This also applies toAgentToolwhen a customparametersschema omitsmessagesand it is not provided throughinputs_from_state. Previously, construction succeeded but invocation failed because the required input was missing. -
ImageContent.from_dict,FileContent.from_dictandTextContent.from_dictnow build the calling class instead of the base class, so a subclass survives ato_dict/from_dictround trip. This matches the behaviour ofByteStream.from_dict. -
Fixed
DocumentCleanerremoving indentation and newlines at the start and end of every page whenremove_regexwas set, even on pages where the regex matched nothing. Pages without a match are now left unchanged, which preserves Markdown and code content whenremove_extra_whitespaces,remove_empty_linesandstrip_whitespacesare disabled. Pages where the regex matches are still stripped as before. -
DocumentJoinernow raises aValueErrorwhenweightscontains NaN or infinite values. Previously, these values were accepted and turned the document scores into NaN in themergeandreciprocal_rank_fusionjoin modes. -
DOCXToDocumentnow reads text that a paragraph keeps inside a tracked insertion, moved text, a smart tag, custom XML, a simple field or a filled-in inline content control. Previously only a paragraph's direct runs and hyperlinks were read, so a document with unaccepted tracked changes lost every inserted word, and a filled form field lost its value. Deleted text, the old place of moved text and content controls that still show their placeholder prompt are left out, as the document does not show them. -
DocumentRecallEvaluatorandDocumentNDCGEvaluatornow support list and dictionary metadata values indocument_comparison_fieldinstead of raisingTypeError. Nested values are supported, and duplicate documents retain the existing recall and relevance scoring behavior. -
FileTypeRouterno longer loses the detected MIME type whenmetais passed alongside file paths. -
Fix
AutoMergingRetrieverincorrectly counting duplicate child documents when evaluating merge thresholds. Documents are now deduplicated by ID before merging, retaining the highest-scoring copy in both synchronous and asynchronous retrieval. -
EvaluationRunResult.comparative_detailed_reportno longer drops the first run's scores when both runs have the samerun_name. Their columns are named with_firstand_secondagain, as they were before the report stopped using pandas. Whenalign_onisn't set and the two runs have a different number of rows, it logs a warning and pads the shorter run withNoneinstead of returning columns of different lengths. -
DocumentToImageContentnow skips an image file that cannot be decoded instead of failing the wholerun()call. The skipped document getsNoneinimage_contentsand a warning is logged. -
ImageFileToImageContentnow skips a source that is not a valid image instead of failing the wholerun()call. A non-image file in a mixed batch, such as a.txtfile, is logged and omitted fromimage_contents, while the other sources are still converted. -
Fixed
ListJoiner.from_dictmutating the caller'sdatain place: the serializedlist_type_string was replaced with the deserialized type, so a secondfrom_dictcall on the same dictionary failed and the dictionary could no longer be dumped to JSON or YAML.from_dictnow works on a copy and the caller's data is left untouched. -
TiktokenCounterno longer raisesValueErrorwhen message or tool content contains literal special-token strings such as<|endoftext|>. They are now encoded as ordinary text and counted, consistent withDocumentSplitterandRecursiveDocumentSplitter. -
Fix
LLMEvaluatorinput validation to reject malformed tuples and non-list types withValueError. -
LLMRankernow skips response indices that do not represent valid integers. -
Fixed
MetadataRouter.from_dictmutating the caller's serialized dictionary in place, which made a secondfrom_dictcall on the same dictionary fail. -
Fix
MSGToDocumentincorrectly including Cc and Bcc recipients in the To line. To, Cc and Bcc now use transport headers when available, falling back to Outlook recipients when headers are missing or blank. -
ChatMessage.from_openai_dict_formatnow accepts an empty string ascontentfor user, system, developer, and tool messages instead of raisingValueError. Messages such asChatMessage.from_tool(tool_result="", ...)orChatMessage.from_user(text="")now round-trip throughto_openai_dict_format. -
Ensures
PythonCodeSplitteremits syntactically valid chunks whenstrip_docstrings=Trueis used on a function or class whose docstring is its only statement. -
Fixed an issue in
PythonCodeSplitterwhere form feed characters (\f) and certain Unicode separators could cause functions to be split incorrectly across chunks. Chunk boundaries and thestart_line/end_linemetadata now remain correct when these characters appear in the source. -
DocumentMRREvaluator,DocumentMAPEvaluatorandDocumentNDCGEvaluatorno longer count an empty comparison value as a match. Previously, when thedocument_comparison_fieldwas an empty string on both a ground truth and a retrieved Document, for examplemeta.file_id="", unrelated Documents were scored as relevant and could get a perfect score.DocumentRecallEvaluatoralready ignored empty values. With the defaultcontentfield, this also means a ground truth Document withcontent=""is now excluded.DocumentMRREvaluatorandDocumentMAPEvaluatornow log a warning when none of the ground truth Documents for a question has a valid comparison value, likeDocumentRecallEvaluatordoes. -
RecursiveDocumentSplitterwithsplit_unit="word"no longer counts a whitespace-only piece, such as a page break (\f) or a tab between two spaces, as a word when measuring chunks againstsplit_length. The word-mode fixed-size fallback already skipped them, so it produced chunks that the rest of the splitter measured as longer thansplit_length, and chunks split at separators held fewer real words thansplit_lengthwhenever they contained a page break. Withsplit_overlap, such a piece could also be taken as the overlapping word.This changes the chunk boundaries for text containing page breaks or other whitespace surrounded by spaces. Re-split and re-index documents if you rely on stable chunk boundaries.
-
OpenAIResponsesChatGeneratorandAzureOpenAIResponsesChatGeneratornow accept chat histories with assistant messages produced by other providers, for example whenFallbackChatGeneratorswitches fromAmazonBedrockChatGenerator. Reasoning without a Responses API reasoning itemidis dropped instead of being sent as an invalid input item, and tool calls without acall_idinextrause the tool callidascall_id. Previously, the API rejected these requests with a 400 error. -
SASEvaluatornow normalizes Cross Encoder scores based on the model instead of the scores in the batch. Previously, raw logits were only passed through a sigmoid if at least one score in the batch was above 1, so the same answer could get a different score depending on the other answers in the batch, and batches of only wrong answers returned negative scores. This affected models that return raw logits, such ascross-encoder/ms-marco-MiniLM-L-6-v2. -
Fixed
DocumentSplittersentence-boundary overlap keeping one extra sentence when a sentence already contained exactlysplit_overlapwords. The next chunk now stops at that word count instead of pulling in the sentence before it. -
SlidingWindowCompactorno longer always removes the summary aSummarizationCompactorleaves in the current task. The summary is now kept or removed together with the Agent step after it. A summary at the start of history is now treated as the oldest historical turn. -
Fixed
StructuredOutputHookfailing to parse JSON wrapped in Markdown code fences using CRLF line endings. Fenced replies using either LF or CRLF line endings are now stripped before structured-output parsing. -
Fixed an issue where
XLSXToDocumentdropped literal text values such as"NA"and"N/A", converting them into empty cells. When neitherkeep_default_nanorna_valuesis supplied inread_excel_kwargs, the converter useskeep_default_na=Falseandna_values=[""]to preserve literal text while still recognizing empty cells as missing. -
Fixed
XLSXToDocumentattaching hyperlinks to the wrong cells whenread_excel_kwargsincludesskiprows,usecols, ornames. -
HierarchicalDocumentSplitternow setspage_numberandsplit_idx_startrelative to the original document for documents below the first level. Previously they were counted from the start of the parent block, so deeper leaves reported the wrong page. -
InMemoryDocumentStore.bm25_retrieval()andembedding_retrieval(), and their async variants, now raise aValueErrorfor a negativetop_k. Previously, a negative value silently dropped documents from the end of the results, for exampletop_k=-1returned 9 of 10 matches. -
In
JSONConverter, reject anextra_meta_fieldsvalue that is a string other than the literal'*'at component creation. Previously such a string was silently iterated character by character, producing meaningless metadata keys and dropping the intended fields from the converted documents. -
PythonCodeSplitterwithstrip_docstrings=Trueno longer drops code that follows a docstring on the same line, such as"""Docs."""; return 5. Previously the whole line was removed, silently deleting the statement and possibly leaving a chunk that fails to parse. In this case the docstring is now kept in the chunk content. -
Pipeline.run_async()passed the values of aVariadicinput, or alistinput connected to several senders, in the order the senders finished, which could change between runs, soDocumentJoinercould apply itsweightsto the wrong retriever. Values now arrive in connection order, as inPipeline.run(), with pipeline inputs passed directly to such a socket first.Pipeline.to_dict()now saves connections in the order they were made, so a pipeline loaded withPipeline.from_dict()keeps the same order. -
LinkContentFetcherno longer adds atext/plainentry to itshandlersdictionary when it fetches content whose type matches no registered pattern (for examplefont/wofformultipart/form-data). Such content is still handled as text, as before. -
LLMnow declaresexit_reasonas an output, so it can be connected to other components in a Pipeline. It is one of"text"(a complete reply),"length"(the reply was cut off by the token limit) or"content_filter"(the reply was cut off by the provider's content filter), taken from the chat generator'sfinish_reason. For example, you can route a truncated reply to a retry with a highermax_tokens. -
ConfirmationHooknow marks the user message it adds when the user modifies a tool call's arguments withmeta={"system_reminder": True}.SlidingWindowCompactorandSummarizationCompactortook that message for the user's task, so compaction could remove the real request. They now skip it when finding the task.StructuredOutputHookmarks its correction messages the same way. -
MarkdownHeaderSplitterno longer includes the optional closing sequence of an ATX header in theheaderandparent_headersmetadata. For example,## Install ##now produces the headerInstallinstead ofInstall ##. The chunk content is unchanged. -
MarkdownHeaderSplitternow follows the CommonMark rules for fenced code blocks, so#lines inside them are no longer split off as headers. A block can now close with a longer fence than the one that opened it, fences may be indented by up to three spaces, and a block that is never closed runs to the end of the document. A line that starts with inline code, where more backticks follow the opening ones on the same line, is not treated as a fence. -
MarkdownHeaderSplitternow recognizes ATX headers with up to three leading spaces, preserving section boundaries and header hierarchy metadata for indented Markdown headers. Lines indented by four or more spaces remain ordinary content. -
MarkdownToDocumentwithtable_to_single_line=Truenow separates table cells with spaces and tables from surrounding content with newlines. Previously, cell contents and consecutive tables were concatenated without separators, losing word boundaries. -
MetaFieldRankernow rejects aNaNweight in its constructor and inrun()with aValueErrorinstead of returning documents withNaNscores. -
MultiRetrievernow raises aValueErrorat initialization ifjoin_modeis not"concatenate"or"reciprocal_rank_fusion". Previously, unsupported values such as"rrf"were silently treated as"concatenate"whentop_kwas not set. -
OpenAIChatGeneratornow works with tools ongpt-5.6models,gpt-6-sol, andgpt-6-luna. Over the Chat Completions API, these models accept tools only withreasoning_effort="none"and returned HTTP 400 when tools were sent without it. When a request to one of these models includes tools andgeneration_kwargsdoesn't setreasoning_effort,OpenAIChatGeneratornow sendsreasoning_effort="none", so these requests run without reasoning. To combine reasoning with tools, useOpenAIResponsesChatGenerator. -
OpenAIResponsesChatGeneratorandAzureOpenAIResponsesChatGeneratornow raise aRuntimeErrorwhen a streamed response fails, that is when the stream ends with aresponse.failedevent or contains anerrorevent. Previously these events were ignored and the generator returned an empty reply without a finish reason, so the failure went unnoticed. -
ByteStream.from_dict()now preserves subclasses when deserializing a dictionary, instead of always returning aByteStreaminstance. -
MockChatGenerator.from_dict()now preserves the supplied serialized configuration, so the same dictionary can be loaded more than once when using predefined responses, a response function, or a streaming callback. -
Pipeline.run()no longer changes the visit counts stored in aPipelineSnapshotwhen resuming it. Replaying the same snapshot or retrying after a downstream failure now preserves the saved state and does not incorrectly exhaustmax_runs_per_component. -
Preserve field text, such as slide numbers, in
PPTXToDocumentwhenlink_formatis"markdown"or"plain". Field values in text boxes and table cells are kept in their original position alongside text and links. -
Preserve all reasoning items returned by
OpenAIResponsesChatGeneratorin both streaming and non-streaming responses, including each item's ID and encrypted content. Individual items are stored in a singleReasoningContent.extra["reasoning_items"]list. Reasoning items and function calls retain their original relative order when replayed in a multi-turn conversation. Previously serialized single-item reasoning metadata remains supported. -
PythonCodeSplitternow keeps decorators with the methods or classes they belong to, without copying them to other chunks. This also works with multiline decorators. -
MetaFieldRankernow treats aNaNrelevance score as out of range inlinear_scoremode. It logs the existing warning and defaults the score to 0 instead of sorting the document by aNaNscore. -
create_tool_from_functionand the@tooldecorator now raiseValueErrorwhen a required parameter name starts with an underscore, such as_tenant, and that parameter is not listed ininputs_from_state. Those names are not exposed to the LLM, so a tool created without the value could not be called. Provide the parameter throughinputs_from_stateor give it a default. Underscore-prefixed parameters that already have a default stay omitted from the schema. -
Fix
MultiQueryEmbeddingRetriever,MultiQueryTextRetriever, andTextEmbeddingRetrieversorting so documents without a score are placed after all scored documents, including documents with negative scores. -
Runtime streaming callbacks now correctly override initialization callbacks, avoiding errors and warnings caused by unused callbacks. Callback selection also correctly handles callable objects that evaluate to false.
-
SearchableToolsetnow rejects catalog tools that share the bootstrap search tool name when the catalog has at leastsearch_thresholdtools. Previously, discovering such a tool produced duplicate names and failed the Agent run. Setsearch_tool_nameto a different name to use that catalog. -
create_tool_from_functionandComponentToolnow exclude callback parameters wrapped intyping.Annotatedfrom their tool schemas, including optional callbacks. Annotating a callback no longer causes tool construction to fail withSchemaGenerationError; the callback's default remains available when the tool is invoked. -
ImageContent.from_file_path()now raises aValueErrornaming the file when it cannot be converted to an image, for example because it does not exist or is not an image. Previously it raised an unhelpfulIndexError: list index out of range. -
SlidingWindowCompactorno longer removes system reminders from the current task, such as the onesStepBudgetReminderHookadds. Before, every compaction removed them, including a reminder added for the LLM call about to happen, which the model then never saw. Now a reminder is kept or removed together with the next assistant message and its tool results, and reminders after the last assistant message are always kept. -
Fix
StructuredOutputHookfor list-typed state keys, such as{"type": list[str]}. The hook now replaces the state value with the validated result, orNoneon failure, instead of appending to it. Previously, a failed attempt followed by a successful retry returned[None, "Paris"]instead of["Paris"]. -
StructuredOutputHooknow honorsmax_retrieswhenStepBudgetReminderHook(or another hook addingsystem_remindermessages) is registered. The hook counts its corrections since the last user message, and it treated each reminder as a new user turn, so the count reset every step: the Agent kept asking for corrections untilmax_agent_stepsand stopped withexit_reason="max_agent_steps". Reminders are now skipped when counting. -
create_tool_from_functionand the@tooldecorator now leave*argsand**kwargsout of the Tool's parameters schema. Before, they were added as required parameters under their own names, such asargsandkwargs, and the LLM was asked to fill them. A value the LLM sent for*argsmade the tool call fail withToolInvocationError, and a value for**kwargsreached the function nested under the parameter's name. An untyped*argsor**kwargsno longer raises a missing type hintValueError. -
Toolnow rejects non-emptyinputs_from_statemappings for tools that take no inputs. The error is raised when the tool is created instead of when it is invoked. If the callable's signature cannot be inspected, parameter names ininputs_from_stateare not checked at construction time. -
Fix
FileSystemToolResultStorealtering line endings in text results on Windows. -
Pipeline.connectnow accepts aUnionoutput connected to aUnioninput when each member of the output fits a member of the input, in any order. For example,str | Noneconnects tostr | int | None, andlist[Path | ByteStream]connects to thelist[str | Path | ByteStream]input of converters such asTextFileToDocument. Before, the members were compared by position, and these connections raisedPipelineConnectError.Literaltypes are matched the same way:Literal["b", "c"]now connects toLiteral["a", "b", "c"]. -
OpenAIDocumentEmbedderandAzureOpenAIDocumentEmbeddernow checkbatch_sizewhen the component is created and raise aValueErrorif it is not a positive integer, such as0,-1, or0.5(for exampleValueError: batch_size must be a positive integer, but got 0). Before this fix, an invalidbatch_sizewas accepted without any error. The problem only appeared later, whenrunorrun_asyncwas called, with errors such asValueError: n must be at least onethat do not mentionbatch_size. -
MultiRetriever,MultiQueryTextRetriever,MultiQueryEmbeddingRetriever,LLMMetadataExtractorandLLMDocumentContentExtractornow raise aValueErrorat initialization whenmax_workersis smaller than 1. Previously the invalid value was accepted,runfailed later insideThreadPoolExecutor, andrun_asyncsilently treated it as 1.
💙 Big thank you to everyone who contributed to this release!
@Aditya17-bot, @Afloat16, @amarthya-14, @anakin87, @anh-nguyenn, @aryamangodara, @Aurantium-47, @bogdankostic, @BugBuster18, @chrikrah, @D1822787566, @davidsbatista, @desty, @Dhruv-Patil-1720, @Elias-Sun, @Elonon901001, @emme1t, @erdogan-deniz, @Eryx-Z, @gauravch-code, @geyazheng, @Guo-Yixin, @Harbor404, @HareeshDaxton, @HarshitR2004, @hazelxue, @hermanrous, @hessamhzDS, @its-amann, @JackyHsu-X, @jaeminbird, @jeevan1098, @JimmyWang0417, @julian-risch, @kerthans, @kun654, @L4XB, @lalith0407, @lester076, @linhongyu510, @MALLADISARMA, @mengchen-atx, @mittalpk, @Naeemeh146, @nik13, @NotZhu, @pcbeingused333, @pei711, @porkedchop, @priy-anshugupta, @Quantum-Enigma-dot, @Revinand, @senthurapandi2000, @shivsin25, @shuaiyafei, @simin-yuan, @sjrl, @Smilewithoutfalling, @solitarybear, @Starchaser1024, @SuhrudhC, @sunhouy, @Tanishq-Choudhary, @TemitopeKadri, @Vedant-Agarwal, @Winoren, @wozhenbang2004, @Ytq1115, @yuv009