Administrator
发布于 2026-07-28 / 3 阅读
0
0

04-Function Calling 实战:让模型会用工具,也不越权

English
中文

Function Calling 容易给人一种错觉:模型已经可以操作系统了。实际情况是,模型只生成“建议调用哪个工具、使用什么参数”。真正执行函数的仍是应用代码。

这个区别决定了安全责任。模型可以提出查询订单,但它不能绕过登录状态,也不能因为用户一句话就重启服务。

一次完整调用有两次模型请求

以只读工具 get_order_status 为例:

  • 应用把用户问题和工具 Schema 发给模型;
  • 模型返回工具名与 JSON 参数;
  • 应用校验工具名、参数、用户权限和调用频率;
  • 应用执行查询,把结构化结果作为 tool 消息回传;
  • 模型根据工具结果生成面向用户的答复。

少了第二次请求,用户只能看到函数结果,模型无法把结果组织成自然回复。少了应用校验,则相当于把外部输入直接接到了内部函数。

Schema 是输入约束,不代表绝对安全

工具定义可以限制 order_id 必须是字符串并要求该字段存在,但模型仍可能返回不存在的工具、额外参数或越权订单号。应用需要使用允许名单,并按当前登录用户重新检查资源归属。

import logging

LOGGER = logging.getLogger(__name__)
ALLOWED_TOOLS = {"get_order_status"}


def validate_tool_call(name: str, arguments: dict, user_id: str) -> str:
    """校验工具调用;arguments 是模型参数,user_id 是已认证用户。"""
    try:
        # 工具名必须来自应用允许名单,不能按模型文本动态查找函数。
        if name not in ALLOWED_TOOLS:
            raise ValueError(f"不允许的工具:{name}")

        order_id = arguments.get("order_id", "").strip()
        if not order_id or len(order_id) > 64:
            raise ValueError("order_id 格式无效")

        # 此处还要查询数据库,确认订单属于当前已认证用户。
        if not order_belongs_to_user(order_id, user_id):
            raise PermissionError("当前用户无权查询该订单")
        return order_id
    except (ValueError, PermissionError):
        LOGGER.warning("工具参数或权限校验失败", exc_info=True)
        raise

不要使用 globals()[name]() 或拼接 shell 命令来执行模型返回值。工具路由应是显式字典,每个入口都有固定参数模型、超时和错误处理。

高风险动作要多一道门

查询天气和查询订单通常是只读操作。删除文件、恢复备份、重启服务和发送邮件会改变外部状态,需要更严格的流程:展示将要执行的对象和影响,取得明确确认,再由有权限的应用身份执行。

“模型判断有必要”不能替代授权。确认信息也不能只写“是否继续”,应包含精确对象,例如服务名、环境、动作和预期影响,避免用户确认了另一件事。

工具结果也不能直接相信

工具可能超时、返回空值,或者产生与 Schema 不一致的数据。回传模型前先做结构校验,并标记数据来源和时间。错误信息只提供解决问题所需内容,不把数据库连接串、内部堆栈和密钥交给模型。

日志至少记录请求关联 ID、工具名、校验结果、耗时和错误类型。参数中如果有个人信息,应做脱敏。高风险动作还要记录谁确认、确认了什么,但不要把认证凭据写进日志。

验收这条链路

准备正常参数、缺失参数、越权资源、不存在工具、工具超时和用户拒绝确认等样例。检查应用是否在正确阶段停止,并确认模型无论怎样输出,都不能绕过允许名单和权限检查。

能通过这些失败样例,Function Calling 才算一条可控的应用链路。


评论