fix(reasoning): prevent LLM-hallucinated HTML tags from rendering as DOM elements (#2321)

* fix

* add test

* fix
This commit is contained in:
Xun 2026-04-19 19:27:34 +08:00 committed by GitHub
parent 80e210f5bb
commit 7c87dc5bca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 24 additions and 1 deletions

View File

@ -11,6 +11,7 @@ import { BrainIcon, ChevronDownIcon } from "lucide-react";
import type { ComponentProps, ReactNode } from "react";
import { createContext, memo, useContext, useEffect, useState } from "react";
import { Streamdown } from "streamdown";
import { reasoningPlugins } from "@/core/streamdown/plugins";
import { Shimmer } from "./shimmer";
type ReasoningContextValue = {
@ -177,7 +178,7 @@ export const ReasoningContent = memo(
)}
{...props}
>
<Streamdown {...props}>{children}</Streamdown>
<Streamdown {...reasoningPlugins}>{children}</Streamdown>
</CollapsibleContent>
),
);

View File

@ -28,6 +28,15 @@ export const streamdownPluginsWithWordAnimation = {
] as StreamdownProps["rehypePlugins"],
};
// Plugins for reasoning/thinking content — derived from streamdownPlugins but without rehypeRaw,
// to prevent LLM-hallucinated HTML tags (e.g. <simd>) from being rendered as DOM elements.
export const reasoningPlugins = {
remarkPlugins: streamdownPlugins.remarkPlugins,
rehypePlugins: streamdownPlugins.rehypePlugins?.filter(
(p) => p !== rehypeRaw,
) as StreamdownProps["rehypePlugins"],
};
// Plugins for human messages - no autolink to prevent URL bleeding into adjacent text
export const humanMessagePlugins = {
remarkPlugins: [

View File

@ -0,0 +1,13 @@
import rehypeRaw from "rehype-raw";
import { expect, test } from "vitest";
import { reasoningPlugins, streamdownPlugins } from "@/core/streamdown/plugins";
test("streamdownPlugins includes rehypeRaw", () => {
expect(streamdownPlugins.rehypePlugins).toContain(rehypeRaw);
});
test("reasoningPlugins does not include rehypeRaw", () => {
const flat = reasoningPlugins.rehypePlugins?.flat();
expect(flat).not.toContain(rehypeRaw);
});