☰
AI Agent学习路线图:从零手写工具调用闭环
2026/10/20 8:24:31 网站建设 项目流程

这两年AI Agent这个词,几乎天天出现在技术社区的文章标题里,也越来越多地出现在各家的招聘需求里。打开讨论区,有人晒出用Agent自动处理报表的截图,有人靠着对工具调用的理解拿下了面试机会;与此同时,大量只写过普通业务代码的程序员心里都会冒出一个疑问:这东西到底该怎么学?网上搜出来的内容,要么把Agent讲得神乎其神,要么列一长串书单和课名,真正能让你从一段可运行的代码开始、一步步明确方向的内容非常少。

这份路线图就是来补这个缺口的。它不是概念科普合集,也不是鸡汤式的时间表,而是我自己从零到能落地几个Agent项目之后,回头整理出来的一条最短路径。适合谁?会写一点Python、了解HTTP和JSON、但对Agent基本空白的开发者。你现在做前端、后端、测试还是运维都不重要,Agent这套思维方式的迁移对谁都公平。整篇文章的目标非常具体:让你今天照着敲代码,就能跑通一个带工具调用能力的Agent,并且搞清楚接下来的每一步该练什么、避什么坑。建议先收藏,跟着节奏走。

1. 先拆清楚AI Agent是什么:学之前先建立正确心智

1.1 AI Agent和普通程序的核心区别

普通程序是开发者预先写好所有逻辑分支:if A就执行B,else就执行C。Agent的思路换了一套:开发者只负责设定目标、提供工具清单和行为边界,具体每一步做什么决策,由大模型在运行时根据上下文实时判断。

我给新人的一句话解释是:普通程序是在执行一串你早已画好的流程,Agent是让大模型充当一个可以自主选路、换工具、自我纠错的分发中心。你负责建工具箱,Agent负责决定什么时候用哪个工具,以及下一步该怎么办。

Agent的几块核心构件要心里有数:

  • 大模型:负责理解目标、拆解步骤、生成自然语言,是整个系统的决策大脑。
  • 工具:模型的手和脚,可能是查询函数、HTTP接口,也可能是写文件的函数。模型不直接执行,只负责发起调用建议。
  • 记忆:为了让模型记住前文说过什么,你需要把历史对话一起塞进上下文,必要时压缩成摘要。
  • 循环机制:模型不断根据新反馈重新做决策,直到任务完成。这个“决策—执行—反馈”的循环就是Agent的生命线。

生活化类比:普通程序像是你给厨师写好的菜谱,每一步都是定死的。Agent则是你请了一位有经验的厨师,你告诉他今天家里来客、口味清淡、预算有限,他自己决定菜单,买不到食材也知道换替代方案。菜好吃不好吃,不再取决于菜谱写得多细,而取决于你描述目标的能力,以及你给他准备了什么厨具。

1.2 为什么现在学Agent是划算的

三件事在最近一段时间同时成熟,直接拉低了学习门槛。

模型能力够用了。现在的商用模型API和开源模型在指令理解、逻辑推理、长文本记忆上已经能覆盖大量日常任务,你不需要懂训练细节,会用接口就行。接口标准了。以前各家模型平台的工具调用协议不统一,现在基本收敛成了同一套JSON风格的结构化输出约定,学一次到处能用。生态起来了。文档、案例、周边工具在爆发式增长,踩坑资料也比两年前丰富太多。

再看需求端。很多企业想把文档问答、报表生成、流程审批这类重复性工作交给Agent去跑,招聘描述里“熟悉Agent应用开发”“有工具调用落地经验”的说法越来越常见。需求是真的,能干活的人也确实缺。现在入场,性价比很高。

1.3 进阶路线图一览:四个阶段四个产出物

阶段目标核心动作产出物
第1周跑通大模型API调用熟练掌握messages结构、请求与响应命令行对话小程序
第2周掌握Prompt工程三大动作角色、格式约束、少样本示例能稳定输出JSON的Demo
第3周理解并手写工具调用闭环手撸Agent循环,不依赖框架带查询功能的可用Agent
第4周起记忆管理与评测迭代上下文压缩、测试集回归可落地的客服/报表Agent原型

这个表建议直接存下来,后面每个阶段你都可以回来对照,看自己到底卡在哪一格。

2. 前置知识清单:大模型API、Prompt和工具调用

2.1 大模型API调用,会发一个JSON请求就够了

调用大模型API,本质上是向一个HTTP接口发送JSON,再接收JSON。对后端开发者来说没什么新鲜的;对前端或非科班转行的朋友,也别被API三个字母吓到。

最基本的使用方式这样写:

import requests def chat_once(user_content, system_content=""): messages = [] if system_content: messages.append({"role": "system", "content": system_content}) messages.append({"role": "user", "content": user_content}) payload = { "model": "你选的模型版本名", "messages": messages, "temperature": 0.7, "max_tokens": 800, } resp = requests.post( "模型的接口地址", headers={"Authorization": "Bearer 你的密钥"}, json=payload, timeout=60, ) return resp.json()["choices"][0]["message"]["content"] print(chat_once("用一句话介绍你自己,不要超过20个字"))

重要的是理解messages这个数组的结构,它里面可以放四类角色:system负责设定角色与行为边界;user是用户输入;assistant是模型的历史回复;tool是工具执行结果的反馈。所谓多轮对话,就是每次把整段messages一起发过去,模型基于完整上下文生成新回复。没有魔法,就是把上下文带上。

温度参数也要顺手调一下。temperature越高,回复越随机发散;越低,输出越保守稳定。做Agent的时候一般建议设低一点,比如0.2到0.3,因为工具调用场景里,更在意稳定而不是花哨。

2.2 Prompt工程三板斧:角色、格式、样例

很多人觉得Prompt工程玄学,其实核心就三个动作,掌握它们就能应对绝大多数场景。

第一是给角色和边界。你不用长篇大论,但一定要说清楚“你是谁、能干什么、不能干什么”。比如系统提示词写成:你是一个内部系统的IT支持助手,只能回答与办公系统使用有关的问题;与办公系统无关的提问,请统一回复无法处理。这样的指令比“你很强大”管用得多。

第二是约束输出格式。想让模型输出标准JSON,直接写:请根据用户问题输出JSON,格式为{"字段": "值"},不要输出其他内容。如果模型不听,再追加一个少样本示例强化。

第三是给示例。模型从样例中学得很快:

用户:帮我查一下Bug单BUG-1024的处理状态 助手:{"tool": "query_bug", "args": {"bug_id": "BUG-1024"}} 用户:你好啊 助手:{"tool": "none", "args": {}}

把大模型当成新来的实习生,这三个动作分别对应:告诉他岗位职责,告诉他交付格式,给他一份参考案例。写得越清楚,他的执行力就越接近你的预期。

2.3 工具调用到底是谁在干活

工具调用是Agent最核心的概念,我第一次看教程的时候也绕了很久,这里用五步说透:

  1. 你提前把可调用的函数做成清单,写明函数名、功能描述、参数结构。
  2. 把清单和用户的对话一起发给大模型。
  3. 模型如果认为需要某个函数,会在返回结果里生成一个tool_calls结构,告诉你它想调用哪个函数,并给出参数。
  4. 你的程序收到这个结构后,自己执行对应的真实函数,把结果以一条tool消息放回历史。
  5. 模型看到工具结果,生成最终回答。

这里有个关键误区必须说清楚:模型本身并不执行任何代码,它只是提出调用建议。真正开关数据库、发HTTP请求、写文件的,仍然是你写在程序里的真实函数。这个分工决定了你始终握着执行权和审计权,Agent再聪明,权限也是你给的。

工具描述直接影响调用准确率。很多新手调不好Agent,第一反应是换更强的模型,其实更常见的问题是工具description写得像废话,模型根本不知道什么时候该用、为什么用。

3. 手写最小Agent闭环:今天就能跑通的实战代码

