news 2026/9/30 5:28:49

AI工程从零开始:最小闭环、提示词迭代与Agent落地的完整链路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI工程从零开始:最小闭环、提示词迭代与Agent落地的完整链路

前段时间我把自己的一个项目命名成ai-engineering-from-scratch,本意是"从零开始做AI工程",结果一个朋友看到后问我:这不是"从零开始学AI"的课程笔记吗?我愣了一下,发现这个误解其实很普遍。很多人以为 AI 工程是"学会用 AI 工具",或者"看懂几篇模型论文",但真正上手后才发现,难点几乎全在engineering这个词上——稳定、可评估、可控、成本可接受地交付一个 AI 功能,而不是"调通一次对话"。

我身边不少朋友收藏了几十个 AI 工具网站、买了提示词模板、看了 Agent 架构图,三个月后再问,还是没能让一个 AI 接口稳定地跑在自己的业务里。问题不在他们不努力,而在没人告诉他们:这条路应该先走哪几步、每一步做到什么程度才算合格。所以我决定把从零到能交付一个 AI 功能的能力链路完整梳理一遍,送给那些准备认真做 AI 工程,而不是停留在"玩 AI"层面的读者。

1. 先拆清楚:"AI工程"和"用AI工具"根本是两回事

在开始之前,我花了很大力气才想明白这件事。ai-engineering-from-scratch里的 "engineering" 不是修饰词,它是主语。AI 只是你手里的一个组件,工程才是你要搭建的系统。

1.1 我以为的起点和实际的起点差距很大

我在立项初期翻了很多资料,差点把自己的第一个里程碑定成"读完整本深度学习花书",理由是"不懂模型原理怎么做AI工程"。现在回头看,这个想法差点把我劝退。

做个类比:你想开车通勤,需要的是交规、路感和油门刹车的配合,而不是先读完整本内燃机原理。真正的模型训练、损失函数、梯度下降,那是"造车"的人关心的事。我们绝大多数人做的是"开车":调用成熟的模型能力,把它接进业务流程里。

这不是说底层原理毫无价值,而是说它不该是起点。AI工程的起点是"用起来",不是"造出来"。

1.2 AI工程要碰的四层东西

按照我后来的实践,一个完整的 AI 工程能力栈可以粗分成四层。每一层的目标和关注点完全不同:

层级核心问题典型动作
模型层用什么模型,能力边界在哪选型、对比测试、了解上下文长度和输出限制
数据层给模型喂什么,怎么喂上下文组装、知识库切分与检索、few-shot 样本
流程层一次调用怎么变成一套流程重试、缓存、状态机、工具调用、Agent 编排
系统层怎么保证"能用"且"可维护"评估集、成本监控、日志追踪、隐私保护

我见过很多人把全部精力放在第一层,天天换模型、比参数,却忽略了后面三层。结果就是"Demo 一时爽,上线火葬场"——单次调用看着挺聪明,放进业务流程里就各种不听话。

1.3 为什么不建议从算法论文开始

"from scratch" 这个短语有迷惑性。它确实有"从零开始"的意思,但在工程领域,它更接近"搭出一个完整可用的最小系统",而不是"把每个底层组件都亲手造一遍"。

我的建议是:把论文阅读和源码研究放到跑通两个完整项目之后。那时候你对"上下文窗口处理的是什么""结构化输出卡在哪""Agent 决策为什么不可控"会有切肤之痛,再看论文一眼就能抓住重点。反过来,先啃论文再碰代码,大概率会在第三周就放弃。

2. 第一个项目应该有多小:最小闭环跑起来再说

如果让我给"from scratch"定义一个真正的第一步,那就是:在一个小时内,用代码完成一次真实的模型调用,并且让返回结果能被程序正确处理。

2.1 用20行代码把一次对话打通

我当时用的是 Python,注册了服务商的开发者平台,拿到 API Key 之后,第一段代码大概长这样:

import os from openai import OpenAI client = OpenAI( api_key=os.environ["API_KEY"], base_url=os.environ.get("API_BASE_URL", None) ) response = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": "你是一个数据提取助手,只输出JSON。"}, {"role": "user", "content": "提取下面这段客户反馈中的问题类型和紧急程度:今天发货又延迟了,客服也不回消息。"} ], temperature=0.2, ) print(response.choices[0].message.content)

