从零搭建一个本地文档智能问答系统:Spring AI + PGvector + DeepSeek 实战

前言

不知道你有没有这样的困扰:电脑里的文档越来越多,想找某段话却只能靠“搜索文件名 + 肉眼扫描”。有没有办法像 ChatGPT 一样,直接问“帮我找找上个月项目总结里关于预算的部分”?

这篇文章将带你从零搭建一个本地文档智能问答系统。你只需要上传 PDF,就可以像跟人聊天一样提问,AI 会基于文档内容给你准确的回答。

最终效果

curl -X POST -F "file=@D:/年终总结.pdf" http://localhost:8080/rag/upload
curl "http://localhost:8080/rag/ask?question=明年的主要目标是什么"
# 输出:根据年终总结文档,明年的主要目标是...

技术选型

组件 选型 理由
后端框架 Spring Boot 3.5 + Spring AI 1.0 Java 开发者友好,约定大于配置
向量数据库 PGvector 基于 PostgreSQL,一条 Docker 命令搞定,无兼容性问题
大模型 DeepSeek 国内可用,价格便宜,中文效果好
Embedding ONNX 本地模型 免费、离线、数据隐私安全

踩坑经历:为什么不用 Chroma?

一开始我选择了 Chroma,但遇到了各种问题:

  • 版本不兼容(v1/v2 API 混乱)
  • Windows 下编译失败
  • 与 Spring AI 集成需要大量手动配置

最终切换到 PGvector,一条 Docker 命令 + 几行配置就搞定了。结论:如果是 Java 开发者,选 PGvector 比 Chroma 省心太多。

第一步:启动 PGvector

PGvector 是 PostgreSQL 的向量扩展,用 Docker 一键启动:

docker run -d \
  --name postgres-pgvector \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_DB=vectordb \
  -e POSTGRES_HOST_AUTH_METHOD=trust \
  -p 5432:5432 \
  pgvector/pgvector:pg16

启动后验证:

docker exec -it postgres-pgvector psql -U postgres -d vectordb -c "SELECT 1"

第二步:创建 Spring Boot 项目

访问 start.spring.io,选择:

  • Spring Boot 3.5.14
  • Java 17
  • 依赖:Spring Web

pom.xml 核心依赖

<!-- Spring AI 版本 -->
<properties>
    <spring-ai.version>1.0.0-M6</spring-ai.version>
</properties>

<!-- 仓库配置 -->
<repositories>
    <repository>
        <id>spring-milestones</id>
        <url>https://repo.spring.io/milestone</url>
    </repository>
</repositories>

<!-- 核心依赖 -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        <version>${spring-ai.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
        <version>${spring-ai.version}</version>
    </dependency>
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>42.7.3</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-pdf-document-reader</artifactId>
        <version>${spring-ai.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-transformers-spring-boot-starter</artifactId>
        <version>${spring-ai.version}</version>
    </dependency>
</dependencies>

第三步:配置文件

application.yml

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/vectordb
    username: postgres
    password: postgres
  ai:
    openai:
      api-key: ${DEEPSEEK_API_KEY}
      base-url: https://api.deepseek.com
      chat:
        options:
          model: deepseek-v4-flash
          temperature: 0.7
    vectorstore:
      pgvector:
        index-type: HNSW
        distance-type: COSINE_DISTANCE
        dimensions: 384
        initialize-schema: true

第四步:核心代码

DocumentService.java

@Service
public class DocumentService {
    private final VectorStore vectorStore;
    private final TokenTextSplitter textSplitter;

    public DocumentService(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
        this.textSplitter = TokenTextSplitter.builder()
                .withChunkSize(500)
                .withMinChunkSizeChars(50)
                .build();
    }

    public String uploadAndIndexPdf(MultipartFile file) throws IOException {
        Path tempFile = Files.createTempFile("upload_", ".pdf");
        file.transferTo(tempFile.toFile());
        
        try {
            PagePdfDocumentReader reader = new PagePdfDocumentReader(tempFile.toString());
            List<Document> chunks = textSplitter.apply(reader.get());
            vectorStore.add(chunks);
            return String.format("成功索引 %d 个片段", chunks.size());
        } finally {
            Files.deleteIfExists(tempFile);
        }
    }

    public List<Document> search(String query, int topK) {
        SearchRequest request = SearchRequest.builder()
                .query(query)
                .topK(topK)
                .build();
        return vectorStore.similaritySearch(request);
    }
}

RagController.java

@RestController
@RequestMapping("/rag")
public class RagController {
    private final ChatClient chatClient;
    private final DocumentService documentService;

    public RagController(ChatClient.Builder builder, DocumentService documentService) {
        this.chatClient = builder.build();
        this.documentService = documentService;
    }

    @PostMapping("/upload")
    public Map<String, Object> upload(@RequestParam("file") MultipartFile file) {
        try {
            return Map.of("success", true, "message", documentService.uploadAndIndexPdf(file));
        } catch (Exception e) {
            return Map.of("success", false, "message", e.getMessage());
        }
    }

    @GetMapping("/ask")
    public Map<String, Object> ask(@RequestParam String question) {
        List<Document> docs = documentService.search(question, 3);
        if (docs.isEmpty()) {
            return Map.of("answer", "未找到相关内容");
        }
        
        String context = docs.stream().map(Document::getText).collect(Collectors.joining("\n\n"));
        String prompt = String.format("基于以下内容回答问题:\n\n%s\n\n问题:%s", context, question);
        
        return Map.of("answer", chatClient.prompt().user(prompt).call().content());
    }
}

第五步:测试

# 上传文档
curl -X POST -F "file=@D:/test.pdf" http://localhost:8080/rag/upload

# 提问
curl "http://localhost:8080/rag/ask?question=这个文档主要讲了什么"

常见问题

问题 解决方案
expected 1536 dimensions, not 384 修改 dimensions: 384,删除旧表重建
password authentication failed 添加 POSTGRES_HOST_AUTH_METHOD=trust 环境变量
依赖下载失败 配置 Spring Milestones 仓库

总结

通过本文,你完成了:

  1. ✅ 一个完整的 RAG 智能问答系统
  2. ✅ 从 0 到 1 的实战经验

下一步优化方向

  • 支持更多文件类型(Word、Excel、TXT)
  • 添加文件监控,自动索引新文档
  • 实现流式输出,优化用户体验

项目源码:[https://gitee.com/wang-qingmin/rag-ai-assistant.git]

欢迎 Star ⭐️ 和 Fork!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