Function Calling 权限模型设计工具调用需要的最小权限原则一、一个查询工具差点删库的故事Agent 调用工具的机制本质是让模型生成参数并触发后端函数。如果权限控制不到位模型的一句幻觉就可能触发危险操作。真实案例一个客服 Agent 的工具集中包含了执行 SQL函数。某次对话中LLM 将用户的查一下我的订单状态误解析为 DROP 操作的前置查询生成了删除语句的参数。幸好 SQL 执行层有只读库的限制否则后果不堪设想。问题根源不在于模型不够智能而在于权限模型的设计给 Agent 的工具权限应该和操作敏感度成反比。二、权限模型的分层设计原理核心原则是最小权限原则Least Privilege每个工具函数只拥有完成其任务所必需的最小权限集合。三、Go 实现分层权限模型package auth import ( context fmt sync time ) // Permission 定义单个权限 type Permission string const ( PermOrderRead Permission order:read // 读订单 PermOrderWrite Permission order:write // 写订单退款/改地址 PermOrderDelete Permission order:delete // 删除订单极高敏感 PermUserRead Permission user:read // 读用户信息 PermSystemAdmin Permission system:admin // 系统管理 ) // ToolDefinition 工具定义包含所需权限 type ToolDefinition struct { Name string // 工具名 Description string // 功能描述给 LLM 的参数之一 Permissions []Permission // 调用该工具所需的权限 RiskLevel RiskLevel // 风险等级 NeedApproval bool // 是否需要人工审批 } // RiskLevel 风险等级 type RiskLevel int const ( RiskLow RiskLevel iota // 只读操作 RiskMedium // 写入操作可逆 RiskHigh // 写入操作不可逆 RiskCritical // 系统级操作 ) // ToolRegistry 工具注册表 权限校验 type ToolRegistry struct { tools map[string]*ToolDefinition mu sync.RWMutex } // RegisterTool 注册工具启动时一次性注册 func (tr *ToolRegistry) RegisterTool(td *ToolDefinition) error { tr.mu.Lock() defer tr.mu.Unlock() if _, exists : tr.tools[td.Name]; exists { return fmt.Errorf(工具 %s 已经注册, td.Name) } tr.tools[td.Name] td return nil } // ValidateCall 校验工具调用权限 func (tr *ToolRegistry) ValidateCall( ctx context.Context, toolName string, userPerms []Permission, ) error { tr.mu.RLock() td, exists : tr.tools[toolName] tr.mu.RUnlock() if !exists { return fmt.Errorf(工具 %s 未注册, toolName) } // 检查用户是否拥有该工具所需的所有权限 userPermSet : make(map[Permission]bool, len(userPerms)) for _, p : range userPerms { userPermSet[p] true } for _, required : range td.Permissions { if !userPermSet[required] { return fmt.Errorf(权限不足: 工具 %s 需要 %s, toolName, required) } } // 高风险操作标记供上层做二次确认 if td.RiskLevel RiskHigh { fmt.Printf([AUDIT] 高风险调用: tool%s, risk%d\n, toolName, td.RiskLevel) } return nil } // RateLimiter 频率控制——防止单个用户滥用工具 type RateLimiter struct { windows map[string]*rateWindow mu sync.Mutex } type rateWindow struct { count int resetTime time.Time } // Allow 检查是否允许本次调用 // userIDtoolName 作为限流 key func (rl *RateLimiter) Allow(userID, toolName string, maxPerMinute int) bool { rl.mu.Lock() defer rl.mu.Unlock() key : fmt.Sprintf(%s:%s, userID, toolName) win, exists : rl.windows[key] now : time.Now() if !exists || now.After(win.resetTime) { rl.windows[key] rateWindow{count: 1, resetTime: now.Add(time.Minute)} return true } if win.count maxPerMinute { return false } win.count return true }初始化工具注册表示例var registry ToolRegistry{tools: make(map[string]*ToolDefinition)} func init() { // 查询订单只读低风险 registry.RegisterTool(ToolDefinition{ Name: query_order, Permissions: []Permission{PermOrderRead}, RiskLevel: RiskLow, }) // 申请退款写入且不可逆中高风险 registry.RegisterTool(ToolDefinition{ Name: refund_order, Permissions: []Permission{PermOrderWrite}, RiskLevel: RiskHigh, NeedApproval: true, // 需要人工确认 }) // 删除订单极高风险 registry.RegisterTool(ToolDefinition{ Name: delete_order, Permissions: []Permission{PermOrderDelete, PermSystemAdmin}, RiskLevel: RiskCritical, NeedApproval: true, }) }四、边界分析与 Trade-offs权限粒度问题过细的权限会导致注册表膨胀。建议按资源操作两个维度建模如order:read借鉴 AWS IAM 的 ARN 模式。LLM 绕过风险LLM 可能通过组合多个低权限工具达到高权限效果。例如先query_user后query_order获取不属于自己的订单信息。这需要在数据层加租户隔离SQL 层面加WHERE tenant_id ?。审批机制的代价高风险操作加人工审批会影响响应速度。实践中可以设置阈值退款金额 100 元自动通过退款金额 100 元触发人工审批Schema 暴露安全给 LLM 的工具描述不要透露内部实体名和表结构。用业务语言替代技术语言。五、总结Function Calling 的权限模型不是一道选择题而是一个分层的防御体系身份认证——确认调用者是谁RBAC——确认调用者能做什么范围限制——确认调用者能访问哪些数据频率控制——防止滥用执行沙箱——在受限环境中运行工具审计日志——记录每一笔操作记住一个原则默认拒绝显式授权。每个工具函数的默认权限应该是所有人禁止只有明确配置了权限策略的用户才能调用。