AI工程从零到实战:提示词、工作流与智能体架构全解析
我见过太多人拿到大模型 API 的第一反应就是“赶紧跑通一个 demo”。这个思路本身没错,但它和“AI 工程”是两码事。demo 证明的是“模型能做这件事”,工程解决的是“这件事能不能稳定做一万次”。这个区别,是整个 ai-engineering-from-scratch 项目里所有内容的核心出发点。
先给 AI 工程一个足够朴素的定义:用大模型作为核心推理组件,结合传统工程方法,构建能够稳定产出价值的可维护系统。注意三个关键词——核心推理组件、稳定产出、可维护。这三者缺一不可。很多人做 AI 应用失败,不是因为模型能力不够,而是因为只盯着模型,忘记了另外两个词。
传统软件工程擅长处理确定性逻辑:输入一个订单号,查数据库,返回订单信息,十次调用十次结果一样。但 AI 工程面对的是概率系统:同样一个问题,换一种问法,模型输出的措辞就会变;换一个上下文,答案甚至可能截然不同。这就像你带一个才华横溢但偶尔走神的新人干活,你不能指望他每次都自觉完成,而是要设计一套机制保证即使他走神,最终交付的东西依然达标。
我把这个“从零开始”拆成三个层次来理解。第一层,单次调用的可靠性,对应提示工程;第二层,多次调用的编排,对应工作流;第三层,系统级自主性,对应智能体。大部分 AI 项目的失败,根源都在于把问题压在了第一层,试图用一个超级提示词解决所有问题。但实际上,可靠性的提升必须靠层次化设计,而不是碰运气式的零散尝试。
拿我自己的团队举例。我们最开始做 AI 客服机器人时,天天在聊天窗口里试提示词,今天加一句“请务必回答”,明天改成“请用简洁的语言”。调了两周,效果忽上忽下。后来我强制把团队拉到白板前,把整个用户请求到最终应答的链路画出来,结论是:问题根本不在提示词,而在于缺少检索层和校验层。模型本身没有问题,是我们把太多责任压在了它一个环节上。从那以后我们定了一个死规矩:凡是生产级 AI 功能,必须画清楚分层架构,不允许只写一个 prompt 就上线。
2. 提示词工程:单次调用的可靠性从哪里来
2.1 提示词的结构化设计方法
先聊地基。提示词工程(Prompt Engineering)被很多人当成玄学,觉得“换个说法,结果好一点,再换个说法,又变差了”。这个感受是真的,但背后的规律其实可以结构化。
我自己的实践是从失败中逼出来的。早期给模型写提示词,我在网上抄了一个模板,加了角色设定、加了输出要求,结果用在真实场景里一样飘。后来我复盘发现,问题出在“边界不清晰”这五个字上。于是我把提示词模板化,固定成五个段落:
- 角色设定:明确模型的身份与知识边界。
- 任务定义:说明输入是什么、要产出什么。
- 上下文范围:限定参考材料的来源和使用方式。
- 输出格式:给出模板、字段定义或示例。
- 拒绝策略:什么情况下必须说“不知道”或拒绝回答。
这五段结构每段都有其对应的大模型行为原理。角色设定是在利用模型在预训练阶段学到的高质量领域先验;任务定义对齐了模型的指令遵循能力;输出格式把自由生成的熵降到最低;拒绝策略是用来对抗幻觉的。尤其是最后一条,我后来所有生产级提示词都保留着它——宁可让用户觉得“这 AI 有点笨”,也不能让用户觉得“这 AI 在胡说八道”。
关于上下文,我还想多说一点。很多人理解上下文窗口就是一个“能装多少字”的容器,这个理解不够准确。更准确的模型是:上下文窗口是模型推理时的外部记忆,但模型对窗口不同位置的注意力是不同的。离得越远的内容,权重越低。所以,塞得太多并不等于记住得更多,反而可能稀释关键信息。这就是为什么我不能接受“把整本书都扔进去让模型找答案”的做法,而是坚持要做检索增强(RAG)。
提示:写提示词时,把“角色设定”当成给模型做人设锚定,把“拒绝策略”当成给模型系上安全带。这两个段落比华丽辞藻重要得多。
2.2 少样本示例与参数调优
纯文本提示词只解决一部分问题,另一部分要靠示例和参数来解决。
少样本示例(few-shot)是提升输出稳定性的一个非常有效的手段。直接给模型两三组“问题 → 期望答案”的示例,它就能快速理解什么是好的输出。这非常像面试时给候选人看优秀作品集,比口头描述效率高得多。我的经验是,示例的数量不要贪多,两到四个高质量示例通常就够,太多反而会让模型困惑于“该模仿哪个”。
参数调优方面,我平时的习惯是:
- 温度默认 0.2 到 0.3,用于大多数业务场景,输出更稳定。
- 需要头脑风暴、创意发散时调到 0.7 到 1.0,但结果必须经过后续校验。
- top_p 和温度配合使用,一般固定在 0.9,不轻易动。
这里要强调一个容易踩的坑:不要把温度调到 0。温度 0 虽然让输出确定性最强,但会导致模型陷入重复和呆板,在需要多样性的场景反而表现更差。所以,稳定性不是靠温度 0 硬压出来的,而是靠提示词结构、示例和后续校验共同实现的。
3. 工作流编排:把“单次调用”变成“可靠流水线”
3.1 为什么单次调用永远不够
单次调用不靠谱,这是大模型的概率特性决定的,不是工程能力不够。但产品必须靠谱,所以工程上就需要多道工序来弥补。我习惯把 AI 应用的每一次完整服务过程拆成一系列模型调用和代码逻辑的组合,这就叫工作流。
一个经典的工作流范式是“拆解、执行、检查、修正”:
- 拆解:把用户请求拆成子任务。
- 执行:各子任务分别调用模型或工具。
- 检查:对输出结果按规则进行自动校验。
- 修正:校验不通过则带着错误信息重新生成。
举一个我在真实项目里做过的例子:自动合同生成工具。第一步让模型提取用户需求中的关键条款;第二步让模型基于条款模板生成合同草稿;第三步用一段固定的规则代码检查合同是否包含必备条款、日期是否合规、金额是否一致;第四步如果检查不通过,把错误信息反馈给模型,让它重新生成修改版。用上这个工作流之后,合同的整体合格率从 62% 提升到了接近 94%,而且不是靠玄学调提示词调出来的,是靠流程补出来的。
3.2 人工介入点与回退机制
工作流里最容易忽略的东西是“人工介入点”。我见过不少 AI 产品设计者,恨不得全流程全自动,觉得人工介入是失败。这个想法太天真了。真实业务里,总有一些情况是模型处理不了或者不允许模型处理的,比如金额特别大的合同、涉及多方利益的拍板环节。这个环节必须把流程暂停,交给人来决定。
所以每个工作流都要设计两个机制:一个是退出机制,当条件不满足时,自动把工单转给人类处理;另一个是重试机制,当模型输出校验不通过时,允许它带着错误信息重试一到两次。注意重试次数要限制,否则一个错误会无限放大成本。
3.3 一个可复用的工作流代码骨架
我团队内部有一套标准的工作流骨架,核心结构如下:
class AIWorkflow: def __init__(self, max_retry=2, human_fallback=None): self.max_retry = max_retry self.human_fallback = human_fallback def run(self, user_input): state = {"input": user_input, "retry": 0} while True: result = self.execute_step(state) ok, feedback = self.validate_step(result) if ok: return result if state["retry"] >= self.max_retry: return self.human_fallback(result, feedback) state["retry"] += 1 state["feedback"] = feedback def execute_step(self, state): raise NotImplementedError def validate_step(self, result): raise NotImplementedError这套骨架的好处是“固定了检查点”。每个环节都有一个校验函数,质量不过就回流,重试超过上限就转人工。它不是高深的技术,但真实效果比大部分复杂的提示词都强。
4. 智能体架构:让 AI 能自主执行多步任务
4.1 智能体的最小实现
聊到智能体(AI Agent)时很多人的第一反应是“这很高级”。其实从纯工程角度看,一个 Agent 就是一个有状态、能循环调用模型和工具的进程。它的核心循环可以被抽象成非常简单的三句话:
- 思考:根据当前状态和可用工具,决定下一步做什么。
- 行动:调用某个工具或模型。
- 观察:读取执行结果,更新当前状态。
代码层面,它就是:
def agent_loop(request, tools, max_steps=8): state = {"request": request, "history": [], "done": False} for step in range(max_steps): plan = llm_plan(state) # 思考 if plan["action"] == "stop": # 判断是否完成 state["done"] = True break result = execute_tool(tools[plan["tool"]], plan["args"]) # 行动与观察 state["history"].append(result) return final_answer(state)这段代码朴素得让人怀疑它是不是真能工作。但事实是,市面上大多数所谓的 Agent 产品,核心逻辑就是这样一个循环,只是套了不同的外壳。不要被“智能体”这个词吓住,从零开始搭一个能用的 Agent,比搭一个能用的 Web 服务还要简单。
4.2 工具接口设计:窄进深出
Agent 最难的工程环节不是循环本身,而是“工具接口的设计”。判断标准非常简单:一个工具接口是否足够专注、是否容易被模型准确调用。
我有个明确的教训。早期我给 Agent 设计了一个“文件操作”工具,参数是 operation(读、写、删、改四选一),结果模型经常把参数传错,明明要追加内容,却传成了覆盖。后来我把一个大工具拆成了四个小工具:read_file、write_file、append_file、delete_file,每个工具参数极少,模型就很少再出错了。这个经验告诉我:给 Agent 的工具一定要“窄而深”,一个工具只干一件事,参数一律精简。
另外一个很容易忽视的点是工具描述。描述要写清楚两点:这个工具是干什么的、什么时候该用它。很多工具接口明明没问题,但模型就是不会正确调用,一看描述,写得含糊不清。比如 query_user 工具,描述里最好写:当需要补充必要信息(如收货地址)时调用此工具,参数解释一栏写清楚各字段的取值。把描述当代码注释来写,认真程度决定 Agent 可用性。
4.3 记忆、规划与退出策略
Agent 的可靠性取决于三个问题:记不记得住前面的步骤、能不能规划多步行动、会不会刹不住车。
记忆方面,轻量场景直接用列表存历史即可;复杂场景要用摘要压缩,把旧的历史对话摘要成一个短文本,避免上下文膨胀。规划方面,我给模型的提示词里固定包含“步骤计划”字段,让它先列出计划再执行,这样至少能在语义上约束它不要跳步。退出策略是最强的安全底线,最大步数限制、连续无进展退出、用户取消信号,三样缺一不可。
5. 用 AI 写 AI:AI 编程的选型与工程化
5.1 主流 AI 编程工具怎么选
AI 工程的项目,本身就是 AI 编程工具最好的试验场。我不打算做评测榜单式的推荐,因为工具更新太快了,几个月就换一轮。我更想说的是选型方法论。
先列一个当前主流的对比表:
| 工具 | 优势 | 短板 | 适合人群 |
|---|---|---|---|
| GitHub Copilot | IDE 集成成熟、代码补全敏捷 | 复杂多文件重构偏弱 | 日常编码辅助 |
| Cursor | 对话式多文件编辑、上下文管理强 | 学习成本略高 | 原型快速验证 |
| DeepSeek | 中文理解好、API 成本友好 | 生态相对年轻 | 国内项目、模型调用 |
| CodeBuddy | harness engineering 支持完善、任务自动拆解 | 相对小众 | 长链路 AI 工程任务 |
这个表只是参考。我的真实使用习惯是“主工具 + 辅助工具”的组合:写框架结构用 Cursor,日常补全用 Copilot,遇到中文相关的复杂逻辑会让 DeepSeek 或 CodeBuddy 参与判断。不要只押注一个工具,捆绑在一个生态里,长期看会限制思路。
5.2 harness engineering:面向 AI 的工程脚手架
近期比较受关注的一个概念是 harness engineering。它强调的不是让 AI 写单个函数,而是搭建一个框架,让 AI 和人类在结构化的任务分解、上下文管理和质量门禁下协作。CodeBuddy 是我见过把这个概念落地得比较具体的工具之一,它能把一个大型开发任务自动拆成子任务,每步都检查输出质量,再继续往下走。
这个思路放在 AI 编程里特别重要。因为 AI 写代码的特点是一次生成很多,但质量参差不齐。如果不给它一块清晰的“脚手架”,它就很容易在一堆需求里迷路。而有了任务分解、上下文隔离、质量检查这三层夹板,AI 的产出效率会稳定很多。我也建议每个团队在搭建自己的 AI 工程体系时,把类似“脚手架”的机制内置进去,而不是完全依赖某一个外部工具。
6. 完整案例:从零搭建一个文档问答机器人
6.1 架构设计与模块拆解
光有工具和方法论还不够,我来拆一个完整的实战案例:文档问答机器人。这也是网上教程最多的项目,但多数教程停留在“加载文档 → 向量化 → 问一句 → 答一句”的 demo 层次。真实工程必须多出好几层。
这是我推荐的架构:
- 第一层:文档解析。支持 PDF、Word、Markdown,处理表格、页眉页脚、扫描件转文字。
- 第二层:文本切分。切片并保留重叠,保证语义完整。
- 第三层:嵌入与索引。把切片向量化,存入向量数据库。
- 第四层:检索模块。根据用户问题返回 TopK 相关片段。
- 第五层:生成模块。拼接上下文调用大模型。
- 第六层:验证模块。检测回答是否基于上下文,是否包含明显编造内容。
为什么需要第六层?还是那个原则——模型会幻觉。不加验证模块,你永远不知道它哪一次回答是编的。加了验证模块,至少在发现语义偏移时能及时兜底。
6.2 核心代码实现与参数细节
下面是这个项目最小可跑的代码。我用 Python 和 DeepSeek 的 API,向量库用 FAISS,尽量不引入重框架,方便理解每一行在干什么:
from langchain_community.document_loaders import PyPDFLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import FAISS from openai import OpenAI # 1. 加载文档(真实项目中,这里会有文件格式路由) loader = PyPDFLoader("product_manual.pdf") docs = loader.load() # 2. 文本切分:块大小 512,重叠 64 splitter = RecursiveCharacterTextSplitter( chunk_size=512, chunk_overlap=64 ) chunks = splitter.split_documents(docs) # 3. 向量化并构建索引 embeddings = HuggingFaceEmbeddings( model_name="BAAI/bge-small-zh-v1.5" ) vectorstore = FAISS.from_documents(chunks, embeddings) # 4. 检索 retriever = vectorstore.as_retriever(search_kwargs={"k": 5}) hits = retriever.invoke("如何安装驱动") # 5. 生成 client = OpenAI( base_url="https://api.deepseek.com", api_key="YOUR_API_KEY" ) context = "\n\n".join([h.page_content for h in hits]) response = client.chat.completions.create( model="deepseek-chat", messages=[ { "role": "system", "content": "你是产品文档助手。只基于给定上下文回答,不要编造。上下文未提及的内容,明确回答未知。" }, { "role": "user", "content": f"上下文:\n{context}\n\n问题:如何安装驱动?" } ], temperature=0.3 ) print(response.choices[0].message.content)代码本身不难,但有几个参数值得说清楚。chunk_size=512,块太大,检索粒度太粗;块太小,语义碎片化严重,512 是一个比较中庸的值。chunk_overlap=64,是为了避免某一段关键信息正好落在切片边界上被截断。k=5,召回片段数量,太少答案凭据不足,太多噪声太多,5 个在大多数场景都很稳。
6.3 评测与回归:怎么知道改完没有变差
demo 跑通之后,真正的工程工作才刚刚开始。你需要一份评测集来守护质量。我的做法是准备 50 到 100 个真实的用户问题,每个都人工标注了期望的答案来源段落和回答要点。每次改动提示词或模型,就跑一遍评测集,统计三项指标:回答相关性、事实准确性、格式合规性。
评测的方式不建议完全靠人肉打分,太累而且主观。更实际的做法是让一个更强的模型当评委,按标准对输出打分,再定期抽取部分结果人工复核。这个“用模型评模型”的流程,在维护成本和评测质量之间平衡得最好。
7. 常见问题与避坑心得
7.1 三个高频问题及排查路径
AI 应用开发中,绝大多数问题翻来覆去都是那三类。我整理成一张排查表,方便直接对照:
| 问题 | 表现 | 排查步骤 | 根因与解法 |
|---|---|---|---|
| 幻觉 | 编造事实、数据、引用 | 1. 检查提示词边界 2. 检查上下文是否包含答案 3. 检查 k 值是否过小 | 引入检索证据、增加拒绝策略 |
| 上下文溢出 | 长对话后“失忆” | 1. 打印 token 用量 2. 观察异常出现位置 | 摘要压缩、滑动窗口 |
| 格式不稳定 | JSON 解析失败、字段缺失 | 1. 保存原始输出 2. 定位失败模式 | 输出校验 + 强制修正、函数调用机制 |
说到幻觉,我再多分享一个调试技巧。遇到幻觉问题,别急着换大模型,先做证据检查。把你给模型的上下文完整打印出来,看它到底基于什么信息编出了答案。很多时候,不是模型本身爱编,而是你给它的上下文就存在矛盾或空白。找到了证据缺口,问题就解决了一半。这个办法我用了很久,比试各种提示词技巧有效得多。
7.2 成本与性能的平衡术
AI 应用的运行成本大头永远是 Token。工程上优化成本,我的顺序是:缓存、压缩、换模型。
缓存最简单也最容易被忽视。同一段文档、同一个问题的回答结果,完全可以缓存复用。我在检索层和生成层都加了缓存,效果立竿见影:高峰期 API 调用量直接下降了将近四成。
压缩稍微复杂一点。长对话场景下,历史记录越堆越多,token 很快涨上去。解决方式是在历史记录超过一定条数时,调用模型把旧消息压缩成摘要,再和后续消息拼接。实测下来,长对话场景的成本能降低一半左右。
换模型策略我给一个分级建议:简单任务用轻量小模型,中等任务用标准模型,复杂推理才用最强模型。把任务按难度放到不同模型上,整体成本可以显著下降。前提是评测集不能丢,每次换模型都要回归。
7.3 团队协作时的工程规范
最后聊一聊从单打独斗到团队协作的过渡。很多 AI 项目在个人手里跑得欢,一转交给团队就崩坏,原因基本是这三个:提示词散落在各个聊天记录里、工作流缺少文档、没有人对质量负责。
我的建议很具体。第一,提示词要入库管理,用 Git 管理版本,每个提示词文件包含版本号、变更原因、评测结果。第二,工作流每个环节要有明确的输入输出接口说明,至少让新人一看就知道这个环节处理什么。第三,建立质量门禁,上线前必须跑评测集,指标低于基准值的变更不允许合并。这套规范听起来有点重,但一旦团队超过三个人,它就是省时间的核心。
最后说点个人体会。做 AI 工程最怕的不是技术难,而是心态急。我前两年写 AI 应用,总想一次把提示词写完美,反反复复微调,结果项目进度被耗死在“完美主义”上。后来我学会一个笨办法:先以比较朴素的方式把全流程跑通,再一步一步加校验、加评测、加优化。模型不是神,它也会抽风,因此你要做的不是消灭抽风,而是接受抽风是常态,然后用工程手段把抽风的影响拦在可控范围内。这一行真正有价值的东西,恰恰是那套“让不稳定的 AI 稳定工作”的体系。希望这篇梳理能在你从零搭建 AI 工程时,帮你少绕几个弯。