初识 spring ai 之rag、mcp、tools calling使用
环境准备
在使用 RAG(检索增强生成)时,可以选择使用本地缓存保存 Embedding 向量,也可以使用数据库存储。本示例选择使用 PostgreSQL 提供的向量数据库(pgvector),并通过 Docker 安装。
Docker 配置
以下是 docker-compose-environment.yml
文件的内容:
version: '3'
services:
vector_db:
image: pgvector/pgvector:v0.5.0
container_name: vector_db
restart: always
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=springai
- PGPASSWORD=postgres
volumes:
- ./pgvector/sql/init.sql:/docker-entrypoint-initdb.d/init.sql
logging:
options:
max-size: 10m
max-file: "3"
ports:
- '5432:5432'
healthcheck:
test: "pg_isready -U postgres -d vector_store"
interval: 2s
timeout: 20s
retries: 10
networks:
- my-network
networks:
my-network:
driver: bridge
init.sql
文件用于安装 pgvector
扩展:
-- 如果当前数据库中尚未安装 vector 扩展,则安装它;如果已经安装,则不做任何操作。
CREATE EXTENSION IF NOT EXISTS vector;
Maven 依赖
在项目中引入以下 Maven 依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-M6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
<version>1.0.0-M6</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-webflux-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
配置文件
在 application.yml
中配置数据源和 AI 服务:
spring:
datasource:
driver-class-name: org.postgresql.Driver
username: postgres
password: postgres
url: jdbc:postgresql://localhost:15432/ai-rag-knowledge
type: com.zaxxer.hikari.HikariDataSource
hikari:
pool-name: HikariCP
minimum-idle: 5
idle-timeout: 600000
maximum-pool-size: 10
auto-commit: true
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
ai:
mcp:
client:
stdio:
servers-configuration: classpath:/config/mcp-servers-config.json
openai:
base-url: xxx
api-key: xxx
RAG
什么是 RAG?
检索增强生成(Retrieval-Augmented Generation,RAG)是一种结合信息检索与大型语言模型(LLM)的技术架构。它通过以下方式显著提升 AI 回答的质量和准确性:
- 从知识库中检索相关文档:通过向量数据库(如 pgvector)存储文档的嵌入向量,能够快速检索与用户问题相关的文档。
- 将检索结果作为上下文提供给 LLM:将检索到的文档内容作为提示词的一部分,提供给 LLM 以生成更准确的回答。
- 生成基于上下文的精准回答:结合检索到的文档内容,LLM 能够生成更具针对性和可靠性的回答。
相比传统 LLM,RAG 能有效解决“幻觉”问题,特别适合企业知识库、技术文档问答等场景。
示例代码
配置向量存储
以下代码展示了如何配置向量存储:
@Bean
public TokenTextSplitter tokenTextSplitter() {
return new TokenTextSplitter();
}
@Bean
public OpenAiApi openAiApi(@Value("${spring.ai.openai.base-url}") String baseUrl, @Value("${spring.ai.openai.api-key}") String apiKey) {
return OpenAiApi.builder().baseUrl(baseUrl).apiKey(apiKey).build();
}
@Bean("openAiSimpleVectorStore")
public SimpleVectorStore simpleVectorStore(OpenAiApi openAiApi) {
OpenAiEmbeddingModel openAiEmbeddingModel = new OpenAiEmbeddingModel(openAiApi);
return SimpleVectorStore.builder(openAiEmbeddingModel).build();
}
@Bean("openAiPgVectorStore")
public PgVectorStore pgVectorStore(JdbcTemplate jdbcTemplate, OpenAiApi openAiApi) {
OpenAiEmbeddingModel openAiEmbeddingModel = new OpenAiEmbeddingModel(openAiApi);
return PgVectorStore.builder(jdbcTemplate, openAiEmbeddingModel).vectorTableName("vector_store_openai").build();
}
上传文档
以下代码展示了如何上传文档到向量数据库:
@Autowired
private OpenAiChatModel openAiChatModel;
@jakarta.annotation.Resource(name = "openAiPgVectorStore")
private PgVectorStore pgVectorStore;
@jakarta.annotation.Resource
private TokenTextSplitter tokenTextSplitter;
@Test
public void upload() {
TikaDocumentReader reader = new TikaDocumentReader("./data/线上常见问题案例和排查工具.md");
List<Document> documents = reader.get();
List<Document> documentSplitterList = tokenTextSplitter.apply(documents);
documents.forEach(doc -> doc.getMetadata().put("topic", "线上常见问题排查案例和排查工具"));
documentSplitterList.forEach(doc -> doc.getMetadata().put("topic", "线上常见问题排查案例和排查工具"));
pgVectorStore.accept(documentSplitterList);
log.info("上传完成");
}
问答功能
以下代码展示了如何实现基于 RAG 的问答功能:
@Test
public void chat() {
String message = "你用过哪些分析定位 Java 故障/性能的工具";
String SYSTEM_PROMPT = """
Use the information from the DOCUMENTS section to provide accurate answers but act as if you knew this information innately.
If unsure, simply state that you don't know.
Another thing you need to note is that your reply must be in Chinese!
DOCUMENTS:
{documents}
""";
SearchRequest request = SearchRequest.builder()
.query(message)
.topK(5)
.filterExpression("topic == '线上常见问题排查案例和排查工具'")
.build();
List<Document> documents = pgVectorStore.similaritySearch(request);
String documentsCollectors = null == documents ? "" : documents.stream().map(Document::getText).collect(Collectors.joining());
Message ragMessage = new SystemPromptTemplate(SYSTEM_PROMPT).createMessage(Map.of("documents", documentsCollectors));
ArrayList<Message> messages = new ArrayList<>();
messages.add(new UserMessage(message));
messages.add(ragMessage);
ChatResponse chatResponse = openAiChatModel.call(new Prompt(
messages,
OpenAiChatOptions.builder()
.model("gpt-4o")
.build()));
log.info("测试结果:{}", JSON.toJSONString(chatResponse));
}
tools calling
什么是tools calling?
tools calling是一种增强 LLM 功能的技术,通过定义一组工具(如时间查询、计算器等),让 LLM 能够调用这些工具来完成特定任务。工具调用的核心思想是将 LLM 的生成能力与外部功能结合,提升其解决问题的能力。
tools calling的主要流程:
- 定义tool:通过注解或接口定义工具的功能和描述。
- 注册tool:将工具注册到 LLM 的调用上下文中。
- 调用tool:在用户输入中触发工具调用,LLM 根据工具的描述生成调用请求。
以下是工具调用的示例代码:
定义tool
class DateTimeTools {
@Tool(description = "获取当前时间")
String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
@Tool(description = "设置用户闹钟,时间格式为 ISO-8601")
void setAlarm(String time) {
LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
System.out.println("闹钟已设置:" + alarmTime);
}
}
测试tools calling
@SpringBootTest
@Slf4j
@RunWith(SpringRunner.class)
public class ToolsCalling {
@Resource
private OpenAiChatModel openAiChatModel;
@Tool(description = "获取当前时间")
String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
@Test
public void test() {
String response = ChatClient.create(openAiChatModel)
.prompt("Can you set an alarm 10 minutes from now?")
.tools(new DateTimeTools())
.call()
.content();
System.out.println(response);
}
}
MCP 服务
什么是 MCP?
MCP(Model-Callable Procedures,模型可调用过程)是一种通过定义工具服务来扩展 LLM 功能的技术架构。它允许 LLM 调用外部服务来完成复杂任务,例如查询数据库、执行计算或获取系统信息。
MCP 的主要特点:
- 工具服务化:将工具功能封装为服务,支持独立运行和调用。
- 模型调用:通过 MCP 协议,LLM 可以调用这些服务来完成任务。
- 动态扩展:支持动态加载和扩展工具服务,满足不同场景需求。
以下是 MCP 服务的实现和使用示例:
创建 MCP 服务
引入依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
</dependency>
编写 MCP 服务:
@Slf4j
@Service
public class ComputerService {
@Tool(description = "获取电脑配置")
public ComputerFunctionResponse queryConfig(ComputerFunctionRequest request) {
log.info("获取电脑配置信息 {}", request.getComputer());
// 获取系统属性
Properties properties = System.getProperties();
ComputerFunctionResponse response = new ComputerFunctionResponse();
response.setOsName(properties.getProperty("os.name"));
response.setOsVersion(properties.getProperty("os.version"));
response.setOsArch(properties.getProperty("os.arch"));
response.setUserName(properties.getProperty("user.name"));
response.setUserHome(properties.getProperty("user.home"));
response.setUserDir(properties.getProperty("user.dir"));
response.setJavaVersion(properties.getProperty("java.version"));
return response;
}
}
请求参数
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ComputerFunctionRequest {
@JsonProperty(required = true, value = "computer")
@JsonPropertyDescription("电脑名称")
private String computer;
}
响应参数
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ComputerFunctionResponse {
@JsonProperty(required = true, value = "osName")
@JsonPropertyDescription("操作系统名称")
private String osName;
@JsonProperty(required = true, value = "osVersion")
@JsonPropertyDescription("操作系统版本")
private String osVersion;
@JsonProperty(required = true, value = "osArch")
@JsonPropertyDescription("操作系统架构")
private String osArch;
@JsonProperty(required = true, value = "userName")
@JsonPropertyDescription("用户的账户名称")
private String userName;
@JsonProperty(required = true, value = "userHome")
@JsonPropertyDescription("用户的主目录")
private String userHome;
@JsonProperty(required = true, value = "userDir")
@JsonPropertyDescription("用户的当前工作目录")
private String userDir;
@JsonProperty(required = true, value = "javaVersion")
@JsonPropertyDescription("Java 运行时环境版本")
private String javaVersion;
}
注册 MCP 服务
@Slf4j
@SpringBootApplication
public class McpServerComputerApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(McpServerComputerApplication.class, args);
}
@Bean
public ToolCallbackProvider computerTools(ComputerService computerService) {
return MethodToolCallbackProvider.builder().toolObjects(computerService).build();
}
@Override
public void run(String... args) throws Exception {
log.info("MCP Server Computer 启动成功!");
}
}
使用 MCP 服务
在 mcp-servers-config.json
中配置服务信息:
{
"mcp-server-computer": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.stdio=true",
"-jar",
"/path/to/mcp-server-computer-1.0.0.jar"
]
}
}
调用 MCP 服务:
@Test
public void test() {
String userInput = "获取电脑配置";
var chatClient = chatClientBuilder
.defaultTools(tools)
.defaultOptions(OpenAiChatOptions.builder()
.model("gpt-4o")
.build())
.build();
System.out.println("\n>>> QUESTION: " + userInput);
System.out.println("\n>>> ASSISTANT: " + chatClient.prompt(userInput).call().content());
}