13|知识入库:从原始文档到可检索索引 - eino教程
13知识入库从原始文档到可检索索引《Eino 实战用 Go 构建 AI 应用与智能体》· 第 13 篇关键词Document Loader、Transformer、Embedding、Indexer、切分、元数据前置条件已完成 11Tool 与 ToolsNode。从这一篇起进入「工具与知识」的第二条线让模型能参考企业自己的资料。大模型不知道你公司的处理规范、内部流程和产品细节。要让它“知道”最实用的办法不是重新训练模型而是在提问时把相关资料一并交给它。这就是检索增强生成RAG。RAG 分两半数据侧把资料整理成可检索的形式和查询侧提问时召回并组装上下文。这一篇讲数据侧。很多 RAG 效果差问题并不在模型而在这一半没做好。一、四条流水线数据处理通常分四步每一步对应一个组件抽象原始文档 ↓ Loader 加载从文件、网页、数据库读取内容 文档 ↓ Transformer 转换清洗、切分、补充元数据 片段chunks ↓ Embedding 向量化把文本变成可比较的向量 向量 文本 ↓ Indexer 入库写入可检索的索引 可检索索引四个抽象的接口都很简单组件接口说明LoaderLoad(ctx, Source) ([]*Document, error)从来源读取TransformerTransform(ctx, []*Document) ([]*Document, error)转换、切分EmbedderEmbedStrings(ctx, texts) ([][]float64, error)批量向量化IndexerStore(ctx, docs) ([]string, error)写入索引返回 ID注意 Loader 和 Transformer 的输入输出都是[]*schema.Document而Document只有三个字段typeDocumentstruct{IDstringContentstringMetaDatamap[string]any}结构简单是有意为之元数据是携带来源信息的地方后面会重点讲。二、切分最容易做坏的一步文档通常太长不能整篇塞进模型上下文所以要切分成片段chunk。切分策略直接决定检索质量。常见的切分方式策略做法适用固定长度按字符数或 Token 数切结构松散的文本按结构按标题、段落、列表切有清晰层级的文档重叠切分相邻片段保留一部分重叠避免句子被拦腰截断固定长度切分有个经典毛病会产生无意义的碎片。下面这段程序会当场暴露它本文写作时已实机运行packagemainimport(contextfmtlogstringsgithub.com/cloudwego/eino/components/documentgithub.com/cloudwego/eino/components/embeddinggithub.com/cloudwego/eino/components/indexergithub.com/cloudwego/eino/schema)// ---- 1. Loader从数据源读取原始文档 ----typememoryLoaderstruct{docsmap[string]string}func(l*memoryLoader)Load(_context.Context,src document.Source,_...document.LoaderOption)([]*schema.Document,error){content,ok:l.docs[src.URI]if!ok{returnnil,fmt.Errorf(未找到来源: %s,src.URI)}return[]*schema.Document{{ID:src.URI,Content:content,// 保留来源信息便于后续引用与追溯。MetaData:map[string]any{source:src.URI},}},nil}// ---- 2. Transformer切分文档并保留元数据 ----typefixedSplitterstruct{sizeint}func(s*fixedSplitter)Transform(_context.Context,src[]*schema.Document,_...document.TransformerOption)([]*schema.Document,error){varout[]*schema.Documentfor_,doc:rangesrc{runes:[]rune(strings.TrimSpace(doc.Content))fori,chunk:0,0;ilen(runes);i,chunkis.size,chunk1{end:is.sizeifendlen(runes){endlen(runes)}// 复制元数据避免多个分片共享同一个 map。meta:make(map[string]any,len(doc.MetaData)2)fork,v:rangedoc.MetaData{meta[k]v}meta[chunk_index]chunk outappend(out,schema.Document{ID:fmt.Sprintf(%s#%d,doc.ID,chunk),Content:string(runes[i:end]),MetaData:meta,})}}returnout,nil}// ---- 3. Embedder用确定性向量模拟避免依赖外部服务 ----typefakeEmbedderstruct{}func(fakeEmbedder)EmbedStrings(_context.Context,texts[]string,_...embedding.Option)([][]float64,error){vecs:make([][]float64,0,len(texts))for_,t:rangetexts{vecsappend(vecs,[]float64{float64(len([]rune(t))),1,1})}returnvecs,nil}// ---- 4. Indexer保留来源与元数据 ----typememoryIndexerstruct{docs[]*schema.Document}func(idx*memoryIndexer)Store(_context.Context,docs[]*schema.Document,_...indexer.Option)([]string,error){ids:make([]string,0,len(docs))for_,d:rangedocs{idx.docsappend(idx.docs,d)idsappend(ids,d.ID)}returnids,nil}funcmain(){iferr:run();err!nil{log.Fatal(err)}}funcrun()error{ctx:context.Background()loader:memoryLoader{docs:map[string]string{handbook/printer.md:打印机无法联网时先检查网线是否插紧再确认所在网段是否允许办公设备接入。若仍离线可通过工单系统申请网络支持组协助排查。,}}splitter:fixedSplitter{size:30}index:memoryIndexer{}// 串联三步加载 - 切分 - 入库。docs,err:loader.Load(ctx,document.Source{URI:handbook/printer.md})iferr!nil{returnfmt.Errorf(加载失败: %w,err)}fmt.Printf(加载到 %d 篇文档\n,len(docs))chunks,err:splitter.Transform(ctx,docs)iferr!nil{returnfmt.Errorf(切分失败: %w,err)}fmt.Printf(切分为 %d 个片段\n,len(chunks))ids,err:index.Store(ctx,chunks)iferr!nil{returnfmt.Errorf(入库失败: %w,err)}fmt.Printf(入库 %d 个片段\n,len(ids))_fakeEmbedder{}// 示例中向量化留到第 14 篇展开// 检查元数据是否被正确保留来源可追溯分片序号可定位。for_,d:rangeindex.docs{fmt.Printf( id%-24s source%-20s chunk%v 内容%q\n,d.ID,d.MetaData[source],d.MetaData[chunk_index],d.Content)}returnnil}实机输出加载到 1 篇文档 切分为 3 个片段 入库 3 个片段 idhandbook/printer.md#0 sourcehandbook/printer.md chunk0 内容打印机无法联网时先检查网线是否插紧再确认所在网段是否允许 idhandbook/printer.md#1 sourcehandbook/printer.md chunk1 内容办公设备接入。若仍离线可通过工单系统申请网络支持组协助排查 idhandbook/printer.md#2 sourcehandbook/printer.md chunk2 内容。请特别看第三个片段内容只有一个句号。这种片段进入索引后可能因为“长得像任何东西”而被检索出来占用宝贵的上下文位置还可能误导模型。这类噪声不是模型的问题是数据流水线的问题。实践中的改进方向过滤过短片段。设一个长度下限低于阈值的直接丢弃或合并到相邻片段。按语义边界切分。优先在段落、句子、标题处断开。加入重叠。相邻片段保留一部分重叠避免关键信息被切断。切完做一次抽查。打印若干片段人工看一遍比盲目调参有效得多。三、元数据决定你能不能追溯来源示例里每个片段都带了两项元数据source来自哪个文件。chunk_index在原文中的位置。这两项看似简单却是后面几件事的前提元数据支撑的能力来源回答时标注出处、点击查看原文位置定位原文、按需取回上下文分类/标签按部门或权限过滤检索范围版本/时间判断资料是否过期没有元数据的知识库检索得再准也无法让人信任——用户看到答案却不知道从哪来就无法判断该不该采纳。还有一个容易忽略的细节切分时要复制元数据 map而不是让多个片段共享同一个 map。meta:make(map[string]any,len(doc.MetaData)2)fork,v:rangedoc.MetaData{meta[k]v}meta[chunk_index]chunk如果直接复用doc.MetaData再改chunk_index所有片段会指向同一个 map最后全都变成相同的值。这类 bug 很隐蔽因为检索仍能工作只是来源信息错乱。四、向量化与入库真实的向量化由Embedder完成vecs,err:embedder.EmbedStrings(ctx,texts)// vecs[i] 对应 texts[i] 的向量两点提醒Embedding 模型与查询时必须用同一个。入库用一种、检索用另一种向量空间不一致结果会毫无意义。批量调用别逐条调用。EmbedStrings接受切片一次提交多条能显著降低开销。Indexer负责写入ids,err:index.Store(ctx,docs)它会返回分配的 ID 列表顺序与传入的docs对应。注意这里只传docs向量通常通过Document携带或由索引实现内部处理具体方式取决于你使用的索引实现。示例里的fakeEmbedder和memoryIndexer都是为了演示而写的模拟实现。真实项目应接入向量数据库或检索服务——Eino 的eino-ext提供了多种集成。五、增量更新别每次全量重建示例代码是全量重建索引。真实知识库会持续变化新增文档、修订内容、删除过期资料。全量重建代价高也不必要。常见做法用稳定的文档 ID。例如文件路径加内容哈希便于判断“这是同一篇文档的新版本”。内容未变则跳过。计算内容哈希未变化就不重复向量化——Embedding 调用是要花钱的。删除要一致。文档删除或修改后旧片段必须从索引中移除否则会检索到已作废的内容。保留版本元数据。便于排查“为什么检索到了旧版本”。一条实用的原则索引更新的正确性和检索算法同等重要。检索到过期或已删除的内容比检索不到更危险。六、常见问题检索总是召回不相关内容先检查切分质量。片段太长会掺入无关信息太短则缺乏上下文。打印片段人工抽查。检索到单个标点或空白片段切分没有过滤短片段。设置长度下限。来源信息全部相同或错乱切分时共享了元数据 map。为每个片段创建独立的 map。入库后检索不到确认入库与检索用的是同一个 Embedding 模型以及同一个索引。索引更新后仍返回旧内容删除逻辑不完整。修改和删除都要同步更新索引。向量化耗时或费用过高内容未变时重复向量化了。加哈希判断跳过。七、小结数据侧流水线是“加载 → 转换 → 向量化 → 入库”四个组件接口都很简单。切分是效果的关键固定长度会产生无意义碎片需要过滤、按语义边界切分并适度重叠。元数据承载来源与位置是引用、追溯、过滤的基础切分时要复制而非共享。入库与检索必须使用同一个 Embedding 模型。增量更新要处理新增、修订、删除三种情况未变化的内容不必重复向量化。下一篇《RAG 入门构建带来源引用的知识问答》我们把检索接进流程让回答有据可依。参考资料与版本说明资料核对日期为2026 年 10 月 9 日Document Loader 使用说明Document Transformer 使用说明Embedding 使用说明Indexer 使用说明Eino 主仓库本文示例按Eino v0.9.21编写。文中四个组件均使用自定义的模拟实现不依赖外部服务已实机运行正文输出包括那个只有句号的碎片为真实结果。

