news 2026/4/17 6:00:15

【手搓 AI Agent 从 0 到 1】第五课:让 AI 调用工具

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【手搓 AI Agent 从 0 到 1】第五课:让 AI 调用工具

📌前置知识:已完成第一课至第四课
🎯本课目标:让 AI 不仅选择动作,还能指定参数,真正调用外部能力
💡核心概念:工具接口 / 结构化工具调用 / 请求与执行分离


前言

上节课,我们让 AI 学会了做决策。

现在它能分析用户的输入,从一组选项中选出最合适的动作。比如用户说"帮我总结这篇文章",AI 选了"summarize_text"

能用。但你仔细想想——它只告诉了你"要做什么",没告诉你"怎么做"。

你的代码大概长这样:

decision=agent.decide("帮我总结这篇文章",choices=["answer","summarize","translate"])ifdecision=="summarize":summarize(???)# 总结什么?原文在哪?elifdecision=="translate":translate(???,???)# 翻译什么?翻译成什么语言?

发现了吗?选项只是个名字,缺少关键信息:

  • 没有参数。AI 说"要计算",但没说算什么、用哪个运算符。
  • 没有细节。AI 说"要翻译",但没说翻译哪段文字、目标语言是什么。

如果 AI 能把这些细节也一起告诉你呢?不是只说"我要用计算器",而是说"用计算器,42 乘以 7"——工具名称 + 具体参数,一次说清楚。

这就是工具调用。


一、第四课还差什么?

回头看第四课做了什么:

decision=agent.decide("What is 42 * 7?",choices=["answer_question","calculate","translate"])# 输出: "calculate"

AI 知道该用calculate——意图识别做对了。但接下来的事情就尴尬了:

ifdecision=="calculate":calculate(???)# 算什么?算 42×7,但 AI 没告诉你

AI 只告诉你"要计算",但具体算什么、用什么运算符,它没说

这是因为第四课的决策输出只有动作名称,没有参数。你的代码拿到了"calculate"之后,还得自己想办法从用户输入里解析出数字和运算符——又回到了第三课之前的老问题:用规则去解析自然语言。

第五课要解决这个问题:让 AI 在选择工具的同时,把参数也一并提取出来。

用户: What is 42 * 7? ↓ AI 输出: { "tool": "calculator", "arguments": { "a": 42, "b": 7, "operation": "multiply" } }

工具名称有了,参数也有了,你的代码可以直接执行,不需要再自己解析用户输入。

对比一下:

第四课第五课
AI 告诉你什么“用 calculate”“用 calculator,a=42, b=7, multiply”
参数从哪来你自己从用户输入里解析AI 帮你提取好了
下游代码要做什么if decision == "calculate": parse_and_calculate(user_input)execute(tool_call)直接执行

第四课的输出是"意图"。第五课的输出是"意图 + 执行细节"。差的就是这个"执行细节"。

两课合在一起,完整的工作流就是:AI 理解意图 → 选择工具 → 提取参数 → 你的代码执行。


二、核心原则:AI 描述意图,你控制执行

第五课最重要的设计原则,用一句话说:

AI 没有能力,你有。

工具接口的定义完全在你的代码里——工具叫什么名字、接受什么参数、做什么事情,全是你说了算。AI 只能通过你给的接口描述来理解工具的存在。

这带来一个很实际的好处:添加新能力不需要重新训练模型。

想让 AI 查天气?加一个weather工具,在 prompt 里描述它的参数。想让 AI 搜索?加一个search工具。模型不需要微调,不需要额外数据——它只需要在 prompt 里看到新的接口描述就能使用。

移除能力也一样简单:从 prompt 里删掉工具描述,模型就"忘了"这个工具的存在。

甚至参数的行为也完全由你控制。AI 说"调用 calculator,a=42, b=7, multiply",但真正执行乘法的是你的代码。你想加日志、加权限检查、加参数校验,都可以——在 AI 看不到的地方做任何事。


三、代码实现

3.1 请求工具:request_tool()

打开agent/agent.py,找到request_tool()方法:

