先说结论
生产环境中的 Function Calling 远不止定义 JSON Schema,协议统一、权限边界、执行可观测是三大硬门槛。
三层架构(service/domain/infra)能有效隔离业务逻辑与工具实现,但代价是增加了抽象层与维护成本。
双重权限过滤与渐进式 skill 披露能显著减少无效 token 消耗,但引入意图识别可能增加首次调用的延迟。
从踩坑反思的角度,拆解生产级工具系统在协议、权限、可观测性的真实取舍与设计代价。
先说结论:生产级工具系统到底在解决什么
很多人对 Function Calling 的理解还停留在“定义 JSON Schema -> 大模型调用函数 -> 返回数据”的阶段。但在真实生产环境里,三件事会立刻让你头疼:
- 协议不统一——团队里有人用 Json Schema,有人自定义,工具定义散乱无法复用。
- 权限边界模糊——大模型可能越权调用不该访问的工具,安全风险不可控。
- 执行过程黑盒——用户等了几秒突然出结果,中间发生了什么完全不可知。
这三点,是生产级工具系统必须跨过的门槛。
[图片1] 一张对比图:左侧是简单 demo 的直调用,右侧是生产环境的复杂架构,标注协议、权限、可观测三个环节。
为什么协议统一是第一道坎?自研 vs Json Schema 的取舍
原文作者没有直接采用 Json Schema,而是仿照它定义了一套轻量协议。理由是 Json Schema“有点重,可读性不高”。
站在纯技术角度看,这个选择可以理解。如果你的系统规模不大,团队对 Json Schema 不够熟悉,自研一套更简洁的协议能降低心智负担。而且后期可以通过适配层兼容 Json Schema,不算死路。
但代价也很明显:自研协议意味着你必须自行维护解析、校验、序列化逻辑,同时第三方工具和库的兼容性会打折扣。如果团队后期需要对接外部系统,适配成本会逐步累积。
我更倾向于这样判断:如果你只是内部工具、团队可控,自研没问题;但如果需要开放给第三方或长期维护,Json Schema 的生态成熟度是更好的起点。
三层架构:解耦的甜头与维护的代价
原文将系统拆分为 service、domain、infra 三层。
- domain 层:定义协议接口,包括工具的元数据、入参出参等。这是整个系统的“宪法”。
- service 层:业务编排,比如根据用户权限筛选可见工具、组装 prompt。
- infra 层:技术实现,比如用 Eino 框架处理工具调用,落库用 GORM。
这种拆法好处明显:权限策略调整只改 service,换 AI 框架只改 infra,互不干扰。
但实际跑起来,你可能会遇到一个问题:层与层之间的边界容易模糊。尤其是 service 层,很容易变成“万能胶水层”,既做业务判断又做数据转换,逐渐臃肿。另外,三层架构增加了代码跳转与调试成本,对于小团队来说,可能反而拖慢迭代速度。
所以,三层架构更适合有一定规模、多人协作的项目。如果只是个人项目或 MVP,可能一层简单封装更现实。
[图片2] 一张架构分层示意图,显示 service、domain、infra 之间的依赖关系,以及各层的关键职责。
权限设计:双重过滤真能防越权?
原文采用了两层权限过滤:
- 获取工具列表时——根据用户的 user_id、org_id、角色(管理员/普通)筛选可见工具,只把有权限的 tools 发给大模型。
- 调用具体 tool 时——再次校验权限,防止大模型在推理过程中绕开第一层限制。
双重过滤的策略,在逻辑上确实比单层更安全。但值得注意的是:权限数据本身也依赖上层应用的正确传递。如果上游用户身份信息被篡改或遗漏,再多的过滤也无用。
另外,权限粒度需要仔细设计。如果给每个工具都分配独立的 capability,后期管理会非常麻烦。原文建议按角色分配 resource capability,类似 RBAC 的思路,这个方向是对的。
性能优化:从全量注入到意图识别的进化
原文提到了三种优化策略:
- 基础:无权限用户直接走纯文本,跳过 ReAct,省 token。
- 渐进式 skill 披露:先只暴露工具的基本信息(名称、一句话描述),等大模型选中后再提供详细参数。
- 意图识别:让大模型自己判断需要什么工具,然后去“搜索”工具,而不是一股脑全塞进去。
前两种已经比较成熟,代价是增加了开发复杂度。
而意图识别虽然激进,但它真正解决了“工具数量爆炸”时上下文塞不下的问题。代价也很明显:首次调用延迟会增加,因为大模型需要多一轮“思考”。如果用户对响应速度敏感,这个方案不一定适用。
所以一个更现实的策略是:根据用户历史行为或业务场景,预加载最可能用到的 top K 个工具,而不是完全取消全量注入。
用户体验:让用户看到“AI 在干嘛”比什么都重要
很多 AI 应用只关心最终结果,忽略了过程的可感知性。原文通过 tool_call_started 和 tool_call_finished 事件,让前端实时展示执行进度。
此外,错误预校验也值得借鉴。如果工具调用参数格式错误,允许大模型重试两次;但如果是必要参数缺失(需要用户补充),才结束本轮对话。这避免了无意义的循环,提升了用户体验。
不过,这种设计依赖于错误类型的准确区分。如果模型误判,可能导致对话提前终止或无限重试。在实际中,可以增加重试次数上限和人工干预入口。
适用边界与权衡
综合来看,原文的设计思路适合:
- 中大型项目,团队有 3 人以上,有明确分工。
- 工具数量超过 20 个,权限粒度要求细致。
- 对安全性和可观测性有较高要求。
不适合:
- 个人开发者快速验证 MVP,三层架构和双重过滤会增加不必要的复杂度。
- 工具数量极少(<5),权限简单,直接硬编码反而更快。
- 团队对 Go 或 Eino 框架不熟悉,学习成本太高。
如果让我选,我更倾向于从最简单的单层协议开始,根据实际痛点逐步引入架构拆分和优化策略。不要一开始就追求完美,先跑通,再迭代。
你可能会问:那我到底该不该自研协议?这个问题没有标准答案,取决于你的团队规模、技术栈和长期规划。但至少现在,你知道了每个选择背后的代价和边界。
最后留一个讨论点
你在实际项目中,是选择自研轻量工具协议,还是直接复用 Json Schema?自研的灵活性 vs 兼容性,你会怎么选?