最近项目中有一个只能分析的功能,需要根据用户输入的问题实现调取文件或者查询知识库,比如:帮我调取XXX图片文件,就需要把文件调出来展示,再比如,介绍一下XXX,就需要正常解答,再比如,你好,你是谁之类没有明确目的时候,就需要返回引导词,比如“你好,我是你的智能助手,有什么需要我帮忙的,你可以这么问我:介绍一下百度”

这个需求最需要解决的问题就是需要识别用户输入的内容表达的意图,然后调用相应的方法即可,所以思路也就清晰了:

第一种方式,不借助类似于langchainj(python)之类的框架,先让AI大模型识别意图,告诉它返回固定的值,比如,如果是调取文件就返回1,查询知识库就返回2,意图不明确的就返回3,再根据返回值调用方法即可,当然这里还有一个问题就是,需要识别出关键次,比如介绍一下百度,就需要把百度提取出来,再去查询知识库

第二种方式,借助类似于langchainj(python)之类的java版本的框架,很庆幸的是找到了langchain4j和agentscope(java版本和python版本的都有)

第三种方式,spring-ai,这个需要springboot3以上才支持,对于一些老的项目,升级可能会有很大风险

下面就简单的介绍一下前面两种方式的入门,我使用的是阿里的通义千问API,其他厂商需要根据实际情况调整api的url和key,废话不多说,直接看代码:

package com.das.ai.langchain4j;

import dev.langchain4j.agent.tool.ReturnBehavior;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.Result;
import dev.langchain4j.service.SystemMessage;
import io.agentscope.core.tool.ToolParam;
import org.apache.commons.lang3.StringUtils;

import java.util.List;

public class Lanngchain4jAppV1 {

    static class Calculator {
        @Tool(value = "两个数相加")
        int add(int a, int b) {
            return a + b;
        }

        // 支持集合类型参数的工具方法 - 修改为只返回计算结果
        @Tool(value = "计算数字列表的总和")
        int sumList(List<Integer> numbers) {
            int sum = numbers.stream().mapToInt(Integer::intValue).sum();
            return sum;
        }

        @Tool(value = "获取当前时间")
        public String getTime(
                @ToolParam(name = "zone", description = "时区,例如:北京") String zone) {
            return java.time.LocalDateTime.now()
                    .format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        }
    }

    @SystemMessage(
            "你是一个严格的工具执行助手。你的规则如下:\n" +
                    "1. 当你需要调用工具时,只输出工具调用请求。\n" +
                    "2. 当你收到工具的返回结果时,你的最终回答 **必须** 并且 **只能** 是工具返回的原始值。\n" +
                    "3. 绝对禁止在工具返回值前后添加任何解释、前缀、后缀或 conversational text (例如 '好的,结果是:' 或 '...')。\n" +
                    "4. 如果工具返回的不是字符串,请将其转换为字符串形式输出。\n" +
                    "5. 只有在用户问题不需要任何工具就能直接回答时,你才能进行正常对话。"
    )
    interface Assistant {
        /**
         * 与助手进行一次对话,返回包含内容与元信息的结果
         */
        String chat(String message);
    }

    public static void main(String[] args) {
        String apiKey = "sk-......";
        if (apiKey == null || apiKey.isEmpty()) {
            throw new IllegalStateException("请先在环境变量中配置 DASHSCOPE_API_KEY");
        }
        OpenAiChatModel model = OpenAiChatModel.builder()
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .apiKey(apiKey)
                .modelName("qwen-max")
                .build();

        Calculator calculator = new Calculator();
        Assistant assistant = AiServices.builder(Assistant.class)
                .chatModel(model)
                .tools(calculator)
                .build();
        // 测试基础功能--大模型function call
        String response1 = assistant.chat("1+1等于几。");
        System.out.println(response1);
        // 测试基础功能--直接调用工具结果
        // 测试复杂对象参数
        String response = assistant.chat("获取当前时间。");
        System.out.println(response);

        // 测试集合类型参数
        String response2 = assistant.chat("计算数字列表[1, 2, 3, 4, 5]的总和。");
        System.out.println(response2);

    }
}
Calculator是工具的集合类,里面定义了各种工具,这里因为只是入门的,所以都是一些很简单的实现,实际在项目中可以进行丰富,查数据库,查es等等
Assistant这个接口的定义是固定的,返回值也是固定的String

