ES_QUERY_RESTRUCTURE_COMPLETE.md
9.28 KB
ES查询结构重构完成报告
完成日期: 2025-11-12
核心原则: 统一约定,保持简单,类型安全
问题回顾
原始问题
- Facets返回为空 - 已修复
- 前端listing time筛选失败 - 已修复
- Filter不作用于KNN查询 - 已修复(核心问题)
根本原因
KNN和query平级,导致filter只作用于query,KNN召回的结果没有被过滤。
原结构(错误):
{
"query": {
"bool": {
"must": [{"multi_match": {...}}],
"filter": [...] // 只作用于multi_match
}
},
"knn": {...} // 与query平级,filter不作用于它
}
解决方案
方案C:Function Score + Bool Should(已实施)
新结构(正确):
{
"query": {
"function_score": {
"query": {
"bool": {
"must": [
{
"bool": {
"should": [
{"multi_match": {...}}, // 文本查询
{"knn": {...}} // KNN查询
],
"minimum_should_match": 1 // 至少匹配一个
}
}
],
"filter": [...] // 作用于整个查询
}
},
"functions": [
{
"filter": {"range": {"days_since_last_update": {"lte": 30}}},
"weight": 1.1
}
],
"score_mode": "sum",
"boost_mode": "multiply"
}
}
}
关键改进
- Filter统一作用 - 外层bool.filter同时作用于文本和KNN
- 灵活召回 - 文本或KNN至少匹配一个(minimum_should_match=1)
- ES层打分 - function_score支持多种打分因子
- 保留扩展性 - RerankEngine禁用但保留,未来可启用本地重排
修改文件清单
1. /home/tw/SearchEngine/search/multilang_query_builder.py
修改点:
- 重构
build_multilang_query方法(156-210行) - 新增
_build_score_functions方法(212-237行)
核心改动:
# 构建内层bool: 文本和KNN二选一
inner_bool_should = [query_clause]
if enable_knn and query_vector is not None:
inner_bool_should.append({"knn": {...}})
inner_bool = {
"bool": {
"should": inner_bool_should,
"minimum_should_match": 1
}
}
# 外层bool包含filter
outer_bool = {
"bool": {
"must": [inner_bool],
"filter": filter_clauses # 作用于整体
}
}
# function_score包裹
function_score_query = {
"function_score": {
"query": outer_bool,
"functions": self._build_score_functions(),
"score_mode": "sum",
"boost_mode": "multiply"
}
}
2. /home/tw/SearchEngine/search/rerank_engine.py(新建)
来源:从 ranking_engine.py 重命名
修改:
- 类名:
RankingEngine→RerankEngine - 添加
enabled参数(默认False) - 更新文档说明
关键代码:
class RerankEngine:
"""本地重排引擎(当前禁用)"""
def __init__(self, ranking_expression: str, enabled: bool = False):
self.enabled = enabled
self.expression = ranking_expression
if enabled:
self.parsed_terms = self._parse_expression(ranking_expression)
def calculate_score(self, hit, base_score, knn_score=None):
if not self.enabled:
return base_score
# ... 原有逻辑
3. /home/tw/SearchEngine/search/searcher.py
修改点:
- 导入:
RankingEngine→RerankEngine - 初始化:
self.rerank_engine = RerankEngine(..., enabled=False) - 重排逻辑:检查
self.rerank_engine.enabled
4. /home/tw/SearchEngine/search/__init__.py
修改:
from .rerank_engine import RerankEngine # 原 RankingEngine
5. /home/tw/SearchEngine/search/ranking_engine.py
删除 - 已重命名为 rerank_engine.py
测试结果
✅ Test 1: Filter作用于文本查询
- 查询:"玩具"
- Filter:
categoryName_keyword = "桌面休闲玩具" - 结果:15 hits(正确过滤)
✅ Test 2: Filter作用于KNN查询
- 查询:"玩具"
- Range filter:
create_time >= "2023-01-01" - 结果:64 hits(正确过滤,KNN结果也被过滤)
- 验证:所有返回结果的create_time都 >= 2023-01-01
✅ Test 3: Function Score时效性加权
- Function:
days_since_last_update <= 30 → weight 1.1 - 结果:打分函数正常工作
✅ Test 4: 混合查询结构
- Inner bool.should包含2个子句:
- 文本查询(multi_match)
- KNN查询
- minimum_should_match=1(至少匹配一个)
✅ Test 5: Facets + Filters
- 返回正确的facets
- Selected字段正确标记
ES Query 结构验证
完整查询示例
{
"size": 5,
"from": 0,
"query": {
"function_score": {
"query": {
"bool": {
"must": [
{
"bool": {
"should": [
{
"bool": {
"should": [
{"multi_match": {"query": "玩具", "fields": [...]}}
],
"minimum_should_match": 1
}
},
{
"knn": {
"field": "title_embedding",
"query_vector": [...],
"k": 50,
"num_candidates": 200
}
}
],
"minimum_should_match": 1
}
}
],
"filter": [
{"term": {"categoryName_keyword": "桌面休闲玩具"}},
{"range": {"create_time": {"gte": "2023-01-01T00:00:00Z"}}}
]
}
},
"functions": [
{
"filter": {
"range": {"days_since_last_update": {"lte": 30}}
},
"weight": 1.1
}
],
"score_mode": "sum",
"boost_mode": "multiply"
}
}
}
结构分析
三层嵌套:
- 最外层:
function_score- 支持额外打分因子 - 外层bool:包含
must和filter- filter作用于所有查询 - 内层bool:包含
should子句 - 文本OR KNN
数据流:
用户查询
→ 文本查询 OR KNN查询(至少一个匹配)
→ 应用filter(同时过滤文本和KNN结果)
→ 应用function_score加权
→ 返回最终结果
架构优势
1. 正确性
- ✅ Filter同时作用于文本和KNN
- ✅ 不会有未过滤的KNN结果混入
2. 灵活性
- ✅ 文本或KNN至少匹配一个(更高召回)
- ✅ Function score支持多种打分因子
- ✅ 保留RerankEngine用于未来扩展
3. 性能
- ✅ Filter在ES层执行(硬过滤,不参与打分)
- ✅ Function score在ES层执行(无需本地重排)
- ✅ 减少数据传输(已过滤)
4. 可维护性
- ✅ 查询结构清晰
- ✅ 统一约定,不做兼容
- ✅ 类型安全(Pydantic模型)
命名规范
RankingEngine → RerankEngine
语义区分:
- Ranking - 排序、打分(通常指ES层的原生排序)
- Rerank - 重排序(通常指对ES结果的二次排序)
新架构:
- ES层:使用
function_score进行打分和排序 - 应用层:使用
RerankEngine进行本地重排(当前禁用)
状态:
RerankEngine.enabled = False- 暂时禁用- 未来如需复杂个性化排序可启用
对比总结
| 方面 | 重构前 | 重构后 |
|---|---|---|
| KNN位置 | 与query平级 | 在bool.should内 |
| Filter作用 | 只作用于文本 | 同时作用于文本和KNN |
| 召回策略 | 文本必须匹配 | 文本OR KNN至少一个 |
| 打分方式 | 本地重排 | ES function_score |
| 时效性加权 | 本地计算 | ES function加权 |
| Rerank | RankingEngine启用 | RerankEngine禁用 |
| 前端错误 | 422错误 | 正常工作 |
后续优化建议
1. Function Score扩展
可添加更多打分因子:
functions:
- filter: {range: {days_since_last_update: {lte: 30}}}
weight: 1.1
- filter: {term: {is_video: true}}
weight: 1.05
- field_value_factor:
field: sales_count
modifier: log1p
factor: 0.01
2. RerankEngine应用场景
未来如需启用本地重排:
- 实时个性化(基于用户画像)
- 复杂业务规则(无法用ES表达)
- A/B测试(不同排序策略)
3. 性能优化
- 添加查询缓存
- 优化embedding生成
- 监控function_score性能影响
4. 测试覆盖
- 添加集成测试
- 性能基准测试
- 边界情况测试
总结
✅ 核心成就
- 修复Filter问题 - Filter现在同时作用于文本和KNN
- 统一约定 - 全系统使用Pydantic模型,不做兼容
- 优化打分 - 使用ES function_score,性能更好
- 命名规范 - RerankEngine语义更清晰
- 代码简洁 - 移除所有兼容代码
🎯 架构原则
"统一约定,不做兼容,保持简单"
- Pydantic模型贯穿全系统
- 单一数据流
- 明确的类型定义
- 清晰的职责划分
📊 代码质量
- ✅ 无Linter错误
- ✅ 类型安全
- ✅ 所有测试通过
- ✅ 代码简洁清晰
版本: v3.3
状态: ✅ 完成并通过测试
下一步: 根据业务需求调整function_score权重