Blame view

api/models.py 14.5 KB
be52af70   tangwang   first commit
1
2
3
4
  """
  Request and response models for the API.
  """
  
6aa246be   tangwang   问题:Pydantic 应该能自动...
5
6
7
8
9
  from pydantic import BaseModel, Field, field_validator
  from typing import List, Dict, Any, Optional, Union, Literal
  
  
  class RangeFilter(BaseModel):
ff5325fa   tangwang   修复:直接在 Searcher 层...
10
11
12
13
14
      """范围过滤器(支持数值和日期时间字符串)"""
      gte: Optional[Union[float, str]] = Field(None, description="大于等于 (>=)。数值或ISO日期时间字符串")
      gt: Optional[Union[float, str]] = Field(None, description="大于 (>)。数值或ISO日期时间字符串")
      lte: Optional[Union[float, str]] = Field(None, description="小于等于 (<=)。数值或ISO日期时间字符串")
      lt: Optional[Union[float, str]] = Field(None, description="小于 (<)。数值或ISO日期时间字符串")
6aa246be   tangwang   问题:Pydantic 应该能自动...
15
16
17
18
19
20
21
22
23
24
25
      
      def model_post_init(self, __context):
          """确保至少指定一个边界值"""
          if not any([self.gte, self.gt, self.lte, self.lt]):
              raise ValueError('至少需要指定一个范围边界(gte, gt, lte, lt)')
      
      class Config:
          json_schema_extra = {
              "examples": [
                  {"gte": 50, "lte": 200},
                  {"gt": 100},
ff5325fa   tangwang   修复:直接在 Searcher 层...
26
27
                  {"lt": 50},
                  {"gte": "2023-01-01T00:00:00Z"}
6aa246be   tangwang   问题:Pydantic 应该能自动...
28
29
30
31
32
33
34
35
36
              ]
          }
  
  
  class FacetConfig(BaseModel):
      """分面配置(简化版)"""
      field: str = Field(..., description="分面字段名")
      size: int = Field(10, ge=1, le=100, description="返回的分面值数量")
      type: Literal["terms", "range"] = Field("terms", description="分面类型")
9a9b9ec5   tangwang   1. facet disjunctive
37
      disjunctive: bool = Field(
7ac1534b   tangwang   disjunctive 修改默认值...
38
          False,
c581becd   tangwang   feat: 实现 Multi-Se...
39
40
          description="是否支持多选(disjunctive faceting)。启用后,选中该分面的过滤器时,仍会显示其他可选项"
      )
6aa246be   tangwang   问题:Pydantic 应该能自动...
41
42
43
44
45
46
47
48
49
      ranges: Optional[List[Dict[str, Any]]] = Field(
          None,
          description="范围分面的范围定义(仅当 type='range' 时需要)"
      )
      
      class Config:
          json_schema_extra = {
              "examples": [
                  {
cadc77b6   tangwang   索引字段名、变量名、API数据结构...
50
                      "field": "category.keyword",
6aa246be   tangwang   问题:Pydantic 应该能自动...
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
                      "size": 15,
                      "type": "terms"
                  },
                  {
                      "field": "price",
                      "size": 4,
                      "type": "range",
                      "ranges": [
                          {"key": "0-50", "to": 50},
                          {"key": "50-100", "from": 50, "to": 100},
                          {"key": "100-200", "from": 100, "to": 200},
                          {"key": "200+", "from": 200}
                      ]
                  }
              ]
          }
be52af70   tangwang   first commit
67
68
69
  
  
  class SearchRequest(BaseModel):
6aa246be   tangwang   问题:Pydantic 应该能自动...
70
71
72
73
      """搜索请求模型(重构版)"""
      
      # 基础搜索参数
      query: str = Field(..., description="搜索查询字符串,支持布尔表达式(AND, OR, RANK, ANDNOT)")
e7ad2b4a   tangwang   测试页面分页配置
74
      size: int = Field(10, ge=1, le=1000, description="返回结果数量")