3.1 选型与准备工作

第一阶段的唯一目标,是手写一个可运行的最小Agent。不做UI,不接数据库,不引入任何Agent框架。技术栈就三样:Python、requests、json。

为什么先不碰框架?因为框架帮你封装了太多东西,你只调用不深究,遇到问题连从哪排查都不知道。等你自己手写过一遍再回来看框架,会发现框架的每个抽象你都能对应到实际环节,那时候框架会成为助力而不是黑盒。

模型选择上,建议先用在线API。原因是文档齐全、可观测性好、省去部署折腾,适合学习和调试。本地部署开源模型是后面优化成本时才需要考虑的事情。

3.2 五个步骤实现一个带工具调用的Agent

先写一个查天气的工具。为了让Demo专注在Agent循环本身,我这里用模拟数据,不真调第三方接口。

import requests import json API_URL = "你的模型接口地址" API_KEY = "你的密钥" MODEL = "你的模型版本名" def call_model(messages, tools=None): payload = {"model": MODEL, "messages": messages} if tools: payload["tools"] = tools headers = {"Content-Type": "application/json", "Authorization": f"Bearer {API_KEY}"} resp = requests.post(API_URL, headers=headers, json=payload, timeout=60) data = resp.json() return data["choices"][0]["message"] def get_weather(city): # 实际项目这里可以接第三方天气预报接口,这里先模拟 return f"{city}今天多云,气温23℃,东北风3级" tools = [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市的今日天气。当用户明确询问某城市天气时,必须调用本工具。", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名称"} }, "required": ["city"] } } } ] messages = [{"role": "user", "content": "明天想去云城,帮我看看云城天气!"}] max_rounds = 5 # 防止死循环 for _ in range(max_rounds): msg = call_model(messages, tools) if msg.get("tool_calls"): messages.append(msg) # 把模型的调用请求写进历史 for tool_call in msg["tool_calls"]: fn_name = tool_call["function"]["name"] args = json.loads(tool_call["function"]["arguments"]) result = get_weather(**args) messages.append({ "role": "tool", "tool_call_id": tool_call["id"], "content": str(result) }) else: print(msg["content"]) break

这段代码看着短,其实是Agent的最小完整闭环,几个关键点拆开讲。

第一个关键点:模型返回的tool_calls是一条特殊的assistant消息,必须原样追加进messages。这一步很多人会漏,漏掉之后模型就看不到“自己刚才想调什么工具”,后续逻辑必然乱。

第二个关键点:工具执行结果要用role为tool的消息放回历史,并且把tool_call_id和模型请求里的id对上号。这是模型把“调用请求”和“调用结果”关联起来的方式。

第三个关键点:不要用eval之类的方式直接执行工具名。更安全也更清晰的做法是提前准备一个函数分发字典,比如TOOL_FUNCTIONS = {"get_weather": get_weather},然后取出来调用。

第四个关键点:max_rounds一定要加。不加的话,极端情况下模型会不停调用工具,你的token账单会很难看。

3.3 验证与观察点

跑上面的代码,你会看到两轮请求:第一轮模型返回了tool_calls,建议调用get_weather;第二轮是模型拿到真实天气结果之后的最终回答。中间那一下“模型决策—程序执行—结果回填”,就是Agent循环的呼吸节奏。

多试几个输入感受一下:

  • 输入“帮我查一下云城的天气”,期望触发工具调用。
  • 输入“你好,你是谁”,期望模型直接回答,不调用任何工具。
  • 输入“先查云城天气,再告诉我适合穿什么”,期望连续两次调用:先查天气,再基于工具结果给穿衣建议。

如果模型在该调用时没调用、在不该调用时乱调用,先不要怀疑模型智商。回头检查工具描述是否足够明确,后面第5节会展开讲这个问题。

4. 进阶实战:记忆管理、多工具编排与评测闭环

4.1 记忆与上下文管理:最容易踩坑的工程问题

