1. 技术选型
| 组件 | 类型 |
|---|---|
| 大语言模型 | deepseek r1 |
| embedding模型 | bge-m3 |
| 框架选择 | spring ai 1.0.1(JDK 17以上) |
| 向量数据库 | pgvector |
2. 项目功能及接口文档
- 提供将法律法规文档通过embedding模型将知识导入向量库(导入文件格式只支持pdf和word)
POST http://127.0.0.1:8088/rag/init?filePath=D:downloads中华人民共和国劳动法_20181229.docx
- 提供法律咨询问答的接口
GET http://127.0.0.1:8088/rag/query?query=请根据劳动法告诉我,工厂每天早上九点上班,晚上十点下班,且没有支付足够响应报酬补偿的,一周无休,是否违法
3. 项目结构和依赖及配置项
pom.xml
"1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>3.2.4version>
<relativePath/>
parent>
<groupId>com.sikaryofficialgroupId>
<artifactId>spring-ai-chatartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>17java.version>
<maven.compiler.source>17maven.compiler.source>
<maven.compiler.target>17maven.compiler.target>
<spring-boot.version>3.2.4spring-boot.version>
<spring-ai.version>1.0.1spring-ai.version>
<spring-ai-alibaba.version>1.0.0.2spring-ai-alibaba.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>${spring-boot.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.aigroupId>
<artifactId>spring-ai-bomartifactId>
<version>${spring-ai.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloud.aigroupId>
<artifactId>spring-ai-alibaba-bomartifactId>
<version>${spring-ai-alibaba.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.aigroupId>
<artifactId>spring-ai-autoconfigure-model-openaiartifactId>
dependency>
<dependency>
<groupId>org.springframework.aigroupId>
<artifactId>spring-ai-autoconfigure-model-chat-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.aigroupId>
<artifactId>spring-ai-pdf-document-readerartifactId>
dependency>
<dependency>
<groupId>org.springframework.aigroupId>
<artifactId>spring-ai-tika-document-readerartifactId>
dependency>
<dependency>
<groupId>org.springframework.aigroupId>
<artifactId>spring-ai-pgvector-storeartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>org.postgresqlgroupId>
<artifactId>postgresqlartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>io.projectreactor.nettygroupId>
<artifactId>reactor-netty-httpartifactId>
dependency>
dependencies>
<repositories>
<repository>
<id>spring-milestonesid>
<name>Spring Milestonesname>
<url>https://repo.spring.io/milestoneurl>
<snapshots>
<enabled>falseenabled>
snapshots>
repository>
<repository>
<id>spring-snapshotsid>
<name>Spring Snapshotsname>
<url>https://repo.spring.io/snapshoturl>
<releases>
<enabled>falseenabled>
releases>
repository>
<repository>
<id>aliyunmavenid>
<name>aliyunname>
<url>https://maven.aliyun.com/repository/publicurl>
repository>
repositories>
<pluginRepositories>
<pluginRepository>
<id>publicid>
<name>aliyun nexusname>
<url>https://maven.aliyun.com/repository/apache-snapshotsurl>
<releases>
<enabled>trueenabled>
releases>
<snapshots>
<enabled>falseenabled>
snapshots>
pluginRepository>
pluginRepositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
配置文件application.yml
server:
port: 8088
spring:
datasource:
url: jdbc:postgresql://192.168.232.194:5432/ai_data?currentSchema=public
username: postgres
password: ske@001
jpa:
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.PostgreSQLDialect
defer-datasource-initialization: true # 允许先创建扩展再初始化表
ai:
openai:
api-key: hismk
base-url: http://192.168.201.230:3045/
chat:
options:
model: deepseek-r1:32b_awq
max_tokens: 1024
temperature: 0.2
top_p: 0.9
embedding:
options:
model: bge-m3
base-url: http://192.168.201.250:11434
enabled: true
# 打印日志
logging:
level:
com.sikaryofficial: DEBUG
提示词模板
当前日期:{current_date}
请基于以下上下文信息回答问题。请遵循以下规则:
1. 回答要专业、准确
2. 如果上下文不包含答案,请明确说明"根据提供的信息无法确定"
3. 使用中文回答
4. 保持回答简洁明了
上下文:
{context}
问题:{input}
请按以下格式回答:
【回答】: (你的回答)
【来源】: (指出回答基于哪些上下文片段,用1,2,3编号)
4. 功能实现
a. 配置相关
- chatClient配置
package com.sikaryofficial.ai.config;
/**
* @author : wuweihong
* @desc : TODO 请填写你的功能描述
* @date : 2025-11-04
*/
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ChatClientConfig {
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
return builder.defaultAdvisors(new SimpleLoggerAdvisor()).build();
}
}
2. 向量存储配置
package com.sikaryofficial.ai.config;
/**
* @author : wuweihong
* @desc : TODO 请填写你的功能描述
* @date : 2025-11-04
*/
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.pgvector.PgVectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@Configuration
public class VectorStoreConfig {
@Bean
public VectorStore vectorStore(EmbeddingModel embeddingClient, JdbcTemplate jdbcTemplate) {
return PgVectorStore.builder(jdbcTemplate, embeddingClient).dimensions(1024).vectorTableName("law_articles").build();
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
3. WebClient配置
package com.sikaryofficial.ai.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;
/**
* @author : wuweihong
* @desc : TODO 请填写你的功能描述
* @date : 2025-07-29
*/
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient() {
// 创建并配置 Netty 的 HttpClient
HttpClient httpClient = HttpClient.create()
.headers(headers -> headers
.remove("Transfer-Encoding") // 显式移除分块头
.set("Connection", "close") // 强制关闭连接避免分块
)
.compress(false); // 禁用压缩
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
}
ⅰ. 接口实现
- 文档导入向量数据库的接口实现
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.ExtractedTextFormatter;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.reader.pdf.ParagraphPdfDocumentReader;
import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig;
import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.List;
@Service
@RequiredArgsConstructor
public class DocumentService {
private final VectorStore vectorStore;
public void loadAndStoreDocuments(String filePath) throws IOException {
List documents = null;
if (StringUtils.isNotBlank(filePath) && filePath.endsWith(".pdf")) {
// 读取PDF文档
documents = loadPDFDocuments(filePath);
System.out.println("Total number of documents: " + documents.size());
} else if (StringUtils.isNotBlank(filePath) && filePath.endsWith(".docx")) {
// 读取DOCX文档
documents = loadDOCXDocuments(filePath);
System.out.println("Total number of documents: " + documents.size());
}
// 文本分割
TokenTextSplitter splitter = new TokenTextSplitter();
List splitDocs = splitter.apply(documents);
// 存储到向量数据库
vectorStore.add(splitDocs);
}
public List loadPDFDocuments(String filePath) throws IOException {
// PDF文档读取配置
PdfDocumentReaderConfig config = PdfDocumentReaderConfig.builder()
.withPageExtractedTextFormatter(new ExtractedTextFormatter.Builder()
.withNumberOfTopTextLinesToDelete(0)
.build())
.build();
//先使用目录分段读取方式读取PDF并分段落
Resource resource = new FileSystemResource(filePath);
ParagraphPdfDocumentReader pdfReader = new ParagraphPdfDocumentReader(resource, config);
List documents = pdfReader.get();
System.out.println("Total number of documents1: " + documents.size());
if (documents.isEmpty()) {
//如果沒有获取到目录,在改用分页方式拆分
PagePdfDocumentReader pdfReader2 = new PagePdfDocumentReader(resource);
documents = pdfReader2.get();
System.out.println("Total number of documents2: " + documents.size());
}
return documents;
}
public List loadDOCXDocuments(String filePath) throws IOException {
// 读取DOCX文档
Resource resource = new FileSystemResource(filePath);
TikaDocumentReader docxReader = new TikaDocumentReader(resource);
List documents = docxReader.get();
System.out.println("Total number of documents: " + documents.size());
return documents;
}
2. rag问答接口实现类
package com.sikaryofficial.ai.service;
/**
* @author : wuweihong
* @desc : TODO 请填写你的功能描述
* @date : 2025-11-04
*/
import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.document.Document;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class RagService {
private final ChatClient chatClient;
private final VectorStore vectorStore;
@Value("classpath:/prompts/rag-prompt-template.st")
private Resource ragPromptTemplate;
public String processQuery(String query) {
// 1. 检索相关文档
List similarDocuments = vectorStore.similaritySearch(query);
System.out.println("检索到相关文档:" + similarDocuments.size());
// 2. 构建上下文
String context = similarDocuments.stream()
.map(Document::getFormattedContent)
.collect(Collectors.joining("nn"));
// 3. 构建提示词
PromptTemplate promptTemplate = new PromptTemplate(ragPromptTemplate);
Prompt prompt = promptTemplate.create(Map.of(
"current_date", new Date().toLocaleString(),
"input", query,
"context", context
));
// 4. 调用LLM生成回答
return chatClient.prompt(prompt).call().content();
}
}
5. 效果自测
- 导入接口测试
- 智能问答接口测试