周五下午的 Demo 会议室,老李把保温杯往桌上一顿,开场就扔了个炸弹。
“你们这个 Agent,说半天不就是个聊天机器人吗?我问它‘本周技术部处理了多少个工单’,它回答‘我没有权限查询数据库’。那我花这么多钱搞 Agent 干嘛?不如直接写个 SQL 报表,还不用猜它是不是编的。”
小王不急不慢地打开投影仪:“老李,你上次不是还夸 Agent 帮你分析日志挺准的吗?”
“分析日志是它厉害,但那是分析文本。真正的业务系统——数据库、工单系统、日历、邮件——它一个都碰不着。一个碰不到现实世界的 AI,跟关在笼子里的鸟有什么区别?”
“那是因为你还没给它装上超能力。”小王调出一段代码,“今天就是给这只鸟开笼子的。”
工具就是 Agent 的手和脚
“LLM 的大脑再聪明,没有手和脚,就只能在脑子里模拟一切。你问它‘本周工单数’,它脑子里没有,就只能编一个。但如果它有一只手——可以伸到数据库里查——那它就能告诉你真实的数字。”
小王在屏幕上投影出一个比喻:“Agent 就像一把瑞士军刀的刀柄。刀柄本身很普通,但当你把剪刀、螺丝刀、开瓶器一个个装上去,它就成了能在野外生存的利器。Tool Calling——工具调用——就是给 Agent 装功能模块的机制。”
“具体怎么装?”老李抱起胳膊。
“三个步骤。”小王走到白板前画了一个流程。“第一,定义工具——告诉 Agent 有哪些功能可用,每个功能叫什么、能干什么、需要什么参数。第二,Agent 决策——用户提问后,Agent 判断是否需要使用工具,以及用哪一个。第三,执行与反馈——系统替 Agent 执行工具,把结果返回给 Agent,让它生成最终回答。”
图:Tool Calling 的基本流程——工具是 Agent 感知现实世界的触角
工具描述:用 JSON Schema 写一份“说明书”
“Agent 怎么知道一个工具该在什么时候用?”老李追问。
“靠的就是一份严谨的说明书——JSON Schema。每个工具都要用 JSON 格式描述清楚它的名字、功能、参数类型和约束。Agent 读这些描述来判断什么场景下用哪个工具。”小王打开代码编辑器。
“我给你看三个工具的定义,分别对应搜索知识库、查数据库、预约会议室——”
# Agent 的工具箱:三个工具的 JSON Schema 定义
tools = [
{
"type": "function",
"function": {
"name": "search_knowledge_base",
"description": "搜索公司内部知识库,返回相关文档片段。当你需要查找公司内部制度、产品文档、技术方案时使用。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索查询词,尽量使用关键词而非完整句子"
},
"max_results": {
"type": "integer",
"description": "返回的最大文档数,默认5",
"default": 5
}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "query_database",
"description": "执行只读 SQL 查询。仅用于查询结构化数据,如工单统计、用户列表、产品库存。禁止执行写操作。",
"parameters": {
"type": "object",
"properties": {
"sql": {
"type": "string",
"description": "要执行的 SELECT 查询语句。仅支持 SELECT,不支持 INSERT/UPDATE/DELETE。"
}
},
"required": ["sql"]
}
}
},
{
"type": "function",
"function": {
"name": "book_meeting_room",
"description": "预约会议室。需要在确定会议室空闲后才能调用。",
"parameters": {
"type": "object",
"properties": {
"room": {"type": "string", "description": "会议室名称,如'3楼A厅'"},
"date": {"type": "string", "description": "日期,格式 YYYY-MM-DD"},
"start_time": {"type": "string", "description": "开始时间,格式 HH:MM"},
"duration_hours": {"type": "number", "description": "预约时长(小时)"}
},
"required": ["room", "date", "start_time", "duration_hours"]
}
}
}
]老李盯着 query_database 的描述看了半天:“你说它‘仅支持 SELECT’,是不是在防着 Agent 把数据库删了?”
“这就是安全边界的核心。给工具加限制,比靠 Agent 的自制力靠谱一万倍。Agent 拿到的只是 SQL 文本,真正执行的是你的后端服务——你可以在执行层做校验、限流、沙箱。Agent 能决定的只是要不要用这个工具,以及传入什么参数。真正危险的操作,压根不暴露给它。”
工具调用的黄金法则
“那怎么设计一个好工具?”老李掏出小本本。
小王掰着手指总结:“单一职责——一个工具只做一件事,Agent 才容易判断什么时候用它。‘查数据库’和‘发邮件’不要混在一个工具里。”
“清晰的描述——不是给你自己看的,是给 LLM 看的。描述要写明‘什么时候该用这个工具’,而不是只写‘用来干嘛’。比如‘当你需要查找公司内部制度时使用’比‘搜索知识库’更明确。”
“准确的参数类型——date 就是 string 并标注格式,count 就是 integer 并给范围。不要用一个笼统的 object 糊弄。参数描述越精确,Agent 传错参的概率越低。”
“幂等性与错误处理——查询类工具天然幂等,多次调用无副作用。但预约会议室这种操作,如果不做防重复提交,Agent 陷入循环可能给一个房间订了十次。所以工具实现时必须自己做幂等检查,并返回明确的错误信息,让 Agent 知道‘操作失败了,原因是什么’,而不是一个模糊的 ‘Error’。”
# 工具执行示例:预约会议室的安全实现
def book_meeting_room(room, date, start_time, duration_hours):
# 1. 检查是否已有相同预约(幂等)
if existing_booking := find_booking(room, date, start_time):
return f"预约失败:{room} 在 {date} {start_time} 已被预约。请选择其他时间。"
# 2. 权限校验
if not user_has_permission(current_user, "book_room"):
return "权限不足:当前用户无预约权限。"
# 3. 执行预约
create_booking(room, date, start_time, duration_hours)
return f"成功预约 {room},{date} {start_time},时长 {duration_hours} 小时。"老李看完,轻轻点了点头:“所以工具不只是一个接口,它是一整套约束——描述约束 Agent 的选择,后端约束执行的安全性。”
多工具编排:十个工具,Agent 怎么选?
“当工具数量超过十个,Agent 选错的概率就会上升。”小王调出一张图表,“比如你同时给了‘查数据库’和‘查知识库’,Agent 有时会把知识库查询的问题也用成 SQL。解决方案有三个。”
“第一,工具分组。不是把所有工具摊平给 Agent,而是根据场景动态注入。用户问工单相关的,只给数据库工具;问政策制度,只给知识库工具。减少干扰。”
“第二,描述中加示例。在工具描述的 description 里追加一两个典型问题,比如‘例如“今天有多少工单”或“上周销量是多少”’,帮 Agent 快速匹配场景。”
“第三,引入轻量级路由。在 Agent 推理前加一层分类器——先判断用户意图,再决定启用哪个工具集。这比让 Agent 直接从十个工具里选靠谱得多。”
老李若有所思:“那咱们现在的工具,有多少个?”
“刚做了三个——搜索、查库、预约。我建议初期不超过五个。工具不是越多越好,是越准越好。”
老李的“原来如此”
小王最后打开一个完整的 Agent 运行日志,是老李刚才那个“本周工单数”的问题。
[Thought] 用户想查询本周技术部的工单数量。这需要查询结构化数据,我应该用 query_database 工具。
[Action] query_database("SELECT COUNT(*) FROM tickets WHERE department='技术部' AND created_at >= date_trunc('week', current_date)")
[Observation] 结果:42
[Thought] 查询成功,获得真实数字。
[Final Answer] 本周技术部共处理了 42 个工单。老李盯着屏幕上那个“42”,沉默了几秒。然后缓缓拧开保温杯,喝了一口。
“这个数是真的?不是编的?”
“你可以自己去数据库跑一遍同样的 SQL。”小王把查询语句复制给老李。
老李打开终端,执行了一下——42。
他靠在椅背上,保温杯在手里转了好几圈:“所以这玩意儿,是真的能干活了。”
“早就能干活了,是你一直不给它工具。”小王笑道。
老李站起来,走到投影前,仔细看了看那三个工具的 Schema 定义。然后指着 query_database 说:“这个权限要再加一层——按部门限制查询范围。不然有人问薪资数据它也去查,就麻烦了。”
小王眼睛一亮:“老李,你这个需求太精准了。我下午就加上字段级别的访问控制。”
老李摆摆手,嘴角却翘了起来:“下不为例啊。还有——你刚说的那个工具分组,如果用户一个问题同时涉及知识库和数据库,比如‘上周发布了几个版本,对应影响了多少用户’,你怎么让 Agent 先查知识库拿到版本号,再查数据库统计用户数?这个多步编排你给我演示一下。”
“行,这就是 ReAct 加多工具的串联玩法。”小王拉开椅子,老李端着保温杯凑到屏幕前。窗外的夕阳打在两个人身上,像一对终于接通了现实世界的 Agent,正在规划它们的下一步行动。

