Maomi.ToMarkdown.Agent
1.0.0-alpha.2
dotnet add package Maomi.ToMarkdown.Agent --version 1.0.0-alpha.2
NuGet\Install-Package Maomi.ToMarkdown.Agent -Version 1.0.0-alpha.2
<PackageReference Include="Maomi.ToMarkdown.Agent" Version="1.0.0-alpha.2" />
<PackageVersion Include="Maomi.ToMarkdown.Agent" Version="1.0.0-alpha.2" />
<PackageReference Include="Maomi.ToMarkdown.Agent" />
paket add Maomi.ToMarkdown.Agent --version 1.0.0-alpha.2
#r "nuget: Maomi.ToMarkdown.Agent, 1.0.0-alpha.2"
#:package Maomi.ToMarkdown.Agent@1.0.0-alpha.2
#addin nuget:?package=Maomi.ToMarkdown.Agent&version=1.0.0-alpha.2&prerelease
#tool nuget:?package=Maomi.ToMarkdown.Agent&version=1.0.0-alpha.2&prerelease
Maomi.ToMarkdown
猫是一个很有自己想法的动物。
English | 简体中文
Maomi.ToMarkdown 是一个自研维护的 .NET 文件转 Markdown 工具库。它把常见办公文档抽取为结构化的 Markdown 文本,可用于文档整理、快速构建语料库、喂给大语言模型等场景。
与常见的"逐页逐个字"的文本抽取工具不同,Maomi.ToMarkdown 会尽量还原文档语义结构:标题、代码块、列表、表格、行内代码、超链接与图片引用。
特性
- 支持 PDF、Word (.docx)、Excel (.xlsx)、PowerPoint (.pptx)、HTML、Markdown、纯文本、JSON 的转换。
- 依据 MIME 类型(由文件扩展名识别)自动挑选合适的抽取器。
- 通过
ITextExtractor抽象与依赖注入,方便注册、扩展与替换。 - 支持抽取嵌入图片:把图片落盘并改写成本地相对路径(
)。 - 支持文本分片(TextSplit):把长文本按多种粒度切割为 RAG 分块(递归、固定长度、句子、段落、Markdown 感知),可配置重叠。
- 针对中文场景做了优化:中英文之间自动补空格、CJK 字体与代码块区分等。
各格式还原效果
| 格式 | 还原能力 |
|---|---|
| 通过字体大小与版面分析识别标题、代码块、列表、表格、行内代码 | |
| 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.Abstractions1.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 | Versions 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. |
-
net10.0
- Maomi.ToMarkdown (>= 1.0.0-alpha.2)
- Microsoft.Agents.AI.Abstractions (>= 1.19.0)
- Microsoft.Extensions.AI.Abstractions (>= 10.9.0)
-
net8.0
- Maomi.ToMarkdown (>= 1.0.0-alpha.2)
- Microsoft.Agents.AI.Abstractions (>= 1.19.0)
- Microsoft.Extensions.AI.Abstractions (>= 10.9.0)
-
net9.0
- Maomi.ToMarkdown (>= 1.0.0-alpha.2)
- Microsoft.Agents.AI.Abstractions (>= 1.19.0)
- Microsoft.Extensions.AI.Abstractions (>= 10.9.0)
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.