🐱 算神的小窝 🤓

实现deepseek或qwen3返回Markdown的think标签渲染.md


CreationTime:5/27/2025 3:25:26 PM LastAccessTime:6/30/2025 12:10:27 PM


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;
}

最终效果展示

image-20250430221437943

An unhandled error has occurred. Reload 🗙