1. 首页
  2. 后端开发
  3. post-spring-ai-pgvector

Spring AI + pgvector 实战:把企业文档变成 AI 的记忆

一、为什么企业需要 AI 知识库?——从"年假怎么算"说起

“年假怎么算?”

“报销流程是什么?”

“项目立项需要谁审批?”

如果你的团队成员每天在群里问这些问题,你已经迫切需要一套企业知识库了。Workforce Hub 作为一个面向中小企业的协同办公平台,知识库模块是我们的核心差异化功能——员工在聊天框里 @ 一下 AI,它就能从公司制度文档里找到答案。

说实话,刚开始做这个功能的时候我觉得挺简单的:不就是把文档丢给大模型嘛。结果踩了整整两周的坑才发现,从文档上传到真正"问对问题拿对答案",中间隔着好几座山。

二、技术选型——为什么不用 Elasticsearch ?

RAG(检索增强生成)的核心链路很简单:

文档 → 分段 → Embedding → 向量存储 → 相似度检索 → LLM 生成回答

但每个环节都有不同的技术方案。为了做出最合适的选择,我对比了几种方案:

方案 优点 缺点 适用场景
Spring AI + pgvector 零额外依赖,数据不离开 PG 大规模检索性能不如 ES 中小企业,文档量 < 10万
Spring AI + Elasticsearch 高性能全文+向量混合搜索 需要额外部署 ES 集群 海量文档、高并发
LangChain + Pinecone 生态成熟,社区活跃 引入 Python 技术栈,增加运维成本 Python 项目
纯 Spring AI + OpenAI 最简单 每次都传全文,Token 爆炸 Demo 阶段

我们选 Spring AI + pgvector 的原因是:

1. 零额外部署成本。 Workforce Hub 本身就用了 PostgreSQL,pgvector 是 PG 的一个扩展,一行 SQL 就能开启。中小企业没有专职运维,多一个 ES 集群就是多一个故障点。

2. Spring AI 和 Spring Boot 3.x 无缝集成。 不用切 Python,不用学新框架,直接在已有的 Service 层注入 AI 组件就能用。

3. 数据安全。 企业文档不出数据库,不用把内部制度上传到第三方向量服务。

说实话:其实选 pgvector 还有一个很现实的原因——我们服务器只有 2 核 4G,跑 ES 内存直接爆。但事后证明这个"穷人的选择"反而是对的,中小企业根本不需要 ES 那么重的东西。

三、RAG 实现——四个核心环节的踩坑记录

3.1 文档分段:最容易翻车的环节 ⭐

我的第一个版本是直接按固定长度切分(每段 512 字符),结果测试的时候翻车了:

// 我一开始这么写——简单粗暴按字符数切
List<String> chunks = new ArrayList<>();
for (int i = 0; i < text.length(); i += 512) {
    chunks.add(text.substring(i, Math.min(i + 512, text.length())));
}

测试文档是一份《员工考勤制度》,里面有一条规则是"年假天数 = 基础天数 + 司龄补贴 - 已休天数"。但由于 512 字符刚好把这条规则切成了两段——

段1:年假天数 = 基础天数 + 司龄补贴
段2:- 已休天数。计算公式详见附表。

AI 查的时候只检索到了段 1,回答"年假天数就是基础天数加司龄补贴",漏掉了"减去已休天数"。这要是在生产环境,HR 看了得气死。

正确做法:按语义边界分段,而不是按字符数。Spring AI 提供了 TokenTextSplitterDocumentReader 组合:

// 改进后的分段策略
var splitter = new TokenTextSplitter(
    512,   // 目标长度
    128,   // 重叠长度——关键!防止语义断裂
    5,     // 最小长度
    1000,  // 最大长度
    true   // 按句子边界切分
);
List<Document> chunks = splitter.split(document);

我踩的坑:重叠长度设太小(之前设了 20),结果还是会出现关键信息被切开的情况。后来把重叠调到 128 才稳。

3.2 Embedding:中文模型的坑

vector 的维度直接影响检索精度。我们用 BAAI/bge-m3 模型(1024 维),通过 SiliconFlow API 调用:

@Bean
public EmbeddingModel embeddingModel() {
    return new SiliconFlowEmbeddingModel(
        "BAAI/bge-m3",  // 支持中英文,1024 维
        apiKey
    );
}

一个小坑:pgvector 建表时向量维度需要和 embedding 模型输出维度一致。我一开始从 1536 维(OpenAI ada-002)切换到 1024 维(BGE-M3)时忘了改表结构,插入一直报错:expected 1536 dimensions, got 1024。排查了半小时才发现是 ALTER TABLE 没执行。