defrequest_tool(self,user_input:str)->Optional[dict]:""" 让模型请求工具调用。 第五课版本。 Args: user_input: 用户的请求 Returns: 工具调用规范,如果请求失败则返回 None """user_prompt=f"""你是一个工具调用助手。当被问到数学问题时,你必须只返回 JSON。 可用工具:calculator - 参数:a (数字), b (数字), operation ("add"、"subtract"、"multiply" 或 "divide") 规则: 1. 只返回有效的 JSON 2. 不要任何解释,不要 Markdown 3. 直接以 {{ 开头,以 }} 结尾 示例格式: {{"tool": "calculator", "arguments": {{"a": 42, "b": 7, "operation": "multiply"}}}} 用户请求:{user_input}请返回 JSON:"""forattemptinrange(3):response=self.client.chat.completions.create(model=self.model,messages=[{"role":"system","content":self.system_prompt},{"role":"user","content":user_prompt},],temperature=0.0,)text=response.choices[0].message.content parsed=extract_json_from_text(text)ifparsedand"tool"inparsedand"arguments"inparsed:returnparsedreturnNone

这段代码的设计,每一步都有前几课的影子:

JSON 输出 +extract_json_from_text()—— 第三课的技能直接复用。

重试 3 次—— 第三课和第四课都用过的老模式。LLM 有随机性,第一次格式错了不代表第二次也错。

验证关键字段—— 第四课验证decision in choices,这里验证toolarguments都存在。同样的工程原则:始终验证模型输出。

temperature=0.0—— 工具调用需要精确的参数提取(42 不能变成 43,multiply 不能变成 add),零温度保证稳定性。

Prompt 里的 few-shot 示例—— 注意 User Prompt 里那行示例:{"tool": "calculator", "arguments": {"a": 42, "b": 7, "operation": "multiply"}}。给模型看一个正确的输出样例,比纯文字描述有效得多。这个技巧在第三课的常见问题里提过,这里直接用上了。

User Prompt 放工具描述—— 和第四课一样,工具列表是动态内容,放在 User Prompt 里而不是 System Prompt 里。System Prompt 保持角色设定稳定。

3.2 执行工具:execute_tool_call()

defexecute_tool_call(self,tool_call:dict)->Any:""" 执行模型请求的工具调用。 Args: tool_call: 带 "tool" 和 "arguments" 的字典 Returns: 工具执行的结果 """returnexecute_tool(tool_call["tool"],tool_call["arguments"])

看起来简单,但注意——请求和执行是两个独立的方法。这不是偷懒,而是有意为之。


四、请求与执行:为什么必须分开?

# 第一步:AI 负责请求tool_call=agent.request_tool("What is 42 * 7?")# 第二步:你负责执行result=agent.execute_tool_call(tool_call)

request_tool()做的事是纯"文字工作":理解用户意图 → 选择工具 → 提取参数 → 组装 JSON。全在 AI 的能力范围内。

execute_tool_call()做的事是"真刀真枪":验证参数 → 调用函数 → 返回结果。这是你的代码负责的。

为什么要分开?两个原因:

安全性。AI 永远无法绕过你的代码直接执行操作。它不能访问文件系统,不能发起网络请求,不能修改数据库——除非你的代码明确允许。AI 是一个请求者,不是一个执行者。

你可能觉得现在只有一个 calculator,分不分开无所谓。但想想后面——当 AI 能调用搜索、发邮件、操作数据库的时候,这个分离就是你的安全网。

可控性。你可以在执行前做任何事:验证参数类型、检查权限、记录日志。不需要 AI 知道,也不需要 AI 同意。

这个分离在后续课程中会越来越重要。第六课加循环、第七课加记忆、第八课加规划之后,AI 的行为会变得非常复杂。但如果"请求"和"执行"的边界始终清晰,系统就不会失控。


五、运行示例

查看complete_example.py中的lesson_05_tools()方法:

fromagent.agentimportAgent agent=Agent(model="qwen2.5:7b")tool_call=agent.request_tool("What is 42 * 7?")print(f"Tool request:{tool_call}")iftool_call:result=agent.execute_tool_call(tool_call)print(f"Tool result:{result}")

运行效果:

Tool request: {"tool": "calculator", "arguments": {"a": 42, "b": 7, "operation": "multiply"}} Tool result: 294

整个流程走一遍:

用户: What is 42 * 7? ↓ 模型收到 prompt(包含 calculator 工具描述) ↓ 模型输出: {"tool": "calculator", "arguments": {"a": 42, "b": 7, "operation": "multiply"}} ↓ 代码验证: tool 存在 ✓,arguments 存在 ✓ ↓ 代码执行: multiply(42, 7) → 294 ↓ 用户收到: 294

注意一件事:模型根本没有做数学运算。它不知道 42 × 7 等于多少。它只是识别出"这是一个计算需求",选了 calculator 工具,从输入中提取了数字和运算符。真正的计算由你的代码完成。

