banner
约 6,000 字
20 分钟

Function Calling 与 MCP:从工具调用到标准化 Agent 扩展实践

摘要

本文从大模型工具调用闭环出发,分析 Function Calling 与 MCP 的职责边界,并以门票数据分析、高德旅行规划和本地 TXT 文件统计为例,说明应用内函数、远程服务与本地工具如何接入 Agent。重点讨论参数模式、工具调度、结果回填、能力发现、安全边界和工程选型。

Function Calling 与 MCP:从工具调用到标准化 Agent 扩展实践

写在前面

Large Language Model,大语言模型(LLM)擅长理解和生成文本,但它不会因为回答中出现一段 Structured Query Language,结构化查询语言(SQL),就自动连接数据库执行查询;也不会仅凭内部知识获得实时地图、网络搜索和本地文件信息。要让模型真正完成任务,应用必须把外部能力描述成工具,让模型决定是否调用,再由程序执行工具并把结果返回给模型。

这个项目围绕两种工具接入方式展开。第一种是 Function Calling,应用直接注册函数及其参数模式,模型生成结构化调用请求,应用负责执行。第二种是 Model Context Protocol,模型上下文协议(MCP),Agent Host 通过统一协议发现和调用外部工具、资源与提示模板。

二者不是替代关系。Function Calling 解决模型如何表达调用意图,MCP 解决工具如何被标准化暴露、发现和连接。实际 Agent 往往同时使用两者:MCP Client 先从 Server 获取工具定义,Host 再将这些定义交给模型;模型产生工具调用后,Host 通过 MCP 完成执行。

全文先解释共同的工具调用闭环,再用门票助手说明 Function Calling 的实现和演进;随后拆解 MCP 架构,并通过高德旅行规划和本地 TXT 文件统计说明远程服务与本地服务的接入方式;最后给出两类方案的选择条件和工程风险。

模块

解决的问题

项目中的实现

Function Calling

模型如何选择应用内函数并生成参数

exc_sql 门票查询与自动可视化

MCP Client

Host 如何连接并管理外部工具服务

Qwen-Agent 中的 mcpServers 配置

远程能力

如何复用地图和网络搜索能力

高德地图 MCP、Tavily MCP

本地能力

如何将本地程序暴露给 Agent

FastMCP 桌面 TXT 文件统计器

工程控制

如何保证工具调用可控、可查、可验证

参数校验、权限隔离、日志与结果检查

一、整体架构:先建立共同的调用闭环

Function Calling 与 MCP 工具调用整体架构(图由AI辅助绘制)
Function Calling 与 MCP 工具调用整体架构(图由AI辅助绘制)

图 1:Function Calling 与 MCP 共用的工具调用闭环。Mermaid 源文件保存在同级 images 目录中(图由AI辅助绘制)。

这套架构包含六个连续步骤。

第一,Host 收到用户请求,并准备对话上下文。第二,Host 将可用工具的名称、用途和参数模式交给模型。第三,模型判断是否需要外部能力;如果需要,则生成包含工具名和参数的结构化请求。第四,Host 校验参数并选择执行路径:应用内函数由本地调度器直接执行,MCP 工具由 Client 转发到对应 Server。第五,执行结果回填到对话上下文。第六,模型基于工具结果生成最终回答、表格或图表。

模型在这个过程中只负责选择工具和生成参数,不直接执行函数。真正的数据库权限、文件权限、网络访问、超时和审计都属于 Host 或工具服务的职责。这个边界决定了系统能否从演示原型进入生产环境。

二、Function Calling 的本质

Function Calling 可以理解为模型与应用之间的一份结构化调用约定。应用把函数描述和参数模式传给模型,模型不返回自由文本指令,而是返回可解析的函数名和参数。应用根据名称找到真实函数,执行后再把结果交还给模型。

一次完整调用包含四类信息。

信息

作用

门票助手中的对应内容

函数名

唯一定位工具

exc_sql

函数描述

告诉模型何时使用

执行生成的 SQL,并返回查询结果

参数模式

约束参数名称、类型和必填项

sql_input: string

工具结果

为模型提供真实外部信息

Markdown 表格和图表路径

参数模式不是形式化装饰。描述过于宽泛时,模型可能选错工具;字段语义不清时,模型可能生成错误参数;可选项没有枚举时,同一概念可能出现多种写法。高质量工具定义应尽量做到单一职责、参数明确、返回值稳定和错误语义可区分。

Qwen-Agent 的实现将自定义工具注册为 BaseTool 子类,再把工具名传入 Assistant。官方项目同时提供 Function Calling、MCP、Code Interpreter 和 RAG 等 Agent 组件。[^6] 下面保留项目中的核心逻辑,并移除数据库凭据和界面代码。

Python
import json
import os

import pandas as pd
from qwen_agent.tools.base import BaseTool, register_tool
from sqlalchemy import create_engine, text


@register_tool("exc_sql")
class ExcSQLTool(BaseTool):
    description = "执行门票订单查询,并返回表格与可视化结果"
    parameters = [{
        "name": "sql_input",
        "type": "string",
        "description": "基于 tkt_orders 表生成的只读 SQL",
        "required": True,
    }]

    def call(self, params: str, **kwargs) -> str:
        sql_input = json.loads(params)["sql_input"]
        engine = create_engine(os.environ["TICKET_DATABASE_URL"])

        df = pd.read_sql(text(sql_input), engine)
        image_path = generate_chart_png(df)

        table = df.head(10).to_markdown(index=False)
        return f"{table}\n\n![查询结果图]({image_path})"

模型侧只需要注册工具并运行对话。

Python
from qwen_agent.agents import Assistant

bot = Assistant(
    llm={"model": "qwen-turbo"},
    name="门票助手",
    system_message=system_prompt,
    function_list=["exc_sql"],
)

for response in bot.run([{"role": "user", "content": user_query}]):
    print(response)

代码很短,但运行质量取决于系统提示中是否给出了准确的表结构、业务口径和时间定义。模型只能依据提供的上下文生成 SQL,不会自动知道一日票、二日票分别对应哪些 Stock Keeping Unit,库存量单位(SKU),也不会自动统一周起始日和跨年周编号。

三、门票数据分析:从查询工具到连续分析

3.1 场景与数据

门票助手面向订单分析场景。核心表 tkt_orders 包含下单时间、用户、地区、SKU、销售渠道、订单状态、金额和数量等字段。系统需要处理三类问题:按周统计一日票与二日票销量、按省份统计入园人数、按渠道统计订单金额。

这类任务不只是自然语言转 SQL。模型还要理解业务分类、选择聚合口径、处理时间边界,并将结果转换为适合分析的表格和图表。项目因此分三版逐步实现。

版本

新增能力

解决的问题

仍然存在的限制

assistant_ticket_bot-1

注册 exc_sql 并返回前 10 行

打通模型生成 SQL、应用执行、结果回填

结果只有表格,不便观察趋势

assistant_ticket_bot-2

查询后自动识别横轴和数值列并绘图

将数据查询扩展为可视化分析

多类别数据会被压缩到同一横轴

assistant_ticket_bot-3

根据类别列构造透视表并绘制分组结果

展示日期、渠道、票种等多维关系

图表类型仍由数据类型启发式决定

3.2 第一版:让模型负责 SQL,让程序负责执行

系统提示中给出表结构、字段解释和票种映射。例如一日票通过 SKU LIKE 'Universal Studios Beijing One-Day%' 聚合,二日票通过 SKU LIKE 'USB%' 聚合。模型根据用户问题生成 SQL 后调用 exc_sql,工具通过 Pandas 执行查询并返回 Markdown 表格。

这个分工比让模型直接输出一段 SQL 更完整。用户得到的是数据库真实结果,不需要手动复制查询语句;模型也能基于执行结果继续解释。不过,原型直接执行模型生成的 SQL,只适合受控环境。生产环境至少需要只读账号、允许表清单、仅允许 SELECT、查询超时、结果行数上限和敏感字段屏蔽。

