feat(phase1): 支持按类别过滤的文档检索
功能增强:
- VectorIndexService 自动提取文档类别
- 文件索引:从路径提取(如 aiops-docs/api/ → "api")
- 上传文档:默认类别 "upload"
- metadata.category 字段存储类别信息
- VectorSearchService 支持类别过滤
- searchSimilarDocuments(query, topK): 原方法,不过滤
- searchSimilarDocuments(query, topK, category): 新方法,按类别过滤
- 使用 Milvus expr 过滤:metadata["category"] == "xxx"
使用场景:
- 目录结构:
aiops-docs/
├── api/ → category="api"
├── domain/ → category="domain"
└── troubleshoot/ → category="troubleshoot"
- 检索示例:
// 只检索 API 文档
searchSimilarDocuments("Redis接口", 5, "api")
// 只检索领域知识
searchSimilarDocuments("缓存原理", 5, "domain")
// 全量检索
searchSimilarDocuments("问题诊断", 5, null)
编译验证:BUILD SUCCESS
This commit is contained in:
@@ -268,6 +268,9 @@ public class VectorIndexService {
|
|||||||
metadata.put("title", chunk.getTitle());
|
metadata.put("title", chunk.getTitle());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 默认类别:上传文档
|
||||||
|
metadata.put("category", "upload");
|
||||||
|
|
||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,6 +344,12 @@ public class VectorIndexService {
|
|||||||
metadata.put("_extension", extension);
|
metadata.put("_extension", extension);
|
||||||
metadata.put("_file_name", fileNameStr);
|
metadata.put("_file_name", fileNameStr);
|
||||||
|
|
||||||
|
// 提取类别(从文件路径中提取目录名)
|
||||||
|
String category = extractCategory(normalizedPath);
|
||||||
|
if (category != null && !category.isEmpty()) {
|
||||||
|
metadata.put("category", category);
|
||||||
|
}
|
||||||
|
|
||||||
// 分片信息
|
// 分片信息
|
||||||
metadata.put("chunkIndex", chunk.getChunkIndex());
|
metadata.put("chunkIndex", chunk.getChunkIndex());
|
||||||
metadata.put("totalChunks", totalChunks);
|
metadata.put("totalChunks", totalChunks);
|
||||||
@@ -353,6 +362,38 @@ public class VectorIndexService {
|
|||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从文件路径中提取类别
|
||||||
|
* 例如:aiops-docs/api/redis-api.md → "api"
|
||||||
|
*/
|
||||||
|
private String extractCategory(String filePath) {
|
||||||
|
try {
|
||||||
|
// 标准化路径分隔符
|
||||||
|
String normalized = filePath.replace("\\", "/");
|
||||||
|
|
||||||
|
// 查找 aiops-docs/ 后的第一级目录
|
||||||
|
int docsIndex = normalized.indexOf("aiops-docs/");
|
||||||
|
if (docsIndex >= 0) {
|
||||||
|
String afterDocs = normalized.substring(docsIndex + "aiops-docs/".length());
|
||||||
|
int slashIndex = afterDocs.indexOf("/");
|
||||||
|
if (slashIndex > 0) {
|
||||||
|
return afterDocs.substring(0, slashIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有 aiops-docs,返回第一级目录
|
||||||
|
int firstSlash = normalized.indexOf("/");
|
||||||
|
if (firstSlash > 0) {
|
||||||
|
return normalized.substring(0, firstSlash);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("提取类别失败,路径: {}", filePath, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插入向量到 Milvus
|
* 插入向量到 Milvus
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -40,23 +40,43 @@ public class VectorSearchService {
|
|||||||
* @return 搜索结果列表
|
* @return 搜索结果列表
|
||||||
*/
|
*/
|
||||||
public List<SearchResult> searchSimilarDocuments(String query, int topK) {
|
public List<SearchResult> searchSimilarDocuments(String query, int topK) {
|
||||||
|
return searchSimilarDocuments(query, topK, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索相似文档(支持类别过滤)
|
||||||
|
*
|
||||||
|
* @param query 查询文本
|
||||||
|
* @param topK 返回最相似的K个结果
|
||||||
|
* @param category 类别过滤(可选,null 表示不过滤)
|
||||||
|
* @return 搜索结果列表
|
||||||
|
*/
|
||||||
|
public List<SearchResult> searchSimilarDocuments(String query, int topK, String category) {
|
||||||
try {
|
try {
|
||||||
logger.info("开始搜索相似文档, 查询: {}, topK: {}", query, topK);
|
logger.info("开始搜索相似文档, 查询: {}, topK: {}, 类别: {}", query, topK, category);
|
||||||
|
|
||||||
// 1. 将查询文本向量化
|
// 1. 将查询文本向量化
|
||||||
List<Float> queryVector = embeddingService.generateQueryVector(query);
|
List<Float> queryVector = embeddingService.generateQueryVector(query);
|
||||||
logger.debug("查询向量生成成功, 维度: {}", queryVector.size());
|
logger.debug("查询向量生成成功, 维度: {}", queryVector.size());
|
||||||
|
|
||||||
// 2. 构建搜索参数
|
// 2. 构建搜索参数
|
||||||
SearchParam searchParam = SearchParam.newBuilder()
|
SearchParam.Builder searchParamBuilder = SearchParam.newBuilder()
|
||||||
.withCollectionName(MilvusConstants.MILVUS_COLLECTION_NAME)
|
.withCollectionName(MilvusConstants.MILVUS_COLLECTION_NAME)
|
||||||
.withVectorFieldName("vector")
|
.withVectorFieldName("vector")
|
||||||
.withVectors(Collections.singletonList(queryVector))
|
.withVectors(Collections.singletonList(queryVector))
|
||||||
.withTopK(topK)
|
.withTopK(topK)
|
||||||
.withMetricType(io.milvus.param.MetricType.L2)
|
.withMetricType(io.milvus.param.MetricType.L2)
|
||||||
.withOutFields(List.of("id", "content", "metadata"))
|
.withOutFields(List.of("id", "content", "metadata"))
|
||||||
.withParams("{\"nprobe\":10}")
|
.withParams("{\"nprobe\":10}");
|
||||||
.build();
|
|
||||||
|
// 添加类别过滤
|
||||||
|
if (category != null && !category.trim().isEmpty()) {
|
||||||
|
String expr = String.format("metadata[\"category\"] == \"%s\"", category);
|
||||||
|
searchParamBuilder.withExpr(expr);
|
||||||
|
logger.info("添加类别过滤: {}", expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchParam searchParam = searchParamBuilder.build();
|
||||||
|
|
||||||
// 3. 执行搜索
|
// 3. 执行搜索
|
||||||
R<SearchResults> searchResponse = milvusClient.search(searchParam);
|
R<SearchResults> searchResponse = milvusClient.search(searchParam);
|
||||||
|
|||||||
Reference in New Issue
Block a user