本地验证 RAG 链路时,SimpleVectorStore很顺手。
几段文本切一下,向量化,放进内存,接口一调就能看到相似内容。
这一步能先确认三件事:
EmbeddingModel 能不能生成向量
VectorStore 能不能检索相似内容
ChatClient 能不能把检索资料交给模型回答
问题通常出在下一步:排障手册、产品说明、制度文档,不可能一直躺在应用内存里。它们会更新、会下线、会有版本,权限也不一样。
你会碰到这些问题:
应用重启后,向量数据怎么保留?
服务部署多台机器,怎么共用一份知识库?
文档更新后,旧 chunk 怎么删?
不同角色能看的资料不同,检索前怎么过滤?
PgVector 适合放在这一层:向量数据存进 PostgreSQL,持久化、共享、metadata 过滤都有地方落。
这条链路会换成这样:
text
Ollama bge-m3:生成 embedding
PgVector:存储和检索向量
DeepSeek deepseek-v4-flash:根据检索资料生成回答
接口保留三个就够:
text
/rag/ingest:读取 txt 并写入向量库
/rag/search:检索相似资料
/rag/ask:检索后交给模型回答
一、换的是存储,不是整条 RAG 链路
Spring AI 里,RAG 相关代码通常会碰到三类对象:
text
EmbeddingModel:把文本转成向量
VectorStore:保存向量,并做相似度检索
ChatClient / ChatModel:根据问题和资料生成回答
SimpleVectorStore和PgVector都是在VectorStore这一层。
所以换 PgVector,不是把整条 RAG 链路推倒重来。
EmbeddingModel还是负责生成向量。
ChatClient还是负责把问题和检索资料交给模型生成回答。
主要变化,是VectorStore的具体实现从内存存储换成 PostgreSQL。
业务代码还是面向VectorStore写。
二、先准备依赖和环境
pom.xml里需要这几个依赖:
xml
<groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-deepseek</artifactId><groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-ollama</artifactId><groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-vector-store-pgvector</artifactId><groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope>本地先准备 PostgreSQL 和 embedding 模型,这里用 Ollama 跑bge-m3。
如果你想省事,可以直接用带 pgvector 的镜像:
bash
docker run -it --rm --name postgres \
-p 5432:5432 \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
pgvector/pgvector:pg16
普通 PostgreSQL 也可以用,但要先安装相关扩展。Spring AI 的 PgVectorStore 初始化表结构时会用到vector、hstore、uuid-ossp这几个扩展。
如果数据库缺少 pgvector 扩展,应用启动时就可能报类似这样的错:
text
could not open extension control file … vector.control
再启动 embedding 模型:
bash
ollama pull bge-m3
ollama serve
bge-m3是 embedding 模型,不是聊天模型。这里用它做本地向量化,/rag/ingest和/rag/search不依赖远程 embedding 服务。
如果要测试/rag/ask,再配置 DeepSeek Key:
bash
export DEEPSEEK_API_KEY=你的 DeepSeek API Key
三、配置 PgVector
application.yaml这样写:
yaml
spring:
application:
name: springai-deepseek-demodatasource:
url: ${POSTGRES\_URL:jdbc:postgresql://localhost:5432/postgres} username: ${POSTGRES\_USER:postgres} password: ${POSTGRES\_PASSWORD:postgres}ai:
model: chat: deepseek embedding: ollama deepseek: api-key: ${DEEPSEEK\_API\_KEY:dummy-key} chat: model: deepseek-v4-flash ollama: base-url: http://localhost:11434 embedding: model: bge-m3 vectorstore: pgvector: initialize-schema: true distance-type: COSINE\_DISTANCE dimensions: 1024 max-document-batch-size: 10000initialize-schema: true:本地开发时可以打开。应用启动后,Spring AI 会尝试在 PostgreSQL 里创建 PgVector 需要的扩展、schema、默认表和索引,默认表名是vector_store,所以示例代码里没有单独写建表 SQL。前提是当前数据库用户有创建扩展和建表的权限。
生产环境不要依赖应用启动时自动建表。表结构和扩展安装,最好交给团队已有的数据库变更流程管理。
dimensions: 1024:要和 embedding 模型输出维度一致。这里用的是bge-m3,向量维度是 1024。
distance-type: COSINE_DISTANCE:表示用余弦距离做相似度检索,这里先用它。
四、从 txt 文档读取到向量库
知识内容不要写死在 Java 代码里。先准备一个 txt 文档:
text
src/main/resources/rag/incident-runbook.txt
txt 里放几条排障记录。这里的logicalId是给人看的逻辑编号,真正写入 PgVector 的id使用稳定 UUID:
text
logicalId: INCIDENT-001
system: incident
status: published
category: payment
支付接口偶发 500,先拿 traceId 查应用日志,不要一上来就让用户重新支付。
日志里如果是 gateway timeout、read timed out 这一类超时,先看支付网关耗时和重试次数,再看订单状态有没有回写成功。
最近半小时有过发布的话,把支付签名、回调地址、商户配置这几项一起过一遍,支付问题经常出在配置变更后。
确认上一笔支付没有生成流水之前,不要引导用户重复下单。
5 分钟内同类错误超过 20 次,带上 traceId、订单号、支付流水号升级给值班负责人。
logicalId: INCIDENT-002
system: incident
status: published
category: database
连接池打满时,先看 active、idle、waiting 三个指标,再翻连接池超时日志。
active 长时间贴着 max pool size,通常不是简单把池子调大就完事。先查慢 SQL、长事务,还有连接有没有正常释放。
慢 SQL 先看执行计划和扫描行数,尤其是最近改过查询条件或新增排序的接口。
临时扩容连接池可以救急,但要盯住数据库 CPU、IO 和锁等待,别把压力直接推到数据库上。
看到 lock wait 或 deadlock,先找持锁 SQL。该回滚发布还是该降级功能,等锁源头确认后再定。
logicalId: INCIDENT-003
system: incident
status: published
category: timeout
接口超时不要先猜代码慢。先看调用链,把耗时拆到 Controller、Service、数据库和外部接口几个段上。
耗时集中在下游服务时,看下游错误率、P95/P99 和最近发布记录;只看平均耗时很容易漏掉长尾。
耗时卡在线程池排队时,看队列长度、拒绝次数和单个任务执行时间,必要时把慢请求样本捞出来单独看。
只有少量用户偶发超时,重点看时间段、用户范围、请求参数和 traceId,很多时候是某类数据把路径打慢了。
logicalId: INCIDENT-004
system: incident
status: internal
category: permission
应急预案只对值班同学开放,普通研发默认只能查看通用排查手册。
降级开关、限流阈值、支付通道切换、批量补偿任务,都要值班负责人确认后再操作。
没有对应权限的用户,只返回通用排查建议,不暴露内部开关名称、后台地址和操作口令。
高风险操作要记录操作人、确认人、影响范围和回滚方式,方便事后复盘。
这个文件只约定两件事:
text
用 — 分隔不同文档
每段前面放 metadata,空一行后面放正文
正文放在 txt 里,Java 代码只负责读取和入库。以后内容变了,改 txt 就行。
metadata 可以理解成每段资料的标签。比如status: published表示可以被检索,status: internal表示内部资料。
Controller 里注入这个文件:
java
private final Resource runbook;
public RagController(VectorStore vectorStore, ChatClient.Builder builder,
@Value("classpath:rag/incident-runbook.txt") Resource runbook) { this.vectorStore = vectorStore; this.chatClient = builder.build(); this.runbook = runbook;}
/rag/ingest负责读取 txt,并写入向量库:
java
@PostMapping(“/ingest”)
public IngestResponse ingest() throws IOException {
List<Document> documents = loadRunbookDocuments(); vectorStore.add(documents); return new IngestResponse(documents.size(), documents.stream() .map(DocumentItem::from) .toList());}
真正写入 PgVector 的动作发生在vectorStore.add(documents)。它会先调用 embedding 模型生成向量,再把文本、metadata 和 embedding 写入 PostgreSQL 的vector_store表。
读取时按---切块,再逐块转成Document:
java
private List loadRunbookDocuments() throws IOException {
String content = runbook.getContentAsString(StandardCharsets.UTF\_8); return Arrays.stream(content.split("(?m)^---\\s\*$")) .map(String::trim) .filter(block -> !block.isBlank()) .map(this::toDocument) .toList();}
转换成Document时,把logicalId保留下来,同时生成 PgVector 更适合使用的 UUID:
java
import java.nio.charset.StandardCharsets;
import java.util.UUID;
private Document toDocument(String block) {
String[] parts = block.split("\\R\\R", 2); if (parts.length != 2) { throw new IllegalArgumentException("Runbook block must contain metadata and body separated by a blank line."); } Map<String, Object> metadata = parseMetadata(parts[0]); String logicalId = metadata.remove("logicalId").toString(); metadata.put("logicalId", logicalId); String text = parts[1].trim(); return new Document(UUID.nameUUIDFromBytes(logicalId.getBytes(StandardCharsets.UTF\_8)).toString(), text, metadata);}
这样人看文档时看INCIDENT-001,数据库里用稳定 UUID。
检索时仍然走VectorStore:
java
private List retrieve(String question) {
return vectorStore.similaritySearch(SearchRequest.builder() .query(question) .topK(3) .filterExpression("system == 'incident' && status == 'published'") .build());}
注意这一行:
java
.filterExpression(“system == ‘incident’ && status == ‘published’”)
INCIDENT-004的状态是internal,不会进入检索结果。
权限、状态、租户、版本这些东西,要在检索层先过滤。
五、实际跑一下
启动应用:
bash
./mvnw spring-boot:run
先读取 txt,并写入向量库:
bash
curl -X POST “http://localhost:8080/rag/ingest”
返回里能看到写入了 4 条文档。
再检索支付错误:
bash
curl --get “http://localhost:8080/rag/search” \
–data-urlencode “question=分析支付错误”
中文问题建议用--data-urlencode,别直接拼到 URL 后面。
返回里会优先命中INCIDENT-001。节选如下:
json
{
“question”: “分析支付错误”,
“matches”: [
{ "id": "9ee4017f-b2aa-35c3-a794-33c7fa6ae6a6", "text": "支付接口偶发 500,先拿 traceId 查应用日志,不要一上来就让用户重新支付。", "score": 0.6748, "metadata": { "system": "incident", "category": "payment", "logicalId": "INCIDENT-001", "status": "published" } }]
}
换一个问题,再检索接口超时:
bash
curl --get “http://localhost:8080/rag/search” \
–data-urlencode “question=接口超时应该先查什么”
这次会优先命中INCIDENT-003:
text
接口超时排查时,先看调用链耗时,再确认下游服务、线程池队列和连接池状态。
最后测试回答接口:
bash
curl --get “http://localhost:8080/rag/ask” \
–data-urlencode “question=分析支付错误”
这个接口会先从 PgVector 检索资料,再交给 DeepSeekdeepseek-v4-flash生成回答。
没有真实DEEPSEEK_API_KEY时,/rag/ingest和/rag/search仍然能验证向量写入和检索;/rag/ask需要真实模型 Key。
六、进项目以后还差什么
把SimpleVectorStore换成 PgVector,只是把向量存储这层换稳。再往项目里放,还要补几块。
第一件事是 metadata。
RAG 不能只看语义相似。比如这句:
接口超时应该先查什么?
普通研发可以看通用排查手册。
值班同学可能还能看应急预案。
平台负责人可能还能看更完整的链路配置。
如果这些文档都放在一个向量库里,检索前就要过滤。只靠 Prompt 写一句不稳:
text
请不要回答用户无权限查看的内容。
不如在文档入库时就把 metadata 留好:
text
tenantId
department
docType
visibility
version
source
updatedAt
status
metadata 写得太粗,后面会很别扭:查不准、权限控不住、也不好回溯。
其他几件事也要提前想好:
- 文档导入独立出来,本地可以手动调用
/rag/ingest,项目里更适合做成管理后台、定时任务,或者知识库同步流程。 - 文档更新要有版本策略,别让新旧 chunk 混在一起。
- 检索日志要留,至少保留用户问题、topK、命中的文档 id、metadata 和最终回答。
- 生产建表走正式变更流程,别依赖应用启动时临时创建。
写在最后
SimpleVectorStore适合把 RAG 主链路跑通,PgVector 适合把向量数据长期放进项目里。
text
用 bge-m3 生成 embedding
读取 txt,转成 Document,再写入 PgVector
用 metadata filter 检索 published 文档
把检索结果交给 deepseek-v4-flash 回答
先把这条链路跑稳,再去调 chunk、rerank、混合检索和评估集。
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋
📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~