如何为 LLM 定义和描述工具(Tool Schema)?
难度:基础 分类:Tool Use
简短回答
工具 Schema 使用 JSON Schema 格式定义三要素:名称(唯一标识符)、描述(告诉 LLM 何时以及如何使用此工具)、参数(输入的类型、约束、是否必填)。描述是最关键的字段——它直接影响 LLM 的工具选择准确率。最佳实践包括:使用清晰的命名空间、利用 Pydantic 自动生成 Schema、处理多工具调用、以及对 LLM 输出做参数验证。
详细解析
工具 Schema 的三要素
代码块
JSON
{
"name": "search_knowledge_base",
"description": "在内部知识库中搜索技术文档。当用户询问产品功能、API 文档或技术规范时使用此工具。不要用于搜索客户订单或财务数据。",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词或自然语言问题"
},
"category": {
"type": "string",
"enum": ["api_docs", "user_guide", "release_notes"],
"description": "文档类别,缩小搜索范围"
},
"max_results": {
"type": "integer",
"description": "返回的最大结果数量",
"default": 5
}
},
"required": ["query"]
}
}
编写高质量描述的原则
描述(description)是 LLM 选择工具的核心依据。差的描述 → 错误的工具选择 → 错误的结果。
代码块
Python
# 差的描述
{"name": "search", "description": "搜索东西"}
# 问题:搜什么?什么时候用?什么时候不用?
# 好的描述
{
"name": "search_product_catalog",
"description": (
"在产品目录中搜索产品信息,包括名称、价格、库存和规格。"
"当用户询问产品详情、价格比较或库存查询时使用此工具。"
高质量描述的检查清单:
说明工具做什么(功能)
说明什么时候应该使用(触发条件)
说明什么时候不该使用(排除条件)
如果有相似工具,说明区别
利用 JSON Schema 的丰富特性
代码块
JSON
{
"type": "object",
"properties": {
"date_range": {
可用特性:type、enum、description、default、minimum/maximum、format、嵌套对象、数组、required。
使用 Pydantic 自动生成 Schema
手动编写 JSON Schema 在工具多了以后容易出错。用 Pydantic 自动生成更可靠:
代码块
Python
from pydantic import BaseModel, Field
from typing import Optional, Literal
import json
class SearchParams(BaseModel):
"""在知识库中搜索文档"""
Pydantic 的优势:
•
自动类型验证和默认值处理
•
description 从 Field 自动提取
•
数据解析和序列化一体
•
Schema 更新时自动同步
工具命名空间
当工具数量增多时,用命名空间组织:
代码块
Python
tools = [
# CRM 域
{"name": "crm.search_contacts", "description": "搜索 CRM 中的联系人"},
{"name": "crm.create_lead", "description": "创建新的销售线索"},
# 计费域
{"name": "billing.get_invoice", "description": "获取发票详情"},
{"name": "billing.process_refund", "description": "处理退款"},
# 物流域
{"name": "shipping.track_package", "description": "追踪包裹状态"},
命名空间帮助 LLM 理解工具的归属领域,减少跨域误选。
处理 LLM 输出验证
LLM 生成的参数可能不完全符合 Schema。始终在执行前验证:
代码块
Python
import jsonschema
def validate_and_execute(tool_name: str, llm_args: dict):
schema = tool_schemas[tool_name]["input_schema"]
# 1. Schema 验证
try:
jsonschema.validate(llm_args, schema)
except jsonschema.ValidationError as e:
return {"error": f"参数验证失败: {e.message}"}
常见的 Schema 设计模式
代码块
Python
# 模式 1:确认型工具(危险操作前确认)
{
"name": "delete_record",
"description": "删除数据库记录。这是不可逆操作,调用前请确认用户意图。",
"input_schema": {
"properties": {
"record_id": {"type": "string"},
"confirmation": {
"type": "string",
Skills 封装:从工具到技能
Function Calling 是 LLM 调用工具的底层机制,但裸用会面临工程问题:工具定义散落、无法复用、缺乏统一生命周期管理。Skills 层在 Function Calling 之上提供封装,解决"如何让工具调用可控、可复用、可维护"的问题。
Skill 是 Function Calling 之上的工程封装层——FC 是 syscall,Skill 是 SDK。
封装判断标准
不是所有能力都需要封装成 Skill。核心公式:复用频次 × 调用复杂度 > 手动调用成本。
#hALTLBOCTVuK text { white-space: pre; }#hALTLBOCTVuK .fb0 { stroke: var(--N900-FG, 0.15); }#hALTLBOCTVuK .fb0 {stroke: rgba(31,35,41, 0.15)}#hALTLBOCTVuK .fx0 { stroke-miterlimit: 10; }#hALTLBOCTVuK .fx0 {stroke-miterlimit: 10}#hALTLBOCTVuK .fa1 { fill: rgb(187, 191, 196); }#hALTLBOCTVuK .fa1 {fill: rgb(187, 191, 196)}#hALTLBOCTVuK .fx1 { font: 400 12px MonospacedNumber, LarkHackSafariFont, LarkEmojiFont, LarkChineseQuote, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Tahoma, "PingFang SC", "Microsoft Yahei", Arial, "Hiragino Sans GB", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; }#hALTLBOCTVuK .fx1 {font-style: normal}#hALTLBOCTVuK .fx2 { text-anchor: middle; }#hALTLBOCTVuK .fx2 {text-anchor: middle}#hALTLBOCTVuK .fx3 { dominant-baseline: central; }#hALTLBOCTVuK .fx3 {dominant-baseline: central}#hALTLBOCTVuK .fx4 { stroke: none; }#hALTLBOCTVuK .fx4 {stroke: none}#hALTLBOCTVuK .fc0 { fill: var(--text-caption); }#hALTLBOCTVuK .fc0 {fill: #646a73}#hALTLBOCTVuK .fa2 { fill: var(--text-link-disabled); }#hALTLBOCTVuK .fa2 {fill: rgb(187, 191, 196)}#hALTLBOCTVuK .fx5 { font: 400 16px MonospacedNumber, LarkHackSafariFont, LarkEmojiFont, LarkChineseQuote, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Tahoma, "PingFang SC", "Microsoft Yahei", Arial, "Hiragino Sans GB", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; }#hALTLBOCTVuK .fx5 {font-style: normal}#hALTLBOCTVuK .fx6 { text-anchor: start; }#hALTLBOCTVuK .fx6 {text-anchor: start}#hALTLBOCTVuK .fx7 { dominant-baseline: alphabetic; }#hALTLBOCTVuK .fx7 {dominant-baseline: alphabetic}#hALTLBOCTVuK .fc1 { fill: rgb(31, 35, 40); }#hALTLBOCTVuK .fc1 {fill: rgb(31, 35, 40)}#hALTLBOCTVuK .fb1 { fill: var(--N900-FG, 0.15); }#hALTLBOCTVuK .fb1 {fill: rgba(31,35,41, 0.15)}#hALTLBOCTVuK .fb2 { fill: rgb(209, 217, 224); }#hALTLBOCTVuK .fb2 {fill: rgb(209, 217, 224)}#hALTLBOCTVuK .fa3 { fill: var(--bg-body); }#hALTLBOCTVuK .fa3 {fill: #fff}ABC1234维度复用频次调用复杂度出错成本高分(需要封装)多个场景/Agent 都会用多步骤编排、需状态管理失败影响大,需要降级低分(直接调用)仅单次使用单次 API 调用即可失败可忽略或重试
粒度的平衡——避免两个极端:
代码块
Plain Text
工具数量管理
工具过多会降低 LLM 的选择准确率:
解决方案——分类路由:先让 LLM 选类别,再暴露该类别的工具。
代码块
Python
tool_categories = {
"数据查询": [search_users, search_orders, search_products],
"数据分析": [run_sql, generate_chart, export_csv],
"消息通知": [send_email, send_slack, create_ticket],
}
# 路由工具:让 LLM 先选类别
{
"name": "route_to_category",
"description": "根据用户需求选择工具类别: 数据查询、数据分析、消息通知",
"input_schema": {
"properties": {
"category": {"type": "string", "enum": ["数据查询", "数据分析", "消息通知"]}
}
常见误区 / 面试追问
误区:"名称比描述重要" — 恰恰相反。描述是 LLM 决定是否使用工具的核心依据。名称只是标识符。好的描述应明确说明用途、触发条件和排除条件。
误区:"Schema 越详细越好" — 过度复杂的 Schema 会消耗上下文窗口且增加 LLM 出错概率。保持简洁,只定义必要的参数。
追问:"工具太多(50+)怎么办?" — (1) 用命名空间分组;(2) 动态加载——根据用户意图只加载相关工具;(3) 用 Embedding 做工具检索(RAG for tools);(4) 分层——先让 LLM 选类别,再加载该类别的工具。
追问:"如何测试工具 Schema 的质量?" — 构建测试用例集:一组用户查询 + 期望被选中的工具。运行后检查 LLM 的工具选择准确率。低于 90% 就需要优化描述。
追问:"Skill 和 Function Calling 的本质区别是什么?" — Function Calling 是模型级能力(LLM 生成结构化的工具调用请求),Skill 是工程级封装(在 Function Calling 之上加了生命周期管理、错误处理、降级策略、审计日志等)。类比:Function Calling 是 syscall,Skill 是 SDK。
追问:"如何判断一个能力是否需要封装成 Skill?" — 三个维度打分:复用频次、调用复杂度、出错成本。多步骤编排、跨 Agent 复用、涉及外部系统交互的能力都应该封装。单次简单 API 调用不需要。