☰
AI应用工程化实战:从零手工搭建RAG全链路,破解生产环境四大坑
2026/9/30 15:36:42 网站建设 项目流程

做AI应用开发超过半年,我发现自己回答最多的问题不是“模型选哪个”,而是“为什么我的东西一上生产就废”。这个问题问的人多了,我才意识到大家缺的并不是Prompt技巧,而是一整套从数据到评估的工程能力。我落地过一个叫 ai-engineering-from-scratch 的项目,核心思路只有一个:不靠任何封装的AI框架,从数据清洗、索引构建、检索召回、提示词设计到评估闭环,全链路亲手搭一遍。这篇文章就是那个项目的完整复盘,包括我为什么坚持“从零开始”,每一层怎么选型、怎么写、怎么排错,以及踩过的那些在文档里根本找不到的坑。适合两类人看:一是刚入门AI应用、想系统建立知识体系的人;二是已经在用API接功能、但被检索质量、幻觉、成本问题反复折磨的工程师。

1. 整体设计思路:真正的“从零”不是从张量开始

1.1 先划清“从零”的边界

项目刚开始,我遇到最大的困惑就是“从零”到底指什么。是不是得像深度学习教科书那样,手写一个Transformer、手动实现反向传播,才算从零?我试过,花了两周时间在PyTorch里复现Attention机制,跑出来的效果一塌糊涂,对业务毫无帮助。后来我意识到,那是模型训练工程师的从零,不是AI应用工程师的从零。

绝大多数团队真正缺的不是再造一个模型,而是把现成大模型稳定接进业务流程的工程能力。所以我给这个项目定的边界很明确:不借助LangChain这类端到端框架,从数据准备、向量化索引、检索召回、生成链路、评估闭环五层,亲手搭一条完整的AI应用流水线。每一层都知道它是怎么工作的,出了问题知道去哪一层排查,而不是对着一个黑盒束手无策。

1.2 五层能力模型与项目路线图

我这张路线图后来也分享给了团队,基本就是AI应用工程的能力分层:

层级核心职责关键问题
数据层采集、清洗、切块、增强数据质量直接决定效果上限
索引层向量化、存储、元数据管理索引结构决定检索效率
检索层召回、重排、过滤找得到相关材料是一切的根基
生成层上下文组装、提示词、输出约束让模型只基于证据说话
评估层指标、评测集、线上追踪没有度量就没有迭代

这五层是有依赖顺序的。我见过不少团队一上来就花大力气调Prompt,结果检索回来的东西根本不对,Prompt调得再花哨也白搭。所以项目路线也按这个顺序推进:先把数据和检索做扎实,再碰生成,最后才建评估。每一步都跑通并写测试,才进入下一层。

1.3 为什么“亲手搭一遍”反而更快

有人会觉得,市面上现成框架一大把,自己造轮子不是傻吗?我的体会恰恰相反。直接用框架,你确实能在一小时里搭出一个Demo,但一旦出了问题,框架的封装层会像俄罗斯套娃一样把错误包在里面。LangChain报一个“Retriever returned no documents”,你根本不知道是embedding模型挂了、向量库连不上、还是切块切出了空文档。

亲手搭一遍,每个环节你都捏过一遍,相当于给系统画了一张“内部地图”。后面再切换到LangChain或者更重的生产框架,你一眼就能看出它在每层做了什么、做得好不好。这个“先裸写,再上框架”的顺序,我后面在实操里还会细说。它前期看着慢,实则在帮你规避后期最大的成本:排查问题的成本。

2. 技术选型:动手之前先把这四个决策想清楚

2.1 模型层:本地小模型打底,云端大模型做生产

模型选型是第一个绕不开的决策。我把选项分成两条路:本地方案和API方案。本地方案用Ollama、llama.cpp或者HuggingFace Transformers跑开源模型,你说什么它都能跑,但效果受硬件限制;API方案效果强、迭代快,但每次调用都花钱,而且调试时很难看到模型内部行为。