相关新闻

普通宽带 + SD-WAN,真的能替代昂贵专线吗?稳定性与安全性深度解析

普通宽带 + SD-WAN,真的能替代昂贵专线吗?稳定性与安全性深度解析

在企业数字化转型的浪潮中,多分支互联、混合云接入、SaaS 应用普及正在重构企业广域网的需求模型。沿用了二十余年的 MPLS 专线,曾经是企业组网的 "标准答案",如今却在成本、部署效率与业务灵活性上频频遭遇瓶颈。与此同时&#xf…

2026/10/17 6:48:00 阅读更多 →
雷达点云地面过滤:为什么Ray Filter比RANSAC更适配激光扫描物理特性

雷达点云地面过滤:为什么Ray Filter比RANSAC更适配激光扫描物理特性

简介:本资源是一个面向ROS机器人开发者的点云地面滤除实践项目,聚焦于自动驾驶与移动机器人环境感知中的关键预处理环节——利用Ray Filter算法高效分离地面与非地面点云。项目基于PCL库实现,封装为可直接编译运行的ROS节点,适用于…

2026/10/17 6:47:00 阅读更多 →
代码审查实战:open-code-review 打造高效流程与自动化落地

代码审查实战:open-code-review 打造高效流程与自动化落地

1. 代码审查这件事,为什么总让人又爱又恨代码审查(Code Review)在软件研发流程里算是个“老生常谈”的话题。每个人都知道它重要,团队规范里也写着“合入前必须走 Review”,但真正执行起来,问题一大堆&…