这段代码没什么高深的,但它完成了三件大事:第一,你证明了 API 通路是通的;第二,你第一次用temperature这个参数控制了输出的确定性;第三,你第一次意识到,模型返回的是一段文本,不是一个 Python 对象——后面所有麻烦都从这里开始。

2.2 理解token、计费和延迟,这三个数字决定后续所有设计

初次跑通之后,我看了一眼账单和日志,才意识到"AI工程"和"普通API开发"最不一样的地方:每一次调用都在烧钱,每一次输出都有延迟,每一个字都占上下文空间。

token 是模型处理文本的基本单位。英文单词大致一个词一两个 token,中文通常一个汉字对应一到两个 token。所以上面那段 20 行的代码,每次调用差不多消耗 100 到 200 个 token。

成本公式很简单:每次调用成本 = 输入token数 × 输入单价 + 输出token数 × 输出单价。这还没算重试和多次 Agent 循环的成本。延迟也一样,输出 token 越多,等待越久,因为模型是一个词一个词"蹦"出来的。

参数作用建议初值
temperature控制随机性,越低越确定0~0.3,提取类任务用低值
max_tokens限制输出长度按任务估算,别给太大
model模型能力和成本的权衡先用小模型跑通,再按需升级

2.3 把"人话对话"升级成"程序接口"

跑通调用之后,我做的第一件事不是加功能,而是加"把输出变成数据"的代码。最简单的方式是要求模型输出 JSON,然后在代码里json.loads解析,解析失败就走重试或人工兜底:

import json content = response.choices[0].message.content try: result = json.loads(content) except json.JSONDecodeError: result = {"raw": content, "error": "parse_failed"}

这一步的价值在于:你不再把模型当成聊天机器人,而是当成一个"不稳定但不笨的接口"。从这一刻起,你才开始做工程。

3. Prompt不是靠灵感,是把它当API去设计

很多人以为提示工程就是"把话说得漂亮一点"。我一开始也这么觉得,直到我在同一个任务上换了十几个说法,效果忽上忽下,才发现根本问题在于:我没有把提示词当成一份需要严格维护的技术资产。

3.1 为什么我从"背模板"改成"拆字段"

网上能找到大量提示词模板,什么"扮演一个资深分析师"、"请一步一步思考"。抄过来用,时灵时不灵。后来我发现,与其被模板牵着走,不如把提示词当成一段有结构的配置,拆成固定字段:

  • 角色定位:这个模型在这个任务里扮演什么角色
  • 任务描述:它要完成的具体事情,最好一句话说清
  • 输入内容:用户提供的原始数据,用分隔符包起来
  • 约束条件:什么不能做,比如"不要编造不存在的订单号"
  • 输出格式:字段名、类型、是否必填
  • 示例:一两个输入输出示例,帮助模型对齐

我在代码里通常这样组织提示词,方便维护和版本管理:

[角色] 你是一个细心的客户反馈整理助手。 [任务] 从用户反馈中提取:问题分类、紧急程度、关键诉求。 [约束] 只输出合法JSON,不要输出任何其他文字。 如果信息缺失,字段值为 null。 [输出格式] {"category": string, "urgency": "high|medium|low", "request": string} [示例] 输入:东西收到了但少了一个配件。 输出:{"category": "缺件", "urgency": "medium", "request": "补发配件"}

这种结构化写法的好处是:出一版,改一版,你永远知道上一个大版本发生了什么变化。提示词从"玄学"变成了"可迭代的配置"。

3.2 few-shot 和结构化输出的真正作用

模板里那段"示例"就是 few-shot,意思是"给模型看几个例子,告诉它你要什么"。它的作用不是提供知识,而是统一风格和格式。模型看到第一个例子是 JSON,它更倾向继续输出 JSON。

但 few-shot 不是越多越好。每加一个示例,输入 token 就增加一段,成本往上涨,响应时间也拉长。我自己常用的量级是两到三个示例,够让它对齐格式就行。

同时,我建议在代码层做"双保险":提示词里要求 JSON,代码里仍然做解析兜底和字段校验。把提示词当成"提高正确概率的手段",而不是"保证一定正确的承诺"。

3.3 一个具体的提示词版本迭代案例

我做过一个"从客户评价里提取三要素"的小功能,迭代过程大概是这样:

版本我改了什么结果
v0.1一句话要求:"提取问题类型、紧急程度和诉求"输出格式飘忽不定,有时是散文
v0.2加了"只输出JSON"格式稳定了一些,但字段名不固定
v0.3明确了字段名和类型格式基本稳定,偶尔有多余文字
v0.4加了两个示例准确率明显上升,格式 95% 以上合规

