“帮我查一下明天的天气。”

对着本地部署的AI对话框敲完这句话,看着回复,才发现它根本做不到。

不是不想做。是没有"查"这个能力。

AI能写诗、能翻译、能讨论哲学——为什么不能查个天气?为什么对话框里那个看似无所不能的东西,连这么简单的事都做不了?

答案比想象中简单:它没有手。

AI 需要一双手

大语言模型的本质是一台预测引擎。输入一段文字,它预测下一段文字最可能是什么。它没有眼睛、连不上网、打不开文件。它的全部世界,就是塞进对话框的那段文字。

“查天气"这三个字对它来说,跟"写首诗"没有区别——都是在预测下一个token。但它预测出来的天气,可能是去年的,可能是编的。

所以问题变成了:怎么给这台预测引擎装上一双能伸出去的手?

这就是工具调用。

工具调用就三个动作

不需要理解代码。就三个动作在循环:

动作一:告诉AI有哪些工具可用。 提前写好一份清单,列出AI能调用的资源——比如"网页搜索"“读文件"“发邮件”。每个工具附带一份说明书,写清楚叫什么、能干嘛、需要什么参数。

动作二:AI判断该调哪个、怎么调。 AI收到消息后,扫一眼清单。如果发现聊天解决不了,而清单上有工具能解决——就决定调动那个工具,并填好参数。

动作三:系统执行,把结果喂回来。 AI只负责"说要用哪个工具”,真正去搜索、读文件、跑命令的,是电脑或服务器。执行完把结果塞回对话,AI基于结果继续回答。

工具调用的三种场景

工具调用不是一个"功能开关”,取决于AI"站在哪里"。

网页端。 最常见的形态。ChatGPT网页版能搜索、能画图、能跑代码——这些就是被预设好的工具。但能看到哪些、能调哪些,是平台定好的,没法自己加。

API调用。 开发者通过API接入。工具完全自己定义——想给AI什么能力,就写什么工具。最灵活,但需要写代码。

AI Agent。 跑在本地或服务器上的AI客户端。能调用shell命令、读写文件、操作软件。装什么工具就能调什么,权限边界自己控制。

三种场景一层比一层自由。要彻底搞懂工具调用,本地Agent是最短路径——每一步都看得见。

一个真实的例子。复盘上周微信公众号时,我让AI帮我看一眼我已经记录在本地笔记中的数据,:

“查看《上下文工程》的阅读量”

AI没有直接回答。它在后台调用了shell命令,在我电脑上执行了文件搜索。拿到搜索结果,再告诉我"23"。看起来是一问一答,背后是一次完整的工具调用循环。

工具调用的四个组件

组件做什么谁负责
工具定义描述工具叫啥、能干嘛、要什么参数人提前写好
工具选择AI判断该用哪个工具AI自己判断
工具执行真正去搜索、读文件、跑命令系统执行
结果整合AI把返回结果消化,继续对话AI自己

四个组件里,最核心也最容易困惑的是第一个——工具定义

一个工具,就两部分:

① JSON说明书——给AI看的菜单。告诉AI:我叫什么、我能干什么、我需要什么参数。

主流格式就两种,本质一模一样:

OpenAI风格——

{
  "name": "get_weather",
  "description": "查询指定城市的当前天气",
  "parameters": {
    "city": {
      "type": "string",
      "description": "城市名称,例如北京、上海"
    }
  }
}

MCP风格——换个字段名,结构不变:

{
  "name": "get_weather",
  "description": "查询指定城市的当前天气",
  "input_schema": {
    "properties": {
      "city": {
        "type": "string",
        "description": "城市名称"
      }
    }
  }
}

② 执行代码——真正干活的。AI不看它,也不执行它:

def get_weather(city):
    """收到调用指令后,系统执行这个函数"""
    import requests
    response = requests.get(f"https://api.weather.com?city={city}")
    return response.json()["weather"]

一句话:JSON说明书是菜单,代码是厨师。AI对着菜单点菜,厨师在厨房做菜。AI不知道厨房长什么样,厨师不关心AI怎么点的。

MCP:给工具配一个标准接口

理解了工具调用,MCP就好讲了。

不同平台定义工具的方式不一样。OpenAI有一套格式,Anthropic有另一套。给Codex写的工具换到Claude Code上,描述格式得重写——这就是碎片化。

MCP做了一件事:制定统一的工具描述标准。像USB接口——不管什么设备,插上去就能用。

MCP不是工具调用本身,是工具调用的标准化实现。它在技术栈里的位置:

AI模型层           ← 只知道"工具调用"这个能力,不知道MCP
    ↓
Agent/应用层       ← MCP在这一层:发现工具、执行、返回结果
    ↓
MCP工具服务器       ← 用MCP协议包装的具体工具

一个极简的MCP工具服务器示例——完整可运行的天气查询:

# ===== 真正干活的函数(跟MCP无关) =====
def get_weather(city):
    """模拟天气查询,实际项目替换为真实API"""
    weather_db = {
        "北京": "多云,25°C",
        "上海": "晴,30°C",
        "深圳": "小雨,22°C",
    }
    return weather_db.get(city, f"找不到{city}的天气数据")


# ===== MCP包装(固定模板) =====
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("weather-server")       # 创建MCP服务器

@mcp.tool()                            # 声明:这是一个工具
def query_weather(city: str) -> str:   # city: str → 参数自动变成工具定义
    """查询指定城市的当前天气。参数:city 城市名称"""
    return get_weather(city)           # 调用真正干活的函数


# ===== 启动 =====
if __name__ == "__main__":
    mcp.run()                          # 启动,等AI来调用

然后在配置文件里加三行:

[mcp_servers.weather]
command = "python"
args = ["weather_server.py"]

重启。AI就能调用这个工具了。

一个普通函数,加上 @mcp.tool(),再注册到配置——就是一个即插即用的工具。格式固定,照抄就行。


提示工程回答"怎么说"。上下文工程回答"怎么管记忆"。工具调用回答"怎么让AI动手"。

从说到管到做,一条递进线。

对话AI的边界不在它"知道"什么,在它"能够得着"什么。提示词写得再好,也只能在对话框里优化;工具调用把墙推倒了——让AI的手伸出去,摸到真实的世界。

这是从聊天工具到生产力的跨越。