3.3 第二版:把查询和绘图放在同一个工具中

最初可以把 SQL 查询和绘图设计成两个工具,但这会产生一个新的状态问题:查询工具返回 Markdown 表格后,绘图工具还要重新解析文本、判断横纵轴,并在多轮调用中保持同一个 DataFrame。中间数据越大,模型上下文和序列化开销越高。

项目最终将查询与绘图放在同一个 exc_sql 工具中。工具执行 SQL 后直接取得 DataFrame,先返回表格,再根据列类型生成图表。这样避免了 DataFrame 在工具之间传递,也保证表格与图片来自同一次查询。

核心流程可以概括为:

纯文本
自然语言问题
  -> 模型生成 SQL 和 exc_sql 调用
  -> 数据库返回 DataFrame
  -> DataFrame 转 Markdown 表格
  -> 根据类别列和数值列生成图表
  -> 表格与图片一起回填
  -> 模型输出最终分析

3.4 第三版:用透视表表达多类别关系

简单数据通常只有一个类别列和多个数值列,例如周编号、一日票销量、二日票销量。第二版可以直接以周编号为横轴绘制两组柱形。

当查询结果同时包含日期和销售渠道时,同一天会出现多行记录。若仍把日期作为唯一横轴,不同渠道会使用重复标签,图表无法表达渠道差异。第三版先识别对象类型列和数值列,再通过 pivot_table 将渠道转为列层级,以日期作为索引,最后绘制多级分组或堆积图。

Python
category_columns = df.select_dtypes(include="object").columns.tolist()
value_columns = df.select_dtypes(exclude="object").columns.tolist()

pivot_df = df.pivot_table(
    index=df.columns[0],
    columns=category_columns[1:],
    values=value_columns,
    fill_value=0,
)

这里的关键不是 pivot_table 本身,而是明确图表语义:第一列表示观察维度,其他类别列表示分组维度,数值列表示度量。若应用场景固定,最好由工具参数显式传入 xseriesvalue 和图表类型,不应长期依赖数据类型自动推断。

3.5 结果与一次时间口径修正

门票周销量查询与可视化结果
门票周销量查询与可视化结果

图 2:门票助手调用 exc_sql 后同时返回周销量表格和柱形图。来源:项目运行结果。

结果显示,2023 年第 14 周到第 25 周的一日票销量大致稳定在 1.9 万至 2.0 万,二日票约为 1.4 万至 1.5 万;第 13 周和第 26 周明显偏低。这个现象不能直接解释为业务异常,因为查询区间是 4 月至 6 月,首尾周只覆盖了部分日期。

继续下钻第 13 周时,初始 SQL 使用了与业务口径不一致的周编号函数,只取到了 4 月 1 日的数据。修正后按 3 月 27 日至 4 月 2 日重新查询,才能得到完整周数据。这个过程说明,Function Calling 能执行 SQL,但不会自动消除统计口径歧义。周起始日、自然周与 ISO 周、跨月和跨年边界必须在系统提示或业务层固定。

四、MCP 为什么必要

应用内 Function Calling 适合少量、单一应用拥有的工具。随着工具数量和客户端数量增加,每个应用都要重复编写工具描述、进程启动、连接、权限和结果解析逻辑。MCP 将这些问题抽象成统一协议,使工具服务能够独立于具体模型和 Host 演进。

Anthropic 在 2024 年 11 月 25 日发布 MCP。当前官方架构将系统划分为 Host、Client 和 Server:Host 是承载模型和交互的 AI 应用;每个 Client 与一个 Server 维持独立连接;Server 提供上下文和可执行能力。MCP 只规定上下文交换协议,不规定 Host 使用哪一种模型,也不规定 Agent 如何管理记忆和规划。[^1][^2]

4.1 Host、Client 和 Server

组件

主要职责

项目中的对应对象

MCP Host

管理模型、用户交互、权限和多个 Client

Qwen-Agent Assistant

MCP Client

