Spring Boot接入企业RAG:文档切分、向量检索、权限过滤和答案溯源

摘要 本文给出一条可落地的企业RAG后端链路:文档入库时保留租户、知识域、来源和版本元数据,查询时先做身份到检索范围的转换,再执行向量检索,最后把命中文档与问题交给模型,并把引用一并返回。

示例基于Spring AI 2.0.0接口思路;官方文档显示Spring AI 2.0.x支持Spring Boot 4.0.x和4.1.x。向量库、模型和过滤语法应按实际实现调整。

  1. 运行环境和目标

示例环境:

图1 Spring Boot企业RAG链路(概念示意,依据正文结构整理)

ALT建议:Spring Boot企业RAG链路,主要包含:文档采集与解析、切分、权限元数据、Embedding与向量存储、检索与权限过滤、上下文组装与生成、引用、评测与审计。

text
Java 21
Spring Boot 4.0.x
Spring AI BOM 2.0.0
一个ChatModel实现
一个支持元数据过滤的VectorStore实现

pom.xml先锁定BOM,不要为每个Spring AI模块分别手写版本。

xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>2.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-rag</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-vector-store</artifactId>
</dependency>
<!-- 再按模型与向量库选择对应starter -->
</dependencies>

官方Getting Started页面是版本兼容性的最终依据。若项目仍在Spring Boot 3.x,不要直接把Spring AI 2.0.0依赖塞入原工程,应选择兼容版本或单独部署AI服务。

  1. 先画清楚企业RAG链路

mermaid

flowchart LR

F"文件或业务知识" --> P"解析与切分"

P --> E"Embedding"

E --> V"VectorStore"

Q"用户问题" --> A"身份与数据范围"

A --> R"带元数据过滤的检索"

V --> R

R --> C"上下文与引用组装"

C --> L"ChatModel"

L --> O"答案、引用、Trace ID"

这条链路有两个不能省的旁路:

图2 从文档入库到带引用回答(概念示意,依据正文结构整理)

ALT建议:从文档入库到带引用回答,主要包含:解析文档、切分并继承ACL、写入向量库、按用户权限检索、生成并返回引用。

文档删除或权限变化后,要触发索引更新。

每次查询要记录用户范围、检索结果、模型版本和最终引用,敏感正文按策略脱敏。

  1. 文档如何切分并保留权限元数据?

切分的目标不是固定字符数,而是在语义完整、召回粒度和模型上下文成本之间平衡。制度文档可按标题层级加段落切分,表格需要保留表头,扫描PDF要把OCR置信度写入元数据。

定义入库对象:

java

public record KnowledgeChunk(

String chunkId,

String tenantId,

String knowledgeBaseId,

String aclScope,

String sourceUri,

String sourceTitle,

String sourceVersion,

int page,

String text

) {}

写入Document时保留可过滤字段和可溯源字段:

java

Document toDocument(KnowledgeChunk c) {

Map<String, Object> metadata = new HashMap<>();

metadata.put("tenantId", c.tenantId());

metadata.put("knowledgeBaseId", c.knowledgeBaseId());

metadata.put("aclScope", c.aclScope());

metadata.put("sourceUri", c.sourceUri());

metadata.put("sourceTitle", c.sourceTitle());

metadata.put("sourceVersion", c.sourceVersion());

metadata.put("page", c.page());

return new Document(c.chunkId(), c.text(), metadata);

}

aclScope是示例中的预计算检索范围,例如finance_read。真实企业常有用户、组织、角色、项目和密级的组合权限。不要直接把超长用户ID列表塞进每个向量元数据;可以先由权限服务把当前用户解析为有限知识域,再构造向量库支持的过滤表达式。

入库过程应幂等:

text

documentId + sourceVersion + chunkNo -> chunkId

同一版本重复导入不会生成重复向量;新版本成功后再切换活动索引,失败时保留旧版本。

  1. 如何在检索阶段做权限过滤?

权限过滤必须发生在检索阶段。先召回全部文档、再在生成前删除无权限片段,会让敏感内容进入日志、缓存或模型上下文。

下面用Spring AI的SearchRequest表达查询意图,字段与过滤表达式需要按所选VectorStore核对:

java

public record RetrievalScope(

String tenantId,

String knowledgeBaseId,

String aclScope

) {}

List<Document> retrieve(

VectorStore vectorStore,

String question,

RetrievalScope scope

) {

String filter = """

tenantId == '%s' &&

knowledgeBaseId == '%s' &&

aclScope == '%s'

""".formatted(

safe(scope.tenantId()),

safe(scope.knowledgeBaseId()),

safe(scope.aclScope())

);

SearchRequest request = SearchRequest.builder()

.query(question)

.topK(8)

.similarityThreshold(0.55)

.filterExpression(filter)

.build();

return vectorStore.similaritySearch(request);

}

safe()不能只是替换引号,最好不要拼字符串,而是使用实现提供的类型安全过滤API。示例阈值0.55也不是生产推荐值,必须通过自己的问答集调优。

对多租户向量库,隔离层级要根据风险选择。Milvus 2.6.x文档列出Database、Collection、Partition和Partition Key四类策略,它们在隔离、规模和灵活性上不同。高敏租户不应只因规模大就默认选择逻辑隔离最弱的方案。

  1. 如何生成答案并返回可点击引用?

为了完整记录命中文档,工程上可以显式检索,再将上下文交给ChatClient。这样引用不依赖模型"记得输出"。

java

public record Citation(

String title,

String uri,

String version,

Integer page

) {}

public record RagAnswer(

String answer,

List<Citation> citations,

String traceId

) {}

核心服务伪代码:

java

RagAnswer answer(String question, UserContext user) {

RetrievalScope scope = permissionService.resolve(user);

List<Document> docs = retrieve(vectorStore, question, scope);

if (docs.isEmpty()) {

return new RagAnswer(

"在当前权限和知识范围内没有找到足够证据。",

List.of(),

traceId()

);

}

String context = docs.stream()

.map(d -> "%s p.%s\n%s".formatted(

d.getMetadata().get("sourceTitle"),

d.getMetadata().get("page"),

d.getText()

))

.collect(Collectors.joining("\n\n"));

String result = chatClient.prompt()

.system("""

只能依据给定材料回答。

证据不足时明确说明不知道。

不得改变材料中的权限、金额和时间条件。

""")

.user("问题:%s\n\n材料:\n%s".formatted(question, context))

.call()

.content();

List<Citation> citations = docs.stream()

.map(this::toCitation)

.distinct()

.toList();

return new RagAnswer(result, citations, traceId());

}

生产实现还要限制上下文总长度、去重同源片段、避免把HTML或恶意指令直接拼进系统提示,并对引用做可访问性检查。答案引用应来自检索结果,而不是让模型凭空生成URL。

  1. 如何评测检索、答案和权限?

RAG不能只看最终答案。RAGAS论文与当前Ragas文档把评估拆为Context Precision、Context Recall、Faithfulness和Answer Relevance等维度;LangSmith教程也把正确性、相关性、groundedness和检索相关性分开。

图3 企业RAG评测指标框架,不包含虚构的实测数据。

ALT建议:企业RAG三层评测矩阵,主要包含:检索质量,Recall@K、上下文精度、回答质量,正确性、忠实性、拒答、权限安全,越权拦截、引用可见性、运行质量,时延、失败率、更新延迟。

最小评测集建议包含:

|------|-----------|----------------|
| 类别 | 样本 | 预期 |
| 标准问答 | 权威文档能直接回答 | 找到正确来源并给出准确答案 |
| 多版本 | 新旧制度都存在 | 只引用当前有效版本 |
| 无答案 | 知识库没有证据 | 拒答,不编造 |
| 越权 | 用户问无权文档 | 检索结果为空或仅返回有权内容 |
| 注入 | 文档中包含恶意指令 | 不改变系统规则、不调用工具 |
| 更新删除 | 原文撤回后再次查询 | 旧片段不再召回 |

建议同时记录Recall@K、上下文精度、答案正确性、引用正确率、越权召回数、P95时延和单次成本。LLM-as-judge可以辅助,但高风险问题要有人审和确定性断言。

  1. 常见故障与排查

答非所问:先检查切分和检索,再调整Prompt;不要直接换更大模型。

有答案但引用错:检查同源去重、版本字段和引用映射。

不同用户结果相同:检查身份到RetrievalScope的转换和过滤是否由向量库实际执行。

删除后仍能问到:检查异步索引任务、缓存和旧索引切换。

线上偶发错误:把模型、检索、权限和解析版本写入Trace,才能重放。

速众AI低代码开发平台可作为企业应用入口、流程和知识服务集成的一种承载方式,但本文代码是独立工程示例,不代表其产品源码。实际接入时仍要按平台当前接口、部署和权限模型核验。

相关推荐
weixin_BYSJ19871 小时前
springboot校园自习室管理小程序---附源码32142
java·javascript·spring boot·python·django·flask·php
神奇小汤圆1 小时前
别再用 nohup java -jar 了:Spring Boot 生产环境该怎么守护?
后端
JavaGuide1 小时前
GitHub 9.8 万 Star!把整个代码仓库变成知识图谱,这个 AI Coding 工具太适合 Claude Code / Codex 了
前端·后端·ai编程
旺仔学长 哈哈1 小时前
Spring Boot 智能停车场管理系统---附源码+数据库文档
数据库·spring boot·后端·智能停车场
用户8356290780512 小时前
使用 Python 在 PDF 中绘制线条、矩形和自定义图形
后端·python
ClickHouseDB2 小时前
ClickHouse托管Postgres:OLTP+OLAP,新能力解锁最佳数据平台
java·前端·数据库
霸道流氓气质2 小时前
Java中集成Smile 技术教程:从入门到工程实践
java·开发语言
wuqingshun3141592 小时前
MyBatis 中#()和$()的区别是什么?
java
小Ti客栈2 小时前
SpringBoot 事件发布机制:原理与应用
java·spring boot·spring