【Agent】工具调用的安全边界:从模型输出到程序执行

工具调用的安全边界

前言

Agent 可以让模型根据任务选择工具,例如查询天气或搜索景点。但模型生成的 Action 只是一段文本,可能格式错误,也可能请求调用不该使用的工具。如果程序把它直接当作代码执行,模型输出就越过了程序应当控制的边界。

本文先介绍 Agent 工具调用的基本安全原则,再以我近期在写的旅行 Agent (该项目源码在文末已贴出)为例,看看程序如何把模型生成的 Action 转换成一次受限的工具调用,以及这套实现仍有哪些局限。

1.安全边界

模型输出的 Action 是调用工具的请求,不是程序必须执行的指令 。

以旅行Agent中的天气查询为例,即使模型写出了 get_weather(city="北京"),程序也要先判断:这是不是允许的调用形式?get_weather 是否已注册?参数能否传给这个工具?只有通过检查,才进入执行环节。

这条原则适用于各种 Agent:由模型提出意图,由程序决定执行范围 。 工具的能力通常比一段聊天文本更具体,也可能产生真实影响,因此不能仅凭模型"想调用"就运行任意代码。程序需要把可调用的工具和可接受的输入限定在明确的范围内。

旅行 Agent 的 v0.1.0 版本采用文本形式的 Action。它从模型回复中提取 Action,解析工具名与参数,再交给工具注册表执行;执行结果作为 Observation 进入下一轮。这里的关键分界点是:模型只生成文本,真正选择函数并调用它的是程序。接下来沿着这条路径看 Action 如何被解析。

2. Action 解析

沿用前面的例子,模型可能输出 Action: get_weather(city="北京")。程序先提取 Action: 后的文本,再判断它是否符合约定的工具调用形式。下面是旅行 Agent v0.1.0 中负责解析调用的代码,完整保留了这段方法的检查路径:

python 复制代码
def _parse_tool_call(
        self,
        action_str: str,
) -> tuple[str, dict[str, Any]] | None:
    """解析工具调用,只允许关键字参数。"""

    try:
        expression = ast.parse(
            action_str,
            mode="eval",
        ).body
    except SyntaxError:
        return None

    if not isinstance(expression, ast.Call):
        return None

    if not isinstance(expression.func, ast.Name):
        return None

    if expression.args:
        return None

    # 后续参数处理省略

这段代码中的 ast.parse(..., mode="eval") 负责把文本解析成 Python 的抽象语法树,并要求输入是一个表达式。它只是在识别结构,并没有执行表达式。随后程序要求节点必须是函数调用 ast.Call,函数名必须是简单名称 ast.Name,并拒绝位置参数。

参数值再通过 ast.literal_eval 转换:

python 复制代码
for keyword in expression.keywords:
    if keyword.arg is None:
        return None

    try:
        kwargs[keyword.arg] = ast.literal_eval(keyword.value)
    except (ValueError, TypeError, SyntaxError):
        return None

return expression.func.id, kwargs

ast.literal_eval 只接受字符串、数字、布尔值、列表等字面量,不能执行函数调用或任意表达式。当前实现因此没有使用 eval,但这仍然只是受限解析,不是完整沙箱:代码没有设置输入长度、嵌套深度等限制,也没有覆盖所有业务层面的风险。

解析完成后,程序只返回工具名称和参数字典。此时还没有调用任何函数;工具是否真的允许使用,要交给下一层的注册表判断。

3. 工具执行

解析出工具名和参数后,程序还不能仅凭这个名字调用函数。旅行 Agent 把可用工具预先注册到 ToolRegistry,执行时只从注册表中查找。下面的代码展示了注册、查找和参数绑定的关键路径;为控制篇幅,省略了描述信息和执行记录相关代码。

python 复制代码
def register_function(
    self,
    name: str,
    func: Callable,
    description: str = "",
    parameters: list[ToolParameter] | None = None,
) -> None:
    if name in self._tools:
        raise ValueError(f"工具已存在: {name}")

    self._tools[name] = func
    # 描述信息和参数信息的保存省略

def has_tool(self, name: str) -> bool:
    return name in self._tools

def execute(self, name: str, **kwargs) -> str:
    """校验参数后执行工具。"""
    start_time = time.perf_counter()

    if not self.has_tool(name):
        result = f"错误:未定义的工具 '{name}'"
        # 执行记录相关代码省略
        return result

    func = self._tools[name]
    tool_signature = signature(func)

    try:
        bound_arguments = tool_signature.bind(**kwargs)
    except TypeError as exc:
        result = f"错误:工具 '{name}' 参数错误: {exc}"
        # 执行记录相关代码省略
        return result

    try:
        result = str(func(**bound_arguments.arguments))
    except Exception as exc:
        result = f"错误:工具 '{name}' 执行失败: {exc}"
        # 执行记录相关代码省略
        return result

    # 执行记录相关代码省略
    return result

注册表在这里充当工具白名单:模型可以写出任意工具名,但程序只会查找事先注册的名称。找到函数后,signature.bind(**kwargs) 检查这些关键字参数能否绑定到函数签名,例如是否缺少必填参数、是否传入了不接受的参数。绑定成功,程序才调用这个已注册的函数,并把结果转成字符串返回给 Agent。

需要注意,参数绑定不等于参数校验完成。signature.bind() 不检查参数值是否符合类型标注,也不判断城市名等输入在业务上是否有效。这些检查如果需要,应由工具或额外的校验逻辑负责。

4. 实现局限

沿着前面的调用路径看,旅行 Agent 的 v0.1.0 已经建立了几道边界:程序解析模型生成的文本,只接受特定的调用形状和字面量参数,再从注册表中选择已注册的工具。它不使用 eval,但也不是完整沙箱。 解析器没有限制输入长度或字面量的嵌套深度;即使调用形式合法,已注册工具仍可能执行网络请求等实际操作。

参数检查也有明确范围。signature.bind() 只判断参数能否绑定到函数签名,不检查类型标注,也不判断输入是否符合业务规则。例如,一个参数在签名中标注为 str,并不意味着绑定时会拒绝其他类型的字面量。工具自身仍需按业务需要校验输入,并控制它能够产生的实际影响。

此外,当前解析代码把关键字参数依次写入字典。如果 Action 中重复使用同一个参数名,后面的值会覆盖前面的值;这段代码没有显式拒绝重复参数。因此,"通过解析并成功绑定"只能说明调用符合当前规则,不能证明输入安全或结果正确。设计其他 Agent 时,也应逐层说明每项检查实际保证了什么,再针对工具的能力补上相应约束。

后记

Agent 工具调用的关键,是把模型生成的 Action 当作请求,由程序决定能否执行 。

先明确允许哪些工具和输入,再让程序逐层检查,最后由工具承担自身的业务校验。

源码

本文项目案例源码:travel-agent-lab(v0.1.0)。文中的实现分析均以该版本为准。

相关推荐
土星云SaturnCloud3 小时前
基于 PP-OCR 的车牌识别:从数据整理到土星云 SE110S 系列边缘部署
人工智能·ai·ocr·边缘计算
zz_nj3 小时前
中兴微方案 MIFI
人工智能
十二同学啊3 小时前
Python 工程化详解:项目结构、依赖管理、代码质量、测试与部署
java·开发语言·python
打工仔折腾 AI3 小时前
鸿蒙移动端直连 Windows 远程桌面配置与性能调优实践
人工智能·windows·后端·python·华为·性能优化·harmonyos
AI_work3 小时前
43 号令下招投标全流程留痕分析:电子卷宗 15 年保存与投标人文档合规体系重构
大数据·人工智能·ai·重构
乐橙开放平台4 小时前
跨地域监控难管理?通过 GB28181 把各地海康大华乐橙摄像头注册到云端平台
开发语言·笔记·物联网·自动化·php·音视频·智能家居
动词ing4 小时前
【学习笔记】51单片机 模块化编程
笔记·学习·51单片机
技灵AI4 小时前
AI 开箱视频怎么做?用创意开场带出商品展示
人工智能·prompt·aigc
王亚博_Abel4 小时前
AI 编程时代怎么选前端框架?同一个应用让 Agent 用 12 种框架各写 5 遍,我整理出这套选型方法
前端·人工智能·前端框架