历史文章

Spring AI:对接DeepSeek实战
Spring AI:对接官方 DeepSeek-R1 模型 —— 实现推理效果
Spring AI:ChatClient实现对话效果
Spring AI:使用 Advisor 组件 - 打印请求大模型出入参日志
Spring AI:ChatMemory 实现聊天记忆功能
Spring AI:本地安装 Ollama 并运行 Qwen3 模型
Spring AI:提示词工程
Spring AI:提示词工程 - Prompt 角色分类(系统角色与用户角色)
Spring AI:基于 “助手角色” 消息实现聊天记忆功能
Spring AI:结构化输出 - 大模型响应内容
Spring AI:Docker 安装 Cassandra 5.x(限制内存占用)&& CQL
Spring AI:整合 Cassandra - 实现聊天消息持久化
Spring AI:多模态 AI 大模型
Spring AI:文生图:调用通义万相 AI 大模型
Spring AI:文生音频 - cosyvoice-V2
Spring AI:文生视频 - wanx2.1-i2v-plus

假设我们对 AI 大模型提问 “今天是几号”,你可能会想,这么简单的问题,它岂不是手拿把掐?

http://localhost:8080/v2/ai/generateStream?message=今天是几号&chatId=2

接口可以看这篇文章Spring AI:整合 Cassandra - 实现聊天消息持久化

在这里插入图片描述
如上图所示,结果却是大失所望,原因是 AI 大语言模型本身是 “封闭” 的:

  • 知识截止: 它们的训练数据有截止日期,无法获取实时信息(如最新天气、股价、新闻)。
  • 缺乏执行能力: 它们无法直接执行操作(如发送邮件、查询数据库、控制智能家居)。
  • 无法访问私有数据: 无法读取你的个人邮件、公司数据库等。

为了解决上述问题,本文中,我们来学习一下 Function Call 功能,它可以让 LLM 成为连接用户自然语言指令和外部工具/服务的智能“中间件”。

什么是 Function Call?

在这里插入图片描述
AI 大模型中的 Function Call(函数调用), 它是一个极其重要的功能,它极大地扩展了大语言模型的能力边界,使其从单纯的文本生成器升级为能够感知、决策并操作外部世界的智能代理。

其优势与价值如下:

  • 访问实时信息: 获取股票、天气、新闻、航班等最新数据。
  • 操作外部系统: 发送邮件、创建日历事件、控制 IoT 设备、操作数据库。
  • 执行精确计算/转换: 调用计算器、货币转换器等。
  • 检索私有/专有数据: 连接企业内部数据库、知识库、CRM 系统。
  • 增强可靠性: 将需要精确性的任务(如计算、数据查询)交给专门工具处理,减少模型“幻觉”。
  • 构建智能代理(Agents): Function Call 是实现 AI Agent 自动规划和执行复杂任务序列(如“查天气-订机票-发通知”)的基础技术。
  • 简化开发: 开发者只需定义好函数接口,模型能理解如何调用,无需复杂的自然语言解析。
  • 提升用户体验: 用户可以用自然语言无缝触发复杂的后端操作。

Spring AI 中 Tool Calling 的处理流程

Spring AI 通过一套灵活的抽象机制支持工具调用 (Tool Calling),使您能够以一致的方式定义、解析和执行工具。

TIP: 在 Spring AI 框架中,Function Call 被称之为 Tool Calling。

在这里插入图片描述
当我们想让模型能够使用某个工具时,我们会在聊天请求中包含该工具的定义。每个工具定义包含一个名称(name)、一个描述(description)以及输入参数的模式(schema),处理流程如下:

1、模型调用: 调用 AI 大模型时,会将提示词以及可供使用的工具集,一同发送过去。由 AI 大模型决定是否需要调用某个工具,当需要时,它会反馈一个响应,其中包含工具名称以及依照定义的输入参数模式构建的输入参数。
2、执行工具: Spring AI 负责使用工具名称来识别对应的工具,并使用提供的输入参数执行该工具。
3、处理结果: Spring AI 处理工具调用的结果。
4、返回结果: Spring AI 将工具调用的结果发送回 AI 大模型。
5、模型生成最终响应: 模型利用工具调用的结果作为额外上下文,生成最终面向用户的响应。

定义 Tool 工具

接下来,针对文章开头 “今天是几号” 的场景,我们实际上手感受一下 Tool Calling 的魅力。首先,新建一个 /tools 包,并新建一个 DateTimeTools 工具类,代码如下:

@Slf4j
public class DateTimeTools {

    @Tool(description = "获取当前日期和时间")
    String getCurrentDateTime() {
        return LocalDateTime.now().toString();
    }

}

解释一下:

  • 定义了一个 getCurrentDateTime() 方法,并获取当前日期和时间;
  • 为方法添加 @Tool 注解,并用自然语言描述此方法的作用(非常关键! 模型依赖此理解何时调用)。

方式一:ChatClient 使用工具调用

Tool 工具定义完成后,首先,我们来演示 ChatClient 要如何使用工具调用。首先,编辑 ChatClientConfig 类,将 ChatClient 使用的模型修改为 DeepSeek:

@Configuration
public class ChatClientConfig {

    @Resource
    private ChatMemory chatMemory;

    /**
     * 初始化 ChatClient 客户端
     * @param chatModel
     * @return
     */
    @Bean
    public ChatClient chatClient(DeepSeekChatModel chatModel) {
        return ChatClient.builder(chatModel)
//                .defaultSystem("请你扮演一个智能客服")
                .defaultAdvisors(new SimpleLoggerAdvisor(), // 添加 Spring AI 内置的日志记录功能
//                                 new MyLoggerAdvisor(), // 添加自定义的日志打印 Advisor
                                MessageChatMemoryAdvisor.builder(chatMemory).build()
                        )
                .build();
    }
}

想要使用 Tool Calling 功能,首先需要明确当前使用的 AI 模型, 它是否支持,要知道,并不是所有 AI 大模型都支持工具调用功能的。而 DeepSeek 就是支持的,具体可以阅读官方文档: https://api-docs.deepseek.com/zh-cn/guides/function_calling/ 。

接着,编辑 ChatClientController 控制器中的 /v2/ai/generateStream 接口,当调用 AI 大模型时,将定义好的工具也提交给它:

    /**
     * 流式对话
     * @param message
     * @return
     */
    @GetMapping(value = "/generateStream", produces = "text/html;charset=utf-8")
    public Flux<String> generateStream(@RequestParam(value = "message", defaultValue = "你是谁?") String message,
                                       @RequestParam(value = "chatId") String chatId
    ) {

        // 流式输出
        return chatClient.prompt()
                .tools(new DateTimeTools()) // Function Call
//                .system("请你扮演一个智能客服")
                .user(message) // 提示词
                .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, chatId))
                .stream()
                .content();

    }

重启后端项目,再来测试一下,看看这次 AI 大模型是否能够回答出当前日期

定义多个 Tool 工具

假设需求又变了,除了想要获知当前时间,还想知道今天的天气怎么样呢?AI 大模型支持注册多个 Tool 工具,在 /tools 包下,新建 WeatherTools 工具类:

@Slf4j
public class WeatherTools {

    @Tool(description = "获取当日的天气情况,时间参数需为 ISO-8601 格式")
    String getWeather(String time) {
        log.info("## time: {}", time);

        // TODO 调用第三方接口,获取当日天气情况

        return "今天天气晴朗,最低温度 18℃,最高温度 38℃";
    }
}

解释一下:

  • 定义一个 getWeather 获取天气的方法,入参为一个字符串类型的 time 日期字段,表示想要查询的目标日期;
  • 方法内部,我们可以调用第三方接口,来获取真实的天气数据,这里就不搞太复杂,直接写死 “天气晴朗…”;
  • 为此方法添加 @Tool 注解,并用自然语言描述此方法的作用,以方便 AI 大模型理解什么时候调用它;

完成上述工作后,将 WeatherTools 工具类也一同注册到工具集中

    /**
     * 流式对话
     * @param message
     * @return
     */
    @GetMapping(value = "/generateStream", produces = "text/html;charset=utf-8")
    public Flux<String> generateStream(@RequestParam(value = "message", defaultValue = "你是谁?") String message,
                                       @RequestParam(value = "chatId") String chatId
    ) {

        // 流式输出
        return chatClient.prompt()
                .tools(new DateTimeTools(), new WeatherTools()) // Function Call
//                .system("请你扮演一名犬小哈 Java 项目实战专栏的客服人员")
                .user(message) // 提示词
                .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, chatId))
                .stream()
                .content();

    }

再次重新启动项目,浏览器请求地址如下,同时询问日期和天气情况:

http://localhost:8080/v2/ai/generateStream?message=今天是几号,天气怎么样&chatId=2

你可能会出现 500 服务器内部错误,如下图所示,查看控制台日志,异常信息中提示缺失了某个 Class 类:

在这里插入图片描述
要想解决此问题,在 pom.xml 文件中,添加如下依赖即可:

// 省略...

    <properties>
        // 省略...
        <jsonschema-generator.version>4.38.0</jsonschema-generator.version>
    </properties>


        <dependencies>
            // 省略...

            <dependency>
                <groupId>com.github.victools</groupId>
                <artifactId>jsonschema-generator</artifactId>
                <version>${jsonschema-generator.version}</version>
            </dependency>
    	</dependencies>

	// 省略...

</project>

然后重启项目,再次测试,不出意外,这次 AI 大模型就能回答出当前日期,以及天气情况了:

方式二:ChatModel 使用工具调用

最后,我们再来看看 ChatModel 要如何使用工具调用功能。在 /controller 包下,新建一个 ToolCallingController 控制器,并声明 /v13/ai/generateStream 接口,相比较 ChatClient 客户端,ChatModel 使用工具调用功能要稍微复杂一点,代码如下:

@RestController
@RequestMapping("/v13/ai")
public class ToolCallingController {

    @Resource
    private DeepSeekChatModel chatModel;

    /**
     * 流式对话
     * @param message
     * @return
     */
    @GetMapping(value = "/generateStream", produces = "text/html;charset=utf-8")
    public Flux<String> generateStream(@RequestParam(value = "message") String message) {
        // 将 DateTimeTools 注册到工具集中
        ToolCallback[] tools = ToolCallbacks.from(new DateTimeTools());

        // 构建聊天选项配置,设置工具回调功能
        ChatOptions chatOptions = ToolCallingChatOptions.builder()
                .toolCallbacks(tools)
                .build();

        // 构建提示词
        Prompt prompt = new Prompt(new UserMessage(message), chatOptions);

        // 流式输出
        return chatModel.stream(prompt)
                .mapNotNull(chatResponse -> {
                    // 获取响应内容
                    DeepSeekAssistantMessage deepSeekAssistantMessage = (DeepSeekAssistantMessage) chatResponse.getResult().getOutput();
                    // 推理内容
                    String reasoningContent = deepSeekAssistantMessage.getReasoningContent();
                    // 推理结束后的正式回答
                    String text = deepSeekAssistantMessage.getText();

                    return StringUtils.isNotBlank(reasoningContent) ? reasoningContent : text;
                });
    }
}

最终效果都是一样的,小伙伴们可以自行测试

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