每一版改完,我都拿同一批测试样本跑一边,对比结果的正确率和格式合规率。没有数据支撑的"感觉变好了"都不算优化。

3.4 提示工程的边界在哪

提示词不是万能的。我遇到过三类问题靠提示词怎么改都没用:一是任务本身超出模型知识边界,它没有这个信息;二是输出要求过于复杂,字段多到模型容易漏;三是矛盾要求,既要求"详细"又要求"简短"。

遇到这类情况,别再死磕提示词了。要么把任务拆成更小的子步骤,要么引入外部知识检索,要么换更大的模型。提示词解决不了的问题,工程结构可以解决。

4. 把模型包进流程:从一个调用到一个会工作的系统

跑通单次调用之后,下一步是把它变成"不会突然挂掉"的服务。这一章讲的全都是经典工程手段,只不过服务对象从数据库变成了模型。

4.1 模型应用也需要重试与缓存

模型 API 不是 100% 可靠的,超时、限流、临时 500 都很常见。而 AI 应用里"重试"还有个特殊意义:同样的输入,模型可能因为随机性返回不同结果。如果你的业务对一致性有要求,重试策略就得小心,有时候重试反而会引入新错误。

我采用的最简方案:

import time from tenacity import retry, stop_after_attempt, wait_exponential @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, max=10)) def call_llm(safe_params): ...

缓存也一样。完全相同的请求(同样的 prompt、同样的模型参数)不应该重复烧钱。我在系统里加了一个简单的精确缓存:以 prompt 和参数的哈希作为 key,第一次请求落库,后续命中直接返回。效果立竿见影,实验阶段能省下三成以上的 token 费用。

4.2 用数据而不是感觉调优:建立你的第一个评估集

这是我觉得整个ai-engineering-from-scratch过程中最重要的一步:给 AI 功能建一套测试集。没有评估集,你所有的优化都是在"盲调"。

我当时的做法很朴素:从真实请求里攒了 20 条样本,每一条都手工标注了期望输出。然后写一个脚本,把提示词版本跑在全部样本上,统计正确率、格式合规率、失败原因分类。20 条不多,但足够暴露大部分问题。

失败类型常见原因应对思路
格式错误提示词没说清 / 模型偶尔抽风加 few-shot、代码兜底解析
信息遗漏任务步骤太多,模型顾此失彼拆分子任务,一次只做一件事
幻觉补充模型编造了原文没有的信息明确"禁止编造",必要时换低 temperature

有了这 20 条样本之后,我再也不纠结"这版提示词好不好",直接跑一遍数据,用结果说话。

4.3 让 AI 进入你的代码:function calling 的 JSON 契约

如果说提示词是让模型"说话",function calling 就是让模型"动作"。它做的事情很简单:定义一组函数,告诉模型"你可以调用这些函数",模型在需要时返回一个结构化的调用请求,由你的程序真正执行。

典型的工具定义长这样:

{ "type": "function", "function": { "name": "query_order_status", "description": "根据订单号查询物流状态", "parameters": { "type": "object", "properties": { "order_id": {"type": "string"} }, "required": ["order_id"] } } }

流程上要做一次循环:模型返回"我想调用 query_order_status" → 你的代码执行这个函数 → 把真实结果回传给模型 → 模型基于结果组织最终答案。

这里有一个我踩过的重要教训:永远让代码去执行函数,而不是相信模型的任何直接输出。模型只负责"决定"调哪个函数、传什么参数,真正读写数据库、发消息的必须是你的代码。

4.4 多轮对话的记忆:别把历史一股脑塞进去

做对话类功能时,最容易犯的错误是把所有历史消息都塞进上下文。上下文窗口是有限的,而且成本随 token 数线性上涨,历史太长还会把模型的注意力稀释掉。

我试过三个方案,按复杂度排序:

  • 滑动窗口:只保留最近五轮对话,超出部分直接丢弃。实现最简单,适合闲聊场景。
  • 摘要压缩:每进行几轮,先把前面的对话总结成一小段摘要,塞进系统提示词。适合需要长期记忆但不需要逐字记忆的场景。
  • 外部检索:把历史消息向量化存库,每轮按相关性捞回最相关的几条。适合知识库类应用。

从零起步的人,我建议先做滑动窗口,把链路跑通,再逐步升级。别上来就上重型方案。

5. Agent:一层一层剥开"自主"的外壳