建立连接、协商能力、发现工具并转发调用

Qwen-Agent 内部 MCP 适配层

MCP Server

暴露工具、资源和提示模板

高德地图、Tavily、TXT 统计服务

一个 Host 可以连接多个 Server,但通常会为每个 Server 创建一个独立 Client。这样可以隔离生命周期、权限和故障范围。高德 Server 不需要理解模型对话,TXT Server 也不需要知道地图工具是否存在;它们只需按协议声明能力并响应请求。

4.2 Tools、Resources 和 Prompts

MCP Server 可以暴露三类核心原语。Tools 是模型可调用的动作,例如查询地图、搜索网页或读取文件;Resources 是由应用选择并加入上下文的数据,例如文件内容、数据库记录和接口响应;Prompts 是可复用的交互模板。官方规范将三者分别概括为模型控制、应用控制和用户控制。[^3]

原语

主要用途

典型操作

Tools

执行动作或获取动态结果

tools/listtools/call

Resources

提供可读取的上下文数据

resources/listresources/read

Prompts

提供参数化提示模板

prompts/listprompts/get

项目主要使用 Tools。Client 连接 Server 后先发现工具列表,再把工具名称、描述和输入模式注册到 Host;模型产生调用后,Client 通过 tools/call 转发参数,并将响应交回 Host。

4.3 数据层与传输层

MCP 数据层基于 JavaScript Object Notation Remote Procedure Call 2.0,JSON 远程过程调用 2.0(JSON-RPC 2.0),负责初始化、能力协商、工具发现、调用和通知。传输层负责进程或网络通信。官方当前定义的主要传输方式是 standard input/output,标准输入输出(stdio)和 Streamable Hypertext Transfer Protocol,可流式超文本传输协议(Streamable HTTP)。本地工具通常使用 stdio,由 Host 启动子进程;远程服务通常使用 Streamable HTTP,并结合鉴权。[^2]

这两层分离后,同一组工具语义可以运行在本地进程,也可以部署为远程服务。Host 不需要针对每个工具重新设计通信格式。

五、高德旅行规划:通过 MCP 复用远程能力

5.1 场景与目标

旅行规划不是一次地点搜索。系统需要先查找候选景点,再获取地址、开放时间和门票信息,随后考虑景点间距离和交通方式,最后组织为可执行的日程。模型负责分解任务和组织结果,地图 Server 负责返回实时地理信息。

项目在 Qwen-Agent 中配置高德地图 MCP Server。Application Programming Interface,应用程序编程接口(API)密钥通过环境变量注入,不写入代码。

Python
import os

tools = [{
    "mcpServers": {
        "amap-maps": {
            "command": "npx",
            "args": ["-y", "@amap/amap-maps-mcp-server"],
            "env": {
                "AMAP_MAPS_API_KEY": os.environ["AMAP_MAPS_API_KEY"],
            },
        }
    }
}]

配置完成后,Host 启动 Server,Client 发现地图工具,并将其交给模型。模型面对上海一日游请求时,先调用文本搜索获取候选地点,再多次调用详情查询补充景点信息,最后根据位置和时间生成行程。整个过程不是模型凭记忆编写攻略,而是模型规划与地图工具返回结果的组合。

高德 MCP 多次工具调用与上海一日游结果
高德 MCP 多次工具调用与上海一日游结果

图 3:左侧为地图工具调用记录,右侧为基于查询结果整理的行程。来源:项目运行结果。

图中可以看到 maps_text_searchmaps_search_detail 被连续调用,最终输出包含豫园、东方明珠、陆家嘴等地点的时间安排、地址和交通建议。这个案例体现了 MCP 的价值:地图能力由独立 Server 提供,Host 只负责连接与调度,模型负责决定查询顺序和组织答案。

结果仍需要人工核验。景点营业时间、票价、临时闭园和实时交通都可能变化;路线规划还应加入出发地点、预算、步行承受范围和必须到达时间等约束。涉及预订或支付时,工具必须要求用户确认,不能让模型直接完成不可逆操作。

