前言
课程链接:https://coding.imooc.com/class/948.html
其实现在的Agent平台把这个课的东西都实现得很好用,让我自己搭个agent我也大概率不会选择用这份代码从头搭建。就当手动搭建了下,对原理多些了解吧。
搭建环境
下载Java的IDEA,我用的IntelliJ。创建项目时选择Maven,OpenJDK21。
然后下载docker桌面版,到命令行 docker pull mysql:8.4
。
在本地把下面命令行的文件夹创建,然后powershell运行
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,建张表
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
<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
<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挺像,都是流式传输数据。
跨域问题解决:
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
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就行了:
<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 填入以下内容:
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版。但是总算跑起来了。
// 强制使用 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
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
<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
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
{
"mcpServers": {
"filesystem": {
"command": "C:\\Program Files\\nodejs\\npx.cmd",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"D:\\data\\MCP"
]
}
}
}
然后在构造时注入mcp tools
ChatServiceImpl.java
public ChatServiceImpl(ChatClient.Builder chatClientBuilder, ToolCallbackProvider tools) {
this.chatClient = chatClientBuilder
.defaultToolCallbacks(tools)
.build();
}
效果还可以
自己在Spring里面实现MCP
这里写下如何创建本地的查询时间的MCP
首先创个mcp-server模块
对这个server模块,做如下配置:
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:
@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();
}
}
工具这样实现:
@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连上去:
server2:
url: http://localhost:9060
sse-endpoint: /sse
就可以了,再次启动,问当前时间,将给出准确的时间。
加入邮箱MCP后的效果: