上一篇文章我介绍了智能体(Agent)的核心工作机制,即推理-行动循环(Reasoning-Action Loop,简称ReAct)。在智能体天气助手中,大语言模型(Large Language Model,简称LLM)做出推理,请求调用天气查询工具。但我们并未深入讨论工具调用是如何实现的?LLM如何输出结构化的工具调用指令?本文将探讨这些内容。

一、智能体天气助手的工具调用

1.1 天气查询工具的实现

        在智能体天气助手中,天气查询工具就是一个通过高德API获得天气情况的函数(Function),函数的形参为city,函数返回城市的天气状况、温度、湿度、风向、风力等信息。工具的实现如代码1所示

def weather_report(city: str) -> str:
    """
    查询指定城市的实时天气信息。
    Args:
        city: 城市名称或城市编码adcode,例如:"北京" 或 "110101"
    Returns:
        返回该城市的天气状况、温度、湿度、风向、风力等信息
    """
    # 高德天气API配置
    api_key = "YOUR_OWN_API_KEY"
    url = "https://restapi.amap.com/v3/weather/weatherInfo"
    params = {
        "city": city,
        "key": api_key,
        "extensions": "base"
    }
    
    try:
        response = requests.get(url, params=params, timeout=5)
        data = response.json()
        
        if data.get("status") == "1":
            lives = data.get("lives", [])
            if lives:
                weather_info = lives[0]
                return (f"{weather_info.get('city')}天气:\n"
                       f"  天气状况:{weather_info.get('weather')}\n"
                       f"  温度:{weather_info.get('temperature')}°C\n"
                       f"  湿度:{weather_info.get('humidity')}%\n"
                       f"  风向:{weather_info.get('winddirection')}\n"
                       f"  风力:{weather_info.get('windpower')}级\n"
                       f"  数据时间:{weather_info.get('reporttime')}")
            else:
                return f"未找到{city}的天气数据"
        else:
            error_msg = data.get("info", "未知错误")
            return f"查询天气失败:{error_msg}"
            
    except Exception as e:
        return f"查询天气时出错:{str(e)}"

代码1:天气查询工具的实现

1.2 天气查询工具的定义

        LLM在推理时会决定是否要调用工具,它如何知道工具的存在呢?又如何知道该调用什么工具,调用时该提供什么参数呢?这些信息都存在工具的定义里。工具的定义采用JSON Schema的数据格式。

        天气查询工具的定义如代码2所示。

tools = [
    {
        "type": "function",
        "function": {
            "name": "weather_report",
            "description": "根据城市名称或者标准adcode查询该城市的天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称或者adcode"
                    }
                },
                "required": ["city"]
            }
        }
    }
]

代码2:天气查询工具的定义

        采用JSON Schema的工具定义有几个关键字:

关键字

说明

type

工具的类型,值为function表示这是一个函数调用(Function Calling)类型的工具

name

工具的唯一标识名,执行工具时通过该名字匹配对应的函数

description

工具的描述,LLM通过该字段获知工具的用途

parameters

调用工具时需要提供的参数及其类型,采用JSON Schema格式

        工具的定义会以实参的形式传给函数llm.chat.completions.create()的形参tools(见代码3),该函数用于向LLM发送对话消息并获得回复。

response = llm.chat.completions.create(
    messages=messages,
    model='deepseek-v4-flash',
tools=tools, # 传入工具的定义
)

代码3:工具定义输入LLM

        同时,系统提示词(System Prompt)、用户问题(User Query)和历史消息等一起作为messages也会传入LLM,LLM据此做出推理:是直接回答用户问题,还是需要先调用工具获得数据。

1.3 天气查询工具的调用

        智能体天气助手,针对用户问题“帮我查一下北京的天气。另外,如果北京下雨,顺便也查一下天津的天气。“,将进行多轮推理-行动循环。

1.3.1 第一轮推理-行动

LLM的输入包括

1.系统提示词:

{'role': 'system', 'content': '你是一位天气预报的助手'}

2.用户问题:

{'role': 'user', 'content': '帮我查一下北京的天气。另外,如果北京下雨,顺便也查一下天津的天气。'}

3.工具定义:见代码2

LLM的输出(仅截取部分内容)

"finish_reason": "tool_calls"

"message": {

    "content": "好的,我先查一下北京的天气情况。",

    "role": "assistant",

    "tool_calls": [

        {

            "id": "call_00_XColc3gwH3xvpF5ZhBTl5198",

            "function": {

                "arguments": "{\"city\": \"北京\"}",

                "name": "weather_report"

            },

            "type": "function",

            "index": 0

        }

    ],

"reasoning_content": "用户想知道北京的天气。如果北京下雨,还需要查天津的天气。我先查北京的天气。"

}

        从LLM输出的字段reasoning_content中,我们可以看到LLM的推理过程,它理解了用户问题中的逻辑,表示要先查北京的天气。在tool_calls字段中,LLM给出了请求工具调用的信息,即调用工具weather_report,并给出了实参 {city:北京}。

        此外,LLM在这轮推理-行动中,也生成了助手消息:{"role": "assistant", "content": "好的,我先查一下北京的天气情况。"}。该消息只是中间消息,并不是用户问题的最终回答。

        LLM的输出是格式化的,这便于智能体的相应模块提取信息做进一步处理,如以参数“北京”执行函数weather_report。函数执行后返回执行结果如下:

调用工具: weather_report

参数: {'city': '北京'}

工具返回: 北京市天气:

天气状况:阵雨

温度:16°C

湿度:100%

风向:东北

风力:≤3级

数据时间:2026-05-17 08:37:48

        LLM的输出中还有一个finish_reason的字段,该字段用于控制推理-行动循环,当该字段的值为tool_calls时,表示推理-行动循环没有结束;当该字段的值为stop时,表示推理-行动循环结束。此时finish_reason的值为tool_calls,表示还有下一轮的推理-行动。

1.3.2 第二轮推理-行动

LLM的输入

1.系统提示词:

{'role': 'system', 'content': '你是一位天气预报的助手'}

2.用户问题:

{'role': 'user', 'content': '帮我查一下北京的天气。另外,如果北京下雨,顺便也查一下天津的天气。'}

3.工具定义:见代码2

4.第一轮推理-行动中LLM的输出

5.第一轮推理-行动中工具的执行结果

LLM的输出(仅截取部分内容)

"finish_reason": "tool_calls"

"message": {

    "content": "北京目前是**阵雨**天气,我来查一下天津的天气情况。",

    "role": "assistant",

    "tool_calls": [

        {

            "id": "call_00_5PlYKW8gii8mq6yjb8gH9618",

            "function": {

                "arguments": "{\"city\": \"天津\"}",

                "name": "weather_report"

            },

            "type": "function",

            "index": 0

        }

    ],

    "reasoning_content": "北京现在的天气是\"阵雨\",属于下雨的情况,所以根据用户的要求,我需要再查一下天津的天气。"

}

        在这轮推理中,LLM通过上一轮的工具执行结果得知北京的天气为阵雨,所以要继续查询天津的天气。在tool_calls字段中,LLM继续给出了请求工具调用的信息,即调用工具weather_report,并给出了实参 {city:天津}。

        同样,在这轮推理-行动中,LLM也生成了助手消息:{"role": "assistant", "content": "北京目前是**阵雨**天气,我来查一下天津的天气情况。"}。

        智能体的相应模块以参数“天津”执行函数weather_report。函数执行后返回执行结果如下:

调用工具: weather_report

参数: {'city': '天津'}

工具返回: 天津市天气:

天气状况:中雨

温度:19°C

湿度:93%

风向:东南

风力:≤3级

数据时间:2026-05-17 08:37:08

        此时finish_reason字段的值为tool_calls,推理-行动循环将继续。

1.3.3 第三轮推理-行动

LLM的输入

1.系统提示词:

{'role': 'system', 'content': '你是一位天气预报的助手'}

2.用户问题:

{'role': 'user', 'content': '帮我查一下北京的天气。另外,如果北京下雨,顺便也查一下天津的天气。'}

3.工具定义:见代码2

4.第一轮推理-行动中LLM的输出

5.第一轮推理-行动中工具的执行结果

6.第二轮推理-行动中LLM的输出

7.第二轮推理-行动中工具的执行结果

LLM的输出(仅截取部分内容)

"finish_reason": "stop"

"message":{

    "content": "好的,以下是两个城市的天气情况:\n\n---\n\n### 🌧 北京市\n- **天气状况**:阵雨\n- **温度**:16°C\n- **湿度**:100%\n- **风向**:东北风(≤3级)\n- **更新时间**:2026-05-17 08:37\n\n### 🌧 天津市(北京下雨,帮你查了)\n- **天气状况**:中雨\n- **温度**:19°C\n- **湿度**:93%\n- **风向**:东南风(≤3级)\n- **更新时间**:2026-05-17 08:37\n\n---\n\n北京目前正在下**阵雨**,天津也在下**中雨**,两地今天都有雨,出门记得带伞哦!"

    "role": "assistant",

    "tool_calls": null,

    "reasoning_content": "好的,现在我有两个城市的天气信息了。我来整理一下回答。"

}

        在这轮推理中,LLM表示已获得了北京和天津的天气信息,将整理回答用户问题了。此时tool_calls字段的值为null,表示不再需要调用工具。

        此时LLM的助手消息是一个针对用户问题的完整回答,可以输出给用户了。

        finish_reason字段的值为stop,推理-行动循环结束。

二、工具分类

        通过上一章的介绍,读者应该对智能体的工具调用有了更深的理解。智能体中的LLM不直接执行工具,而是生成调用工具的请求,工具的执行由智能体的其他组件来完成。

        细心的读者可能发现了,我在上一章中的论述混用了工具(Tool)和函数(Function),貌似它们是相同的概念,其实两者是不同的。

        在智能体的上下文中,工具是一个统一的概念,指的是任何能让智能体与外部环境交互,进而获取额外信息或执行具体操作的功能(Functionality)。而函数(Function)只是工具的一种实现形式,而且一个工具可以包含一个函数,也可以包含多个函数。在很多语境下,混用工具和函数并不会影响理解。

        目前主流的智能体框架中,工具大致分为以下几类:自定义工具(Custom Tool),内置工具(Build-in Tool),MCP服务(MCP Server)

2.1 自定义工具

        上一章中介绍的weather_report就是自定义工具,它也被称为函数调用(Function Calling)。这类工具需要用JSON Schema格式手动编写工具的定义。工具的定义会以实参的形式传给函数llm.chat.completions.create()的形参tools,这里不再赘述。

2.2 内置工具

        内置工具不需要像自定义工具那样手动编写工具的定义,LLM在训练阶段已经学习了工具定义,即这类工具的定义已经被训练进了模型的参数中。比如OpenAI的内置工具web_search,只要把{"type": "web_search"}传递给函数llm.chat.completions.create()的形参tools即可。

tools = [
    {
        "type": "web_search"
    },
],
response = llm.chat.completions.create(
    messages=messages,
    model="gpt-4.1-mini",
    tools=tools
)

代码4:内置工具输入LLM

2.3 MCP服务

        MCP服务也不需要像自定义工具那样手动编写工具的定义,只需向MCP服务获取工具的定义(同样是采用JSON Schema格式)即可。作为示例,我们可以通过高德提供的MCP服务获取相关工具的工具定义。下面的代码将获得15个工具的名称和描述,而作为说明我们专门获取了map_weather的工具定义。

from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_core.utils.function_calling import convert_to_openai_tool

client = MultiServerMCPClient(
    {
        "gaode_map": {
            "transport": "http",
            "url": "https://mcp.amap.com/mcp?key=YOUR_OWN_KEY"
        }
    }
)

all_tools = await client.get_tools()

print(f"获取到 {len(all_tools)} 个 MCP 工具:")
for tool in all_tools:
    print(f"  - {tool.name}: {tool.description}")
    if tool.name == "maps_weather":
        openai_tools = [convert_to_openai_tool(tool)]
        print(f"\nmaps_weather的工具定义: {openai_tools}")

码5:通过MCP服务获取工具定义

[

    {

        'type': 'function',

        'function': {

            'name': 'maps_weather',

            'description': '根据城市名称或者标准adcode查询指定城市的天气',

            'parameters': {

                'type': 'object',

                'properties': {

                    'city': {

                    'type': 'string',

                    'description': '城市名称或者adcode'

                    }

                },

                'required': ['city']

            }

        }

    }

]

        通过MCP服务获得工具定义,也将以实参的形式传给函数llm.chat.completions.create()的形参tools。

        无论是自定义工具,内置工具还是MCP服务提供的工具,LLM的工作方式是一样的,即根据输入进行推理,如果要调用工具,会生成格式化的工具调用请求,即提供工具名称和参数。工具的执行由智能体的相应模块来完成。

三、参考文献

1. Anthropic. Writing effective tools for agents — with agents. https://www.anthropic.com/engineering/writing-tools-for-agents, 2025

2. OpenAI. Using tools. https://developers.openai.com/api/docs/guides/tools?tool-type=remote-mcp

3. OpenAI. Function calling. https://developers.openai.com/api/docs/guides/function-calling

作者:徐宏勤
版权声明:本文为原创内容。如需转载,请务必在文章开头标注作者和来源,并保持文章完整,否则视为侵权。 

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