codecamp

Grok 未知工具白名单

「模型怎么会调用一个不存在的函数?」——会的。多轮对话里残留的旧工具名、提示词里提到但没注册的操作、模型对工具描述的自由发挥,都可能让它请求一个你没实现的工具。最常见的处理是 registry[name] 直接取值,然后一个 KeyError 把整个 Agent 循环打断。

这一节把「未知工具」当成协议内的正常分支,而不是异常。

一、问题不是「模型会不会乱调」,而是「你怎么回它」

工具调用的流程是固定的五步:定义工具、随请求带上、模型返回 tool_call、你本地执行、把结果回传、模型继续。官方示例里对未知函数的处理很直接:构造一条 {"error": "Unknown function: ..."} 作为工具输出回传。

这条处理揭示了一个关键点:未知工具不是程序错误,是模型的一次「请求失败」。它应该被当成业务数据回给模型,而不是抛异常给运行时。前者模型能看到并调整,后者只会让循环静默死掉。

二、最小可运行示例:白名单 + 显式拒绝

下面的脚本不联网、不需要真实密钥。它把「未注册」「参数不匹配」都转成可回传的工具结果,而不是异常。

"""工具分发:白名单校验,未知工具与参数错误都回传为结果。"""

import json

def make_temp(location: str, unit: str = "celsius") -> dict:
    return {"location": location, "temperature": 22 if unit == "celsius" else 72}

REGISTRY = {"get_temperature": make_temp}

def dispatch(name: str, args: dict) -> dict:
    """返回可回传的 function_call_output;任何失败都变成 error 字段。"""
    if name not in REGISTRY:
        return {"output": json.dumps({"error": f"未注册工具:{name}"}, ensure_ascii=False)}
    try:
        result = REGISTRY[name](**args)
    except TypeError:
        return {"output": json.dumps({"error": "参数与函数签名不匹配"}, ensure_ascii=False)}
    return {"output": json.dumps(result, ensure_ascii=False)}

def offline_check() -> None:
    cases = [
        ("已知工具", "get_temperature", {"location": "上海"}),
        ("未知工具", "delete_all", {}),
        ("参数不匹配", "get_temperature", {"city": "上海"}),
    ]
    for label, name, args in cases:
        out = dispatch(name, args)
        print(f"{label}:{json.loads(out['output'])}")

if __name__ == "__main__":
    offline_check()

真正上线时,REGISTRY 与请求里 tools 的定义必须来自同一份清单——请求告诉模型有哪些工具,REGISTRY 决定能不能执行。两份各写一遍,是「模型调了但执行不了」的根源。

三、三种处置方式的代价

处置 行为 后果
抛异常 循环中断 任务失败,日志里看不出是模型请求了未注册工具
静默忽略 不回结果 模型缺信息,可能反复请求同一个工具
显式拒绝 回一条 error 结果 模型能调整或收尾,成本可控

三件要强调的事:

  • 拒绝信息要具体。回 {"error": "未注册工具:delete_all"} 比回 {"error": "failed"} 有用得多;模型能据此换一个已注册的工具,或者直接告诉用户「这个操作不支持」。
  • 白名单要单点维护。把工具清单定义成一份常量,请求构造与本地分发都从它派生。两处手写,迟早不一致。
  • 不要用工具的「名字相似」做模糊匹配。delete_user 与 delete_users 差一个字母,模糊匹配会把一次危险操作落到错误的实现上。宁可拒绝。

四、预期输出与异常分支

运行分发器,预期输出:

已知工具:{'location': '上海', 'temperature': 22}
未知工具:{'error': '未注册工具:delete_all'}
参数不匹配:{'error': '参数与函数签名不匹配'}

换成真实流量时,若出现异常,按下面顺序定位:

现象 优先怀疑 第一步动作
循环莫名中断 未注册工具抛异常 改成回传 error 结果
同一工具反复被请求 拒绝信息太模糊 把可用工具名写进 error
工具名对得上却执行失败 白名单与请求不同源 统一到一份清单
危险操作被执行 做了模糊匹配 改精确匹配并收窄白名单

最后一行尤其重要:工具分发的安全边界,不是「模型不会做坏事」,而是「未注册的一律拒绝」。白名单的意义正在于此。

五、判据卡

判断项 该这么做 不该这么做
未知工具 回传 error 结果 抛异常打断循环
拒绝信息 写明未注册的工具名 只回 failed
清单维护 请求与分发同源 两处各写一份
名称匹配 精确匹配 模糊匹配“差不多”的名字
危险操作 白名单收窄到最小集 图省事放开全部

这张卡的重点是把未知当成协议分支。五条里有三条在说:拒绝要可读、要可追溯、要不可绕过。

六、来源与核验

  • 官方 Tools Overview:https://docs.x.ai/developers/tools/overview(核验日期 2026-11-04)。内置工具与自定义函数的分工、工具调用的整体流程以该页为准。
  • 官方 Function Calling:https://docs.x.ai/developers/tools/function-calling(核验日期 2026-11-04)。五步调用流程、function_call_output 回传方式,以及官方示例中「未知函数返回 error」的写法来自该页。
  • 本节的白名单校验与三档处置为工程框架,官方页只给正常流程与一个未知函数示例,未规定统一的拒绝策略。

七、动手试

  1. 把你的工具清单抽成一份常量,让请求构造与本地分发都从它派生,验证两边条数一致。
  2. 给 dispatch() 加一个审计日志:记录被拒绝的工具名与请求时间。想一想:这类日志在什么场景下比错误堆栈更有用?
  3. 设计一条「模型连续两次请求未注册工具」的处理规则:是继续回 error,还是直接结束并告知用户?写出你的判断依据。
Grok 工具参数解析失败处理
Grok Agent 有界循环
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }