前几篇我们做了三件事:搭了一个 Agent 骨架(第01篇),给它装了『操作系统』------重试、超时、步数限制(第02篇),然后帮它省了 80% 的 Token 费(第03篇)。
但你发现一个问题没?
这个 Agent 到现在还是个『嘴炮选手』------它能说会道,但一件事都干不了。
- 想查一下实时天气?『抱歉我无法获取实时数据。』
- 想让它写个文件?『建议您手动操作。』
- 想从数据库查个东西?『我不具备数据库访问能力。』
这就是没有工具调用的 Agent ------ 只有大脑,没有手脚。
今天这篇,我们给它装上双手。
一、工具调用是什么
工具调用(Tool Calling),在 OpenAI 体系里叫 Function Calling,在 Anthropic 体系里叫 Tool Use。名字不同,本质一样:让大模型能够调用外部函数和 API。
没有工具调用的模型:
markdown
用户:北京今天的天气怎么样?
模型:抱歉,我的训练数据截止到2025年,
无法获取实时天气信息。建议您打开天气App查询。
有工具调用的模型:
css
用户:北京今天的天气怎么样?
模型:[调用函数 get_weather(city="北京") → 返回 "晴 22-28°C"]
模型:北京今天晴,气温22到28度,
适合外出,紫外线中等,建议防晒。
区别不在于模型『知道』什么------而在于模型能不能『行动』。工具调用就是给模型一个能力:它可以在生成回复之前,决定去调用某个函数,拿到结果后再组织回答。
1.1 工作流程
工具调用的完整流程分三步:
- 注册函数:定义好函数的名称、参数、用途,告诉模型『你有这些工具可用』
- 模型决策:模型分析用户意图,决定『要不要调用工具、调用哪个工具、传什么参数』
- 执行-返回:你(代码)去执行这个函数,把结果塞回上下文,模型看到结果后组织最终回复
注意:模型不帮你执行函数。模型只负责『决定该不该调用』和『给出参数建议』。真正执行函数的是你的代码------这是 Agent 架构的精髓:模型是决策者,代码是执行者。
二、从零实现┃Function Calling 实战
我们用 OpenAI 兼容的 API 来做演示。目前 DeepSeek、Qwen、Kimi 都支持 Function Calling,API 接口完全兼容。
2.1 第一步:定义你的第一个工具
工具定义是一个 JSON Schema,告诉模型『这个函数叫什么、做什么用、需要什么参数』:
ini
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的实时天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如北京、上海、深圳"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位",
"default": "celsius"
}
},
"required": ["city"]
}
}
}
]
关键字段说明:
- name:函数名,唯一标识符,模型用它来『叫』这个函数
- description:简短描述,告诉模型什么时候用这个函数。很重要------模型的决策全靠这段描述
- parameters:参数定义,用 JSON Schema 格式,模型按这个结构生成参数
- required:必填参数列表,没列出来的都是可选
2.2 第二步:实际的函数实现
python
# 这是真正执行的函数体
import requests
def get_weather(city: str, units: str = "celsius") -> str:
"""调用天气API获取实时天气"""
api_key = "your_api_key"
url = f"https://api.openweathermap.org/data/2.5/weather"
params = {
"q": city,
"appid": api_key,
"units": "metric" if units == "celsius" else "imperial"
}
resp = requests.get(url, params=params)
data = resp.json()
temp = data["main"]["temp"]
desc = data["weather"][0]["description"]
humidity = data["main"]["humidity"]
return f"{city}天气:{desc},温度{temp}°{'C' if units == 'celsius' else 'F'},湿度{humidity}%"
2.3 第三步:完整的调用循环
这是最核心的部分------把模型决策和函数执行串联起来:
ini
from openai import OpenAI
client = OpenAI(
api_key="your-api-key",
base_url="https://api.deepseek.com/v1"
)
# 工具定义的注册表------名字到函数的映射
function_registry = {
"get_weather": get_weather
}
messages = [
{"role": "system", "content": "你是一个智能助手,可以通过工具获取实时信息。"},
{"role": "user", "content": "北京今天天气怎么样?适合去公园吗?"}
]
# 第一轮调用
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=tools, # 传入工具定义
tool_choice="auto" # 让模型自动决定是否调用
)
# 检查模型是否想要调用工具
tool_calls = response.choices[0].message.tool_calls
if tool_calls:
# 模型决定调用了!
for tool_call in tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
# 在注册表中找到函数并执行
func = function_registry[func_name]
result = func(**func_args)
# 把结果塞回消息列表
messages.append(response.choices[0].message)
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": func_name,
"content": str(result)
})
# 带着工具结果再调一次模型,让它组织最终回复
final_response = client.chat.completions.create(
model="deepseek-chat",
messages=messages
)
print(final_response.choices[0].message.content)
else:
# 模型没调用工具,直接输出
print(response.choices[0].message.content)
这个循环模式(模型的回复 → 检查工具调用 → 执行 → 返回结果 → 再调模型)是所有 Agent 框架的底层模式------OpenAI SDK、LangChain、AutoGen、Claude Tool Use 全都是这个逻辑。
三、高级模式┃多工具协作
一个工具不够用?那我们就给它一组工具。多工具协作是 Agent 真正的能力所在。
3.1 多工具注册
css
tools = [ { "type": "function", "function": { "name": "web_search", "description": "搜索互联网获取最新信息", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "搜索关键词"} }, "required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "read_webpage",
"description": "读取指定URL的页面内容",
"parameters": {
"type": "object",
"properties": {
"url": {"type": "string", "description": "网页URL"}
},
"required": ["url"]
}
}
},
{
"type": "function",
"function": {
"name": "save_to_file",
"description": "将内容保存到本地文件",
"parameters": {
"type": "object",
"properties": {
"filename": {"type": "string", "description": "文件名"},
"content": {"type": "string", "description": "文件内容"}
},
"required": ["filename", "content"]
}
}
},
{
"type": "function",
"function": {
"name": "run_code",
"description": "执行Python代码并返回结果",
"parameters": {
"type": "object",
"properties": {
"code": {"type": "string", "description": "Python代码"}
},
"required": ["code"]
}
}
}
]
# 注册表也相应扩展
function_registry = {
"web_search": web_search_func,
"read_webpage": read_webpage_func,
"save_to_file": save_to_file_func,
"run_code": run_code_func
}
有了这组工具,你的 Agent 就能做这样的事:
场景示例:一个科研助手
ini
用户:帮我查一下最新的Agent框架对比,整理一下保存到文件。
第1步:模型调用 web_search(query="2025年 AI Agent框架对比")
第2步:查看搜索结果,发现几个关键文章
第3步:调用 read_webpage(url=...) 逐篇阅读
第4步:调用 save_to_file(filename="agent_frameworks_对比.md", content=...)
最终回复:已为您整理好5个主流Agent框架的对比,
已保存到 agent_frameworks_对比.md
注意:模型不是一次性调用所有工具,而是一步一步推演------每步调用一个工具,看到结果后再决定下一步。这就是 Agent 的「思考链条「。
3.2 并行工具调用
很多场景下,多个工具调用没有依赖关系,可以同时执行。DeepSeek 和 OpenAI 都支持并行工具调用:
python
# 模型可能在一个回复中请求多个工具调用
# 举例:对比三家公司的财报
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=tools
)
tool_calls = response.choices[0].message.tool_calls
# tool_calls 可能是数组,每个独立
import asyncio
async def execute_tool_calls(tool_calls, registry):
"""并行执行无依赖的工具调用"""
async def execute_one(tc):
func = registry[tc.function.name]
args = json.loads(tc.function.arguments)
result = await func(**args)
return {
"tool_call_id": tc.id,
"role": "tool",
"name": tc.function.name,
"content": str(result)
}
# 全部并行执行
results = await asyncio.gather(*[
execute_one(tc) for tc in tool_calls
])
return results
并行调用可以大幅缩短多步骤任务的执行时间。比如查询三个城市的天气,串行要6秒,并行只要2秒。
四、实战┃在 Agent 框架中集成工具系统
前几篇我们一直在搭 Agent 框架,现在是时候把工具系统集成进去了。先回顾一下我们已有的 Agent 结构:
python
class Agent:
def __init__(self, model="deepseek-chat"):
self.model = model
self.tools = [] # 工具定义列表
self.registry = {} # 函数注册表
self.history = [] # 对话历史
self.max_steps = 10 # 最大执行步数
def register_tool(self, name: str, fn, schema: dict):
"""注册一个工具"""
schema["name"] = name
self.tools.append({
"type": "function",
"function": schema
})
self.registry[name] = fn
async def run(self, user_input: str):
messages = [
{"role": "system", "content": self.system_prompt},
*self.history[-10:], # 仅保留最近10轮
{"role": "user", "content": user_input}
]
for step in range(self.max_steps):
response = await self._call_llm(messages)
msg = response.choices[0].message
if not msg.tool_calls:
# 模型没调用工具,直接输出
self.history.append({"role": "user", "content": user_input})
self.history.append({"role": "assistant", "content": msg.content})
return msg.content
# 模型调用了工具
messages.append(msg)
for tc in msg.tool_calls:
fn = self.registry.get(tc.function.name)
if not fn:
result = f"错误:未注册的函数 {tc.function.name}"
else:
try:
args = json.loads(tc.function.arguments)
result = await fn(**args)
except Exception as e:
result = f"执行错误:{str(e)}"
messages.append({
"tool_call_id": tc.id,
"role": "tool",
"name": tc.function.name,
"content": str(result)[:5000] # 限制结果长度
})
return "已达到最大执行步数,任务未完成。"
关键设计要点:
- 步数限制:防止工具调用死循环(比如搜索-看到结果-再搜索-再看到结果-永远没完)
- 结果长度限制:工具返回的内容可能非常大,超出上下文窗口前果断截断
- 异常处理:工具可能抛异常,不要让整个 Agent 崩溃
- 历史管理:每轮对话后保留历史,但只保留最近 N 轮(接第03篇的上下文压缩)
4.1 实用工具库
下面这几个工具是 Agent 的「标配「,几乎每个 Agent 都用得上:
python
tools_library = {
# 1. 网络搜索
"web_search": {
"fn": lambda q: requests.get(
f"https://api.duckduckgo.com/?q={q}&format=json"
).json(),
"schema": {
"description": "在互联网上搜索信息",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"}
},
"required": ["query"]
}
}
},
# 2. 网页读取
"read_url": {
"fn": lambda url: requests.get(url).text[:10000],
"schema": {
"description": "读取指定URL的文本内容",
"parameters": {
"type": "object",
"properties": {
"url": {"type": "string"}
},
"required": ["url"]
}
}
},
# 3. 文件操作
"read_file": {
"fn": lambda path: open(path, "r").read(),
"schema": {
"description": "读取本地文件内容",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "文件路径"}
},
"required": ["path"]
}
}
},
"write_file": {
"fn": lambda path, content: (
open(path, "w").write(content), f"已保存到 {path}"
)[1],
"schema": {
"description": "将内容写入本地文件",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string"},
"content": {"type": "string"}
},
"required": ["path", "content"]
}
}
},
# 4. 代码执行
"python_exec": {
"fn": exec_sandboxed_python, # 安全沙箱
"schema": {
"description": "在安全的沙箱中执行Python代码",
"parameters": {
"type": "object",
"properties": {
"code": {"type": "string"}
},
"required": ["code"]
}
}
}
}
安全提醒:代码执行工具(python_exec)一定要跑在沙箱里,永远不要在宿主环境直接 exec 模型生成的代码。可以用 Docker 容器、pyodide 沙箱或者子进程加限制。
五、踩坑指南┃工具调用的 10 个坑
实战中,工具调用的坑比想象的多。我把自己踩过的坑都列出来:
5.1 一个经典的死循环处理
python
# 检测重复调用------防止死循环
class ToolCallTracker:
def __init__(self, max_repeats=3):
self.call_history = []
self.max_repeats = max_repeats
def track(self, name: str, args: dict) -> bool:
"""跟踪工具调用,检测是否陷入死循环"""
self.call_history.append((name, str(args)))
# 检查最近N次是否都是同一个工具调用
recent = self.call_history[-self.max_repeats:]
if len(recent) >= self.max_repeats:
# 检查是否全是同一个调用
first = recent[0]
if all(c == first for c in recent):
return False # 死循环,需要干预
return True
def force_stop(self, messages, step_info):
"""强制打断并注入上下文"""
messages.append({
"role": "system",
"content": f"你已连续重复调用工具 {self.call_history[-1][0]} {self.max_repeats} 次。"
f"请分析已获得的信息,直接回答用户问题,不要继续调用工具。"
})
return messages
六、对比┃各家工具调用的差异
不同厂商的工具调用接口有细微差别,但核心模式相同。这里总结关键差异:
建议的做法:统一用 OpenAI 兼容接口格式,这样切换模型只需要改 base_url 和 api_key,不需要改工具定义。
七、最佳实践总结
- 工具描述要精确:『搜索互联网获取最新信息』比『帮助用户搜索信息』好得多
- 参数尽量少:最少原则,能不传的参数就别定义
- 设置步数限制:永远加 max_steps,Agent 不是永动机
- 避免返回过大的结果:工具返回数据要精炼,不是全部 dump
- 加检测机制:检测循环调用、幻觉函数名、参数解析失败
- 用标准接口:OpenAI 兼容格式,方便切换模型
- 函数体要健壮:try/except 保底,不要让 Agent 因为工具异常而崩溃
- 安全第一:代码执行要沙箱,文件操作要限制路径
八、下期预告
- 第05篇: Agent 记忆系统------从『转头就忘』到『过目不忘』
- 第06篇: Multi-Agent 模式------把 Agent 变成团队
- 第07篇: Skills 工程------如何给 Agent 批量安装技能
提示:工具调用是 Agent 从『聊天的』变成『干活的』最关键的一步。
没有工具的 Agent 就像一个超级聪明但手脚被绑住的人------他能理解你的每一个字,知道该怎么做,但就是做不了。而有了工具,你的 Agent 才能真正『动手』。
很多人在搭 Agent 的时候,花大把时间写 Prompt,却忽略了工具定义的质量。实际上,工具定义的精度直接决定了 Agent 的能力上限。工具是 Agent 的接口,接口质量 = 能力上限。
最后提醒一句:工具不是越多越好。5个精心设计的工具,强过50个随意定义的。
下一篇,我们来解决 Agent 的『失忆症』------记忆系统。
提示 :本文由 码农大坚果 出品,欢迎转发分享,转载请注明出处。
参考: OpenAI Function Calling 文档、DeepSeek API 文档、Claude Tool Use 指南、知识库 concepts/harness-paradigm | 整理 by 码农大坚果