六、本地 TXT 文件统计:实现最小 MCP Server

高德案例说明如何复用已有 Server,本地 TXT 文件统计器则说明如何把自己的 Python 函数变成 MCP 工具。项目使用官方 Python Software Development Kit,软件开发工具包(SDK)中的 FastMCP,注册了统计文件数量、列出文件名和读取指定文件三个工具。

下面是最小实现。

Python
from pathlib import Path

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("桌面 TXT 文件统计器")


@mcp.tool()
def count_desktop_txt_files() -> int:
    """统计桌面上的 .txt 文件数量。"""
    desktop = Path.home() / "Desktop"
    return len(list(desktop.glob("*.txt")))


@mcp.tool()
def list_desktop_txt_files() -> str:
    """返回桌面上的 .txt 文件名。"""
    desktop = Path.home() / "Desktop"
    files = list(desktop.glob("*.txt"))
    return "\n".join(file.name for file in files)


if __name__ == "__main__":
    mcp.run()

@mcp.tool() 会依据函数签名和文档字符串生成工具模式。Server 启动后,Client 可以通过 tools/list 发现工具,再通过 tools/call 执行。项目还使用 MCP Inspector 检查请求参数、返回内容和错误状态。

MCP Inspector 中的 TXT 工具请求与结果
MCP Inspector 中的 TXT 工具请求与结果

图 4:Inspector 调用本地工具后统计到 2 个 TXT 文件,并在历史记录中显示 tools/call 请求和 isError: false 响应。来源:项目运行结果。

这个最小案例验证了三件事:函数可以被自动描述为工具,Client 可以发现并执行工具,调用结果遵循统一响应结构。它也暴露了本地工具最需要注意的权限问题。文件工具不应默认访问整个用户目录;读取文件时要对路径做规范化,确认最终路径仍位于允许目录内,并限制扩展名、文件大小和编码。Tool 能运行不等于 Tool 可以获得无限权限。

项目依赖中固定了 mcp==1.7.1。MCP Python SDK 仍在演进,部署时应锁定 SDK 版本并针对该版本编写配置和测试,不能把不同版本的启动命令、传输参数和内部属性直接混用。官方 SDK 同时提供 FastMCP 和 Inspector,适合完成工具定义、连接与协议级调试。[^4]

七、Tavily 搜索:同一种接入方式扩展另一类能力

网络搜索与地图查询的业务逻辑不同,但 MCP 接入结构相同。项目通过 tavily-mcp@0.1.4 启动搜索 Server,将 TAVILY_API_KEY 作为环境变量传入,再把 Server 配置交给 Qwen-Agent。

Python
tools = [{
    "mcpServers": {
        "tavily-mcp": {
            "command": "npx",
            "args": ["-y", "tavily-mcp@0.1.4"],
            "env": {
                "TAVILY_API_KEY": os.environ["TAVILY_API_KEY"],
            },
        }
    }
}]

模型可以据此完成新闻搜索、资料检索和网页内容提取。与地图案例相比,搜索结果更容易包含不可信网页内容,因此 Host 需要保留来源链接、限制返回长度、区分网页文本与系统指令,并防止工具返回内容通过提示注入改变 Agent 的权限和目标。

这个扩展示例说明,新增外部能力时,应用不必再为每种服务重写一套模型适配代码。只要 Server 遵循 MCP,Host 就能复用连接、发现和调用流程。

八、Function Calling 与 MCP 如何选择

维度

Function Calling

MCP

主要解决的问题

模型如何表达函数调用

工具、资源和提示如何标准化暴露与连接

工具位置

通常位于应用内部

本地进程或远程服务

工具发现

应用启动时手动注册

Client 通过协议动态发现

通信与生命周期

由应用自行实现

协议定义初始化、能力协商和调用

复用范围

更适合单个应用

更适合多个 Host 或独立工具团队

实现成本

少量工具时较低

初始接入更复杂,但规模化后更统一

典型场景

计算、格式转换、应用内数据库查询

