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」的写法来自该页。 - 本节的白名单校验与三档处置为工程框架,官方页只给正常流程与一个未知函数示例,未规定统一的拒绝策略。
七、动手试
- 把你的工具清单抽成一份常量,让请求构造与本地分发都从它派生,验证两边条数一致。
- 给
dispatch()加一个审计日志:记录被拒绝的工具名与请求时间。想一想:这类日志在什么场景下比错误堆栈更有用? - 设计一条「模型连续两次请求未注册工具」的处理规则:是继续回 error,还是直接结束并告知用户?写出你的判断依据。

TRAE-AI编程


更多建议: