Maomi.ToMarkdown.Agent 1.0.0-alpha.2

This is a prerelease version of Maomi.ToMarkdown.Agent.
dotnet add package Maomi.ToMarkdown.Agent --version 1.0.0-alpha.2
                    
NuGet\Install-Package Maomi.ToMarkdown.Agent -Version 1.0.0-alpha.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Maomi.ToMarkdown.Agent" Version="1.0.0-alpha.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Maomi.ToMarkdown.Agent" Version="1.0.0-alpha.2" />
                    
Directory.Packages.props
<PackageReference Include="Maomi.ToMarkdown.Agent" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Maomi.ToMarkdown.Agent --version 1.0.0-alpha.2
                    
#r "nuget: Maomi.ToMarkdown.Agent, 1.0.0-alpha.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Maomi.ToMarkdown.Agent@1.0.0-alpha.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Maomi.ToMarkdown.Agent&version=1.0.0-alpha.2&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Maomi.ToMarkdown.Agent&version=1.0.0-alpha.2&prerelease
                    
Install as a Cake Tool

Maomi.ToMarkdown

猫是一个很有自己想法的动物。

English | 简体中文

Maomi.ToMarkdown 是一个自研维护的 .NET 文件转 Markdown 工具库。它把常见办公文档抽取为结构化的 Markdown 文本,可用于文档整理、快速构建语料库、喂给大语言模型等场景。

与常见的"逐页逐个字"的文本抽取工具不同,Maomi.ToMarkdown 会尽量还原文档语义结构:标题、代码块、列表、表格、行内代码、超链接与图片引用。

特性

  • 支持 PDF、Word (.docx)、Excel (.xlsx)、PowerPoint (.pptx)、HTML、Markdown、纯文本、JSON 的转换。
  • 依据 MIME 类型(由文件扩展名识别)自动挑选合适的抽取器。
  • 通过 ITextExtractor 抽象与依赖注入,方便注册、扩展与替换。
  • 支持抽取嵌入图片:把图片落盘并改写成本地相对路径(![...](images/xxx.png))。
  • 支持文本分片(TextSplit):把长文本按多种粒度切割为 RAG 分块(递归、固定长度、句子、段落、Markdown 感知),可配置重叠。
  • 针对中文场景做了优化:中英文之间自动补空格、CJK 字体与代码块区分等。

各格式还原效果

格式 还原能力
PDF 通过字体大小与版面分析识别标题、代码块、列表、表格、行内代码
Word (.docx) 标题、有序/无序列表、代码块、行内代码、表格(含合并单元格)、超链接、图片
Excel (.xlsx) 每个工作表渲染为一个 Markdown 表格,另支持图片
PowerPoint (.pptx) 标题、项目符号列表、正文、图片,可跳过隐藏页
HTML 语义化 Markdown,支持 data URI / http(s) / 本地图片落地
Markdown / 纯文本 / JSON 原样透传

安装

通过 NuGet 安装:

dotnet add package Maomi.ToMarkdown

或在项目文件中添加:

<ItemGroup>
  <PackageReference Include="Maomi.ToMarkdown" Version="1.0.0-alpha.1" />
</ItemGroup>

如需本地调试源码,可克隆后直接引用项目:

git clone https://github.com/whuanle/maomi.tomarkdown.git
<ItemGroup>
  <ProjectReference Include="path/to/maomi.tomarkdown/src/Maomi.ToMarkdown/Maomi.ToMarkdown.csproj" />
</ItemGroup>

使用

方式一:依赖注入 + TextExtractionService(推荐)

在 Program.cs(或其他容器注册处)注册服务:

using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();
services.AddTextExtraction();          // 注册全部默认抽取器
var provider = services.BuildServiceProvider();

var service = provider.GetRequiredService<TextExtractionService>();

然后任意抽取文件或文件流:

// 1) 根据文件路径抽取(按扩展名识别 MIME 类型)
string markdown = await service.ExtractAsync(@"C:\docs\report.pdf");