文件系统、代码仓库、地图、搜索和企业服务

选择时可以遵循三个判断。

工具数量少、逻辑归当前应用所有、调用链简单时,直接 Function Calling 更合适。工具需要跨多个 Agent 或编辑器复用,或需要独立部署、独立权限和动态发现时,使用 MCP 更合适。大多数复杂系统不需要二选一:MCP 负责连接工具生态,模型原生 Function Calling 负责产生结构化工具调用。

九、从原型到可用系统的工程控制

9.1 参数与返回值必须可验证

模型生成的参数只能视为不可信输入。Host 应按模式验证类型、范围、枚举和必填项,工具返回值也应有稳定结构。查询结果最好返回字段含义、单位、时间范围和数据来源,而不是只返回一段自然语言。

门票助手当前通过列类型推断图表。更稳妥的做法是让工具显式接收图表模式,或由业务规则决定维度和度量。结构明确后,系统才能对生成结果编写自动检查。

9.2 权限要放在工具侧,而不是提示词中

提示模型不要执行危险 SQL 不是安全边界。数据库需要只读账号和网络隔离,SQL 需要语法解析和允许表检查;文件工具需要允许目录;地图和搜索密钥需要最小权限;有副作用的操作需要用户确认和幂等设计。

MCP Server 应按能力拆分,避免一个 Server 同时拥有不相关的高权限。Host 还应允许用户查看即将调用的工具和关键参数,对写文件、发消息、提交订单等操作设置确认步骤。

9.3 工具链需要可观测

至少记录请求标识、工具名、参数摘要、Server、开始时间、耗时、结果大小、错误类型和模型最终结论。敏感参数不写入日志。连续工具调用出现问题时,需要区分模型选错工具、参数不合法、Server 连接失败、外部 API 失败和结果解释错误。

MCP Inspector 适合验证协议层和单个工具,完整 Agent 还需要端到端任务集。测试问题应覆盖正常输入、边界日期、空结果、超大结果、权限拒绝、超时和恶意输入。

9.4 控制工具数量和上下文开销

Host 将大量工具定义一次性传给模型,会增加上下文长度,也可能降低工具选择准确率。工具较多时,应按任务、权限或领域筛选工具,只向模型暴露当前需要的子集。多步调用还会增加模型往返和工具结果回填成本,应压缩中间结果,并避免把无关原始数据全部送回模型。官方 MCP Client 最佳实践同样强调工具筛选、结果大小和执行资源限制。[^5]

十、项目结果与局限

项目完成了三类验证。

门票助手打通了自然语言问题、SQL 生成、数据库执行、表格回填和自动可视化,并通过三次迭代支持多类别数据。高德地图 MCP 完成了工具发现、多次地点查询和一日游行程生成。FastMCP 本地服务成功暴露 TXT 统计工具,并通过 Inspector 验证 tools/call 的请求与响应。

这些结果证明工具链路能够工作,但不等于已经达到生产标准。当前门票助手没有给出查询正确率、工具选择准确率、执行延迟和并发性能的量化评估;SQL 安全和图表语义仍需加强。旅行规划依赖外部数据质量,且没有覆盖预订确认和实时交通。TXT 工具验证了协议流程,但权限范围和路径校验仍需收紧。

后续改进应优先建立一套可复现任务集,对每个问题同时检查工具选择、参数、执行结果和最终答案;随后再补充只读权限、结构化返回、审计日志和用户确认机制。工具越强,验证和权限控制越不能省略。

小结

Function Calling 让模型从生成文本扩展为生成结构化调用意图,MCP 则让外部能力从应用内私有实现扩展为可发现、可连接、可复用的标准服务。

门票助手说明了应用内工具如何从 SQL 查询演进到连续分析和自动可视化;高德旅行规划说明了 Agent 如何组合多个远程工具结果;TXT 统计器说明了本地函数如何通过 FastMCP 变成标准工具。三者共同指向同一个工程结论:模型负责理解、选择和组织,程序负责权限、执行和验证。

END