2026/10/17 6:47:00 阅读更多 →

最新新闻

IDA逆向工程实战:从静态分析到二进制补丁的完整流程

IDA逆向工程实战:从静态分析到二进制补丁的完整流程

干逆向这一行的,绕不开IDA。不管是分析一个陌生二进制文件到底干了什么、给某个老程序打补丁,还是解CTF里的Reverse题,IDA几乎都是所有人默认打开的第一件工具。它最核心的价值就是把机器码翻译成人类能读的伪代码和汇编,同时把整…

2026/10/17 7:18:06 阅读更多 →
MySQL数据库与表管理实战:从建库建表到字符集与DDL避坑指南

MySQL数据库与表管理实战:从建库建表到字符集与DDL避坑指南

数据库管理这事,入门容易,想管得明白、少在晚上被电话叫醒,靠的全是细节。MySQL作为使用面最广的开源关系型数据库之一,大家对它的印象往往是“装完就能用”,可真到了建库建表、调整表结构、处理字符集乱码、删错数据的…

2026/10/17 7:18:06 阅读更多 →
数据加密如何重塑仓储管理系统:字段级加密、密钥与查询设计

数据加密如何重塑仓储管理系统:字段级加密、密钥与查询设计

做仓储管理系统,大家第一反应就是把入库、出库、库存台账、盘点这些业务功能铺开,最后再在功能树上挂一个“数据加密”就算交差。但这份“基于数据加密的仓库货物管理系统设计与实现任务书”的落脚点,恰恰不在“仓库货物管理”这六个字&#…

