如果你正在学AI工程,但目前的水平还停留在跑通一个API Demo、或者在本地加载一个开源模型玩一玩的阶段,那我觉得是时候聊点更扎实的东西了。这篇内容来自我自己完整走完的一个AI工程项目复盘,英文代号就叫“ai-engineering-from-scratch”,直白一点说,就是从零开始做AI工程。我不打算做一个纯教程搬运,而是把从想法到上线的完整过程、技术选型、提示词设计、Agent架构、模型部署和踩坑经验一条条拆开讲清楚。适合的对象很明确:想做真实AI应用的开发者、想参与AI工程落地协作的产品经理,以及所有不想永远只当“调包侠”的人。无论你要做知识库问答、客服机器人、内容生产工具,还是复杂的数据抽取,这套路径都能套着用。
1. 从零开始先想清楚:AI工程到底在解决什么问题
很多人在开始做AI项目时,会把“跑通一个模型”当成“完成了一个工程”,这是最大的误区。模型调用只是整个系统里的一个环节,真正的AI工程是一整套围绕可靠性、成本和效率展开的系统设计。这一节先不谈代码,谈思路。
1.1 先定义边界,再决定要不要写代码
我拿到一个AI项目,第一件事从来不是打开笔记本写代码,而是先回答四个问题:输入是什么、来自哪里?输出给谁用、格式是什么?哪些错误是致命的?这个功能需要实时响应,还是可以异步处理?把答案写下来之后,再决定用什么模型、什么链路。这个顺序一旦反了,后面很容易被快速多变的提示词和模型版本带着跑,项目做了一半才发现需求和实现根本对不上。
举个例子,我做过一个合同信息抽取的需求。输入是PDF文件,输出是包含甲方、乙方、金额、期限的JSON对象。如果一上来就直接把PDF整本丢给模型,让它“提取信息”,大概率会出现三种情况:输出不是合法JSON、金额字段被算错、模型把不在合同里的信息也编出来。表面上是模型能力不够,实际上是因为你没有定义好输入输出的边界。后面我把流程拆成“PDF解析成文本、文本按条款切片、切片逐块过模型、输出结果按schema校验”,整套链路才真正稳定下来。
这个“先定义边界”的习惯,决定了后面所有技术选型。比如输入是长文本还是短文本,就决定了你要不要接检索层;输出是不是必须结构化,就决定了你要不要用JSON模式或函数调用;是不是要处理高并发,就决定了你选择API还是自建推理服务。边界想清楚了,很多技术选择会自然浮现,不需要硬套。
1.2 技术选型:API、开源模型还是微调
在工程选型上,我习惯把方案分成三条路径:云端API、本地开源模型、微调。这三条路径没有绝对的好坏,只有适合与否。很多项目其实是混合使用的,比如主体用开源模型,部分难任务走云端API,都是很常见的搭配。
| 路径 | 适用场景 | 成本 | 可控性 | 建议 |
|---|---|---|---|---|
| 云端API | 快速验证、数据量大且无敏感要求 | 按token付费,初期低 | 低 | 优先选择 |
| 本地开源模型 | 数据敏感、离线部署、垂直场景 | 硬件和运维成本高 | 中高 | 有明确需求再上 |
| 微调 | 固定任务形态、要特定风格和业务知识 | 数据标注加训练成本高 | 高 | 尽量往后放 |
对于大多数场景,我都建议先走API加提示词工程,而不是直接微调。微调成本高、周期长,而且只能提升模型在某类任务上的稳定性,并不能解决“上下文不够长”“幻觉严重”这类基础问题。我遇到过一个团队,一心想微调一个模型来抽取合同信息,结果折腾了一个月,效果还不如用精心设计的few-shot提示词加规则校验。后来把微调停了,改用提示词加检索加输出校验,成本降了一大半,效果反而更稳。
所以我的选型原则是:能用提示词解决的,不微调;能用检索解决的,不硬灌;能用规则兜底的,不全靠模型。模型的能力边界要做到心里有数,但工程的世界里,稳定性永远比单点能力的炫技更重要。
2. 环境搭建与基础能力准备
选型定好之后,下一步是搭环境。很多人觉得这一步没什么好讲的,但我在实际项目里见过不少因为环境问题浪费大量时间的情况。AI工程里的环境,不只是“装个Python库”那么简单。
2.1 开发环境怎么搭最省心
如果用Python,我建议把环境隔离做好。conda和uv我都用过,近两年更倾向于uv,速度快、依赖解析也稳定。核心依赖其实不多:调用LLM的SDK、数据处理库、Web框架、测试工具,按需添加,不要一把梭把机器学习全家桶都装上去。
如果用的是云端API,最大的坑是密钥管理。要把所有密钥放在环境变量或专门的配置管理服务里,绝对不要硬编码在代码中,更不要提交到Git仓库。我亲眼见过不止一次因为密钥被提交到公开仓库导致被盗刷的情况,账单一下能跑出几千块,这个成本非常高昂。所以在项目的第一个commit之前,先把.env文件加到.gitignore里。
本地推理的话,学习和测试阶段可以优先用Ollama,一条命令就能起服务,体验很轻快。生产环境则建议用vLLM,它支持连续批处理和PagedAttention,吞吐量明显更好。要提醒的是,不要上来就下载最大的模型。7B到14B量级的模型,配合一台普通GPU或者Mac就能跑很多业务场景,足够验证大部分需求。一上来就部署70B模型,光是显存规划和推理优化就能把项目拖入泥潭。
2.2 数据准备与评估基线
这是我认为AI工程里最容易被跳过、但最重要的环节。模型可以换、提示词可以改,唯独没有评估基线,你完全没法判断每次改动到底是变好还是变坏。
我会从真实业务数据里准备一个“黄金评估集”,规模不用太大,50到100条就够,但必须覆盖典型输入、边界输入和历史上容易失败的输入,并且为每个样本标注期望输出。评估集建好之后,在当前方案上跑一遍,记录准确率、召回率、格式正确率和单个请求的平均成本,这就是基线(baseline)。
之后每一次改动,都在同一个评估集上重跑。我的经验是,这样做能避开三大坑:第一,“感觉这版提示词更好了”但实际指标下降;第二,“偶然一次成功就以为系统稳定”的错觉;第三,“上线之后再集中暴露问题”的大规模返工。评估集还要持续维护,把用户反馈中出现的典型坏样本定期添加进去。这样系统不是越用越飘,而是越用越稳。
3. 提示词工程:从“会写”到“能上线”
提示词工程表面上是写文字,本质上是在给模型做“接口设计”。很多开发者写提示词,就像跟一个聪明但容易瞎猜的实习生说话,话说得不清楚,他就自由发挥。工程化思维,是把自由发挥的空间尽量缩小。
3.1 提示词的系统方法
我写提示词会遵循一套固定结构:角色与任务说明、上下文材料、任务要求与约束、输出格式定义、少量示例。角色说明让模型知道它在干什么,上下文材料给足信息,约束告诉它不能做什么,输出格式让结果可解析,示例则用两三个具体例子替代抽象描述。这套结构看似简单,却是我试过最稳定的写法。
这里有个反直觉的点:很多提示词问题并不是靠“写得更清楚”解决的,而是靠“减少模型的选择空间”解决的。如果你给模型太多自由发挥的余地,它的输出方差就会变大。比如要求模型“总结这段文本的重要内容”,它可能每次输出的语气、结构、详略都不一样。但如果你规定“必须输出三条要点,每条不超过50个字,按重要性排序”,输出的稳定性立刻会上一个台阶。
温度参数的选择也必须跟着任务走。抽取、分类、结构化输出这类任务,温度设置在0到0.3之间最稳;内容创作类任务再考虑调到0.7以上。我以前踩过坑,用默认温度做信息抽取,结果同样的输入输出波动很大,后来才意识到温度没有跟着任务类型调整。模型本身的随机性加上高温度,只会让工程系统的输出变得不可控。
3.2 结构化输出与函数调用的关键细节
工程化应用里,最稳定的做法是让模型输出可以直接被程序解析的结构化数据。目前主流模型基本都支持JSON模式或函数调用,相比纯靠提示词里的“请输出JSON”,这种方式的输出合规率要高得多。
以Python为例,给模型定义一个抽取合同信息的工具是这样写的:
tools = [ { "type": "function", "function": { "name": "extract_contract_info", "description": "从合同文本中抽取关键信息", "parameters": { "type": "object", "properties": { "party_a": {"type": "string", "description": "甲方名称"}, "party_b": {"type": "string", "description": "乙方名称"}, "amount": {"type": "number", "description": "合同金额"}, "deadline": {"type": "string", "description": "合同期限"} }, "required": ["party_a", "party_b", "amount"] } } } ]需要注意的是,不要只依赖模型“尽量输出JSON”,一定要在代码里做两层校验。第一层检查模型返回值能否被正确解析,第二层检查解析出来的字段是否落在业务预期范围内。模型输出难免有意外,所以校验和重试机制是工程必备。我常用的做法是:如果第一次输出不合法,把错误信息回传给模型,让它根据错误修正输出,最多重试两到三次。如果还是失败,就进入兜底流程,比如交给人工处理,或者放到一个待处理队列里,而不是让用户看到一团乱码。
4. AI Agent架构与多AI协作
这两年AI Agent从概念到落地,速度非常快。我当初是从一个问题开始理解Agent的:当模型不仅要回答问题,而是要主动决定“调用哪个工具、按什么顺序、什么时候结束”时,这个系统就已经变成Agent了。
4.1 Agent的核心组件
一个最小的Agent系统通常包含四个部分:模型、工具、记忆、规划循环。模型负责决策和生成,工具负责执行外部操作,记忆负责保存历史信息和中间状态,规划循环决定下一步动作。
我的建议是,不要在项目早期就上复杂的Agent框架。用一个简单循环就可以验证核心逻辑:把用户问题、工具列表和当前状态组装成模型输入,让模型决定调用哪个工具;执行工具后,把结果返回给模型;如果模型判断任务完成,就输出最终答案。这套循环用几十行代码就能实现,等逻辑确认稳定后,再考虑接入LangGraph、CrewAI这类框架。
构建Agent时,最需要关注的是“终止条件”。模型在循环中可能会一直调用工具,或者反复执行同一个操作。所以必须设置最大迭代次数、超时时间和输出校验机制。我做过几次实验,在没有限制的情况下,Agent有时候会自己“绕圈圈”,每一步看起来都没重复,但整体就是在原地打转。加了最大迭代数和超时之后,这个问题才被控制住。
4.2 多Agent协作与工作流编排
并不是所有任务都需要多Agent。多Agent能解决复杂任务的拆解,但也会引入一致性问题。做AI工程有一条很实用的原则:可以用固定工作流解决的,就不要上Agent;可以在单个Agent里解决的,就不要上多Agent。
多Agent协作常见的模式有三类。流水线模式是多个Agent完成不同子任务,输出依次传递,适合环节清晰的业务;编排者-执行者模式是中央Agent拆分任务,分发给若干执行Agent,再汇总结果,适合任务边界不太固定的场景;辩论模式是多个Agent从不同角度讨论同一个问题,最后融合共识,这个模式最有意思但成本和延迟都很高,实际项目里我用得很少。
多Agent协作里最关键的是“Agent之间的契约”,也就是每个子任务的输入输出格式必须明确。我把这个叫“Agent之间的API设计”。如果两个Agent只靠自然语言传话,信息在传递过程中就会失真;但如果把中间结果定义成结构化对象,每个Agent只负责消费和产出自己对应格式的数据,整个系统才会像一条流水线一样可靠。
5. 模型部署与上线实践
模型部署是AI工程落地的最后一段路,也是很多从Notebook走向生产的人最容易卡住的地方。这里聊几条我在实践里确认有效的路径和原则。
5.1 推理服务化的常用路径
如果走云端API,部署这一环会轻很多,核心是要做好统一的网关和密钥管理。如果是自部署开源模型,我推荐用vLLM这类推理框架,它支持连续批处理、PagedAttention,吞吐量比naive的transformers推理高不少。
一个基本的vLLM启动命令是这样:
vllm serve Qwen/Qwen2.5-7B-Instruct \ --tensor-parallel-size 1 \ --max-model-len 8192 \ --gpu-memory-utilization 0.9 \ --port 8000几个参数要特别注意:tensor-parallel-size用于多卡并行,只有一张卡就设为1;max-model-len决定了单次能处理的最大上下文长度,设太大会导致显存消耗飙升;gpu-memory-utilization控制显存利用率,并不是越高越好,要给请求波动留出缓冲。启动之后,vLLM提供OpenAI兼容接口,上层应用可以直接复用现有的SDK,不用改太多代码。
还要泼一盆冷水:本地部署并不总是更省钱。GPU硬件、电费、运维成本全算进去,有时候比直接买API额度还贵。如果你只是做一个内部小工具,数据不敏感、并发量不高,直接调用云端API可能是更经济的选择。部署一套本地推理服务,等于在维持一个小型在线系统,需要监控、升级、容量规划,这些看不见的运维成本经常比API账单更吓人。
5.2 性能、成本与监控
上线前后,监控是必须做的。我一般从三个层面去搭:模型层记录输入输出token数、模型调用耗时、重试次数;业务层记录请求成功率、无效输出比例、用户反馈指标;成本层统计每次请求的token消耗和月度费用趋势。
延迟方面不要只看平均延迟,要看P95甚至P99。大模型输出是流式的,所以还要关注首token延迟和总延迟。有时候问题不是模型慢,而是上游数据处理慢,比如PDF解析、向量检索占了大头。只有把监控做好了,你才能分清楚瓶颈到底在哪一层。
成本控制上,我常用的手段包括:给请求设置token上限、对可缓存的请求做语义缓存、优先用小模型处理简单任务、减少不必要的上下文重复注入。千万别在没有任何监控的情况下盲目做成本优化,否则你根本不知道省在哪、也不知道哪个环节最烧钱,优化方案只会成为新一轮的瞎猜。
6. 端到端项目实战:从零到一的完整流程
方法论讲多了容易飘,这里用一个切切实实的项目串一遍。我最近做了一个文档问答系统:输入是几十份内部技术文档,用户用自然语言提问,系统返回带来源出处的答案。这个项目麻雀虽小,但五脏俱全。
6.1 项目拆解与MVP定义
我把它拆成了四层:数据层负责对文档做解析、切片、清洗、入库;检索层用向量检索加关键词检索做混合召回;生成层基于召回结果构造提示词,调用模型生成答案;校验层检查答案是否与文档相关、是否包含引用、格式是否正确。每一层的目标都很单一,层与层之间用清晰的数据结构衔接。
MVP版本我没有做复杂前端,只做了命令行工具加一个简单的API接口,目的就是验证核心链路通不通。这个阶段,我建议不要纠结于界面美化和并发性能,先把“输入一个问题能得到一个靠谱答案”这件事跑通。如果你连核心链路的准确性都没有验证,就去优化架构和并发,那只是在给一个还没站起来的系统盖高楼。
6.2 从原型到生产的关键步骤
从MVP到生产,我按这个顺序推进:第一,把代码模块化,数据层、检索层、生成层、校验层各自独立,接口定义清楚;第二,把配置外置,模型选择、检索参数、提示词模板全部放配置文件,模型名称、温度、chunk大小这些参数后续会频繁调整,硬编码在代码里改起来非常痛苦;第三,增加日志和追踪,每次请求的输入、输出、检索命中了哪些文档、模型耗时、成本都要记录,这是排查线上问题的唯一依据;第四,加缓存和限流,相同问题走缓存,同时控制并发,避免被调用方压垮;第五,灰度上线,先放量10%用户,观察核心指标和坏样本,稳定后再逐步放开。
这个过程看起来不酷,但它是工程化产品里最能避开灾难的一步。我见过太多项目跳过了灰度这一步,结果一上线就被奇怪的线上输入打得措手不及。灰度给了你一个缓冲区间,可以在真实流量的压力下检验系统,而不是在故障发生之后慌慌张张修补丁。
7. 常见问题与排查技巧
从零开始做AI工程,踩坑几乎是必修课。这里挑几个我印象最深的坑来说。
7.1 我在实战中踩过的坑
第一个是幻觉问题。模型在找不到资料时,会一本正经地编造答案,这在文档问答系统里尤其明显。我的解法是:强制模型在提示词里声明“如果没有足够信息,直接说不知道”,同时在输出校验层检查回答中是否出现过检索内容里的关键信息。这个检查并不完美,但能拦住一部分明显胡编的回答。
第二个是上下文太长导致费用和延迟失控。有些文档切片后还是很长,如果把整篇都塞给模型,一次请求的成本能把人吓一跳。我的做法是限制注入上下文长度,只取排序靠前的几个切片,而不是贪多求全。毕竟检索的排序本身就是为了筛选信息,真正常用的可能只有其中一小部分。
第三个是Agent死循环。前面说过,不设置最大迭代次数的时候,Agent可能会一直循环调用工具。加了“最大迭代数加超时加人工中断”三件套后,这个问题基本被控制住了。我自己的经验是,宁可让Agent提前给出一个“不确定”的答案,也不能让它无限循环下去。在工程系统里,快速失败永远比永远卡住要好。
7.2 问题速查表
| 问题 | 可能原因 | 解决思路 |
|---|---|---|
| 模型输出JSON无法解析 | 提示词约束不够、模型输出被截断 | 使用JSON模式或函数调用,加重试机制 |
| 回答包含编造内容 | 检索结果不足、提示词未约束 | 增加检索条数,强制“不确定就说不知道” |
| 请求延迟很高 | 上下文过长、并发设置不当、上游解析慢 | 缩短上下文、做异步处理、定位上游耗时 |
| 费用快速增长 | 重复注入长文本、无缓存、模型过大 | 做语义缓存、设token上限、用小模型处理简单任务 |
| Agent反复调用工具 | 缺少终止条件、工具描述模糊 | 设置最大迭代次数,明确工具使用条件 |
这张表很适合贴在自己的项目文档里,遇到问题先查一遍,往往比重新读一遍框架文档更高效。
最后再分享一个我自己一直保持的习惯:每一个AI工程项目,我都会留一份“坏样本观察清单”。不管系统做得再稳,总会出现意料之外的输入。把这些坏样本记下来、定期复盘,把典型样本加进评估集,你会看到系统的质量曲线在一点点往上走。这个习惯看起来不起眼,但恰恰是“工程”和“实验”之间最本质的区别——实验追求一次惊喜,工程追求持续稳定。