两个多月前,有位读者给我留言:“我已经能把大模型API调通了,但总觉得自己写的是聊天机器人,离AI Agent还差着十万八千里,到底差在哪?”这个问题最近被问得特别频繁,所以我决定用一整篇文章把它讲透。在Python生态里做AI Agent实战开发,并不是去套某个神秘框架,而是要真正理解一个智能体如何感知环境、调用工具、处理反馈,并在这套循环里做出稳定决策。这篇文章适合已经会基本Python、看过一点大模型接口文档、但还没完整搭过智能体的朋友。你会看到一个最小可用的智能体,是如何一步步从想法变成可执行代码的。
1. 为什么说“能调通大模型API”不等于“拥有智能体”
1.1 聊天机器人一直在“等你说下一句”
先抛结论:很多人对AI Agent的第一印象是“功能更强的聊天机器人”,这个类比其实很误导。你调通大模型API之后,默认交互形态是请求→回复的问答闭环:用户问一句,模型答一句,然后流程结束,等待下一次提问。整个过程里没有目标、没有行动、没有反馈修正。
智能体的核心词根是“agent”,直译就是“能主动做事的行为体”。它至少要有三个区别于聊天机器人的特征:有目标、能行动、会迭代。目标决定它要达成什么;行动决定它调什么工具、拿什么数据;迭代决定它拿到结果之后是否继续思考、纠正、逼近目标。
用一个生活类比来解释:聊天机器人像一台自动售货机,你投币选饮料,它吐一罐出来,交互到此结束。智能体像外卖配送调度员,你告诉他“下午三点前把货送到”,他不会原地等你追问,而是自己查库存、看路线、评估天气、选择配送方式,然后完成派单。如果路上出现异常,他还会主动调整方案。这个“自动售货机”和“配送调度员”之间的差距,就是Agent开发真正要做的事。
1.2 几个高频名词的边界,先厘清再说
我经常在社区看到有人把Agent、工作流、RAG这几个概念混着用,这里顺手梳理一下:
| 概念 | 核心特征 | 典型形态 |
|---|---|---|
| 大模型 | 文本生成模型,本身不做决策,也不调工具 | 文本输入输出 |
| 工作流 | 人预先定义好的固定步骤,模型只做其中一环 | 规则引擎+模型 |
| RAG | 从外部知识库检索信息后再交给模型生成回答 | 文档问答 |
| Agent | 模型自主规划步骤、调用工具、观察结果、迭代推进 | 工具调用+循环 |
Agent和工作流最核心的区别在于“决策权放在哪里”:工作流的走法在代码里写死,Agent的走法由模型每一步自主决定。真实项目里两者经常混用,固定流程用工作流,需要随机应变的部分交给Agent。另外补充一个认知:Agent并不是新理论,“感知-决策-行动”的闭环在传统人工智能和控制论里存在了几十年,大模型的加入只是让“决策”这一步从规则判断升级成了自然语言推理,普通开发者才真正接得住。
1.3 从“先想后做”到“边想边做”
早期Agent实现常采用“先规划后执行”的流程:Agent第一步把任务拆成子任务清单,第二步按清单逐项调用工具,最后汇总。这种模式的问题是,一旦执行中发现规划不合理,重头再来成本很高。
目前更主流的是ReAct模式,也就是推理与行动交叉进行。每一步模型先写一段思考,比如“用户要算送达时间,我需要先查出订单发出时间”,然后发起一次工具调用,拿到结果之后再思考下一步。翻译成人话就是:Agent不再等到把所有事情都想清楚才动手,而是边想边做,做一步看一步。
ReAct之所以流行,是因为现实任务很少能一次性规划到位。比如用户问“这个订单什么时候到”,你需要先知道订单号,再知道从哪里发出、发往哪里,然后才能估算时效。中间任何一个环节信息缺失,Agent都要临时补一个动作去查。这种动态补缺的能力,是问答式API给不了你的。
2. 选好第一个场景比学会框架更重要
2.1 为什么我不建议你直接写“万能个人助理”
很多人的第一个Agent想法是“像市面上主流助手一样什么都干”。愿望很好,但作为第一个练手项目,几乎必崩。原因很简单:万能助理面对的是开放域问题,要同时覆盖聊天、搜索、文件操作、日程管理、邮件、提醒等十几个能力域,每个域的工具都要处理权限控制、状态管理和错误恢复,工程量一开始就是地狱级。
更合理的路径是先做一个“垂直小助手”:边界清晰、术语固定、动作种类少、失败影响可控。我帮人选场景时常用两个标准:第一,任务的输入输出是不是结构化的,订单号、时间、地点这类信息比“帮我写篇文章”好处理得多;第二,能不能明确判断任务成没成功,有明确答案比开放式回答好验证得多。
2.2 候选场景对比:为什么最后选了物流调度助手
我准备过几个候选场景做对比,也顺便展示一下选型时真正应该关注什么:
| 候选场景 | 工具复杂度 | 验证难度 | 翻车成本 | 综合推荐 |
|---|---|---|---|---|
| 文章摘要助手 | 低 | 中 | 低 | 适合热身 |
| 数据分析问答 | 中 | 中 | 中 | 适合进阶 |
| 物流调度助手 | 中 | 高 | 低(模拟数据) | 适合第一个完整项目 |
| 智能日程管理 | 高 | 低 | 高(会误发消息) | 不适合入门 |
最终我选的是模拟环境里的“调度助手Demo”。它接收类似这样的请求:“订单SO-20250321-002预计几点到城市乙?如果不是很急,能不能不安排加急?”这类任务需要Agent查订单、查路线时效、判断优先级,再给出调度建议,既要调用多个工具,又要组合信息做推理,但所有数据都是模拟的,写错了也不会造成真实损失。
2.3 把目标压缩成一句话,后面所有代码才有方向
动手写代码之前,我用一句话定义了Demo目标:输入一段自然语言物流问题,Agent通过调用三个内部工具获取真实数据,基于数据推理并输出可执行结论;若信息不足,必须主动追问,绝不编造。
这句话锁定了四个约束:一是工具范围固定,只有三个;二是回答必须基于工具返回值;三是允许发起追问;四是禁止编造。“禁止编造”这一点尤其重要,它决定了系统提示词怎么写、主循环要不要加校验步骤。这也是为什么我说,先压缩目标再写代码,比先装环境再想做什么要高效得多。如果目标不能用一句话说清楚,说明场景还没选明白。
3. Agent运行机制拆解:工具、记忆与循环如何协同
3.1 工具层:函数调用到底是怎么运作的
工具层是智能体的手脚。在这个Demo里,工具就是三个普通Python函数。问题在于:模型怎么知道用户想调哪一个?答案是函数调用机制,不同平台叫法略有差异,本质一致。你先把每个函数的名字、参数、描述,按约定的JSON Schema格式发给大模型,模型就会在输出里返回一个“想要调用某函数”的结构化结果。它不会真的替你执行。
这里必须澄清一个常见误解:大模型不会在你的进程里执行函数,它只是“推荐一个函数调用”。真正执行的是你的代码。主循环拿到模型输出的函数名和参数后,自己调用Python函数,再把返回值以消息形式发回给模型。模型看到返回值之后,才能结合原始问题组织最终回答。
关于工具描述写多细,我的经验是:参数说明一定要给示例值,否则模型很容易传错格式。比如“出发时间”只写“depart_time: 出发时间”,模型可能传“下午1点”而不是“13:00”;如果写上“24小时制字符串,示例13:00”,准确率会明显提升。
3.2 记忆层:短期会话与长期召回的分工
记忆层回答的是“Agent还记不记得之前聊了什么”。最简单的是短期记忆:把用户问题、模型回复、工具结果全放进消息数组,每次都整体传给大模型,相当于让模型重新读一遍聊天记录。代价是token消耗随轮数线性增长,对话一长成本很可观。
长期记忆则是把有用的历史信息抽出来存成外部索引,最常用的是向量数据库。下次用户带来类似问题时主动召回相关内容,作为上下文的一部分。这个Demo初级阶段用的是短期记忆加一个小的人工缓存,到后面才扩展成长记忆。新手不要一上来就上向量库,先把消息数组玩明白,理解“上下文长度就是记忆边界”,再谈持久化。
3.3 主循环:让Agent“边想边做”的裁判机制
把所有组件串起来的,是一个几十行的循环,逻辑上只有四步:发请求、看回应、执行工具、反馈回模型。伪代码如下:
- 把系统提示词、用户问题和历史记录拼成消息数组
- 发给大模型,带上工具Schema
- 判断模型返回内容里有没有工具调用请求
- 有工具调用 → 执行对应函数 → 把结果加进消息数组 → 回到第2步
- 没有工具调用 → 把最终内容返回给用户,结束循环
这段流程对应着ReAct模式:模型每次的思考留在消息数组里,工具的观察结果也留在消息数组里,循环就是“边想边做”的实现载体。这里最容易出问题的不是单步逻辑,而是循环终止条件:如果模型一直想调用工具、迟迟不产出最终答案,代码会无限跑下去。所以主循环必须设最大步数上限。
3.4 框架与手写:Agent编排脚手架到底帮了什么
初学者最容易问:直接用现成的Agent编排框架不是更快吗?我的看法是:框架适合生产项目里快速整合模块,但不太适合学习第一个Agent。框架把工具调用、记忆管理、执行器逻辑都封装在黑盒里,一旦出问题,你很难判断是模型输出错、工具参数错、还是框架缓存错。
我的实践顺序是:先用原生API手写一轮主循环;跑通后,再去看框架源码里对应模块,这时你能很快看懂它封装的原因;最后再决定要不要引入框架。跳过手写直接上框架,等于学开车直接开自动驾驶,翻车了都不知道该查哪个部件。
4. 从0到1实现你的第一个智能体:物流调度助手
4.1 环境准备与依赖安装
我用的是Python 3.11,3.10到3.12之间都问题不大,别用太旧的版本。建议创建虚拟环境再装依赖:
python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装你选用的模型平台SDK(llm_sdk为占位,按实际平台文档替换包名) pip install llm_sdk python-dotenvllm_sdk只是占位符号,请你按实际选择的大模型平台文档安装对应SDK。真正要紧的是这个前提:所选模型必须支持函数调用或工具调用能力,否则后面的机制无从谈起。python-dotenv用来读取.env配置文件,把API密钥写在环境变量里,不硬编码进源码。
.env文件大致这样:
API_KEY=your_api_key_here BASE_URL=https://your-compatible-endpoint MODEL_NAME=your-model-name再写一个config.py统一加载配置。密钥绝对不能硬编码,这是基础习惯,但我见过太多次因为硬编码导致泄露的案例,这里必须强调。
4.2 工具层:三个最朴素的函数
工具层是整个项目里最不能被框架绑架的部分,Agent手里的每一个函数,本质都是你业务能力的映射。我这里写了三个函数:query_order查订单、calc_eta算时效、gen_schedule生成调度建议。
# tools.py from datetime import datetime, timedelta ORDER_DB = { "SO-20250321-001": {"from_city": "仓A", "to_city": "城市甲", "depart_time": "09:30", "priority": "normal"}, "SO-20250321-002": {"from_city": "仓A", "to_city": "城市乙", "depart_time": "13:00", "priority": "high"}, "SO-20250321-003": {"from_city": "仓B", "to_city": "城市甲", "depart_time": "10:00", "priority": "normal"}, } def query_order(order_id: str) -> dict: """按订单号查询订单基础信息""" return ORDER_DB.get(order_id, {"error": f"未找到订单 {order_id}"}) def calc_eta(from_city: str, to_city: str, depart_time: str) -> dict: """根据出发地、目的地、出发时间估算送达时间""" route_hours = {"仓A_城市甲": 4, "仓A_城市乙": 6, "仓B_城市甲": 3, "仓B_城市乙": 5} key = f"{from_city}_{to_city}" hours = route_hours.get(key, 5) eta = datetime.strptime(depart_time, "%H:%M") + timedelta(hours=hours) return {"route": key, "hours": hours, "eta": eta.strftime("%H:%M")} def gen_schedule(order_id: str) -> dict: """根据订单信息生成调度安排""" info = query_order(order_id) if "error" in info: return {"error": f"无法生成调度安排:{info['error']}"} schedule = (f"订单{order_id}由{info['from_city']}在{info['depart_time']}发出," f"运往{info['to_city']},优先级{info['priority']}," f"送达后完成入库") return {"schedule": schedule}这三个函数刻意写得很朴素,它的返回结构是dict,因为结构化结果会让模型解析起来更稳定。错误信息写得明确,比如“未找到订单xxx”,模型看到这个反馈就能决定下一步是改参数重试,还是直接告知用户。
4.3 工具注册表与模型调用封装
接下来把工具暴露给大模型。我需要一个字典完成函数名到函数对象的映射,用于执行;还需要一个符合平台要求的工具Schema列表,用于告诉模型有哪些函数可调。
# agent_core.py import json from tools import query_order, calc_eta, gen_schedule TOOL_MAP = { "query_order": query_order, "calc_eta": calc_eta, "gen_schedule": gen_schedule, } TOOL_SCHEMAS = [ { "type": "function", "function": { "name": "query_order", "description": "查询订单基础信息", "parameters": { "type": "object", "properties": { "order_id": { "type": "string", "description": "订单号,示例:SO-20250321-001" } }, "required": ["order_id"] } } }, { "type": "function", "function": { "name": "calc_eta", "description": "估算送达时间", "parameters": { "type": "object", "properties": { "from_city": {"type": "string", "description": "出发仓,示例:仓A"}, "to_city": {"type": "string", "description": "到达城市,示例:城市甲"}, "depart_time": {"type": "string", "description": "出发时间,24小时制字符串,示例:13:00"} }, "required": ["from_city", "to_city", "depart_time"] } } }, { "type": "function", "function": { "name": "gen_schedule", "description": "基于订单信息生成调度安排", "parameters": { "type": "object", "properties": { "order_id": { "type": "string", "description": "订单号,示例:SO-20250321-001" } }, "required": ["order_id"] } } } ]写参数描述时,我格外强调示例值。模型很容易把“下午1点”传成“1:00 PM”或“13点”,Schema给得越具体,后面调试越省事。TOOL_MAP负责把模型推荐的函数名转成真实调用,执行部分则由主循环处理。
模型调用封装我写成一个llm_chat函数,把消息数组和工具列表传给接口,temperature固定为0.2。Agent不是创意写作,不需要发散性,温度低一点能让结果更稳定。
# config.py import os from dotenv import load_dotenv load_dotenv() # llm_sdk 为占位,按实际选择的大模型平台替换导入方式 from llm_sdk import Client client = Client(api_key=os.getenv("API_KEY"), base_url=os.getenv("BASE_URL")) def llm_chat(messages, tools=None): resp = client.chat.completions.create( model=os.getenv("MODEL_NAME"), messages=messages, tools=tools or [], temperature=0.2, ) return resp.choices[0].message4.4 主循环:最精简的Agent骨架
主循环是整个Demo的发动机,它保证“模型提需求、代码执行、结果反馈、模型再思考”这个闭环正确运转:
def run_agent(user_input: str, max_steps: int = 5): messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_input}, ] for step in range(max_steps): response = llm_chat(messages=messages, tools=TOOL_SCHEMAS) messages.append(response) tool_calls = getattr(response, "tool_calls", None) if not tool_calls: return response.content for call in tool_calls: fn_name = call.function.name args = json.loads(call.function.arguments) print(f"[step-{step}] call {fn_name}({args})") result = TOOL_MAP[fn_name](**args) messages.append({ "role": "tool", "tool_call_id": call.id, "content": json.dumps(result, ensure_ascii=False), }) return "已超过最大执行步数,停止运行。请简化需求或补充信息后重试。"这段代码是整个Demo里最短的部分,但信息量很大。for循环天然限制了最大步数,这是防失控的第一道保险。每次拿到模型响应,先检查有没有工具调用请求;没有就说明模型准备直接回答,可以结束。有就执行,执行结果以tool角色消息回到消息数组,让模型看到观察结果。
中间那行print是刻意留下的调试口子,控制台能看到Agent每一步在做什么、调了哪个函数。别小看它,下面的排查工作全靠它。
4.5 提示词:决定Agent像不像样的第一要素
主循环只是骨架,系统提示词才是“性格”。我给的提示词长这样:
SYSTEM_PROMPT = """你是物流调度助手。你的任务是根据用户问题,逐步调用可用工具获取真实信息,再给出结论。 可用工具: - query_order(order_id):查询订单信息,返回订单的发出仓、目的地、出发时间、优先级 - calc_eta(from_city, to_city, depart_time):估算送达时间,depart_time使用24小时制字符串如"13:00" - gen_schedule(order_id):基于订单信息生成调度安排 工作规则: 1. 回答前必须先用工具确认信息,禁止编造订单号、时间或路线 2. 一次只调用一个工具,观察结果后再决定下一步 3. 获取足够信息后,立即用一句话给出结论,不要重复调用工具 4. 如果工具返回"未找到订单",应坦诚告知用户,并提示提供正确订单号 """提示词写法跟写接口文档很像:告诉模型你有什么、怎么用、边界在哪。四条规则都不是摆设:第一条压制幻觉,第二条让行为可控,第三条给循环一个明确的出口,第四条规定了错误处理方式。
4.6 端到端跑通一个任务
体系搭好之后,拿一个真实输入测试:“订单SO-20250321-003什么时候能送到城市甲?着急吗?”
调用run_agent之后,控制台会打出类似这样的调用链:
[step-0] call query_order({'order_id': 'SO-20250321-003'}) [step-1] call calc_eta({'from_city': '仓B', 'to_city': '城市甲', 'depart_time': '10:00'})最终模型输出类似:“订单SO-20250321-003从仓B开往城市甲,10:00发出,预计13:00送达,优先级正常,不算最急。”这说明Agent完成了完整闭环:先查订单拿基础信息,再测算时效,最后基于数据回答。
如果用户输入缺了必要信息,比如“帮我算下某订单什么时候到”,Agent在query_order查不到订单后,会根据错误信息主动追问“请提供正确的订单号”,而不是编一个答案。这正是前面强调的“信息不足必须追问”原则在起作用。
5. 把任务跑顺并不等于跑对:测试阶段最典型的4次翻车
5.1 翻车一:Agent无限循环调同一个函数
第一个版本的提示词没有写“获取足够信息后立即给结论”,结果遇到含糊任务时,控制台出现一连串calc_eta调用,参数几乎一样,像卡了带一样重复执行。原因很快定位:模型每次拿到相同的工具结果,如果上下文里没有“可以结束”的明确信号,它就倾向于“再确认一次”。
修复分两处:第一,在提示词第三条明确写出“获取足够信息后立即给出结论,不要重复调用工具”;第二,主循环的max_steps设置为5,双重保险。复盘时我发现这类问题几乎都源于提示词没给模型“出口暗示”。模型不是不想停,是你没告诉它什么时候可以停。
5.2 翻车二:工具参数传错,根因在Schema描述
另一个高频翻车是参数错乱。有一次调试,模型调用calc_eta时把出发时间传成了“下午1点”,datetime解析直接抛异常。我当时第一反应是模型不行,拉出日志才发现是Schema描述写得模糊,只写了“出发时间”,模型当然按人类语言习惯传值。
修复方式是:Schema描述里明确“24小时制字符串,示例13:00”,并在工具函数入口加一层异常捕获,解析失败时返回“出发时间格式应为HH:MM”。模型看到这个错误反馈会自动修正参数重试。这个经验后来我复用到所有工具上:描述里给示例,比任何提示词规则都管用。
5.3 翻车三:对话长了之后,Agent忘了最初目标
测试连续提问时发现了记忆衰减问题:用户先问“只关心今天的订单”,几轮之后Agent就开始计算明天的单子,因为上下文里“今天的订单”这个约束被后续对话冲淡了。这是短期记忆的天然短板,模型对越靠后的内容权重越高。
第一轮修复是在每轮对话末尾把当前核心约束回写成“当前目标”消息,重新塞进上下文,相当于给模型贴了张便利贴。要更彻底的话,就得引入外部长期记忆,把用户偏好存成索引,在下一个任务开始前主动召回。
5.4 翻车四:模型“理直气壮”地编造工具结果
最危险的一次翻车是幻觉:我问“订单SO-20250321-999在哪”,Agent在工具返回“未找到订单”之后,仍然煞有其事地回答“该订单正从仓A运往城市甲,预计下午3点送达”。这完全是编造,而且编得很流畅自然。
我检查调用链后发现,提示词第一条虽然写了禁止编造,但模型依然可能在被工具结果打脸时强行圆场。修复方向是细化错误处理规则:“如果工具返回未找到订单,直接告诉用户未找到,并请提供正确订单号,禁止补充任何额外信息。”同时我在主循环里增加了后置判断:只要最终回答中不含任何工具返回的关键信息,比如订单号、时间,就输出“异常终止,建议重新提问”。虽然简单,但能拦下一部分幻觉。
这套思路后来被我总结成“回答必须能溯源”原则:语言可以自由组织,但每个事实性结论都要能被某次工具结果证明。这是Agent从“会聊天”到“办正事”的分水岭。
5.5 调试三件套:打印调用链、模拟工具、监控成本
最后把调试经验收拢成三件事:
- 主循环里那行print是开发期的生命线,正式环境可以换成结构化日志,但开发期必须保留。
- 给每个工具写单元测试,把合法和非法输入都测一遍。遇到问题先判断是模型决策错还是工具实现错,别一上来就怪模型。
- 统计每轮消耗的token和调用次数,给单次会话设置成本上限。等月底账单出来再意识到失控,那就晚了。
6. 进阶:这就是智能体真正落地的分水岭
6.1 长期记忆:让Agent记住“这个用户总问哪条线”
小Demo跑通之后,第一件值得做的事是加长期记忆。最简单的方案:把每次会话结束时的关键信息,比如用户常问线路、常用仓、偏好,提取成短文本,向量化存入本地索引。下一次会话开始时,用当前问题做一次相似度搜索,召回最相关的几条历史记录,拼进系统提示词。
在模拟项目X里,效果很明显:用户第二次来问同一线路时效时,Agent已经能说出“您上次问过仓A到城市乙,这次同样按6小时估算”,体验感和一次性聊天完全不同。但长期记忆也会带来新问题:召回不准会误导Agent,隐私数据不能随便存。起步时建议先做按用户ID隔离的轻量记忆,不要做全局共享记忆。
6.2 多Agent协作:不要把希望压在单个模型身上
把一个大任务拆给多个Agent是另一个进阶方向。比如拆成“需求理解Agent”和“调度决策Agent”:前者把用户口语化问题转成结构化需求,后者只负责基于结构化需求优化排班。两个Agent之间通过JSON协议通信,而不是自由对话。
这里最核心的工程经验是:Agent之间的通信协议越简单越好。如果你发现两个Agent在来回传递一大段自然语言,说明拆分粒度不对,应该把自然语言交互限定在面向用户的那一端,内部全部走结构化消息。多Agent的收益来自单一职责加清晰接口,不是模型数量堆出来的智能。
6.3 回归测试集:改一次提示词,把20个老问题重跑一遍
做Agent开发最痛苦的是改提示词导致“修好一个旧问题、弄坏三个老功能”。我后来强制自己维护了一个20条问题的回归集,每次改完提示词或工具逻辑,就把这20条全部跑一遍,统计通过率。虽然全量跑要花十几分钟,但这十几分钟挡住了大量线上回归事故。
回归集里的问题要刻意覆盖三类:正常输入、缺参数输入、错误输入。通过标准不能是“回答内容差不多”,而要检查关键字段是否出现在最终回答里。如果要求答案能溯源,就让脚本自动比对回答里的订单号、时间值与工具返回是否一致。
6.4 落地前最后一道闸:人工确认与权限最小化
如果这个Demo要接真实业务,我建议至少加两道闸。第一道是高风险动作确认机制:gen_schedule这类会产生实际业务影响的动作,工具执行后不要直接返回结果,而是返回“待确认状态”,由用户点击确认后才真正落地。第二道是权限最小化:Agent能调用的工具必须走白名单,绝不能让它拿到一个自由执行的Shell或万能HTTP客户端,否则一次提示词注入就能酿成大事故。
此外还要做输入长度限制和敏感信息过滤,日志里避免记录真实身份信息。这些听起来跟Agent没关系,但真实项目上线后,被讨论最多的往往是这部分。
最后分享一点个人体会。我在跑通第一个智能体之后,很长时间都在反复想一个问题:为什么同样的模型、同样的工具,有的人写出来像老练的业务员,有的人写出来像复读机?答案慢慢落在这三件事上:工具是否干净可靠、提示词是否把边界说透、主循环是否设好了止损条件。这三件事没有一件需要高深理论,却决定了Agent从“能跑”到“好用”的全部距离。如果你也正在搭自己的第一个智能体,我的建议是:挑一个两小时能做完的垂直任务,把主循环手写一遍,然后认真写十条测试问题。做完这些,你对Agent的理解会比看十篇框架文档深得多。