2026/10/17 7:18:06 阅读更多 →
5款AI编程神器横评:从补全到自动Debug

5款AI编程神器横评:从补全到自动Debug

2026年还没过半,AI编程工具的洗牌速度已经让不少人看花了眼。前两年大家还在为一款编辑器疯狂买单,那场面我至今记得——好像装上它,代码就能自己写完。如今再回头看,那句话不算全错,只是说得太早了。因为它擅长的本来…

2026/10/17 7:18:06 阅读更多 →
MySQL 8.0 CTE实战:从嵌套子查询到清晰报表的SQL重构指南

MySQL 8.0 CTE实战:从嵌套子查询到清晰报表的SQL重构指南

最近在做一批历史报表SQL的梳理,遇到一个印象很深的查询:统计各区域销售Top 10产品,源数据就三张表,写出来的SQL却有将近三百行,光子查询嵌套就有七层。不是说跑不动,而是后来同事想在这个基础上加一个过滤…

2026/10/17 7:18:06 阅读更多 →
鸿蒙应用运行机制详解:从点击图标到第一帧画面

鸿蒙应用运行机制详解:从点击图标到第一帧画面

上篇说完环境搭建、建完工程、能跑出“你好,鸿蒙”的页面之后,很多初学的朋友都会冒出一个问题:我就按了一下图标,屏幕上就从无到有出现了一个应用界面,这中间到底发生了什么事?这篇就专门把“应用是怎样运…

2026/10/17 7:17:05 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →