摘要:

        本文主要介绍了AI应用开发的两个重要概念--工具调用和检索增强,并通过一个具体的例子,搭建一个智能助手结合进行讲解,旨在帮助小伙伴对AI在实际Java开发中的具体应用进行了解,帮助大家扩展大模型开发方面的知识。

一,工具调用(Funtion-call)

1,基本介绍

        工具调用(亦称函数调用)是 AI 应用的常见模式,允许模型通过与一组 API(即工具)交互来扩展其能力。简单来说,工具调用就是让大模型能够调用你定义好的各种工具,其中包括:信息检索、业务功能等等,使大模型变得更强大。

更多关于大模型“工具调用”的细节可以访问官网了解:工具(Tool)调用 :: Spring AI 中文文档

2,基本原理

        本质就是用户提出“额外”需求,如常用的豆包的联网搜索等,也是通过网页搜索工具实现,而非大模型的功能;大模型获取工具结果然后包装返回给用户。

  1. 工具定义:当我们想让模型使用某个工具或业务方法时,我们会在聊天请求中包含该工具的定义。每个工具的定义都包括名称、描述和输入参数

  2. 工具调用请求:当模型决定调用工具时,它会发送包含工具名称及符合预定义模式的输入参数的响应

  3. 执行工具:应用程序负责根据工具名称识别对应工具,并使用提供的输入参数执行该工具

  4. 处理结果并返回:工具调用的结果由程序进行处理,应用程序将工具调用结果返回至模型

  5. 包装结果:模型最终利用工具调用结果作为附加上下文生成响应

3,具体实战

【1】@Tool注解

(1)name:工具名称。若不指定,默认使用方法名称。模型通过此名称识别调用工具,因此不允许在同一类中存在同名工具。模型处理单个聊天请求时,所有可用工具的名称必须保持全局唯一。

(2)description:工具描述,用于指导模型判断何时及如何调用该工具。若未指定,默认使用方法名称作为工具描述。但强烈建议提供详细描述,因为这对模型理解工具用途和使用方式至关重要。若描述不充分,可能导致模型在该调用工具时未调用,或错误调用工具。

(3)returnDirect:控制工具结果直接返回客户端(true)还是传回模型(false)。

【2】工具实现

(1)创建一个工具类,标注为“Component”,用于依赖注入。

(2)实现具体业务方法,也可以在该工具中调用已存在的业务方法,例如:保存用户预约看房信息并返回预约单号。

(3)通过description工具描述和参数描述让大模型了解这个函数的作用,同时函数名也应该更加规范清晰,如此一来能够使大模型对工具调用更加精准。

    @Tool(description = "保存用户预约看房信息并返回预约单号")
    public String setViewAppointment(
            @ToolParam(description = "用户姓名") String name,
            @ToolParam(description = "用户电话号码") String phone,
            @ToolParam(description = "用户选择的公寓名称对应的公寓id") Long apartmentId,
            @ToolParam(description = "用户给定的预约时间") Date appointmentTime,
            @ToolParam(description = "用户的备注信息") String additionalInfo
    ){
        Long userId = LoginUserHolder.getLoginUser().getUserId();
        ViewAppointment viewAppointment = new ViewAppointment();
        viewAppointment.setUserId(userId);
        viewAppointment.setName(name);
        viewAppointment.setPhone(phone);
        viewAppointment.setApartmentId(apartmentId);
        viewAppointment.setAppointmentTime(appointmentTime);
        viewAppointment.setAdditionalInfo(additionalInfo);
        viewAppointment.setAppointmentStatus(AppointmentStatus.WAITING);
        boolean save = viewAppointmentService.save(viewAppointment);

        if (save) {
            return UUID.randomUUID().toString()+viewAppointment.getId();
        }else {
            return "预约失败";
        }
    }

【3】装载工具

        我们定义了许多工具(函数)等,那如何让大模型知道有哪些工具(函数)供其使用呢?放心,Spring Ai框架早就为我们考虑好了这个问题:

        在构造chatClient实例中有一个defaultTools()方法,该方法接收Object参数,我们通过传入工具(函数)所在类的实例(注入或者new皆可)即可,这些工具的基本信息会自动和prompt发送给大模型,这样大模型就能够根据用户请求决定是否调用工具/哪个工具。

二,搭建RAG知识库

1,基本介绍

【1】向量模型

        顾名思义,这种模型的主要作用就是将文本转化为设定好维数的向量,以阿里的通用向量模型text-embedding-v4、v3等为例,具体参数如下:

【2】向量数据库

        向量数据库中的数据都会被向量模型(或特定算法)解析为固定维度(可以更改,一般需要与向量转化模型生成的向量维数一致)的向量[0.36732,0.74398......]

        同时在向量数据库中,查询与传统关系型数据库不同,它们执行的是相似性搜索而非精确匹配,当给定一个向量作为查询时,向量数据库会返回与查询向量 “相似” 的向量

【3】RAG检索增强生成

        当用户需要向 AI 模型发送查询时,首先在向量数据库中检索一组相似文档。这些文档随后作为用户问题的上下文,与用户查询整合一起发送给大模型。这种技术被称为检索增强生成。

        大模型的训练需要时间,因此,大模型的回答会有滞后性,而且会捏造对专业领域问题的回答,所以通过额外的知识库可以使大模型更加真实专业。

2,基本配置

【1】引入依赖

        构造rag检索增强的核心依赖“spring-ai-rag”;读取pdf文件的依赖不是必须的,这里是增加的一个额外功能--“方便管理端通过pdf导入业务知识”;连接数据库需要具体看大家选择什么数据库作为向量数据库,如果不知道自己的数据库需要引入哪个可以参考向量数据库 :: Spring AI 中文文档记载了目前各种支持的向量数据库

        <!--构建RAG库的依赖-->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-rag</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!--读取pdf文件的依赖-->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-pdf-document-reader</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!--连接向量库的依赖-->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-vector-store-elasticsearch</artifactId>
            <version>1.0.0</version>
        </dependency>
        <dependency>
            <groupId>co.elastic.clients</groupId>
            <artifactId>elasticsearch-java</artifactId>
            <version>8.18.1</version>
        </dependency>

【2】yaml文件配置

(1)首先是es的连接配置,IP、端口默认9200和用户名(默认)、密码

(2)下面是关于ai的es配置,初始化索引(类似表)、索引名、向量维数(这里的维数一定要和选择的向量模型一致,如:v3默认转化1024维所以这里我填写1024)和相似算法

spring:
   elasticsearch:
   uris: https://<host-ip>:9200<默认>
   password:xxxx
   username: elastic
   ai:
     vectors tore:
     elasticsearch:
       initialize-schema: true
       index-name: springai-index #索引名相当于MySQL表名
       dimensions: 1024
       similarity: cosine

【3】温馨提示(es版本>8.10)

(1)es的下载和一些配置有些麻烦,如果第一次接触es的小伙伴可以参考这个大佬的文章:

Linux环境下安装Elasticsearch,史上最详细的教程来啦~_linux elasticsearch-CSDN博客

大佬的文章非常详细,包括各种突发情况等,小伙伴自行参阅。

(2)在连接es时可能会出现SSL证书的连接问题,两种解决方案:1,在es的配置文件中关闭SSL验证机制;2,将es的SSL证书加入到jdk的信任文件中

3,代码实现

【1】构造检索增强的chatClient实例

(1)自动注入DashScope模型、VectorStore接口实现类实例ElasticsearchVectorStore和聊天记忆ChatMemory

(2)添加Rag流程的拦截器RetrievalAugmentationAdvisor,这是实现Rag的核心,下面的方法都是其增强组件,如:queryExpander、documentRetriever...等等

(3)这里的增强检索流程是:扩充问题(扩充用户问题) -> 对生成的问题进行上下文增强 -> 向量库检索 -> 拼接检索到的文件

    @Bean
    public ChatClient ragChatClient(
        DashScopeChatModel model, 
        ElasticsearchVectorStore vectorStore, 
        ChatMemory chatMemory
     ) {
        ChatClient.Builder expanderBuilder = ChatClient.builder(model);

        ContextualQueryAugmenter contextualQueryAugmenter = ContextualQueryAugmenter.builder().allowEmptyContext(true).build();

        VectorStoreDocumentRetriever vectorStoreDocumentRetriever = VectorStoreDocumentRetriever.builder().topK(3).vectorStore(vectorStore).build();

        MultiQueryExpander multiQueryExpander = MultiQueryExpander.builder().chatClientBuilder(expanderBuilder).build();

        return ChatClient.builder(model)
                .defaultSystem(new ClassPathResource("system_prompt.txt"))
                .defaultSystem("请根据上下文回答问题,遇到上下文没有的问题,不要自己随意编造")
                .defaultAdvisors(
                        MessageChatMemoryAdvisor.builder(chatMemory).build(),
                        new SimpleLoggerAdvisor(),
                        RetrievalAugmentationAdvisor.builder()
                                //扩充问题,默认3个+原来那1个
                                .queryExpander(multiQueryExpander)
                                //向量库中检索
                                .documentRetriever(vectorStoreDocumentRetriever)
                                //拼接检索到的文档
                                .documentJoiner(new ConcatenationDocumentJoiner())
                                //对生成的问题进行上下文增强:
                                .queryAugmenter(contextualQueryAugmenter).build()
                )
                .defaultTools(new AgreementTools(), new AppointmentTools())
                .build();
    }

【2】定义RAG问答接口

        接口定义基本无需改动,基本的用户prompt和聊天记忆ID,rag的具体实现和增强组件基本都包装到ragChatClient实例中,接口直接调用会自动实现检索增强

    @GetMapping(value = "/chat/rag", produces = "text/html;charset=UTF-8")
    public Flux<String> chatByRag(String prompt) {
        return ragChatClient.prompt()
                .user(prompt)
                .advisors(a -> a.param(
                        ChatMemory.CONVERSATION_ID,
                        LoginUserHolder.getLoginUser().getUserId())
                )
                .stream()
                .content();
    }

通过ChatClient配置这些组件,检索到的相关文档数量提升 30%~50%(尤其针对模糊查询或专业领域术语),减少的重复代码,新业务接口接入 RAG 只需一行代码,模型 “幻觉”(编造未在文档中出现的信息)概率降低,使增强检索流程更加“好用”。

【3】测试结果展示

(1)测试为了节约时间,采用的是基于内存实现的VectorStore->inMemoryVectorStore

    @Bean
    public VectorStore inMemoryVectorStore(DashScopeEmbeddingModel embeddingModel) {
        return SimpleVectorStore.builder(embeddingModel).build();
    }

(2)测试类中注入ragChatClient,定义测试类testRAG手动向向量数据库(内存)添加数据,因为修改内存存储,原来ragChatClient实例的es暂时修改为inMemoryVectorStore即可

    @Test
    public void testRAG() {
        List<Document> documents = List.of(
                new Document("李冲,原名李思冲,字思顺。"),
                new Document("李冲,陇西狄道(今甘肃临洮县)人。"),
                new Document("李冲,南北朝时期北魏名臣,镇北将军李宝的幼子。")
        );
        inMemoryVectorStore.add(documents);
        String content = ragChatClient.prompt("李冲是谁?").call().content();
        System.out.println(content);

(3)测试结果展示

2025-09-28T17:05:37.271+08:00  INFO 21768 --- [spring-ai] [           main] o.s.ai.vectorstore.SimpleVectorStore     : Calling EmbeddingModel for document id = aa34001f-9a57-4f15-8948-1ce6db95209a
2025-09-28T17:05:37.454+08:00  INFO 21768 --- [spring-ai] [           main] o.s.ai.vectorstore.SimpleVectorStore     : Calling EmbeddingModel for document id = f6b0d34b-b25b-406e-864a-5fe69d035d54
2025-09-28T17:05:37.634+08:00  INFO 21768 --- [spring-ai] [           main] o.s.ai.vectorstore.SimpleVectorStore     : Calling EmbeddingModel for document id = 28e8584a-f291-4a07-838b-25851e0f4a77
李冲,原名李思冲,字思顺,陇西狄道(今甘肃临洮县)人,是南北朝时期北魏的名臣,镇北将军李宝的幼子。

总结:

        通过上述流程,一个完完整整的与业务紧密相连的AI智能助手已经开发完成,从基本交互-->工具调用-->最后到检索增强RAG,智能助手功能更加强大完善,重要的是各位小伙伴也从搭建的过程中加深了对AI应用开发的具体了解,这也是本文的重点,也希望大家能够根据这个例子,发挥奇思妙想开发属于自己的AI应用,提升自己的AI应用开发能力。

Logo

更多推荐