欢迎来到《Python + AI Agent 实战开发完全指南》专栏!

在前面的课程中,我们让大模型学会了如何思考(ReAct)以及如何严谨地输出决策(JSON 结构化)。但一个真正的 Agent 绝不能仅仅停留在"纸上谈兵",它必须具备感知外部世界和执行真实任务的能力。今天这一讲,我们将为 Agent 装上真实的"手脚"——工具调用(Tool Calling)

很多新手会问:“既然我们在 Prompt 里告诉大模型有哪些工具,它不就能自己调用了吗?“答案是:不能。大模型本质上只是一个文本生成器,它没有权限也没有能力去执行 Python 函数或发起网络请求。所谓的"Agent 调用工具”,其实是一场由大模型和我们的 Python 代码共同完成的"双簧”。

一、揭秘 Tool Calling 的底层真相

目前主流的大模型(如智谱 GLM、通义千问等)都在 API 层面提供了原生的 Function Calling 支持。它的标准工作流分为四步:

  1. 声明工具:在调用 API 时,把工具的 JSON Schema(名称、描述、参数类型)传给大模型。
  2. 意图识别:大模型根据用户问题,决定是否需要使用工具。如果需要,它会返回一个包含 tool_calls 的指令,而不是普通文本。
  3. 本地执行:我们的 Python 代码拦截这个指令,在本地安全地运行对应的函数(比如查数据库、发 HTTP 请求)。
  4. 结果回传:将函数的真实执行结果作为 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)
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