SpringAI-Tool简单实践

背景

以往Java后端调用AI的方式基本上是通过定义prompt,然后通过chat的方式给大模型,然后大模型基于提示词做处理返回,但是如果涉及复杂的处理流程,且需要结合业务数据时,单靠提示词恐怕难以胜任,需要类似工作流的方式进行处理,在SpringAI 1.0版本之前使用Function Calling处理,1.0.0.M6版本中,官方废弃了Function Calling,统一使用Tool Calling,其实二者在底层原理上是相同的,后面统一学习一下。今天先学习一下Tool的实现。

引入依赖

xml 复制代码
        <dependency>
			<groupId>org.springframework.ai</groupId>
			<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
			<version>1.0.0-M6</version>
		</dependency>

简单开发

按照官方文档,只需要简单定义一个类,方法上使用@Tool注解即可实现。(默认已经配置了ai相关)。

官方文档:https://www.spring-doc.cn/spring-ai/1.1.0/api_tools.html

java 复制代码
public class DateTimeTools {

    /**
     * 获取当前时间tool
     * @return
     */
    @Tool(description = "Get the current date and time in the user's timezone")
    String getCurrentDateTime() {
        return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
    }

    /**
     * 设置闹钟tool
     * @param time
     */
    @Tool(description = "Set a user alarm for the given time, provided in ISO-8601 format")
    void setAlarm(String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("Alarm set for " + alarmTime);
    }
}

调用

java 复制代码
@GetMapping("/getDateTime")
    public String getDateTime(@RequestParam String prompt){
        String response = ChatClient.create(deepSeekAiChatModel)
                .prompt(prompt)
                .tools(new DateTimeTools())
                .call()
                .content();

        return response;
    }

在浏览器调用
http://localhost:8080/ai-service/api/v1/demo/getDateTime?prompt=我是1992年出生的,现在应该多少岁,明年是哪一年?

后的结果:

bash 复制代码
您出生于1992年,现在是2025年12月23日,您的年龄是33岁(如果生日已过)或32岁(如果生日未到)。明年将是2026年。

可以看到,大模型是可以基于Tool获取到的内容,然后对内容进行封装回复的。

进阶用法

文档中有如下说明(中文版有点别扭):

1.当我们想让模型使用某个工具时,会在聊天请求中包含其定义(提示)并调用聊天模型API 向 AI 模型发送请求。

2.当模型决定调用工具时,会发送响应(聊天回应工具名称和输入参数均依据定义的模式建模。

3.这聊天模型将工具调用请求发送给ToolCallingManager应用程序接口。

4.这ToolCallingManager负责识别调用的工具,并以提供的输入参数执行。

5.工具调用的结果返回给ToolCallingManager.

6.这ToolCallingManager返回工具执行结果聊天模型.

7.这聊天模型将工具执行结果返回给 AI 模型(工具响应信息).

8.AI模型利用工具调用结果作为额外上下文生成最终响应,并将其发送给呼叫者(聊天回应)通过ChatClient.

基于这个内容,我觉得可以在调用的时候,在提示词中定义要调用的tool信息,来实现整个工具流程的调用。

实践样例:

现在需求是要根据用户输入信息,做出json字符串的提取,需要判断用户输入的不能是时间段,并与页面固有json字符串做整合,形成一个最终完整的json信息。

工具定义:

1.find_datetime_from_text :提取时间信息

2.extract_query_from_text:进行字段抽取(使用ai提取)

3.merge_query_with_base_json:将抽取结果和baseJson合并(使用ai合并)

代码实现

java 复制代码
    @Tool(name = "find_datetime_from_text",description = "根据用户自然语言描述,提取时间信息")
    public String findDateTimeFromText(String userText) {
        if (userText == null || userText.trim().isEmpty()) {
            return "可以正常回复";
        }
        if (DATE_RANGE_PATTERN.matcher(userText).find()) {
            return "我无法回答您的问题,可尝试手工分析";
        }
        return "可以正常回复";
    }


/**
     * 基于用户自然语言描述,并抽取结构化查询条件。
     */
    @Tool(name = "extract_query_from_text",
            description = "根据用户自然语言描述,抽取查询字段(产业、工厂、时间、KPI、图表设置等)。")
    public String extractQuery(String userText) {

        //提取产业或者工厂或者子产业
        IndustryFactoryDTO industryAndFactoryList = getIndustryAndFactoryList();
        List<String> factories = industryAndFactoryList.getFactories();
        List<String> industries = industryAndFactoryList.getIndustries();
        List<String> subIndustries = industryAndFactoryList.getSubIndustries();
        List<String> matchedSubIndustries = subIndustries.stream()
                .filter(userText::contains)
                .collect(Collectors.toList());

        List<String> matchedFactories = factories.stream()
                .filter(userText::contains)
                .collect(Collectors.toList());

        List<String> matchedIndustries = industries.stream()
                .filter(userText::contains)
                .collect(Collectors.toList());

        // 1) 给数据提取的ai定义提示词 buildAnalysisQueryPromptForTool(userText)
        String prompt = buildAnalysisQueryPromptForTool(userText,matchedIndustries,matchedFactories,matchedSubIndustries);

        // 2) 调用大模型做"字段抽取"
        String aiResp = deepSeekAiChatModel.call(prompt);

        log.info("AI提取用户信息json返回内容:{}",aiResp);
        if (aiResp.contains("无法回答")) {
            return "我无法回答您的问题,可尝试手工分析";
        }

        // 3) 解析 AI 返回的 JSON
        //   让 AI 只返回 JSON(不带 ```),然后用 Jackson 直接转。
        String json = JsonUtil.extractPureJson(aiResp);

        return json;
    }

@Tool(name = "merge_query_with_base_json",
            description = "将抽取出的 query 与前端提供的 baseJson 合并,字段冲突则覆盖,相同列表进行聚合去重。")
    public String mergeQueryWithBaseJson(String query, JSONObject baseJsonStr) {
        //Java对象转json
        String dealUserMessage = JacksonUtil.toJson(query);
        String str = buildAnalysisQueryPrompt(dealUserMessage, baseJsonStr);
        String aiResp = deepSeekAiChatModel.call(str);
        log.info("AI合并json返回内容:{}",aiResp);
        String json = JsonUtil.extractPureJson(aiResp);

        return json;
    }

调用

java 复制代码
public String chatNew(ChatVO vo) {
        String userPrompt = vo.getPrompt();
        JSONObject baseJson = vo.getData();

        String orchestratorPrompt = AiUtil.buildTopOrchestrationPrompt(userPrompt, baseJson);

        String response = ChatClient.create(deepSeekAiChatModel)
                .prompt(orchestratorPrompt)
                .tools(new ChatAndBuildJsonTools(deepSeekAiChatModel))
                .call()
                .content();

        return JsonUtil.extractPureJson(response).trim();
    }

AiUtil.buildTopOrchestrationPrompt

java 复制代码
    public static String buildTopOrchestrationPrompt(String userText, JSONObject baseJson) {
        return """
        你是一个后端编排助手,你可以使用如下工具:
        1)find_datetime_from_text:进行时间条件过滤抽取;
        2)extract_query_from_text:进行字段抽取;
        3)merge_query_with_base_json:将抽取结果和baseJson合并.
        

        说明:
        - 抽取流程:
          (1) 调用 find_datetime_from_text 得到 一个结果字符串,如果返回结果是"我无法回答您的问题,可尝试手工分析",就停止后面所有操作,直接返回{"error","我无法回答您的问题,可尝试手工分析"}
          (2) 若 find_datetime_from_text 返回"可以正常回复",执行extract_query_from_text;
          (3) extract_query_from_text执行数据抽取时:
              - 然后将最终extract_query_from_text的返回结果与和baseJson 传入 merge_query_with_base_json。
          (4) 最终只输出 merge_query_with_base_json 的返回结果 JSON。

        输出要求:
        - 最终响应必须为合法 JSON 字符串,不要输出任何解释文字或 ```包裹。

        userText:
        %s

        baseJson:
        %s
        """.formatted(userText, baseJson.toJSONString());
    }

以上只是个人开发简单记录,仅代表个人观点,具体实现逻辑后面学习一下Spring AI源码再整理.

相关推荐
JavaBoy_XJ5 小时前
spring-gateway配置详解
spring·bootstrap·gateway
虹科网络安全6 小时前
艾体宝洞察 | Redis vs Valkey:解决 ElastiCache 的无序扩张与资源效率问题
数据库·redis·spring
此剑之势丶愈斩愈烈7 小时前
Spring获取URL信息
java·后端·spring
关于不上作者榜就原神启动那件事8 小时前
Spring Data Redis 使用详解
java·redis·spring
海南java第二人8 小时前
Spring事务传播行为完全指南:从原理到实战
spring
程序猿零零漆9 小时前
Spring之旅 - 记录学习 Spring 框架的过程和经验(一)BeanFactory和ApplicationContext入门和关系
java·学习·spring
Ahuuua9 小时前
Spring 事务传播行为详解
数据库·sql·spring
武子康9 小时前
Java-210 Spring AMQP 整合 RabbitMQ:JavaConfig 注解配置、RabbitTemplate 发送/同步接收与坑位速查
xml·java·spring·消息队列·rabbitmq·java-rabbitmq·mq
廋到被风吹走9 小时前
【Spring】ThreadLocal详解 线程隔离的魔法与陷阱
java·spring·wpf