main方法里面就是实际调用了,运行效果如下:

1+1等于2。
当前的时间是2026-01-07 14:24:08。
数字列表 [1, 2, 3, 4, 5] 的总和是 15。

注意这里返回的结果,看上去是被润色了的,比如add方法的返回值是2但是调用模型结果返回的是1+1=2,在实际项目中可能并不需要润色,只需要结果,特别是从数据库查询的结果,可能就是直接返回给页面展示了,所以有两种方式:

第一,在模型调用的时候,prompt限定:

String response1 = assistant.chat("1+1等于几,如果是工具调用,请直接返回工具调用结果,不要添加任何额外说明。");
System.out.println(response1);
// 测试基础功能--直接调用工具结果
// 测试复杂对象参数
String response = assistant.chat("获取当前时间,如果是工具调用,请直接返回工具调用结果,不要添加任何额外说明。");
System.out.println(response);

// 测试集合类型参数
String response2 = assistant.chat("计算数字列表[1, 2, 3, 4, 5]的总和,如果是工具调用,请直接返回工具调用结果,不要添加任何额外说明。");
System.out.println(response2);

输出结果如下:

2
2026-01-07 14:52:47
15

第二,langchian4j的tool注解里面有个参数可以支持直接返回结果,不经过大模型润色:

package com.das.ai.langchain4j;

import dev.langchain4j.agent.tool.ReturnBehavior;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.Result;
import dev.langchain4j.service.SystemMessage;
import io.agentscope.core.tool.ToolParam;
import org.apache.commons.lang3.StringUtils;

import java.util.List;

public class Lanngchain4jAppV2 {

    static class Calculator {
        @Tool(value = "两个数相加", returnBehavior = ReturnBehavior.IMMEDIATE)
        int add(int a, int b) {
            return a + b;
        }

        // 支持集合类型参数的工具方法 - 修改为只返回计算结果
        @Tool(value = "计算数字列表的总和", returnBehavior = ReturnBehavior.IMMEDIATE)
        int sumList(List<Integer> numbers) {
            int sum = numbers.stream().mapToInt(Integer::intValue).sum();
            return sum;
        }

        @Tool(value = "获取当前时间", returnBehavior = ReturnBehavior.IMMEDIATE)
        public String getTime(
                @ToolParam(name = "zone", description = "时区,例如:北京") String zone) {
            return java.time.LocalDateTime.now()
                    .format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        }
    }

    static void printResult(Result<?> result) {
        if (result.content() != null) {
            System.out.println(result.content());
        } else {
            result.toolExecutions().forEach(ex -> {
                if (StringUtils.isNoneBlank(ex.result())) {
                    System.out.println(ex.result());
                }
            });
        }
    }

    @SystemMessage(
            "你是一个严格的工具执行助手。你的规则如下:\n" +
                    "1. 当你需要调用工具时,只输出工具调用请求。\n" +
                    "2. 当你收到工具的返回结果时,你的最终回答 **必须** 并且 **只能** 是工具返回的原始值。\n" +
                    "3. 绝对禁止在工具返回值前后添加任何解释、前缀、后缀或 conversational text (例如 '好的,结果是:' 或 '...')。\n" +
                    "4. 如果工具返回的不是字符串,请将其转换为字符串形式输出。\n" +
                    "5. 只有在用户问题不需要任何工具就能直接回答时,你才能进行正常对话。"
    )
    interface Assistant {
        /**
         * 与助手进行一次对话,返回包含内容与元信息的结果
         */
        Result<String> chat(String message);
    }

