阅读本文前,建议具备以下基础知识:RAG检索增强生成、Elasticsearch全文检索、向量数据库、知识图谱基础概念
一、项目背景与核心挑战
1.1 业务场景描述
你有全国各地的警情数据,需要构建一个智能知识库系统,实现以下核心功能:
- 语义检索:通过自然语言查询相似案件,如"查找与张三相关的所有入室盗窃案"
- 关系分析:发现案件之间的关联性,挖掘团伙作案线索
- 智能问答:基于历史案件数据回答办案人员的专业问题
- 跨库检索:同时支持关键词精确匹配和语义相似度搜索
1.2 面临的核心挑战
📊 数据量巨大
千万级案件记录
单次全量扫描不可接受
🔄 入库工作量大
需要批量处理流水线
否则内存溢出
⏱️ 查询速度慢
单次查询可能耗时数秒
影响用户体验
🎯 准确性要求高
办案数据不容有误
检索结果必须可追溯
💡 需要构建分层混合架构
面对这些挑战,简单的单一技术栈无法满足需求,需要构建一套分层混合架构。
二、系统架构总览与核心设计思想
2.1 整体架构图
缓存层
查询层 - 四阶段检索
存储层 - 数据分层设计
数据接入层
原始数据源
(JSON/CSV/Parquet)
数据清洗服务
实体识别服务
向量化服务
Elasticsearch
L1: 全文索引层
(精准过滤)
Milvus
L2: 向量索引层
(语义召回)
Neo4j
L3: 知识图谱层
(关系推理)
MinIO
L0: 原始数据层
(归档存储)
Stage1: ES预过滤
(缩小范围)
Stage2: 向量召回
(语义匹配)
Stage3: 图谱扩展
(关系增强)
Stage4: 融合重排
(精排输出)
Redis查询缓存
热门预计算
2.2 核心设计思想:数据分层与检索分层
检索分层执行 Query Layering
数据分层存储 Data Layering
存储层
存储层
存储层
L0 原始数据层
MinIO/S3
原始文件永久归档
L1 全文索引层
Elasticsearch
关键词精准过滤
L2 向量索引层
Milvus
语义相似度召回
L3 知识图谱层
Neo4j
关系网络推理
Stage1 预过滤
使用L1精准缩小范围
Stage2 语义召回
使用L2进行向量相似度搜索
Stage3 关系扩展
使用L3进行图遍历增强
Stage4 融合重排
综合多路结果精排输出
关键点:每一层都有明确的职责边界,数据按层次组织,检索按层次执行
2.3 技术选型与职责划分
| 组件 | 选型 | 职责边界 | 性能目标 |
|---|---|---|---|
| L0原始数据层 | MinIO/S3 | 原始文件归档、审计追溯 | 吞吐优先 |
| L1全文索引层 | Elasticsearch | 关键词匹配、时间/地域过滤 | P99 < 50ms |
| L2向量索引层 | Milvus/Qdrant | 语义相似度搜索、ANN检索 | P99 < 100ms |
| L3知识图谱层 | Neo4j | 关系推理、路径发现、团团伙分析 | P99 < 200ms |
| 缓存层 | Redis | 查询结果缓存、热数据预加载 | 命中率 > 80% |
三、数据分层设计详解
3.1 为什么需要数据分层?
数据分层的核心原因
- 职责单一性原则
每个存储层只负责一种检索模式
避免功能耦合
2. 性能隔离
ES: IO密集型-优化磁盘读写
向量: CPU密集型-优化内存带宽
图谱: 网络密集型-优化CPU缓存
3. 成本可控
冷热数据分离
使用不同存储介质降低成本
4. 独立扩展
各层可独立扩容
按需分配资源
3.2 各层数据结构设计
L0层:原始数据层(归档存储)
json
{
"layer": "L0_RAW",
"storage": "MinIO/S3",
"format": "Parquet",
"schema": {
"case_id": "string (主键)",
"region_code": "string (6位行政区划)",
"case_type": "string",
"occurrence_time": "timestamp",
"report_time": "timestamp",
"full_text": "string (原始文本)",
"source_file": "string (来源文件)",
"created_at": "timestamp",
"checksum": "string (数据校验)"
},
"partition": "region_code/year/month",
"retention": "永久保留"
}
L1层:全文索引层(精准过滤)
json
{
"layer": "L1_FULLTEXT",
"storage": "Elasticsearch",
"index": "police_case_v2",
"shards": 15,
"replicas": 2,
"mappings": {
"properties": {
"case_id": {"type": "keyword"},
"region_code": {"type": "keyword"},
"region_province": {"type": "keyword"},
"region_city": {"type": "keyword"},
"case_type": {"type": "keyword"},
"case_subtype": {"type": "keyword"},
"occurrence_time": {"type": "date"},
"report_time": {"type": "date"},
"keywords": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"keyword": {"type": "keyword"}
}
},
"full_text": {
"type": "text",
"analyzer": "ik_max_word"
},
"severity": {"type": "keyword"},
"status": {"type": "keyword"},
"victim_count": {"type": "integer"},
"suspect_count": {"type": "integer"},
"amount": {"type": "float"},
"entities": {
"type": "nested",
"properties": {
"type": {"type": "keyword"},
"value": {"type": "keyword"},
"count": {"type": "integer"}
}
}
}
},
"routing": "region_code",
"aliases": ["police_case_write", "police_case_read"]
}
L2层:向量索引层(语义召回)
json
{
"layer": "L2_VECTOR",
"storage": "Milvus",
"collection": "police_case_vectors",
"dimension": 1024,
"metric_type": "COSINE",
"index_type": "HNSW",
"index_params": {
"M": 16,
"efConstruction": 200
},
"search_params": {
"ef": 128
},
"partition_key": "region_code",
"schema": {
"fields": [
{"name": "case_id", "type": "VarChar", "max_length": 64, "is_primary": true},
{"name": "region_code", "type": "VarChar", "max_length": 8},
{"name": "embedding", "type": "FloatVector", "dim": 1024},
{"name": "occurrence_time", "type": "Int64"},
{"name": "case_type", "type": "VarChar", "max_length": 32}
]
}
}
L3层:知识图谱层(关系推理)
cypher
// 节点类型定义
(:Person {name, id_card, phone, role}) // 人物
(:Location {address, region_code, lng, lat}) // 地点
(:Case {case_id, case_type, time}) // 案件
(:Vehicle {plate, brand, color}) // 车辆
(:Evidence {type, description}) // 证据
(:Organization {name, type}) // 组织
// 关系类型定义
(p:Person)-[:INVOLVES_IN {role}]->(c:Case) // 涉案人
(l:Location)-[:OCCURS_AT]->(c:Case) // 案发地点
(c1:Case)-[:SIMILAR_TO {score}]->(c2:Case) // 相似案件
(c:Case)-[:USES]->(v:Vehicle) // 涉案车辆
(c:Case)-[:TEMPORAL_NEAR {days}]->(c2:Case) // 时间相近
(p1:Person)-[:ASSOCIATED_WITH]->(p2:Person) // 人员关联
(v:Vehicle)-[:APPEARED_AT {time}]->(l:Location) // 车辆出现
四、搜索流程优化详解
4.1 为什么检索流程需要优化?
存在的问题
- 串行执行导致延迟累加
- 每一步都依赖上一步结果
无法并行
3. 无法中途截断
大量无效计算
4. 缺少结果融合机制
原始方案: 串行执行
用户查询
ES关键词搜索
耗时 50ms
向量语义搜索
耗时 100ms
依赖ES结果
知识图谱推理
耗时 200ms
依赖向量结果
返回结果
总耗时: 350ms+
4.2 优化方案:并行召回 + 分层融合
Stage 1: 并行召回阶段 同时执行
Stage 4: 精排输出阶段
Cross-encoder精细化评分
综合权重: 语义40% + 关键词20% + 时间20% + 地域20%
输出: TOP-20最终结果
耗时: 80ms
Stage 3: 上下文增强阶段
LightRAG上下文窗口检索
对TOP-500候选集进行局部上下文增强
LightRAG全局图谱推理
对TOP-100进行全局关系推理
耗时: 150ms
Stage 2: RRF融合阶段
RRF融合算法
RRFd = Σ 1/k + rankd
输出: TOP-500候选集
耗时: 20ms
用户查询: 查找上海近期ATM盗窃案件
ES过滤召回
50ms
TOP-5000精确匹配
向量语义召回
100ms
TOP-1000语义相似
图谱关系召回
80ms
TOP-200关系相关
总耗时: max50,100,80 + 20 + 150 + 80 = 270ms
相比串行: 节省 80ms+ 约23%的延迟优化
4.3 各阶段详细设计
Stage 1-1:ES预过滤层(精准缩小范围)
python
class ElasticsearchPreFilter:
"""ES预过滤服务 - 精准定位候选集"""
def __init__(self):
self.es_client = Elasticsearch(["es-node-1:9200", "es-node-2:9200"])
self.index = "police_case_v2"
def build_filter_query(self, query: str, filters: dict) -> dict:
"""
构建复合查询
Args:
query: 用户查询文本
filters: 过滤条件 {
"region_code": "310000",
"case_type": "盗窃",
"time_range": ("2024-01-01", "2024-12-31"),
"severity": ["一般", "重大"]
}
Returns:
ES查询DSL
"""
must_clauses = []
filter_clauses = []
# 1. 关键词匹配
if query:
must_clauses.append({
"multi_match": {
"query": query,
"fields": ["keywords^3", "full_text^1"],
"type": "best_fields",
"minimum_should_match": "50%"
}
})
# 2. 地域过滤
if filters.get("region_code"):
filter_clauses.append({
"term": {"region_code": filters["region_code"]}
})
# 3. 时间范围过滤
if filters.get("time_range"):
filter_clauses.append({
"range": {
"occurrence_time": {
"gte": filters["time_range"][0],
"lte": filters["time_range"][1]
}
}
})
# 4. 案件类型过滤
if filters.get("case_type"):
filter_clauses.append({
"term": {"case_type": filters["case_type"]}
})
# 5. 严重程度过滤
if filters.get("severity"):
filter_clauses.append({
"terms": {"severity": filters["severity"]}
})
return {
"size": 5000, # 预过滤候选集上限
"query": {
"bool": {
"must": must_clauses if must_clauses else [{"match_all": {}}],
"filter": filter_clauses
}
},
"_source": ["case_id", "region_code", "case_type",
"occurrence_time", "severity", "score"],
"sort": [{"_score": "desc"}, {"occurrence_time": "desc"}],
"track_total_hits": True
}
def execute_filter(self, query: str, filters: dict) -> FilterResult:
"""执行预过滤查询"""
es_query = self.build_filter_query(query, filters)
# 使用routing优化,按地域路由减少扫描范围
region_code = filters.get("region_code", "*")
response = self.es_client.search(
index=self.index,
body=es_query,
routing=region_code,
request_timeout=50 # 50ms超时保护
)
return FilterResult(
total=response["hits"]["total"]["value"],
case_ids=[hit["_source"]["case_id"] for hit in response["hits"]["hits"]],
scores={hit["_source"]["case_id"]: hit["_score"]
for hit in response["hits"]["hits"]},
latency_ms=response["took"]
)
Stage 1-2:向量语义召回层(语义扩展)
python
class VectorSemanticRecall:
"""向量语义召回服务 - 捕捉语义相关性"""
def __init__(self):
self.milvus_client = MilvusClient(uri="http://milvus:19530")
self.embedding_model = load_embedding_model(
model_name="text2vec-base-chinese",
device="cuda" # GPU加速
)
async def execute_semantic_search(
self,
query: str,
filters: dict,
prefiltered_ids: list = None,
top_k: int = 1000
) -> SemanticResult:
"""
执行向量语义搜索
Args:
query: 用户查询
filters: 过滤条件
prefiltered_ids: ES预过滤的case_id列表(用于交集)
top_k: 返回的TOP-K结果
Returns:
语义召回结果
"""
# 1. 查询向量化
query_embedding = self.embedding_model.encode(
query,
normalize_embeddings=True
)
# 2. 构建过滤表达式
filter_expr = self._build_filter_expr(filters)
# 3. ANN向量搜索
search_params = {
"metric_type": "COSINE",
"params": {"ef": 128},
"round_decimal": 4
}
results = self.milvus_client.search(
collection_name="police_case_vectors",
data=[query_embedding.tolist()],
filter=filter_expr,
limit=top_k * 2, # 多召回一些用于后续交集
output_fields=["case_id", "region_code", "case_type", "occurrence_time"],
search_params=search_params
)
# 4. 与ES预过滤结果取交集
if prefiltered_ids:
semantic_ids = [hit["entity"]["case_id"] for hit in results[0]]
intersection_ids = set(prefiltered_ids) & set(semantic_ids)
# 重新排序交集结果
filtered_results = [
hit for hit in results[0]
if hit["entity"]["case_id"] in intersection_ids
]
else:
filtered_results = results[0][:top_k]
return SemanticResult(
case_ids=[hit["entity"]["case_id"] for hit in filtered_results],
scores=[1 - hit["distance"] for hit in filtered_results], # 距离转相似度
latency_ms=sum(hit.get("latency", 0) for hit in filtered_results)
)
def _build_filter_expr(self, filters: dict) -> str:
"""构建Milvus过滤表达式"""
conditions = []
if filters.get("region_code"):
conditions.append(f'region_code == "{filters["region_code"]}"')
if filters.get("case_type"):
conditions.append(f'case_type == "{filters["case_type"]}"')
if filters.get("time_range"):
start_ts = int(pd.Timestamp(filters["time_range"][0]).timestamp())
end_ts = int(pd.Timestamp(filters["time_range"][1]).timestamp())
conditions.append(f"occurrence_time >= {start_ts} and occurrence_time <= {end_ts}")
return " and ".join(conditions) if conditions else ""
Stage 1-3:图谱关系召回层(关系增强)
python
class GraphRelationRecall:
"""知识图谱关系召回服务 - 发现关联案件"""
def __init__(self):
self.neo4j_driver = GraphDatabase.driver(
"neo4j://neo4j:7687",
auth=("neo4j", "password")
)
async def execute_graph_search(
self,
query: str,
filters: dict,
context_cases: list = None,
top_k: int = 200
) -> GraphResult:
"""
执行图谱关系搜索
Args:
query: 用户查询
filters: 过滤条件
context_cases: 从其他召回层获取的case_id列表
top_k: 返回的TOP-K关联结果
Returns:
图谱召回结果
"""
# 1. 实体识别 - 从查询中提取实体
entities = self._extract_entities(query)
person = entities.get("person", [])
location = entities.get("location", [])
case_type = entities.get("case_type", [])
cypher_queries = []
# 2. 基于实体的关系查询
if person:
cypher_queries.append(f"""
MATCH (p:Person)-[:INVOLVES_IN]->(c:Case)
WHERE p.name IN {person}
RETURN c.case_id AS case_id, count(p) AS relevance_score
ORDER BY relevance_score DESC
LIMIT {top_k}
""")
if location:
cypher_queries.append(f"""
MATCH (l:Location)-[:OCCURS_AT]->(c:Case)
WHERE l.address CONTAINS '{location[0]}'
RETURN c.case_id AS case_id, 1.0 AS relevance_score
ORDER BY c.occurrence_time DESC
LIMIT {top_k}
""")
# 3. 基于上下文的相似案件查询
if context_cases:
cypher_queries.append(f"""
MATCH (c1:Case)-[:SIMILAR_TO]->(c2:Case)
WHERE c1.case_id IN {context_cases[:50]}
WITH c2, avg(r.score) AS avg_score
ORDER BY avg_score DESC
RETURN c2.case_id AS case_id, avg_score AS relevance_score
LIMIT {top_k}
""")
# 4. 执行查询并融合结果
results = []
with self.neo4j_driver.session() as session:
for cypher in cypher_queries:
result = session.run(cypher)
results.extend([dict(record) for record in result])
# 5. 去重并排序
case_scores = {}
for item in results:
case_id = item["case_id"]
score = item.get("relevance_score", 0)
case_scores[case_id] = case_scores.get(case_id, 0) + score
sorted_cases = sorted(
case_scores.items(),
key=lambda x: x[1],
reverse=True
)[:top_k]
return GraphResult(
case_ids=[case_id for case_id, _ in sorted_cases],
scores=[score for _, score in sorted_cases],
relations=self._extract_relations([case_id for case_id, _ in sorted_cases[:50]])
)
Stage 2:多路召回融合(RRF算法)
python
class HybridFusionEngine:
"""混合检索融合引擎 - RRF算法实现"""
def __init__(self, rrf_k: int = 60):
self.rrf_k = rrf_k
def reciprocal_rank_fusion(
self,
es_results: FilterResult,
vector_results: SemanticResult,
graph_results: GraphResult,
weights: dict = None
) -> List[FusedDocument]:
"""
RRF融合算法实现
公式: RRF(d) = Σ 1/(k + rank(d))
加权版本: RRF(d) = Σ wi / (k + ranki(d))
Args:
es_results: ES过滤结果
vector_results: 向量召回结果
graph_results: 图谱召回结果
weights: 各路召回权重,默认均衡
Returns:
融合排序后的文档列表
"""
if weights is None:
weights = {
"es": 0.35,
"vector": 0.40,
"graph": 0.25
}
# 初始化分数字典
scores = defaultdict(float)
doc_sources = defaultdict(list) # 记录每条doc的来源
# ES结果贡献
for rank, case_id in enumerate(es_results.case_ids, start=1):
doc_score = weights["es"] / (self.rrf_k + rank)
scores[case_id] += doc_score
doc_sources[case_id].append(("es", 1.0 / (self.rrf_k + rank)))
# 向量结果贡献
for rank, case_id in enumerate(vector_results.case_ids, start=1):
# 向量结果有自己的相似度分数,可用于加权
vector_weight = vector_results.scores[rank - 1] if rank <= len(vector_results.scores) else 0.5
doc_score = weights["vector"] * vector_weight / (self.rrf_k + rank)
scores[case_id] += doc_score
doc_sources[case_id].append(("vector", vector_weight / (self.rrf_k + rank)))
# 图谱结果贡献
for rank, case_id in enumerate(graph_results.case_ids, start=1):
graph_weight = graph_results.scores[rank - 1] if rank <= len(graph_results.scores) else 1.0
doc_score = weights["graph"] * graph_weight / (self.rrf_k + rank)
scores[case_id] += doc_score
doc_sources[case_id].append(("graph", graph_weight / (self.rrf_k + rank)))
# 按融合分数排序
sorted_docs = sorted(
scores.items(),
key=lambda x: x[1],
reverse=True
)
# 构建结果列表
fused_results = []
for case_id, total_score in sorted_docs:
fused_results.append(FusedDocument(
case_id=case_id,
fusion_score=total_score,
sources=doc_sources[case_id],
es_score=es_results.scores.get(case_id, 0),
vector_score=next(
(s for cid, s in zip(vector_results.case_ids, vector_results.scores)
if cid == case_id), 0
),
graph_score=next(
(s for cid, s in zip(graph_results.case_ids, graph_results.scores)
if cid == case_id), 0
)
))
return fused_results
def apply_diversity_boosting(
self,
results: List[FusedDocument],
max_same_region: int = 3,
max_same_type: int = 5
) -> List[FusedDocument]:
"""
多样性增强 - 避免结果过度集中
MMR (Maximal Marginal Relevance) 策略
"""
selected = []
remaining = results.copy()
region_counts = defaultdict(int)
type_counts = defaultdict(int)
for doc in results:
# 检查地域多样性
if region_counts[doc.region_code] >= max_same_region:
continue
# 检查类型多样性
if type_counts[doc.case_type] >= max_same_type:
continue
selected.append(doc)
region_counts[doc.region_code] += 1
type_counts[doc.case_type] += 1
remaining.remove(doc)
if len(selected) >= 100: # 限制候选集大小
break
# 将未选中的追加到末尾
selected.extend(remaining[:100 - len(selected)])
return selected
Stage 3:LightRAG上下文增强
python
class LightRAGContextEnhancer:
"""LightRAG上下文增强服务"""
def __init__(self):
self.local_context_window = 5
self.global_graph_depth = 2
async def enhance_with_local_context(
self,
query: str,
candidate_docs: List[FusedDocument],
knowledge_base: any
) -> List[ContextEnhancedDocument]:
"""
局部上下文检索增强
对每个候选文档,找到其窗口内的相关文档作为上下文
"""
enhanced_docs = []
for doc in candidate_docs[:100]: # 只对TOP-100增强
# 获取窗口上下文
window_context = knowledge_base.get_window_context(
center_doc_id=doc.case_id,
window_size=self.local_context_window,
query=query # 用于相关性过滤
)
enhanced_docs.append(ContextEnhancedDocument(
case_id=doc.case_id,
original_doc=doc,
local_context=window_context,
context_relevance=self._compute_context_relevance(
query, window_context
)
))
return enhanced_docs
async def enhance_with_global_graph(
self,
query: str,
candidate_docs: List[ContextEnhancedDocument],
neo4j_driver: any
) -> List[GraphEnhancedDocument]:
"""
全局图谱推理增强
使用Neo4j进行2跳范围内的关系推理
"""
# 提取查询中的关键实体
key_entities = self._extract_entities(query)
# 批量查询图关系
with neo4j_driver.session() as session:
cypher = f"""
MATCH path = (start)-[*1..{self.global_graph_depth}]-(end)
WHERE start.case_id IN {[doc.case_id for doc in candidate_docs[:50]]}
WITH path, nodes(path) as path_nodes, relationships(path) as path_rels
RETURN path_nodes, path_rels,
[n IN path_nodes | CASE WHEN n:Case THEN n.case_id END] as case_ids
LIMIT 200
"""
graph_paths = list(session.run(cypher))
# 构建图上下文
graph_context = self._build_graph_context(graph_paths)
return [GraphEnhancedDocument(
case_id=doc.case_id,
context_enhanced_doc=doc,
graph_context=graph_context,
relation_count=len([p for p in graph_paths
if doc.case_id in p["case_ids"]])
) for doc in candidate_docs]
Stage 4:Cross-encoder精排
python
class CrossEncoderReranker:
"""Cross-encoder精细化重排序"""
def __init__(self):
self.model = SentenceTransformer("cross-encoder/ms-marco-MiniLM-L-12-v2")
self.model.max_seq_length = 512
async def rerank(
self,
query: str,
candidates: List[GraphEnhancedDocument],
top_k: int = 20
) -> List[RerankedDocument]:
"""
Cross-encoder精细化重排
使用交互式模型计算query-doc对的精细化相似度
"""
# 准备输入对
doc_texts = [
self._build_doc_text(doc.context_enhanced_doc.original_doc)
for doc in candidates
]
pairs = [(query, doc_text) for doc_text in doc_texts]
# 批量计算交叉编码分数
cross_scores = self.model.predict(pairs, show_progress_bar=False)
# 综合多维度分数
final_scores = []
for i, doc in enumerate(candidates[:100]):
original_doc = doc.context_enhanced_doc.original_doc
# 多维度加权
final_score = (
0.40 * self._normalize(cross_scores[i], cross_scores) + # Cross-encoder
0.25 * original_doc.fusion_score / max(d.fusion_score for d in candidates) + # 融合分数
0.20 * (1 if original_doc.es_score > 0 else 0) + # ES命中
0.10 * doc.context_enhanced_doc.context_relevance + # 上下文相关性
0.05 * doc.relation_count / max(d.relation_count for d in candidates if d.relation_count > 0) # 关系数量
)
final_scores.append(RerankedDocument(
case_id=doc.case_id,
original_doc=original_doc,
cross_encoder_score=float(cross_scores[i]),
final_score=final_score,
rank=0
))
# 按最终分数排序
final_scores.sort(key=lambda x: x.final_score, reverse=True)
# 设置最终排名
for rank, doc in enumerate(final_scores[:top_k], start=1):
doc.rank = rank
return final_scores[:top_k]
def _build_doc_text(self, doc: FusedDocument) -> str:
"""构建文档文本用于交叉编码"""
return f"""
案件类型: {doc.case_type}
地域: {doc.region_code}
发生时间: {doc.occurrence_time}
严重程度: {doc.severity}
ES匹配分数: {doc.es_score:.4f}
向量相似度: {doc.vector_score:.4f}
融合分数: {doc.fusion_score:.4f}
"""
五、查询流程完整代码实现
python
class HybridQueryEngine:
"""混合查询引擎 - 整合所有阶段"""
def __init__(self):
self.es_filter = ElasticsearchPreFilter()
self.vector_recall = VectorSemanticRecall()
self.graph_recall = GraphRelationRecall()
self.fusion_engine = HybridFusionEngine()
self.lightrag_enhancer = LightRAGContextEnhancer()
self.reranker = CrossEncoderReranker()
self.cache = RedisCache()
async def execute_query(
self,
query: str,
filters: dict = None,
mode: str = "hybrid" # light | hybrid | full
) -> QueryResponse:
"""
执行混合查询
Args:
query: 用户查询
filters: 过滤条件
mode: 查询模式
- light: 仅向量搜索
- hybrid: 标准混合搜索
- full: 完整混合+图谱增强
Returns:
查询响应
"""
if filters is None:
filters = {}
cache_key = self._generate_cache_key(query, filters, mode)
# 1. 检查缓存
cached_result = await self.cache.get(cache_key)
if cached_result:
return cached_result
# 2. Stage 1: 并行召回
if mode == "light":
# 仅向量搜索
vector_results = await self.vector_recall.execute_semantic_search(
query, filters
)
es_results = FilterResult(case_ids=[], scores={}, total=0, latency_ms=0)
graph_results = GraphResult(case_ids=[], scores={}, relations={})
else:
# 三路并行召回
es_results, vector_results, graph_results = await asyncio.gather(
asyncio.to_thread(self.es_filter.execute_filter, query, filters),
self.vector_recall.execute_semantic_search(
query, filters, top_k=2000
),
self.graph_recall.execute_graph_search(
query, filters,
context_cases=vector_results.case_ids[:100] if mode == "full" else None
)
)
# 3. Stage 2: RRF融合
fused_results = self.fusion_engine.reciprocal_rank_fusion(
es_results, vector_results, graph_results
)
fused_results = self.fusion_engine.apply_diversity_boosting(fused_results)
# 4. Stage 3: LightRAG增强 (仅full模式)
if mode == "full":
enhanced_docs = await self.lightrag_enhancer.enhance_with_local_context(
query, fused_results[:100]
)
graph_enhanced = await self.lightrag_enhancer.enhance_with_global_graph(
query, enhanced_docs
)
else:
graph_enhanced = [
GraphEnhancedDocument(
case_id=doc.case_id,
context_enhanced_doc=ContextEnhancedDocument(
case_id=doc.case_id,
original_doc=doc,
local_context="",
context_relevance=0
),
graph_context="",
relation_count=0
)
for doc in fused_results[:100]
]
# 5. Stage 4: Cross-encoder精排
final_results = await self.reranker.rerank(query, graph_enhanced, top_k=20)
# 6. 构建响应
response = QueryResponse(
query=query,
filters=filters,
mode=mode,
total_hits=es_results.total if es_results.total else len(fused_results),
results=[
ResultItem(
case_id=doc.case_id,
rank=doc.rank,
score=doc.final_score,
cross_score=doc.cross_encoder_score,
source_scores={
"es": doc.original_doc.es_score,
"vector": doc.original_doc.vector_score,
"fusion": doc.original_doc.fusion_score
}
)
for doc in final_results
],
latency={
"es_filter": es_results.latency_ms,
"vector_recall": vector_results.latency_ms,
"fusion": 0,
"rerank": 0,
"total": sum([
es_results.latency_ms,
vector_results.latency_ms,
0, 0
])
},
cached=False
)
# 7. 写入缓存
await self.cache.set(cache_key, response, ttl=3600)
return response
六、性能优化策略
6.1 缓存策略
多级缓存架构
多级缓存架构
L3: 预计算缓存
存储位置: 本地内存/SSD
更新频率: 每小时刷新热门查询
Key设计: top_1000_queries
命中率目标: > 30%
L2: 向量缓存
存储位置: Redis
TTL: 24小时
Key设计: hashembedding
适用场景: 相同语义不同表述的查询
L1: 查询结果缓存
存储位置: Redis Cluster
TTL: 1小时
Key设计: hashquery + filters + mode
命中率目标: > 60%
6.2 异步处理优化
python
# 异步并行执行关键路径
async def execute_parallel_stages(self, query, filters):
"""
并行执行可并发的阶段
"""
# Stage 1: 完全并行
es_task = asyncio.to_thread(self.es_filter.execute, query, filters)
vector_task = self.vector_recall.execute(query, filters)
graph_task = self.graph_recall.execute(query, filters)
es_results, vector_results, graph_results = await asyncio.gather(
es_task, vector_task, graph_task,
return_exceptions=True
)
# Stage 2: 等待Stage1完成后执行
fusion_results = self.fusion_engine.fuse(
es_results, vector_results, graph_results
)
# Stage 3: 可选并行(与其他阶段无关)
context_task = self.lightrag_enhancer.enhance(fusion_results, query)
relation_task = self.neo4j_driver.query_relations(fusion_results)
context, relations = await asyncio.gather(context_task, relation_task)
# Stage 4: 必须等待Stage3
final_results = await self.reranker.rerank(fusion_results, context, relations)
return final_results
6.3 早停策略
python
class EarlyStoppingReranker:
"""
早停策略 - 当确信度足够高时提前终止
"""
def __init__(self, confidence_threshold: float = 0.95, min_docs: int = 5):
self.confidence_threshold = confidence_threshold
self.min_docs = min_docs
def should_stop(self, results: List[RerankedDocument]) -> bool:
"""
判断是否应该停止
条件:
1. 已处理最小文档数
2. TOP-K文档的分数方差足够小(确信度高)
"""
if len(results) < self.min_docs:
return False
top_k = results[:self.min_docs]
scores = [doc.final_score for doc in top_k]
# 计算变异系数 (CV)
mean = statistics.mean(scores)
stdev = statistics.stdev(scores) if len(scores) > 1 else 0
cv = stdev / mean if mean > 0 else float('inf')
# CV越小,确信度越高
return cv < 0.1 # 可调参数
七、数据分层检索保证
7.1 如何保证检索到需要的数据?
机制4: 分层验证,确保数据完整性
入库时: L0 → L1 → L2 → L3 顺序写入
校验点: 每个写入完成后验证数据一致性
修复任务: 定时检查并修复跨层不一致
机制3: 降级策略,防止单层故障
如果ES故障 → 仅使用向量+图谱召回,仍可返回结果
如果向量故障 → 仅使用ES+图谱召回,仍可返回结果
如果图谱故障 → 仅使用ES+向量召回,仍可返回结果
降级不影响核心检索能力,只是准确度略有下降
机制2: 候选集交集,确保高精度
Stage1 ES过滤 → 输出case_id集合A 精准匹配
Stage2 向量召回 → 输出case_id集合B 语义相似
Stage3 图谱扩展 → 输出case_id集合C 关系相关
最终候选集 = A ∪ B ∪ C 并集,不遗漏
精排输出 = Intersection候选集, 权重排序 交集,保证准确
机制1: 每层独立索引,数据不丢失
L0: 原始文件在MinIO,任何数据可追溯
L1: ES存储case_id + 元数据,case_id不丢失
L2: Milvus存储case_id + embedding,可通过case_id回查
L3: Neo4j存储case_id + 关系,可通过case_id关联
关键: case_id作为跨层关联的主键
7.2 跨层一致性保证
python
class CrossLayerConsistencyChecker:
"""
跨层一致性检查器
"""
def __init__(self):
self.es_client = Elasticsearch(...)
self.milvus_client = MilvusClient(...)
self.neo4j_driver = GraphDatabase.driver(...)
async def check_consistency(self, sample_size: int = 1000) -> ConsistencyReport:
"""
检查跨层数据一致性
Returns:
一致性报告
"""
# 1. 从ES随机采样
es_samples = await self._sample_from_es(sample_size)
inconsistencies = []
for case_id in es_samples:
# 2. 检查Milvus
milvus_exists = await self._check_milvus(case_id)
if not milvus_exists:
inconsistencies.append({
"case_id": case_id,
"missing_in": "milvus"
})
# 3. 检查Neo4j
neo4j_exists = await self._check_neo4j(case_id)
if not neo4j_exists:
inconsistencies.append({
"case_id": case_id,
"missing_in": "neo4j"
})
return ConsistencyReport(
total_checked=len(es_samples),
inconsistent_count=len(inconsistencies),
inconsistencies=inconsistencies,
consistency_rate=1 - len(inconsistencies) / len(es_samples)
)
async def repair_inconsistencies(self, report: ConsistencyReport):
"""
修复跨层不一致
"""
for item in report.inconsistencies:
case_id = item["case_id"]
missing_layer = item["missing_in"]
if missing_layer == "milvus":
# 从ES获取数据,重新向量化并写入Milvus
await self._rebuild_milvus_record(case_id)
elif missing_layer == "neo4j":
# 从ES获取数据,重新构建图关系并写入Neo4j
await self._rebuild_neo4j_record(case_id)
八、方案总结
8.1 核心设计要点
- 准确性保证
case_id作为跨层关联主键
候选集并集 + 精排交集机制
多路召回互为补充 精准 + 语义 + 关系
Cross-encoder精细化评分 - 性能优化
多级缓存 L1查询结果 + L2向量 + L3预计算
异步并行执行 Stage内并行,Stage间流水
早停策略 确信度足够时提前终止 - 检索分层执行
Stage1: 并行三路召回 ES + 向量 + 图谱
Stage2: RRF融合 综合多路得分
Stage3: LightRAG增强 上下文 + 全局
Stage4: Cross-encoder精排 最终TOP-K - 数据分层设计
L0: 原始归档层 MinIO
L1: 全文索引层 ES → 精准过滤
L2: 向量索引层 Milvus → 语义召回
L3: 知识图谱层 Neo4j → 关系推理
8.2 性能指标预期
| 指标 | 目标值 | 说明 |
|---|---|---|
| P50延迟 | < 150ms | 简单查询 (Light Mode) |
| P95延迟 | < 300ms | 标准混合查询 (Hybrid Mode) |
| P99延迟 | < 500ms | 完整查询 (Full Mode) |
| 召回率 | > 92% | TOP-20召回率 |
| 精确率 | > 88% | TOP-20精确率 |
| QPS峰值 | 1000+ | 支持高并发查询 |
| 缓存命中率 | > 70% | 热门查询缓存 |
| 系统可用性 | 99.9% | 多级容灾降级 |
8.3 方案对比
| 维度 | 原始串行方案 | 优化后的分层方案 |
|---|---|---|
| 执行模式 | 串行执行 | 并行召回 + 分层融合 |
| Stage1耗时 | 50+100+200=350ms | max(50,100,80)=100ms |
| 候选集规模 | 逐层递减 | 并集保留,不遗漏 |
| 融合机制 | 无 | RRF + Cross-encoder |
| 容错能力 | 单点故障 | 多级降级 |
| 准确度 | 单一路径 | 三路互补 |
九、多智能体协同架构设计
9.1 为什么需要多智能体架构?
多智能体解决方案
任务解耦
每个智能体专注单一职责
并行处理
多个智能体协同工作
灵活编排
根据场景动态组合智能体
独立进化
单个智能体可独立优化升级
警情业务复杂挑战
任务多样性
语义检索、关系分析、智能问答、预警预测
专业性强
需要不同领域的专业知识
实时性要求
不同任务有不同的响应时间要求
可扩展性
新功能需要独立迭代和部署
9.2 多智能体总体架构
存储层 Storage
工具层 Tools
专业智能体层 Specialist Agents
智能体编排层 Orchestrator Agent
处理类智能体
分析类智能体
查询类智能体
主控智能体
任务分解、路由、结果聚合
语义检索智能体
Semantic Search Agent
关系分析智能体
Relation Analysis Agent
智能问答智能体
QA Agent
模式识别智能体
Pattern Recognition Agent
风险预警智能体
Risk Warning Agent
趋势预测智能体
Trend Prediction Agent
实体抽取智能体
Entity Extraction Agent
知识图谱构建智能体
KG Builder Agent
数据清洗智能体
Data Cleaning Agent
混合检索引擎
Hybrid Search Engine
知识图谱引擎
Neo4j Graph Engine
向量数据库
Milvus Vector DB
大语言模型
LLM Service
缓存服务
Redis Cache
Elasticsearch
Milvus
Neo4j
MinIO
9.3 核心智能体详细设计
9.3.1 语义检索智能体 Semantic Search Agent
输出
排序后的案件列表
元数据信息
处理流程
意图识别
Intent Recognition
查询重写
Query Rewriting
混合检索执行
Hybrid Search
结果排序
Result Ranking
输入
用户自然语言查询
过滤条件
职责:
- 接收自然语言查询并理解用户意图
- 调用混合检索引擎执行四阶段检索
- 返回排序后的相关案件列表
能力:
- 支持多轮对话式查询细化
- 自动识别查询中的实体和过滤条件
- 根据场景选择light/hybrid/full检索模式
9.3.2 关系分析智能体 Relation Analysis Agent
输出结果
关系图谱
关键节点
异常模式
可视化报告
分析方法
图遍历算法
Graph Traversal
社区发现
Community Detection
路径分析
Path Analysis
中心性计算
Centrality
分析任务
人员关联分析
Person Network
案件串并分析
Case Linking
时空轨迹分析
Spatiotemporal
团伙挖掘
Gang Discovery
职责:
- 基于知识图谱进行深度关系推理
- 发现案件、人员、地点之间的隐藏关联
- 识别团伙作案模式和关键人物
能力:
- 支持1-N跳关系查询
- 自动识别关键节点和桥梁节点
- 生成可视化关系图谱和洞察报告
9.3.3 风险预警智能体 Risk Warning Agent
预警动作
实时告警
生成预警报告
推荐处置方案
推送至责任人
预警规则
民转刑风险
Civil to Criminal
重复报警
Repeat Reports
升级趋势
Escalation
关联案件
Related Cases
实时监控
新警情接入
特征提取
风险评分
职责:
- 实时监控新警情数据并评估风险等级
- 识别潜在的民转刑风险案件
- 提前预警可能升级的矛盾纠纷
能力:
- 基于历史数据训练风险预测模型
- 多维度风险评估人员、地点、时间、类型
- 生成可解释的预警原因和处置建议
9.3.4 智能问答智能体 QA Agent
知识库 大语言模型 QA智能体 用户 知识库 大语言模型 QA智能体 用户 提出专业问题 意图识别&实体抽取 检索相关案情 返回上下文 生成回答请求 生成专业回答 事实核查&引用标注 返回答案+依据
职责:
- 回答办案人员的专业问题
- 基于历史案例提供决策参考
- 提供法律依据和处置流程指导
能力:
- 支持复杂多跳问答
- 自动标注答案来源和可信度
- 提供相关案例推荐
9.4 智能体协同工作流
场景1: 复杂案件深度分析
智能问答 关系分析 语义检索 主控智能体 用户 智能问答 关系分析 语义检索 主控智能体 用户 "分析张三相关的所有盗窃案件" 执行语义检索 返回50个相关案件 分析案件关联关系 生成关系图谱,发现3个团伙 生成分析报告 生成详细报告 返回分析结果+可视化
场景2: 风险预警联动
告警系统 关系分析 语义检索 风险预警 新警情事件 告警系统 关系分析 语义检索 风险预警 新警情事件 新警情接入 风险评分85分 检索历史相似案件 找到12个相似案件 分析关联风险 发现升级趋势 触发高级预警 推送处置方案
9.5 智能体通信协议
消息总线
Kafka/RabbitMQ
gRPC
RESTful API
通信协议
同步调用
Sync RPC
异步消息
Async Message Queue
事件驱动
Event Driven
流式传输
Streaming
消息格式
message_id: 唯一标识
agent_type: 发送者类型
intent: 意图类型
payload: 数据载荷
context: 上下文信息
priority: 优先级
消息格式示例:
json
{
"message_id": "msg_20240101_001",
"agent_type": "semantic_search",
"intent": "search_cases",
"payload": {
"query": "张三盗窃案",
"filters": {"region": "310000", "time_range": ["2024-01-01", "2024-12-31"]},
"top_k": 50
},
"context": {
"session_id": "session_123",
"user_id": "user_456",
"previous_results": []
},
"priority": "high",
"timeout_ms": 5000
}
9.6 智能体编排策略
应用场景
编排模式
串行编排
Sequential
并行编排
Parallel
条件编排
Conditional
循环编排
Iterative
串行: 检索→分析→报告
并行: 多路召回同时进行
条件: 高风险才触发深度分析
循环: 多轮对话细化查询
编排引擎配置示例:
yaml
workflow:
name: "deep_case_analysis"
steps:
- name: "semantic_search"
agent: "SemanticSearchAgent"
timeout: 5000
retry: 2
- name: "relation_analysis"
agent: "RelationAnalysisAgent"
depends_on: ["semantic_search"]
condition: "results.count > 10"
timeout: 10000
- name: "generate_report"
agent: "QAAgent"
depends_on: ["relation_analysis"]
timeout: 8000
parallel:
- "risk_assessment"
- "trend_analysis"
9.7 多智能体性能优化
性能指标
单智能体响应 < 200ms
工作流总耗时 < 1000ms
并发支持 > 100 QPS
可用性 > 99.9%
性能优化策略
智能体缓存
Agent Caching
结果复用
Result Reuse
懒加载
Lazy Loading
超时控制
Timeout Control
降级策略
Degradation
9.8 智能体监控与可观测性
可视化面板
实时监控大屏
智能体健康度
业务指标报表
告警通知中心
链路追踪
分布式追踪
OpenTelemetry
智能体调用链
性能瓶颈定位
异常根因分析
监控维度
性能监控
延迟、QPS、错误率
业务监控
检索准确率、召回率
资源监控
CPU、内存、GPU
质量监控
用户满意度、反馈
9.9 多智能体架构优势总结
多智能体架构
模块化设计
职责单一
独立部署
易于测试
灵活扩展
新增智能体
动态编排
热插拔
性能优化
并行处理
结果缓存
智能路由
高可用性
故障隔离
自动降级
容错机制
可维护性
独立迭代
版本管理
A/B测试
| 维度 | 单体架构 | 多智能体架构 |
|---|---|---|
| 职责划分 | 功能耦合 | 职责单一,边界清晰 |
| 扩展性 | 整体升级 | 独立扩展,按需部署 |
| 容错能力 | 单点故障 | 故障隔离,自动降级 |
| 开发效率 | 协调成本高 | 并行开发,独立测试 |
| 资源利用 | 资源竞争 | 独立伸缩,按需分配 |
| 运维复杂度 | 整体部署 | 独立发布,灰度上线 |