C#实现DeepSeek或qwen3返回Markdown的<think>
标签渲染
DeepSeek-R1或qwen3思考部分会返回以下内容:
<think>
思考内容
</think>
回复正文内容
C#可以通过流行的Markdown渲染库Markdig,去自定义一个标签渲染来实现,具体实现方法:
自定义 ThinkBlock 解析器
using Markdig.Parsers;
using Markdig.Renderers.Html;
using Markdig.Renderers;
using Markdig.Syntax;
using Markdig;
// 增强版 ThinkBlock 解析器
public class ThinkBlock(BlockParser parser) : ContainerBlock(parser)
{
}
public class ThinkBlockParser : BlockParser
{
public ThinkBlockParser()
{
OpeningCharacters = ['<'];
}
public override BlockState TryOpen(BlockProcessor processor)
{
var lineStr = processor.Line.ToString();
int startIdx = lineStr.IndexOf("<think>", StringComparison.Ordinal);
if (startIdx == -1) return BlockState.None;
// 创建块并跳过开始标签
var block = new ThinkBlock(this)
{
Span = new SourceSpan(processor.Start, processor.Line.End),
Column = startIdx // 记录标签起始列
};
processor.NewBlocks.Push(block);
processor.GoToColumn(startIdx + "<think>".Length); // 关键:跳过开始标签
return BlockState.Continue;
}
public override BlockState TryContinue(BlockProcessor processor, Block block)
{
var thinkBlock = (ThinkBlock)block;
var lineStr = processor.Line.ToString();
// 在全行范围内搜索结束标签
int endIdx = lineStr.IndexOf("</think>", StringComparison.Ordinal);
if (endIdx != -1)
{
// 精确定位结束标签的物理位置
var endPosition = processor.Line.Start + endIdx;
thinkBlock.Span.End = endPosition + "</think>".Length - 1;
// 关键:移动处理器到结束标签之后
processor.GoToColumn(endIdx + "</think>".Length);
return BlockState.Break; // 终止当前块
}
return BlockState.Continue;
}
}
// HTML 渲染器
public class ThinkBlockRenderer : HtmlObjectRenderer<ThinkBlock>
{
protected override void Write(HtmlRenderer renderer, ThinkBlock obj)
{
renderer.EnsureLine();
renderer.Write("<div class=\"think-container\">\n");
renderer.Write("<div class=\"think-icon\">🤔</div>");
renderer.Write("<div class=\"think-content\">\n");
// 递归渲染子元素
foreach (var child in obj)
{
renderer.Render(child);
}
renderer.Write("\n</div></div>");
}
}
// 扩展注册
public class ThinkExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
{
pipeline.BlockParsers.InsertBefore<HtmlBlockParser>(new ThinkBlockParser());
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is HtmlRenderer htmlRenderer)
{
htmlRenderer.ObjectRenderers.InsertBefore<HtmlBlockRenderer>(new ThinkBlockRenderer());
}
}
}
使用方法
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Use<ThinkExtension>().Build();
HtmlData = (MarkupString)Markdown.ToHtml(Response, pipeline).Replace(@"⋅", "⋅");
CSS
.think-container {
background: #2E7D32;
border: 1px solid #cce0ff;
border-radius: 8px;
margin: 1.5rem 0;
padding: 1rem;
position: relative;
}
.think-icon {
position: absolute;
left: -15px;
top: -15px;
background: white;
border-radius: 50%;
padding: 5px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.think-content {
margin-left: 1.5rem;
}