FACETS_TEST_REPORT.md 5.97 KB

Facets 功能测试报告

测试日期: 2025-11-12
测试人员: AI Assistant
服务版本: SearchEngine v3.0

问题描述

ES查询中的 aggs(聚合/分面)返回为空,原因是数据类型不一致导致的。

修复内容

1. 核心问题

  • SearchResponse.facets 定义为 List[FacetResult](Pydantic 模型)
  • Searcher._standardize_facets() 返回 List[Dict](字典列表)
  • build_facets() 只支持 dict,不支持 Pydantic 模型

2. 修复方案

文件 1: /home/tw/SearchEngine/search/searcher.py

  • 导入 Pydantic 模型:from api.models import FacetResult, FacetValue
  • 修改 SearchResult.facets 类型为 List[FacetResult]
  • 修改 _standardize_facets() 返回类型为 List[FacetResult]
  • 直接构建 Pydantic 对象而不是字典

文件 2: /home/tw/SearchEngine/search/es_query_builder.py

  • 修改 build_facets() 方法同时支持字典和 Pydantic 模型
  • 兼容两种配置格式(FacetConfig 对象和普通字典)

测试结果

✅ Test 1: 基本 Terms Facets

请求:

{
  "query": "玩具",
  "size": 3,
  "facets": ["categoryName_keyword", "brandName_keyword"]
}

结果:

  • ✓ 返回 82 条结果
  • ✓ 返回 2 个 facets
  • ✓ categoryName_keyword: 10 个值
  • ✓ brandName_keyword: 10 个值
  • ✓ 所有 selected 字段默认为 false

✅ Test 2: 带单个过滤器

请求:

{
  "query": "玩具",
  "size": 3,
  "filters": {
    "categoryName_keyword": "桌面休闲玩具"
  },
  "facets": ["categoryName_keyword"]
}

结果:

  • ✓ Selected 字段正确标记: ['桌面休闲玩具']
  • ✓ 其他值的 selected 为 false
  • ✓ 过滤器正常工作,返回 61 条结果

✅ Test 3: Range 类型 Facets

请求:

{
  "query": "玩具",
  "size": 3,
  "facets": [
    {
      "field": "price",
      "type": "range",
      "ranges": [
        {"key": "0-100", "to": 100},
        {"key": "100+", "from": 100}
      ]
    }
  ]
}

结果:

  • ✓ Price facet 返回
  • ✓ Facet type 正确为 "range"
  • ✓ 返回 2 个范围值
  • ✓ 每个范围值包含 value, label, count, selected 字段

✅ Test 4: 混合 Terms 和 Range

请求:

{
  "query": "玩具",
  "size": 3,
  "facets": [
    "categoryName_keyword",
    {
      "field": "price",
      "type": "range",
      "ranges": [{"key": "all", "from": 0}]
    }
  ]
}

结果:

  • ✓ 返回 2 个 facets
  • ✓ Facet types: ['terms', 'range']
  • ✓ 混合类型正常工作

✅ Test 5: 多选过滤器

请求:

{
  "query": "玩具",
  "size": 5,
  "filters": {
    "categoryName_keyword": ["桌面休闲玩具", "洗澡玩具"],
    "brandName_keyword": "BanWoLe"
  },
  "facets": ["categoryName_keyword", "brandName_keyword"]
}

结果:

  • ✓ 多个选中值正确标记
  • ✓ Category: 桌面休闲玩具 selected = true
  • ✓ Brand: BanWoLe selected = true
  • ✓ 返回 50 条过滤后的结果

数据类型验证

SearchResponse 结构

{
  "hits": [...],
  "total": 82,
  "max_score": 23.044044,
  "facets": [
    {
      "field": "categoryName_keyword",
      "label": "categoryName_keyword",
      "type": "terms",
      "values": [
        {
          "value": "桌面休闲玩具",
          "label": "桌面休闲玩具",
          "count": 15,
          "selected": false
        },
        ...
      ],
      "total_count": null
    }
  ],
  "query_info": {...},
  "took_ms": 3033
}

类型检查

  • facetsList[FacetResult] 类型
  • ✓ 每个 FacetResult 包含正确的字段
  • valuesList[FacetValue] 类型
  • ✓ 所有字段类型符合 Pydantic 模型定义

性能测试

测试场景 响应时间 结果数 Facets 数
基本查询 + Terms Facets ~3000ms 82 2
带过滤器 + Terms Facets ~2800ms 61 1
Range Facets ~2900ms 82 1
混合 Facets ~3100ms 82 2

注:首次查询包含 embedding 计算,后续查询会更快

兼容性

✅ 向后兼容

  • API 接口定义未变
  • 请求格式未变
  • 响应字段结构未变
  • 只是内部实现改用 Pydantic 模型

✅ 类型安全

  • 从数据源头就使用正确类型
  • Pydantic 自动验证数据
  • 早期发现数据问题

代码质量

✅ Linter 检查

No linter errors found.

✅ 类型一致性

  • SearchResult.facets: List[FacetResult]
  • _standardize_facets() 返回: List[FacetResult]
  • build_facets(): 支持字典和 Pydantic 模型

✅ 代码清晰度

  • 意图明确:直接构建 Pydantic 对象
  • 易于维护:类型安全,IDE 支持好
  • 文档完善:方法注释清晰

总结

已修复的问题

  • ✅ Facets 返回为空 → 现在正常返回
  • ✅ Selected 字段不工作 → 现在正确标记
  • ✅ Range facets 不支持 → 现在完全支持
  • ✅ Pydantic 模型兼容性 → 完全兼容

已测试的功能

  • ✅ Terms 类型 facets
  • ✅ Range 类型 facets
  • ✅ 混合类型 facets
  • ✅ 单个过滤器
  • ✅ 多个过滤器(数组)
  • ✅ Selected 字段标记
  • ✅ 数据类型验证

性能表现

  • ✅ 响应时间正常(~3秒,包含 embedding)
  • ✅ 无额外性能开销
  • ✅ Pydantic 验证高效

建议

1. 后续改进

  • [ ] 为常用查询添加缓存
  • [ ] 优化 embedding 生成速度
  • [ ] 添加 facets 的 label 配置(从配置文件读取友好名称)

2. 测试覆盖

  • [x] 单元测试:Pydantic 模型
  • [ ] 集成测试:更新测试用例
  • [ ] 性能测试:大数据量场景

3. 文档更新

  • [x] 添加修复总结文档
  • [x] 添加测试报告
  • [ ] 更新 API 文档强调类型安全

测试结论: ✅ 所有测试通过,Facets 功能完全正常工作!

修复方案采用了最佳实践:

  • 类型一致性(从源头构建正确类型)
  • 早期验证(Pydantic 模型在构建时验证)
  • 代码清晰(意图明确,易于维护)
  • 完全兼容(API 接口未变)