这道题其实在问一个问题:当我们把大模型接入外部系统的时候,怎么让它既能安全高效地调用工具,又不会因为工具设计不好而把整个流程搞崩?核心就是围绕LLM和外部系统之间的契约来设计。
先说工具抽象层次。这里容易走极端,要么把工具拆得太碎,比如一个天气功能拆成按城市查、按经纬度查、查预报三个独立工具,LLM得花好多步才能完成一次查询。要么搞一个大而全的万能工具,比如一个operate_database包揽所有CRUD操作,LLM根本搞不清该传什么参数。我的做法是区分原子工具和复合工具。原子工具负责单一职责,比如get_weather,参数明确且无歧义。复合工具则是把多个原子工具编排成一个高阶业务操作,比如book_travel内部调航班、酒店、租车,这样LLM只需要一次调用,推理负担小很多。真正落地的时候,我会根据业务场景搭配使用:对简单查询用原子工具,对复杂流程用复合工具。举个例子,在客服退款场景里,退款操作本身是原子工具,但处理退货流程可能涉及查询订单、验证商品状态、发起退款、记录备注,我会把这些打包成一个process_refund复合工具,LLM一步完成。
再来看接口设计,也就是Schema怎么写给LLM看。这里的关键是描述要包含使用场景的触发条件。比如一个search_product工具,它的description不能只写“根据关键词搜索商品”,还要加上“当用户询问‘有什么手机’时使用此工具”。这样LLM才能准确判断何时调用,减少误调用。参数也要自描述,枚举值给出可选项,复杂对象尽量扁平化。另外,我还支持动态工具发现,运行时根据用户权限或当前场景过滤可用工具列表。比如普通用户看不到删除订单的工具,管理员才能看到。
错误处理这块,我把它分成三个层级。第一层是参数校验,前置拦截,LLM传的参数不对,直接返回结构化错误,比如MISSING_PARAM: "keyword is required",LLM马上就能补传。第二层是执行异常,比如API超时,我会重试一次,如果还失败就返回TOOL_ERROR,并附上降级建议,比如“天气服务暂时不可用,请稍后再试”。第三层是结果异常,比如返回空数组,我会包装成{"results": [], "suggestion": "try broader keywords"},让LLM有改进方向。每个工具调用还会生成唯一trace_id,记录输入参数、执行耗时、原始响应,方便问题定位。
安全性和扩展性也是必须考虑的。权限隔离是基础,敏感操作比如支付、删除,需要LLM先确认用户身份,甚至二次确认。输入净化也不能少,特别是SQL或代码执行类工具,防止注入攻击。热更新和版本管理让工具可以灰度发布,先让少量流量试用新工具,没问题再全量上线。
这里我多说一句,工具集设计还有一个容易被忽略的点:工具之间的依赖关系。比如退款工具依赖订单查询工具,如果订单查询挂了,退款工具也跑不了。我目前的做法是在复合工具内部做依赖检查,如果底层工具不可用,复合工具直接返回降级结果,而不是让LLM去猜。但更好的方案可能是让工具集支持动态编排,根据工具健康状态自动调整调用链,这块我还在探索。
所以总的来说,我会把工具集设计看成是LLM和外部系统之间的契约,重点在粒度适中、Schema友好、错误分层、安全隔离这几个方面。而最终取舍上,我更倾向先保证安全,再追求效率。因为工具调用一旦出错,影响的是整个业务,不能为了省几步就把风险放大。