
很多Java团队在做AI知识库时,第一反应往往是起一个Python项目,用FastAPI加LangChain写一套微服务,再让Java主服务通过RPC或HTTP去调用。但在实际生产环境里,这种架构很快会暴露问题:团队需要维护两套完全不同的语言运行环境、两套日志和监控体系,网络序列化还会白白增加几十毫秒的延迟。
如果你现有的业务系统基于Spring Boot构建,完全可以直接在JVM里把知识库落地。Spring AI的诞生就是为了抹平大模型调用和Java后端工程之间的鸿沟。它把模型交互、向量存储、文档处理抽象成了标准的Spring编程模型。
核心选型:向量数据库该怎么挑?
搭建知识库的第一步是选定向量存储(Vector Store)。很多团队一上来就盲目部署专用的分布式向量数据库,结果业务量还没跑起来,运维成本先翻倍了。
在Java技术栈中,目前主流的几种方案各有优缺点。下面是我们在千万级和百万级真实业务场景下的选型对比:
| 选型方案 | 部署与运维成本 | 百万级向量QPS响应 | 混合检索能力(全文+向量) | 核心优势 | 适用场景 |
|---|---|---|---|---|---|
| PostgreSQL (pgvector) | 极低(直接复用关系库或RDS) | ~30ms – 50ms(HNSW索引) | 优秀(原生支持GIN+Full Text Search) | 事务一致性好,无需额外数据同步,支持元数据复杂关联 | 数据量在500万以内、已有PG体系、重视权限隔离的团队 |
| Milvus | 中到高(需要维护协调节点与存储节点) | ~5ms – 15ms | 良好(新版本增强了分词检索) | 吞吐量极高,原生支持大规模集群横向扩容 | 向量规模超千万、专职数据运维团队、高并发问答场景 |
| Redis (RedisVL) | 中等(内存成本高) | 极快(< 5ms,全内存操作) | 一般(分词支持相对简单) | 极低延迟,天然兼容已有缓存架构 | 热点高频问答、需要毫秒级低延迟的即时会话缓存 |
| Elasticsearch | 中等(已有ES集群则成本极低) | ~20ms – 40ms | 行业顶尖(强大的BM25全文检索能力) | 文本匹配底子厚,从传统搜索向AI搜索迁移平滑 | 企业已有成熟ES集群、对关键词精准匹配要求严苛的场景 |
首选建议:如果你的知识库文档量在几十万到数百万切片之间,无脑选 PostgreSQL + pgvector。这样做能让你省去数据双写一致性校验、跨系统权限同步等一系列复杂的工程问题。当单表向量数据突破千万级且检索耗时明显上升时,再平滑迁移到 Milvus。
实战步骤:手把手搭建RAG链路
整个知识库的底层链路可以分为四步:文档提取(Read) -> 切块分词(Transform) -> 向量化与入库(Write) -> 检索增强生成(Retrieve & Answer)。
第一步:引入依赖与配置环境
以 Spring Boot 3.3.x 和 Spring AI 1.0.0-M4 为例,在 `pom.xml` 中引入核心依赖:
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
</dependency>
</dependencies>
接着在 `application.yml` 中配置数据库连接和模型端点:
spring:
ai:
openai:
base-url: https://api.deepseek.com
api-key: ${AI_API_KEY}
chat:
options:
model: deepseek-chat
temperature: 0.3
vectorstore:
pgvector:
index-type: HNSW
distance-type: COSINE_DISTANCE
dimensions: 1536 # 必须与你使用的 Embedding 模型的维度严格一致
initialize-schema: true
这里有一个高频报错点:`dimensions` 参数必须严格匹配向量模型的输出维度。比如 OpenAI 的 `text-embedding-3-small` 是 1536 维,阿里的 `text-embedding-v3` 或本地的 `bge-large-zh` 是 1024 维。如果这里的参数和模型对不上,PG 在写入向量时会直接抛出 SQL 异常。
第二步:文档切块的避坑细节
很多初学者喜欢直接拿字符长度切分文本(比如每500个字符切一刀),这会导致语义被腰斩。Spring AI 提供了 `TokenTextSplitter`,它按 Token 计数进行切分,更符合大语言模型的上下文处理方式。
先看一个健壮的切块实现:
@Service
public class DocumentService {
@Autowired
private VectorStore vectorStore;
public void importMarkdownFile(Resource resource, String deptId) {
// 1. 读取原始文档
TikaDocumentReader reader = new TikaDocumentReader(resource);
List<Document> documents = reader.get();
// 2. 配置分块器:块大小 600 tokens,重叠 100 tokens
TokenTextSplitter splitter = new TokenTextSplitter(600, 100, 5, 10000, true);
List<Document> splitDocs = splitter.apply(documents);
// 3. 补充业务元数据(租户隔离与权限控制的关键)
for (Document doc : splitDocs) {
doc.getMetadata().put("deptId", deptId);
doc.getMetadata().put("source", resource.getFilename());
}
// 4. 分批次写入向量数据库,防止单次请求超时
int batchSize = 50;
for (int i = 0; i < splitDocs.size(); i += batchSize) {
List<Document> batch = splitDocs.subList(i, Math.min(i + batchSize, splitDocs.size()));
vectorStore.accept(batch);
}
}
}
注意看第 4 步中的分批写入机制。如果一次性把几千个切片塞给 `vectorStore.accept(docs)`,Spring AI 会发起一次性包含几千条文本的 Embedding HTTP 请求。国内大多数模型提供商的单次请求限制通常在 16 到 64 条之间,超过就会直接超时或报 429 错误。单次批量写入控制在 50 条左右,稳定性最高。
第三步:构建智能问答服务
有了向量数据,接下来我们要通过 `ChatClient` 和 Spring AI 的 `QuestionAnswerAdvisor` 来实现真正的检索增强生成。
@RestController
@RequestMapping("/api/chat")
public class KnowledgeChatController {
private final ChatClient chatClient;
public KnowledgeChatController(ChatClient.Builder builder, VectorStore vectorStore) {
// 装配核心问答拦截器
this.chatClient = builder
.defaultAdvisors(
new QuestionAnswerAdvisor(
vectorStore,
SearchRequest.builder()
.topK(4) // 召回最相关的 4 条上下文
.similarityThreshold(0.70) // 相似度阈值过滤,低于 0.70 直接丢弃
.build()
)
)
.build();
}
@GetMapping(value = "/ask", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> ask(@RequestParam String question, @RequestParam String deptId) {
// 动态附加元数据过滤表达式,实现部门级数据隔离
return chatClient.prompt()
.user(question)
.advisors(advisorSpec -> advisorSpec.param(
QuestionAnswerAdvisor.FILTER_EXPRESSION,
"deptId == '" + deptId + "'"
))
.stream()
.content();
}
}
上面的代码里隐藏了两个关键参数:
1.`similarityThreshold(0.70)`(相似度过滤阈值):如果不配置这个参数,默认会召回相似度极低的内容强行拼接进 Prompt,模型就会顺着垃圾内容开始胡说八道。在余弦相似度下,设置在 0.70 到 0.75 是过滤无关噪声的合理区间。
2.`FILTER_EXPRESSION`(元数据过滤):企业内部知识库绝不能让财务数据被研发人员搜出来。Spring AI 支持标准的 SQL 风格过滤语法(如 `deptId == ‘finance’`),它会在向量检索阶段把条件推给数据库执行,确保数据安全性。
生产环境里的三个必踩“深坑”
在本地跑 Demo 往往很顺利,但一上预发或生产环境,往往会遇到以下问题:
1. PDF 解析导致 JVM 堆内存溢出(OOM)
很多团队直接拿 `PagePdfDocumentReader` 解析几十兆的扫描版 PDF。Apache PDFBox 在解析高分辨率图片或复杂版式时,会在内存中展开大量位图,几篇大文档并发上传就能直接把 4G 的堆内存吃满。
应对方案:针对大体积文件,切忌直接在业务 Web 节点同步解析。建议把文件扔到对象存储后,通过消息队列把解析任务交给专用的异步 Worker 节点处理,或者前置采用成熟的版面分析工具(如 MinerU、PaddleOCR)转换为 Markdown 后再入库。
2. “答非所问”:关键词检索与语义检索的脱节
纯向量检索擅长处理概念相关的模糊问题,但对精准编码、专有名词、版本号极不敏感。例如用户问“ERR-80023 错误怎么处理”,向量检索可能会召回一堆通用的“异常排查指南”,却漏掉包含“ERR-80023”错误码的文档。
应对方案:在生产环境中,要开启混合检索(Hybrid Search)。借助 PG 的 `pgvector` 配合全文检索模块,先同时做向量检索与关键字 BM25 检索,然后再将召回的内容做倒排去重。
3. 缺乏问答记忆带来的单轮僵局
知识库不能只回答孤立的问题,用户经常会追问“那第一步里的依赖怎么配”。原生 `QuestionAnswerAdvisor` 默认不带历史记忆。这时候需要配合 `MessageChatMemoryAdvisor` 一起使用,把用户的历史会话维护在 Redis 中,拼接在 Prompt 中一起输入给模型。
总结
在现有的 Java 业务工程中,引入 Spring AI 搭建企业内部知识库,无论在开发效率还是后续运维上都是一条清晰、可控的路径。先用 PostgreSQL + pgvector 跑通最小闭环,把控好切块的 Token 边界与元数据过滤,再加上严格的 相似度阈值 过滤,你就能构建出一个高可用、低噪音的生产级知识库问答服务。
原创文章,作者:七点互动,如若转载,请注明出处:https://ai.qidianhudong.com/aikonw/3948.html