这就是"AI 描述意图,你控制执行"的具体体现。


六、工具的能力上限 = 你的代码能力上限

第五课有一个很容易被忽略但非常深刻的洞察:

模型不会做微积分?没关系,只要你的 calculator 支持微积分就行。
模型不懂 SQL?没关系,只要你的 database 工具能接收 SQL 查询就行。

模型是一个通用的"意图到接口"翻译器。它负责理解用户想做什么、选对工具、提取对参数。至于工具具体能做什么、做到什么程度——完全取决于你的代码实现。

你提供多少接口,AI 就有多少能力。不需要重新训练模型,只需要写代码、加接口。

这也是为什么本课的标题是"让 AI 调用工具"而不是"给 AI 添加能力"——能力一直是你的,AI 只是学会了请求使用它们


七、常见问题

Q:模型请求了一个不存在的工具怎么办?

A:在执行前根据可用工具列表验证工具名称。在 prompt 里清晰列出可用工具(就像代码里做的那样),能大幅减少这种情况。

Q:模型传的参数类型不对怎么办?

A:在execute_tool_call()里加参数校验。比如 calculator 期望数字,模型传了字符串,就报错并重试。另外,在工具描述里明确类型(a (number)而不是a (any))也能帮助模型输出正确格式。

Q:该用工具的时候模型直接回答了怎么办?

A:在 prompt 里加更强的约束,比如 “You MUST use the tool for math questions”。提供 few-shot 示例(代码里已经做了)也很有效。

Q:怎么添加新工具?

A:两步走:① 在代码里实现工具函数;② 在 prompt 的工具描述里添加名称和参数说明。模型会自动理解并使用。


八、下期预告

第六课:智能体循环——让 AI 持续思考和行动

前五课,AI 每次只做一件事:回答一个问题,做一个决策,调用一个工具。拿到结果就结束了。

但真实的智能体不是这样的。它应该能反复思考、反复行动——调用工具拿到结果,分析结果,决定下一步,再调用工具……直到任务完成。

下一课,我们把决策和工具调用放进一个循环里。这是 Agent 真正"活"起来的时刻。

敬请期待!


完整代码获取

本课涉及的完整代码包括:

  • request_tool()方法——带验证和重试的工具请求系统
  • execute_tool_call()方法——安全的工具执行层
  • calculator 工具的完整实现
  • 多种测试用例

完整代码获取,请参考 第一篇 最后


标签

#Python#AI Agent#LLM#工具调用#Function Calling#Ollama#Qwen#大模型#手搓Agent


本文为《手搓 AI Agent 从 0 到 1》系列教程第 5 课

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 5:59:51

sqli-labs靶场 less-1

一、注入点这个网站连接数据库后端的查询用户是用id查询的,并且请求方式是get所以在传入接口的网址后面添加id2或者别的数字,就会查询id1的用户信息?id1二、查看有多少字段?id1 order by 3-- //要查询表id1的数据有没有三个字段 是要提前闭合后台自带…

作者头像 李华
网站建设 2026/4/17 5:59:43

Gerber文件导出避坑手册:Allegro光绘参数设置与立创EDA兼容性实战

Gerber文件导出避坑手册:Allegro光绘参数设置与立创EDA兼容性实战 在硬件设计领域,Gerber文件作为PCB生产的"通用语言",其导出质量直接决定生产成败。尤其当使用Allegro这类国际EDA工具对接国产立创EDA生态时,参数设置差…

作者头像 李华
网站建设 2026/4/17 5:52:30

生成式AI灰度发布必须设置的4个动态熔断阈值:基于token级延迟、置信度衰减率与用户纠错频次

第一章:生成式AI应用灰度发布策略 2026奇点智能技术大会(https://ml-summit.org) 生成式AI应用的灰度发布需兼顾模型行为不确定性、用户反馈敏感性与系统稳定性。不同于传统服务,大语言模型输出具有非确定性、上下文强依赖及潜在幻觉风险,因…

作者头像 李华
网站建设 2026/4/17 5:48:00

SDMatte Web界面使用教程:上传→框选→选择模式→下载四步详解

SDMatte Web界面使用教程:上传→框选→选择模式→下载四步详解 1. 认识SDMatte:你的智能抠图助手 SDMatte是一款专为高质量图像抠图设计的AI工具,它能帮你轻松完成各种复杂的抠图任务。想象一下,你正在为电商商品制作宣传图&…

作者头像 李华