我的建议是学习和调试阶段用本地小模型,生产环境按预算和效果选API或私有化部署。为什么?本地小模型虽然聪明程度一般,但它能在你机器上稳定复现,方便你一条条测试检索链路。比如我用Qwen2.5-7B-Instruct做开发和排查,确认检索和提示词都没问题后,再生产环境切换成效果更强的商用模型。这样既省钱,又能把“模型能力”和“工程链路”这两个变量分开来排错。

2.2 数据与向量化:切块参数才是检索质量的胜负手

项目里我花了最多时间的不是模型,而是数据。原始文档要先清洗:去重、去噪、统一格式。接着是切块(chunking),这步直接决定检索质量。切块太大,一个块里塞了太多无关信息,向量表达被稀释,召回精度暴跌;切块太小,语义不完整,召回又容易漏。

我实测下来,通用技术文档用512个token一块、块与块之间重叠80个token比较稳。重叠的原因是防止关键句子恰好被切在边界上,导致语义断裂。这个参数不是拍脑袋定的,我是用一组标注好的问答对,迭代测试不同切片长度下的召回率,发现500到600这个区间最稳。没有标准答案,但每换一种文档类型,都应该重新做一次这个测试。

2.3 向量数据库:从Chroma到生产级方案的路径

向量库的选择也不用过度纠结。学习阶段我强烈推荐Chroma或者FAISS。Chroma直接pip安装就能跑,自带持久化,几百兆内存就能启动,适合把链路先跑通;FAISS是Meta出的轻量库,索引机制透明简练,适合理解向量索引的底层原理。等数据量到了百万级、需要高并发和权限过滤时,再迁移到Qdrant、Milvus或者Weaviate这类真正的分布式向量数据库。

这里插一句:向量数据库不是越快越好。生产项目里,权限过滤、混合检索、多租户隔离往往比裸查询性能更重要。选型之前先列一下你的过滤条件有哪些,比如“只检索某个部门的文档”“排除已过期的文档”,这些在轻量方案里实现起来会非常痛苦,尽早用带Metadata Filter的方案才能少走弯路。

2.4 编排框架:晚一点再上LangChain

La链这个工具,我的评价是:很好,但别急着用。我在项目的前六周完全没碰任何编排框架,用原生Python写数据加载、向量化、检索、拼上下文、调模型。等每一步的逻辑都理解透了,才对照着看LangChain是怎么组织的。这个切换过程非常丝滑,因为我完全看得懂它每一步在做什么。

框架的抽象本身没有错,错的是在没理解基础链路之前就依赖框架。框架帮你省掉的是样板代码,帮你省不掉的是理解和排查。先裸写后框架的顺序,也让我在选框架的时候有底气:不盲从最新热门的那个,而是看它的组件边界是否清晰、日志是否透明、是否便于我插入自己的评估逻辑。

3. 实操全流程:从零搭建一个文档问答助手

3.1 第一步:圈定场景,控制数据范围

实操部分,我拿一个非常典型的场景来走全流程:给团队做一套“运维故障手册问答助手”。为什么不选一个宏大的“企业知识库”?因为第一版项目最重要的目标是跑通闭环,而不是覆盖所有功能。范围越小,越容易验证每一层的对错。

我收集的数据来源有三个:历史故障工单、运维手册Wiki、常见问题排查文档。文档格式有Markdown、PDF和HTML,共两百多份。第一步不是急着处理,而是先人工把所有文档浏览一遍,删掉过时内容,补上缺失的版本号。这一步虽然费眼睛,却是整条流水线的地基——脏数据进索引,后面所有环节都会被污染。

3.2 第二步:清洗、切块与向量化索引

清洗的时候有几个细节值得说。一是把所有格式统一转成纯文本,PDF必须经过解析抽文本,表格用分隔符转成CSV风格;二是重复内容按标题和正文哈希去重,避免同一份知识在检索里重复出现;三是给每个文档块打上Metadata,比如来源文档名、章节路径、更新时间,这些后面做权限过滤和引用溯源都要用。

切块和向量化的代码很简短,我用的Embedding模型是BAAI/bge-large-zh-v1.5,中文场景里性价比很高。核心逻辑大概长这样:

from chromadb import Documents, EmbeddingFunction, Embeddings from sentence_transformers import SentenceTransformer model = SentenceTransformer("BAAI/bge-large-zh-v1.5") class MyEmbeddingFunction(EmbeddingFunction): def __call__(self, input: Documents) -> Embeddings: return model.encode(input, normalize_embeddings=True).tolist() # 切块:按 token 数切,带 overlap def chunk_text(text: str, chunk_size: int = 512, overlap: int = 80) -> list[str]: tokens = text.split() # 生产环境请用正式 tokenizer chunks = [] start = 0 while start < len(tokens): end = start + chunk_size chunks.append(" ".join(tokens[start:end])) start += chunk_size - overlap return chunks

构建索引之前先做一个小验证:把10个带标准答案的问题和对应的正确文档块,分别算相似度,确认Embedding模型语义理解符合预期。这一步能提前暴露Embedding模型选错的问题,比如用了通用英文模型来处理中文文档,检索效果会非常糟糕。

3.3 第三步:检索召回与重排细节

索引建好了,检索看起来就是一句collection.query(),但里面的参数值得细抠。首先是top_k,我测试下来4到8个块比较合适。太少会漏信息,太多会把噪声塞进上下文,让模型不知道该信哪段。

其次是相似度阈值。我用的是余弦相似度,Chroma里默认的distance函数是L2,需要显式改成余弦。阈值太高会问什么都召不回,太低又会召回一堆无关内容,我根据自己的评测集把阈值定在0.65到0.75之间,低于这个的直接不返回,宁缺毋滥。

还有一个容易忽略的问题是检索排序。向量相似度只保证“语义相近”,不保证“答案顺序正确”。我当时遇到一个典型case:问题问“数据库连接超时怎么排查”,召回的三个块里,最相关的那段被排在了第二位,模型照着顺序读下来,反而被前一段干扰。后来加了重排环节,用一个轻量rerank模型把第一轮召回的候选重新排序,回答质量立刻上了一个台阶。如果不想引入额外模型,至少要在Prompt里让模型“先看哪段再做判断”,也能缓解排序带来的影响。

3.4 第四步:生成链路与提示词模板

生成层要解决的三个问题:怎么把检索结果装进上下文、怎么约束模型只基于证据回答、怎么保证输出格式可控。

先说上下文组装。模型有上下文窗口限制,不能把五个块一股脑塞进去。我先对各块按相似度排序,留前5个,再把每个块压缩成“摘要+关键证据句”的形式,保证总长度不超过窗口的一半,为模型输出留足空间。这一步有个我踩过的坑:不要按原始文档顺序拼接上下文,正常段落里离提问越远的内容越容易被模型忽略,所以必须把最相关的内容放最后、紧挨着提问语句,实测效果更好。

提示词模板没有必要写得很玄,关键是给模型立规则。我的模板核心部分如下:

你是一名运维知识库助手。请严格根据给定的参考文档回答问题。 规则: 1. 如果参考文档中有答案,请用你自己的话清晰作答; 2. 如果参考文档中没有答案,只说“我无法根据现有资料回答”,不要编造; 3. 回答结尾必须标注引用来源编号,格式为【来源1】【来源2】。 参考文档: 【来源1】{chunk_1} 【来源2】{chunk_2} 问题:{question}

生成参数也不要忽略。事实问答场景temperature我设在0.2,太高模型会“自由发挥”;max_tokens限制在500以内;如果希望输出JSON结构化,可以在Prompt里指定Schema,并要求模型只输出JSON,实测大部分模型都能很好配合。

3.5 第五步:评估闭环与上线迭代

很多人做到上一步就宣布大功告成,实际上漏了最重要的评估环节。我建了一套离线评估集,从真实工单里挑出80条问题,每条标注了标准答案和对应的文档来源。每次改动切块参数、Embedding模型或Prompt,就跑一遍这80条,用三个指标衡量:检索召回率、答案忠实度、答案相关性。

其中“忠实度”这个指标最值得做。做法是拿生成的答案去和参考资料对比,看答案里的关键事实是否都能在参考文档里找到出处,找不到的就是一次幻觉。这个指标在RAG场景下比任何人工打分都更有说服力。线上再埋点记录用户反馈,比如“点赞/点踩”,定期抽样看坏case。

