我一直在琢磨一个问题:同样是用大模型做东西,为什么有人能稳定交付一个能跑、能维护、能升级的AI系统,有人却一直停留在“调通一个Demo”的水平?后来我想明白了,差的不是某一次调用API的手感,而是有没有一套系统性的AI工程思维。这也是我为什么特别想聊一聊“ai-engineering-from-scratch”这个话题——从零开始,把AI工程这件事实实在在地搭起来。
这里说的“从零”,不是从矩阵求导、反向传播开始啃,而是从工程视角出发:怎么设计提示、怎么搭智能体、怎么把工作流串起来、怎么把模型部署成能给别人用的服务。它适合想转行做AI应用开发的程序员、已经在做业务但想引入AI能力的技术负责人,以及被各种概念绕晕了想落地的人。我尽量不用套话,直接把我验证过的一套路径和方法摊开讲。
1. 内容整体设计与思路拆解
1.1 先给AI工程下一个“能落地的定义”
AI工程不是“会用ChatGPT”,也不是“会写几段调用大模型API的脚本”。它更像是把大模型当成一个不稳定但能力很强的“新同事”:你需要给它写清楚的任务说明书(提示工程),给它配工具和协作流程(Agent和Workflow),还要给它设计考核和容错机制(评估、监控、降级)。一句话总结,AI工程的核心在于“用工程手段把模型的智力变成稳定可靠的产品能力”。
我从零开始摸索时,最纠结的问题是“先学哪个”。市面上有铺天盖地的Prompt教程、Agent框架、部署工具,看起来每个都重要,但如果不排序,就一定陷入“学了一堆名词,做不出一个东西”的困境。后来我给自己画了一条主线:提示工程打底,智能体让模型动起来,工作流把任务串成流水线,部署让能力被外部调用,最后用工程实践兜底。
这条路径的合理性在于它符合“从简单到复杂、从单点到系统”的自然规律。提示工程是单次对话的优化,最容易上手;Agent在单次对话之上加入循环和工具调用;工作流再把多个Agent和业务逻辑编排起来;部署解决的是稳定性与并发问题。每一步都在前一步的基础上增加复杂度,每一步都有可验证的产物,不会让人觉得虚无缥缈。
1.2 从零开始的四层能力地图
我把整个AI工程的知识体系拆成四层,方便对照检查自己缺哪一块。
第一层是“与模型对话的能力”,包括角色设定、上下文管理、结构化输出设计、少样本示例选择。这一层解决的是“能不能让模型稳定地按我的要求输出”。第二层是“让模型做事的能力”,核心是Agent机制:模型如何决策调用哪个工具、如何处理工具返回的结果、如何判断任务是否完成。这一层解决的是“模型能不能替我完成闭环任务”。
第三层是“编排与集成能力”,涉及AI工作流设计、多Agent协作、外部系统对接。业务从来不是单次问答,而是多步骤流程,这一层解决的是“AI能力怎么嵌入真实业务”。第四层是“部署与运营能力”,包含模型服务化、推理优化、性能监控、成本控制。前面所有工作最终都要变成可以被用户流畅使用的东西,这一层解决的是“怎么让AI服务稳定可用”。
这四层不是线性关系,而是层层叠加的依赖关系:如果提示写得烂,Agent再聪明也白搭;如果Agent不稳定,工作流跑起来就会到处卡壳。我的建议是按顺序逐层突破,每层先做一个小项目验证,再进入下一层。“从零开始”最怕的不是慢,而是跳着学导致地基不稳。
2. 提示工程与AI智能体的底层逻辑
2.1 提示工程:不是“写几句话”,而是“给模型搭脚手架”
很多人以为提示工程就是靠一句“你是一个资深专家”来调教模型,实际做起来远没那么简单。我更喜欢把提示工程理解成“给模型搭脚手架”——你要在输出目标、生成范围、判断标准、约束条件这四个维度上都给出明确支撑,模型才能在正确的地方“施工”。
比如说,你要让模型把一段客服对话整理成工单。只给一句“请总结这段对话并生成工单”,模型大概率会给你一段泛泛的文字。而一份合格的提示应该包含:角色(你是客服系统的工单生成器)、输入格式(对话内容用什么分隔符包裹)、输出结构(JSON字段:问题类别、紧急程度、客户诉求、建议处理部门)、判断标准(什么时候算紧急、什么时候算普通)、负向约束(不要输出对话原文、不要推测没有提到的信息)。
我把提示工程分为四类基础技法:角色设定法、结构化输出法、少样本驱动法、思维链引导法。角色设定解决说话口吻和知识边界,结构化输出解决机器可解析问题,少样本驱动法解决“说不如示范”,思维链引导解决复杂推理任务。实际使用时,四者通常组合出现。比如我在做一个会议纪要助手时,就是把角色设定成“会议记录专员”,用JSON格式要求输出决议事项与责任人,再附上两条高质量示例,最后在提示里加了一句“先从时间线角度梳理讨论过程,再总结结论”,这四条出来以后效果立刻不一样。
实操心得:我见过太多人把提示工程当成“一次到位”的事。真实情况是,提示需要像调参数一样反复迭代。我的迭代方法是每改一版就保存下来,用同一组测试用例跑分,看哪个版本的通过率更高。不要凭感觉判断“感觉这次回答更好”了,要拿数据和案例说话。
2.2 AI智能体:从单次问答到自主任务闭环
如果说提示工程让模型“说得好”,AI智能体让模型“做得到”。我理解中的Agent是一个循环系统:接收任务、规划拆解、调用工具、观察结果、修正计划,直到任务完成或者主动放弃。这个循环的核心不是模型本身,而是“控制逻辑”与“工具边界”的设计。
2025年我做过一个信息搜集Agent,任务是根据关键词找到相关公开资料并生成摘要报告。最开始的版本特别天真:丢给模型一个提示,让它“去找资料”,可模型自己根本没有联网能力,它只能编造链接和标题。后来我引入工具调用机制,给Agent配了搜索接口、网页抓取工具、内容缓存模块,并且告诉模型“每一步先选择工具,再根据工具的返回结果决定下一步”,问题立刻解决了一大半。
这里有一个关键设计点:Agent的工具边界要尽量窄。每个工具只做一件事,并且工具的输入输出要用严格的Schema描述。比如“search_web(query: str, limit: int) -> List[Dict]”和具体的描述“在公开网络中搜索与query相关的内容,返回标题和URL列表”。模型靠这些描述来选择工具,描述写得模糊,模型就会乱选。这就像给新人布置任务,你不说清楚资源和边界,他就会自由发挥,结果不可控。
Agent还有个容易踩的坑:它并不是越自主越好。任务模糊、工具不可靠、反馈信号弱,这三个条件只要占两个,自主Agent就会进入死循环或产生严重错误。我在早期就吃过亏——让Agent自动修改一份JSON配置,结果它以为自己改了,实际改错了字段,还循环检查了三遍才报错,浪费了整整一批调用额度。现在我所有Agent项目都强制要求“每个关键步骤都输出中间状态,并且由上层工作流决定是否继续”,自主性只停留在“执行层面”,决策权留在人或者确定性的流程里。
2.3 多AI协作与工具调用设计
聊到AI Agent,就绕不开“多AI协作”和“工具调用设计”这两件事。多AI协作不是把多个模型拼在一起开会,而是根据不同模型的强项做分工。比如我在一个文档审查系统里,用推理能力更强的模型做逻辑一致性检查,用速度更快、成本更低的模型做格式和错别字初审,最后再让总结模型把两路结果合并成审查意见。
这个分工背后有个工程原则:把任务切块,让每个Agent的职责足够单一。我最早犯的错误是想让一个全能Agent“一条龙”完成所有事情,结果指令工程复杂到连我自己都调试不动。后来把所有任务拆成“采集—清洗—分析—生成—审核”五个独立Agent,每个Agent的上下文窗口只装载自己需要的那部分数据,效果和可维护性都大幅提升。
工具调用设计方面,我强烈建议对工具做“登记、注册、鉴权、审计”四个动作。登记是指工具的用途和参数写清楚;注册是让Agent能看到这个工具的存在;鉴权是决定哪些Agent能调用哪些工具,防止越权操作;审计是记录每一次工具调用,后面出问题才好回溯。我在生产环境里还加了一个“工具调用预算”机制,限制一个任务最多调用多少次工具,超过就终止,这个机制救过我很多次——至少不会因为Agent陷入死循环而烧掉一整月的推理配额。
3. AI工作流的搭建与核心实操
3.1 工作流设计的“三段式”框架
如果说Agent是单兵,工作流就是一条生产线。AI工作流的本质是把多个步骤(可能是模型的调用、可能是不确定性代码的编写、也可能是人工审批)编排起来,形成一条确定性的流水线。我常用的设计框架叫“三段式”:输入标准化、处理模块化、输出统一化。
输入标准化的意思是,所有进入工作流的数据都要经过清洗、格式化、补充上下文的处理,不能让模型直接吃原始数据。处理模块化是把整个业务流程拆成一个个节点,每个节点只做一件事。输出统一化是指不管中间用了多少模型、多少分支,最终对外交付的格式必须一致,调用方不需要关心内部是Model A还是Model B。这三段式的好处是每一段都可以单独测试、单独替换,不会牵一发动全身。
举个例子,我搭过一条“客户反馈分析”工作流。输入标准化节点负责把客服工单、在线评论、问卷结果全部转成统一的文本格式,并打上渠道标签;处理节点分两步,先做情感分类,再提取高频问题和产品建议;输出统一化节点把结果拼装成固定JSON格式,直接写入看板数据库。整条流程既有大模型节点,也有普通Python节点和规则判断节点,配合起来比“全交给模型”稳定得多。
3.2 一个可复用的Agent工程代码骨架
下面我贴一个非常简化的Agent循环骨架,它展示了我刚才说的“控制逻辑”是怎么写的。源码级别不复杂,但你可以在这套骨架上扩展工具、记忆和状态管理。
import json from typing import List, Dict, Callable class SimpleAgent: def __init__( self, llm_call: Callable[[str], str], tools: Dict[str, Callable], max_steps: int = 10, ): self.llm_call = llm_call self.tools = tools self.max_steps = max_steps def run(self, task: str) -> str: messages = [ { "role": "system", "content": ( "你是一个任务执行Agent。每次只做一步:" "先输出你的思考(reasoning),再输出你要调用的工具名" "(tool_name)和参数(tool_args)," "或者输出最终答案(final_answer)。" "使用JSON格式。" ), }, {"role": "user", "content": task}, ] for step in range(self.max_steps): raw = self.llm_call(json.dumps(messages, ensure_ascii=False)) action = json.loads(raw) if "final_answer" in action: return action["final_answer"] tool_name = action.get("tool_name") tool_args = action.get("tool_args", {}) if tool_name not in self.tools: messages.append( { "role": "assistant", "content": raw, } ) messages.append( { "role": "user", "content": f"工具 {tool_name} 不存在,请重新选择。", } ) continue try: result = self.tools[tool_name](**tool_args) except Exception as e: result = f"工具调用失败:{e}" messages.append({"role": "assistant", "content": raw}) messages.append( { "role": "user", "content": ( f"工具返回结果:{json.dumps(result, ensure_ascii=False)}" "请根据结果决定下一步:继续调用工具或给出最终答案。" ), } ) return "达到最大步骤数,任务未完成,请检查任务描述或工具配置。"用的时候只需要实现一个llm_call函数(比如封装OpenAI或国产大模型的API)和一个工具字典。这个骨架里有两个我特别在意的细节:一是每一步都要求模型输出“思考过程”,这能让模型更准确地选工具;二是工具调用失败后不直接终止,而是把错误信息回喂给模型,让它自己调整参数重试,很多小问题这样就解决了。
实际项目里还需要在骨架外加两层:记忆层(保存历史摘要,避免上下文无限膨胀)和检查层(自定义校验函数,判断输出是否符合预期)。我不建议一上来就引入重型Agent框架,先用这个几十行的骨架跑通业务,再按需替换组件,你会发现思路清晰得多。
3.3 工具选型:框架、模型API与部署方式的取舍
工具选型这件事,我把它拆成三个独立决策:模型决策、框架决策、部署决策。先说模型决策,每次选模型都要同时看四个指标:推理能力、上下文长度、响应速度、Token成本。能力最强的不一定合适,因为速度和成本随能力同步上升。我的通用建议是“按任务定模型”,简单分类任务用轻量模型,复杂推理任务用旗舰模型,中间再加一层自动路由,按问题的复杂度分诊。
框架决策要克制。“LangChain、LangGraph、LlamaIndex、自研控制逻辑”这四类我都试过,最大的感受是框架的价值在“集成能力”而非“抽象能力”。如果项目已经有清晰的流程节点,自研逻辑反而比框架更可控、更好调试。框架适合快速原型验证,尤其是涉及大量文档索引、相似度检索的场景。生产环境我更倾向于“用框架做集成适配、用自研逻辑做核心编排”,两边取长补短。
部署方式决策主要看三点:并发量、数据隐私、预算。并发量低、数据敏感度不高时,直接调用托管API最划算;并发量大且需要极致延迟优化时,考虑自托管开源模型并用张量并行推理;数据必须留在内网时,没有别的选择,只能私有化部署。我用一个表格把常见部署方式的特点对列一下:
| 部署方式 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 托管API | 接入快、维护少、持续更新 | 成本随调用量线性增长、数据出域 | 原型验证、中小流量 |
| 云端自托管 | 成本可预期、数据可控性中等 | 运维复杂、需要GPU优化 | 稳定中高流量 |
| 私有化部署 | 数据完全内网、离线可用 | 前期投入高、迭代慢 | 金融、政务、企业内网 |
关于工具选型,我必须多说一句:不要迷信“最新最强”的框架或模型。生产系统最怕的是不可控的依赖链。选工具前先想清楚,如果这个框架下周不维护了,我的代码能不能快速迁移?尽量把核心逻辑写在自己的控制范围内,外部工具只做薄薄一层适配。
4. AI模型部署与工程化落地要点
4.1 部署不是“把模型跑起来”,而是“让模型稳定服务”
很多人在部署AI模型这件事上有个误区:以为能用Python把模型加载进来,输入一段文本返回一个结果,就算部署成功了。严格说,那只是“把模型跑起来”。真正的AI模型部署要考虑并发排队、超时控制、优雅降级、自动扩缩容、安全认证、成本监控这一整套服务化能力。
2025年我做一个内部知识库问答系统的时候,最开始就是简单地用Flask包了一层模型调用,上线第一天就被打爆了。原因有三个:没有做请求排队,并发一高进程直接卡死;没有做超时控制,模型偶尔推理时间过长,前端请求全部挂起;没有做上下文长度限制,用户丢一篇长文进来直接把模型输入截断,回答前言不搭后语。后来我重构部署架构,把模型推理放到独立推理服务里,外面加任务队列,再在接入层做超时熔断和输入长度校验,服务才稳定下来。
由此我总结了部署AI服务的三个核心设计原则:一是“永远别让外部请求直连模型推理进程”,中间必须有队列或网关做缓冲;二是“所有外部输入默认不可信”,长度、格式、内容都要在校验后才进入模型;三是“为失败设计出路”,模型超时怎么办、服务过热怎么办、模型返回异常怎么办,这些都要在上线前就写好降级策略。
4.2 从Jupyter到生产:一个最小可部署服务的演进路径
我用一个最小可部署的示例来展示演进路径。第一阶段是Jupyter里跑通模型调用,完成业务验证;第二阶段把它封装成FastAPI服务,把模型参数、提示模板、业务逻辑分离;第三阶段加入Redis队列、错误重试和基础监控;第四阶段再加网关、鉴权、灰度发布。给一个FastAPI的极简示例,感受一下第二阶段的形态:
from fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field from typing import Optional app = FastAPI(title="AI 推理服务", version="0.1.0") class GenerateRequest(BaseModel): prompt: str = Field(..., min_length=1, max_length=2000) temperature: float = Field(0.7, ge=0.0, le=2.0) class GenerateResponse(BaseModel): text: str prompt_tokens: int completion_tokens: int @app.post("/v1/generate", response_model=GenerateResponse) async def generate(req: GenerateRequest): try: # 这里替换成你的模型推理调用 result = fake_infer(req.prompt, req.temperature) return result except TimeoutError: raise HTTPException(status_code=504, detail="推理超时,请稍后重试") except Exception: raise HTTPException(status_code=500, detail="推理服务内部错误")交付生产环境时,你还需要在FastAPI前面再套一层API网关,负责鉴权、限流、日志。不要把鉴权逻辑写在业务代码里,那样每加一个服务就要重复写一遍。网关层解决通用问题,业务服务只负责业务,这个边界从第一天就要划清楚。
4.3 性能、成本与可观测性:模型上线后真正重要的三件事
模型上了线,真正的工程挑战才开始。性能方面,我实测下来对大模型影响最大的三个参数是输入Token数、输出Token数和并发数。输入Token越多,首Token延迟越高,所以前置的压缩和检索策略要尽量把输入控制在必要范围内;输出Token数决定最终响应时间,如果不做流式输出,用户就要干等很久。
成本方面,我发现大量成本浪费来自“无效调用”:同一个问题被重复提问、Agent死循环反复调用、排查问题时对着生产环境狂试Prompt。我在每次调用中都会记录下请求参数、Token用量、耗时,月底复盘成本时,哪类任务烧钱一目了然。可观测性是我特别想强调的,AI服务的监控指标跟普通Web服务不一样,除了QPS和错误率,还必须看“模型回答质量分”、“提示模板版本号”、“工具调用成功率”这些AI专属指标。
我在团队里推行了一个“AI服务监控四件套”:日志全量采集,包括输入输出摘要和Token数;指标打点,包括延迟、并发、错误分类;链路追踪,把一次业务请求串起的所有模型调用和工具调用展示出来;离线评测,每天自动跑一批测试样例,监控回答质量有没有回退。没有这套监控,模型升级后表现变差你都发现不了,这才是最可怕的。
5. 常见问题排查与AI工程实践避坑实录
5.1 高频故障一:上下文窗口溢出与回答漂移
上下文窗口溢出是最常见的问题,表现为模型突然报错“超过最大长度限制”或者忽略了你最初的指令。它在工程上的根源不是模型不聪明,而是你没有做上下文压缩。我处理的方法是用“摘要漂移”的思路:每经过一段对话,就把前面的历史内容压缩成结构化摘要,只保留关键事实和未完成任务,然后把摘要放回上下文最前面。
回答漂移更隐蔽,模型越聊越偏,早期内容逐渐被新内容覆盖出注意力窗口。这种问题的排查方向是“检查指令的位置”。我的经验是,系统指令放在上下文最开头并没有绝对保险,在关键节点重复核心指令会更有效。比如在Agent每轮循环开始前,都把“你的目标是什么、当前做到哪一步、接下来只能做什么”这三句话附在用户消息的开头,漂移概率会明显下降。
5.2 高频故障二:Agent工具调用失灵与死循环
Agent工具调用的典型故障有三种:工具名幻觉、参数格式错误、返回结果解析失败。工具名幻觉是指模型凭空生成一个不存在的工具名;参数格式错误是模型把字符串传给了整数字段;解析失败是模型返回的JSON里带了多余的反引号或散文文本。这三种问题我都有对应的工程防线:工具名幻觉靠“工具不存在时回喂错误信息让模型重新选择”来解决;参数格式错误靠“解析前先做类型规整”兜底;JSON解析失败靠“提取代码块或JSON子串再解析”处理。
死循环是Agent工程最让人抓狂的问题。模型反复调用同一个工具、每次结果差不多、永远不给最终答案。我分析过大量死循环案例,根因几乎都是“任务目标不够明确,或终止条件没有定义清楚”。我的修复手段是双重的:一是在系统提示里写明“当你连续三次得到相似结果时,直接基于已有信息给出答案,不许再调用工具”;二是在代码层面设置步骤数、调用数双阈值,超了就强制返回当前最优结果。不要指望模型自己“觉醒”,要给它设置刹车。
5.3 高频故障三:模型输出不稳定,如何用工程手段兜底
同一个提示下,模型输出在边界情况中经常不稳定,尤其是涉及格式、数值、枚举值场景。如果你在业务里直接使用模型返回的原始字段,迟早出事故。我的工程兜底三板斧是校验、默认值、重试。
校验是在业务逻辑前加一层严格Schema校验,不符合就让模型“按错误信息重新生成一次”,这比盲目重试有效得多。默认值是给每个关键字段设计一份默认值,防止校验不通过时整个流程卡死。重试是有策略的,不能无限重试同一个坏提示,要“升级提示”:第一次只让模型修格式;第二次把错误信息原样回喂,并且附上一个正确示例;第三次还失败就直接走人工兜底通道。
5.4 我的个人实操心得:从零到上线,最值得记住的几条经验
从零开始做AI工程这两年,如果说只能留下几条经验,我会选这几条。
第一,永远先把“评估集”建起来。没有评估集,你改的每一版提示、每一个模型、每一条Agent逻辑都分不清是变好还是变坏。我吃过最亏的一次,是凭感觉换了一个“看起来更强”的模型,结果业务指标掉了一截,还浑然不觉。现在任何AI项目,第一周永远在搭评估集,其他都往后排。
第二,提示工程一定要做版本管理。我见过太多人提示一改就再也回不去了,连原来的版本长什么样都忘了。我把提示模板放到独立配置文件里,用Git管理,每次修改都记录原因和测试效果。这样做还有一个好处:模型升级后如果效果变差,可以快速回滚提示版本定位问题。
第三,引入AI能力是要“花钱买不确定性管理的”。这句话听起来像废话,但真正理解的人不多。你的预算里要预留一部分给模型调用量,一部分给评测集构建,还有一部分给异常处理和人工复核。只按“调用成本”算账,后期一定会被不确定性打得措手不及。
第四,也是最重要的一条,能用确定性代码解决的问题,就不要用AI解决。AI适合解决“理解语义、生成内容、复杂判断”这类问题,而格式转换、字段映射、数据校验这类问题,写规则比让模型做又快又稳又省钱。工程化的最高境界不是AI用得越多越好,而是AI和确定性代码各司其职,整体系统才最可靠。