当前位置: 首页 > news >正文

Document 组件:把文件喂给 AI 之前,必须先做这三步

系列「企业级 AI Agent 实现拆解」E12 篇。上一篇 E11 讲了 Embedding——把文字变成向量。但向量化的前提是先有干净的文本。问题来了:你的内容在 PDF 里、在网页里、在 Word 文档里,格式五花八门,长度动辄几万字。这篇拆Document 组件:从原始文件到能被 AI 消化的知识块,中间发生了什么。

读完这篇你会知道

  • Document结构体是什么:三个字段背后藏着哪些能力
  • Loader怎么把文件"搬进来":为什么 Loader 不管格式
  • Parser怎么把 HTML/PDF/Word 转成纯文字:以 HTMLParser 为例
  • 四种切片策略怎么选:RecursiveSplitterMarkdownHeaderSplitterHTMLHeaderSplitterSemanticSplitter
  • 怎么用compose.Graph把三步串成一条流水线,一行代码跑完全程

一、先说为什么要"处理文档"

你想让 AI 回答公司内部知识库里的问题。AI 的记忆是有限的——你不可能把整本手册塞进去。

工程上的解法叫RAG(检索增强生成):

  1. 建库:把文档切成小块,算向量,存进数据库
  2. 用时:用户提问 → 捞出最相关的几块 → AI 看着这几块回答

第一步"建库"就是 Document 组件负责的事:加载 → 解析 → 切片


二、Document是什么

源码在eino/schema/document.go,结构体只有三个字段:

typeDocumentstruct{IDstring// 这块内容的唯一编号Contentstring// 实际文字内容MetaDatamap[string]any// 附带的额外信息}

Content是正文,MetaData存来源、分数、向量等附加信息。

从 HTML 页面解析出来的 Document 大概长这样:

{"id":"doc-001","content":"Go 是 Google 开发的编程语言,设计目标是简洁、高效...","meta_data":{"_source":"https://example.com/go-intro.html","_title":"Go 语言简介","_language":"zh"}}

MetaData 不是普通 map——有一批专属方法:

doc.Score()// 取检索相关性分数(不用手动从 map 挖)doc.DenseVector()// 取向量doc.ExtraInfo()// 取附加说明doc.SubIndexes()// 取多分区路由索引

这些值底层都存在 MetaData 的保留 key 里(_score_dense_vector等),但对外暴露方法,不让你直接操 map。


三、Loader:把文件搬进来

接口定义在eino/components/document/interface.go

typeLoaderinterface{Load(ctx context.Context,src Source,opts...LoaderOption)([]*schema.Document,error)}typeSourcestruct{URIstring// 文件路径或 URL}

接口极薄。eino-ext 提供了三种现成实现:

Loader用途URI 格式
file.FileLoader读本地文件/path/to/file.md
url.Loader抓网页https://...
s3.Loader读 AWS S3s3://bucket/key

关键设计:Loader 不管格式。它只负责把字节流读进来,格式解析交给Parser。两者分开,换格式不改 Loader,换数据源不改 Parser。

// FileLoader 内部逻辑大致如此:func(f*FileLoader)Load(ctx context.Context,src Source,opts...LoaderOption)([]*schema.Document,error){file,_:=os.Open(src.URI)deferfile.Close()// 把文件流交给 Parser,扩展名由 URI 携带returnf.parser.Parse(ctx,file,parser.WithURI(src.URI))}

四、Parser:把格式转成纯文字

接口定义在eino/components/document/parser/interface.go

typeParserinterface{Parse(ctx context.Context,reader io.Reader,opts...Option)([]*schema.Document,error)}

接受字节流,返回 Document 列表。

TextParser:最简单

直接把整个流读成字符串,返回一个 Document。处理.txt.md这类纯文本够用。

HTMLParser:解析网页

来自 eino-ext,底层用 goquery(Go 版 jQuery)操作 DOM。

// 源码:eino-ext/components/document/parser/html/html.gohtmlParser,_:=html.NewParser(ctx,&html.Config{Selector:gptr.Of("body"),// 用 CSS 选择器只抠出 body 内容})

解析后自动提取 meta 信息写入 MetaData:

_title <- <title> 标签内容 _description <- <meta name="description"> 内容 _language <- <html lang="..."> 属性 _charset <- 字符编码 _source <- 来源 URL

安全上用bluemonday UGC 策略过滤危险 HTML 标签,防止把恶意脚本当文本存进知识库。

ExtParser:按扩展名自动派活

如果你要处理多种格式:

// 源码:eino/components/document/parser/ext_parser.goextParser,_:=parser.NewExtParser(ctx,&parser.ExtParserConfig{Parsers:map[string]parser.Parser{".html":htmlParser,".pdf":pdfParser,".docx":docxParser,},FallbackParser:parser.TextParser{},// 其他格式兜底})// 关键:必须传 URI,否则 ExtParser 不知道用哪个 Parserdocs,_:=extParser.Parse(ctx,file,parser.WithURI("./report.html"))

eino-ext 目前支持的格式:HTML、PDF(逐页或合并)、Word(docx,可按节切分)、Excel(xlsx,逐行转 Document)。


五、Transformer:切片

一篇文章几万字,必须切成小块才能存入向量数据库。Transformer干这个:

// 源码:eino/components/document/interface.gotypeTransformerinterface{Transform(ctx context.Context,src[]*schema.Document,opts...TransformerOption)([]*schema.Document,error)}

输入一批 Document,输出更多更小的 Document。eino-ext 提供四种切片策略。

策略 1:RecursiveSplitter(通用首选)

源码:eino-ext/components/document/transformer/splitter/recursive/recursive.go

按分隔符递归切分。先按\n切,块还是太大就换.试,再不够就换?……直到块足够小。

splitter,_:=recursive.NewSplitter(ctx,&recursive.Config{ChunkSize:1500,// 每块最多 1500 字符OverlapSize:300,// 相邻块重叠 300 字符,保留边界上下文Separators:[]string{"\n",".","?","!"},KeepType:recursive.KeepTypeNone,// 分隔符本身丢弃})

OverlapSize是关键:切块边界处的内容会在相邻两块都出现,防止一句话被切断后两边都看不懂。

// 一行示例(源码:recursive/examples/main.go)data,_:=os.ReadFile("./document.md")docs,_:=splitter.Transform(ctx,[]*schema.Document{{Content:string(data)}})fmt.Printf("切成了 %d 块\n",len(docs))

策略 2:MarkdownHeaderSplitter(结构化文档)

源码:eino-ext/components/document/transformer/splitter/markdown/header.go

按 Markdown 标题层级切,每块继承父级标题写入 MetaData:

splitter,_:=markdown.NewHeaderSplitter(ctx,&markdown.HeaderConfig{Headers:map[string]string{"#":"chapter",// 一级标题 -> metadata key "chapter""##":"section",// 二级标题 -> metadata key "section"},TrimHeaders:true,// 切出来的块里不包含标题行本身})

切出的 Document 带结构化 MetaData:

{"content":"Go 的并发模型基于 CSP...","meta_data":{"chapter":"第三章 并发编程","section":"3.1 Goroutine 基础"}}

检索时可按章节过滤,不只是全文搜。

策略 3:HTMLHeaderSplitter

源码:eino-ext/components/document/transformer/splitter/html/header.go

和 MarkdownHeaderSplitter 同理,但处理 HTML 的<h1><h6>标签。适合爬下来的结构化网页文档,用 DFS 递归遍历 DOM 树,追踪标题层级。

策略 4:SemanticSplitter(高质量,慢)

源码:eino-ext/components/document/transformer/splitter/semantic/semantic.go

前三种按字符或结构切,不管语义。SemanticSplitter 先把文本 embed 成向量,计算相邻段落的余弦距离,在语义跳跃处切

splitter,_:=semantic.NewSplitter(ctx,&semantic.Config{Embedding:myEmbedder,// 必须接入 Embedding 模型Percentile:0.9,// 距离超过第 90 百分位才切BufferSize:1,// 对比时考虑前后各 1 句话的上下文MinChunkSize:100,// 过小的块丢弃})

工作流程:

  1. 先用 Separators 粗切成句子
  2. 每句话附带前后 BufferSize 句话的上下文拼在一起
  3. 整体 embed 成向量
  4. 计算相邻向量的余弦距离
  5. 距离超过 Percentile 阈值的地方真正切断

代价:每次切片都要调 Embedding API,比前三种慢很多。对质量要求极高时用。


六、把三步串成流水线

单独用每个组件没问题。eino 真正的价值在于用compose.Graph把它们连成流水线。

下面是 eino-examples 里quickstart/eino_assistant的知识入库流水线,改了注释:

// 源码:eino-examples/quickstart/eino_assistant/eino/knowledgeindexing/orchestration.gofuncBuildKnowledgeIndexing(ctx context.Context)(compose.Runnable[document.Source,[]string],error){g:=compose.NewGraph[document.Source,[]string]()// 节点 1:读文件(本地 Markdown)fileLoader,_:=file.NewFileLoader(ctx,&file.FileLoaderConfig{})g.AddLoaderNode("Loader",fileLoader)// 节点 2:按 Markdown 标题切片splitter,_:=markdown.NewHeaderSplitter(ctx,&markdown.HeaderConfig{Headers:map[string]string{"#":"title","##":"section"},})g.AddDocumentTransformerNode("Splitter",splitter)// 节点 3:存入向量数据库(返回存储 ID 列表)indexer,_:=newVectorIndexer(ctx)g.AddIndexerNode("Indexer",indexer)// 连线:START -> Loader -> Splitter -> Indexer -> ENDg.AddEdge(compose.START,"Loader")g.AddEdge("Loader","Splitter")g.AddEdge("Splitter","Indexer")g.AddEdge("Indexer",compose.END)returng.Compile(ctx,compose.WithGraphName("KnowledgeIndexing"))}

运行:

pipeline,_:=BuildKnowledgeIndexing(ctx)ids,_:=pipeline.Invoke(ctx,document.Source{URI:"/docs/manual.md"})fmt.Printf("已存入 %d 个知识块\n",len(ids))

流水线的好处:

  • 单节点可测:用Splitter单独测切片效果,不依赖 Loader
  • 可观测:插入 callback 监控每步耗时、输出块数
  • 可替换:换RecursiveSplitter替代MarkdownHeaderSplitter,其他节点不动

七、一个必须记住的原则:MetaData 只能增不能减

Transformer切片时,必须把原 Document 的 MetaData 完整复制给每个切片,只能追加新 key,不能删除已有 key。

原因:Document 的溯源信息(来源文件、章节、时间戳)在流水线最开始由 Loader/Parser 打上。如果 Splitter 把这些信息丢掉,下游就无法追溯"这条知识来自哪里"——出了问题没法排查,用户问"你说的这个依据从哪来?"也答不上。

eino-ext 的几个 Splitter 实现都遵守这条规则,切片时做的是deep copy(原 MetaData) + 追加新 key


小结

原始文件 (PDF / HTML / MD / Word) ↓ Loader(搬运工) 字节流 ↓ Parser(翻译官,TextParser / HTMLParser / ExtParser) [Document] ← 完整文档,可能几万字 ↓ Transformer(切割机) [Doc, Doc, Doc...] ← 每块 1000~2000 字 ↓ Indexer 向量数据库

选哪个 Splitter?

场景推荐
通用文本,不在乎结构RecursiveSplitter
有标题层级的 Markdown 文档MarkdownHeaderSplitter
爬下来的结构化网页HTMLHeaderSplitter
质量优先,不差 API 调用钱SemanticSplitter

Document 组件是 RAG 的地基。地基的质量直接影响检索精度:块切得太大,塞不进上下文;切得太小,丢失上下文;切错地方,语义断裂。值得认真选型。

http://www.jsqmd.com/news/1039662/

相关文章:

  • Microchip嵌入式开发资源全解析:从工具链到学习路线
  • 英雄联盟专业录像编辑工具:用League Director打造电影级游戏视频
  • Honey Select 2:5分钟搞定完整汉化与模组整合,开启你的终极游戏体验 [特殊字符]
  • 基于ArcFace与ResNet的深度度量学习实践:从细粒度分类到特征空间构建
  • 2026年6月济南百岁山配送厂家推荐:专业配送服务如何重塑企业用水体验 - 品牌鉴赏官2026
  • 2026年更新:南宁柳沙片区朋友聚会烧烤店联系方式与选择指南 - 品牌鉴赏官2026
  • 还在为音频编辑烦恼吗?免费开源神器如何重塑你的创作体验?
  • 鹤壁高口碑黄金铂金回收白银回收实体老店排行 5 家靠谱门店电话地址全收录
  • 零壹教育:动态定价时代,商家如何用爬虫技术做好价格监测
  • 百度网盘批量转存终极指南:3分钟搞定100个链接的高效神器
  • 技术深度:iCloud Photos Downloader的架构设计与容错机制
  • 小说下载终极指南:5分钟学会保存全网小说,告别404错误
  • 2026年中海珠区老酒回收怎么联系?深度剖析专业服务商广州劲人电子商务有限公司 - 品牌鉴赏官2026
  • MPC5200 USB主机控制器寄存器详解与DMA协同设计
  • 2026 申博哪个机构靠谱?业内 5 大硬核筛选标准,申博人闭眼参考
  • Win11Debloat:如何一键清理Windows 11预装软件和广告,让你的电脑速度提升51%
  • Adobe-GenP技术架构深度解析:二进制补丁机制与自动化破解原理
  • 计算机毕业设计之大学生素质拓展学分系统
  • 在PC上畅玩Switch游戏:yuzu模拟器完全入门指南
  • 网安人专属的6个副业方向,每一个都是一条技术后路
  • Python模块:random模块的随机数生成与应用
  • 我按结构化方法重写了 7 个常用 Prompt,LLM 输出准确率从 47% 跳到了 83%
  • 2026年现阶段,江苏企业选择有实力的仓库仓储服务团队,需要关注哪些核心能力? - 品牌鉴赏官2026
  • 三相温升交直流升流器的结构组成
  • 2026 集成式 RJ45 插座连接器行业市场分析TOP品牌厂家排行——佳迅智能(JIAXUN)脱颖而出
  • Illustrator设计师的救星:告别重复劳动,让替换工作自动化
  • TCN75A I2C温度传感器在低功耗物联网节点中的实战应用
  • Microchip 24XX128 I2C EEPROM选型与实战:从硬件设计到软件驱动的嵌入式存储指南
  • 暗黑破坏神2存档可视化编辑:告别十六进制,拥抱直观操作
  • 2026年近期河南木质粉状活性炭选型指南:为何河南神华活性炭有限公司值得重点关注 - 品牌鉴赏官2026