跳到正文

Tool Calling 工具集设计原则

从抽象层次、接口设计、错误处理三方面阐述原则

原题:在构建大模型工具调用(Tool Calling)能力时,工具集的设计应该遵循哪些原则?请从工具抽象层次、接口设计、错误处理等方面进行阐述。

Agent · 字节真题

30 秒回答

  1. 工具抽象层次:区分原子工具与复合工具,避免过细或过粗
  2. 接口设计:Schema清晰、参数自描述、支持动态发现
  3. 错误处理:分级容错、可观测性、优雅降级
  4. 安全性:权限隔离、输入校验、执行沙箱

回答与解析

答案要点

  • 工具抽象层次:区分原子工具与复合工具,避免过细或过粗
  • 接口设计:Schema清晰、参数自描述、支持动态发现
  • 错误处理:分级容错、可观测性、优雅降级
  • 安全性:权限隔离、输入校验、执行沙箱
  • 可扩展性:工具注册机制、版本管理、热更新

一、工具抽象层次

核心原则:粒度适中,语义清晰

  • 原子工具:单一职责,如get_weathersend_email,参数明确、无副作用歧义
  • 复合工具:高阶封装,如book_travel内部编排航班+酒店+租车,降低Agent推理负担
  • 避免反模式
    • 过细:get_weather_by_lat + get_weather_by_city + get_weather_forecast → 应合并为统一get_weather
    • 过粗:operate_database 包含CRUD所有操作 → 应按操作类型拆分

二、接口设计(Schema)

面向LLM友好的描述

{
  "name": "search_product",
  "description": "根据关键词搜索商品,返回匹配的商品列表。当用户询问'有什么手机'时使用此工具",
  "parameters": {
    "type": "object",
    "properties": {
      "keyword": {"type": "string", "description": "搜索关键词,如'iPhone 15'"},
      "sort_by": {"type": "string", "enum": ["price_asc", "sales_desc"], "description": "排序方式"}
    },
    "required": ["keyword"]
  }
}

关键要点

  • description必须包含使用场景触发条件(when to use),减少LLM误调用
  • 参数类型明确,枚举值给出可选项,复杂对象扁平化处理
  • 支持动态工具发现:运行时根据用户权限/场景过滤可用工具列表

三、错误处理机制

分层容错策略

层级 处理策略 示例
参数校验 前置拦截,返回结构化错误 必填字段缺失 → MISSING_PARAM: "keyword is required"
执行异常 重试+降级,Agent可感知 API超时 → 返回TOOL_ERROR: "weather service unavailable, please try later"
结果异常 后处理兜底,保证下游可用 返回空数组 → 包装为{"results": [], "suggestion": "try broader keywords"}

可观测性:每个工具调用生成唯一trace_id,记录输入参数、执行耗时、原始响应,便于问题定位

四、安全与扩展

  • 权限隔离:工具绑定角色权限,敏感操作(支付、删除)需二次确认
  • 输入净化:参数防注入,特别是SQL、代码执行类工具
  • 热更新:工具版本化管理,支持A/B测试和灰度发布

口语版讲法(约4分钟)

  • 这道题本质问的是LLM和外部系统怎么安全高效地交互
  • 工具粒度要适中,原子和复合工具搭配使用
  • Schema设计要面向LLM友好,描述要包含触发条件
  • 错误处理要分层,让Agent能感知并降级
  • 安全隔离和热更新是上线前必须考虑的

这道题其实在问一个问题:当我们把大模型接入外部系统的时候,怎么让它既能安全高效地调用工具,又不会因为工具设计不好而把整个流程搞崩?核心就是围绕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友好、错误分层、安全隔离这几个方面。而最终取舍上,我更倾向先保证安全,再追求效率。因为工具调用一旦出错,影响的是整个业务,不能为了省几步就把风险放大。

关键一句:工具之间的依赖关系也需要管理,比如复合工具依赖原子工具,底层工具不可用时应该自动降级。

面试官还可能这样问

  1. 问法 1 · 场景切入

    假设你在做一个电商客服机器人,用户说“帮我查一下昨天订单”,你需要调一个工具去查订单。那你的工具集里,是把查订单、查物流、退换货都做成单独的工具,还是合在一起?你设计工具的时候会怎么抽象它们?

  2. 问法 2 · 层层追问

    你做过工具调用吧,你觉得给大模型暴露工具时,接口该怎么设计?……比如参数的描述怎么写能让模型不瞎调?……那如果工具调用出错了,比如超时或者参数不对,你的系统怎么处理?……还有,工具多了以后怎么管理?

  3. 问法 3 · 直球架构

    设计大模型工具调用的工具集,从工具抽象层次、接口设计、错误处理三个方面,你会遵循哪些原则?具体说说怎么避免工具粒度过细或过粗,Schema怎么写才友好,以及错误怎么分级容错。

同模块相关题目