Agent 是这两年最火的词,也是最容易被包装成玄学的词。我一开始对 Agent 持怀疑态度,觉得不过是"循环调用模型"的噱头。后来自己实现了一个最小的 Agent 循环,才发现它的核心思想确实朴素,但朴素不代表简单。

5.1 为什么我一开始不信 Agent,后来又真香

早期我看到各种"Agent 自主规划、自我进化"的宣传,第一反应是抗拒。直到我读到 ReAct 模式的描述:Agent = 思考(Reasoning) + 行动(Act)的循环——模型根据当前状态决定下一步做什么,调用一个工具,观察结果,再决定下一步。

这个描述击中了我的痛点:遇到分支不确定的任务时,传统的硬编码流程根本写不全所有情况,而 Agent 可以在运行时动态决定走哪条路。它不是玄学,它是把"决策"从程序员手里转移到了模型手里。

5.2 ReAct 循环的最小实现

我用伪代码记录过这个循环,核心逻辑非常简单:

状态 = 初始消息 工具列表 = [查订单, 查库存, 发消息] for 步数 in range(最大步数): 决策 = 调用模型(状态, 工具列表) if 决策 == "输出最终答案": break 结果 = 执行工具(决策.工具名, 决策.参数) 状态 = 追加观察结果(状态, 结果)

每一步里,模型看当前状态,决定调用哪个工具,你的程序执行工具并返回结果,模型再接着思考。这个循环之所以有用,是因为模型能在"现实反馈"的基础上继续推理,而不是凭空想象。

5.3 让 Agent 可控的护栏设计

Agent 最大的问题不是"蠢",而是"太自由"。让模型自己决定调用哪些工具,意味着它可能调用你没打算让它碰的东西。我的护栏设计是四件套:

  • 工具白名单:模型只能调用你注册过的函数,天然碰不到别的系统。
  • 最大循环轮数:限制 5~10 轮,防止出现死循环。
  • 成本上限:每次执行记录 token 消耗,超过阈值立即熔断。
  • 关键操作人工审批:涉及删除、转账、发送消息这类高危动作,模型只能生成"申请",由人来确认。

这四条每一条都救过我。有一次 Agent 在调试数据时差点把测试库的表清空,就是因为我在工具定义里没有限制写操作,后来加了审批才兜住。

5.4 从单 Agent 到串行工作流:把任务拆成节点

做完单 Agent 循环后,我的下一个顿悟是:大部分真实业务不需要一个什么都干的 Agent,而是需要一条"工作流"。工作流的每个节点可能是固定逻辑,也可能是模型调用。

举个例子,我做过一个资料整理流水线:

  1. 意图识别节点:判断用户输入是"查资料"还是"写摘要"
  2. 数据提取节点:从原文抽出关键实体
  3. 生成节点:用提示词生成结构化结果
  4. 校验节点:检查 JSON 合法性、必填字段完整度

这条流水线每一步都是确定的,只有生成节点用到了模型。它的好处是:每一步都可以单独调优、单独测试、单独替换。能用工作流解决的问题,我绝不用自由 Agent 解决。

6. 真正拉开差距的工程细节:评估、成本、可观测性

跑通 Demo 的人很多,能把 AI 功能稳定上线的人很少。差距不在提示词技巧,而在三个容易被忽视的工程细节:你有没有测试集、你知不知道钱花在哪、你能不能事后复盘。

6.1 评估集:给 AI 应用建一套自己的测试

通用 benchmark 测的是模型能力,不是你的业务效果。你的业务有自己的特殊性:你的客户评价长什么样、你的订单编号规则是什么、你的用户喜欢用什么词——这些必须靠自有评估集。

我的建议是从 20 条开始,逐步扩充到 100 条以上。每条样本包含:

  • 输入(原始文本)
  • 期望输出(手工标注)
  • 允许的偏差范围(比如字段值同义是否算对)

评估方式可以分层:先做规则校验(JSON 合法、字段存在、枚举值正确),再做模型打分(用一个强模型当裁判,对比输出和期望的语义一致性),最后人工抽检。三层跑下来,你对"这版改动是变好还是变坏"就有了数。

6.2 成本控制:token 在哪里飞走的,就在哪里抓回来

