
1. 我为什么把“从零构建AI工程”当成一个正经项目来做刚接触“ai-engineering”这个词的时候我和大多数人的想法一样这不就是调个API、跑个开源模型、写两行Prompt嘛有什么好“工程”的直到我真的把一个带检索、带记忆、带评估的AI应用推上线才明白AI工程和“玩AI”完全是两回事——前者是稳定、可控、可迭代地交付价值后者更像实验室里的一次性实验。这篇文章算是我个人对“ai-engineering-from-scratch”这个项目的一次完整复盘。我会从技术选型、数据管道、模型落地到评估迭代把整条链路拆开揉碎讲清楚每一步“为什么这么做”、踩过哪些坑、哪些地方可以放心抄作业。适合三类人一是准备从传统后端/算法转向AI应用开发的工程师二是已经在用LangChain或各种框架、但总觉得“差点意思”的实践者三是对AI落地感兴趣、想弄清楚“模型背后到底还藏着多少活”的产品和技术负责人。先说结论AI工程的核心不是训练模型而是构建一个能让模型稳定产生高质量输出的系统。这中间涉及数据工程、检索优化、评估闭环、部署运维没有任何一项可以被“一个模型搞定一切”的叙事替代。下面我从头把这个系统的各个零件唠一遍。2. 内容整体设计与思路拆解2.1 从“写模型”到“做系统”的转变很多人以为AI工程是从PyTorch开始的但如果你真正做过一个生产级的AI应用就会知道模型代码在整个工程里往往只占不到20%的工作量。剩下的时间你在干什么清洗数据、拼业务逻辑、管理Prompt版本、设计评估指标、处理并发、盯着日志排查“为什么这一条答非所问”……我用一个生活化类比来解释如果模型是一个刚毕业的高材生那AI工程就是给他配一整套办公环境——岗位说明书提示词、参考资料库RAG知识库、绩效考核体系评估集、工位和电脑推理部署甚至还包括一个带他的老员工Agent的规划能力。这个高材生能不能产出取决于这套系统配得好不好而不只是他个人聪不聪明。所以我在这个项目里给自己定的原则是不急于训练、微调模型先把系统的骨架搭起来。事实上大部分业务场景根本不需要微调一个检索质量过关的RAG加上结构良好的Prompt就能覆盖80%的常见需求。微调反而是最后的、最昂贵的手段。2.2 AI工程的核心技术栈全景如果要用一张图来概括现代AI工程的技术地图这里没有图我直接用清单列出来大概是这样数据层文档解析、清洗去重、分块Chunking、向量化、数据版本管理模型层基础模型选型开源/闭源、微调LoRA/QLoRA、量化GPTQ/AWQ检索层向量数据库Milvus/Qdrant/FAISS、混合检索关键词向量、重排序Reranking生成层Prompt工程、Agent框架Function Calling、记忆与上下文管理评估层评测集构建、LLM-as-Judge、RAGAS指标、回归测试运维层模型推理服务vLLM/TGI、容器化部署、可观测性、日志与追踪这六层不是线性流程而是互相咬合的闭环。我最开始犯的错误是把它们当成六个独立模块依次搭建结果发现每做完一层前面那层就要返工。这有点像装修房子你以为先铺地板再刷墙结果发现电路没走完地板白铺了。正确做法是先想清楚评估指标再倒推每一层需要提供什么能力。2.3 为什么“从零开始”反而比用全家桶框架更值市面上已经有LangChain、LlamaIndex这类全家桶框架为什么不直接拿来用我在项目里做的一个关键决策是初期只把框架当工具书核心链路自己手写一遍。原因很简单框架的抽象层级太高出了问题你很难排查。我已经数不清有多少次遇到“Agent没走预期分支”“检索结果莫名其妙”的诡异问题最后发现要么是框架内部某个默认参数不适合当前场景要么是底层某个第三方库版本不一致。当你对每一行链路逻辑都心里有数时线上出问题你能三分钟定位而不是对着框架源码怀疑人生。当然我不是说框架一无是处。在业务快速迭代、团队人手不足的场景下用LangChain搭个原型是完全合理的。但在“from scratch”的学习项目里我更推荐先手写一遍再上框架。这样你对框架里每个抽象组件背后的代价和假设都有体感用起来才不会像开一辆自己拆不开的汽车。3. 核心细节解析与实操要点数据与检索篇3.1 文档清洗与解析——最无聊但最容易翻车的环节我在项目里第一个处理的业务数据是几十份PDF格式的产品文档。一开始我以为把PDF喂给解析库就万事大吉结果光是“表格跨页”“多栏排版”“页眉页脚污染”这三个问题就折腾了很久。如果你连数据都没解析干净后面所有检索和生成都是建立在沙地上。常见的文档解析工具有PyMuPDF、pdfplumber、OCR服务等。我的实操经验是文本型PDF先用pdfplumber提取元信息同时保留页码坐标后续分块时可以追溯到原文位置扫描版PDFOCR是必经之路建议先做图像预处理二值化、去噪能显著提高识别率表格数据普通文本解析会把表格拆得七零八落需求不复杂时可以让模型结构化抽取但必须人工抽检另外清洗绝对不只是删除乱码。文档里的版权页、目录、重复段落、失效链接都会在检索时变成噪音。我的做法是先把所有文档转成Markdown做一层肉眼检查把明显没用的页面剔除再用脚本批量去重最后才交给分块器。整个过程听上去机械但它是后续所有环节质量的基石。3.2 分块Chunking策略——检索质量的真正胜负手如果说解析是地基分块就是承重墙。分块的大小和方式直接决定向量检索的召回质量这是整个RAG系统里最值得反复实验的环节。我踩过的最大的坑是以为“分块越小越精准”。实际上分块太小会导致上下文碎片化模型拿不到完整信息分块太大则会让向量表征混沌化检索返回一堆“看起来相关但实际是擦边”的内容。以中英文混合的文档为例我的经验区间是分块策略适用场景我的实测感受固定长度 256~512 token新闻报道、段落结构清晰的网页简单但容易切断语义需要重叠句/段边界切分产品文档、技术博客语义完整推荐优先尝试语义/递归分块结构松散的内容综合效果最好但计算开销大我最终采用的是**“标题层级段落长度约束”组合分块**先按Markdown的标题结构切出大段再在每段内部按句子边界切分最终保证每个块上限不超过500 token并且相邻块之间保留20 token的重叠。这个方案的思路很简单保留文档原有逻辑层次再在细节处控制粒度。另外很重要的一点分块参数不是拍脑袋定的要靠检索质量评估倒推。我会在下一节详述怎么量化评估。这里先记住一句话如果你发现检索结果不理想先别急着换向量模型很可能只是分块策略没调对。3.3 向量化与检索优化——命中的不一定是答案选Embedding模型时我也走过弯路。最初图省事用了某个通用模型结果在垂直领域文档上召回率惨不忍睹。后来换成开源的bge系列中大型模型情况立刻好转。经验是Embedding模型的领域匹配度比“模型有多大”重要得多。你甚至可以先用一个较小的领域模型做基线再和大模型对比效果没必要上来就追最重的模型。检索环节的另一个优化点是混合检索重排序。纯向量检索对同义改写和语义相关比较擅长但对精确关键词、产品编号、人名这类查询容易失灵。我的做法是同时跑BM25关键词检索和向量检索各自取Top 50然后用Reranker模型对合并结果重新排序最终取Top 5给LLM。这套组合拳在实测里把检索准确率提升了大概三成。过程中我也踩过“返回片段看似吻合、实则张冠李戴”的坑后来定位到问题在于重排序时不看上下文窗口。Reranker会单独对“查询-候选块”打分但某些块单独看完整、放到文档上下文看却是错的答案。解决办法不算优雅却很有效把所有候选块带上章节路径和相邻块信息一起做二次校验命中率高了很多。4. 实操过程与核心环节实现从零搭建一个知识库问答系统先声明一下这个项目我全程用的是比较朴素的技术栈Python、FastAPI做服务层、Qdrant当向量库、开源模型部署用vLLM核心链路逻辑自己实现。下面我把整个实操过程压缩成四个阶段按我真实推进的顺序讲。4.1 阶段一需求定义与技术选型需求听起来非常简单输入一个问题返回基于文档的可溯源回答。但“可溯源”三个字把难度拉高了——它要求系统不仅要给出答案还要能指出答案来自哪篇文档的哪一段。我当时的选型思路是数据存储Qdrant因为自带向量标量过滤部署轻量社区活跃Embedding模型bge-large-zh领域文档效果不错显存占用可接受生成模型本地部署一个7B级开源模型主打可控核心API单独接更强大的商业模型做对照服务框架FastAPI异步并发能力足够生态成熟团队都熟监控与日志OpenTelemetry 一套简单的自定义日志格式这个选型谈不上惊艳但符合“从零开始但要上生产”的定位。原则就两条组件能自己跑就不强上K8s能用标准接口就不要搞私有协议。4.2 阶段二数据管道搭建数据管道是我的第一道坎。原始数据有PDF、有Word、还有少量网页抓取我统一处理成Markdown中间格式然后进了下面的流程# 伪代码示意数据管道核心流程 def build_data_pipeline(raw_docs: list[str]) - list[Chunk]: # 1. 解析与清洗 markdown_list [convert_to_markdown(doc) for doc in raw_docs] cleaned_list [clean_document(md) for md in markdown_list] # 2. 结构感知分块 chunks [] for doc in cleaned_list: sections split_by_markdown_heading(doc) for section in sections: for block in split_by_sentence_with_overlap(section, max_tokens500, overlap_tokens20): chunks.append(Chunk( contentblock.content, metadata{ source: doc.source, section: section.heading, page: block.page_no, chunk_id: str(uuid4()) } )) return chunks看到这个流程你可能觉得简单但真正跑起来会发现几个细节特别闹心编码问题Word转出的文本经常有乱码或特殊字符Python默认解码器会直接报错后来统一强制UTF-8加自动跳过非法字节头尾污染相邻页面重复的页眉页脚会混进块里造成检索时出现大量“没什么用的重复内容”我用连续重复检测把它们清掉了圆圈数字和特殊符号清洗阶段必须处理掉否则向量化之后容易让模型抓错重点管道跑通之后我把清洗后的文本、分块后的块信息、向量化的向量一层层做了日志记录。这样后面定位问题时能回溯到具体是哪一步处理出了问题。数据管道一定要有可观测性否则数据出问题了你都不知道是源头脏还是解析器坑。4.3 阶段三检索与生成链路实现这阶段是整个系统的心脏。我最终实现的是典型的RAG链路手写了一个无状态的检索服务# 伪代码示意混合检索 重排序 def retrieve(query: str, top_k: int 50): # 同时跑向量检索和BM25关键词检索 vector_hits qdrant_client.search( collection_namedocs, query_vectorembed(query), limittop_k ) bm25_hits bm25_index.search(query, top_k) # 合并去重交给reranker merged_candidates merge_and_dedup(vector_hits, bm25_hits) reranked reranker.rerank(query, merged_candidates, top_n5) # 附带上下文信息供LLM引用 return enrich_with_context(reranked)写这段代码的时候我犯过一个印象很深刻的错合并检索结果后直接用权重平均算相关度结果两个单一信号都不算强的片段反而排到了前面。后来改成“两类检索各自返回的Top 20强制保留其余按重排序分数排”效果立刻正常了。经验就是不同类型信号的合并处理要保守先保住强信号再谈融合。生成环节我不仅把候选块拼接进Prompt还把每个块的“来源章节”列表单独传给模型要求它只在给定信息范围内回答。这个设计是为了做“可溯源”——在Prompt里明确约束之后模型编造的频率会下降不少。4.4 阶段四评估与迭代闭环这可能是整个项目里最容易被跳过、但也是我最想强调的部分。AI工程如果缺了评估就等于在黑暗里开车。我自建了一个200多条问题的评测集分成三类事实类问答答案是文档中明确写明的推理类问答需要串联多个文档内容边界问题文档中确实没有答案的。然后用RAGAS框架里的指标加上人工抽样每个版本都跑一遍全套评测。有一组数据让我印象很深在第一次正式评测里事实类问答准确率只有62%把检索Top 5之外的候选块也纳入重排序后一下子提高到81%。这个提升不是模型换出来的而是评估驱动出的结果。后来我养成了一个习惯每改一次分块策略或重排序参数都必须跑一次评测集对比通过率。版本管理在这里极其重要——好在新系统的设计让我能快速回滚。另外“拒绝回答”也是一种能力。我刻意在评测集中留了一批文档覆盖不到的问题观察系统在这些问题上的表现。前期系统会试图强行猜测答案造成“一本正经胡说八道”的幻觉。后来我把Prompt里的置信度要求写得更严格并且加入一条无答案输出路径幻觉率才真正降下来。5. 常见问题与排查技巧实录生产环境踩坑备忘5.1 高频问题速查表我把自己两个月里遇到的典型问题和排查方法整理成一张表希望你能少走点弯路。现象常见根因排查步骤检索返回结果主题相关但没用分块过大/过小、Embedding漂移检查评测集召回指标逐个看Top 5样例对比不同分块参数同一问题答案不稳定生成本身有随机性、Prompt约束弱降低temperature增加few-shot示例必要时做答案结构化输出来源引用错误重排序忽略上下文、块之间语义粘连给Reranker更多候选块检查跨块引用时的章节归属首次响应极慢推理服务冷启动、索引未预热启动时预加载模型权重打开连续批处理做首token延迟监控新增文档不生效增量索引链路缺失、缓存未失效检查数据处理管道是否会触发增量写入确认查询是否被缓存命中模型幻觉增多上下文中有矛盾信息、Prompt约束失效先检查文档是否有新旧版本混存再清洗Prompt中的模糊指令这张表看起来轻巧但每条背后都是真实调试的还原。比如“来源引用错误”那条我花了整整一天才定位到是因为章节标题文字被分块切断了导致Reranker对候选块无法关联到正确的章节路径。解决方案是给每个块额外存储ancestors字段把各级标题串在一起从根因上解决了上下文归属问题。5.2 新手最容易犯的认知错误作为一路踩坑过来的人我发现新手做AI工程时往往会在几个认知层面出问题而不是单纯的工具不熟第一个错误是把模型当万能解药。业务效果不好就想着换大模型、上微调却不先排查数据质量和检索能力。事实上很多问题的根源是文档数据本身脏乱、分块策略不合理换了模型一样拉胯。第二个错误是忽视评估集的建设。没有评测集你根本分不清“感觉变好了”和“真正变好了”之间的区别。模型或链路一改你不知道是不是引进了回归。说句不好听的没有评估体系的AI应用上线以后就是定时炸弹。第三个错误是混淆“应用开发”和“AI工程”。单纯调API写Prompt当然也是开发但当你需要处理多数据源、多模型切换、灰度发布、日志追踪、成本控制时那些经典工程问题一个都跑不掉。AI工程不是魔法它是把模型能力装进工程容器里持续迭代的技术实践。第四个错误是低估了成本与性能的平衡。不是所有请求都需要最强模型也不是所有环节都需要向量化。合理设置“简单问题走规则、中等问题走小模型、复杂问题走大模型”的分层策略节省的成本比你想象的多得多。我后期在系统里加入了一个前置意图分类器将近30%的通用问题直接命中缓存或规则直接降低了推理负载。6. 我自己的三点收尾体会项目做到现在我最想分享的三条体会都不算技术却比技术更关键。第一条是AI工程的难度不在模型在数据、评估和期望管理。给模型接上业务数据很容易但让模型在“边界case”下稳定输出背后是无数的数据清洗、Prompt调试和评估回归。这个东西没有银弹只有笨办法加持续的耐心。第二条是手写核心链路一遍比用十个框架都值。框架帮你省的时间最后可能要十倍还回去。尤其是RAG、Agent这类还远未定型的领域框架天天在变而你手写过核心逻辑之后框架更新对你来说只是换了个壳。第三条是评估必须从第一天就开始。哪怕一开始只有20条人工标注的评测问题也比没有强。所有后续优化都应该建立在可量化比较的基础上否则你只是在用随机姿态逼近目标。如果你也想做类似的项目我的建议很简单先把一门语言Python练溜把一个向量库跑通把一个LLM API接好把头100个真实问题人工评测一遍。做完这些你对AI工程的认知会比刷一百篇文章都深刻。这条路上没有捷径但每一步你踩过的坑、补全的细节都会成为你未来做新系统时最扎实的底气。