// 2) 根据文件流抽取(显式传入文件名)
await using var stream = File.OpenRead(@"C:\docs\report.docx");
string markdown2 = await service.ExtractAsync(stream, "report.docx");

AddTextExtraction() 会一次性注册 TextExtractionService 与全部默认 ITextExtractor。

方式二:使用 TextExtractorFactory

不依赖容器,直接通过 MIME 类型或文件名创建抽取器:

using Maomi.ToMarkdown.TextExtract;

// 按 MIME 类型创建
ITextExtractor extractor = TextExtractorFactory.Create("application/pdf");

// 或按文件名创建
ITextExtractor extractor2 = TextExtractorFactory.CreateByFileName(@"C:\docs\report.docx");

string markdown = await extractor.ExtractAsync(stream);

未注册或未匹配到抽取器时会抛出 NotSupportedException。

方式三:调用转换器

希望对某个具体格式做细粒度控制、或需要抽取图片时,可以直接使用封装好的转换器:

using Maomi.ToMarkdown;

// Word (.docx) -> Markdown
string md = DocxToMarkdownConverter.ConvertToMarkdown(@"C:\docs\report.docx");

// PDF -> Markdown
string md2 = PdfToMarkdownConverter.ConvertToMarkdown(@"C:\docs\report.pdf");

抽取图片(图片落盘到 images 目录,并回填路径列表):

var images = new List<string>();
string md = DocxToMarkdownConverter.ConvertToMarkdown(@"C:\docs\report.docx", "images", images);
// images 中保存了所有已落盘图片的完整路径

支持的 MIME 类型

扩展名 MIME 类型 抽取器
.txt text/plain PlainTextExtractor
.json application/json PlainTextExtractor
.md / .markdown text/markdown MarkdownExtractor
.html / .htm text/html HtmlExtractor
.pdf application/pdf PdfExtractor
.docx application/vnd.openxmlformats-officedocument.wordprocessingml.document MsWordExtractor
.xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet MsExcelExtractor
.pptx application/vnd.openxmlformats-officedocument.presentationml.presentation MsPowerPointExtractor

注:旧式 .doc 二进制格式不受 OpenXML 支持,需先转存为 .docx。

自定义抽取器

实现 ITextExtractor 接口即可接入新的文件类型。接口定义:

public interface ITextExtractor
{
    bool SupportsMimeType(string mimeType);

    Task<string> ExtractAsync(Stream stream, CancellationToken cancellationToken = default);

    // 可选:支持图片抽取时重写此方法
    Task<string> ExtractAsync(Stream stream, string imagePath, IList<string> images, CancellationToken cancellationToken = default);
}

通过依赖注入注册:

services.AddSingleton<ITextExtractor, MyCustomExtractor>();

当多个抽取器支持同一 MIME 类型时,TextExtractionService 采用最后匹配者优先,因此可以用自定义抽取器覆盖默认行为。

文本分片(TextSplit)

用于把长文本切割成若干小块,便于构建语料库、喂给 RAG / 大语言模型。分片模块是一组无状态的静态扩展方法,作用于 string,返回 IReadOnlyList<TextChunk>。

分片方法

方法 说明 默认重叠单位
SplitRecursive(text, chunkSize, ...) 通用递归分片:段落 → 句子 → 词 → 字符逐级回退,语义保持最好 Character
SplitFixedSize(text, chunkSize, ...) 忽略语义,按固定字符数切分 Character
SplitBySentence(text, chunkSize, ...) 在句子边界尽量长地切分 Sentence
SplitByParagraph(text, chunkSize, ...) 在段落(空行)边界尽量长地切分 Paragraph
SplitByMarkdown(text, chunkSize, ...) 保护围栏代码块,优先在标题/列表/表格等结构边界切分 Character

TextChunk

每个分块携带以下元数据:

字段 含义
Text 含重叠的完整块文本
ChunkText 去除重叠后的原始块文本
OverlapText 从前一块重叠进来的文本(首块为空)
ChunkIndex 1-based 序号
StartPosition / EndPosition 基于原始文本的 1-based 字符起止位置
SeparatorUsed 用于切出该块的分隔符
StartLine / StartColumn / EndLine / EndColumn 基于原始文本的 1-based 行列坐标

