切换主题
第 3 章:工具调用,让 Agent 从会说话变成会干活
上一章的小助手只会回答问题,这一章给它一双手。工具调用的核心不是“Java 方法可以被调用”这么简单,而是:模型先判断要不要用工具,框架再执行工具,最后模型把工具结果整理成用户能看懂的话。
本章边界
本章讲三件事:工具怎么声明,模型怎么选择工具,框架怎么执行工具。复杂工具平台、Sandbox 深入实现和生产级权限系统先不展开,但危险工具的边界会先讲清楚。
1. 生活类比:模型是前台,工具是后厨
用户问:“今天杭州天气怎样,适合跑步吗?”
前台服务员不会自己跑到窗外测天气。他会判断:这事要问天气系统。后厨拿到单子,查数据,回传结果;前台再把“温度、风力、是否适合跑步”组织成一句人话。
在 Agent 里也是三段式:
text
模型判断 -> 框架执行工具 -> 模型整理答案你一定要分清这三件事。模型不是工具执行器,它只是提出 tool call;真正执行动作的是框架侧。
2. 最小工具示例
先做一个天气工具,简单到有点朴素,但足够说明问题:
java
record WeatherRequest(String city) {}
class WeatherTool implements Function<WeatherRequest, String> {
@Override
public String apply(WeatherRequest request) {
return request.city() + ":晴,22 度,适合慢跑。";
}
}
@Bean
ToolCallback weatherTool() {
return FunctionToolCallback
.builder("query_weather", new WeatherTool())
.description("根据城市名查询当前天气和运动建议")
.inputType(WeatherRequest.class)
.build();
}
@Bean
ReactAgent weatherAgent(ChatModel chatModel, ToolCallback weatherTool) {
return ReactAgent.builder()
.name("weather_agent")
.model(chatModel)
.instruction("你是运动建议助手。需要实时天气时,优先使用工具。")
.tools(weatherTool)
.build();
}这里最重要的不是 Java 语法,而是工具说明:
| 配置 | 作用 |
|---|---|
工具名 query_weather | 给模型看的工具标识,名字越清楚越好 |
description(...) | 告诉模型什么时候该用这个工具 |
inputType(...) | 告诉模型工具需要什么参数 |
.tools(weatherTool) | 把工具挂到 Agent 的工具箱里 |
工具说明写得含糊,模型就像看到一个写着“杂物间”的门牌,很难判断该不该进去。
3. 工具调用的流程图
换成工程语言,就是:
- Agent 把工具定义交给模型。
- 模型判断是否需要工具,并生成 tool call。
- 框架读取 tool call,找到对应工具。
- 工具执行后返回结果。
- 结果作为消息回到模型,模型组织最终回答。
这也是为什么工具返回值不一定原样展示给用户。工具只负责交付原材料,最终上桌前还要经过模型整理。
4. 危险工具:别把电锯放进儿童玩具箱
天气工具很安全,因为它只返回字符串。但 Shell、Python、文件读取这类工具就不一样了。它们不是“小助手的手”,它们是电锯、钥匙和仓库门。
生产里至少要有这张安全表:
| 风险 | 推荐做法 |
|---|---|
| 工具描述不清,模型误调用 | 工具名、描述、入参 schema 写清楚;高风险工具加人工审批 |
| 工具超时 | 设置超时时间;超时后返回可解释错误 |
| Shell/Python 越权 | 限定工作目录、禁用危险命令、限制网络和文件权限 |
| 文件读取泄露 | 只允许读白名单目录,不允许模型自由读任意路径 |
| 工具失败 | 返回降级回答,并记录 tool name、input、error、threadId |
| 审计缺失 | 记录谁触发、触发了哪个工具、参数是什么、结果是否成功 |
你可以把工具调用当成一次“让模型开工单”。模型可以申请,但系统必须审批、执行、记录和兜底。
5. 工程里怎么设计一个好工具
一个好工具像一个好接口:名字清楚、输入稳定、失败可解释。
| 设计点 | 好写法 | 坏写法 |
|---|---|---|
| 工具名 | query_weather、search_order | tool1、doSomething |
| 描述 | “根据城市查询当前天气和运动建议” | “查询信息” |
| 入参 | 小而明确的 record | 一个大 Map 什么都塞 |
| 返回 | 可读、可解析、错误清晰 | 成功失败都返回一坨字符串 |
| 权限 | 白名单、超时、审计 | 模型想干什么就干什么 |
先从一个工具开始,跑通、观察、调描述,再加第二个。工具不是越多越强,工具越多,模型选错的机会也越多。
6. 本章小结
工具调用的本质是:
text
模型负责判断,框架负责执行,工程负责边界。从这一章开始,Agent 不再只是聊天对象,而是一个能调用外部能力的执行单元。下一章我们给它加记忆,让它不要每次都像第一次见你。
7. 练习题
- 给
query_weather增加字段activity,让工具能根据城市和运动类型返回建议。 - 设计一个
query_order_status工具,写出工具名、description、入参 record 和失败返回格式。 - 给 Shell 工具设计一张权限白名单表:允许哪些目录、禁止哪些命令、超时时间多少、失败日志记录哪些字段。
课后源码索引:想验证实现时再打开
| 你想验证的结论 | 源码锚点 |
|---|---|
| 官方工具调用示例 | examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/ToolsExample.java,类 ToolsExample,方法 programmaticToolSpecification()、toolsInReactAgent()、methodToolsExample()、combinedToolProvisionExample() |
| Builder 如何收集工具 | spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/DefaultBuilder.java,类 DefaultBuilder,方法 gatherLocalTools()、build() |
| 模型节点如何把工具交给模型 | spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/node/AgentLlmNode.java,类 AgentLlmNode,方法 apply(...)、filterToolCallbacks(...)、buildChatClientRequestSpec(...) |
| ReactAgent 如何在模型和工具间循环 | spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/ReactAgent.java,类 ReactAgent,方法 makeModelToTools(...)、makeToolsToModelEdge(...) |
| 工具节点如何真正执行工具 | spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/node/AgentToolNode.java,类 AgentToolNode,方法 apply(...)、executeToolCallsSequential(...)、executeToolCallsParallel(...) |
| Sandbox 工具边界 | spring-ai-alibaba-sandbox/src/main/java/com/alibaba/cloud/ai/sandbox/SandboxAwareTool.java,类 SandboxAwareTool,方法 getSandbox()、setSandbox(...)、getSandboxClass() |