图片

让Java开发者像写Spring Boot一样开发AI应用——第二阶段开篇

写在前面

前六天,我们完成了第一阶段的“核心范式与架构篇”——从AI Agent的概念启蒙,到ReAct循环、多Agent协作、Graph工作流编排,最后用一个完整的“智能天气助手”项目收了官。

但有一个问题始终没有解决:Agent只能“说”,不能“做”。

它可以告诉你“天气查询需要调用天气API”,但它自己调不了;它可以告诉你“计算123×456应该用计算器”,但它自己算不准。

这就像招了一个“知道很多但什么都不会做”的实习生——你问他什么他都能说上两句,但让他真正动手做件事,他就傻眼了。

今天,我们正式进入第二阶段“核心能力实战篇”的第一篇,解决这个最核心的问题——工具调用(Tool Calling)。

📌 第二阶段预告:第7-14天,我们将深入Agent的四大核心能力——工具调用、RAG、记忆系统、多智能体协同。今天先攻克第一关:让Agent拥有“动手能力”。

一、为什么需要Tool Calling?

AI的能力边界

在接触工具调用之前,先理解大模型的一个根本性限制:AI大模型虽然很聪明,但它有一个致命短板——它只能“生成文字”,无法执行真实世界的操作。

| 你的问题

|

没有工具的AI会怎么回答?

|

问题出在哪里?

“今天天气怎么样?”

|

“很抱歉,我无法获取实时天气信息”

|

模型不知道实时数据

| |

“现在几点了?”

|

“我无法获取当前时间”

|

模型没有时钟

| |

“123×456等于多少?”

|

可能算对,但复杂计算容易出错

|

模型不擅长精确计算

| |

“帮我查一下北京到上海的机票”

|

“建议您访问XX网站查询”

|

模型无法执行操作

|

💡 一句话总结:大模型就像一个“知道很多但什么都不会做”的学霸——你问他什么他都能说上两句,但让他真正动手做件事,他就傻眼了。

Tool Calling的诞生

Tool Calling真正解决的,不是“让模型回答得更花哨”,而是让它在需要外部信息时,不再只靠猜。

传统方式 vs Tool Calling方式:

<span leaf="">【传统方式】</span>

Tool Calling的核心价值:让AI在回答过程中,发现需要某些信息时,自动调用外部工具获取,而不是凭空猜测。

二、工具调用的核心原理

工具的三要素

定义一个工具,需要提供三个核心要素:

| 要素

|

说明

|

示例

名称

工具的唯一标识

| get_weather

calculate

| | 描述 |

告诉模型这个工具是做什么的、何时调用

|

“查询指定城市的实时天气”

| | 输入输出Schema |

定义工具需要什么参数、返回什么结果

|

输入:城市名;输出:天气信息

|

⚠️ 最关键的是“描述”——模型根据描述来决定“什么时候该调用这个工具”。描述写得越清晰、越具体,模型调用工具的准确性就越高。

完整工作流程

工具调用不是模型“直接执行”工具,而是模型“请求”应用程序执行工具:

<span leaf="">第1步:注册工具</span>

关键安全说明:模型永远无法直接访问作为工具提供的任何API——工具调用由客户端应用程序执行,模型只能请求调用并提供输入参数。这是一个关键的安全考虑因素。

三、Spring AI的两种工具定义方式

Spring AI支持两种方式,你可以根据场景灵活选择:

| 方式

|

名称

|

特点

|

适用场景

方法工具 @Tool

 注解

|

声明式,简洁,推荐

| 大多数场景 | | 函数工具 | FunctionToolCallback |

编程式,灵活

|

需要精细控制的场景

|

方式一:@Tool注解(推荐)

核心思想:通过注解将普通Java方法暴露为AI可调用的工具。

<span leaf=""><span>import</span>&nbsp;org.springframework.ai.tool.annotation.Tool;</span>

在Agent中注册:

<span leaf=""><span>ReactAgent</span>&nbsp;<span>agent</span>&nbsp;<span>=</span>&nbsp;ReactAgent.builder()</span>

💡 @Tool注解通过反射扫描带有注解的方法,自动封装成可用的工具。开发者只需关注业务逻辑,框架会自动处理剩下的事情。

