Repository navigation
Releases
v3.5.0
⭐️ Highlights
🧭 Classify and Route with the OpenAI Decisions API
Three new components use the OpenAI Decisions API to choose how to handle your data without instructions. 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): ci...
v3.5.0-rc1
v3.5.0-rc1
v2.31.1
🔒 Security Notes
- Haystack now requires
anyio>=4.14.2.anyiois installed transitively throughhttpxandopenai; versions before 4.14.2 are affected by CVE-2026-63374 (GHSA-82r6-8w77-94w6). - Haystack now requires
certifi>=2026.10.19.certifiis installed transitively throughhttpxandposthog; earlier versions trust eMudhra/emSign certificate authorities whose private keys were compromised (GHSA-6q48-6c7w-9gw2). If you pincertifi, raise the pin to at least 2026.10.19.
v2.31.1-rc1
v2.31.1-rc1
v3.4.1
🔒 Security Notes
- Haystack now requires
certifi>=2026.10.19.certifiis installed transitively throughhttpxandposthog; earlier versions trust eMudhra/emSign certificate authorities whose private keys were compromised (GHSA-6q48-6c7w-9gw2). If you pincertifi, raise the pin to at least 2026.10.19.
v3.4.1-rc2
v3.4.1-rc2
v3.4.1-rc1
Release Notes
v3.4.0
⭐️ 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_numbermetadata field now means the same thing in every splitter: the page the chunk's text starts on. Two cases change:RecursiveDocumentSplitterreported the page a chunk ended on. A chunk spanning a page break now reports the page it starts on, matchingDocumentSplitterandMarkdownHeaderSplitter.DocumentSplitter,MarkdownHeaderSplitterandEmbeddingBasedDocumentSplitterreported 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 bysplit_by="page"still get their own number.
If you display
page_numberto users or store it alongside embeddings, re-index documents that contain form feeds to pick up the corrected values. -
RecursiveDocumentSplitterwithsplit_unit="word"andsplit_overlapgreater 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 waysplit_lengthdoes, 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. -
RecursiveDocumentSplitternow keeps the whitespace between sentences whensentence_splitter_paramssetskeep_white_spacestoFalse. That is the sentence tokenizer's default, so this applies wheneversentence_splitter_paramsis 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 towardsplit_length, page breaks (\f) between sentences were lost, andsplit_idx_startandpage_numberfell behind the chunk's real start. Chunks are now copied from the document whateverkeep_white_spacesis 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.MERGEnow intersects initialization and runtime logical filters when both useORorNOT. Callers that relied on the previous broader result should construct a single explicitORfilter containing all alternatives and pass it withFilterPolicy.REPLACEinstead. -
ChatMessage.from_openai_dict_formatnow raises aValueErrorfor content parts it can't convert: images referenced by a URL such ashttps://example.com/image.png(only images embedded as base64 data URLs, likedata:image/png;base64,..., are supported), files referenced byfile_id, and other part types such asinput_audio. Previously, such messages were accepted because the whole list was stored as a single text, andChatMessage.to_openai_dict_formatsent 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 exampleChatMessage.from_user(content_parts=["Describe this image", ImageContent.from_url(url)]). Send files inline as base64file_datainstead of referencing them byfile_id. Extra keys on content parts, such ascache_control, are no longer passed through, so prompt caching through OpenRouter or LiteLLM stops for these messages. -
HTMLToDocumentnow defaults toencoding=None, delegating HTML encoding detection to Trafilatura. Setencoding="utf-8"to retain explicit UTF-8 decoding. -
Previously,
HTMLToDocumentused theencodingvalue 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 exampleHTMLToDocument(encoding="latin-1"). -
The default model for
OpenAITextEmbedder,OpenAIDocumentEmbedder,AzureOpenAITextEmbedder, andAzureOpenAIDocumentEmbedderhas been changed fromtext-embedding-ada-002totext-embedding-3-small.text-embedding-3-smallis roughly 5x cheaper per token than the previous generationtext-embedding-ada-002and 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_deploymentrefers to the deployment name in your Azure OpenAI resource, not a model identifier. If you do not already have atext-embedding-3-smalldeployment 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()andToolset.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_toolsorwarm_up_tools_async. Lazy toolsets such asMCPToolsetmust be warmed up before passing them to a Chat Generator.
🚀 New Features
-
Added the experimental
ToolResultOffloadCompactorforCompactionHook. Once compaction is triggered, it writes older tool results to aToolResultStoreand 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
Agentruns inside aPipeline, 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 eachComponentTool, and the classes of anyToolsetobjects. -
Haystack now officially supports Python 3.15.
-
Removed the
Sockets.__getattribute__override. It looked up a_socketsattribute that no instance has, so on every attribute access it r...
v3.4.0-rc1
v3.4.0-rc1
v3.3.0
⭐️ 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_retrievalandInMemoryBM25RetrieverwithBM25L(the default) orBM25Plusnow return only documents that contain at least one query term, with or withoutscale_score. Previously, documents without any query term were returned with a positive score and filled uptop_k. As a result, retrieval can now return fewer thantop_kdocuments, or none at all. Thedeltalower 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.BM25Okapiis not affected.
⚡️ Enhancement Notes
TextCleaner.runnow raises a clearTypeErrorwhentextsis not a list or any element is not astr, instead of failing later or producing unexpected results.
🔒 Security Notes
- Haystack now requires
anyio>=4.14.2.anyiois installed transitively throughhttpxandopenai; versions before 4.14.2 are affected by CVE-2026-63374 (GHSA-82r6-8w77-94w6).
🐛 Bug Fixes
-
Fixed BM25 tokenization in
InMemoryDocumentStorefor Chinese, Japanese and Korean text. The defaultbm25_tokenization_regexnow 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()raisingTypeError: object of type 'ellipsis' has no len()when one of the sockets is annotated withCallable[..., T]. ACallable[..., T]now matches callables with any parameters, in both directions, as long as the return types are compatible. -
Fixed
ChatPromptBuilderdropping content parts when the template is a list ofChatMessageobjects. Only the firstTextContentpart 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 allTextContentparts are rendered and the remaining content parts are passed through unchanged. -
Fixed
CompactionHook.close()andclose_async()to release the token counter's resources as well as the compactor's. Previously, resources such asOpenAITokenCounter's HTTP client remained open after the hook or Agent was closed. -
ConfirmationHookno 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 anon_exitreminder. Before, those messages were removed from the history the Agent sends to the model. -
DocumentToImageContentno longer raises aKeyErrorfor 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 getsNoneinimage_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
DOCXToDocumentbreaking 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. Thecsvtable format is unchanged: it already kept such cells intact by quoting them. -
Fixed
DOCXToDocumentdropping hyperlink addresses inside tables. Withlink_formatset tomarkdownorplain, 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 themarkdownandcsvtable formats. The defaultlink_format="none"output is unchanged. -
Fixed
ConditionalRouter.from_dictmutating the caller'sroutesdata in place: serializedoutput_typestrings were deserialized directly inside the caller's dictionaries, so reusing the same serialized pipeline dict afterwards yielded already-deserialized type objects.from_dictnow works on a copy and the caller's data is left untouched.BranchJoiner.from_dictreceived the same treatment. -
LinkContentFetcherno longer adds itstimeoutandfollow_redirectsdefaults to theclient_kwargsdictionary 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
MetaFieldRankerto return no documents whenmissing_meta="drop"and all documents lack the ranking field or have aNonevalue. -
MetaFieldRankernow treats a Document whosemeta_fieldvalue isNonethe same as a Document that is missing the field, applying themissing_metasetting to it. Previously a singleNonevalue made sorting fail, so the ranker logged a warning and returned all Documents in their original order, ignoringmissing_meta="drop"as well. -
MockTextEmbedderandMockDocumentEmbeddernow accept non-positivedimensionvalues whenembeddingorembedding_fnis provided, matching the documented behavior. The default deterministic embedding mode still requires a positivedimension. -
ChatMessage.from_openai_dict_formatnow accepts tool-callargumentsthat are already a dictionary instead of raising aTypeError. Some OpenAI-compatible servers send a parsed object rather than a JSON string. -
MetaFieldGroupingRankernow treats agroup_byorsubgroup_byvalue ofNoneas missing, the same way it already treatssort_docs_by. Documents withNonego 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
JSONConverterfailing withValueErrorwhencontent_keycontains numeric or boolean scalar values. These values are now converted to strings before creating theDocument, whilenullvalues remain unchanged. -
Fixed
SentenceSplitterlosing the whitespace between two sentences when the first one ends with a closing quote, as inHe said "Hi." Bye.. Those characters were missing from the chunk text and shifted thesplit_idx_startoffset 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 asDocumentSplitter,RecursiveDocumentSplitter,MarkdownHeaderSplitterandEmbeddingBasedDocumentSplitter. Chunk boundaries change for text that contains quoted sentences, so re-indexing an existing corpus produces different chunks than before. -
Fixed
LLMMetadataExtractorincorrectly treating a chat generator output that carries its ownerrorfield as a failed LLM call, which sent every document tofailed_documentswithmetadata_extraction_errorset toNone. Such documents are now processed normally. -
Retrievers now handle
top_kconsistently. A negativetop_kpassed at runtime toInMemoryBM25Retriever,InMemoryEmbeddingRetrieverorMultiRetriever(top_kandtop_k_per_retriever) now raises aValueError. Previously it was applied as a negative slice, silently dropping the last documents. A runtimetop_k=0returns no documents.MultiRetrievernow also validatestop_kandtop_k_per_retrieverat initialization, raising aValueErrorwhen they are set and not greater than 0, matching the in-memory retrievers.Nonestill means no limit. -
Fixed
Pipeline.run(),Pipeline.run_async(), andPipeline.stream()treating a flat input with a dictionary value as a component name. For example,{"payload": {"x": 1}}now reaches components with apayloadinput without requiring the component name in the input data. -
Pipeline.run_asyncnow lets aBreakpointExceptionorPipelineRuntimeErrorraised by a nested component propagate unchanged, instead of wrapping it in anotherPipelineRuntimeError. This matches the synchronousPipeline.runand preserves the original error context (for example an agent snapshot) when a component internally runs a pipeline. -
XLSXToDocumentnow 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