从零开始AI工程:RAG、Agent与部署实战指南

发布时间:2026/9/30 4:25:24
从零开始AI工程:RAG、Agent与部署实战指南 想做好AI工程从零开始ai-engineering from-scratch并不是一个“跟着教程调API”的小任务。我花了三个月从只会写Python脚本到把一个带知识库问答和自动执行任务的AI服务稳定跑上线过程比想象中折磨也特别有意思。这个项目最核心的价值是不靠现成框架掩盖问题自己搭环境、处理数据、跑模型、接检索、做评估、部署上线每一步都手动踩一遍。如果你也想自己从零搭一个真正能用的AI系统而不是做一个漂亮的demo壳子这篇内容应该能帮你少走不少弯路。1. 项目整体拆解AI工程到底在解决什么问题1.1 从“调用API”到“AI工程”的分界线很多人觉得AI工程就是写两句prompt再调API。早期我也这么想直到一次做demo翻车模型答非所问、检索结果一团糟、服务一遇到并发就超时才发现靠套壳根本收不了场。真正的AI工程核心是“可控”两个字。大模型的概率输出天生不稳定模型自身对训练数据之外的事实也不敏感工程要做的不是把模型当成全知全能的答案机而是在模型外面套一层可预期、可反馈、可修复的系统。这层系统至少包括几个部分数据清洗管道、知识切块与索引、检索排序、模型调用、工具调度、输出校验、日志与指标收集。任何一个环节断了用户感知到的就是“这个AI不聪明”或者“这个AI在胡说”。所以AI工程和传统后端工程最大的区别是传统后端关注逻辑确定性AI工程则要同时处理逻辑和概率。写一个接口很容易写一个稳定产出可接受结果的接口才需要完整的工程链路。我在项目早期就把目标定得很小先做一个能稳定回答内部知识库问题的系统。这句话听起来简单实际上要覆盖文档解析、文本切块、向量化、检索融合、生成策略、评估反馈六个环节。只要有一个环节没做好结果就崩。事实证明这套最小闭环是小步快跑的最佳起点。1.2 我确定的五层学习路线图从零开始最怕的不是没资源而是不知道按什么顺序学。我给自己定了一个五层路线图每层都有明确产出物第一层模型与数据处理基础。跑通文本清洗、Tokenization、Embedding理解模型输入输出格式目标是能做一个简单的文本分类或相似度匹配。第二层检索增强生成RAG。先解决“模型不知道新知识”的问题目标是拿到问题后能检索到相关片段并生成有依据的回答。第三层Agent工具链。让模型不只是说话还能通过工具执行动作比如查数据库、调用天气接口、读本地文件。第四层评估与性能工程。搭离线评测集合量化回答质量和延迟目标是让改进可衡量。第五层部署与成本控制。做服务化、并发控制、模型量化目标是让系统能上线、能维护、成本可控。这个顺序不是随意定的。先理解模型边界才知道要用检索来补什么有检索能力之后Agent的工具调用才能在“有上下文”的前提下做决策最后用评估和部署来确保稳定性和成本。很多人一上来就研究Agent多智能体或者花哨的RAG框架结果基础数据一塌糊涂最后所有优化都是在沙地上盖楼。1.3 为什么从零自己搭而不是直接用框架市面上的LangChain、LlamaIndex等框架用起来是真省事几行代码就能串起一个管道。但如果直接依赖它们你很容易被黑盒局限坑到。框架为了通用性会加很多抽象层一旦检索结果不对你很难说清楚是embedding的锅、切块的锅还是框架调用逻辑的锅。而自己从零搭一遍相当于把每个环节的内部结构亲手打通一遍。我当时的选择是先不用那些重型框架只依赖transformers、faiss、fastapi这类偏底层的组件手写RAG链路。这样做的好处是无论出什么问题都能从自己的代码里排查等到理解了内在关系再去看框架源码就是降维打击。这套方法当然有代价比如开发速度更慢但对我这种想打好基础的人来说慢就是快。2. 从零搭建第一个可用系统环境与数据篇2.1 开发环境选型云上还是本地先谈算力。如果目标只是跑通流程本地CPU完全跑得动一个小型embedding模型和一个7B以下的量化模型。但如果要微调即便是LoRA这种轻量方案也需要一块至少16G显存的GPU或者直接用云GPU实例。我的实际做法是先在本地跑通数据清洗和评估脚本再租云GPU实例做微调和推理压力测试。这样本地负责快速迭代云端负责重活两边不互相拖。别一上来就租最高配置。最开始那个月我基本都在处理数据格式和代码逻辑GPU就算租了也是在等数据。等确认要训练了再去开实例比一直开着按时计费省一大笔。环境创建我一般这样搞conda create -n ai-engineering python3.10 conda activate ai-engineering pip install torch transformers datasets faiss-cpu chromadb fastapi uvicorn版本锁定一定要做。去年我因为transformers和torch版本不匹配跑算子时频繁报错浪费了整整一个下午。建议用requirements.txt固定版本每次改动依赖后打一个标签备份。AI项目环境炸掉的概率比普通项目高很多一是因为依赖重二是因为GPU相关库对版本非常敏感。2.2 数据准备与处理没有高质量语料一切白搭我的数据来源有三类业务PDF文档、FAQ表格、历史问答日志。PDF清洗最麻烦因为表格和多栏排版会让内容顺序乱掉。我的方法是先用pypdf提取文本再按页分割按句子切块后用规则合并相近段落最后人工抽查50条。抽查看起来土但非常必要能快速发现清洗规则里的系统性错误。清洗时有几个我踩过的坑页眉页脚一定要去掉否则embedding会被页码和文档标题污染导致检索结果偏差。图片和公式暂时没法直接处理我先把这些位置标记成“图片”占位避免切块把文字和图表内容混在一起。去重不能只看文本完全一致要算SimHash或embedding余弦相似度相似度高于0.92的片段直接剔除。否则知识库里会反复出现相似内容生成时很容易围绕同一段重复回答。数据量真不在多关键在于覆盖和干净。我最后只用了3000多个有效片段就足够撑起一个内部知识库问答的demo。数据管线做得好不好直接决定后面检索和生成的天花板。2.3 搭建自己的基线模型一个小型LLM微调实验并不是任何项目都要从预训练开始但至少要亲手做一次微调才明白模型是怎么被数据影响的。我选了7B模型做LoRA微调用指令数据让模型学会“根据检索片段引用依据而不是自己编”。LoRA的优势是显存占用低只训练少量可训练参数适合从零阶段的实验。训练配置的经验如下# 以transformerspeft为例的关键参数 lora_r16 lora_alpha32 learning_rate2e-4 num_train_epochs3 max_seq_len1024这里提醒一个典型新手坑不要一上来就调大学习率和训练轮次。我试过把学习率调到5e-4结果模型过拟合回答全变成背诵训练样本。建议先用小步长跑到第一个epoch结束看验证集loss是否下降再决定要不要继续。微调的最终目标不是让模型“变聪明”而是让模型“更听话”。在RAG链路里我们不希望模型自由发挥更希望它能严格贴合检索到的证据输出。只要把这件事跑通后续换模型、换数据都能快速复用同一套流程。2.4 代码组织与版本管理AI项目代码比普通Web项目更容易乱因为中间产物太多。我给自己定的目录结构是这样的app/ api/ # FastAPI接口 pipeline/ # 数据清洗、切块、索引 rag/ # 检索、重排、生成 agents/ # 工具注册与调度 eval/ # 离线评估 data/ raw/ # 原始文件 processed/ # 清洗后数据 configs/ model.yaml prompts/ tests/关键原则是“数据不进代码”。每个处理环节都要把中间结果落盘成parquet或jsonl方便追溯问题。我用DVC管理数据集版本用git管理代码版本。AI项目最大的痛点不是代码回滚不了而是数据、模型、提示词这三者的版本对不上。每次微调前我都会在configs里记录data_version、model_version、prompt_version。这样过两周再看实验结果还能清楚地知道当时的组合是什么。3. 核心环节实现RAG检索与Agent工具链3.1 RAG落地的关键步骤RAG的本质是让大模型“开卷考试”。模型不直接靠记忆作答而是先查资料、再根据资料生成回答。我落地RAG时拆成五步解析文档、切块、向量化、检索、生成。其中最容易被低估的是切块切块太大检索可能把无关上下文卷进来切块太小又容易把完整信息切断。我用的策略是“固定长度段落边界感知”每段约256个token并保留前后各32个token的重叠。重叠的作用是避免跨段信息被截断比如一个完整列表刚好被切到两段时模型在任意一段里都能看到一部分上下文。另外解析PDF时遇到过表格被切碎的问题。后来改成先识别表格区域把表格转成Markdown再做切块。这样检索到表格时模型看到的是结构化文本而不是一堆散列的数字。这个小改动让表格类问题的回答准确率直接提升了十几个百分点。3.2 向量化与混合检索细节纯向量检索有一个典型问题关键词完全匹配时语义相关的片段不一定排到前面。比如用户问“价格”如果某文档通篇写“费用”和“资费”语义向量可能会找到但关键词检索更容易直接命中。所以我的方案是混合检索用BM25跑一遍关键词检索用embedding跑一遍语义检索再用RRF融合排序。为什么不直接用重排模型因为重排模型每次请求都要额外跑一遍交叉编码器成本高、延迟大。RRF虽然简单但几十行代码就能稳定提升召回率适合从零阶段优先采用。核心思路如下def reciprocal_rank_fusion(results_list, k60): scores {} for results in results_list: for rank, doc_id in enumerate(results): scores[doc_id] scores.get(doc_id, 0) 1 / (rank k) return sorted(scores.items(), keylambda x: x[1], reverseTrue)k值通常取60附近太小会让靠前的排名过度影响结果。实际项目中我把关键词检索和向量检索各取Top30融合后取Top10再去重。效果比只用向量搜索有明显提升而且没有引入额外模型线上压力可控。3.3 Agent工具的注册与选路Agent部分让模型从一个“回答者”变成一个“执行者”。我搭的第一个Agent不复杂请求进来先做意图分类知识类走RAG需要查数据或调用接口的走对应工具。工具注册表里每一项都要写清楚名称、功能描述、参数结构。模型判断用哪个工具靠的就是这些描述是否清晰。一个很重要的教训是不要一开始就做复杂的多Agent协作。先做一个Router给每个领域配一个函数函数返回结构化JSON最后统一交给生成器输出。等积累的工具超过十个再考虑动态生成调用链。从零开始最大的诱惑是过度设计看到社区都在跑多智能体就心动。但真正跑通业务闭环之后你会发现自己最需要的其实是稳定、可排查的简单路由。3.4 评估体系没有指标就无法优化没有评估的AI项目本质上只是演示。我搭了一套离线评估脚本准备50条测试问题每条标注期望回答包含的要点每条问题跑三遍取平均结果。核心指标选了准确率、召回率、答案忠实度、检索召回率。其中“答案忠实度”是判断模型有没有产生幻觉的关键指标。忠实度没法完全靠代码判断但我用一个辅助模型来打分把回答和检索片段一起喂给辅助模型让它判断“回答是否完全来自给定片段”。这个方案不是100%可靠但已经足够帮我定位大多数幻觉问题。评估集合里千万不要只放成功案例一定要加边界案例比如“文档里没有答案的问题”和“多个文档内容冲突的问题”。没有这些案例你永远不知道系统会在什么时候突然一本正经地胡说八道。4. 部署、性能与成本优化实录4.1 服务化部署怎么避免“本地能跑上线就崩”本地是单用户上线是并发。我第一次上线时踩的第一个坑是启动时加载模型太慢如果第一个请求进来时才加载用户等到的就是十秒空白。后来我用FastAPI的lifespan机制在服务启动时把模型和索引全部预热而不是等请求进来才懒加载。第二个坑是没有超时控制。大模型生成慢如果上游请求积压服务内存会直接被打爆。解决办法是加信号量限制并发数超过限制就快速返回“繁忙”状态而不是所有请求一起排队死等。一个直接可用的做法是用asyncio.Semaphore控制并发sem asyncio.Semaphore(4) async def generate(...): async with sem: ...实测下来并发数并不是越大越好。先压测到P95延迟在可接受范围内再倒推并发限制。我最后把并发数控制在4到8之间配合流式输出用户体验远好于无脑放开并发。4.2 Prompt缓存、并发与响应时长取舍Prompt模板如果包含大量固定前缀每次都重复传给模型既费钱又费时间。我做了两层缓存第一层对完全相同的问题直接返回缓存结果第二层对前缀完全相同的系统提示做KV Cache复用。这两层把常见问题的首字延迟从2秒降到300毫秒左右成本也能省下三成。响应时长和质量的取舍我一直坚持“首字延迟优先”。用户的耐心非常有限先让首字出来再逐字输出比等十几秒一次性返回完整内容体验好得多。流式输出配合前端打字机效果是很实用的工程决策也能在长回答场景中大大降低用户感知等待时间。从零开始做AI服务如果你只做一个性能优化我建议先做流式。4.3 量化、蒸馏与硬件选择的经验如果机器只有CPU跑7B模型太慢可以试试4bit量化用GPTQ或AWQ效果会下降但依然可用。我自己的经验是任务越简单量化损失越小任务越复杂比如数学推理量化后下降明显。所以我的习惯是离线分析用FP16保证准确度线上对延迟敏感的任务用4bit。蒸馏这个事我建议从零阶段先别碰。不是没用而是你没有足够的师生数据对去训练小模型。先把量化和缓存做好性价比赛过蒸馏。硬件选择上也不要迷信单卡越大越好。分布式推理会引入通信开销小项目单卡或双卡完全够用省下来的预算可以多攒点高质量数据。5. 常见问题与排查技巧速查表5.1 最常踩的5个坑整理一下我在这个项目里实际遇到的坑做成速查表方便你提前预防现象根因解决回答内容与检索无关切块时不感知段落边界改用段落感知切块重复回答同一段向量去重不足加embedding相似度去重模型总是说“我不知道”Prompt要求太严格放宽Prompt并加few-shot示例服务重启后第一次请求很慢模型没预热启动时lifespan预加载并发一高就卡死无超时保护加Semaphore和超时这些坑都不算特别深奥但每一个都真实影响过用户体验。如果你也是从零开始建议先把这五类现象记到笔记里做系统设计时提前留好控制位比如把切块参数做成可配置、把检索TopN做成可调整。否则等系统跑起来再去改牵一发动全身。5.2 排查工具与日志设计AI服务的排查难度远高于普通接口因为问题可能在检索环节也可能在模型生成环节。如果日志不贯穿全链路出了问题只能盲猜。我设计日志时让一次请求从进入到返回都携带同一个trace_id分阶段记录检索、模型调用、工具调用三个部分的日志。每次日志输出都包含原始问题、检索命中片段id、排序得分、最终生成的Prompt、响应内容、耗时。这套日志帮我快速区分“检索没召回”和“模型没按召回内容回答”两种截然不同的问题。小细节是把模型输入输出的token数也打出来一个月后统计一下就知道哪些模块在烧钱。不要等平台出账单才来看成本自己做结构化日志能更早发现问题。5.3 从失败案例里总结的经验最让我难忘的一次失败是上线评审前用生产环境的新数据做评测结果各项指标比测试数据差了一大截。查了很久才发现数据切片逻辑变了但embedding索引没有同步重建。那天之后我给所有索引进增加了hash版本号每次数据变更都会强制触发重建流程并在配置文件里记录data_version。从此再也没出现过“数据换新但索引还在旧版本”的诡异问题。另一个失败尝试是过度依赖一个“万能Prompt”。我以为把人间的逻辑全部写进系统提示词就能让模型自己判断一切。结果提示词越写越长模型行为越来越随机。后来我把判断逻辑拆成显式规则先做分类再决定走哪条链路。把复杂度交给代码而不是交给Prompt是我从零开始做AI工程得到的最大教训。我个人在实际操作中的体会是“ai-engineering-from-scratch”这个项目最大的收获不是模型跑得多好而是建立了对“不确定性”的敬畏。模型输出是概率的数据是脏的框架是会过时的唯一能握在手里的是把每个环节都亲手验证过的工程基本功。如果你也打算从零开始我建议不要贪多先跑通一个最小闭环再做基于反馈的迭代。一个能稳定回答30个问题的系统远比一个什么都敢说但经常出错的大杂烩有价值。先追求可控再追求复杂这条路不会错。