重叠(Overlap)

重叠的单位由 OverlapUnit 指定:Character(固定字符数,词汇安全)、Sentence(重叠 N 句)、Paragraph(重叠 N 段)。chunkOverlap 默认 0 表示不重叠。

using Maomi.ToMarkdown.TextSplit;

string text = ...; // 长文本(例如从文件中抽取出的 Markdown)

// 通用递归分片,块最大 500 字符,前后重叠 80 字符
var chunks = text.SplitRecursive(500, 80);

// 段落分片,重叠最后一段
var paragraphChunks = text.SplitByParagraph(1000, 1, OverlapUnit.Paragraph);

// Markdown 感知分片,保护代码块
var mdChunks = text.SplitByMarkdown(600, 60);

foreach (var chunk in chunks)
{
    Console.WriteLine($"[{chunk.ChunkIndex}] {chunk.Text}");
}

当 chunkSize <= 0 或 chunkOverlap < 0 时会抛出 ArgumentException;空文本返回空列表。

按字数 / token 计数(可选)

chunkSize 的计量单位由 ITextSizeCounter 决定,默认使用零依赖的 CharacterSizeCounter(按字符数)。若希望按 token 数 切分,引入可选的 Maomi.ToMarkdown.Token 包即可;它基于 SharpToken(tiktoken 兼容)实现了 TiktokenSizeCounter:

dotnet add package Maomi.ToMarkdown.Token
using Maomi.ToMarkdown.TextSplit;
using Maomi.ToMarkdown.Token;

// 用枚举指定编码(推荐)
var counter = new TiktokenSizeCounter(TiktokenEncoding.O200kBase);

// 或按模型名,自动映射编码
var counter2 = new TiktokenSizeCounter("gpt-4o");

// 每个方法最后都有一个可选的 ITextSizeCounter 参数,传入即可替换默认的字数计数
var chunks = text.SplitRecursive(500, 80, sizeCounter: counter);

foreach (var chunk in chunks)
{
    Console.WriteLine($"[{chunk.ChunkIndex}] {counter.Count(chunk.ChunkText)} tokens");
}

不引用 Maomi.ToMarkdown.Token 时,主库默认按字符数计,无任何额外依赖。你也可以自行实现 ITextSizeCounter 接入任意计数策略。

模型分片(Maomi.ToMarkdown.Agent)

独立 NuGet 包:Maomi.ToMarkdown.Agent。在文本分片的基础上,用大模型按语义完整性把长文本切成块。模型以流式方式对话,且只读取正文,忽略思考 / 推理内容(TextReasoningContent)。

安装

dotnet add package Maomi.ToMarkdown.Agent
<ItemGroup>
  <PackageReference Include="Maomi.ToMarkdown.Agent" Version="1.0.0-alpha.1" />
</ItemGroup>

该包会传递依赖 Maomi.ToMarkdown(提供 TextChunk 分片模型)。

使用

ModelChunker 提供两个静态方法,分别基于 Microsoft.Extensions.AI.IChatClient 与 Microsoft.Agents.AI.AIAgent。二者都会提示模型在语义完整的块之间插入分隔符(默认 <|CHUNK|>),随后在分隔符处切分并映射为 TextChunk;若模型未返回任何分隔符,则回退到 text.SplitRecursive(...)。

基于 IChatClient
using Maomi.ToMarkdown.Agent;
using Maomi.ToMarkdown.TextSplit;
using Microsoft.Extensions.AI;

IChatClient client = ...; // 你的模型客户端,如 OpenAI / Ollama / Azure
string text = ...;        // 长文本(例如从文件中抽取出的 Markdown)

// 流式分片,默认分隔符 "<|CHUNK|>"
IReadOnlyList<TextChunk> chunks = await ModelChunker.SplitWithChatClientAsync(client, text);

// 自定义分隔符与 ChatOptions
var options = new ChatOptions { Temperature = 0 };
var myChunks = await ModelChunker.SplitWithChatClientAsync(client, text, "<|CHUNK|>", options);