-- pgvector 建表
CREATE TABLE knowledge_chunks (
    id SERIAL PRIMARY KEY,
    doc_id VARCHAR(64),
    chunk_text TEXT,
    chunk_index INT,
    embedding vector(1024),  -- 必须和模型输出维度一致
    metadata JSONB,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- 创建 HNSW 索引加速检索
CREATE INDEX ON knowledge_chunks
USING hnsw (embedding vector_cosine_ops);

3.3 相似度检索:余弦距离 vs 欧氏距离

向量检索默认用余弦相似度(cosine similarity),这在高维稀疏向量中最有效。Spring AI 的 pgvector store 默认就用的 cosine。

实际查询代码

@Service
public class KnowledgeService {

    @Autowired
    private VectorStore vectorStore;

    @Autowired
    private ChatClient chatClient;

    public String ask(String question) {
        // Step 1: 向量检索 TOP 5 相关文档段
        List<Document> relevantDocs = vectorStore.similaritySearch(
            SearchRequest.query(question)
                .withTopK(5)           // 取 Top 5
                .withSimilarityThreshold(0.7)  // 相似度低于 0.7 的不算
        );

        // Step 2: 拼接上下文 + 提示词
        String context = relevantDocs.stream()
            .map(Document::getContent)
            .collect(Collectors.joining("\n\n"));

        String prompt = """
            你是一个企业知识库助手。请根据以下文档内容回答问题。
            如果文档中没有相关信息,请明确说"暂无相关制度记录"。

            文档内容:
            %s

            问题:%s
            """.formatted(context, question);

        // Step 3: 调用 LLM 生成回答
        return chatClient.prompt()
            .user(prompt)
            .call()
            .content();
    }
}

血泪教训similarityThreshold(0.7) 这个参数非常重要。一开始我没设阈值,结果用户问"今天天气怎么样",AI 强行从不相关的考勤文档里拼凑了一个回答:“根据考勤制度,天气不影响出勤”。设了阈值后,不相关的问题会直接返回"暂无相关制度记录"——这才是企业场景下该有的行为。

3.4 提示词工程:让 AI 说人话

有了上下文和问题还不够,提示词直接决定了回答质量。我调试了十几个版本,最终确定的有效提示词套路:

系统指令 + 角色设定:
"你是一个企业知识库助手,请严格基于文档内容回答"

约束条件:
"如果文档中没有相关信息,请明确说'暂无相关制度记录'"
"回答要简洁,不超过 200 字"
"涉及具体数字(天数、金额)时,必须引用原文"

格式要求:
"使用 Markdown 格式,关键信息加粗"

小技巧:给提示词里加一个"负面示例"比正面示例更有效。我告诉模型"不要像这样回答——‘根据相关规定,您需要…’"——这种官腔回答比说错信息还让人反感。

四、架构设计——知识库在 Workforce Hub 中的位置

整个知识库模块在系统架构中扮演"AI 大脑"的角色:

📌 图片位置标记:请在此处插入 RAG 架构图(wh-rag-diagram.png
图片路径:上传至媒体库后替换此处
内容:左侧"管理员上传文档" → “文档分段” → “Embedding 生成” → “向量存储(pgvector)”;右侧"员工提问" → “相似度检索” → “LLM 生成” → “返回回答”

核心交互流程:

  1. 文档入库:管理员上传 PDF/Word → Spring AI DocumentReader 解析 → TokenTextSplitter 分段 → EmbeddingModel 生成向量 → 存入 pgvector
  2. 问答检索:员工 @AI 提问 → 问题转 embedding → pgvector 相似度检索 Top K → 拼接上下文 → LLM 生成回答
  3. 反馈闭环:员工对回答点 👍/👎 → 记录到数据库 → 后续用于优化检索参数

五、效果与改进

部署后的实际效果:

✅ 做得好的

  • 制度类问题准确率 90%+(年假、报销、考勤规则等有明确文档的问题)
  • 响应速度 < 2 秒(512MB 文档库,HNSW 索引)
  • 零外部依赖,运维成本极低

❌ 还需要改进的

  • 纯经验类的知识捕捉不到(比如"XX 客户喜欢什么样的提案风格")
  • 多轮对话支持不够(问完年假,接着问"那我能休几天"时,AI 不知道"我"是谁)

下一步计划

  1. 引入 rerank 模型(BGE-reranker)提升检索精度
  2. 支持图片和多模态文档
  3. 自动从聊天记录中提取知识条目

六、总结

核心收获

  1. 分段策略比模型选择更重要。 好的分段(按语义、加重叠)对最终效果的影响远大于换一个大模型
  2. pgvector 对中小企业完全够用。 不需要 ES,不需要 Pinecone,PG 一个扩展就能搞定
  3. 提示词是最后一道防线。 阈值过滤 + 强制来源引用 + 明确的"不知道"话术,这三条比任何技术优化都管用

给初学者的建议:不要一上来就搞复杂的 Rag Pipeline。先用 Spring AI + pgvector 跑通最简单的"上传→检索→回答"链路,把分段策略和提示词调好再往上加东西。

个人感悟:做企业软件和做个人项目最大的区别是——个人项目你追求技术酷炫,企业软件你追求稳定可靠。pgvector 不酷,但它不崩。

环境信息:Java 17 + Spring Boot 3.x + Spring AI 1.0 + pgvector 0.7 + BAAI/bge-m3 + SiliconFlow API
发布日期:2026年5月21日


让我们忠于理想,让我们面对显示

SailTrack

entp 辩论家

站长

不具版权性
不具时效性

文章内容不具时效性。若文章内容有错误之处,请您批评指正。

目录

欢迎来到SailTrack的站点,为您导航全站动态

34 文章数
11 分类数
3 评论数
28标签数