一、向量存储是什么
将一段文字通过 Embedding 模型转换成一串数字(向量),存入向量数据库。搜索时把用户的问题也转成向量,和库里所有向量做相似度计算,返回最相关的结果。
文字 → Embedding模型 → [0.12, 0.87, 0.34, ...] → 存入 Chroma
向量代表的是语义,而不是字符串匹配。"手机"和"iPhone"在向量空间里距离很近,即使没有相同的字也能匹配到。
二、Document 是独立的
每个 Document 在向量化时只看自身内容,不知道其他 Document 的存在,片段和片段之间没有关联。
Document1:"iPhone17很厉害" → 向量A
Document2:"它搭载了A19处理器" → 向量B
Document3:"它卖10000块" → 向量C
三个向量完全独立,搜索时各自和用户问题做相似度计算,互不影响。
后果: Document2 和 Document3 里的"它"失去了指代,语义不完整,搜索精度下降。
正确做法: 把同一个主题的信息合并成一个 Document 存。
java
// ❌ 错误:信息割裂
vectorStore.add(List.of(
new Document("id1", "iPhone17很厉害", Map.of()),
new Document("id2", "它搭载了A19处理器", Map.of()),
new Document("id3", "它卖10000块", Map.of())
));
// ✅ 正确:信息完整
String content = "iPhone17很厉害,它搭载了A19处理器,售价10000块";
vectorStore.add(List.of(new Document("id1", content, Map.of())));
三、搜索原理
用户提问 → 转成向量 → 和库里每个 Document 的向量计算相似度 → 返回 TopK 个最相关的片段 → 交给大模型综合回答。
用户问:"A19处理器的手机多少钱"
↓ embedding
[0.23, 0.91, ...]
↓ 相似度计算
Document1:"iPhone17搭载A19处理器,性能强劲,售价10000元" 相似度 0.92 ✅
Document2:"索尼耳机,9成新,售价500元" 相似度 0.11 ❌
↓
返回 Document1 给大模型
↓
大模型回答:"iPhone17搭载A19处理器,售价10000元"
四、分片的场景和意义
为什么要分片
Embedding 模型有 token 上限(一般 512~8192 token),超出直接报错。即使没超限,内容太长会导致向量语义被稀释,搜索精度下降。
什么时候需要分片
| 内容类型 | 是否需要分片 |
|---|---|
| 商品标题+描述 | ❌ 不需要,直接拼一条存 |
| 长篇商品说明书 | ✅ 需要 |
| 政策/协议文档 | ✅ 需要 |
| 聊天记录 | ✅ 按对话轮次分片 |
Spring AI 分片实现
java
@Bean
public TokenTextSplitter textSplitter() {
return new TokenTextSplitter(
800, // 每片最大 token 数
400, // 相邻片段重叠 token 数
10, // 最小片段长度
5000, // 最大片段长度
true
);
}
// 使用
Document doc = new Document(fullText, Map.of("articleId", articleId));
List<Document> chunks = textSplitter.split(doc);
vectorStore.add(chunks);
五、重叠的真正意义
相邻片段重叠一部分内容,目的是让每个片段自身的语义更完整,避免关键信息被切断在两片之间。
原文:iPhone17搭载A19处理器,性能非常强劲。售价10000元,支持分期付款。
不重叠:
片段1:"iPhone17搭载A19处理器,性能非常强劲。"
片段2:"售价10000元,支持分期付款。"
→ 片段2 向量只含价格语义,搜"A19多少钱"时匹配度低
有重叠:
片段1:"iPhone17搭载A19处理器,性能非常强劲。"
片段2:"性能非常强劲。售价10000元,支持分期付款。"
→ 片段2 向量混入了性能相关语义,和"A19多少钱"匹配度略高
重叠不是银弹,提升有限。根本解法是 topK 多取几个片段,让大模型综合理解。
六、实践建议
存入时: 同一主题的信息拼完整再存,不要割裂。
java
String content = String.format(
"商品:%s。描述:%s。价格:%s元。成色:%s。",
goods.getTitle(), goods.getDescription(),
goods.getPrice(), goods.getCondition()
);
vectorStore.add(List.of(new Document(goods.getId(), content, metadata)));
搜索时: topK 取 3~5 个,把多个片段都交给大模型,由大模型综合回答,而不是只取第一个。
java
List<Document> results = vectorStore.similaritySearch(
SearchRequest.builder()
.query(userQuestion)
.topK(5)
.similarityThreshold(0.6)
.build()
);
// 把 results 里的内容拼成 context 塞给大模型
分片配置: 内容不长的场景无需关心,用默认配置即可。只有处理长文档时才需要调整分片大小和重叠比例。