foreach (var chunk in chunks)
{
    Console.WriteLine($"[{chunk.ChunkIndex}] {chunk.Text}");
}
基于 AIAgent
using Maomi.ToMarkdown.Agent;
using Microsoft.Agents.AI;

AIAgent agent = ...; // 任意 Microsoft Agent Framework 的 Agent(如 ChatClientAgent)

IReadOnlyList<TextChunk> chunks = await ModelChunker.SplitWithAgentAsync(agent, text);

说明

  • 默认分隔符 <|CHUNK|> 可通过第二个参数替换,分隔符须在原文中未出现,以免误切。
  • 通过 IChatClient.GetStreamingResponseAsync 与 AIAgent.RunStreamingAsync 以流式调用,仅正文(TextContent)会被读取,思考/推理(TextReasoningContent)等非正文内容会被忽略。
  • 返回 IReadOnlyList<TextChunk>,与 TextSplit 的分块模型一致;ChunkIndex 自 1 起,位置 / 行列坐标基于原始文本。
  • 空文本或全空白文本返回空列表;模型未返回分隔符时自动回退到 SplitRecursive。
  • 在 Microsoft.Agents.AI.Abstractions 1.19.0 中,Agent 基类类型为 Microsoft.Agents.AI.AIAgent。

项目结构

Maomi.ToMarkdown.slnx
Directory.Packages.props        # 统一管理包版本
src/Maomi.ToMarkdown/
    ITextExtractor.cs           # 抽取器抽象
    TextExtractionService.cs    # 按 MIME 类型编排抽取
    TextExtractionExtensions.cs # AddTextExtraction() 依赖注入扩展
    MimeTypes.cs                # MIME 类型常量
    MimeTypesDetection.cs       # 扩展名 -> MIME 类型
    Converters/
        DocxToMarkdownConverter.cs
        PdfToMarkdownConverter.cs
    TextExtract/
        PlainTextExtractor.cs
        MarkdownExtractor.cs
        HtmlExtractor.cs
        PdfExtractor.cs
        MsWordExtractor.cs
        MsExcelExtractor.cs
        MsExcelExtractorConfig.cs
        MsPowerPointExtractor.cs
        MsPowerPointExtractorConfig.cs
        TextExtractorFactory.cs
        StringExtensions.cs
        StringBuilderExtensions.cs
    TextSplit/
        TextChunk.cs                # 分片结果模型
        OverlapUnit.cs              # 重叠单位
        ITextSizeCounter.cs         # 大小计数器抽象
        CharacterSizeCounter.cs     # 默认:按字符数(零依赖)
        TextSplittingExtensions.cs  # 分片扩展方法(递归引擎 + 5 种方法)
        MarkdownSplitter.cs         # Markdown 感知分片
src/Maomi.ToMarkdown.Token/
    TiktokenSizeCounter.cs          # 按 token 数(基于 SharpToken)
src/Maomi.ToMarkdown.Agent/
    ModelChunker.cs                 # 基于大模型的语义分片(Agent / IChatClient)
tests/Maomi.ToMarkdown.Tests/    # xunit 单元测试

依赖

包 用途
PdfPig PDF 内容解析
DocumentFormat.OpenXml Word / PowerPoint 解析
ClosedXML Excel 解析
ReverseMarkdown HTML 转 Markdown
Microsoft.Extensions.DependencyInjection.Abstractions / Logging.Abstractions DI 与日志抽象
Microsoft.Agents.AI.Abstractions / Microsoft.Extensions.AI.Abstractions Maomi.ToMarkdown.Agent 的模型分片(Agent / IChatClient)抽象
SharpToken Maomi.ToMarkdown.Token 的 token 计数(tiktoken 兼容,可选)

运行测试

dotnet test Maomi.ToMarkdown.slnx

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-alpha.2 81 8/23/2026

Initial release. Converts PDF, Word (.docx), Excel (.xlsx), PowerPoint (.pptx), HTML, Markdown, plain text and JSON to Markdown.