这套闭环跑起来之后,我才真正理解了什么叫“迭代”。以前调Prompt是凭感觉,现在是改完一个参数,用数字说话。整个项目的后半段,我的精力基本都花在补数据、调检索,而不是调模型,这是评估体系带来的最大改变。

4. 踩坑实录:生产环境最常见的四类问题排查

4.1 检索召回差:八成问题出在数据,不是模型

召回差是最容易迷茫的问题。表现为用户问A,系统答非所问。我排错的第一步永远不是看模型,而是把召回的原始文档块打出来,肉眼检查。检查下来发现,大部分问题集中在三个方面:切块破坏了语义、文档本身质量差、Embedding模型领域不匹配。

举个具体case。我的故障手册里有张表,列了各种错误码和对应解决办法。切块的时候表格被拦腰截断,错误码在上一块、解决办法在下一块,检索自然召不回完整信息。解决办法是切块前把表格转成带描述的自然语言文本,再参与切块。还有一次是排查工单里的“内存”和“内存条”语义相近但场景差异大,通用Embedding分不清,最后是通过在文档里补充上下文描述搞定的。记住,模型解决不了数据本身缺的东西。

4.2 幻觉拦不住:别把Prompt当万能补丁

幻觉问题人人都会提,但多数人只会在Prompt里加一句“不要编造”,然后发现没用。根本原因在于,如果检索没能给模型提供证据,Prompt再狠也拦不住模型凭训练记忆发挥。

我的处理分两步。第一步是硬约束:在Prompt里明确规则“不能使用参考文档之外的知识”,同时把产生回答所用的证据块编号嵌入答案,来源没法指向任何参考文档的回答,直接判定为不合格。第二步是软兜底:如果检索结果的最高相似度低于阈值,直接告诉用户“这个问题不在知识库范围内”,而不是让模型硬答。产品层面做一次“拒绝回答”比答错一次更值得信任。

4.3 上下文一长就乱:留意“Lost in the Middle”效应

有段时间助手回答开始不稳定,相关问题变多时,答案经常忽略掉中间部分的资料。一开始我以为是模型能力问题,后来查资料才发现,这本身是长上下文模型的通病:对开头的指令和结尾的输入记忆更强,对中间的细节容易遗忘,研究里称为Lost in the Middle。

所以组装上下文时,我把答案最关键、相似度最高的证据块放到整个上下文最后,紧贴问题;次重要的放开头;那些补充性内容才放中间。这个方法我看过论文验证,也在自己的项目里对比过,回答准确率有明显提升。生产环境不需要和模型的天性做对抗,顺着它的注意力分布来组织内容,比强行堆更多资料有效得多。

4.4 评估流于形式:你的评测集可能在自欺欺人

最后一个坑,出现在我自认为已经做得很好的时候。评测指标全绿,上线后真实用户却反馈不少问题。后来查出来,我的评测集混入了大量来自原始训练文档的句子,模型在这些问题上表现好,是因为它在训练时“见过”类似的表述,而不是系统真的检索对了。用不干净的评测集做评估,等于每次拿答案去估分,分数当然漂亮。

修正办法是用人工从真实用户问题中积累评测样本,并且每季度做一次样本更新,防止数据分布漂移。另一个心得是,评测不要只看正确率,要把检索的召回来源打印出来,人工抽看“召回内容是否真的对应了问题”。这个动作能帮你精准发现评测集在哪个环节造假,及时纠正方向。

回头再看这段从零搭建AI工程链路的经历,我最深的体会是:这个领域真正稀缺的不是模型能力,而是把每个环节的基础做扎实的耐心。现在我看到一个新框架或者新模型,第一反应不是追着上,而是先想它接进我的五层链路里,替代的是哪一层,会不会让排查变得更透明。最后再分享一个小建议:从零开始的第一版项目,规模一定要小,领域一定要窄,但环节一个都不能省。小场景可以让你快速看清每层的问题,全环节则能帮你建立完整的系统观和排查手感。这些,才是面对AI应用生产环境变化时不会慌的真正底气。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询