1. 两天的学习主线与内容取舍1.1 为什么把1月23日和1月24日放在一起记录2026年1月23日到24日我把手头一个内部知识库问答项目重新撸了一遍。这两天没有追新框架也没追热点就是老老实实把整套RAG链路从能跑Demo的阶段推进到能扛真实任务的阶段。之所以把这两天的学习笔记写在一起是因为它们本来就是同一件事的两个面第一天解决“检索得准不准”第二天解决“系统稳不稳、能不能承接真实并发”。很多入门教程只讲前者但实际项目里往往是后者决定方案能不能上线。这段学习的受众我建议是在做知识库问答、本地文档检索、AI应用落地的开发者。如果你手头刚好有一个“文档丢进去问它答它”的项目这两天记录里的分块参数、重排策略、异步任务设计可以直接拿去参考。如果是纯小白也别急着划走我会把每个环节的“为什么”都解释清楚不光是给参数还会给参数背后的判断依据。我见过不少学习笔记贴一堆配置截图却不解释为什么这么配换个项目完全套不上那笔记基本就等于白写了。1.2 第一天主线从原始文档到可检索片段第一天的主线很明确把散落在不同格式文档里的知识转成一套能被快速检索的片段库。完整链路是“文档加载 - 文本清洗 - 分块 - 向量化 - 写入向量库”。花了大半个上午做的不是写代码而是折腾分块。原因很简单向量检索质量的上限七成在分块阶段就已经决定了。加载和清洗当然也重要比如PDF有时候会带出页眉页脚Markdown会残留很多空行和特殊符号这些脏数据不处理分出来的块就会把噪声一并向量化后期检索时经常召回一些莫名其妙的内容。这里还要回答一个基础问题为什么用向量检索而不是老式关键词检索。关键词检索只能匹配字面比如文档里写“导出报表”用户问“把表格下载下来”这俩字面完全不搭关键词检索就漏了。向量检索先把文本变成高维空间里的向量语义相近的内容距离更近所以能处理“换了一种说法但其实在问同一件事”的情况。代价是它需要模型推理、需要向量库链路更复杂。知识库问答这种场景复杂链路是划算的因为用户的问题千变万化不可能靠维护同义词表来覆盖。1.3 第二天主线异步化、缓存与可评估第二天做的事是把第一天的链路拆成能抗压的系统。最大的变化是把“上传文档后同步建索引”改成“上传后立即返回后台异步处理”。这一步看着简单其实牵扯出任务表、状态机、失败重试、幂等写入、内容指纹缓存一堆问题。处理同样一批文档异步化之后用户体验完全不一样而且后台还能批量做向量化吞吐比逐条调接口高很多。另外花了一个下午搭评估集。没有评估集调参基本靠感觉。我准备了20组“问题-标准答案片段”对应关系每次改完参数就跑一遍看Top5命中率变化这样折腾分块和阈值时心里才有底。这也是第一天和第二天内容衔接最关键的一环第一天的任何参数改动都要靠第二天的评估结果来验证。如果没有这套验证机制你改完参数后只知道“好像好了”但说不出好在哪里下次坏了也不知道该回滚哪个改动。2. 第一天RAG检索质量的关键参数与踩坑2.1 分块策略为什么不能只按固定长度切很多人第一版分块就是按500字符硬切。我当时也这么干过测试时问“某某模块的权限怎么配置”检索回来的片段中间夹了无关内容答案自然跑偏。固定长度分块的优点是实现简单、开销稳定缺点是不知道什么时候会把一个完整语义单元拦腰截断。而且截断位置一旦不对后续向量化出来的向量本身就是一个残缺信息后面所有环节都会被带偏。实操时我用的参数是Markdown文档先按一级、二级标题切成章节如果某个章节字符数超过1500再按段落符、句号、逗号逐级向下切每两个相邻块之间保留100字符的重叠。为什么要重叠因为知识点往往跨段落存在比如“先配置A再调用B”可能被切开重叠能降低边界截断的损失。重叠也不是越大越好太大会让不同块的内容高度相似检索时容易返回一堆重复片段。口说无凭我把当天用的简化版拆分逻辑贴出来实际开发中可以根据语言的字符串处理能力换成对应写法。整段逻辑不复杂重点在递归降级和重叠参数搜索问题时打印出“上下文标题块序号”的组合会非常方便推理。代码如下import re from dataclasses import dataclass dataclass class Chunk: text: str source: str section: str def split_markdown_by_headers(md_text, source_path): # 按标题切章节保留标题层级作为section上下文 sections re.split(r(?m)^(#{1,3})\s(.*?)$, md_text) # 返回标题和正文块正文过长再递归切 ...实际开发中这一步的核心不是“比示例更复杂”而是“怎么保住上下文信息”。我给每个块额外记录所属章节标题作为元数据。检索到某一块之后提示词里携带章节上下文比只给一段裸文本要准确得多。这个小技巧在后续评估里确实让答案可读性改善了不少。另有一个容易被忽略的地方分块时保留列表型内容的换行结构。比如操作步骤是“1. 打开设置 2. 找到导出 3. 点击确定”如果你按句号把列表拆散了模型看到的就是零散招式完全对不上用户问的“先后顺序”。2.2 向量化与检索维度、相似度、阈值的取舍向量化模型的选择我第一反应是“维度越高越强”测试下来发现这是个误区。当前主流开源向量模型的维度常见的有384、768、1024这几个档次。维度越高表达信息越丰富但存储体积和检索延迟同步上涨。对我这个内部知识库场景来说768维已经够用在检索延迟和控制成本之间能取一个平衡。如果文档量只有几千篇384维甚至也够关键是拿自己的数据实测而不是盲信排行榜。关于相似度我默认用了余弦相似度。这里有个关键细节写入向量库之前对向量先做一次L2归一化。归一化之后点积和余弦相似度在数学上是等价的后续不管代码里调的是哪种距离行为都是稳定的。否则你会在排查相似度阈值问题时搞不清楚到底是模型问题还是算法口径不一致。这个“口径统一”的坑很隐蔽尤其当你从某个向量库迁移到另一个向量库时两边默认距离算法可能不一样线上效果忽好忽坏先查这里。阈值方面我习惯设一个0.30到0.35的下限。低于这个值的片段哪怕召回来也会在后续重排阶段拉低质量干脆在粗召回阶段就过滤掉。这个数字不是拍脑袋定的是拿评估集跑了几轮看命中率和误召回变化得出来的。不同模型、不同领域阈值最好都重新跑一遍别人的配置只能当起点不能当终点。另外阈值和召回数量要联动考虑阈值设太高可能导致候选太少、正确答案被全量过滤设太低又会让后续重排压力变大。2.3 重排为什么多用一次重排比单纯加大召回更划算检索阶段我让向量库返回Top 20的候选片段然后交给重排模型重新打分最后只取Top 5送入大模型。刚开始我也觉得这一步似乎是额外开销直接召回Top 5不就行了吗答案是不行。向量召回看的是语义相似度一个片段整体相似度很高但真正能回答问题的关键句可能在很靠后的位置只取Top 5容易把正确答案丢掉。反过来说先把候选放宽到20条再用重排模型逐条精读打分准确率明显更稳。成本也没那么吓人重排模型只在20条候选上跑耗时通常几十毫秒到百毫秒级别和生成回复的秒级耗时相比可以接受。用一句话概括这层设计向量检索是粗筛重排是精读。粗筛负责“别漏”精读负责“别错”。如果你追求简单也可以第一步就只召回10到15条但从我的测试结果看20条再重排是性价比比较高的组合。重排之后不要急着把Top 5拼起来直接丢给模型我建议按照重排分数从高到低排序同时附带“章节标题”再拼接这样生成阶段能明显感觉到上下文更完整。2.4 提示词与引用让模型“会拒绝”而不是“瞎编”提示词设计属于那种“看起来很虚、实际影响巨大”的环节。我的模板包含三个要点第一只依据给定检索片段回答问题第二如果片段中找不到明确答案直接回答不知道不要自行编造第三回答时标注对应引用编号方便溯源。初版我漏了“会拒绝”这一条。测试时问了一句资料里根本没写的问题模型依然一本正经地给出建议这在真实业务里是灾难。加了一行约束之后至少表面上稳住了。引用编号也很好用用户能自己核对排障时也能快速定位是哪条片段影响了答案。这个设计改动成本极低但对系统可信度提升很大。我曾经遇到过一种场景用户对结果不满意来投诉说“答案有问题”。没有引用编号时我只能自己猜是模型生成的问题还是检索片段的问题有了编号之后直接把片段原文拉出来一眼就知道是不是分块把关键内容切掉了。这个习惯值得保留哪怕只是内部工具不为给用户看也要为自己排障留一条线索。3. 第二天从Demo到工程化的一天3.1 为什么索引流程必须异步化第一天晚上我把整个流程跑通了兴奋之余直接给前端接口“上传 - 同步建索引 - 返回成功”。结果一测就有点尴尬传了60来个文档同步接口耗时接近3分钟前端早就超时了。这里的原因很好理解文档加载、清洗、分块、向量化、写库每一步都吃时间尤其是向量化属于计算密集型操作。如果每个用户上传文档都在同步接口里等结果Web进程会被长期卡住其他普通请求也跟着遭殃。异步化是个更自然的解决方式。可以类比饭店点菜用户下单后不需要站在后厨等菜全部做完拿个小票就能先走后厨按顺序出菜做完通知取餐。技术上的实现就是接口收到上传请求后只做两件事生成任务ID、把任务丢进队列然后立刻返回。后台Worker慢慢消费队列处理完再更新状态。这个改动最大的收益是用户无感无论你是一次传10个还是1000个文档前端都像瞬时应答一样。对于文本量特别大的文档异步也能让向量化模型批次更稳定不容易出现内存被打满的情况。3.2 异步任务的最小可用设计异步任务看着简单实际需要一张任务表来记录状态。我当天的最小设计是task_id、status、doc_fingerprint、error_msg、created_at、updated_at、retry_count。status只有四个值pending、running、done、failed。用户拿task_id查询进度Worker处理完更新到done。这里要特意处理两个问题失败重试和幂等。Worker可能因为网络抖动、第三方接口临时报错挂掉任务必须允许重跑。重跑的时候如果前面已经写了一半向量库就会产生重复数据。解决办法是给块记录一个稳定的唯一ID比如“文档ID章节路径块序号”写入时按ID做覆盖或跳过。有了这个ID整个任务跑多少次都不会污染数据这就是幂等的含义。另外任务表里的error_msg字段不能省。只要Worker失败就把堆栈和失败阶段写进去这样排查问题时不用翻日志猜。我在这个字段上吃过亏一开始只记录statusfailed完全不知道是加载失败、向量化失败还是写库失败后来加上错误信息后大部分问题能在几秒内定位。队列组件也不需要多复杂本地项目用最简单的内存队列加两个Worker就够了真要上生产再考虑引入持久化消息系统也不迟。3.3 缓存与去重别让同一份文档反复吃算力异步化解了接口超时但没解决“同一份文档被反复上传”的问题。我做的第一个优化是文档指纹缓存。指纹怎么算可以用文件内容加修改时间取哈希。文档没变直接复用旧索引文档变了才需要删除旧块、重新建索引。这样一改重复上传项目文档的场景几乎不再产生计算成本。去重同样重要。有时用户会分多次上传同一份文件指纹缓存只能避免完全相同的文件重复处理但如果文件内容一样、文件名不同指纹是否忽略文件名就取决于你的设计。我建议指纹只基于内容本身不包含文件名这样效果更稳。这里有一个容易踩的坑指纹粒度太粗会把“不同版本”也当成同一份文档。比如用户改了文档里一句话内容哈希肯定变了指纹会判定为不同文档这是对的。但如果指纹只取文件大小或者文件名就会出现误判缓存、老索引一直生效的尴尬。我在最初版本就用过“文件名文件大小”做指纹结果文档内容改了但大小没变线上一直返回旧答案。后来全部改成“内容哈希修改时间”的组合才彻底解决。3.4 效果评估用20组问题来检验检索质量这一小节能展开的不多但价值很大。没有评估集你所有的参数调整都是在自我感觉良好。我花了一个下午把知识库里最有代表性的业务问题整理了20组“问题-标准答案片段”对应关系。每次改完分块策略、相似度阈值、重排参数就跑一遍评估脚本统计Top5命中率。指标上我主要看Recall5和MRR。前者表示“正确答案是否出现在前5条候选里”后者更严格会看正确答案排在第几位。我调完参数后Top5命中率从0.75提到0.90左右MRR也有明显提升。有这组数据撑着后面再有人质疑检索效果不好至少知道问题出在哪个参数上而不是两眼一抹黑地试。评估集需要注意一点问题要有代表性不能全挑容易的来。我当时特意加入了“换一种说法问同一件事”、“文档里没有答案的问题”、“需要跨两个片段才能答完整的问题”这三类结果第一轮评估就发现好几个隐藏问题被漏掉了。如果没有这些刁钻问题你测出来的分数再高上线也照样翻车。4. 踩坑记录与排查技巧实录4.1 这两天遇到最典型的几个问题现象原因解决办法检索返回片段内容对但答案仍跑偏分块过大片段里混了多主题内容调小分块上限保留标题前缀中文问题经常召回乱切的内容分块逻辑只按英文句号、逗号切补充中文标点必要时按句号、问号、感叹号切扫描版PDF看着能搜到生成结果却很怪检索到的可能是OCR噪声对应的向量先做OCR质量检测低置信度片段不要写入同一份文档重新上传后索引没生效忽略内容指纹未识别变更指纹融合“内容哈希修改时间”上传大目录时接口超时同步建索引改成异步任务状态轮询4.2 现场复现一个“答非所问”的排查过程有一个案例特别有代表性。用户问“怎么导出报表”系统召回了几条片段内容都是“报表模块权限配置”看起来很相关但就是没解释导出流程。我第一反应是阈值设错了调高阈值后情况没好转。后来打开召回片段逐个看发现问题出在分块上“导出报表”的操作步骤和一个很长的权限说明在同一段里被切到了一个超长块里。这段块的向量整体和“导出”相关但重排后Top 5里真正讲“导出步骤”的那句其实在块的中后部。解决方法是把重叠从50字符加大到100字符同时在分块策略里规定“操作步骤”这种列表型内容单独成块。另一层收获是排查时千万不要只看Top 1要把候选片段打印出来逐条过一遍才能定位是召回问题还是重排问题。这个思路对RAG排障很有用我也养成了每次改参数都会打印“召回片段原文文本”的习惯。做过几次之后你会发现大部分“答案不对”的锅不在模型而在检索到的片段本身就不对。4.3 几个容易被忽略的小细节最后一节补几个细节。第一写入向量库的ID要包含文档ID和块序号不要只放时间戳否则删旧索引时很难精准清理。第二批量向量化的吞吐远高于逐条处理我测试批量32条并发时整体耗时比逐条调用快了接近一个数量级前提是模型推理环境的批量参数要拉开。第三生成阶段温度参数不要调高知识库问答保持0到0.3之间即可太高会引入不必要的发散。第四分块或向量化时尽量保留标题层级作为前缀这样模型回答时更像“带着目录看全文”而不是看一团散装文字。这些小细节单拎出来都很简单但组合在一起效果差距就很明显。我个人在实际操作中最想强调的还是评估意识和排障习惯。这两天结束后这套知识库问答系统从“演示能用”变成了“上线能扛”真正靠的不是某一两个炫技参数而是把分块、阈值、异步、缓存这些基础环节一个个抠实。最后再分享一个小技巧每次改动参数前先把当时的检索结果截图存档跑完再对比你会发现“凭感觉调参”有九成都是在做无用功。先有数据再谈优化这大概是这两天学习笔记里最有价值的一句话。
阅读完成 · 觉得有帮助?