# ES查询结构重构完成报告 **完成日期**: 2025-11-12 **核心原则**: 统一约定,保持简单,类型安全 --- ## 问题回顾 ### 原始问题 1. **Facets返回为空** - 已修复 2. **前端listing time筛选失败** - 已修复 3. **Filter不作用于KNN查询** - 已修复(核心问题) ### 根本原因 **KNN和query平级,导致filter只作用于query,KNN召回的结果没有被过滤。** 原结构(错误): ```json { "query": { "bool": { "must": [{"multi_match": {...}}], "filter": [...] // 只作用于multi_match } }, "knn": {...} // 与query平级,filter不作用于它 } ``` --- ## 解决方案 ### 方案C:Function Score + Bool Should(已实施) 新结构(正确): ```json { "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" } } } ``` ### 关键改进 1. **Filter统一作用** - 外层bool.filter同时作用于文本和KNN 2. **灵活召回** - 文本或KNN至少匹配一个(minimum_should_match=1) 3. **ES层打分** - function_score支持多种打分因子 4. **保留扩展性** - RerankEngine禁用但保留,未来可启用本地重排 --- ## 修改文件清单 ### 1. `/home/tw/SearchEngine/search/multilang_query_builder.py` **修改点**: - 重构 `build_multilang_query` 方法(156-210行) - 新增 `_build_score_functions` 方法(212-237行) **核心改动**: ```python # 构建内层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) - 更新文档说明 **关键代码**: ```python 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` **修改**: ```python 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 结构验证 ### 完整查询示例 ```json { "size": 5, "from": 0, "query": { "function_score": { "query": { "bool": { "must": [ { "bool": { "should": [ { "bool": { "should": [ {"multi_match": {"query": "玩具", "fields": [...]}} ], "minimum_should_match": 1 } }, { "knn": { "field": "name_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" } } } ``` ### 结构分析 **三层嵌套**: 1. **最外层**:`function_score` - 支持额外打分因子 2. **外层bool**:包含`must`和`filter` - filter作用于所有查询 3. **内层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扩展 可添加更多打分因子: ```yaml 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. 测试覆盖 - 添加集成测试 - 性能基准测试 - 边界情况测试 --- ## 总结 ### ✅ 核心成就 1. **修复Filter问题** - Filter现在同时作用于文本和KNN 2. **统一约定** - 全系统使用Pydantic模型,不做兼容 3. **优化打分** - 使用ES function_score,性能更好 4. **命名规范** - RerankEngine语义更清晰 5. **代码简洁** - 移除所有兼容代码 ### 🎯 架构原则 **"统一约定,不做兼容,保持简单"** - Pydantic模型贯穿全系统 - 单一数据流 - 明确的类型定义 - 清晰的职责划分 ### 📊 代码质量 - ✅ 无Linter错误 - ✅ 类型安全 - ✅ 所有测试通过 - ✅ 代码简洁清晰 --- **版本**: v3.3 **状态**: ✅ 完成并通过测试 **下一步**: 根据业务需求调整function_score权重