所谓记忆,就是我们每次把历史messages完整发给模型。模型能看到的只有这些内容,窗口之外的信息它一概不知。窗口长度是有限的,所以上下文管理就是在有限窗口里做取舍。

有三大策略,按需选用。

滑动窗口最简单,只保留最近N轮对话,超过就丢弃。代价是模型会遗忘太早的信息,比如用户在第1轮说过“我爱吃辣”,第20轮你问它推荐什么菜,它已经想不起来了。

摘要压缩是把较早的对话提炼成一段摘要,作为一条system消息放到最前面,然后截断详细历史。伪逻辑大致是这样:

# 当消息超过20条时,把最早的一部分做成摘要 if len(messages) > 20: old_part = messages[2:12] # 截取最早的一部分对话 summary = call_model([ {"role": "system", "content": "请用3行以内的文字概括以下对话的核心需求和已执行的动作。"}, {"role": "user", "content": str(old_part)} ]) # 用一条system消息承载摘要,塞回历史顶部,再截断详细历史 messages = [{"role": "system", "content": f"历史对话摘要:{summary}"}] + messages[:2] + messages[12:]

长期记忆则更进一步,把用户画像或业务知识放到向量检索库里,每次对话前检索相关片段塞进上下文。工程上建议不要一上来就上向量检索,先用滑动窗口加摘要,跑不动了再升级。我见过太多团队第一个月就引入重方案,最后运维成本比Agent本身还高。

还有个实用小技巧:在系统提示词里写一句“如果对话内容过长,请提醒用户重置会话”,它虽然简单,但实测能省下大量意外超限的排查时间。

4.2 多工具编排与条件跳转

跑通单工具之后,把工具从1个加到3个是很好的进阶练习。给Agent加上“限行查询”工具,工具描述可以这样写:

{ "type": "function", "function": { "name": "get_traffic_restriction", "description": "查询指定城市当天的机动车限行尾号。仅当用户明确询问限行、尾号、能不能开车时调用;闲聊不要调用。", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名"} }, "required": ["city"] } } }

多工具的关键不是会注册,而是学会引导模型选择。一个屡试不爽的做法是:在每个工具描述里都写明“什么时候必须调用”“什么时候绝对不要调用”。这相当于给模型画了清晰的路牌。

还要注意,不是所有决策都该交给模型。业务里有大量稳定且硬性的规则,比如某些时段禁止外部联网、某些用户角色不能触发写操作,这些直接用代码判断拦截,别指望用自然语言约束模型在所有情况下都守纪律。规则在前、模型在后,是工程上更稳的混合决策模式。

多Agent编排先别急着学。只有当单个Agent反复调优还解不动问题时,才考虑拆成规划者和执行者的子Agent结构。拆完之后通信协议、失败传递、权限边界都要重新设计,复杂度是几何级上涨的。我见过太多人还没把单Agent跑稳就去搞多智能体协作,最后连问题出在哪个环节都定位不到。

4.3 评测闭环:让Agent越改越好

Agent开发里最容易出现的错觉是“我今天又改了一版,感觉模型变聪明了”。这种主观判断没有意义,最终要靠测试集说话。

建一个最朴素的测试集,记录用户输入、期望行为、期望工具、回复关键内容,比如:

测试ID用户输入期望行为期望工具实际结果
C01云城天气怎么样调用工具get_weather通过
C02你是做什么的直接回答无失败
C03明天云城限行吗调用工具get_traffic_restriction通过

每改一次Prompt或工具描述,就把同一批case跑一遍,记录通过率。这个过程枯燥,但它会把“感觉变好了”变成可量化的数据。

调优优先级供参考:工具描述 > System Prompt > 少样本示例 > 更换更强模型。如果你的Demo工具调用通过率极低,先检查工具命名是否准确、描述是否啰嗦、参数是否有歧义,大概率不用换模型就能解决。

5. 新手最容易踩的坑:问题排查与速查表

这条路我走过的坑不算少,下面这几个是高发区,遇到直接对号入座。

第一个坑:上下文越聊越长,费用和延迟一起涨。聊天类Agent最容易犯这个错。根因是messages没有裁剪,所有历史包括工具返回全堆在请求里。解决办法就是第4节说的滑动窗口加摘要压缩,再加一个重置会话的入口。

第二个坑:工具参数解析失败。模型偶尔会返回格式不合法的JSON,甚至把JSON包在markdown代码块里。解析前先做一次清理,把首尾的```标记和换行去掉,再用json.loads。解析失败时不要干等,把错误信息写回上下文让模型修正:

try: args = json.loads(tool_call["function"]["arguments"]) except Exception as e: messages.append({ "role": "user", "content": f"工具参数解析失败,错误信息:{e},请重新生成合法的JSON参数。" }) continue

第三个坑:该调不调、不该调乱调。这个现象十有八九是工具描述和System Prompt的问题。该调不调,说明工具描述里没有写清楚触发条件;不该调乱调,说明System Prompt里缺少边界约束。尝试在描述里写明“仅当用户明确表达XX需求时才调用”,在System Prompt里加上“如果没有合适工具,直接回答用户,不要为了调用而调用”。

第四个坑:工具结果正确,但回复在编数据。这是最迷惑人的坑。你可能发现调用了正确的工具、工具也返回了正确结果,但模型回答里的数字和返回对不上。先检查工具结果有没有真的塞进messages。很多人漏掉把role为tool的消息放回历史这一步,模型压根没看到结果,它只能靠自己脑补。再就是给模型下死命令:在System Prompt里写“回答时只能引用工具结果中的信息,不要自行补充或推断数据”。

第五个坑:死循环。模型疯狂调用工具不收敛。对Agent来说,工具本身可能出错,工具结果也可能让模型误判“还需要继续调”。工程上必须在循环外面加轮次上限,超过就中断,并把最后状态记录下来。

把高频问题整理成一张速查表,贴在工位上非常实用:

现象常见原因快速解法
工具参数总解析失败模型把JSON抱进markdown代码块解析前清除```和换行
模型总不调用工具工具描述缺少触发条件强化description,补少样本示例
模型乱调用工具System Prompt缺少调用边界添加“仅当…”约束
回答在瞎编数据工具结果没放回上下文检查tool消息是否成功追加
上下文超限历史消息无裁剪滑动窗口/摘要压缩
死循环无轮次上限增加max_rounds并告警

6. 路线图之外的实践习惯:给新手的几条建议

最后分享几条不在任何教程里、但长期实践下来最受用的习惯。

第一条,先不装Agent框架,手写最小闭环至少三遍。第一遍照抄,第二遍不看代码重写,第三遍换一个工具类型再写一遍。三遍之后,你对“决策—执行—反馈”这个闭环的理解会远超看十篇框架分析文章。

第二条,学习过程中每个Demo都做成命令行可复跑的小壳子,不要一上来就做UI。命令行能把整个循环过程暴露得一清二楚,你一眼就能看到模型调了什么、返回了什么、最后怎么回答的。UI会把这些过程藏起来,反而不好调试。

第三条,用测试集代替感觉来判断模型好坏。哪怕测试集只有二十个case,也比你说“感觉今天模型变厉害了”靠谱得多。Agent这个领域变量太多,没有回归测试,你根本不知道哪一次改动是真正有效的。

第四条,多读模型平台官方文档里关于工具调用和结构化输出的章节。那里是一手信息,比看一百篇二手转发文章都顶用。文档里往往还会标注参数限制、错误码和反例,这些才是避坑的关键。

我自己最早跑第一个Agent的时候,闭着眼抄了一遍教程,结果工具结果没放回上下文,模型一本正经地给我编天气。那一刻我才意识到,Agent开发表面上是“调模型”,实质上是“管道工程”,脏活累活全在数据流转上。模型不神秘,Agent也不神秘,神秘的是那个循环。跑通它、管好它、测好它,你就能用它干很多以前需要人力才能完成的活儿。希望这份路线图能帮你少踩几个我踩过的坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询