Function Calling 结果缓存:缓存工具结果,也要缓存权限上下文

发布时间:2026/7/7 12:28:16
Function Calling 结果缓存:缓存工具结果,也要缓存权限上下文 Function Calling 结果缓存缓存工具结果也要缓存权限上下文一、工具结果缓存不能只看参数相同Function Calling 里有些工具调用很适合缓存比如查询配置、读取公开文档、获取非实时指标。缓存能省下游压力也能减少延迟。但工具结果往往和用户权限、租户、数据版本有关。参数一样不代表结果可以共享。比如同样查询项目列表不同用户看到的结果不同同样查询库存状态不同时间点结果不同。缓存 key 如果只用函数名和参数迟早会把别人的数据返回出去。工具缓存的第一条规则权限上下文必须进 key。我做内部工具平台时遇到过类似情况。一个查询接口的缓存 key 是tool:list_orders:{status}看似没问题。但有一天财务发现能看到不属于自己部门的订单。排查后发现订单列表接口返回的数据会经过租户权限过滤接口内部会根据 user_id 做数据隔离但缓存层不知道这一点。A 用户查了 pending 状态的订单缓存了 10 条数据B 用户也查 pending缓存直接返回了 A 的结果而 B 不应该看到 A 部门的订单。缓存 key 里加tenant_id和permission_hash后这个问题才解决。二、缓存 key 要包含调用者、策略和数据版本工具结果由工具名、参数、租户、权限版本、数据版本和策略版本共同决定。flowchart TD A[工具调用] -- B[参数规范化] C[用户和租户] -- F[缓存 Key] D[权限版本] -- F E[数据版本] -- F B -- F F -- G{缓存命中} G --|是| H[返回缓存结果] G --|否| I[执行工具] I -- J[写入缓存]权限版本很关键。用户角色变了旧缓存要失效。否则权限系统改了缓存还在装没听见。实时检查权限版本可以用一个全局递增的版本号或时间戳每次权限变更时更新缓存 key 里包含这个版本号。权限表更新后版本号递增所有包含旧版本号的缓存自动失效。数据版本同样重要。上游数据 ETL 更新后前一天的实际数据可能已经变化。缓存必须知道引用的数据快照是哪个版本。三、用结构化 key 避免漏字段下面示例用 Go 构造 key避免手写字符串随便拼。type ToolCacheKey struct { Tool string json:tool TenantID string json:tenant_id PermissionVer string json:permission_ver DataVer string json:data_ver ArgsHash string json:args_hash } func BuildToolCacheKey(k ToolCacheKey) (string, error) { if k.Tool || k.TenantID || k.PermissionVer || k.ArgsHash { return , fmt.Errorf(missing cache key field) } raw, _ : json.Marshal(k) sum : sha256.Sum256(raw) return tool: hex.EncodeToString(sum[:]), nil }缓存 key 的字段越清楚代码评审越容易发现风险。工具缓存不是小优化它可能直接影响权限边界。参数规范化也很重要。参数顺序不同{a:1,b:2}和{b:2,a:1}应该生成相同 key。参数里有时间戳、随机数这类与数据无关的字段应该剔除。如果参数里包含大段文本建议先 hash 再拼接避免 key 长度失控。四、缓存内容也要分级不是所有工具结果都适合缓存。公开配置可以缓存长一点用户私有数据要短缓存或不缓存写操作结果最好只缓存幂等确认不缓存完整响应。还要处理数据新鲜度。缓存返回时要带上生成时间和来源版本。Agent 拿到旧数据时应知道这是缓存结果而不是实时查询。否则模型可能用旧结果做新结论。最后缓存失败不能影响主链路。Redis 或本地缓存挂了工具应直接执行。缓存是加速层不应变成单点故障。缓存写入也要谨慎。工具执行成功但缓存写入失败不应该让主请求失败。反过来工具执行失败也不要缓存完整失败结果最多短时间缓存限流或不可用状态避免同一错误被反复放大。还有一个常见坑是缓存粒度太粗。把整个工具响应缓存起来很方便但下游字段一变缓存对象就难兼容。可以只缓存稳定字段或者给响应 schema 加版本。缓存不是数据仓库别把所有历史包袱都塞进去。另外缓存失效不仅靠 TTL。权限变更、数据刷新时应该能主动失效。实现上可以用 Pub/Sub 通知或数据库 binlog 触发但要注意失效的延迟和一致性。主动失效如果延迟太久和被动 TTL 也就没有区别了。缓存命中率指标要加权限命中的子项。总命中率 95% 听起来很好但如果其中有 10% 返回了权限错误的数据那这些命中比未命中更危险。监控拆分到权限维度才能发现这类隐蔽问题。五、总结Function Calling 结果缓存要把权限上下文放进 key。工具名和参数不够租户、权限版本、数据版本和策略版本都要考虑。缓存内容按敏感度分级返回时带数据新鲜度。缓存能省钱省时间但绝不能省掉权限判断。一条错误的缓存返回可能比不缓存更严重——因为它让人以为系统工作了。