在使用 RAG(检索增强生成)时,可以选择使用本地缓存保存 Embedding 向量,也可以使用数据库存储。本示例选择使用 PostgreSQL 提供的向量数据库(pgvector),并通过 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 依赖:
<dependency>
<groupId>org.springframework.aigroupId>
<artifactId>spring-ai-bomartifactId>
<version>1.0.0-M6version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.aigroupId>
<artifactId>spring-ai-openai-spring-boot-starterartifactId>
<version>1.0.0-M6version>
dependency>
<dependency>
<groupId>org.springframework.aigroupId>
<artifactId>spring-ai-tika-document-readerartifactId>
<version>1.0.0-M6version>
dependency>
<dependency>
<groupId>org.springframework.aigroupId>
<artifactId>spring-ai-pgvector-store-spring-boot-starterartifactId>
<version>1.0.0-M6version>
dependency>
<dependency>
<groupId>org.springframework.aigroupId>
<artifactId>spring-ai-mcp-server-spring-boot-starterartifactId>
<version>1.0.0-M6version>
dependency>
<dependency>
<groupId>org.springframework.aigroupId>
<artifactId>spring-ai-mcp-client-webflux-spring-boot-starterartifactId>
<version>1.0.0-M6version>
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
检索增强生成(Retrieval-Augmented Generation,RAG)是一种结合信息检索与大型语言模型(LLM)的技术架构。它通过以下方式显著提升 AI 回答的质量和准确性:
相比传统 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是一种增强 LLM 功能的技术,通过定义一组工具(如时间查询、计算器等),让 LLM 能够调用这些工具来完成特定任务。工具调用的核心思想是将 LLM 的生成能力与外部功能结合,提升其解决问题的能力。
tools calling的主要流程:
以下是工具调用的示例代码:
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);
}
}
@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(Model-Callable Procedures,模型可调用过程)是一种通过定义工具服务来扩展 LLM 功能的技术架构。它允许 LLM 调用外部服务来完成复杂任务,例如查询数据库、执行计算或获取系统信息。
MCP 的主要特点:
以下是 MCP 服务的实现和使用示例:
引入依赖:
<dependency>
<groupId>org.springframework.aigroupId>
<artifactId>spring-ai-mcp-server-spring-boot-starterartifactId>
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;
}
@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-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());
}