
过去半年我陆续把团队的AI项目从跑通Demo推进到线上稳定运行中间踩过的坑比过去五年做传统后端加起来都多。每次和人聊起AI工程这个词我发现大家基本分成两派一派觉得它就是个营销概念把Prompt包装成工程另一派觉得它高不可攀好像得先读完全部机器学习论文才能动手。今天想借这个ai-engineering-from-scratch的话题把这段从零开始的真实经历摊开讲讲包括我自己对Harness Engineering、Loop Engineering这些新词的理解以及普通人如何一步步构建自己的AI工程能力。先给个结论AI工程不是把模型跑通而是在模型输出天然不确定的前提下设计一套系统让它稳定产出业务价值。它和传统软件工程最大的不同在于需求—设计—实现—测试的线性流程失效了你需要换成设计—试验—观察—加固的螺旋循环。这篇文章适合两类人一类是刚接触AI开发、想系统构建知识体系的初学者另一类是已经在用API写业务代码、但经常被模型输出搞到崩溃的开发者。下面我会从最底层的Prompt能力一路讲到Agent协作尽量把每个环节的原理、代码和坑都讲透。1. 先搞清楚AI工程到底在工程化什么1.1 从调接口到构建系统我们对AI工程的误解很多人觉得AI工程就是调用大模型的API把OpenAI或者国产模型的SDK装好传几句提示词解析一下返回结果这不就是一个工程了吗真这么想上线不到一周就会被用户骂回来。为什么因为单个接口调用只是零件工程化是把零件组装成有容错、有监控、有迭代闭环的系统。举个例子。我最早给内部工具写过一个自动总结会议纪要的功能第一版代码极其简单把会议录音转文字然后把文字扔给模型让它输出摘要最后把摘要存到数据库。看起来天衣无缝实际跑起来问题一箩筐有时候模型输出一大堆废话有时候摘要里混入幻觉信息有时候格式不是纯文本而是Markdown前端渲染直接崩。这些问题都不是模型不好导致的而是我根本没有考虑模型输出是一个概率分布而不是一个确定性的JSON对象。真正的AI工程要处理的是不确定性。传统前端后端面对的是明确的输入输出最多担心一下边界情况。AI工程要面对的是同一个Prompt模型今天可能给你A结果明天给你B结果换了温度参数又给你C结果的随机性。所以工程化的第一步不是写更多的代码而是转变思维模式把模型当成一个极度聪明但经常说谎、偶尔发癫的同事你要做的事情不是信任他而是给他一整套流程约束。1.2 AI工程与传统软件工程、机器学习工程的分工这张边界曾经也让我困惑了很久。软件工程处理确定逻辑机器学习工程处理模型训练和部署AI工程处理的则是已经训练好的模型如何接入业务系统。它横跨三者但有自己独特的问题域。维度传统软件工程机器学习工程AI工程核心输入明确的需求逻辑标注数据与训练目标自然语言Prompt 上下文数据主要难点架构设计、并发、一致性数据质量、模型收敛、特征工程输出不确定性、幻觉、链路可靠性测试重点单元测试、集成测试评估指标准确率、召回率场景对抗测试、输出校验、回归集迭代模式需求变更驱动训练数据更新驱动提示词与护栏策略驱动关键技能编程能力、系统设计机器学习理论、数据处理编排能力、结构化思维、评估方法论我个人的体会是AI工程在这个体系里更像是翻译器——把业务需求翻译成模型能理解的指令再把模型输出翻译成业务能使用的数据。这个过程里你最好既懂点软件工程知道怎么设计接口、怎么处理并发又懂点机器学习至少知道温度、采样、上下文窗口是什么意思更重要的是你还要具备一种跟随机性共事的工程嗅觉。2. 地基Prompt Engineering可不只是写提示词2.1 大模型的输出不确定性与可控性从哪里来先说原理。大语言模型本质是一个概率函数给定一段文本token序列它预测下一个token的概率分布。所谓生成结果其实是反复从这个分布里采样。温度参数就是在采样时放大或缩小概率差距——温度越高高概率token和低概率token之间的差距越小输出就越随机。这带来两个工程上必须接受的现实第一非确定性是模型的内在属性不可能通过Ping一个降温参数彻底消除第二我们虽然不能百分之百控制输出但可以通过改变输入的概率分布把输出推向我们想要的方向。Prompt Engineering的核心就是通过设计输入文本改变模型内部的概率权重。很多时候你觉得模型理解了你的话其实它只是在高维空间里找到了一个与你文本相关的概率路径。这意味着Prompt里的每个词语都在影响这个路径的走向。你写请用专业的方式回答和写请用初中生能听懂的话解释模型走的是完全不同的路径。这不是玄学这是概率计算。2.2 一套可复用的结构化Prompt模板我自己从零开始摸索出来的Prompt结构后来发现和很多开源项目的思路基本一致。给大家一个可以直接拿去用的模板框架用Markdown组织未必是最好的但比零散的一段话稳定得多# 角色 你是一位{角色定义}擅长{领域}具有{特质}。 # 任务背景 {描述当前场景提供足够的上下文信息} # 用户目标 你要完成的具体任务是什么用一句话说清楚。 # 输入数据 {用户提供的内容或从系统读取的数据} # 输出要求 - 格式{JSON / Markdown / 纯文本} - 结构{需要哪些字段用示例说明} - 风格{正式/口语/简洁/详细} - 长度{字数或行数限制} # 约束与禁区 - 不得编造数据 - 如果信息不足请明确说信息不足 - 必须使用繁体中文或简体字 - 如果遇到法律问题不要给具体建议 # 思维步骤 1. 先分析输入里的关键信息 2. 再对照输出要求规划结构 3. 最后生成内容这个模板不是花架子。它解决的是提示词原子化的问题。一旦你养成了写结构化Prompt的习惯就能把上一段话下同一个指令变成在系统里维护几套不同场景的模板后续做Harness Engineering才有基础。我强烈建议你把Prompt当作代码一样去管理用版本控制记录每个版本在测试集上的表现不要用记事本存提示词。这个习惯会救你很多次——当线上效果变差时你能快速回滚到上一个稳定版本而不是对着聊天记录翻来覆去地回忆。2.3 思维链与少样本示例的真正用法思维链Chain of Thought这个词你可能听腻了但我还是要说一个容易踩的误区思维链不是让模型把思考过程写出来这么简单它的真正作用是诱导模型把复杂问题分解成多个中间步骤从而把一步到位的概率估计拆成多个小步的概率连乘每一步的置信度都比直接输出答案更高。实操中我的经验是对于逻辑推理类任务在Prompt里明确加上先一步步分析再给出答案对于数据提取类任务最好在思维链之后再要求以JSON格式输出这样既保留推理质量又方便代码解析。少样本示例Few-shot同样需要注意示例不是越多越好而是越代表真实输入分布越好。你给三个示例实际上是在告诉模型我期望的输出长这样。如果示例本身质量不高模型会学歪。我曾经试过给一组带明显偏见的示例结果模型输出也跟着带偏后来换了三个干净、典型、覆盖边界的示例效果立刻回升。3. 让AI听话的关键Harness Engineering与Loop Engineering3.1 Harness Engineering到底是什么意思最近圈子里流行两个词一个是Harness Engineering一个是Loop Engineering。我第一次看到Harness的时候以为跟测试框架里的测试夹具有关后来才理解它的核心含义给模型加装一套约束装置就像给马套上缰绳和鞍具让它在预设轨道上跑而不是漫山遍野地撒欢。Harness Engineering研究的就是如何设计这套约束装置。它不是单指某个工具而是一整套工程手法结构约束通过Function Calling或JSON Schema约束输出格式。内容约束通过系统提示词和内容过滤规则限制主题范围。流程约束通过代码控制模型必须在某个业务规则允许的范围内作答。外部上下文通过RAG检索把知识边界锁死在你的数据源里。我记得有人打过一个比方大模型是一个超强但不懂规矩的新人Harness就是给他准备的《员工手册》和审批流程。你当然可以不给他任何流程直接下命令那他一定会自作主张地搞出一堆你不想看到的操作。有了Harness他只能在授权范围内发挥聪明才智。3.2 用代码构造内部脚手架校验、重试、降级Harness不是只在Prompt里做文章很多时候要靠代码缝合。下面是我做的一个最小可用的内部Harness示例用带格式约束的调用方式让模型返回结构化数据再在代码里做一次严格校验。这一步非常关键——永远不要把模型输出直接当业务数据使用必须经过校验。import json from pydantic import BaseModel, ValidationError from openai import OpenAI client OpenAI() # 定义预期的输出结构 class MeetingSummary(BaseModel): title: str key_points: list[str] action_items: list[str] risk: str none def summarize_meeting(transcript: str) - MeetingSummary: prompt f 你是会议纪要助手。请根据下面的会议转录生成结构化摘要。 输出必须是合法JSON不要包含除JSON外的任何文字。 JSON的schema示例如下 {title: 会议标题, key_points: [要点1, 要点2], action_items: [行动项1], risk: 风险描述或none} 转录原文 {transcript[:12000]} for attempt in range(3): try: resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}], temperature0.2, response_format{type: json_object}, # 强制JSON ) raw resp.choices[0].message.content data json.loads(raw) # 用Pydantic校验 return MeetingSummary(**data) except (ValidationError, json.JSONDecodeError) as e: print(f第{attempt1}次输出校验失败: {e}, 重试...) continue # 降级策略返回一个带错误标记的默认结构 return MeetingSummary(title解析失败, key_points[], action_items[])这段代码里做了四件工程化的事强制JSON格式、结构校验、失败重试、降级返回。别小看这四步它们构成了整个Harness的骨架。你可以根据业务需要加入更多校验逻辑比如检测关键字段是否为空、判断输出内容是否触及敏感词、用规则表达式验证格式等等。3.3 Loop Engineering从生成一次到循环闭环Loop Engineering是另一个让我茅塞顿开的词。它指的是把AI从一次通话变成一个持续循环的认知过程。很多AI应用之所以给人智障的印象是因为它们只做了单次推理——问一句答一句没有反馈回路。而真正好用的AI应用内部是有一到多轮循环的。一个典型的Loop包含四个环节生成模型产生初步输出。评估用规则或另一个模型检查输出质量。反馈把检查结果喂回给模型要求它修正。再生成模型根据反馈优化输出然后再次评估。这个循环在学术上有另一个名字叫Self-Critique或Reflexion但在工程上我更愿意把它理解为写代码时的断点调试——模型生成一份答案你让它自己检查一遍甚至两遍质量会显著提升。下面是我常用的一个自反思循环示意不是完整代码逻辑供参考def generate_with_self_reflection(query: str, modelgpt-4o-mini): # 第一轮生成 draft call_llm(query) # 自我批评 critique_prompt f以下是你的回答。请扮演一个严格的审核员指出其中的错误、模糊、缺少逻辑的部分并给出修改建议。不要修改原文只输出批评意见。 回答内容{draft} critique call_llm(critique_prompt) # 根据批评重新生成 refine_prompt f用户的原始问题{query} 你之前的回答{draft} 审核员的批评{critique} 请根据批评意见重新写一份高质量回答保留有效部分修正错误和不足。 refined call_llm(refine_prompt) return refined这个例子非常简单但已经构成了一个完整的生成—评估—反馈—再生成闭环。你完全可以根据成本预算决定循环次数。我个人的经验是高价值任务循环2轮普通问答任务1轮就够超过3轮边际收益会快速下降因为模型有时候会把自己的正确内容改成错误内容。3.4 一个完整的HarnessLoop的Demo设计光说不练假把式。很久之前我做了一个简历信息提取器输入一份简历文本输出候选人的结构化档案。这个项目麻雀虽小五脏俱全完整展示了Harness和Loop的配合。流程设计是这样的输入简历文本 → Prompt模板组装角色、输出格式约束 → 调用模型提取JSON → 校验JSON结构Pydantic → 如果校验失败自动重试并在重试的Prompt里附带上一次的错误信息 → 如果校验通过进入信息完整性检查阶段 → 用规则检查关键字段姓名、电话、邮箱是否为空 → 如果关键字段缺失触发修补Loop只让模型补全缺失字段 → 输出最终结构化档案其中第二轮的修补Loop是最有价值的部分。因为第一轮通常能提取出大部分信息但因为格式问题漏掉一个字段如果全量重新跑一遍成本和耗时都翻倍。更好的做法是只在Prompt里提示模型上次漏掉了电话字段请只补上这个字段不要修改其他内容这样既省token又避免模型把原本正确的数据改坏。这个Demo教会我一件事Harness和Loop不是两种互斥的选项而是同一个系统的两个层次。Harness在输入和输出端做约束Loop在推理过程中做质量控制。对一个生产级AI应用来说两者缺一不可。4. 从单次任务到自主决策AI Agent的构建思路4.1 Agent的三大核心部件记忆、规划、工具上面的内容讲的都是接一个任务、出一份结果的脚本模式。但如果你想让AI系统完成更复杂的、需要多步骤决策的活儿就得用到Agent。业界对Agent的分解比较统一它通常由三部分组成记忆Memory包括短期记忆当前会话的上下文窗口和长期记忆向量数据库或文件存储。没有记忆的Agent是金鱼聊完就忘无法处理多轮任务。规划Planning把大目标拆分成子步骤决定调用哪些工具、按什么顺序调用。最原始的规划方式就是ReAct模式推理Reason→ 行动Act→ 观察Observe。工具ToolsAgent通过工具与外部世界交互。什么算是工具搜索、计算器、发HTTP请求、执行SQL查询、读写文件甚至调用其他AI模型。工具拓展了Agent的能力边界。从工程角度我认为记忆是最容易被做砸的部分。很多初学者把所有对话历史都塞进上下文结果很快超过上下文窗口或者花掉巨额token。正确的做法是对记忆分层。当前对话用窗口缓存重要信息抽取到长期记忆无关信息直接丢弃。我见过不少团队没做记忆分级项目一上线成本就爆表。4.2 从零实现一个最小可用的Agent跟着教程写过几个框架之后我觉得即使不用LangChain只用一个循环加几个工具函数也能构建一个能用的Agent。关键在于理解循环。from typing import Callable class MinimalAgent: def __init__(self, tools: dict[str, Callable], max_steps5): self.tools tools self.max_steps max_steps def run(self, user_request: str): messages [{role: user, content: user_request}] for _ in range(self.max_steps): # 1. 让模型决定下一步动作ReAct response call_llm( system_prompt( 你是一个Agent。请根据用户的目标决定执行哪个工具。 输出必须包含: action工具名和args。 如果任务已经完成输出final_answer。 f可用工具: {list(self.tools.keys())} ), messagesmessages ) messages.append({role: assistant, content: response}) # 2. 解析模型输出 action, args, final parse_agent_response(response) if final: return final if action in self.tools: # 3. 执行工具并观察结果 observation self.tools[action](**args) messages.append({role: user, content: f观察结果: {observation}}) else: messages.append({role: user, content: 工具不存在请重新选择。}) return 达到最大步数任务未完成。 # 工具示例 def search_web(query: str): return f搜索结果前3条: ... def calculate(expression: str): return str(eval(expression)) agent MinimalAgent(tools{search_web: search_web, calculate: calculate}) print(agent.run(查一下今天的日期并计算一周后的日期))这个代码有个陷阱eval在真实项目里极度危险我只是为了演示才用。真实项目中工具函数必须做严格的参数校验和权限控制否则Agent就是给攻击者递刀。但它的核心逻辑是通用的模型只是决策头真正的能力和风险全在工具层。Agent最容易翻车的点是模型决定调用工具的步骤是随机的。今天它可能先搜索再计算明天可能先计算再搜索。你的工具层必须设计成无论以什么顺序调用都能得到安全结果。这也意味着不要给Agent开放没有幂等性、没有权限限制的工具。4.3 多Agent协作的两种模式单Agent的能力终究有限所以现在很多项目开始搞多Agent协作。我不建议上来就上多Agent那是给自己找麻烦但理解两种常见模式能帮你判断什么时候该上。模式一编排者模式Orchestrator Pattern一个管理者 Agent负责拆解任务然后把子任务分发给不同的专家 Agent最后汇总结果。优点是职责清晰缺点是大佬Agent成了瓶颈被骂的对象。模式二对等协作模式Peer-to-Peer Pattern多个Agent地位平等通过共享消息通道互相协作各自独立处理一部分遇到问题互相求助。典型场景是主持人Agent 发言人Agent 批评者Agent的开会形式。这种模式能激发创造力但容易陷入无休止的互相扯皮。从我的实践看绝大多数业务场景用编排者模式就够了。把多Agent当作营销话术可以但当工程架构先掂量一下你的系统有没有复杂到需要一个Agent议会。5. 实战我为什么建议从AI编程助手开始5.1 用AI编程暴露工程化短板如果你想找一个人门级项目来实践上面的所有概念我会非常推荐AI编程助手。为什么因为编程任务自带明确的正确性标准——代码能不能跑、测试过不过、回答是否解决了问题——这些都是天然的质量信号。相比之下你写个AI情感分析很难判断模型到底对不对但AI编程的反馈清晰得多。另外编程任务本身有大量现成的工具链可以接编译器、执行器、Git、测试框架。你不用自己造测试集只需要让Agent把运行测试当作一个工具就能实现非常完整的HarnessLoop闭环。我们团队做的第一个AI编程助手项目结构是这样的用户输入需求 → 需求澄清AgentLoop: 提问-确认 → 任务规划Agent将需求拆解为改动清单 → 代码生成Agent按清单逐文件生成diff → 编译检查工具Harness: 自动编译 → 测试执行工具Harness: 跑单测 → 失败反馈Loop把报错信息喂给生成Agent修复 → 生成最终PR描述你发现了吗整个过程就是一个巨大的Loop每个环节都有对应的Harness。它不是聊天写代码而是一个有明确输入、输出、校验、纠错的工程系统。5.2 数据流设计从需求描述到Pull Request具体的落地步骤我认为设计好任务状态机比让Agent写代码更重要。我把一次AI编程任务的状态定义成这样初始用户提交自然语言需求。澄清Agent判断需求是否足够清晰不够则提问。规划需求清晰后将改动拆成文件级别的子任务。实施逐个文件生成代码改动。验证执行编译、单元测试、静态检查。修复验证失败时将错误信息回传生成修复补丁。交付所有验证通过生成PR摘要。状态之间的切换逻辑完全可以用代码写死而不是让Agent随便乱跳。把AI的能力限制在状态内部而非状态流转上这是AI工程和纯Prompt玩法最本质的区别。状态机是你的HarnessAgent只是其中处理数据的引擎。5.3 AI测试开发你需要的不是跑通而是可回归很多做AI应用的朋友最头疼的就是测试。传统测试断言输入输出但AI输出是随机的怎么断言我的答案是不要断言模型的最终答案要断言工程系统的行为。也就是说你要测试的不是模型是否说出了正确答案而是输出是否符合JSON Schema关键字段是否为空是否包含敏感内容在固定回归集上模型输出的核心语义是否保持稳定重试机制是否在模型宕机时正常工作降级方案是否在超时时被触发。这个思路被称为AI测试开发。它要求你把测试重点从结果正确性转移到行为鲁棒性和系统可用性上。我甚至会用两个不同的模型做交叉评估A模型生成答案B模型为A的答案打分。虽然会增加成本但对于那些错误代价高昂的业务多花一次调用的钱换来稳定的质量完全值得。我们团队的回归测试集大概有几百条带标签的典型输入每次修改Prompt或流程后脚本自动跑一遍统计合法输出率关键字段缺失率语义一致性评分。有了这个回归集我就敢放心迭代Prompt了——因为在AI工程里Prompt的每一次改动都是一次可能引入回归的风险操作必须用回归集兜底。6. 项目落地过程中的十一个高频坑与我的处理方案一路从零走到现在我把踩过的坑整理成一张对照表。每一条都是真金白银换来的希望你在设计系统时可以提前规避。序号高频坑典型表现我的处理方案1忽略输出校验模型偶尔输出非法JSON程序崩溃所有模型输出先过校验层非法就重试或降级2无脑堆Prompt提示词五屏长效果却不升反降始终保持核心指令关键约束删除一切废话3温度设太高同样问题回答差别巨大生产环境固定温度在0~0.3创意场景单独放开4上下文无限膨胀多轮对话后token超限费用暴涨设计记忆分级只保留最近几轮抽取的关键信息5没有回归测试改了一句提示词线上效果突然崩建立固定回归集每次改动自动跑质量指标6把Agent当搜索引擎让Agent瞎逛网络答非所问限制工具白名单用状态机控制Agent流程7忽略工具安全Agent执行了恶意代码或删除操作工具层做权限校验禁用危险操作高危操作需人工确认8幻觉当成功能模型编造数据业务误以为真要求模型标注信息来源或不确定性关键数据外接知识库核实9成本失控用户的每个请求都调用多轮多个模型设置成本上限、缓存高频请求、用便宜模型做预筛10模型切换未验证换了新版或别的模型输出差异巨大保留多个模型版本切换前在回归集上做AB对比11忘记存量兼容Prompt优化后老用户数据没法处理所有输出带schema版本号必要时让旧数据走迁移脚本这十一个坑不是严格的顺序但多数项目至少会踩中其中五个。我在前几个项目里几乎全踩了个遍后来才慢慢形成一套固定的AI工程检查清单写代码前先决定输出Schema调用模型前先想好校验规则上线前先跑一遍回归集。这套清单就像飞机起飞前的检查表虽然繁琐但每次都能拦住几个致命问题。从零开始的最后一段路把AI工程当系统工程而不是咒语聊了这么多回到ai-engineering-from-scratch这个话题本身。我最后想分享的体会是AI工程真正难的地方从来不是模型或算法的数学原理而是你是否愿意把它当成一个严肃的系统来对待。很多人学了很久Prompt技巧却做不出能稳定运行的应用根本原因是缺少工程化精神——总觉得模型输出不好是模型的问题而不是自己的流程没约束好。多想一想如果模型的每个输出都不是确定的我的系统是否依然可控如果模型偶尔撒谎我的业务是否会因此受损如果模型供应商明天涨价我的成本架构是否能支撑这些问题的答案不在那些铺天盖地的AI速成课里而是在你亲手把一个微小功能点反复打磨、校验、测试、加固的过程中。如果你现在准备开始我的建议是先挑一个非常窄的、有明确输入输出的场景比如从一段简历抽取联系人信息或从会议记录生成待办清单然后老老实实把Prompt模板、结构化输出、Pydantic校验、重试降级、回归测试这五个步骤走一遍。不要贪多不要一心想着做全能Agent。把这五步走顺了你会发现后面所有复杂系统拆开看都是这些基础单元的排列组合。记住一个朴素的道理模型就像驾驶性能极佳的跑车而你是赛道工程师。你能跑多快不取决于发动机参数而取决于你铺设的赛道有多平整、护栏有多结实、维修站有多及时。从零开始做AI工程重要的不是跑起来而是摔不坏。共勉。