方式二:FunctionToolCallback(编程式)

适用场景:

  • 需要动态创建工具(运行时决定工具逻辑)

  • 工具逻辑来自第三方库,无法添加@Tool注解

  • 需要更精细地控制工具的定义过程

<span leaf=""><span>@Component</span></span>

两种方式对比

| 对比维度

|

@Tool注解

|

FunctionToolCallback

定义方式

声明式(注解)

|

编程式(Builder)

| | 代码量 | |

稍多

| | 灵活性 |

中等

| | | 学习成本 | |

| | 适用场景 |

大多数场景(推荐

|

动态工具、第三方集成

|

四、实战:让Agent拥有“动手能力”

实战一:获取当前时间(FunctionToolCallback方式)

<span leaf=""><span>@Component</span></span>

实战二:计算器工具(@Tool注解方式)

<span leaf=""><span>@Component</span></span>

实战三:多工具Agent——自动选择最合适的工具

<span leaf=""><span>@Configuration</span></span>

运行效果:

| 用户问题

|

模型决策

|

调用的工具

“现在几点了?”

|

需要获取时间

| get_current_time | |

“123*456等于多少?”

|

需要计算

| calculate | |

“现在是几点?另外,100+200等于多少?”

|

需要两个工具

|

依次调用两个工具

|

💡 观察点:模型会根据问题的语义自动选择工具——不需要人为指定“这个问题该用哪个工具”。这就是Tool Calling的智能之处。

五、工具调用的最佳实践

1. 编写高质量的工具描述

工具的description是模型决定“何时调用”的唯一依据。

❌ 差的描述:

<span leaf=""><span>@Tool(description = ”计算”)</span></span>

✅ 好的描述:

<span leaf=""><span>@Tool(description = ”””</span></span>

好的描述应该包含:

  • 工具是做什么的(功能说明)

  • 什么时候应该调用(触发条件)

  • 需要什么参数(参数说明)

2. 工具命名规范

  • 使用动词开头getWeathersendEmailcalculate

  • 名称应语义清晰,一看就知道用途

  • 避免使用缩写

3. 参数设计原则

  • 参数类型尽量使用基础类型(String、int、double、boolean)

  • 每个参数都要有@ToolParam描述

  • 复杂对象可以使用DTO,但字段要清晰

4. 错误处理

工具执行可能失败,需要优雅处理:

<span leaf=""><span>@Tool</span>(description = ”查询天气”)</span>

5. 工具数量控制

一个Agent注册的工具数量建议控制在5个以内。工具太多会导致:

  • 模型选择困难,容易选错工具

  • 上下文增长,消耗更多Token

  • 响应延迟增加

六、课后挑战

任务:实现3个不同功能的工具

要求:

  1. 信息检索类:实现一个“股票价格查询”工具(模拟数据即可)

  2. 计算类:实现一个“单位转换”工具(如:公里↔英里、摄氏↔华氏)

  3. 操作类:实现一个“待办事项管理”工具(添加、删除、列表)

验收标准:

  • 3个工具均能正常注册到Agent

  • 每个工具都有清晰的description

  • 每个参数都有@ToolParam描述

  • Agent能根据用户问题自动选择合适的工具

七、本日核心收获

  1. 工具调用让Agent从“会说”进化为“会做”——这是Agent与Chatbot最本质的区别之一

  2. 工具的三要素:名称、描述、输入输出Schema——描述最關鍵

  3. 两种定义方式@Tool注解(推荐,简洁)和FunctionToolCallback(灵活,适合动态场景)

  4. 完整链路:注册工具 → 用户提问 → AI决策 → 应用执行 → 返回结果

  5. 最佳实践:清晰描述、动词命名、参数规范、错误处理、控制数量

📌 本文是第二阶段“核心能力实战篇”的第一篇。接下来的7天,我们将继续深入Agent的三大核心能力——RAG(检索增强生成)、记忆系统(Memory)、多智能体协同(Multi-Agent),让Agent从“能跑”到“能用”,再到“好用”。

有任何问题,欢迎在评论区留言交流!


作者:Java老兵搞AI,专注Java生态下的AI应用开发

如果觉得有用,点个「在看」支持一下吧,下期见!