    public static void main(String[] args) {
        String apiKey = "sk-.....";
        if (apiKey == null || apiKey.isEmpty()) {
            throw new IllegalStateException("请先在环境变量中配置 DASHSCOPE_API_KEY");
        }
        OpenAiChatModel model = OpenAiChatModel.builder()
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .apiKey(apiKey)
                .modelName("qwen-max")
                .build();

        Calculator calculator = new Calculator();
        Assistant assistant = AiServices.builder(Assistant.class)
                .chatModel(model)
                .tools(calculator)
                .build();
        // 测试基础功能--大模型function call
        Result<String> response1 = assistant.chat("1+1等于几。");
        printResult(response1);
        // 测试基础功能--直接调用工具结果
        System.err.println(calculator.add(1, 1));

        // 测试复杂对象参数
        Result<String> response = assistant.chat("获取当前时间。");
        printResult(response);

        // 测试集合类型参数
        Result<String> response2 = assistant.chat("计算数字列表[1, 2, 3, 4, 5]的总和。");
        printResult(response2);

    }
}

注意V2版本Assistant接口里面的方法返回值不一样,解析返回结果的方式也不一样

圈重点:在我的项目中,会涉及到调用es查询数据,如果返回的数据字段很多的话,润色的过程非常耗时,5条数据润色完要一分多钟,即使不需要润色,也就是在promt里面限定了,也是会很耗时,因为返回的结果还是先要经过大模型,所以推荐采用第二种方式,当然也有其他的方式,在下面的agentscope中会提到

下面介绍agentscope(java)的入门,也很简单:https://java.agentscope.io/en/intro.html

废话不多说,直接上代码:

package com.das.ai.agentscope;

import io.agentscope.core.ReActAgent;
import io.agentscope.core.message.Msg;
import io.agentscope.core.model.DashScopeChatModel;
import io.agentscope.core.tool.Tool;
import io.agentscope.core.tool.ToolParam;
import io.agentscope.core.tool.Toolkit;

import java.util.List;

public class QuickStart {
    public static void main(String[] args) {
        // 准备工具
        Toolkit toolkit = new Toolkit();
        toolkit.registerTool(new SimpleTools());

        // 创建智能体
        ReActAgent jarvis = ReActAgent.builder()
                .name("Jarvis")
                .sysPrompt("你是一个名为 Jarvis 的助手")
                .model(DashScopeChatModel.builder()
                        .apiKey("sk-.......")
                        .modelName("qwen3-max")
                        .build())
                .toolkit(toolkit)
                .build();

        // 发送消息
        Msg msg = Msg.builder()
                .textContent("你好!Jarvis,现在几点了?")
                .build();

        Msg response = jarvis.call(msg).block();
        System.out.println(response.getTextContent());

        msg = Msg.builder()
                .textContent("你好!Jarvis,5加3等于多少?")
                .build();

        response = jarvis.call(msg).block();
        System.out.println(response.getTextContent());
    }
}

// 工具类
class SimpleTools {
    @Tool(name = "get_time", description = "获取当前时间")
    public String getTime(
            @ToolParam(name = "zone", description = "时区,例如:北京") String zone) {
        return java.time.LocalDateTime.now()
                .format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }

    @Tool(name = "add", description = "加法计算")
    public String add(
            @ToolParam(name = "a", description = "第一个加数") int a,
            @ToolParam(name = "b", description = "第二个加数") int b) {
        return String.valueOf(a + b);
    }

    @Tool(name = "arrayAdd", description = "数组加法计算")
    public int sumList(
            @ToolParam(name = "numbers", description = "整数数组") List<Integer> numbers) {
        int sum = numbers.stream().mapToInt(Integer::intValue).sum();
        return sum;
    }
}

比langchain4j的代码要清爽,简洁很多,agentscope的tool注解不支持直接返回结果(spring-ai的tool注解支持),也就是结果一定会经过大模型,所以要在promt里面加上限定词,最后再说一说如果是agentscope调用,但是返回的结果字段很多,很慢的问题,假设有这么一个方法:

@Tool(name = "queryRag", description = "知识库查询")
public String queryRag(
        @ToolParam(name = "query", description = "查询内容") String query) {
    return "知识库查询结果:" + query;
}

如果返回的结果有几千甚至上万个字,那么一定会很耗时,这个时候,可以返回一个固定值,比如return "rag:query",再定义一个全局的map,把结果放到这个map里面,再在调用的时候判断结果是不是等于rag:query,也就是Msg response = jarvis.call(msg).block();这里的

response.getTextContent()

,如果等于rag:query就从全局map里面把结果取出来就行

PS:如果项目允许的话,还是建议使用spring-ai

Logo

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

更多推荐