我一开始根本不看账单,直到一个月底发现费用超出预期三倍。逐条翻日志后,问题集中在三个地方:

  • 系统提示词写得过长:一段 1000 字的角色设定,每次请求都带着,再乘以调用量,就是一大笔钱。
  • 每轮对话都携带完整历史:聊到十轮的时候,十轮的历史成了最贵的输入。
  • 无差别的模型选型:简单分类任务也用了最大模型,纯属浪费。

我的调优动作包括:系统提示词精简到 200 字以内、历史改用摘要压缩、简单任务用小模型、引入语义缓存。这三个动作加起来,月度成本降了接近一半。

6.3 日志与可观测:当 AI 出错时,你要能事后复盘

模型应用的调试和传统后端不一样。传统后端报错有堆栈、有固定逻辑,模型应用报错经常是"它没报错,但答错了"。这时候如果没有日志,你连它是怎么错的都不知道。

我给每次请求都分配了一个request_id,在日志里记录:

字段说明
request_id一次完整任务的唯一标识
model用了哪个模型、哪个版本
prompt_hash提示词内容的哈希,便于对比版本
tokens输入/输出 token 数
latency耗时
raw_output模型原始返回(需脱敏)

有了这些日志,一次线上事故就能完整回溯:找到request_id→ 看链路里每一步的输入输出 → 定位是哪一步的提示词或上下文设计导致了错误。我还养成了一个习惯:把每次"错误但没报异常"的案例单独存一份。这些案例是教程里学不到的,是你自己的专属训练数据。

7. 如果只允许我说一句话的经验

我把这个项目从头走完一遍之后,最想分享的经验是:先跑通最小闭环,再谈优化。很多人卡住的真正原因,不是不懂原理,而是迟迟不肯写那 20 行代码、建那 20 条测试样本。AI 工程的门槛从不在起点,而在"能不能持续迭代"。

最后再分享一个我现在一直在用的习惯:每次模型输出和预期不符,我不着急改提示词,而是先把"错误案例"记下来。攒够十个错误案例之后集中分析一次,你会发现错误模式非常清晰——有的输入就是格式难解析,有的任务就是需要拆成两步,有的场景就是需要换模型。这些一手数据,比任何提示词模板都值钱。

从零开始做 AI 工程,真正难的不是 AI,是"工程"两个字里包含的耐心和系统性。希望这篇拆解能帮你少走几步弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 5:28:40

ML Visuals:专为神经网络设计的声明式结构图生成工具

1. 为什么我宁愿重装三遍系统,也要把 ML Visuals 装进科研日常做神经网络结构图这件事,我踩过的坑比跑过的 epoch 还多。三年前第一次画 Transformer 的 encoder-decoder 结构,用 PowerPoint 拉了 47 个矩形框、手动对齐 23 条注意力箭头、调…

作者头像 李华
网站建设 2026/9/30 5:27:58

在WSL Ubuntu中运行GitHub Copilot Agent:环境搭建与实战指南

1. 先说清楚:Copilot Agent为什么需要 Linux 环境GitHub 前几天放出了一份 Copilot WSL 教程,官方手把手教你在 Ubuntu 里运行编程 Agent。这件事表面上只是把 Copilot 装进了 WSL,但我看完之后觉得,它背后其实藏着一个很重要的信…

作者头像 李华
网站建设 2026/9/30 5:26:46

YOLOv5训练数字识别:从数据集标注到ONNX部署实战

数字识别听起来像是OCR领域的老题目,但真到工业现场的电表读数、快递面单编号、仪表盘数值、仓库货架标签这些场景里,你会发现一个很尴尬的事实:现成的通用OCR方案在规整印刷体上表现还行,一旦遇到倾斜、模糊、光照不均、数字被遮…

作者头像 李华
网站建设 2026/9/30 5:25:47

丛台区首爱月子会所地址在哪,营业时间及收费标准如何

深夜的孕晚期,许多准妈妈都经历过这样的时刻:一手轻抚隆起的腹部,一手翻看手机里五花八门的月子中心介绍,越看心里越没底。有的机构照片拍得精致,实地探访却拥挤嘈杂;有的报价看似亲民,入住之后护理加项、餐…

作者头像 李华
网站建设 2026/9/30 5:25:11

有机表面老化材质制作全流程:从参考图分解到Substance Painter实战

这些年做材质相关的工作,接触过不少同行,大家普遍遇到的一个瓶颈期,不是软件操作不熟练,而是拿到一张参考图不知道怎么拆。尤其是有机表面的东西,比如破损的皮夹克、沾了泥土的帆布背包、半腐蚀的木质门板,…

作者头像 李华