这两年AI Agent的热度有多高,不用我多说。朋友圈里隔三差五就有人晒“三天跑通AI Agent应用”的成果,GitHub上各种Agent框架换了一茬又一茬,连不少写传统后端、前端的同事都开始关心“要不要转行做Agent”。我自己的私信里经常出现一类问题:刚学完Python基础和模型API调用,想入门AI Agent,是不是应该直接找个主流框架去啃?
我的答案一直很明确:别一上来就啃框架。这不是说框架不好,而是对于新手和转行者,框架恰恰是最不该先碰的东西。框架替你封装了大量关键机制,你跑通Demo的一瞬间会觉得自己会了,可一旦业务场景稍微变化,立刻寸步难行。这篇文章我想把AI Agent真正应该按什么顺序学、每步学到什么程度才算过关,完整梳理一遍。全程不带货、不吹框架,只讲底层逻辑和实操路径。
1. 先搞清楚Agent是什么,再决定学什么
1.1 Agent到底是个什么东西:从点一次外卖说起
很多人对AI Agent的理解是“能自己完成任务的AI”,这个说法没错,但太含糊。含糊的后果就是:学的时候不知道该抓什么重点,看框架文档也看不出门道。
我经常用一个特别朴素的类比来解释Agent。想象你让一个朋友帮你点外卖。他不会直接把手机扔给你,而是先问你想吃什么,然后打开外卖软件筛选商家,比对配送时间和评分,下单之后还要盯着订单状态,骑手快到了再让你下楼取餐。整个过程包含好几个环节:理解你的需求、制定计划、调用工具(打开App)、收集反馈(看订单状态)、调整下一步行动。
大模型驱动的Agent本质上就是这个流程的自动化翻版。它最核心的组成可以拆成五块:
| 组成部分 | 作用 | 通俗理解 |
|---|---|---|
| 大模型 | 做决策、理解语言 | 整个系统的大脑 |
| 工具调用 | 执行具体动作 | 手和脚 |
| 记忆 | 记录上下文和中间结果 | 短期便签 |
| 编排流程 | 决定先做什么后做什么 | 行动顺序的控制逻辑 |
| 反馈回路 | 根据结果修正后续动作 | 做完一步看效果再走下一步 |
把这五块放在心里,你再看市面上的框架就会明白,框架干的活无非是把其中某一两块封装好,让你少写代码。但框架不会替你建立“这五块是怎么配合的”这一层直觉。缺了这层直觉,你连排查问题的入口都找不到。
1.2 框架替你做了什么,以及你没学到什么
主流Agent框架确实节省了大量重复劳动。它们通常会帮你做这几件事:模型接入与多供应商切换、工具注册与自动调用、记忆的读写管理、多轮对话循环、错误重试机制。
听上去很香对吧?问题在于,这些恰好是一个Agent入门者最需要亲手体验一遍的东西。
我见过一个真实案例。某位读者A同学,跟着教程把某编排框架的RAG Demo跑通了,兴奋地截图给我看。我问他:当用户问了一个知识库里没有答案的问题时,你的Agent该怎么判断“查不到”,又该怎么回复?他答不上来。因为整个流程里,检索动作是框架自动触发的,回复模板也是封装好的,他根本没看过中间那一步Prompt长什么样。
这就是典型的“能跑不能改”。啃框架学到的东西,在面试或做真实项目时撑不住:换模型供应商就不会改配置,加一个新工具不知道要调哪些参数,Agent回答跑偏了也不知道是该改Prompt还是改工作流还是改后处理逻辑。
所以我给新人和转行者的第一个建议是:把框架当成最后的甜点,而不是开胃菜。先用手写的方式把Agent的每一个环节都摸一遍,再去看框架怎么把环节串起来,你会看穿它的一切魔法。
2. 入门第一课:Prompt工程才是Agent的基本功
2.1 从“问一句答一句”升级到“写操作系统任务说明书”
很多新手觉得Prompt工程就是“把问题问得更清楚”,这个理解放在普通聊天场景下没错,但放在Agent场景下就远远不够了。
Agent场景下的Prompt,承担的是“系统任务说明书”的职责。你需要让模型清楚知道:它是什么角色、要完成什么目标、输入数据长什么样、处理步骤是什么、输出格式是什么、遇到异常情况怎么办。一套合格的Agent Prompt,本质上是在和模型“签合同”。
有个生活类比特别好用。你临时喊同事帮你“整理一下那份文档”,和给他一份写清楚“按A维度分类、输出表格、缺失数据标红、完成后发我检查”的SOP,效果天差地别。写Agent的System Prompt,就是在写后者。
从“问一句答一句”到“写SOP式说明书”,这个思维升级是关键。你写的每一句话,最终都会变成模型在运行时遵循的规则。写得不清楚,模型就会“自由发挥”,而自由发挥在Agent场景里意味着行为不可控。
2.2 一套可复用的结构化Prompt模板与示例
我平时写Agent的系统提示词,基本固定用下面这套结构。以“信息摘要Agent”为例:
角色:你是一名信息摘要助手,负责从用户提供的原始文本中提取核心信息。 目标:输出包含“主题”“关键事实”“行动项”三部分的摘要,方便用户快速决策。 输入格式:用户会粘贴一段文本或链接内容,链接内容你需要自己访问后提取正文。 处理步骤: 1. 如果输入是链接,先抓取页面正文,忽略导航、广告等干扰内容。 2. 提取文本中的核心主题,用一句话概括。 3. 列出3-5条关键事实,按重要程度排序。 4. 如果原文中包含需要后续处理的事项,提取为行动项;没有就写“无”。 输出格式: 严格使用以下JSON结构,不要输出任何额外解释或代码块标记: {"topic": "主题", "facts": ["事实1", "事实2"], "actions": ["行动项1"]} 边界情况: - 如果输入文本过短(少于20字),直接回复“文本过短,无法摘要”。 - 如果抓取链接失败,回复“链接不可访问”,不要编造内容。注意几个细节。角色定义给了模型行为基调;处理步骤把复杂任务拆成了顺序动作,模型不容易跳步;输出格式用JSON约束,方便后续程序直接解析;边界情况专门处理“模型最容易瞎编”的失败场景。这几条加在一起,才是Agent能稳定工作的基础。
对比一下很多人常见的写法:“帮我总结一下这段文字。”这样的Prompt不是不能用,只是输出格式时好时坏,你今天解析它花的心力,远超写上面那段结构化Prompt的成本。
2.3 评测回路:怎么知道Prompt改好了
写Prompt最怕“凭感觉改”。改几个字,跑一次看结果“好像还行”,再过几天又发现不行,根本不知道是哪里回退了。
我建议从第一天就建立评测习惯。方法极其朴素:准备一个小测试集,每条测试用例都标注“期望输出长什么样”。每次修改Prompt后,把整个测试集跑一遍,记录通过数。只有通过率变高或者失败项的性质发生了明显变化,这次改动才算有效。
比如“信息摘要Agent”的测试集,可以准备五六条不同长度的文本,覆盖正常段落、超短文本、含行动项的文本、格式混乱的文本。花半个小时跑完,你就能清楚地看到哪些Prompt描述有效、哪些描述无效。这个习惯会贯穿你之后所有的Agent开发工作,越早建立越好。
这里必须强调一个新手最容易犯的错误:拿一两个case来回调Prompt,调到那两三个case都通过就宣布“调好了”。真实场景里,测试集没有覆盖边界情况,一到线上就翻车。所以测试集宁可一开始小,也一定要包含边界和异常输入。
3. 入门第二课:Function Calling的底层机制,必须亲手跑一次
3.1 工具调用其实是个“前台接线”的过程
模型本身不能访问外部系统,它不知道今天的天气、查不了数据库、发不了HTTP请求。但Agent需要这些能力,怎么办?靠的是Function Calling(函数调用)机制。
这个机制很多人理解反了。它不是模型“自己执行”函数,而是模型在一堆候选工具里做选择,决定“这个问题应该调用哪个函数”,然后把函数名和参数以结构化JSON的形式返回给你。真正的函数执行,始终发生在你自己的代码里。
打个比方,这就像前台接线。用户电话打进来,转接的接待员(模型)不懂财务业务的细节,但他知道“报销相关的事应该转给财务部门”,于是把用户工号、问题类型记录下来,转给财务部门。真正解决问题的是财务部门,也就是你写的那个Python函数。
完整的通信协议是这样的:你发给模型“所有可用工具的说明”+用户请求,模型返回“调用哪个工具、传什么参数”,你在自己代码里执行这个工具并把结果以文本消息的形式回传,模型再结合结果生成最终答复。理解这条链路,是后面所有Agent开发的基础。
3.2 用原生API写一次工具调用:代码走读
下面这段代码是一个最小示例。假设我们要让模型查询订单状态,工具是一个根据订单号返回状态的函数。
import os from openai import OpenAI client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) def get_order_status(trade_no: str) -> dict: # 这里模拟一个订单查询接口,真实场景会请求业务系统 fake_db = {"20250101A": {"status": "已发货"}, "20250101B": {"status": "待支付"}} return fake_db.get(trade_no, {"status": "未找到订单"}) tools = [ { "type": "function", "function": { "name": "get_order_status", "description": "查询订单最新状态,输入商户订单号,返回状态描述", "parameters": { "type": "object", "properties": { "trade_no": {"type": "string", "description": "商户订单号,形如20250101A"} }, "required": ["trade_no"] } } } ] messages = [ {"role": "system", "content": "你是订单客服助手,用户询问订单状态时请查询后再回答。"}, {"role": "user", "content": "帮我查一下订单20250101A寄出了没有"} ] # 第一次请求:模型拿到工具定义后,决定是否调用工具 resp = client.chat.completions.create( model="gpt-4o-mini", messages=messages, tools=tools ) msg = resp.choices[0].message if msg.tool_calls: # 模型决定调用工具,我们自己在代码里执行 tool_call = msg.tool_calls[0] result = get_order_status(tool_call.function.arguments) # 把工具执行结果作为tool消息追加到对话中 messages.append(msg) messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": str(result) }) # 第二次请求:模型基于工具结果生成最终回复 resp2 = client.chat.completions.create( model="gpt-4o-mini", messages=messages, tools=tools ) print(resp2.choices[0].message.content)这段代码只有二十几行,却包含了工具调用的完整生命周期。我建议每一个入门的同学都照着跑一遍,然后尝试改三个地方试试:换一个工具让模型调用;修改参数描述看模型传参是否受影响;去掉工具定义直接问订单状态,看模型怎么回复。这三个实验做完,你对Function Calling的理解会超过绝大多数只跑过框架Demo的人。
3.3 新手设计的工具为什么总被模型用错
都说Function Calling简单,真做起来翻车率很高。常见坑我列在下面:
- 参数描述写得太敷衍。比如参数叫
status,描述只写“状态”,模型根本不知道应该传什么格式。正确的描述要把取值范围或示例写进去:pass或fail。 - 工具描述没有说明“什么时候用”。模型是看description决定是否调用的。描述里只写“获取天气”,模型遇到非天气类问题就不会调用。更好的写法是“查询指定城市当前天气情况,当用户询问温度、降雨、空气质量时使用”。
- 工具返回格式随意。有的同学让工具返回一个纯字符串,里面混着换行和中文标点,后续交给模型去“猜”。这很容易导致模型解读错误。建议工具统一返回结构化JSON字符串,字段名稳定。
- 没有处理调用失败。工具抛异常了怎么办?网络超时怎么办?最好在工具函数内部try-except,返回一个统一格式的错误结果,让模型知道“这次调用失败了,可以换一种方式重试或如实告知用户”。
记住一句话:模型用对工具,靠的不是模型聪明,而是你的工具定义足够清楚。把每个工具描述当成写给一个认真但有点笨的实习生看的操作手册,出错的概率会小很多。
4. 入门第三课:把Agent拆成工作流,不要写成一坨万能提示词
4.1 单次调用和多轮编排的本质区别
很多人第一次跑通“把用户问题发给模型、模型决定调工具、返回答案”这个循环之后,会觉得自己已经会Agent了。其实那只能叫“带工具的单次问答”。
真正的Agent,核心价值在多轮编排。它可能需要先收集信息,再制定计划,执行一部分,发现问题,修正路线,再继续执行。这个过程不是一次函数调用能覆盖的。
可以这样理解:单次问答是照着菜谱做一道固定步骤的菜,每一步都写在纸上;Agent则是一个掌勺的大厨,做菜过程中发现盐不够了,会让帮厨去买,回来后尝一口觉得味道偏淡,再决定要不要补一点,整个流程是弹性的、递进的。
要把这种弹性做出来,靠“把流程写进一段超长Prompt”是行不通的。Prompt里写再多步骤,模型也只是“建议你去执行”,一旦某个中间结果不对,后面的步骤容易整体跑偏。正确的做法是在程序代码层面显式定义工作流的节点,让每一步都是可观测、可拦截、可回退的。
4.2 最小工作流设计实例:自动写周报Agent
我用一个大家都会遇到的场景来拆解:自动写周报的Agent。你将团队各成员的平日工作记录交给它,它输出一份结构化周报。
这个Agent的工作流可以拆成五个节点:
| 节点 | 输入 | 输出 | 失败兜底 |
|---|---|---|---|
| 收集数据 | 用户提供的工作记录列表 | 按日期/项目归类的结构化事件 | 空数据时提示用户补充 |
| 草稿生成 | 归类后的事件列表 | 周报初稿 | 事件过少时明确提示可能不完整 |
| 自检 | 初稿 | 修正后的稿件 | 连续两次自检不一致,以第一次为准 |
| 格式规整 | 修正稿 | 符合周报模板格式的文本 | 格式转换失败时输出纯文本 |
| 输出 | 最终文本 | 展示给用户确认 | 用户可一键打回重写 |
关键点在于:每个节点都是独立的代码步骤,有自己的输入输出和失败分支,而不是靠一段Prompt把所有逻辑塞给模型。
实际写代码时的结构大概是:一个data_collector函数负责数据整理,一个generate_draft函数调用模型生成初稿,一个self_review函数再次调用模型检查初稿,最后format_report负责格式转换。每两个节点之间,你都插入检查逻辑:数据为空就不生成,初稿太短就允许直接跳过自检步骤。
4.3 兜底设计:让人在回路里待命
新手做Agent工作流通常只关注“正常路径”:收集数据、生成、输出。一旦中间出了岔子,要么卡死,要么模型编造结果。这两件事都是线上事故。
工程上解决问题的手段很简单,但很多人就是不做:给工作流设置最大循环次数,比如最多三个推理循环,超过就停下来问用户;每一步包上try-except,工具失败时返回明确错误信息而不是静默吞掉;关键操作前设置人工确认点,比如“周报即将发送给领导,是否确认”。
“generating without confirmation”这种设计听上去不酷,但恰恰是它在真实业务中保护你。我在带人做Agent项目时,会要求所有工作流的最后一个节点前都必须有一个“人可以介入”的出口。这既是对用户的尊重,也是Agent可控性的底线。别小看这点,很多Agent项目翻车,不是模型能力不够,而是缺少一个让人说“停”的地方。
5. 实战:用原生API做一个不依赖框架的迷你Agent
5.1 为什么要用原生API做,而不是直接上框架
到这一步,我依然建议你选择原生API,不要用任何编排框架。原因很简单:原生API只是替你封装了HTTP请求,Agent的循环、工具调度、结果处理全部由你自己写。你会被迫看清楚每一个环节,学习效率最高。
等你亲手写完这个迷你Agent,再去看框架,会发现框架文档里那些概念——agent、tool、memory、workflow——你全都见过原型了。到那时候框架才是工具,而不是黑盒。
这个迷你Agent我选择做一个“天气穿搭推荐Agent”。它具备两个工具:一个根据城市名查询天气,一个根据温度和天气状况生成穿搭建议。用户只需要说“我在某城市,明天穿什么”,Agent就会自己先查天气,再给建议,最后汇总成一段自然语言回复。
5.2 完整代码与走读
import os from openai import OpenAI client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) def get_location_weather(city: str) -> dict: """模拟查询城市天气,真实场景可替换为天气API""" fake_weather = { "上海": {"temp": 28, "condition": "多云", "humidity": 0.6}, "北京": {"temp": 15, "condition": "小雨", "humidity": 0.8}, "广州": {"temp": 30, "condition": "晴", "humidity": 0.7}, } return fake_weather.get(city, {"temp": None, "condition": "未知", "humidity": None}) def get_weather_advice(temp: int, condition: str) -> str: """根据天气生成穿搭建议""" if temp is None: return "暂时无法查询到该城市天气,建议携带外套以防万一。" advice = [] if temp >= 28: advice.append("短袖短裤,注意防晒") elif temp >= 20: advice.append("长袖或薄衬衫") elif temp >= 12: advice.append("毛衣加外套") else: advice.append("厚外套棉服") if "雨" in condition or "雪" in condition: advice.append("记得带伞") if condition == "晴": advice.append("晴天紫外线强,建议戴帽") return ";".join(advice) + "。" tools = [ { "type": "function", "function": { "name": "get_location_weather", "description": "获取指定城市的当前天气,包括温度、天气状况、湿度", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名,例如:上海"} }, "required": ["city"] } } }, { "type": "function", "function": { "name": "get_weather_advice", "description": "根据温度和天气状况生成穿搭建议", "parameters": { "type": "object", "properties": { "temp": {"type": "number", "description": "摄氏温度,如28"}, "condition": {"type": "string", "description": "天气状况,如多云"} }, "required": ["temp", "condition"] } } } ] SYSTEM_PROMPT = """ 你是天气穿搭助手。用户会告诉你城市和场景(通勤、运动、出游等)。 你必须先调用工具查询城市天气,再根据天气给出穿搭建议。 最终回答要包含:城市、天气概况、具体穿搭建议、备注。 如果天气工具返回未知城市,如实告知用户,不要虚构天气数据。 """ def run_agent(user_input: str): messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_input} ] for _ in range(3): # 最多三个推理循环,防止死循环 resp = client.chat.completions.create( model="gpt-4o-mini", messages=messages, tools=tools ) msg = resp.choices[0].message if not msg.tool_calls: # 模型认为不需要调用工具,直接返回最终回答 print(msg.content) return # 本轮可能存在多个工具调用,依次执行 messages.append(msg) for tool_call in msg.tool_calls: name = tool_call.function.name args = eval(tool_call.function.arguments) # 注意:演示用eval,生产环境用json.loads if name == "get_location_weather": result = get_location_weather(args["city"]) elif name == "get_weather_advice": result = get_weather_advice(args["temp"], args["condition"]) else: result = {"error": f"未知工具{name}"} messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": str(result) }) print("已达到最大循环次数,终止执行。") # 测试 run_agent("我在上海,明天通勤穿什么?")代码里有一个循环:模型每次返回要么是工具调用请求,要么是最终回答。只要还在调用工具,我们就继续执行并回传结果,最多循环三次。这个循环结构就是一切Agent框架最核心的“推理-行动循环”,你亲手写出来后会发现它其实不玄。
需要说明的是,示例代码里我用eval解析参数是为了演示简洁,实际项目必须用json.loads等安全方式,防止任意代码执行。
5.3 试跑结果与几个新手必踩的坑
跑一下这个Agent,用户输入“我在上海,明天通勤穿什么?”模型的推理过程大概是:先调用get_location_weather("上海"),拿到温度28度、多云;然后调用get_weather_advice(28, "多云"),得到“短袖短裤,注意防晒;多云天气舒适”;最后模型归纳为:“上海明天多云,气温28度左右,通勤建议穿短袖短裤或透气衬衫,注意防晒。”整个过程的中间步骤你都看得见。
新手在自己写类似Agent时,最常见的问题有三个:
第一,没有处理多个工具调用。上面的例子里模型可能在同一轮返回两个tool_call。很多人只处理第一个,第二个被丢掉,结果模型明明查了天气却没有得到建议文本,回答就会缺一半。代码里我写了for tool_call in msg.tool_calls,就是防这个。
第二,API key配置错误或模型名写错。用os.getenv前记得确认环境变量已经设置,报接口错误时第一时间检查模型名是否在你账号权限范围内。
第三,模型返回的arguments不是合法JSON。大多数时候没问题,偶尔模型会返回带换行或注释的文本。生产环境建议解析失败后让模型重新返回一次,或者把这段错误信息原样回传,让模型自己修正。
把这些坑都踩完、修完,你的入门阶段正式结束,可以接触框架了。
6. 什么时候该学框架,以及怎么选框架
6.1 先对着清单自测,再谈学框架
每个来问我“该学哪个框架”的人,我都会先问他四个问题。如果四个问题里有三个答不上来,我会建议他再等一等,回炉补基础。
| 自测问题 | 过关标准 |
|---|---|
| 能解释完整工具调用链路吗 | 可以说清从请求到tool_call到执行函数再回传结果的完整流程 |
| 能不用框架写多轮工具循环吗 | 能独立实现一个带最大循环次数和错误处理的Agent |
| 能设计带节点和兜底的工作流吗 | 能把一个任务拆成步骤、状态和失败分支 |
| 能独立排查Agent跑偏问题吗 | 能从Prompt、工具定义、模型选项三个方向定位问题 |
如果你已经能做到前三条,框架对你来说就是一个省时工具,学起来会很快;如果都做不到,框架反而会成为一个巨大的认知负担。很多人的痛苦不是框架难用,而是基础没到位却直接上了同步带,结果在框架抽象层和底层原理之间两头抓瞎。
6.2 框架选型的四个判断维度
真到选框架的时候,我的建议不看GitHub Star数,看下面四个维度。
| 维度 | 判断标准 | 推荐偏好 |
|---|---|---|
| 抽象层级 | 框架是否把关键流程抽象得看不清 | 选择封装最小、可配置项可见的框架 |
| 调试能力 | 能否在日志里看到完整Prompt和完整工具调用记录 | 必须有,否则线上排障全靠猜 |
| 文档完整度 | 是否有清晰的入门教程和API参考 | 文档差的框架再火也别碰 |
| 底层可控性 | 是否允许自己接管某一步逻辑 | 能修改、能替换、能注入回调的优先 |
还有一个非常实用的判断办法:打开框架的源码,看三处——模型调用处、工具分发处、对话循环处。如果你能看懂这三处在干什么,这个框架就是你能驾驭的;看不懂,说明框架的抽象层级超出你当前的能力范围,换一个更简单的或者继续补基础。
6.3 用框架之后,也不是复制粘贴就结束
最后想破除一个执念:框架不是免学底层文档的通行证。用框架只是让你不用重复造轮子,但该懂的概念一个都逃不掉。
我见过一些同学,用框架跑通Demo后,遇到问题和当初不用框架时一样迷茫——加了自定义工具没反应,第一反应不是看日志,而是去群里问“有没有遇到过同样问题的”。真正高效的排查路径是:打开框架日志,看Agent当前执行到哪个节点、模型拿到了什么Prompt、工具返回了什么内容。能在这一步沉住气的人,学Agent的速度远快于那些只会改配置的。
框架的学习终点,本质上还是回到第一二课的内容:Prompt写得好不好、工具定义清不清楚、工作流设计完不完善。框架只是把你以前手写的循环包起来了,你该会的判断力一条都少不了。
7. 我的一点个人体会
带过几个人之后,我把入门路径固定成了五步:Prompt工程,函数调用,工作流设计,手写迷你Agent,最后才看框架。每一步的过关标准都很简单——Prompt能稳定输出结构化结果不再乱变;函数调用能讲清链路并手写循环;工作流能画出节点和兜底分支;迷你Agent能独立调试;玩框架能读懂源码关键位置。
坦白说,这条路比我当年直接啃框架要花更长的时间,但它的收益是实打实的:你积累的不是某个框架的小技巧,而是一套可以迁移到任何框架、任何模型、任何业务场景的系统设计能力。转行的人尤其要想清楚,行业要的是能解决问题的人,不是能背框架配置的人。
如果这篇文章能给你一个明确的行动清单,我的目的就达到了:今天回去先别急着下载框架,用你手上的模型API写一个带两个工具的迷你Agent,把那段循环跑通。跑通了,你会比刷十篇教程都踏实。