第5讲:打通“手脚”——原生实现联网搜索与 Tool Calling 机制
欢迎来到《Python + AI Agent 实战开发完全指南》专栏!
在前面的课程中,我们让大模型学会了如何思考(ReAct)以及如何严谨地输出决策(JSON 结构化)。但一个真正的 Agent 绝不能仅仅停留在"纸上谈兵",它必须具备感知外部世界和执行真实任务的能力。今天这一讲,我们将为 Agent 装上真实的"手脚"——工具调用(Tool Calling)。
很多新手会问:“既然我们在 Prompt 里告诉大模型有哪些工具,它不就能自己调用了吗?“答案是:不能。大模型本质上只是一个文本生成器,它没有权限也没有能力去执行 Python 函数或发起网络请求。所谓的"Agent 调用工具”,其实是一场由大模型和我们的 Python 代码共同完成的"双簧”。
一、揭秘 Tool Calling 的底层真相
目前主流的大模型(如智谱 GLM、通义千问等)都在 API 层面提供了原生的 Function Calling 支持。它的标准工作流分为四步:
- 声明工具:在调用 API 时,把工具的 JSON Schema(名称、描述、参数类型)传给大模型。
- 意图识别:大模型根据用户问题,决定是否需要使用工具。如果需要,它会返回一个包含
tool_calls的指令,而不是普通文本。 - 本地执行:我们的 Python 代码拦截这个指令,在本地安全地运行对应的函数(比如查数据库、发 HTTP 请求)。
- 结果回传:将函数的真实执行结果作为
tool角色追加到上下文中,大模型看到结果后,再总结出最终的自然语言回复。
二、实战演练:手写一个真实的联网搜索 Agent
为了让你彻底搞懂这套机制,今天我们抛开所有高级框架,用最纯粹的原生 Python,给 Agent 接入一个真实的天气查询接口。
2.1 定义工具的 JSON Schema
这是最关键的一步。你必须用极其精确的结构化语言告诉大模型这个工具是干嘛的、需要什么参数。
# 向大模型声明的工具元数据
weather_tool_schema = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "获取指定城市当前的实时天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "中国城市名称,例如:北京"},
},
"required": ["city"],
}
}
}
]
2.2 编写真实的业务逻辑
大模型只负责"下达命令",真正干活的是这段本地代码。这里我们可以接入任何第三方的免费天气 API。
import requests
def get_current_weather(city: str) -> str:
"""模拟或真实调用天气API"""
# 实际开发中这里替换为真实的API请求
return f"{city}当前气温 26℃,天气晴朗,微风"
2.3 组装核心交互引擎
接下来,我们将这两者结合起来,跑通完整的闭环。
from openai import OpenAI
client = OpenAI(api_key="your-api-key", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1")
def run_agent_with_tools(user_query: str):
messages = [{"role": "user", "content": user_query}]
# 第一步:带上工具声明,发起首次请求
response = client.chat.completions.create(
model="qwen-turbo",
messages=messages,
tools=weather_tool_schema, # 【关键】注入工具列表
tool_choice="auto" # 让大模型自主决定是否调用
)
assistant_message = response.choices[0].message
# 第二步:判断大模型是否决定调用工具
if assistant_message.tool_calls:
print(f"[Agent 决定调用工具]: {assistant_message.tool_calls[0].function.name}")
# 必须先将助手的原始消息加入上下文
messages.append(assistant_message)
# 第三步:遍历并执行每一个工具调用
for tool_call in assistant_message.tool_calls:
func_name = tool_call.function.name
# 安全解析参数
import json
func_args = json.loads(tool_call.function.arguments)
# 在本地执行真实函数
result = get_current_weather(**func_args)
# 第四步:将真实结果以 'tool' 角色反馈给大模型
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
# 第五步:带着工具的执行结果,再次请求大模型进行总结
final_response = client.chat.completions.create(
model="glm-4-flash",
messages=messages
)
return final_response.choices[0].message.content
else:
# 如果不需要工具,直接返回普通文本
return assistant_message.content
# 测试一下
print(run_agent_with_tools("帮我查一下杭州现在的天气怎么样?"))
三、避坑指南与工程建议
在实际落地 Tool Calling 时,有几个极易踩坑的细节需要特别注意:
3.1 工具描述要详尽
大模型是通过阅读 description 来决定何时调用工具的。如果你的描述太简略,它可能会在不需要的时候乱调用,或者该用的时候不用。
错误示范:
{
"name": "search",
"description": "搜索信息",
"parameters": {"type": "object", "properties": {"query": {"type": "string"}}}
}
正确示范:
{
"name": "search_weather",
"description": "通过搜索引擎查询指定城市的实时天气状况,包括温度、天气状况和风力",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "需要查询天气的城市名称,如北京、上海"}
},
"required": ["city"]
}
}
3.2 严格的异常处理
本地执行的函数随时可能报错(比如断网、API 超时)。你的 Python 代码必须捕获这些异常,并将错误信息(如"网络超时")作为 content 返回给大模型,让它自行决定是重试还是告知用户。
try:
result = get_current_weather(**func_args)
except Exception as e:
result = f"调用工具时发生错误:{str(e)}"
3.3 上下文膨胀问题
每次工具调用的结果都会塞进 messages 列表中。如果工具返回的数据极大(比如抓取了整个网页),很容易撑爆大模型的 Token 限制。建议在本地执行完函数后,先做一层数据清洗和摘要,再传给大模型。
四、本节小结
今天我们揭开了 Tool Calling 的神秘面纱。你应该已经深刻理解:大模型只是大脑,Python 才是手脚。通过标准的 API 协议,我们成功实现了自然语言到真实物理操作的跨越。
掌握了这一套原生机制后,你已经具备了开发绝大多数单智能体应用的能力。下一讲,我们将正式引入 LangChain 生态,看看如何用几行优雅的代码替代我们今天手写的这套繁琐流程,让你的开发效率提升十倍!
附录:完整代码示例
import json
from openai import OpenAI
class NativeAgent:
def __init__(self, api_key, base_url, model="qwen-turbo"):
self.client = OpenAI(api_key=api_key, base_url=base_url)
self.model = model
self.tools = []
self.tool_map = {}
def register_tool(self, name, description, func, parameters):
"""注册工具"""
tool_schema = {
"type": "function",
"function": {
"name": name,
"description": description,
"parameters": parameters
}
}
self.tools.append(tool_schema)
self.tool_map[name] = func
def run(self, user_query):
"""运行Agent"""
messages = [{"role": "user", "content": user_query}]
# 第一次调用,让模型决定是否需要工具
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
tools=self.tools,
tool_choice="auto"
)
assistant_message = response.choices[0].message
if assistant_message.tool_calls:
# 将助手的消息加入上下文
messages.append(assistant_message)
# 执行工具调用
for tool_call in assistant_message.tool_calls:
try:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
# 执行本地函数
result = self.tool_map[func_name](**func_args)
# 将结果反馈给模型
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
except Exception as e:
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": f"工具调用失败:{str(e)}"
})
# 第二次调用,让模型总结结果
final_response = self.client.chat.completions.create(
model=self.model,
messages=messages
)
return final_response.choices[0].message.content
else:
return assistant_message.content
# 使用示例
def get_current_weather(city: str) -> str:
"""获取实时天气"""
# 这里可以接入真实的天气API
return f"{city}当前气温 25℃,天气晴朗"
# 初始化Agent
agent = NativeAgent(
api_key="your-api-key", #更换成自己的api_key
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
# 注册工具
agent.register_tool(
name="get_current_weather",
description="获取指定城市当前的实时天气信息",
func=get_current_weather,
parameters={
"type": "object",
"properties": {
"city": {"type": "string", "description": "中国城市名称"}
},
"required": ["city"]
}
)
# 运行测试
result = agent.run("今天北京天气怎么样?")
print(result)
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)