6aa246be   tangwang   问题:Pydantic 应该能自动...
75
      from_: int = Field(0, ge=0, alias="from", description="分页偏移量")
577ec972   tangwang   返回给前端的字段、格式适配。主要包...
76
77
78
79
      language: Literal["zh", "en"] = Field(
          "zh",
          description="响应语言:'zh'(中文)或 'en'(英文),用于选择 title/description/vendor 等多语言字段"
      )
6aa246be   tangwang   问题:Pydantic 应该能自动...
80
81
      
      # 过滤器 - 精确匹配和多值匹配
f7d3cf70   tangwang   更新文档
82
      filters: Optional[Dict[str, Union[str, int, bool, List[Union[str, int]], Dict[str, Any], List[Dict[str, Any]]]]] = Field(
6aa246be   tangwang   问题:Pydantic 应该能自动...
83
          None,
f7d3cf70   tangwang   更新文档
84
          description="精确匹配过滤器。单值表示精确匹配,数组表示 OR 匹配(匹配任意一个值)。支持 specifications 嵌套过滤:{\"specifications\": {\"name\": \"color\", \"value\": \"green\"}} 或 [{\"name\": \"color\", \"value\": \"green\"}, ...]",
6aa246be   tangwang   问题:Pydantic 应该能自动...
85
86
87
          json_schema_extra={
              "examples": [
                  {
f7d3cf70   tangwang   更新文档
88
                      "category_name": ["手机", "电子产品"],
d7d48f52   tangwang   改动(mapping + 灌入结构)
89
                      "vendor.zh.keyword": "奇乐",
f7d3cf70   tangwang   更新文档
90
91
92
93
94
95
96
                      "specifications": {"name": "颜色", "value": "白色"}
                  },
                  {
                      "specifications": [
                          {"name": "颜色", "value": "白色"},
                          {"name": "尺寸", "value": "256GB"}
                      ]
6aa246be   tangwang   问题:Pydantic 应该能自动...
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
                  }
              ]
          }
      )
      
      # 范围过滤器 - 数值范围
      range_filters: Optional[Dict[str, RangeFilter]] = Field(
          None,
          description="数值范围过滤器。支持 gte, gt, lte, lt 操作符",
          json_schema_extra={
              "examples": [
                  {
                      "price": {"gte": 50, "lte": 200},
                      "days_since_last_update": {"lte": 30}
                  }
              ]
          }
      )
      
      # 排序
13320ac6   tangwang   分面接口修改:
117
118
      sort_by: Optional[str] = Field(None, description="排序字段名。支持:'price'(价格,自动根据sort_order选择min_price或max_price)、'sales'(销量)、'create_time'(创建时间)、'update_time'(更新时间)")
      sort_order: Optional[str] = Field("desc", description="排序方向: 'asc'(升序)或 'desc'(降序)。注意:price+asc=价格从低到高,price+desc=价格从高到低")
6aa246be   tangwang   问题:Pydantic 应该能自动...
119
      
