组件开发 · 第 3 篇 / 共 3 篇 —— 权限
定义组件权限
这一篇主要给接入第三方消息通道的组件用。别人的消息从你写的接口推进来,你就得回答两个问题:这条消息的发送者能不能调本组件的命令(TOOL_POLICY),这次推送触发的任务能用哪些工具(TRIGGER_TOOL_POLICY)。
前置:组件能接收外部消息、能把消息交给 Agent 处理。纯本地工具类组件不对外接消息,基本用不到这一篇。
1. 命令级:谁能调用本组件的命令
在组件类里声明 TOOL_POLICY,键是裸命令名(不带组件名前缀),值决定谁能用。
class NapcatComponent(BaseCell):
# 键是裸命令名,不带组件名前缀
TOOL_POLICY = {
"reset_config": "deny", # 所有人禁用
"add_user": "admin", # 仅管理员
"shutdown": {"mode": "blacklist", "users": ["12345"]}, # 名单内用户禁用
"send_private": {"mode": "whitelist", "users": ["10001"]}, # 仅名单内用户可用
}
TOOL_POLICY_DEFAULT = "allow" # 未声明的命令默认放行,改成 "deny" 则未声明的全禁五种取值
| 值 | 效果 |
|---|---|
"allow" | 放行(默认) |
"deny" | 谁都不许用 |
"admin" | 仅管理员可用 |
{"mode": "whitelist", "users": [...]} | 仅名单内用户可用 |
{"mode": "blacklist", "users": [...]} | 名单内用户禁用,其余可用 |
TOOL_POLICY_DEFAULT 控制未声明命令的默认行为:默认是 allow;改成 deny 后,只有显式放行的命令能调,适合高敏感组件。
2. 管理员怎么判定
BaseCell 已有默认实现:看 platform_context 里的 tool_allowed,没有则看 is_admin。QQ 通道会自动传 tool_allowed,所以大多数情况不用写代码。需要接自己的管理员名单时,重写 is_policy_admin。
# 默认实现(BaseCell 自带)
# tool_allowed 优先,其次 is_admin,都没有则视为非管理员
# 接自己的管理员名单时重写
def is_policy_admin(self, platform_context=None) -> bool:
user_id = str((platform_context or {}).get("user_id") or "")
return user_id in {"10001", "10002"} # 你的管理员 QQ 号判定内部报错会被当作非管理员处理,所以这个方法里不要抛异常,自己兜底返回 False。
3. 完整例子:只让管理员维护城市,所有人查天气
add_city / remove_city 需要管理员,query 未声明 → 走默认放行。
class WeatherBot(BaseCell):
TOOL_POLICY = {
"add_city": "admin", # 加城市要管理员
"remove_city": "admin", # 删城市要管理员
# query 未声明 → 默认所有人可用
}
def is_policy_admin(self, platform_context=None) -> bool:
user_id = str((platform_context or {}).get("user_id") or "")
return user_id in {"10001", "10002"}
def _cmd_query(self, city: str = "", **kwargs) -> Dict[str, Any]:
return {"success": True, "weather": f"{city} 晴 25℃"}
def _cmd_add_city(self, city: str = "", **kwargs) -> Dict[str, Any]:
...用户发「查北京天气」→ 正常执行;发「添加上海」→ Agent 调 add_city 被拦,工具直接返回错误,Agent 据此告知用户无权限。
# Agent 调用 add_city 时被拦,工具返回:
{
"success": False,
"error": "命令 add_city 仅管理员可用",
"_source": "component:weather_bot"
}
# Agent 看到这个错误会向用户说明无权限,而不是重试4. 任务级:本组件触发的任务能用哪些工具
TOOL_POLICY 只管自己组件内的命令。想让「经本组件触发的任务」禁用其他工具(比如 shell、sub_agent),声明 TRIGGER_TOOL_POLICY。支持工具名,也支持 工具名.命令名 的命令级精确拦截。
class NapcatComponent(BaseCell):
# 经本组件转发的消息,Agent 不许用这些工具
TRIGGER_TOOL_POLICY = {
"mode": "blacklist",
"tools": ["shell", "sub_agent", "qq_files.download"],
}效果:QQ 用户发消息 → 组件转发触发 Agent → 调 shell 被拦,调 qq_files.download 被拦,但 qq_files 的其他命令不受影响。触发请求的 source 必须等于组件的 cell_name,声明才会生效。
class MonitorComponent(BaseCell):
# 告警触发的任务只许用这两个工具
TRIGGER_TOOL_POLICY = {
"mode": "whitelist",
"tools": ["monitor", "napcat.send_msg"],
}
def _on_alert(self, alert):
"""告警回调:触发 Agent 处理,任务自动受限"""
httpx.post(f"{self._base_url}/api/component/event", json={
"session_id": "monitor:alerts",
"message": f"CPU 超阈值: {alert}",
"source": self.cell_name, # ← 按这个字段匹配上面的声明
"platform_context": {
"platform": "monitor",
"user_id": alert.user_id,
},
})5. 触发时临时传权限
不写代码声明,直接在 /api/component/event 的 platform_context 里传。
| 传法 | 效果 |
|---|---|
"tool_allowed": false | 启用系统默认黑名单(shell、file、component、sub_agent 等约 20 项全禁),适合转发普通用户消息 |
tool_policy blacklist | 自定义黑名单,与默认黑名单合并(只能加不能减) |
tool_policy whitelist | 只放行列出的工具,其余全禁(默认黑名单不参与) |
POST /api/component/event
{
"session_id": "qq:12345:group",
"message": "帮我处理下这个",
"source": "napcat",
"platform_context": {
"platform": "qq",
"user_id": "12345",
"tool_allowed": false
}
}转发示例:普通用户只给 qq_files,管理员不带策略字段 → 完全不限制。
def _trigger_agent(self, message: str, user_id: str):
is_admin = user_id in self._admins
ctx = {"platform": "qq", "user_id": user_id, "message_type": "group"}
if not is_admin:
ctx["tool_policy"] = {"mode": "whitelist", "tools": ["qq_files", "napcat.send_msg"]}
# 管理员不带任何策略字段 → 不限制
httpx.post(f"{self._base_url}/api/component/event", json={
"session_id": f"qq:{user_id}",
"message": message,
"source": self.cell_name,
"platform_context": ctx,
})优先级:请求里显式传了 tool_policy 时,以调用方为准,组件声明被覆盖。没声明 TRIGGER_TOOL_POLICY 的组件,行为完全不变。
决策速查
| 想要的效果 | 写在哪 |
|---|---|
| 本组件某命令谁都不许用 | TOOL_POLICY = {"xxx": "deny"} |
| 本组件某命令仅管理员 | "xxx": "admin" |
| 本组件某命令仅指定用户 | "xxx": {"mode": "whitelist", "users": [...]} |
| 经本组件触发的任务禁用其他工具 | TRIGGER_TOOL_POLICY blacklist |
| 经本组件触发的任务只许用几个工具 | TRIGGER_TOOL_POLICY whitelist |
| 触发时临时定权限 | platform_context + tool_allowed / tool_policy |
常见坑
- 键名格式不同:TOOL_POLICY 的键是裸命令名(add_user);TRIGGER_TOOL_POLICY 的键是工具名或 工具名.命令名(qq_files.download)。写错不报错,只是不生效。
- 白名单会绕过默认黑名单:whitelist 里写 shell 就能放行 shell,只对可信调用方用白名单。
- 黑名单只能加不能减:blacklist 模式自动与系统默认黑名单合并,想放行默认黑名单里的工具只能改用 whitelist。
- 命令级拦截依赖 command 参数:qq_files.download 这种键只在调用时显式传了 command 才命中;没传时按裸工具名匹配。
- WebUI 内部调用不传 user_id:whitelist 模式的命令在 WebUI 里也会被拦;blacklist 模式不传 user_id 则放行。
权限拦截是「事后拒绝」而不是「不出现在工具列表」:被禁的工具仍然对 LLM 可见,调用时返回 error。想让模型一开始就不尝试,靠提示词约束,不要指望权限表。
