SpringAI-Tool简单实践

2025博客之星年度评选已开启 10w+人浏览 2.3k人参与

背景

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

引入依赖

        <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

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);
    }
}

调用

@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年出生的,现在应该多少岁,明年是哪一年?
后的结果:

您出生于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合并)

代码实现

    @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;
    }

调用

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

    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源码再整理.

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值