13320ac6   tangwang   分面接口修改:
120
121
      # 分面搜索
      facets: Optional[List[FacetConfig]] = Field(
6aa246be   tangwang   问题:Pydantic 应该能自动...
122
          None,
13320ac6   tangwang   分面接口修改:
123
          description="分面配置对象列表。支持 specifications 分面:field=\"specifications\"(所有规格名称)或 field=\"specifications.color\"(指定规格名称)",
6aa246be   tangwang   问题:Pydantic 应该能自动...
124
125
          json_schema_extra={
              "examples": [
6aa246be   tangwang   问题:Pydantic 应该能自动...
126
                  [
13320ac6   tangwang   分面接口修改:
127
128
129
130
131
132
133
                      {"field": "category1_name", "size": 15, "type": "terms"},
                      {"field": "category2_name", "size": 10, "type": "terms"},
                      {"field": "specifications.color", "size": 20, "type": "terms"},
                      {"field": "specifications.size", "size": 15, "type": "terms"}
                  ],
                  [
                      {"field": "category1_name", "size": 15, "type": "terms"},
6aa246be   tangwang   问题:Pydantic 应该能自动...
134
                      {
f7d3cf70   tangwang   更新文档
135
                          "field": "min_price",
6aa246be   tangwang   问题:Pydantic 应该能自动...
136
137
138
                          "type": "range",
                          "ranges": [
                              {"key": "0-50", "to": 50},
13320ac6   tangwang   分面接口修改:
139
140
141
                              {"key": "50-100", "from": 50, "to": 100},
                              {"key": "100-200", "from": 100, "to": 200},
                              {"key": "200+", "from": 200}
6aa246be   tangwang   问题:Pydantic 应该能自动...
142
                          ]
f7d3cf70   tangwang   更新文档
143
                      },
13320ac6   tangwang   分面接口修改:
144
                      {"field": "specifications", "size": 10, "type": "terms"}
6aa246be   tangwang   问题:Pydantic 应该能自动...
145
146
147
148
149
150
151
152
153
                  ]
              ]
          }
      )
      
      # 高级选项
      min_score: Optional[float] = Field(None, ge=0, description="最小相关性分数阈值")
      highlight: bool = Field(False, description="是否高亮搜索关键词(暂不实现)")
      debug: bool = Field(False, description="是否返回调试信息")
506c39b7   tangwang   feat(search): 统一重...
154
155
156
157
      ai_search: bool = Field(
          False,
          description="是否开启 AI 搜索(调用本地重排服务对 ES 结果进行二次排序)"
      )
6aa246be   tangwang   问题:Pydantic 应该能自动...
158
      
ca91352a   tangwang   更新文档
159
      # SKU筛选参数
