【智能体工具使用实战02】智能体使用工具的核心机制
第2章 智能体使用工具的核心机制
本章你将学到:
- 工具(Tool)到底是什么,为什么描述比代码更重要
- Function Calling的完整链路——模型如何决定调用工具,代码如何执行,结果如何返回
- 手写一个最小工具调用循环,使用DeepSeek API,只用一个计算器工具跑通全流程
- 如何将Harness五步法扩展到工具增强型Agent
本章你将产出:一个极简但完整的工具调用Agent脚本
minimal_agent.py,以及一个理解Function Calling全链路的清晰心智模型
全部章节:收录在专栏《AI应用工程化实战教程》之【智能体工具使用实战】
2.1 什么是工具(Tool)
在第1章结束的时候,你设想了三个函数:read_file、execute_python、write_file。你想让Agent在需要的时候自己调用它们。
但Agent是一个语言模型,它不“知道”这些函数的存在。它也不能直接执行任何Python代码。要让Agent使用工具,你必须做两件事:
第一,告诉它有哪些工具可用。 你需要用一段描述,让Agent理解每个工具的功能、适用场景、需要哪些参数、返回什么。这段描述会和用户消息一起发送给模型。
第二,当模型说“我想用这个工具”时,由你的代码真正执行它。 模型只负责“做决定”——决定调哪个工具、传什么参数。它不负责执行。执行是你写的Python代码的事。
所以一个工具,在代码层面由两部分组成:
| 组成部分 | 谁负责 | 内容 |
|---|---|---|
| 工具描述(传给模型) | 你定义 | 函数名、用途说明、参数定义(名称、类型、是否必填) |
| 工具实现(Python函数) | 你实现 | 真正的业务逻辑——读文件、执行代码、调用外部API |
举个例子。一个最简单的“计算器”工具,它的描述长这样:
calculator_tool = {
"type": "function",
"function": {
"name": "calculator",
"description": "执行一个数学表达式并返回计算结果。支持加减乘除、幂运算、括号。例如:'2+3*4'、'(15.8+2.3)*4'。",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "需要计算的数学表达式,如 '1+2*3'"
}
},
"required": ["expression"]
}
}
}
对应的实现函数长这样:
def calculator(expression: str) -> str:
"""安全地计算数学表达式"""
try:
# 使用eval的安全替代方案,或使用简单的表达式解析
# 这里为了教学,使用安全的数值计算
result = eval(expression, {"__builtins__": {}}, {})
return str(result)
except Exception as e:
return f"计算错误: {e}"
工具描述是写给AI看的,工具实现是写给Python解释器看的。描述的好坏,直接决定了AI能不能在正确的时机选对工具、传对参数。
2.2 工具描述规范
一个工具描述必须包含以下四个要素。缺了任何一个,模型都可能用错工具。
要素一:名称(name)
用英文,snake_case。这是模型在输出中说“我要调用这个工具”时引用的标识符。例如 read_file、execute_python、send_email。命名要精准描述功能,不要起 tool1 或 func_a 这种名字。
要素二:用途描述(description)
用自然语言写清楚这个工具是干什么的。这是整个描述中最关键的部分——模型就是靠这段话来判断“当前这个任务该不该用这个工具”。
差的描述:"执行代码"。
好的描述:"在沙盒中执行一段Python代码,返回标准输出。适用于需要计算、数据处理、文件操作等任务。只支持pandas、numpy、math等安全模块。"
好的描述回答了三个问题:这个工具做什么?什么时候应该用它?它有什么限制?
要素三:参数定义(parameters)
使用JSON Schema格式。每个参数需要指定:
type:参数类型(string / number / integer / boolean)description:参数的含义和约束,用自然语言写清楚required:是否必填(在工具定义的最外层有一个required列表)
一个示例:
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "需要读取的文件路径,可以是相对路径或绝对路径"
},
"encoding": {
"type": "string",
"description": "文件编码,默认utf-8",
"default": "utf-8"
}
},
"required": ["path"]
}
要素四:返回值格式(在描述或函数注释中体现)
模型需要知道调用这个工具之后会拿到什么。你可以在 description 末尾加上:“返回文件内容的字符串”,或者“返回JSON格式的计算结果”。
如果工具可能执行失败,也要说明失败时返回什么。例如:“如果文件不存在,返回错误信息字符串。”
2.3 Function Calling的完整链路
现在我们来看一次完整的工具调用到底经历了哪些步骤。
假设用户说:“帮我算一下 (15.8 + 2.3) × 4 等于多少。”
第一步:用户消息 + 工具列表一起发给模型
你的代码把用户的问题和所有可用工具的JSON描述一起,发给DeepSeek的API。发给模型的messages大概是这样的(简化表示):
messages = [
{"role": "system", "content": "你是一个助手,可以使用工具来完成任务。"},
{"role": "user", "content": "帮我算一下 (15.8 + 2.3) × 4 等于多少。"}
]
tools = [calculator_tool] # 工具列表
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=tools
)
第二步:模型返回“我要用工具”
模型读完你的问题,发现:“这个问题需要做数学计算,而我的工具列表里有一个 calculator 工具,正好能做这个。”于是它不再生成普通文本,而是返回一个 tool_call 指令。
从 response 中拿到的消息长这样(简化表示):
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "calculator",
"arguments": "{\"expression\": \"(15.8 + 2.3) * 4\"}"
}
}
]
}
注意:content 是 null——当模型决定调用工具时,它不生成对话文字,而是生成一个工具调用指令。arguments 是一个JSON字符串,里面的参数完全符合你在工具描述里定义的格式。
第三步:你的代码真正执行工具
模型只是说“我想调 calculator,参数是这串表达式”。真正跑代码的是你。你的代码从 tool_calls 中提取函数名和参数,调用对应的Python函数:
import json
tool_call = response.choices[0].message.tool_calls[0]
func_name = tool_call.function.name # "calculator"
arguments = json.loads(tool_call.function.arguments) # {"expression": "(15.8 + 2.3) * 4"}
# 调用真正的函数
result = calculator(**arguments) # 返回 "72.4"
第四步:把执行结果传回模型
工具执行完了,拿到了结果 "72.4"。但故事还没有结束——模型还不知道这个结果。你必须把执行结果包装成一条消息,追加到对话历史里,再发给模型:
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result # "72.4"
})
第五步:模型根据工具结果生成最终回答
现在再次调用模型,它拿到了工具执行的结果 "72.4",整合后生成最终回答:
(15.8 + 2.3) × 4 = 72.4
这是整个链路的完整循环。用一张图表示:
用户提问
│
▼
模型判断:需要工具吗?
│
├── 不需要 ──→ 直接生成回答 ──→ 结束
│
└── 需要
│
▼
模型返回 tool_calls(工具名 + 参数)
│
▼
你的代码执行工具函数
│
▼
工具结果追加到对话历史
│
▼
再次调用模型,模型整合结果生成回答
│
▼
结束
这就是 Function Calling 循环。如果模型一次工具调用后觉得还需要再调另一个工具,它会继续返回新的 tool_calls,你的代码继续执行,直到模型觉得“够了”为止。
2.4 手写一个最小工具调用循环
现在我们把这个逻辑写成完整的Python脚本。目标是最小化——只用一个 calculator 工具,让Agent能够执行数学计算。这个脚本会是你后面所有工具增强型Agent的基础骨架。
2.4.1 项目准备
在你的 data-analyst-agent 项目目录下,确保 .env 文件中已经配置了DeepSeek的API Key:
DEEPSEEK_API_KEY=sk-你的密钥
确保已安装依赖:
pip install openai python-dotenv
2.4.2 完整代码:minimal_agent.py
在Trae的AI对话面板中输入以下指令,让Trae帮你生成代码(你也可以直接对照下面的代码手动创建):
在当前项目 data-analyst-agent 中创建一个 minimal_agent.py。
要求:
1. 使用 openai 库调用 DeepSeek API(base_url="https://api.deepseek.com")
2. 从 .env 文件中读取 DEEPSEEK_API_KEY
3. 定义一个 calculator 工具,描述清晰,参数为一个数学表达式字符串
4. 实现工具调用循环:
- 将用户消息和工具列表发给模型
- 如果模型返回 tool_calls,则执行对应函数,将结果以 tool 角色追加到 messages
- 重复直到模型不再返回 tool_calls,打印最终回答
5. 在脚本底部写一个测试:计算 "(15.8 + 2.3) * 4"
6. 每一步在终端打印 log,显示模型返回了什么、工具执行了什么
如果你选择手动写,下面是一个可以参考的示例代码:
"""
minimal_agent.py —— 最小工具调用Agent演示
使用 DeepSeek API,带一个 calculator 工具
"""
import os
import json
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
# 初始化 DeepSeek 客户端(兼容 OpenAI 接口)
client = OpenAI(
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com"
)
# ============================================================
# 1. 定义工具
# ============================================================
calculator_tool = {
"type": "function",
"function": {
"name": "calculator",
"description": (
"执行一个数学表达式并返回计算结果。"
"支持加减乘除、幂运算、括号。"
"例如:'2+3*4'、'(15.8+2.3)*4'。"
"返回值为字符串格式的数字或错误信息。"
),
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "需要计算的数学表达式,如 '1+2*3'"
}
},
"required": ["expression"]
}
}
}
tools = [calculator_tool]
# ============================================================
# 2. 工具实现函数
# ============================================================
def calculator(expression: str) -> str:
"""安全地计算数学表达式"""
# 允许的命名空间(只允许基本的数学运算)
allowed_names = {
"abs": abs, "round": round,
"min": min, "max": max,
"pow": pow
}
try:
# 使用 eval 的安全方式:空的内建,限制的命名空间
result = eval(expression, {"__builtins__": {}}, allowed_names)
return str(result)
except Exception as e:
return f"计算错误: {e}"
# 工具函数名到函数的映射
TOOL_MAP = {
"calculator": calculator
}
# ============================================================
# 3. 工具调用循环
# ============================================================
def run_agent(user_query: str):
"""运行一次Agent对话,处理可能的工具调用"""
# 初始消息
messages = [
{"role": "system", "content": "你是一个助手,可以使用工具来完成任务。如果用户的问题需要计算,请使用 calculator 工具。"},
{"role": "user", "content": user_query}
]
print("=" * 60)
print(f"用户: {user_query}")
print("=" * 60)
# 循环:最多10轮,防止死循环
for turn in range(10):
print(f"\n--- 第 {turn+1} 轮调用模型 ---")
# 调用API
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=tools
)
msg = response.choices[0].message
# 如果模型没有要求调用工具,说明它已经生成了最终回答
if not msg.tool_calls:
print(f"模型回答: {msg.content}")
return msg.content
# 模型要求调用工具
print(f"模型要求调用 {len(msg.tool_calls)} 个工具")
# 把模型的响应加入对话历史
messages.append(msg)
# 逐一处理每个工具调用
for tool_call in msg.tool_calls:
func_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
print(f" → 调用工具: {func_name}({arguments})")
# 找到对应的函数并执行
if func_name in TOOL_MAP:
func = TOOL_MAP[func_name]
result = func(**arguments)
else:
result = f"错误:未知工具 {func_name}"
print(f" → 工具返回: {result}")
# 将工具执行结果追加到对话历史
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result)
})
# 超过最大循环次数
return "达到最大工具调用轮数,请简化问题。"
# ============================================================
# 4. 测试
# ============================================================
if __name__ == "__main__":
# 测试1:简单计算
run_agent("帮我算一下 (15.8 + 2.3) × 4 等于多少")
print("\n" + "=" * 60)
# 测试2:多步计算(模型可能需要判断用几次工具)
run_agent("计算 1024 除以 32,再把结果乘以 7")
2.4.3 运行与观察
在终端中运行:
python minimal_agent.py
你会看到类似以下的输出:
============================================================
用户: 帮我算一下 (15.8 + 2.3) × 4 等于多少
============================================================
--- 第 1 轮调用模型 ---
模型要求调用 1 个工具
→ 调用工具: calculator({'expression': '(15.8 + 2.3) * 4'})
→ 工具返回: 72.4
--- 第 2 轮调用模型 ---
模型回答: (15.8 + 2.3) × 4 = 72.4
观察终端里的每一次API调用。你看到了什么?
- 第一轮:模型没有输出任何文字,而是返回了一个
tool_calls。它判断“我需要先计算这个表达式”,于是调用了calculator,传入表达式"(15.8 + 2.3) * 4"。 - 你的代码执行了工具:
calculator函数被调用,eval执行了表达式,返回字符串"72.4"。注意,这一步没有任何AI参与——是真正的Python解释器在算。 - 第二轮:你把工具返回的结果
"72.4"追加到对话历史,再次调用模型。这次模型拿到了结果,不再需要工具,直接生成了最终回答。
如果你运行第二个测试(1024 / 32 * 7),观察模型的行为。它可能一次性在 expression 里写 "1024 / 32 * 7",直接调一次工具完事;也可能先调一次 "1024 / 32",拿到结果后再调一次 "32 * 7"。两种方式都有可能,取决于模型自身的判断。这正是Function Calling灵活性的体现。
2.5 将Harness五步法扩展到工具增强型Agent
在本专栏的第一部**《AI智能体工程化实战》**中,Harness五步法是为纯推理型Agent设计的:规范定义、数据构建、Agent开发、自动化评测、迭代优化。评测的核心是“Agent的输出文本与黄金标准是否一致”。
现在Agent的行为变了——它不再只是“输出一段文本”,而是“选择工具→调用工具→整合结果→输出文本”。评测的复杂度增加了。你需要回答的新问题包括:
- Agent选了正确的工具吗?
- 传给工具的参数对吗?
- 工具执行之后,Agent正确理解了返回结果吗?
- 如果工具执行失败,Agent知道怎么处理吗?
因此,五步法在第二部需要做以下扩展:
| 步骤 | 第一部(纯推理Agent) | 第二部(工具增强型Agent) |
|---|---|---|
| Step1 规范定义 | 定义输出内容的评测维度 | 新增:工具选择正确性、参数正确性、工具失败时的降级行为 |
| Step2 数据构建 | GT只包含最终输出 | GT扩展为:{"final_output": "...", "expected_tool_calls": [...]} |
| Step3 Agent开发 | 生成Prompt和脚本 | 生成Prompt + 工具定义 + 工具实现 + 工具调用循环 |
| Step4 自动化评测 | 评测Agent比对最终输出 | 评测Agent新增“工具调用审计”模块,比对各步工具调用 |
| Step5 迭代优化 | 根据最终输出错误修改Prompt | 根据工具选择错误、参数错误分别优化工具描述或Prompt |
这个扩展版的五步法将在后续章节中逐步落地。在第6章,你会用Trae生成一套完整的工具调用评测体系。现在你只需要记住这个概念框架——当Agent的行为从“说”扩展到“做”时,你的评测也必须从“评说什么”扩展到“评做什么”。
2.6 本章小结
- 工具(Tool) = 工具描述(给AI看) + 工具实现(给代码执行)。描述的质量决定了AI能不能选对工具。
- 工具描述四要素:名称、用途描述、参数定义(JSON Schema)、返回值说明。缺一不可。
- Function Calling完整链路:用户提问 → 模型返回tool_call → 你的代码执行工具 → 结果返回模型 → 模型生成回答。这是一轮循环,多轮循环直到模型不再要求工具。
- 手写最小实现:
minimal_agent.py演示了带一个calculator工具的完整循环。你确认了代码每一步都在做什么。 - Harness五步法扩展:评测体系需要新增工具调用审计维度——不仅评最终输出,还要评中间每一步工具选择与参数。
下一章,我们将在Trae中搭建真正的数据分析环境,配置DeepSeek API Key,让Agent拥有第一个真正有用的工具——read_file。
课后练习
- 运行
minimal_agent.py,尝试以下测试:"计算 2024 年 6 月 9 日是星期几"—— Agent会怎么处理?它能用calculator解决吗?"我的账单:午餐45元,晚餐68元,打车32元。帮我算总共花了多少,以及平均每项多少钱。"—— 观察Agent是否会把问题拆成两次计算。
- 修改
calculator工具的描述,把"支持加减乘除、幂运算、括号"改成"只能做加减法"。然后再次测试乘除运算。Agent的行为会有什么变化?为什么工具描述如此重要? - (进阶)在
minimal_agent.py中增加第二个工具get_current_time,无参数,返回当前的时间字符串(用Python的datetime.now())。修改工具列表和TOOL_MAP,测试Agent能否在需要时选择合适的工具。 - (预备思考)如果你要给Agent添加一个
read_file工具,它的工具描述应该怎么写?写出完整的四要素,我们将在第3章实现它。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)