ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

从零搭建大模型应用:AI工程实践全指南

从零搭建大模型应用:AI工程实践全指南 三年前我第一次把大语言模型接进真实业务系统时心里是发虚的。接口调通了Prompt写了好几版Demo跑起来也能有来有回地聊天可一放上生产就露馅回答质量时好时坏、知识库里明明有的内容检索不到、用户的连续追问一多模型就开始胡说。那段时间我试遍了各种调Prompt大法效果始终不稳定。后来我终于想明白一件事——问题不在模型在我自己根本还没建立起AI工程的思维方式。于是我做了一个决定从零开始把大模型应用开发里的每个环节都拆开揉碎逐个攻破再把所有代码、笔记和踩坑记录整理成一个项目名字就叫 ai-engineering-from-scratch。这个项目不是一份看完就忘的教程而是一套可以照着跑、照着改、照着排查的工程实践。它适合三类人一是刚接触大模型应用开发的程序员想系统建立知识体系二是后端或全栈工程师想把LLM能力接入现有业务三是已经在做AI应用但效果总是不稳定、想搞明白为什么的人。这篇文章我会把项目背后的设计思路、关键技术选型、完整实操过程和真实踩坑记录都摊开来讲你能直接复用不用重复走我走过的弯路。1. 项目定位与学习路线设计1.1 AI工程和传统软件开发的本质区别只用一句话概括我做完这个项目最大的感受传统软件开发是在确定性世界里做约束AI工程是在概率世界里做兜底。传统开发里函数传入相同参数必然返回相同结果一切都有明确的输入输出契约。大模型应用完全不是这样——同样的Prompt、同样的参数两次调用可能给出完全不同的回答。这种不确定性让很多习惯传统开发模式的程序员非常难受包括我自己。调试的时候你很难判断这到底是bug还是模型的随机性。所以AI工程的第一课不是学会调API而是接受不确定性并且学会用工程手段把不确定性压制到可控范围。这个认知转变决定了后面所有技术选型的方向。具体来说我在项目里总结了三个关键差异点第一错误处理范式变了。传统代码里异常是偶发模型输出里的错误是必然。业务系统必须假设模型一定会输出格式错误的JSON、一定会在某些问题上答错。所有代码都得按这个假设来写。第二评估方式变了。传统软件用单元测试断言输出是否正确AI应用需要构建评估集、用评估指标来判断回答够不够好。这个判断标准本身就是一门学问而且直接决定了你做优化时往哪个方向使力。第三性能瓶颈变了。传统应用瓶颈常在数据库与并发AI应用的瓶颈在上下文长度、Token消耗、检索质量。同样的业务逻辑因为模型的存在实现方式可能完全不同。1.2 项目路线图的四阶段设计ai-engineering-from-scratch 的仓库结构按照四个阶段组织每个阶段都有对应的示例代码、配置文件和QA记录。这个设计思路值得单独说一下。第一阶段叫跑通。不追求效果只追求把最基础的链路搭起来——加载模型、调用推理接口、处理流式输出。很多新手一上来就搞RAG、搞Agent结果连温度参数会影响什么、上下文长度有多重要都说不上来。这个阶段的目标是让任何人都能在半小时内跑起一个最小的对话应用先建立我能行的信心。第二阶段叫理解。深入Prompt工程的系统方法、上下文管理、结构化输出。这一阶段解决的核心问题是模型能不能稳定按你的要求输出。我把这个阶段放在检索增强前面是因为如果连模型的输出都不可控后面加再多外部知识都是空中楼阁。第三阶段叫增强。加入知识库检索、工具调用和外部数据接入也就是现在主流的RAG和Agent相关技术。这一阶段解决的是模型不知道的东西怎么办。第四阶段叫上线。围绕成本、延迟、评估、监控做工程化改造。一个Demo要变成能承受真实流量的服务需要处理缓存、限流、日志追踪、效果回归等一系列问题这些在教程里很少被系统讲到。这种设计最大的好处是每个阶段都有可验证的产出不会让人学到中途就放弃。我实际带人的时候发现最容易劝退新人的不是技术难而是不知道学到什么程度算学会了。四个阶段的划分本质上是四个里程碑每完成一个都能看到可演示的成果。2. 五个绕不开的核心模块详解2.1 Prompt工程从会聊天到会交付Prompt在我眼里不是简单的一句话而是你和模型之间的完整任务说明书。项目里我把Prompt工程拆成四个层级指令层级、上下文层级、示例层级、格式层级。指令层级是最基础的告诉模型要做什么、不做什么。很多人在这一步就翻车因为指令写得模糊。请总结这篇文章和请用3个要点总结这篇文章每个要点不超过50字并标注信息来源段落的产出质量天差地别。上下文层级决定模型知道什么。这里我想强调一个反常识的点不是上下文越多越好。模型对中间部分的注意力天然偏弱业界通常把这个现象叫做Lost in the Middle。我在项目里验证过把关键信息放在开头和结尾检索效果明显优于把所有材料塞进中间段。上下文塞得越满模型越容易忽略你真正想让它用的部分。示例层级就是Few-Shot。与其花一小时用自然语言解释你想要的输出格式不如直接给模型看两个输出样例。我常用的做法是先让模型生成一版我再人工修改一个标准答案把这组配对作为示例放进后续调用里。这样比凭空写示例更能贴合模型的生成习惯因为示例本身就是从模型的实际输出里修正来的。格式层级是结构化输出的关键。坦白讲用自然语言描述JSON格式再要求必须按格式输出是最脆弱的一种做法。模型随时可能多一个注释、少一个逗号。我在项目里通常让模型用原生支持的结构化输出能力或者输出带标记的内容再用代码解析。这个细节在后面的Function Calling部分展开说。2.2 RAG链路让模型学会检索再回答RAG全称Retrieval-Augmented Generation检索增强生成是目前把私有知识接入大模型最主流的手段也是我整个项目里投入时间最多的部分。核心思想一句话就能讲完模型不知道的知识你先从外部知识库检索出来塞进上下文再让模型基于这些材料回答。但一句话和真正跑通之间隔着大量工程细节。完整RAG链路至少包含五个环节文档解析、文本切分、向量化、向量检索、答案生成。每个环节都有独立的坑。文档解析这一环经常被新手忽略。很多人拿PDF直接丢进工具结果PDF里是扫描图片解析出来全是乱码。我的经验是先做文档体检——看看格式、编码、表格再决定用文本抽取还是走OCR。花十分钟做体检能省下后面几小时的排查时间。文本切分是影响检索质量最直接的因素。切得太碎语义不完整切得太整向量检索可能抓不到精准片段。我在项目里实现了一套递归切分策略先从段落级别切再按句子边界、字符数上限逐级降级。配合10%到15%的重叠比例能有效避免语义在切分处断裂。关于重叠参数的影响我在第3章实操部分会用真实数据说明。向量化的选型直接影响成本和效果。工业界和开源社区已经有很多成熟方案可以按模型尺寸和场景选择合适的嵌入模型。中文场景里BGE系列的表现一直比较稳多语言场景可以考虑通用的大型嵌入模型。另外嵌入模型一定要和业务语言匹配用英文模型处理中文内容检索效果会差一大截。检索环节我强烈建议加一层混合检索。纯向量检索对语义理解强但对关键词精确匹配弱纯关键词检索BM25恰好互补。把两者的结果做加权融合很多原本检索不到的问题都能解决。后面实操章节会给出具体的融合实现方式。2.3 Embedding和向量数据库相似度计算的底层故事很多文章把Embedding讲得很玄其实它就是一句话把文本转换成一串数字向量让语义相近的内容在向量空间里距离更近。然后检索就成了找离得最近的向量。我建议所有入门者都自己动手算一次向量相似度感受会完全不一样。两个句子的相似度本质上就是余弦相似度公式很直白两个向量做点积再除以模长的乘积。值越接近1表示方向越一致语义越接近。举个例子今天天气怎么样和明天会不会下雨算出来的相似度一定比今天天气怎么样和红烧肉怎么做高得多。选向量数据库时我一贯的原则是先按数据量决定不要一开始就上分布式系统。几千条到几万条文档的场景本地跑FAISS或者Chroma完全够用数据量到了百万级再考虑Milvus这类分布式方案如果团队本来就在用PostgreSQLpgvector是最省事的选择——不用引入新基础设施直接用SQL就能做相似查询。这个选择背后其实是一个朴素的工程原则技术复杂度应该跟着业务量走而不是跟着趋势走。我在项目里遇过好几拨人明明只有几万条数据却上来就搭三节点集群运维成本比业务成本还高。选型的时候多问自己一句我现在真的有这个量吗。2.4 Function Calling把大模型接进业务系统如果说Prompt工程是让模型说对话那Function Calling就是让模型干对事。它解决的是模型和外部系统之间的连接问题模型不直接调你的代码而是输出一个结构化的调用意图由你的程序去执行再把结果回传给模型让模型基于真实数据生成最终回答。我在项目里用天气查询Agent演示这套机制用户问上海明天温度怎么样模型理解意图后输出一个JSON标明要调用 search_weather 这个函数、参数是上海和明天。然后你的程序真正去请求天气数据接口拿到结果后拼进Prompt再让模型生成自然语言回答。整个流程里模型负责理解意图和组织语言你的代码负责确保真实。真正做的时候你会发现Function Calling的难点不在于调通而在于参数的稳定性——模型输出的JSON偶尔会多一个字段、少一个引号。我的实操经验是两件事并行一是在函数定义里把参数约束写得尽量具体枚举值就写成枚举别留自由发挥空间二是把模型输出的结构化内容放进一个解析修复的中间层解析失败时启用纠错逻辑而不是直接把错误抛给业务层。另外一个容易被忽略的点是工具返回的结果必须做长度钳制。我见过最典型的翻车是搜索工具返回了三万字的内容一股脑塞进上下文模型瞬间丢失重点。工具返回的内容应该经过摘要或截断只保留与用户问题最相关的部分。把工具输出也当成需要加工的数据来处理而不是原封不动塞进去。2.5 评估体系没有指标就没有优化RAG链路跑通很容易但效果到底行不行这个问题几乎难住了每一个初学者。我在项目里花大力气做的评估体系就是为了解决这个好不好的问题。评估分两个层面检索质量层面和生成质量层面。检索质量看召回率——真正相关的文档有没有被检索出来生成质量看忠实度、相关性和答案完整性——模型有没有基于材料回答、有没有答非所问、有没有漏掉关键点。具体操作上我准备了一个大约一百条问题的评估集每一条都标注了标准答案和应该引用的文档ID。评估脚本每次跑完自动计算检索命中率和答案的忠实度评分。有了这套东西每次改切分策略、换模型、调Prompt都能量化看到效果是变好还是变坏而不是靠感觉好像好了点。我还要强调一个做评估时的细节别用模型评估模型的时候不做交叉验证。很多人直接用同一个大模型既当选手又当裁判打分结果自我感觉良好实际没有参考价值。我的做法是至少用两个不同模型分别打分再结合预设的规则检查比如答案引用了不存在的文档ID就直接判负。评估体系本身也需要被评估这一点是整个项目里我最想提醒的。3. 从零搭建一个可运行的RAG项目3.1 环境准备与依赖配置实操部分从环境准备讲起。这个项目我用的技术栈比较轻Python 3.10以上LangChain做流程编排但底层向量检索我选择直接用FAISS配合自写检索逻辑而不是完全依赖框架封装。这样做的原因是框架让你快速跑通但也把很多关键参数藏起来出了问题很难排查。我的建议是学习阶段少用框架生产阶段再用框架提效。依赖分三块模型推理、向量索引、数据处理。模型推理走OpenAI兼容接口调用这个接口格式已经成为事实标准切换不同模型供应商只需改base_url和API Key。向量索引用的FAISS数据处理用LangChain的文档加载器加自写的切分函数。整套依赖用一个requirements.txt就能装完不涉及任何复杂的分布式组件。这里有一个真实的踩坑记录。我第一次跑通链路时用了框架默认的文档切分器结果切出来的文本块大小不一有的块只有二十个字有的块超过两千字检索命中率惨不忍睹。换用自写的递归切分器并统一块大小后同一批评估题目的召回率从62%提升到81%。这个差距说明切分策略在RAG链路里的权重远比大多数人以为的高。3.2 文档切分与索引构建切分这一步我最终实现了一个递归切分器逻辑分三层先用段落标记分块再检查每个块是否超过容量上限超出的部分按句子边界继续切分最后给相邻块加上15%的重叠。重叠比例这个参数值得单独说。不加重叠一个完整知识点恰好被切在两个块中间的概率很高检索时两边都只抓到一半回答自然不完整。加了15%的重叠等于给语义连续性上了一道保险。我测试过5%、15%、30%三档15%在成本和效果之间最均衡。重叠过大只会浪费存储和检索时间收益却很小5%则不足以覆盖边界语义断裂的问题。索引构建完成后我的习惯是马上做一轮抽样检视。随机挑几个查询语句打印出每个查询召回的Top-5文档人工看一眼这些文档是不是真的相关。这一步不花多少时间但能在你写复杂评估代码之前就暴露七八成的问题——切分粒度不对、脏数据混入、某些文档压根没被正确解析都会在这一步现出原形。抽样检视还有另一个作用帮你校准检索参数。比如你发现Top-5里有两条完全不相关的内容那就该考虑调高相似度阈值或者调高BM25的权重。别急着写一堆自动化先用眼睛看再让脚本算。3.3 检索与生成的完整闭环实现以下是我在项目里用的检索生成闭环核心实现做了简化注释重点在于让你看清链路里每一步在做什么。def retrieve_and_generate(question, index, embed_model, llm, top_k5): # 1. 问题向量化 query_vector embed_model.encode(question) # 2. 向量检索 关键词检索混合 vector_results index.search(query_vector, top_k) bm25_results bm25_index.search(question, top_k) # 3. 结果融合向量得分0.7权重关键词得分0.3权重 merged merge_scores(vector_results, bm25_results, weights(0.7, 0.3)) # 4. 拼接上下文构建生成Prompt context \n\n.join([doc.text for doc in merged]) prompt build_prompt(question, context) # 5. 调用大模型生成答案 answer llm.chat(prompt) return answer, [doc.id for doc in merged]这段代码看起来简单但每一步都有讲究。混合检索的权重建议从0.7比0.3起步再根据评估结果微调。如果你的业务场景里关键词很重要比如产品名、型号、订单号那关键词权重应该往上调甚至可以到0.5比0.5。没有万能比例只有适合你数据的比例。生成Prompt的构建同样关键。我的模板固定包含三段角色设定、上下文材料、任务要求。任务要求里明确写出如果材料中没有相关信息请直接说明不知道不要编造。这句兜底话术加上约束后我在评估集上测过幻觉比例下降了大约三成。就这么一句话的差别效果能差出几个百分点。另外特别注意检索返回的文档要去重。同一个内容可能因为不同来源被索引了两遍直接拼进上下文会重复消耗Token还可能让模型误以为这个信息很重要。我在merge_scores函数里拿文档ID做了去重这个细节虽然不起眼但对效率和稳定性都有实打实的帮助。3.4 一次真实的调优现场记录这一节我完整还原一次调优过程你能看到我是怎么根据评估数据一步步定位问题的。第一轮跑完评估结果显示检索召回率只有58%但生成答案的忠实度有90%。这说明问题出在检索端——模型拿到的东西不对但拿到对的东西时用得不错。我立刻排查切分结果发现很多块末尾被截断了半句话。调整切分器的容量上限并优化块结束时的句子边界判定召回率升到71%。第二轮问题换到了生成端。忠实度掉到了75%我检查了部分错误样本发现场景是用户连续追问时新问题被孤立地送进检索器没有带上对话历史中的关键上下文。于是我给检索增加了一步对话查询改写先把用户的最新问题和历史消息压缩成一个独立的自包含问题再送入检索链路。改完之后忠实度回升到86%。第三轮我看的是延迟。单次请求平均耗时2.8秒太慢了。分析后发现向量检索占了大头因为索引文件全部加载在内存里做暴力搜索。我把索引换成二进制文件映射加载并加了缓存层——完整的答案按问题哈希缓存命中的请求直接返回。最终平均耗时降到0.75秒其中约四成请求命中缓存零计算。三轮调优下来检索召回率从58%到81%再到稳定在85%左右忠实度稳定在86%以上延迟降了73%。这个结果不是靠某一个神奇技巧实现的而是靠评估体系持续定位问题、逐步修正得到的。这也是我反复强调没有指标就没有优化的原因。4. 常见问题与排查技巧实录4.1 效果不理想时按什么顺序排查我见过太多人一遇到回答质量差就第一时间去改Prompt改了半天没用。我的排查顺序是先查检索再查上下文最后才碰模型和Prompt。理由很简单如果模型压根没拿到相关材料你再怎么写Prompt都是巧妇难为无米之炊。怎么快速判断检索有没有问题把检索结果直接打印出来看或者做一个直读测试——把检索到的文档原文拼成Prompt问模型根据以下材料回答XX问题。如果答案还是不对问题在检索如果答案对了说明检索OK问题出在生成端的上下文组织方式。上下文这一层很多人会忽略上下文污染。检索返回了十个文档其中八个相关、两个是噪音模型的注意力会被噪音带跑。我建议做剪枝把检索结果里相似度分数低于阈值的直接丢掉宁可少给也不能给错的。这个操作对回答质量的提升往往比换模型还明显。最后才回到模型层。如果检索和上下文都对答案依然不理想那就需要考虑是否该换更强模型、是否该用Few-Shot示例、甚至是否该加后处理规则做修正。这个顺序走下来能避免90%盲目调参的时间浪费。4.2 成本与性能的平衡点Token成本是大模型应用绕不开的话题。我的经验是三条线并行控制。第一是检索端控成本向量化一次查询的成本极低真正贵的是塞进上下文的那些材料。我最常做的优化是把Top-K从5降到3配合阈值剪枝。别小看这个改动K值从5降到3一次请求的输入Token可能直接少掉三分之一。第二是生成端控成本在业务允许的范围内把答案的最大输出长度尽可能调小。很多场景根本不需要一千字200到300字足够。输出Token的单价通常是输入Token的三到五倍这个优化立竿见影。第三是加缓存和复用。同质化问题比如退货政策是什么这类高频问题命中缓存的比例非常高。我在生产环境里观察到加了语义缓存之后真实流量里大约30%到40%的请求可以不调用模型。这个收益是纯利润而且是立刻就能看到的纯利润。性能方面流式输出能极大改善用户体验因为首字延迟被压到了几百毫秒。很多人把总耗时当作体验指标其实用户感知的是第一个字出来的时间。项目里我把流式输出也做了完整封装建议你在自己的应用里尽早接入。别等上线了再补流式输出的改动力度比想象中大。4.3 典型坑位速查表最后这张表是我整个项目过程中踩过的坑和对应的解决方案整理成速查表按场景查就行。症状根因解决方案检索召回率低文档切分破坏语义单元改用递归切分按段落-句子两级处理加15%重叠答案引用不存在的内容模型幻觉Prompt加不知道就直说约束配合引文校验JSON输出解析失败模型输出格式漂移用结构化输出能力约束加解析修复中间层长文本回答越到后面越跑题上下文过长导致注意力衰减把关键指令放Prompt首尾精简上下文材料连续追问答非所问检索没有携带对话历史增加查询改写先生成自包含问题再检索延迟高全量暴力检索换ANN索引或二进制映射加载加缓存向量库里混入脏数据文档解析不完整构建索引前做文档体检异常文档单独排查这张表不是严格的理论推导而是我实测总结出来的规律。每个症状对应的根因我都花过大量时间验证。我的建议是把它当成排查起点而不是终点——每个问题都可能有新的表现形态排查思路比结论结论值钱。我在实际做这个项目的过程中最大的体会是AI工程和传统开发之间最大的不同在于每个环节都无法靠想当然过关。你以为切分没问题跑一版评估数据说话你以为参数合理压一次真实流量数据说话。这个以数据驱动迭代的习惯可能比任何具体技术都更重要。希望这份从头到尾的拆解能帮你少走一段弯路尽快把自己的AI项目从一个能跑的Demo变成一套真正可靠的系统。
返回列表