本地搭建搜索Agent(SpringAI + RAG + SearXNG + MCP)

前言

课程链接:https://coding.imooc.com/class/948.html

其实现在的Agent平台把这个课的东西都实现得很好用,让我自己搭个agent我也大概率不会选择用这份代码从头搭建。就当手动搭建了下,对原理多些了解吧。

搭建环境

下载Java的IDEA,我用的IntelliJ。创建项目时选择Maven,OpenJDK21。

然后下载docker桌面版,到命令行 docker pull mysql:8.4

在本地把下面命令行的文件夹创建,然后powershell运行

bash 复制代码
docker run -p 5506:3306 --name mysql9-imooc `
-v E:/mysql8/log:/var/log/mysql `
-v E:/mysql8/data:/var/lib/mysql `
-v E:/mysql8/conf:/etc/mysql/conf.d `
-v E:/mysql8/mysql-files:/var/lib/mysql-files `
-e MYSQL_ROOT_PASSWORD=root `
-d mysql:8.4 `
--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

下载个navicat,建张表

sql 复制代码
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for product
-- ----------------------------
DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (
`product_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '商品编号',
`product_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '商品名称',
`brand` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '品牌',
`price` int NOT NULL COMMENT '销售价格(单位:分)',
`stock` int unsigned NOT NULL DEFAULT '0' COMMENT '库存数量',
`description` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '商品简介',
`status` int NOT NULL DEFAULT '1' COMMENT '状态(0-下架 1-上架 2-预售)',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商品表';

SET FOREIGN_KEY_CHECKS = 1;

vscode 安装Live Server,给前端用。

快速入门Springboot

打开JavaIDE,这样配置下pom.xml

xml 复制代码
<packaging>pom</packaging>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.5.0</version>
    <relativePath/>
</parent>

这样就路由了127.0.0.1:8080/hello/world

可以在resources里面解耦不同开发环境下的配置

将deepseek接口接入项目

调用api

xml 复制代码
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>

普通对话、流式回答

先定义路由

Java的类似乎是得先写接口类、再实现具体的方法。

然后这样在浏览器里面访问下,能观察到回答是流式地在输出

AOP切面

比如现在要给一个路由加上计时,传统的做法是在这个函数里面加两个时间点。

如果是把所有的路由都要上计时,这个做法显得不那么优雅。

这样就拦截了,不过计时功能对于流传输而言是失效的。

配置.env



使用SSE与前端通信

解决跨域问题

和python里面的flask+yield挺像,都是流式传输数据。

跨域问题解决:

java 复制代码
package org.example;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Value("${website.domain}")
    private String domain;

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins(domain)
                .allowedMethods("*")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

之后前端访问后端配置的 website.domain 就能通信了。

前端和大模型交互

对接下接口

RAG

安装 Redis

bash 复制代码
docker pull redis/redis-stack:latest

docker run -d --name redis-stack `
-p 9379:6379 `
-e REDIS_ARGS="--requirepass 123456" `
redis/redis-stack:latest

然后可以装个 Tiny RDM

在项目里,把 pom.xml 填入以下内容。可能会出现提示没有包,把maven reload就行了:

xml 复制代码
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-advisors-vector-store</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-vector-store-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-transformers</artifactId>
        </dependency>

        <!--解析文档-->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-tika-document-reader</artifactId>
        </dependency>

把 application.yml 填入以下内容:

yml 复制代码
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      base-url: ${OPENAI_BASE_URL}
      chat:
        options:
          model: ${OPENAI_MODEL}
    vectorstore:
      redis:
        initialize-schema: true         # 是否初始化所需的模式
        index-name: lee-vectorstore     # 用于存储向量的索引名称
        prefix: 'embedding:'             # Redis 键的前缀
        
  data:
    redis:
      host: 127.0.0.1
      port: 9379
      password: 123456

  profiles:
    active: dev

然后遇到了开了梯子,github在IDEA里面连不上去。参考的这篇方法3:https://blog.csdn.net/DunKan/article/details/112646344

一直报错

C:\Users\PC.djl.ai\pytorch\2.5.1-cu124-win-x86_64\torch_cuda.dll: Can't find dependent libraries

这个地方配环境,,tmd配了一天,最后还是没能把GPU配上去,改成了CPU版。但是总算跑起来了。

java 复制代码
        // 强制使用 CPU 版本,避免 CUDA 依赖问题
        System.setProperty("PYTORCH_VERSION", "2.5.1");
        System.setProperty("PYTORCH_FLAVOR", "cpu");

        // 添加 PyTorch 镜像设置
        if (System.getProperty("PYTORCH_MIRROR") == null) {
            System.setProperty("PYTORCH_MIRROR", "https://download.pytorch.org/libtorch");
        }

这里我试了下,效果没有dify平台自带的分词效果好,我用起来真的很吐槽,但是就当个教程看看效果还是够用的。

分词要根据自己的文本来。我找了个一问一答的,按照问题+答案分的类。

然后把搜出来的数据,加上一些提示词,发给ai就好了。

SearXNG

bash 复制代码
docker pull searxng/searxng:latest

docker run -p 6080:8080 `
        --name searxng `
        -d --restart=always `
        -v "D:\data\SearXNG:/etc/searxng" `
        -e "BASE_URL=http://localhost:6080/" `
        -e "INSTANCE_NAME=wsm-instance" `
        searxng/searxng

然后在D:\data\SearXNG\settings.yml 里面改下,加上json

复制代码
  formats:
    - json

就可以使用json格式查数据了

复制代码
http://localhost:6080/search?q=风间影月&format=json

在流程上,和RAG差不多,调用本地的工具获得数据后,加上提示词发给大模型去处理。

MCP

将第三方的MCP集成进Spring

pom.xml

xml 复制代码
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-mcp</artifactId>
        </dependency>

application.yml

yml 复制代码
    mcp:
      client:
        enabled: true
        name: spring-ai-mcp-client-1.0.0
        type: ASYNC
        sse:
          connections:
            server1:
              url: https://mcp.amap.com
              sse-endpoint: /sse?key=09557e21174fff33be82c6cf0fa2cbcb
        stdio:
          servers-configuration: classpath:mcp-server.json

mcp-server.json

json 复制代码
{
  "mcpServers": {
    "filesystem": {
      "command": "C:\\Program Files\\nodejs\\npx.cmd",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "D:\\data\\MCP"
      ]
    }
  }
}

然后在构造时注入mcp tools

ChatServiceImpl.java

java 复制代码
    public ChatServiceImpl(ChatClient.Builder chatClientBuilder, ToolCallbackProvider tools) {
        this.chatClient = chatClientBuilder
                .defaultToolCallbacks(tools)
                .build();
    }

效果还可以

自己在Spring里面实现MCP

这里写下如何创建本地的查询时间的MCP

首先创个mcp-server模块

对这个server模块,做如下配置:

yml文件:

yml 复制代码
spring:
  application:
    name: spring-ai-mcp-server
  profiles:
    active: dev
  ai:
    mcp:
      server:
        name: spring-ai-mcp-server-sse
        version: 1.0.0
        sse-endpoint: /sse
        type: async

logging:
  level:
    root: info

在main函数里面注册MCP:

java 复制代码
@SpringBootApplication
public class Application {

//    http://localhost:9060/sse

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    /**
     * 注册MCP工具
     */
    @Bean
    public ToolCallbackProvider registMCPTools(DateTool dateTool) {
        return MethodToolCallbackProvider.builder()
                .toolObjects(dateTool)
                .build();
    }

}

工具这样实现:

java 复制代码
@Component
@Slf4j
public class DateTool {

    @Tool(description = "根据城市所在的时区id来获得当前的时间")
    public String getCurrentTimeByZoneId(String cityName, String zoneId) {

        log.info("========== 调用MCP工具:getCurrentTimeByZoneId() ==========");
        log.info(String.format("========== 参数 cityName:%s ==========", cityName));
        log.info(String.format("========== 参数 zoneId:%s ==========", zoneId));

        ZoneId zone = ZoneId.of(zoneId);

        // 获取该时区对应的当前时间
        ZonedDateTime zonedDateTime = ZonedDateTime.now(zone);
        String currentTime = String.format("当前的时间是 %s",
                zonedDateTime.format(
                        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        return currentTime;
    }

    @Tool(description = "获得当前时间")
    public String getCurrentTime() {

        log.info("========== 调用MCP工具:getCurrentTime() ==========");

        String currentTime = String.format("当前的时间是 %s",
                LocalDateTime.now().format(
                        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        return currentTime;
    }

}

在client的yml里把本地的mcp连上去:

yml 复制代码
            server2:
              url: http://localhost:9060
              sse-endpoint: /sse

就可以了,再次启动,问当前时间,将给出准确的时间。

加入邮箱MCP后的效果:

相关推荐
怎么面试6 小时前
EasyExcel 基础用法
java·服务器
huaiqiongying7 小时前
Springboot3+SpringSecurity6Oauth2+vue3前后端分离认证授权-客户端
java·vue.js·spring boot
华仔啊7 小时前
Java异常处理别再瞎搞了!阿里大神总结的 9 种最佳实践,让你的代码更健壮!
java·后端
杨杨杨大侠7 小时前
手搓责任链框架 5:执行流程
java·spring·github
Metaphor6927 小时前
Java 压缩 PDF 文件大小:告别臃肿,提升效率!
java·经验分享·pdf
m0_709788627 小时前
单片机点灯
java·前端·数据库
Pierre_7 小时前
通过SpringCloud Gateway实现API接口镜像请求(陪跑)网关功能
spring·spring cloud·gateway
野犬寒鸦7 小时前
力扣hot100:螺旋矩阵(边界压缩,方向模拟)(54)
java·数据结构·算法·leetcode
初始化8 小时前
JavaFx:生成布局 ViewBinding,告别 @FXML 注解
java·kotlin