dev.to #ai短讯
一个接口,七种格式:Solon AI 如何将文件、网页和数据库模式转化为 RAG 文档
Solon AI 提供统一的加载器接口,支持将文件、网页及数据库模式等七种格式转换为 RAG 文档。本文基于 solon-ai-rag-loaders 源码解析其实现细节,展示如何扩展检索器的输入类型。
每个 RAG 管道都以相同的方式开始:你拥有数据,而模型需要文档。
有趣的问题在于这一理念能延伸多远。Solon AI 通过一个刻意精简的契约来回答这个问题——然后将其扩展到七种格式,其中包括一种你可能从未尝试过喂给检索器的格式:你的数据库模式。
这是对 solon-ai-rag-loaders 的源代码之旅。以下所有声明均针对当前源码树进行了验证;如果某个类的行为与其名称不符,我会特别指出。
契约包含三个方法
public interface DocumentLoader {
DocumentLoader additionalMetadata(String key, Object value);
DocumentLoader additionalMetadata(Map<String, Object> metadata);
List<Document> load() throws IOException;
}这就是整个契约:两个元数据方法和一个 load()。没有 provider 字段,没有 API 密钥,也没有供应商。七个 Maven 子模块实现了它(solon-ai-load-markdown、-pdf、-word、-excel、-html、-ppt、-ddl),每个模块仅拉取自己的解析依赖项——commonmark、PDFBox、POI、jsoup、Tika。
基类 AbstractOptionsDocumentLoader 通过两个入口点添加了选项模式:
MarkdownLoader loader = new MarkdownLoader(file)
.options(o -> o.codeBlockAsNew(true));
// 或者,如果你已经持有一个 Options 实例:
loader.options(myOptions);每个加载器中都有一个 SupplierEx 构造函数,因此你的源可以是文件、URL、字节数组或任何其他可以惰性生成流的内容。
默认分割方式揭示了每种格式的含义
七个加载器,但没有单一的“块大小”旋钮。相反,每个加载器选择其默认的意义单元——并且这些默认值故意存在分歧:
| 加载器 | 默认单元 | 默认模式 |
|---|---|---|
| MarkdownLoader | 章节(按标题) | AST 遍历,标题始终分割 |
| PdfLoader | 页面 | LoadMode.PAGE |
| WordLoader | 段落 | LoadMode.PARAGRAPH |
| PptLoader | 整个文档 | LoadMode.SINGLE |
| ExcelLoader | 工作表,每 200 行一批 | JSON 行 |
| HtmlSimpleLoader | 整个页面 | 单个文档 |
| DdlLoader | 表 | 每个 SHOW CREATE TABLE 一个 |
这种不对称性是设计使然。段落是散文的自然检索单元;页面是 PDF 的自然单元;幻灯片演示通常作为一个文档更有意义;表格是一个完整的思想。你可以覆盖默认值(PdfLoader 变为 SINGLE,WordLoader 变为 SINGLE,PptLoader 在 "\n\n\n" 处分割),但开箱即用的行为已经编码了针对“这里的块是什么?”的每格式答案。
Markdown:基于 AST 分割,而非正则表达式
MarkdownLoader 不使用正则表达式切片文本。它使用 commonmark 将文档解析为 AST,并使用访问者进行遍历:
- 标题始终开始一个新文档。这不是一个选项——是一条规则。
- 三个开关默认为关闭:horizontalLineAsNew、blockquoteAsNew、codeBlockAsNew。
- 围栏代码块更为微妙。当 codeBlockAsNew(true) 时,代码块开始其自己的文档。无论哪种情况,围栏代码块总是结束其文档——因此代码永远不会渗入紧随其后的散文块中。
- 生成的文档携带你可以在稍后过滤的元数据:category=header_1..6 带有标题,category=code_block 带有语言,或 category=blockquote。
在依赖元数据之前值得了解的一个细微差别:访问者在遍历时将 title/category 写入当前文档。如果一个部分在其内容之前没有标题文本,则该块的元数据 simply 不会存在。这对于检索来说没问题;如果你在其之上构建 UI,则值得记住。
PDF 和 Word:相同的两个理念,不同的真相
PdfLoader(PDFBox)默认每页一个文档,每个文档都盖上了 page、total_pages 以及“第 3 页”的摘要——这在搜索 UI 中很方便。切换到 LoadMode.SINGLE,你将获得整个文件作为一个文档,页面由 "\n\f" 连接,仅有一个页数计数。
WordLoader 处理两种二进制格式:它使用 POI 的 FileMagic 检查流,并将 .docx (OOXML) 和传统的 .doc (OLE2) 路由到不同的读取器。它默认采用段落模式——每个段落一个 Document——并提供一个单一的逃逸出口(escape hatch)。
Excel:行输入,JSON 输出
ExcelLoader(基于 POI + snack4)将工作表中第一个非空行视为标题行,然后将每一后续行映射为 {column: value},并以 JSON 文档的形式序列化批次。两个默认设置决定了其行为:
- 每个文档 200 行。一个包含 620 行的工作表会被拆分为 4 个文档。设置 documentMaxRows(-1) 可使每个工作表保持为一个文档。
- 空行会停止读取。读取循环在此处中断,因此第一个空白行之后的内容会被静默忽略——这是设计使然,尾部的空行不应导致解析失败,但空白行之后的数据不会被索引。在使用手动编辑的电子表格时请留意这一点。
公式单元格读取的是其公式文本,而非计算后的值。
PowerPoint:信任 Tika
PptLoader 本身不解析幻灯片 XML。它将流交给 Apache Tika 的 AutoDetectParser,并获取正文文本返回。默认模式为 SINGLE——将整个演示文稿作为一个文档;如果你的演示文稿具有可预测的幻灯片分隔符,PAGE 模式会在 "\n\n\n" 处进行分割。
DDL:你的数据库架构本身就是文档
这是唯一一个能改变你对流水线认知的方式。DdlLoader 连接到一个普通的 DataSource(无需 ORM,无需实体),并为每张表生成一个包含其 DDL 的 Document:
DdlLoader loader = new DdlLoader(dataSource); // 内置 MySQL 配置
loader.options(o -> o.loadOptions("shop", null)); // 仅加载 schema:所有表
List<Document> docs = loader.load();通过 loadOptions(schema, table) 提供三种粒度:整个实例、单个 schema、单张表。默认配置是 MySQL(使用 information_schema + SHOW CREATE TABLE,排除系统 schema),但每个 SQL 字符串都是模板——加载器通过 Solon 自己的表达式引擎(SnEL.evalTmpl)运行这些模板,因此你可以通过在 DdlLoadConfig 中替换模板集来将其适配到其他数据库。
一个我喜欢的细节:SHOW CREATE TABLE 返回的是 CREATE TABLE \order(...) —— 这是一个仅在 schema 内部才有意义的表名。加载器重写标题为 CREATE TABLE \shop.\order(...),使得每个检索到的 DDL 文档都是自描述的,并添加元数据 metadata("table", "order"),以便你的过滤层可以直接定位到特定的表。
应用场景不言自明:将其指向生产环境(只读!),你的 AI 助手就能检索到真实的架构事实,而不是胡乱猜测列名。
load() 之后:下游统一形态
无论何种格式,load() 都会返回 List —— 包含内容、元数据以及流畅字段(title, url, summary, id, embedding, score)。从这里开始,一切与格式无关:嵌入、存储到 Repository、或作为工具附加。加载器是流水线上唯一拥有特定格式知识的地方。
何时可能跳过此模块
为了对你的架构审查公平起见:如果你的语料库已经是干净的 Markdown,你可能不需要七个加载器——Solon AI 的分段故事在嵌入时单独处理分段问题。当来源异构(办公文件、网页、实时架构)或者自然单元(页面、段落、表格)应该决定分块方式,而不是字符计数时,这些加载器才物有所值。
总结
solon-ai-rag-loaders 是一个坚守小型契约的良好范例:三个方法,七种实现,以及针对每种格式的默认设置,这些设置编码了真实的观点,而不是一个通用的旋钮。如果你构建需要准确谈论数据库的助手,光是 DDL 加载器就值得你一看。
- 项目:GitHub 上的 solon-ai
- 文档:solon.noear.org
- 本文中引用的所有源码均已对照 solon-ai-rag-loaders 的当前源码树进行了核查。
译文已达到本站中文翻译的字数上限,剩余内容请查看原文。