ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从零开始的AI工程化:从Prompt到生产环境的完整路线

从零开始的AI工程化:从Prompt到生产环境的完整路线 1. 项目定位为什么我要折腾一个从零开始的 AI 工程先把这个项目名拆开看。ai-engineering-from-scratch 翻译过来就是从零开始的 AI 工程化它不是一个教你调用 API 的入门教程也不是一份甩一堆前沿论文的读书清单而是一条以能落地的 AI 应用为目标、从最底层往上逐层构建的工程实践路线。我做这个项目的核心原因很简单市面上大多数 AI 课程都在教怎么跑通一个 demo但真实业务需要的从来不是把 prompt 调通而是把一个模型变成稳定、可维护、可观测、成本可控的线上服务。我观察过好几个团队大家都有类似困境模型选型会了、Prompt 会写了一旦涉及并发、流式响应、缓存、日志、回滚、评估就集体发懵。这个项目就是把这些没人系统讲透的工程死角摊开让你自己动手从第一行代码开始把它搭起来。它适合谁一类是刚接触 LLM 应用开发、想搞懂除了调 API 之外到底还要会什么的开发者另一类是已经在业务里用过一两个开源模型但觉得每次上线都像在走钢丝、想系统性补齐工程能力的后端或算法工程师。我自己就是后者所以这个项目里所有踩过的坑后面几乎都会提到。这个项目最大的价值不在于代码量而在于把 AI 应用的复杂度做了清晰分层。我会从最基础的 prompt 结构化开始一路走到 RAG 检索增强、模型评估、微调、Agent 工具调用再到容器化部署和监控。每一层都遵循同一个原则先自己实现再谈优化先看懂原理再上现成框架。这样既能锻炼基本功又不至于被各种抽象框架带偏。下面我按照实际推进顺序把整条路线、核心技术点和最容易出问题的地方串起来讲。2. 路线设计与思路拆解从 Prompt 到生产环境的四个阶段这一节我要先把整条学习路线的设计逻辑讲明白因为很多人失败不是不够努力而是路线本身就有问题。一上来就搞 LangChain 全家桶Prompts、Memory、Agent、Vector Store 全拧在一起遇到报错连是哪个环节挂了都分不清。所以我给这个项目设计了四个阶段每个阶段只解决一个核心问题阶段之间可以独立验证。2.1 阶段一把 Prompt 当成软件工程来管理第一阶段做的事情看起来很简单围绕模型封装一个统一的调用层把 system prompt、user prompt、few-shot 示例、输出格式全部结构化。我的做法是定义一个 PromptTemplate 类每个业务场景对应一个独立的模板文件用配置文件管理模型名称、temperature、max_tokens 等参数而不是在业务代码里到处硬编码。这一步的核心不在于技术难度而在于培养提示词也是代码资产的意识。很多团队最终 Prompt 改到无法维护就是因为提示词散落在业务逻辑里换个模型、改个输出格式就要全局搜索。我的建议是从项目的第一天起所有 Prompt 必须走版本管理必须能测试必须能快速回滚。这个阶段虽然不涉及任何高端技术但它决定了后续所有阶段的工程质量。2.2 阶段二用服务化思维改造单机调用当调用层稳定之后第二阶段要解决的是从脚本到服务的问题。很多人把模型调用写成一个函数就完事了但真实线上场景需要的是并发控制、超时管理、请求排队、流式响应、错误重试。这一阶段我会用 FastAPI 写一个独立的 LLM 代理服务对外暴露 HTTP 接口内部封装模型调用链路。选 FastAPI 不是为了赶时髦而是它天然支持异步和流式响应而且类型校验、OpenAPI 文档都是现成的。加上 Uvicorn 做 ASGI 服务器配合一层进程内队列做速率限制就能实现一个最简单但完整的模型服务。这个阶段最能训练工程感觉同样是调一个模型从自己电脑上跑通到能扛住线上请求差距就在这些服务化细节里。2.3 阶段三从调一次进化为带着上下文调第三阶段开始进入 RAG。这个阶段的核心目标是解决模型的记忆缺陷模型不会知道你私有文档里的信息所以要把相关资料检索出来拼进上下文。但这个阶段最容易犯的错误是把 RAG 等同于建个向量数据库然后 embedding 一下。我的设计是把它拆成三个独立子系统文档解析与清洗、分块与向量化、检索与重排序。文档解析要做好格式兼容PDF、Word、Markdown、扫描件得分别处理分块策略要结合文档结构而不是按固定字数硬切检索则要同时支持向量召回和关键词召回并在排序阶段做融合。这个阶段我强烈建议先自己实现一遍基本的检索逻辑再引入向量数据库。否则你会完全无法理解为什么搜索结果这么差。2.4 阶段四让模型自我评估与持续迭代最后一个阶段也是多数项目不会教你的评估。AI 应用和传统软件最大的不同在于没有确定性输出同一个 Prompt 不同模型给的结果可能天差地别。所以一定要建立一套离线评估流程用测试集去衡量每次改动是变好了还是变坏了。这里我会引入三类评估手段基于规则的校验、基于模型的打分、基于人工抽检的异常追踪。其中 LLM-as-judge 是目前比较主流的方式用 GPT-4 级别的大模型去给回答质量打分但在工程实现时要特别注意打分一致性和偏差控制。这个阶段看似不是核心功能但实际上它才是决定项目能不能长期维护的关键。不建立评估体系后期所有优化都是盲人摸象。3. 核心细节解析技术栈选型背后的真实考量项目推进到一半我开始意识到技术选型不是哪个火选哪个而是哪个能让我更好地理解问题。以下几条选型决策是我在整个过程中踩了很多坑才确定的供你参考。3.1 模型层使用开源模型而非 API 依赖这个项目从一开始就刻意选用本地可部署的开源模型而不是单一厂商的闭源 API。做这个决定有几点考量第一闭源 API 确实方便但它把模型细节全部黑盒化不利于理解生成原理第二线上项目一旦涉及私有数据模型部署位置往往会有严格要求第三成本可控性。实际使用中我主要用的模型包括 Qwen 系列、Llama 系列和 Mistral 系列针对不同任务场景轮换使用。选开源模型也有麻烦的地方模型权重要自己下载、部署环境要自己配、量化版本的选择也要自己测。我记得第一次在本地跑一个 70B 模型单是显存规划就折腾了两天后来干脆把整个部署流程脚本化、容器化这才算彻底解决。这个过程中学到的 GPU 内存管理、张量并行、KV Cache 池化等知识在各行各业都是有长期价值的。3.2 检索层为什么我坚持没有完美的分块策略做 RAG 检索时分块策略是影响最终效果的首要因素。很多教程会说按 512 个 token 切就行但实际项目里这种固定切法经常把完整语义割碎。我的做法是结构感知分块先解析文档的标题层级按语义段落切分再在同一段落内做重叠滑窗。比如一个技术文档通常有概述、环境准备、参数说明、常见问题这些章节。按章节边界切块再对过长的章节做二级切分要比均匀切块好得多。另一方面检索不能只靠向量相似度我还要保证关键词搜索的通道。因为业务专有名词经常在向量空间里表达不佳比如一个医疗术语缩写embedding 模型可能根本没见过。所以我在检索层做的是向量召回 关键词召回 得分融合这样对各类查询都能兜底。3.3 服务层流式输出是最容易翻车的环节模型服务化里面流式输出是体验的关键也是最容易翻车的环节。前端的打字机效果背后其实是后端通过 SSEServer-Sent Events把 token 逐步推给前端。这里第一个坑是很多模型推理框架的流式输出并不完全兼容标准 SSE 协议需要自己做格式转换第二个坑是流式过程中如果出现异常连接会直接断开导致用户只看到半句话。我最终实现了一个比较稳妥的方案在服务层做两层缓冲。第一层是模型生成的 token 流缓冲第二层是前端消费的事件缓冲。如果模型生成中断服务层会补发一个错误标记如果前端断连服务层会主动取消生成任务避免算力浪费。这个细节看起来不起眼但实际线上环境的稳定性很大程度就取决于这些边界情况处理得干不干净。3.4 评估层LLM 当裁判也要防作弊LLM-as-judge 是目前比较火的评估方式我用下来确实效率很高但也发现了不少系统性问题。最重要的一点是位置偏差让模型对两个回答进行对比打分时排在前面和排在后面的结果往往会被区别对待。我做了一个简单的消偏处理同一组对比把两个回答的先后顺序调换后再打分一次取平均值作为最终分。另外如果拿小模型当裁判它对复杂回答的判断很不稳定。我实际测试下来至少要使用当前可用模型里第一梯队的那档来做打分否则评估结果本身就是噪声。评估工程化之后我每次修改 Prompt 或调整 RAG 参数都会先跑一版评估报告再决定是否合入业务逻辑这个流程让整个迭代过程变得可预期、可回滚。4. 实操过程与核心环节实现这一节我会挑出几个最有代表性的实操环节从配置到实现一步一步给你过一遍。重点是让你看完后能直接在自己的环境里照着重做而不是只记住一些结论。4.1 第一步搭好本地模型服务的环境模型服务是整个项目的地基。我的推荐配置是一台至少有 16GB 显存的 GPU 机器配合 Docker 环境。如果条件不足也可以用 CPU 推理跑一个小一点的模型先练手但真正完整的检索跑下来还是建议有 GPU。环境搭建顺序大概是这样的安装 Docker 和 NVIDIA Container Toolkit让容器内能访问 GPU。拉取一个支持 OpenAI 兼容接口的推理框架镜像比如带 OpenAI 协议适配的 LLM 推理框架。下载模型权重并映射到容器内部路径。启动容器配置模型名称、上下文长度、量化精度等参数。利用标准 REST 接口先测试一个最简单的请求。这里有个容易踩的坑很多模型在推理框架里默认上下文长度远小于模型实际能力如果不显式配置 max length系统可能会在长文本输入时直接报错。我的做法是显式配置上下文长度同时给服务加上健康检查接口方便后续做容器编排时识别服务状态。启动成功后用 curl 发一条最简单的对话消息验证链路通了再进下一步。4.2 第二步实现 LLM 代理服务与统一调用层本地模型跑通后接下来要做的不是直接写业务逻辑而是实现一个面向全项目的统一调用层。我用 FastAPI 搭了一个名为 llm-service 的应用对外暴露 /v1/chat/completions 接口这个接口内部会统一处理模板加载、参数注入、模型调用、超时控制、错误重试。关键代码逻辑并不复杂核心是一个带状态管理的 routerfrom fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field import asyncio app FastAPI() class ChatRequest(BaseModel): prompt: str Field(..., description用户输入) scene: str Field(..., description业务场景用于选择模板) stream: bool Field(False, description是否流式输出) app.post(/v1/chat/completions) async def chat(req: ChatRequest): try: template load_prompt_template(req.scene) messages template.fill(user_inputreq.prompt) if req.stream: return streaming_response(messages) result await sync_llm_call(messages) return {choice: result} except LLMTimeoutError: raise HTTPException(status_code504, detailmodel inference timeout)不要小看这个看似简单的封装。它把模型厂商的接口差异和业务代码彻底隔离开来。后续如果想换模型、加缓存、加拦截器都只在这个服务内部改动业务方完全无感知。这里还应该加上一个简单的内存缓存对相同请求的重复问题进行命中这个优化在线上的收益非常明显。4.3 第三步从零实现一个 RAG 检索链路RAG 部分我建议先别急着上向量数据库。我第一次做的时候直接用了成熟的向量库结果排障时完全看不出来是分块的问题、embedding 的问题还是检索参数的问题。后来换成先自己实现一个朴素的检索器才把这些环节一个个搞明白。自己做检索流程是这样的先用解析器把文档转换成结构化文本按前面说的结构感知分块法切块再把每个块用 embedding 模型转成向量。没有向量库之前可以先存在本地文件里用暴力遍历算余弦相似度数据量几百条时完全够用。这样做的好处是你能清楚看到每个块是如何被召回的也能直观感受分块质量对结果的影响。检索接口我设计为同时支持向量召回和关键词召回关键词部分用简单的 TF-IDF 权重就可以没必要一上来就上 BM25 或者混合检索框架。最后用一个加权公式融合两部分得分典型权重是向量 0.6、关键词 0.4这个比例要基于具体数据微调。整个检索链路用 Python 实现不超过 200 行但理解深度远超直接用框架。4.4 第四步把检索结果拼进 Prompt做一次完整问答RAG 链路拼起来之后完整问答的流程是用户输入 → 改写查询 → 检索召回 → 重排序 → 拼装上下文 → 调 LLM → 输出回答。这里值得抠细节的是查询改写。直接用用户原始输入去检索经常因为口语化表达、指代不明导致召回不准确。所以我加了一步先把用户输入发给大模型改写成一个适合检索的查询语句再做检索。举个例子用户问它支持哪些语言这里的它非常含糊。先让模型改写为支持语言功能清单再去做检索命中率会明显提升。这个步骤看起来是纯工程技巧但本质上是用模型能力换检索精度。整个完整流程跑通之后我明显感受到AI 应用工程化真正的复杂度不在某一个环节而在于把各个环节可靠地串联起来并且每一条串联路径都能被验证。5. 常见问题速查与排查技巧实录这个项目做到后期我整理了一份问题速查表。挑选几个典型问题分享出来都是线上真正让人头疼的场景。5.1 相似问题排查为什么检索结果总是不理想检索不理想分为两种一种是召回不到一种是召回一堆但排序不对。召回不到要先检查分块粒度是不是太大导致一个块内信息太杂再看 embedding 模型是不是和领域文本不匹配。排序不对大概率是重排序环节缺失或者融合权重没有针对测试集调优。我自己的一个惨痛教训是最开始用通用 embedding 模型处理专业文档检索效果一直很差后来换成了在专业语料上微调过的 embedding 模型同一个测试集的效果提升非常明显。这说明在 RAG 系统里embedding 模型的领域适配度有时比模型本身的大小更重要。5.2 流式输出中断与假死流式输出是另一个高频故障点。现象是前端页面长时间没有新 token但后端进程还活着似乎哪个环节卡住了。这个问题的根源往往在模型推理框架本身尤其是在批量并发推理时某个长请求长时间占住了算力后续请求全部排队造成假死。我的排查思路是先给所有推理请求加上超时和进度日志看看具体是卡在排队还是生成中如果是排队问题就调整服务队列策略把长请求和短请求分开调度如果是生成问题就要检查是不是生成了超长序列加一个最大 token 数限制。另外每次升级推理框架后必须重新做一次全链路压测因为框架的队列行为经常有变化。5.3 Prompt 优化后效果反而变差有意思的是很多时候我们优化 Prompt 后人工看是变好了但整体评估分数却下降了。这通常不是模型变笨了而是 Prompt 变化后原有测试集里的某些 case 对格式、语气的要求和之前不一致了。解决这个问题没有捷径只有把评估测试集做细按场景分类统计才能看清到底是哪一类 case 变差、为什么变差。我现在养成的习惯是每次 Prompt 改动都生成一个评估报告按任务类型、输入长度、输出长度三个维度做交叉统计。如果某个维度明显下降就回滚改动不要盲目追求某个指标好看。这个流程看上去费时间但它避免了大量这次改完感觉好了下次上线就翻车的情况。6. 一些踩坑后的个人经验项目推进到现在我自己最大的体会是做 AI 工程化最稀缺的不是模型知识而是稳定复现的能力。模型能力固然重要但如果连改了个参数为什么效果变了都说不清这个项目基本上只能靠运气维护。所以每做一个改动我都会同时记录三件事改了什么、验证方法是什么、预期结果是什么。这条习惯帮我避免了很多无效优化。另一个经验是工具选型一定要留给未来足够的更换空间。我在这个项目里没有把任何核心模块跟具体框架绑死模型服务、检索器、评估器之间都是通过简单接口对接的。现在回头看正是这种低耦合、可替换的设计让项目能持续迭代。很多团队用一个大而全的框架把一切封装好结果业务逻辑被框架绑架想换组件几乎等于重写这种代价在后期会非常痛苦。最后分享一个小技巧搭完整个链路后一定要留一份最小可运行版本的脚本它只依赖一个本地模型和一个小的示例文档但能跑通完整问答流程。以后无论是模型升级、组件切换还是环境迁移都先拿这份最小脚本验证。只要它还跑得通你的系统地基就没有大问题剩下的优化都可以慢慢来。
返回列表