a3a5d41b   tangwang   (sku_filter_dimen...
160
      sku_filter_dimension: Optional[List[str]] = Field(
001b4889   tangwang   1. docs
161
          ["option1"],
a3a5d41b   tangwang   (sku_filter_dimen...
162
163
164
165
166
167
          description=(
              "子SKU筛选维度(店铺配置),为字符串列表。"
              "指定后,每个SPU下的SKU将按这些维度的组合进行分组,每个维度组合只保留一个SKU返回。"
              "例如:['color'] 表示按颜色分组,每种颜色选一款;['color', 'size'] 表示按颜色+尺码组合分组。"
              "支持的值:'option1'、'option2'、'option3' 或选项名称(如 'color'、'size',将通过 option1_name/2_name/3_name 匹配)。"
          )
ca91352a   tangwang   更新文档
168
169
      )
      
6aa246be   tangwang   问题:Pydantic 应该能自动...
170
171
172
      # 个性化参数(预留)
      user_id: Optional[str] = Field(None, description="用户ID,用于个性化搜索和推荐")
      session_id: Optional[str] = Field(None, description="会话ID,用于搜索分析")
be52af70   tangwang   first commit
173
174
175
  
  
  class ImageSearchRequest(BaseModel):
6aa246be   tangwang   问题:Pydantic 应该能自动...
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
      """图片搜索请求模型"""
      image_url: str = Field(..., description="查询图片的 URL")
      size: int = Field(10, ge=1, le=100, description="返回结果数量")
      filters: Optional[Dict[str, Union[str, int, bool, List[Union[str, int]]]]] = None
      range_filters: Optional[Dict[str, RangeFilter]] = None
  
  
  class SearchSuggestRequest(BaseModel):
      """搜索建议请求模型(框架,暂不实现)"""
      query: str = Field(..., min_length=1, description="搜索查询字符串")
      size: int = Field(5, ge=1, le=20, description="返回建议数量")
      types: List[Literal["query", "product", "category", "brand"]] = Field(
          ["query"],
          description="建议类型:query(查询建议), product(商品建议), category(类目建议), brand(品牌建议)"
      )
  
  
  class FacetValue(BaseModel):
      """分面值"""
      value: Union[str, int, float] = Field(..., description="分面值")
      label: Optional[str] = Field(None, description="显示标签(如果与 value 不同)")
      count: int = Field(..., description="匹配的文档数量")
      selected: bool = Field(False, description="是否已选中(当前过滤器中)")
  
  
  class FacetResult(BaseModel):
      """分面结果(标准化格式)"""
      field: str = Field(..., description="字段名")
      label: str = Field(..., description="分面显示名称")
      type: Literal["terms", "range"] = Field(..., description="分面类型")
      values: List[FacetValue] = Field(..., description="分面值列表")
      total_count: Optional[int] = Field(None, description="该字段的总文档数")
be52af70   tangwang   first commit
208
209
  
  
cadc77b6   tangwang   索引字段名、变量名、API数据结构...
210
211
212
  class SkuResult(BaseModel):
      """SKU 结果"""
      sku_id: str = Field(..., description="SKU ID")
577ec972   tangwang   返回给前端的字段、格式适配。主要包...
213
      # 与 ES nested skus 结构对齐
1f6d15fa   tangwang   重构:SPU级别索引、统一索引架构...
214
215
      price: Optional[float] = Field(None, description="价格")
      compare_at_price: Optional[float] = Field(None, description="原价")
577ec972   tangwang   返回给前端的字段、格式适配。主要包...
216
      sku_code: Optional[str] = Field(None, description="SKU编码")
1f6d15fa   tangwang   重构:SPU级别索引、统一索引架构...
217
      stock: int = Field(0, description="库存数量")
577ec972   tangwang   返回给前端的字段、格式适配。主要包...
218
219
220
221
222
223
      weight: Optional[float] = Field(None, description="重量")
      weight_unit: Optional[str] = Field(None, description="重量单位")
      option1_value: Optional[str] = Field(None, description="选项1取值(如颜色)")
      option2_value: Optional[str] = Field(None, description="选项2取值(如尺码)")
      option3_value: Optional[str] = Field(None, description="选项3取值")
      image_src: Optional[str] = Field(None, description="SKU图片地址")
1f6d15fa   tangwang   重构:SPU级别索引、统一索引架构...
224
225
  
  
cadc77b6   tangwang   索引字段名、变量名、API数据结构...
226
227
228
  class SpuResult(BaseModel):
      """SPU 搜索结果"""
      spu_id: str = Field(..., description="SPU ID")
1f6d15fa   tangwang   重构:SPU级别索引、统一索引架构...
229
      title: Optional[str] = Field(None, description="商品标题")
577ec972   tangwang   返回给前端的字段、格式适配。主要包...
230
      brief: Optional[str] = Field(None, description="商品短描述")
1f6d15fa   tangwang   重构:SPU级别索引、统一索引架构...
231
232
233
      handle: Optional[str] = Field(None, description="商品handle")
      description: Optional[str] = Field(None, description="商品描述")
      vendor: Optional[str] = Field(None, description="供应商/品牌")
577ec972   tangwang   返回给前端的字段、格式适配。主要包...
234
235
236
237
238
239
240
241
      category: Optional[str] = Field(None, description="类目(兼容字段,等同于category_name)")
      category_path: Optional[str] = Field(None, description="类目路径(多级,用于面包屑)")
      category_name: Optional[str] = Field(None, description="类目名称(展示用)")
      category_id: Optional[str] = Field(None, description="类目ID")
      category_level: Optional[int] = Field(None, description="类目层级")
      category1_name: Optional[str] = Field(None, description="一级类目名称")
      category2_name: Optional[str] = Field(None, description="二级类目名称")
      category3_name: Optional[str] = Field(None, description="三级类目名称")
bf89b597   tangwang   feat(search): ada...
242
      tags: Optional[List[str]] = Field(None, description="标签列表")
1f6d15fa   tangwang   重构:SPU级别索引、统一索引架构...
243
244
245
246
247
      price: Optional[float] = Field(None, description="价格(min_price)")
      compare_at_price: Optional[float] = Field(None, description="原价")
      currency: str = Field("USD", description="货币单位")
      image_url: Optional[str] = Field(None, description="主图URL")
      in_stock: bool = Field(True, description="是否有库存")
577ec972   tangwang   返回给前端的字段、格式适配。主要包...
248
249
250
251
252
253
254
255
256
257
258
259
      # SKU 扁平化信息
      sku_prices: Optional[List[float]] = Field(None, description="所有SKU价格列表")
      sku_weights: Optional[List[int]] = Field(None, description="所有SKU重量列表")
      sku_weight_units: Optional[List[str]] = Field(None, description="所有SKU重量单位列表")
      total_inventory: Optional[int] = Field(None, description="总库存")
      option1_name: Optional[str] = Field(None, description="选项1名称(如颜色)")
      option2_name: Optional[str] = Field(None, description="选项2名称(如尺码)")
      option3_name: Optional[str] = Field(None, description="选项3名称")
      specifications: Optional[List[Dict[str, Any]]] = Field(
          None,
          description="规格列表(与 ES specifications 字段对应)"
      )
cadc77b6   tangwang   索引字段名、变量名、API数据结构...
260
      skus: List[SkuResult] = Field(default_factory=list, description="SKU列表")
f0577ce4   tangwang   fix last up
261
      relevance_score: float = Field(..., ge=0.0, description="相关性分数(ES原始分数)")
1f6d15fa   tangwang   重构:SPU级别索引、统一索引架构...
262
263
  
  
be52af70   tangwang   first commit
264
  class SearchResponse(BaseModel):
1f6d15fa   tangwang   重构:SPU级别索引、统一索引架构...
265
      """搜索响应模型(外部友好格式)"""
6aa246be   tangwang   问题:Pydantic 应该能自动...
266
267
      
      # 核心结果
cadc77b6   tangwang   索引字段名、变量名、API数据结构...
268
      results: List[SpuResult] = Field(..., description="搜索结果列表")
6aa246be   tangwang   问题:Pydantic 应该能自动...
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
      total: int = Field(..., description="匹配的总文档数")
      max_score: float = Field(..., description="最高相关性分数")
      
      # 分面搜索结果(标准化格式)
      facets: Optional[List[FacetResult]] = Field(
          None,
          description="分面统计结果(标准化格式)"
      )
      
      # 查询信息
      query_info: Dict[str, Any] = Field(
          default_factory=dict,
          description="查询处理信息(原始查询、改写、语言检测、翻译等)"
      )
      
1f6d15fa   tangwang   重构:SPU级别索引、统一索引架构...
284
285
286
      # 推荐与建议
      suggestions: List[str] = Field(default_factory=list, description="搜索建议")
      related_searches: List[str] = Field(default_factory=list, description="相关搜索")
6aa246be   tangwang   问题:Pydantic 应该能自动...
287
288
289
290
291
292
293
294
295
296
297
298
299
300
      
      # 性能指标
      took_ms: int = Field(..., description="搜索总耗时(毫秒)")
      performance_info: Optional[Dict[str, Any]] = Field(None, description="详细性能信息")
      
      # 调试信息
      debug_info: Optional[Dict[str, Any]] = Field(None, description="调试信息(仅当 debug=True)")
  
  
  class SearchSuggestResponse(BaseModel):
      """搜索建议响应模型(框架,暂不实现)"""
      query: str = Field(..., description="原始查询")
      suggestions: List[Dict[str, Any]] = Field(..., description="建议列表")
      took_ms: int = Field(..., description="耗时(毫秒)")
be52af70   tangwang   first commit
301
302
303
304
305
306
307
308
309
310
311
312
  
  
  class DocumentResponse(BaseModel):
      """Single document response model."""
      id: str = Field(..., description="Document ID")
      source: Dict[str, Any] = Field(..., description="Document source")
  
  
  class HealthResponse(BaseModel):
      """Health check response model."""
      status: str = Field(..., description="Service status")
      elasticsearch: str = Field(..., description="Elasticsearch status")
be52af70   tangwang   first commit
313
314
315
316
317
318
  
  
  class ErrorResponse(BaseModel):
      """Error response model."""
      error: str = Field(..., description="Error message")
      detail: Optional[str] = Field(None, description="Detailed error information")