很多开发者第一次正儿八经搭 AI Engineering 项目时都以为难点在模型选型GPT 还是 Claude开源还是闭源上下文窗口多大。实际跑过一遍才发现真正的坑全在工程链路上。模型调用只是最后一步前面需求拆解、上下文构造、Prompt 版本管理、评估回归随便哪个环节偷了懒后期都要花十倍时间补。我自己从零搭过几个完整项目这篇就把那条踩平的路重走一遍把 AI Engineering from Scratch 到底在 Scratch 什么讲清楚。1. AI Engineering 从零搭建时最容易陷入的三个误区先说说我见过最多的翻车现场。很多人觉得 AI Engineering 和传统软件开发的区别就是多调一个 API其余照旧。结果项目跑到一半模型表现不稳定、Prompt 改一版崩一版、评估全靠肉眼抽几条看效果整个项目变成不可维护的黑盒。第一个误区是把 Prompt 当一次性文本。写过传统代码的人都知道变量要命名、函数要拆分、逻辑要注释。但到了 Prompt 这里很多人直接在手写一段长文本塞进代码里改的时候全局搜索替换。几轮迭代之后连自己都不知道哪版 Prompt 对应哪个线上效果。Prompt 本质上是一段需要版本管理、评审、回归测试的代码不认真对待就会变成技术债的重灾区。第二个误区是忽视上下文工程。我早期做 RAG 项目时核心精力全放在向量库选型和 Embedding 模型调优上结果效果一直上不去。后来逐条分析才发现问题出在上下文拼装方式同一份知识库里既有长篇技术文档又有短问答对直接拼进 Prompt 后模型注意力被分散关键信息被淹没。上下文不是简单把资料塞给模型它决定模型能看到什么、关注什么这才是 AI Engineering 最吃功夫的地方。第三个误区是用肉眼代替自动化评估。没有评估体系的项目Prompt 一改有没有变好全靠感觉。很多时候感觉好像更顺了实际跑一批测试用例发现准确率反而掉了。更麻烦的是模型版本升级、向量库数据更新这些外部变化都会悄悄影响输出质量没有自动化评估兜底问题往往要等到线上用户反馈才暴露。这三个误区叠加在一起基本等于把一个 AI 项目的工程属性抹掉了。要避免它们需要从项目的第一天就建立起一套完整的 AI Engineering 工作流下面我按自己的实践顺序展开讲。2. 项目的第一块地基把模糊需求翻译成模型可执行的任务AI Engineering 和传统开发最大的不同在于需求方描述的是一个感觉而模型需要的是一个任务。帮我做一个智能客服这种需求传统开发还能靠产品经理细化功能列表但 AI 项目里细化方式完全不同——你要定义的是输入输出边界、判断准则、兜底策略。2.1 从想要什么效果倒推需要什么能力拿到一个需求我习惯先问三个问题模型需要做什么类型的任务任务的输入是什么形态输出要被谁消费这三个问题看着简单实际操作中特别容易含糊。举个例子有次我需要一个资料总结助手需求方的原始描述是把长文变成要点。但如果我只按这个描述去写 Prompt输出的可能是全文大意概括也可能是逐段要点罗列完全取决于模型当时的心情。我后来把需求拆成明确的任务定义输入是 5000 字以内的技术文章输出是 5 到 8 条结构化要点每条要点不超过 50 字并且要保留原文中的关键数字和术语。这一拆模型的输出立刻稳定了。任务定义还有一个容易忽略的维度判断准则。你要清楚模型在什么情况下算做对。比如总结任务什么叫好忠实原文、覆盖关键信息、不添加原文没有的结论。这些准则要写清楚一方面用来指导 Prompt 设计另一方面也是后面做自动化评估时的打分依据。2.2 为模型准备输入输出的最小契约传统开发里接口要有 SchemaAI 项目里输入输出同样要有。我在实践中发现给模型的输入输出定义一份最小契约能省掉后期大量麻烦。输入契约要写明用户原始输入是什么系统需要附加哪些背景信息哪些字段是必填的哪些允许为空。输出契约要定义格式纯文本还是 JSON要不要包一层固定结构枚举值有哪些。我通常会让模型输出带固定字段的 JSON这样下游解析稳定也方便接入后续的校验和评估逻辑。这里有一个特别实用的技巧用 System Prompt 里的输出格式说明 少量示例做约束。光靠请以 JSON 格式返回这种描述模型偶尔会漏字段或加多余字段。我一般在 System Prompt 里放一个完整示例再在 User 消息里明确严格按上述格式返回实测下来字段稳定性高了很多。后续我把这套称为契约式上下文它是上下文工程的第一步。2.3 拆任务一个复杂需求拆成多条 Pipeline很多需求表面是一个任务实际上是多个任务的串联。我做过一个报告自动撰写项目需求方说给我一份完整分析报告。如果直接丢给模型让它一口气生成输出的东西往往空泛、没有依据。我的做法是把它拆成三条链路先做资料检索和筛选再对选中的资料逐段要点提取最后基于要点做报告合成。每一步的输入输出都是上一步的产出链路清晰哪个环节出了问题都好定位。拆任务还有一个额外好处你可以对每一步单独做评估而不是等最后整篇报告出来才发现质量不行那时候连是哪一步出的问题都难查。3. 上下文工程决定模型输出质量上限的真正关键有人觉得上下文工程就是把数据库查出来的资料拼进 Prompt。如果 AI Engineering 这么简单那最值钱的岗位应该是拼接字符串工程师。真实的上下文工程要解决三个问题给模型什么、以什么顺序给、怎么防止它被无关信息干扰。3.1 RAG 之外的上下文拼装细节做 RAG 项目时大部分人会在检索环节花大力气却忽略拼装环节。我有过一段很典型的踩坑经历向量检索召回 10 段内容为了充分利用我全部塞进上下文结果模型输出质量反而变差。分析下来是因为召回的段落里有相互矛盾的信息模型不知道以哪个为准于是自己和稀泥。正确的做法是在拼装前加一道过滤和排序。过滤规则可以根据业务自定义比如剔除相似度低于阈值的内容、剔除与当前问题实体无关的段落、剔除重复信息。排序则要考虑信息优先级直接答案优先、权威来源优先、时间新者优先。做完过滤排序之后再在上下文里加一句引导告诉模型以下资料按优先级排序如果资料之间存在冲突以排序靠前的为准。这种细节不写进代码里看不出区别但实际效果差异巨大。还有拼接方式同一段资料直接贴原文和先做一次压缩再贴入模型的理解效果完全不同。我的实践经验是如果资料里有大量无关的修饰性语句先让模型做一次信息压缩——提取结论和关键数据再把这些压缩后的片段拼进最终上下文。这样上下文里每句话都在为输出服务模型注意力不会被稀释。3.2 对话历史的管理策略多轮对话项目里上下文工程还包括历史消息管理。无脑把所有历史塞进去很快会把上下文窗口撑爆而且早期对话里的噪声会影响当前问题的判断。我采用的分层策略是短期记忆完整保留——最近两到三轮对话因为这些内容与当前意图强相关中期记忆做摘要——更早的对话让模型先总结成几个要点再存入上下文长期记忆只留关键事实——用户偏好、确认过的信息、重要的状态变化这些用结构化记录存放。这套策略实现起来不算复杂却能把上下文利用率提到最高。另外一个容易被忽略的点是历史消息的角色标记。有些模型对连续多条 User 消息处理不稳定我习惯在拼接时强制交替 User 和 Assistant 角色或者把系统信息合并成一条带标记的消息。这些小细节属于不说你可能永远不知道说了你一试就懂的范畴。3.3 信息密度的平衡不是给得越多越好我给上下文工程定的一个核心原则是信息密度优先于信息总量。模型处理能力有限给它 100 段有用程度递减的资料不如给它 10 段高度相关、去噪后的资料。实操中我会给每条上下文内容打一个信息密度分从三个维度评估与当前任务的语义相关度、作为答案依据的可信度、信息的新鲜度。总分低于阈值的内容直接丢弃。同时控制总量比如单条回答的上下文总长度尽量不超过模型上下文窗口的三分之一留出足够的生成空间也让模型不会因为信息过载而迷路。有人担心丢弃内容会损失信息其实丢失一些边缘信息换来的是核心信息被更有效地利用整体收益更大。4. Prompt 工程从随缘调参走向版本化管理Prompt 写得好不好直接决定模型下限高不高。但比写好更重要的是让你的 Prompt 体系可控、可迭代、可回滚。我把 Prompt 当作一等公民来管理而不是散落在代码里的字符串。4.1 System Prompt 的结构拆解角色、任务、准则、输出格式我推荐把 System Prompt 拆成四个区块来写而不是一大段意识流。第一块是角色和目标。给模型一个清晰的定位说明它是什么、为谁服务。第二块是任务描述讲清楚它要完成什么类型的任务输入是什么输出给谁。第三块是执行准则列出模型必须遵守的规则比如必须基于上下文回答不得编造信息遇到无法回答的情况要明确告知。第四块是输出格式和示例。这种结构化写法最大的好处是可维护性。某一版效果波动时能快速定位是哪一块出了问题——角色定义不清、准则冲突、还是格式约束不够。我见过很多混乱的 Prompt本质上是把四类信息揉在一起写改一处牵动全部越改越乱。4.2 写 Prompt 的版本迭代方法与实践心得写 Prompt 不要指望一版定稿要像写代码一样走实现—测试—反馈—修改循环。我通常从 V1 开始先搭一个粗略的完整版本跑通流程然后用一批测试用例去试探边界。这里有一个很重要的心得不要只在理想输入上测试要多测边缘情况。比如空输入、超长输入、包含攻击性内容的输入、用户意图模糊的输入。我整理了一组约 30 条的毒打测试集专门用来检验 Prompt 的鲁棒性。每当 Prompt 改版先跑这组测试集看有没有明显的输出异常再跑正式评估集看效果指标。版本迭代时我会在 Prompt 文件头部维护一个变更记录写明每个版本改了哪个区块、为什么改、上一版效果如何。有些版本的改动看似合理实际效果反而变差了这时候版本记录就派上用场——可以快速回滚到效果好的版本而不是对着 Git 历史一头雾水地找。4.3 上下文中的动态变量与静态模板分离把 Prompt 里的静态模板和动态变量分开是工程化的基本要求。静态模板是每次请求都不变的部分比如任务说明、准则、示例。动态变量是每次请求都不同的部分比如用户问题、检索回来的资料、对话历史。我的做法是模板文件里用${变量名}占位运行时统一渲染。这样做的好处有三个模板修改不用动代码、变量注入逻辑统一可控、模板内容可以直接做版本对比。另外我强烈建议不要用字符串拼接的方式动态构造 Prompt很容易出现变量内容里包含特殊字符导致格式被破坏的情况。统一走模板渲染数据清洗和格式转义都放在渲染前问题会少很多。5. 评估体系没有它AI 项目的所有优化都是自我感动在传统开发里功能对不对可以靠单元测试断言。AI 项目不一样模型输出是概率性的同一问题不同时候回答可能不同。评估体系的作用就是把概率性的输出变成可量化、可比较的指标让每一次改动都有据可查。5.1 从肉眼评估到三层次评估金字塔我自己在实践中把评估分成三个层次从低到高分别是单元评估、场景评估、端到端评估。单元评估针对单次模型调用的输出质量比如回答是否包含关键信息、格式是否合规、是否偏离上下文。场景评估针对某个完整链路比如 RAG 项目里从问题到检索到回答的全流程质量。端到端评估则模拟用户真实使用路径比如连续多轮对话后的最终结果是否满足需求。三层评估各自的成本和价值不同。单元评估最便宜适合每次 Prompt 改动后快速回归场景评估中等成本适合每周跑一次端到端评估最贵适合上线前和重大版本升级时跑。三层结合既能快速迭代又能保证整体方向不偏。5.2 评估数据集的构建思路不是堆数量而是覆盖场景评估数据集是评估体系的核心资产比模型的 Prompt 更值钱。构建数据集的关键不是数量而是场景覆盖度。我的数据集分为几类标准正例——符合预期的正常输入输出对边界用例——输入特别短、特别长、含特殊字符等对抗用例——包含诱导性、歧义性、超出知识范围的问题回归用例——历史上曾经出过错的问题专门防旧病复发。每条用例除了输入和期望输出我还会附带评分维度比如关键信息覆盖率格式合规性信息忠实度。这样每次评估跑完能看到模型在哪个维度上退步了而不是只有一个模糊的整体分数。5.3 自动评估方法LLM-as-Judge 的实战配置与避坑评估方法上我现在最常用的是 LLM-as-Judge——用一个强模型给另一个模型的输出打分。这个方法效果好但有几个坑必须避开。第一个坑是评分标准模糊。如果 Judge 的 Prompt 只写请评估以下回答的质量不同次评估之间的评分尺度会漂移。我的做法是把评分标准拆成具体的分项每项 1 到 5 分并配上每个分值的具体特征描述。比如信息忠实度5 分表示所有关键信息与参考一致3 分表示存在部分漏报。第二个坑是位置偏见。Judge 模型在对比两个回答时可能会偏向排在前面的那个。我的规避方法是把两个回答的顺序随机打乱多次评估取平均。第三个坑是 Judge 被带偏——如果被评估的回答很长且包含很多正确内容Judge 可能忽略其中夹带的错误。我的做法是强制 Judge 先列出回答中的具体错误点再给分数这样能迫使它先做检查而不是直接给感觉分。5.4 回归测试让每一次改动都有据可查回归测试是我在所有 AI 项目里都会坚持的一环。它的逻辑和传统软件测试一样每次改动后跑一遍完整的评估数据集对比改动前后的指标变化。我把评估指标分为核心指标和观察指标。核心指标是必须守住底线的比如回答准确率不得低于某个阈值、格式合规率不得低于某个阈值。观察指标是参考性的比如回答平均长度、引用资料数量。核心指标一旦回退再好的改动也要重新审视。回归测试还有一个重要作用它能让团队里所有人都对模型效果有一个共识。不然产品说感觉变好了开发说感觉变差了谁也说服不了谁。有了量化数据讨论就能聚焦在具体指标上而不是停留在感觉层面。6. 可复现性与工程化把 AI 项目当正规军来建设AI Engineering 到了一定阶段拼的就不是一两次奇思妙想而是整个系统的可复现性。同样的代码别人跑不出你跑出的效果问题就大了。6.1 锁定模型版本与参数配置让玄学变科学模型 API 和开源模型都在快速迭代。今天用的模型明天可能就换版本了输出行为也随之变化。如果不锁定版本某次改动后效果变差你甚至分不清是代码问题还是模型版本的问题。我的做法是调 API 时固定模型版本号并把 temperature、top_p、max_tokens 等参数写死到配置文件里。这些参数组合会显著影响输出风格和稳定性属于必须版本化的内容。如果项目用到开源模型还要锁定模型权重文件的哈希值防止下载源不一致导致权重不完全相同。这一套下来结果不可复现的概率会大幅降低。6.2 数据与流程的可复现固定测试集、固定参考输出、固定评估配置评估结果要可复现评估数据和评估配置本身就得固定。我通常把测试集文件、参考输出、评估用的 Judge Prompt 配置都纳入版本管理任何改动都有记录。这样做还有一个额外好处新加入的同事能快速看到当前项目的效果基线是什么上手就有参照物。流程可复现还体现在运行记录的保存上。每次评估跑完把输入输出、模型参数、评估得分一起存档文件名带时间戳和 Git commit 号。一旦线上出现问题能回溯到当时是哪次改动、哪份代码、哪套参数产生的输出。这个习惯帮我解决过不少疑难问题价值难以估量。6.3 用配置文件管好所有隐藏参数AI 项目里的参数比传统项目多得多。模型版本、温度、上下文长度、检索阈值、Embedding 模型、Judge 模型、评分标准……任何一个参数变化都可能影响最终效果。我统一用配置文件管理做到参数与代码分离。配置管理还有一个容易忽略的部分——分层覆盖。我维护三套配置本地开发配置、测试环境配置、生产环境配置。三套配置之间只允许在预定义的字段上不同比如 API Key、模型版本其他参数保持一致。这样做的目的是避免开发时效果不错、上线后效果变差的经典悲剧。6.4 整体工程链路从数据到部署的完整拼图今天聊的每个环节最终都要拼成一条完整的工程链路需求拆解 → 上下文构造 → Prompt 渲染 → 模型调用 → 输出解析 → 自动评估 → 回归验证 → 部署上线。每个环节都有对应的配置和记录任何一个环节改动都能回溯到对应的效果变化。这条链路跑通之后AI 项目就不再是一个调 Prompt 碰运气的黑盒而是一个可迭代、可维护、可回滚的正规工程系统。后续加新功能、换模型、优化效果都有清晰的路径可走而不是每次从零开始试错。我自己从零搭完这一套体系之后最深的感受是AI Engineering 的复杂度不在模型的智能程度而在工程系统对不确定性的控制能力。把每个环节都做成可观测、可评测、可回溯的状态AI 项目才能真正像一个工程产品一样稳定交付。无论是个人项目还是团队协作早一点建立这套体系后面省下的时间都是十倍起步。