PythonCodeSplitter + string ChatGenerators + async retrievers
haystack
LLM FrameworksAn open-source AI orchestration framework for building production-ready LLM applications and RAG systems in Python
Features
- Design modular pipelines and agent workflows with explicit control over retrieval, routing, memory, and generation
- Build scalable Retrieval-Augmented Generation (RAG) systems and multimodal applications
- Enable semantic search, question answering, and autonomous agents in a transparent architecture
Recent releases
View all 13 releases →- LLM component now requires user_prompt parameter with at least one Jinja2 template variable
- Agent.run() and Agent.run_async() now require messages as explicit argument
- request_with_retry and async_request_with_retry raise httpx.HTTPError instead of requests.exceptions.RequestException
- Tools and components can declare State parameter in signature to receive live agent State object at invocation
- MarkdownHeaderSplitter now supports header_split_levels parameter and ignores headers in code blocks
- Added run_async method to LLMMetadataExtractor enabling concurrent ChatGenerator requests
Full changelog
Release Notes
v2.28.0
Upgrade Notes
-
As part of the migration from
requeststohttpx,request_with_retryandasync_request_with_retry(inhaystack.utils.requests_utils) no longer raiserequests.exceptions.RequestExceptionon failure; they now raisehttpx.HTTPErrorinstead. This also affectsHuggingFaceTEIRanker, which relies on these utilities. Users catchingrequests.exceptions.RequestExceptionshould update their code to catchhttpx.HTTPError. -
The
LLMcomponent now requiresuser_promptto be provided at initialization and it must contain at least one Jinja2 template variable (e.g.{{ variable_name }}). This ensures the component always exposes at least one required input socket, which is necessary for correct pipeline scheduling.required_variablesnow defaults to"*"(all variables inuser_promptare required), and passing an empty list raises aValueError.If you are affected: update any code that instantiates
LLMwithout auser_prompt, or with auser_promptthat has no template variables, to include at least one variable.Before:
llm = LLM(chat_generator=OpenAIChatGenerator(), system_prompt="You are helpful.")After:
llm = LLM( chat_generator=OpenAIChatGenerator(), system_prompt="You are helpful.", user_prompt='{% message role="user" %}{{ query }}{% endmessage %}', ) -
Agent.run()andAgent.run_async()now requiremessagesas an explicit argument (no longer optional). If you were relying on the defaultNonevalue in Haystack version 2.26 or 2.27, pass an empty list instead:agent.run(messages=[], ...)LLM.run()andLLM.run_async()are unaffected — they still acceptNoneand default to an empty list internally.
New Features
-
Tools and components can now declare a
State(orState | None) parameter in their signature to receive the live agentStateobject at invocation time — no extra wiring needed.For function-based tools created with
@toolorcreate_tool_from_function, add astateparameter annotated asState:from haystack.components.agents import State from haystack.tools import tool @tool def my_tool(query: str, state: State) -> str: """Search using context from agent state.""" history = state.get("history") ...For component-based tools created with
ComponentTool, declare aStateinput socket on the component'srunmethod:from haystack import component from haystack.components.agents import State from haystack.tools import ComponentTool @component class MyComponent: @component.output_types(result=str) def run(self, query: str, state: State) -> dict: history = state.get("history") ... tool = ComponentTool(component=MyComponent())In both cases
ToolInvokerautomatically injects the runtimeStateobject before calling the tool, andState/Optional[State]parameters are excluded from the LLM-facing schema so the model is not asked to supply them.This is an alternative to the existing
inputs_from_stateandoutputs_to_stateoptions onToolandComponentTool, which map individual state keys to specific tool parameters and outputs declaratively. Injecting the fullStateobject is more flexible and useful when a tool needs to read from or write to multiple keys, but it couples the tool implementation directly toState.
Enhancement Notes
- Clarify in the Markdown-producing converter documentation that
DocumentCleanerwith its default settings can flatten Markdown output, and update the example pipelines forPaddleOCRVLDocumentConverter,MistralOCRDocumentConverter,AzureDocumentIntelligenceConverter, andMarkItDownConverterto avoid routing Markdown content through the default cleaner configuration. - Made
_create_agent_snapshotrobust towards serialization errors. If serializing agent component inputs fails, a warning is logged and an empty dictionary is used as a fallback, preventing the serialization error from masking the real pipeline runtime error. - Standardize HTTP request handling in Haystack by adopting
httpxfor both synchronous and asynchronous requests, replacingrequests. Error reporting for failed requests has also been improved: exceptions now include additional details alongside the reason field. - Add
run_asyncmethod toLLMMetadataExtractor.ChatGeneratorrequests now run concurrently using the existingmax_workersinit parameter. MarkdownHeaderSplitternow accepts aheader_split_levelsparameter (list of integers 1–6, default all levels) to control which header depths create split boundaries. For example,header_split_levels=[1, 2]splits only on#and##headers, merging content under deeper headers into the preceding chunk.MarkdownHeaderSplitternow ignores#lines that appear inside fenced code blocks (triple-backtick or triple-tilde), preventing Python comments and other hash-prefixed lines in code from being misidentified as Markdown headers.- Expand the
PaddleOCRVLDocumentConverterdocumentation with more detailed guidance on advanced parameters, common usage scenarios, and a more realistic configuration example for layout-heavy documents.
Bug Fixes
-
Fix
ToolInvoker._merge_tool_outputssilently appendingNoneto list-typed state when a tool'soutputs_to_statesource key is absent from the tool result. This is a common scenario withPipelineToolwrapping a pipeline that has conditional branches where not all outputs are always produced even if defined inoutputs_to_state. The mapping is now skipped entirely when the source key is not present in the result dict. -
When using the MarkdownHeaderSplitter, in the split chunks, the child header previously lost its direct parent header in the metadata. Previously if one executed the code below:
from haystack.components.preprocessors import MarkdownHeaderSplitter from haystack import Document text = """ # header 1 intro text ## header 1.1 text 1 ## header 1.2 text 2 ### header 1.2.1 text 3 ### header 1.2.2 text 4 """ document = Document(content=text) splitter = MarkdownHeaderSplitter( keep_headers=True, secondary_split="word" ) result = splitter.run(documents=[document])["documents"] for doc in result: print(f"Header: {doc.meta['header']}, parent headers: {doc.meta['parent_headers']}")We would have expected this output:
Header: header 1, parent headers: [] Header: header 1.1, parent headers: ['header 1'] Header: header 1.2, parent headers: ['header 1'] Header: header 1.2.1, parent headers: ['header 1', 'header 1.2'] Header: header 1.2.2, parent headers: ['header 1', 'header 1.2']But instead we actually got:
Header: header 1, parent headers: [] Header: header 1.1, parent headers: [] Header: header 1.2, parent headers: ['header 1'] Header: header 1.2.1, parent headers: ['header 1'] Header: header 1.2.2, parent headers: ['header 1', 'header 1.2']The error happened when a parent header had its own content chunk before the first child header.
This has been fixed so even when a parent header has its own content chunk before the first child header all content is preserved.
-
Reverts the change that made
Agentmessages optional as it caused issues with pipeline execution. As a consequence, theLLMcomponent now defaults to an empty messages list unless provided at runtime.
💙 Big thank you to everyone who contributed to this release!
@Aftabbs, @Amanbig, @anakin87, @bilgeyucel, @bogdankostic, @davidsbatista, @dina-deifallah, @jimmyzhuu, @julian-risch, @kacperlukawski, @maxdswain, @MechaCritter, @ritikraj2425, @sarahkiener, @sjrl, @soheinze, @srini047, @tholor
- Fixed ChatPromptBuilder template variable injection vulnerability
- Automatic list joining in pipelines
- Metadata inspection methods in InMemoryDocumentStore
- FileContent support for PDFs in chat generators
- ChatPromptBuilder: specially crafted template variables could be interpreted as images, tool calls, or other structured content instead of plain text; now automatically sanitized during rendering
Full changelog
Security Notes
- Fixed an issue in
ChatPromptBuilderwhere specially crafted template variables could be interpreted as structured content (e.g., images, tool calls) instead of plain text. Template variables are now automatically sanitized during rendering, ensuring they are always treated as plain text.
🐛 Bug Fixes
- Fix malformed log format string in
DocumentCleaner. The warning for documents withNonecontent used%{document_id}instead of{document_id}, preventing proper interpolation of the document ID.
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.