Commit 001b488959c30296277dd112463ad004d81d4e3d
1 parent
b1bafbbc
1. docs
2. 设置sku_filter_dimension参数的默认值为option1
Showing
6 changed files
with
855 additions
and
135 deletions
Show diff stats
api/models.py
| @@ -154,7 +154,7 @@ class SearchRequest(BaseModel): | @@ -154,7 +154,7 @@ class SearchRequest(BaseModel): | ||
| 154 | 154 | ||
| 155 | # SKU筛选参数 | 155 | # SKU筛选参数 |
| 156 | sku_filter_dimension: Optional[List[str]] = Field( | 156 | sku_filter_dimension: Optional[List[str]] = Field( |
| 157 | - None, | 157 | + ["option1"], |
| 158 | description=( | 158 | description=( |
| 159 | "子SKU筛选维度(店铺配置),为字符串列表。" | 159 | "子SKU筛选维度(店铺配置),为字符串列表。" |
| 160 | "指定后,每个SPU下的SKU将按这些维度的组合进行分组,每个维度组合只保留一个SKU返回。" | 160 | "指定后,每个SPU下的SKU将按这些维度的组合进行分组,每个维度组合只保留一个SKU返回。" |
docs/UA-站内全站埋点数据结构定义.md deleted
| @@ -1,105 +0,0 @@ | @@ -1,105 +0,0 @@ | ||
| 1 | -# UA 站内全站埋点数据结构定义(推荐/搜索/画像统一口径) | ||
| 2 | - | ||
| 3 | -本文档用于定义站内全量行为采集(基于“全站埋点采集”,可落地到神策 JS SDK 二次开发),覆盖推荐、搜索、画像、转化分析等核心场景。 | ||
| 4 | - | ||
| 5 | -## 1. 目标与约束 | ||
| 6 | - | ||
| 7 | -- **目标**:用一套统一事件模型覆盖全站行为(Pageview / Exposure / Click / View Product / Add to Cart / Purchase / Search&Filtering / Cart&Checkout)。 | ||
| 8 | -- **约束**:事件需要能在“推荐/搜索请求”与“曝光-点击-加购”等链路上串联;同时需要能表达购物车/结账阶段的状态快照,用于搭配、互补、凑单、替代品推荐。 | ||
| 9 | - | ||
| 10 | -## 2. 关键设计原则 | ||
| 11 | - | ||
| 12 | -- **统一事件骨架**:所有事件共享同一套顶层字段(用户标识、时间、页面、trace、设备、扩展字段),业务差异放到 `event`(oneof)里。 | ||
| 13 | -- **可串联**:用 `trace_id` 串联一次搜索/推荐请求与后续点击、加购、详情浏览等行为;用 `session_id` 串联同一次访问会话。 | ||
| 14 | -- **可回放/可训练**:曝光事件尽量携带曝光商品列表、位置、模块;购物车/结账事件携带快照(items + 国家/币种/价格等)。 | ||
| 15 | -- **停留时长口径一致**:对 `page_view`/`view_item` 统一给出 `dwell_time_ms` 的采集口径与上报方式。 | ||
| 16 | - | ||
| 17 | -## 3. 顶层事件模型(概念) | ||
| 18 | - | ||
| 19 | -每条 UA 事件为一个 `UserActionEvent`,包含: | ||
| 20 | - | ||
| 21 | -- **identity**:tenant / user / anonymous / device / cookie | ||
| 22 | -- **time**:事件发生时间(毫秒时间戳) | ||
| 23 | -- **page**:页面类型、URL、来源、模块位置信息 | ||
| 24 | -- **trace**:`trace_id`、`session_id`、实验信息 | ||
| 25 | -- **device**:OS / UA / viewport 等 | ||
| 26 | -- **event**:具体业务事件(page_view、exposure、click、view_item、search、filter、cart、checkout、purchase…) | ||
| 27 | -- **extra**:业务扩展 KV | ||
| 28 | - | ||
| 29 | -> proto 草案见 `docs/proto/user_action.proto`(本次补充文件)。 | ||
| 30 | - | ||
| 31 | -## 4. 核心字段说明 | ||
| 32 | - | ||
| 33 | -### 4.1 标识体系(强烈建议) | ||
| 34 | - | ||
| 35 | -- **tenant_id**:租户/店铺/商家维度的隔离主键。 | ||
| 36 | -- **user_id**:登录用户 ID(未登录可为空)。 | ||
| 37 | -- **anonymous_id**:匿名用户 ID(建议 Cookie 级别稳定,登录后可与 user_id 做归因合并)。 | ||
| 38 | -- **device_id**:设备指纹(可选;Web 端更推荐 anonymous_id + cookie_id)。 | ||
| 39 | -- **cookie_id**:首次访问生成的持久化 Cookie ID(与 anonymous_id 可以一致,也可分开)。 | ||
| 40 | - | ||
| 41 | -### 4.2 trace_id(最重要) | ||
| 42 | - | ||
| 43 | -**定义**:串联一次“搜索/推荐请求(曝光)”与后续点击、详情、加购等行为的请求级会话 ID,同时用于串联内部技术模块(召回/排序/重排等子模块日志)。 | ||
| 44 | - | ||
| 45 | -**规则建议**: | ||
| 46 | - | ||
| 47 | -- **新搜索/新推荐刷新**:生成新的 `trace_id`。 | ||
| 48 | -- **搜索翻页**:沿用原 `trace_id`,用 `page_number` 区分页(见 `SearchEvent`)。 | ||
| 49 | -- **曝光 → 点击/详情/加购**:沿用产生曝光的 `trace_id`,用于表达“该转化源自哪一次曝光/哪一次请求”。 | ||
| 50 | -- **购买/支付**:如果链路被购物车长时间阻隔,串联成本高,可不强制要求延续 `trace_id`;但建议在 `PurchaseEvent.attribution_trace_id` 上尽力保留“主要来源”的 trace(若可得)。 | ||
| 51 | - | ||
| 52 | -### 4.3 停留时长(dwell_time_ms) | ||
| 53 | - | ||
| 54 | -**适用事件**: | ||
| 55 | - | ||
| 56 | -- `PageViewEvent`:用户在该页面的停留时长 | ||
| 57 | -- `ViewItemEvent`:用户在详情页/商品内容上的停留时长(可与 pageview 相同,也可更精细) | ||
| 58 | - | ||
| 59 | -**采集口径建议(Web)**: | ||
| 60 | - | ||
| 61 | -- 在 `pagehide` / `beforeunload` / SPA 路由切换时计算当前页面停留:`now - enter_time`。 | ||
| 62 | -- 若页面进入后立即产生 `page_view`,则可在离开页面时补发一条 `page_view_end`(或在同一条事件里填充 `dwell_time_ms`,需要延迟上报)。 | ||
| 63 | -- 对详情页:可按“进入详情页到离开详情页”计算;若停留时长无法计算,允许不上报或置 0,但需要在文档/ETL 中区分“缺失 vs 真实 0”。 | ||
| 64 | - | ||
| 65 | -## 5. 必须覆盖的事件清单(推荐) | ||
| 66 | - | ||
| 67 | -- **Pageview** | ||
| 68 | - - 分类页、活动页、列表页、首页、店铺页、购物车页、结账页等 | ||
| 69 | - - *最好携带该页面曝光的商品列表*(见 `ExposureEvent`,或在 `PageViewEvent.exposed_items` 携带“本页首屏/本次渲染”曝光) | ||
| 70 | -- **Exposure / Click** | ||
| 71 | - - 列表/推荐位曝光与点击(必须包含 position / module_id / item) | ||
| 72 | -- **View product(详情页浏览)** | ||
| 73 | - - 包含 `dwell_time_ms` | ||
| 74 | -- **Add to cart** | ||
| 75 | - - 商品信息 + 数量 + 价格(若可得)+ 当前 cart 快照(若可得) | ||
| 76 | -- **Purchase** | ||
| 77 | - - 订单信息(order_id / currency / total)+ 购买 items | ||
| 78 | -- **On-site Search & Filtering** | ||
| 79 | - - 搜索词、建议词、排序、筛选条件(结构化,避免仅 JSON 字符串) | ||
| 80 | - - 搜索结果曝光/点击建议归入 Exposure/Click,但必须携带 `search_context` | ||
| 81 | -- **Cart Logic / Checkout 状态** | ||
| 82 | - - `cart_snapshot`:购物车当前 items 及属性(qty、price、category、brand…) | ||
| 83 | - - `checkout`:shipping_country、step、payment_method(如可得) | ||
| 84 | - | ||
| 85 | -## 6. 落地建议(神策 JS SDK 二次开发) | ||
| 86 | - | ||
| 87 | -- **事件名**:建议统一用一个事件名(如 `ua_event`),将 `action_type`(主类型)+ `event_id`(子类型,可选)作为属性;或按神策习惯拆分事件名,但必须保证字段一致。 | ||
| 88 | -- **批量上报**:曝光事件可批量(一屏/一次渲染一次上报)以降低量级;点击/加购/购买等强转化事件单条实时上报。 | ||
| 89 | -- **字段扩展**:放入 `extra.debug_info`,严禁在顶层无限加字段造成版本失控。 | ||
| 90 | - | ||
| 91 | -## 7. proto 草案 | ||
| 92 | - | ||
| 93 | -见 `docs/proto/user_action.proto`。 | ||
| 94 | - | ||
| 95 | -### 7.1 枚举命名说明(避免 proto 枚举值冲突) | ||
| 96 | - | ||
| 97 | -为兼容较旧版本 `protoc`(枚举值采用 package 级作用域),本 proto 中枚举值都带前缀: | ||
| 98 | - | ||
| 99 | -- **ActionType**:`ACTION_TYPE_*`(如 `ACTION_TYPE_PAGEVIEW`) | ||
| 100 | -- **EventId**:`EVENT_ID_*`(如 `EVENT_ID_ADD_TO_CART`) | ||
| 101 | -- **PageType**:`PAGE_TYPE_*` | ||
| 102 | -- **SortType**:`SORT_TYPE_*` | ||
| 103 | -- **CheckoutStep**:`CHECKOUT_STEP_*` | ||
| 104 | - | ||
| 105 | - |
docs/blog/语义搜索.md
| @@ -41,7 +41,7 @@ | @@ -41,7 +41,7 @@ | ||
| 41 | 搜索“sports shoes”时,系统自动识别并强化关键购买决策维度:“运动类型(跑步/篮球)”、“适用场地(户外/室内)”、“减震技术”等,使结果更聚焦于用户真实意图。 | 41 | 搜索“sports shoes”时,系统自动识别并强化关键购买决策维度:“运动类型(跑步/篮球)”、“适用场地(户外/室内)”、“减震技术”等,使结果更聚焦于用户真实意图。 |
| 42 | 42 | ||
| 43 | 3. **跨语言自适应能力** | 43 | 3. **跨语言自适应能力** |
| 44 | - 采用先进的跨语言对齐模型,基于英语训练的系统无需重新训练即可理解泰语、越南语、西班牙语等查询,实测跨语言召回率提升40%,真正实现“一次训练,全球适用”。 | 44 | + 商家上传的商品可以是任何一种语言,用户可以用英语、中文、法语、意大利语、波兰语、西班牙语等30多种语言进行查询。 |
| 45 | 45 | ||
| 46 | **阶段二:语义重排——让业务目标融入排序决策** | 46 | **阶段二:语义重排——让业务目标融入排序决策** |
| 47 | 47 | ||
| @@ -69,16 +69,3 @@ | @@ -69,16 +69,3 @@ | ||
| 69 | 69 | ||
| 70 | ### 3. 混合查询精准解析:拥抱语言多样性 | 70 | ### 3. 混合查询精准解析:拥抱语言多样性 |
| 71 | 针对东南亚、拉美等市场的混合语言查询,系统不再依赖简单的词典翻译,而是理解语言混合背后的完整意图。搜索“裙 kasual wanita”能够准确理解这是“女士休闲裙”的需求,即使查询中包含本地语言词汇。 | 71 | 针对东南亚、拉美等市场的混合语言查询,系统不再依赖简单的词典翻译,而是理解语言混合背后的完整意图。搜索“裙 kasual wanita”能够准确理解这是“女士休闲裙”的需求,即使查询中包含本地语言词汇。 |
| 72 | - | ||
| 73 | -## 实测效果:数据驱动的价值证明 | ||
| 74 | - | ||
| 75 | -采用语义搜索技术的独立站客户已观察到显著改善: | ||
| 76 | - | ||
| 77 | -- **搜索转化率提升**:平均提升35%,长尾查询提升尤为明显 | ||
| 78 | -- **零结果率下降**:从行业平均12%降至2%以下 | ||
| 79 | -- **运营效率飞跃**:同义词维护工作量减少80%,可专注于更高价值的优化 | ||
| 80 | -- **跨语言覆盖扩展**:支持语言从5种扩展到30+种,无需为每种语言单独训练模型 | ||
| 81 | -- **新品曝光加速**:新上架商品在首周获得搜索曝光的概率提升3倍 | ||
| 82 | - | ||
| 83 | ---- | ||
| 84 | - |
| @@ -0,0 +1,464 @@ | @@ -0,0 +1,464 @@ | ||
| 1 | +# 对话式智能导购设计(服饰鞋帽独立站) | ||
| 2 | + | ||
| 3 | +## 1. 核心场景与用户旅程 | ||
| 4 | + | ||
| 5 | +### 1.1 典型对话流程示例 | ||
| 6 | + | ||
| 7 | +**场景 A:新用户首次咨询(需要完善档案)** | ||
| 8 | +``` | ||
| 9 | +用户: "我想买一件适合夏天的T恤" | ||
| 10 | +导购: "好的,我来帮您找。请问您平时穿什么尺码?" | ||
| 11 | +用户: "M码" | ||
| 12 | +导购: "好的。您比较喜欢什么颜色?我们这里有白色、黑色、蓝色、灰色等" | ||
| 13 | +用户: "白色或浅色都可以" | ||
| 14 | +导购: "了解。您希望是休闲款还是运动款?" | ||
| 15 | +用户: "休闲的" | ||
| 16 | +导购: "好的,我为您找到了几款,价格在50-150元之间,您看看..." | ||
| 17 | +[展示结果] | ||
| 18 | +``` | ||
| 19 | + | ||
| 20 | +**场景 B:老用户(已有档案,快速推荐)** | ||
| 21 | +``` | ||
| 22 | +用户: "帮我找一条牛仔裤" | ||
| 23 | +导购: "好的,根据您的尺码偏好(30码),我为您推荐..." | ||
| 24 | +[直接展示结果,如不满意再细化] | ||
| 25 | +``` | ||
| 26 | + | ||
| 27 | +**场景 C:模糊需求逐步明确** | ||
| 28 | +``` | ||
| 29 | +用户: "我想买件外套" | ||
| 30 | +导购: "好的,请问是用于什么场合?日常通勤、运动、还是正式场合?" | ||
| 31 | +用户: "日常通勤" | ||
| 32 | +导购: "了解。您希望是薄款还是厚款?现在这个季节..." | ||
| 33 | +用户: "薄款就行" | ||
| 34 | +导购: "好的,我为您推荐几款适合通勤的薄外套..." | ||
| 35 | +``` | ||
| 36 | + | ||
| 37 | +### 1.2 核心价值点 | ||
| 38 | + | ||
| 39 | +- **降低搜索门槛**:用户用自然语言表达,无需熟悉筛选器 | ||
| 40 | +- **个性化合身推荐**:通过尺码/合身偏好,减少退货率 | ||
| 41 | +- **提升转化**:多轮对话收集偏好,推荐更精准 | ||
| 42 | +- **完善用户档案**:每次对话积累偏好,后续推荐更准 | ||
| 43 | + | ||
| 44 | +--- | ||
| 45 | + | ||
| 46 | +## 2. 技术架构 | ||
| 47 | + | ||
| 48 | +### 2.1 整体架构(LLM + 商品检索 + 推荐引擎 + 用户档案) | ||
| 49 | + | ||
| 50 | +``` | ||
| 51 | +┌─────────────────────────────────────────────────────────┐ | ||
| 52 | +│ 前端对话界面 │ | ||
| 53 | +│ (Web Chat Widget / 小程序 / H5) │ | ||
| 54 | +└────────────────────┬────────────────────────────────────┘ | ||
| 55 | + │ | ||
| 56 | + ▼ | ||
| 57 | +┌─────────────────────────────────────────────────────────┐ | ||
| 58 | +│ 对话式导购服务层 (Conversational Service) │ | ||
| 59 | +│ ┌──────────────────────────────────────────────────┐ │ | ||
| 60 | +│ │ 对话状态管理 (Dialogue State Tracker, DST) │ │ | ||
| 61 | +│ │ - 当前意图 (intent) │ │ | ||
| 62 | +│ │ - 已收集的偏好 (collected_preferences) │ │ | ||
| 63 | +│ │ - 待确认信息 (pending_confirmation) │ │ | ||
| 64 | +│ │ - 对话历史 (dialogue_history) │ │ | ||
| 65 | +│ └──────────────────────────────────────────────────┘ │ | ||
| 66 | +│ ┌──────────────────────────────────────────────────┐ │ | ||
| 67 | +│ │ LLM 对话引擎 (核心) │ │ | ||
| 68 | +│ │ - 意图理解 (NLU) │ │ | ||
| 69 | +│ │ - 实体抽取 (NER: 类目/颜色/尺码/价格/风格...) │ │ | ||
| 70 | +│ │ - 回复生成 (NLG) │ │ | ||
| 71 | +│ │ - 策略决策 (何时问问题/何时展示结果) │ │ | ||
| 72 | +│ └──────────────────────────────────────────────────┘ │ | ||
| 73 | +│ ┌──────────────────────────────────────────────────┐ │ | ||
| 74 | +│ │ 商品检索与推荐引擎 │ │ | ||
| 75 | +│ │ - 基于偏好生成 filters/range_filters │ │ | ||
| 76 | +│ │ - 调用搜索 API (复用现有 /search/) │ │ | ||
| 77 | +│ │ - 结果排序与多样性控制 │ │ | ||
| 78 | +│ └──────────────────────────────────────────────────┘ │ | ||
| 79 | +│ ┌──────────────────────────────────────────────────┐ │ | ||
| 80 | +│ │ 用户档案管理 (User Profile) │ │ | ||
| 81 | +│ │ - 尺码偏好 (size_preferences) │ │ | ||
| 82 | +│ │ - 风格偏好 (style_preferences) │ │ | ||
| 83 | +│ │ - 价格偏好 (price_range) │ │ | ||
| 84 | +│ │ - 历史购买/浏览 │ │ | ||
| 85 | +│ └──────────────────────────────────────────────────┘ │ | ||
| 86 | +└────────────────────┬────────────────────────────────────┘ | ||
| 87 | + │ | ||
| 88 | + ┌────────────┼────────────┐ | ||
| 89 | + ▼ ▼ ▼ | ||
| 90 | +┌─────────────┐ ┌──────────┐ ┌──────────────┐ | ||
| 91 | +│ 搜索 API │ │ 商品库 │ │ 用户行为库 │ | ||
| 92 | +│ (现有) │ │ (ES/DB) │ │ (埋点数据) │ | ||
| 93 | +└─────────────┘ └──────────┘ └──────────────┘ | ||
| 94 | +``` | ||
| 95 | + | ||
| 96 | +### 2.2 核心组件说明 | ||
| 97 | + | ||
| 98 | +#### 2.2.1 对话状态管理 (DST) | ||
| 99 | +- **存储位置**:Redis(key: `dialogue:{tenant_id}:{user_key}:{session_id}`) | ||
| 100 | +- **状态结构**: | ||
| 101 | + ```json | ||
| 102 | + { | ||
| 103 | + "intent": "find_product|refine_search|ask_question|...", | ||
| 104 | + "collected_preferences": { | ||
| 105 | + "category": "T恤", | ||
| 106 | + "size": "M", | ||
| 107 | + "color": ["白色", "浅色"], | ||
| 108 | + "style": "休闲", | ||
| 109 | + "price_range": {"min": 50, "max": 150}, | ||
| 110 | + "occasion": "日常", | ||
| 111 | + "season": "夏季" | ||
| 112 | + }, | ||
| 113 | + "pending_confirmation": ["size", "color"], | ||
| 114 | + "dialogue_history": [ | ||
| 115 | + {"role": "user", "content": "..."}, | ||
| 116 | + {"role": "assistant", "content": "..."} | ||
| 117 | + ], | ||
| 118 | + "current_results": [...], // 当前推荐结果(如有) | ||
| 119 | + "turn_count": 3 | ||
| 120 | + } | ||
| 121 | + ``` | ||
| 122 | + | ||
| 123 | +#### 2.2.2 LLM 对话引擎 | ||
| 124 | +- **输入**:用户消息 + 对话状态 + 商品库元信息(可选:类目列表、颜色列表、尺码列表) | ||
| 125 | +- **输出**: | ||
| 126 | + - **结构化输出**(必须):`intent`、`extracted_entities`、`action`(ask_question|show_results|clarify) | ||
| 127 | + - **自然语言回复**(用于前端展示) | ||
| 128 | +- **LLM 选择**: | ||
| 129 | + - **推荐**:GPT-4o / Claude 3.5 Sonnet(强推理、结构化输出) | ||
| 130 | + - **成本优化**:GPT-4o-mini / Claude Haiku(简单场景) | ||
| 131 | + - **私有化**:Qwen2.5 / GLM-4(如需要) | ||
| 132 | + | ||
| 133 | +#### 2.2.3 商品检索与推荐引擎 | ||
| 134 | +- **输入**:对话状态中的 `collected_preferences` | ||
| 135 | +- **处理**: | ||
| 136 | + 1. 将偏好转换为搜索 API 的 `filters` / `range_filters` | ||
| 137 | + 2. 调用现有 `/search/` API | ||
| 138 | + 3. 结果排序(可结合用户历史、商品热度、多样性) | ||
| 139 | +- **输出**:商品列表(含图片/标题/价格/链接) | ||
| 140 | + | ||
| 141 | +#### 2.2.4 用户档案管理 | ||
| 142 | +- **存储位置**:MySQL/Redis(key: `user_profile:{tenant_id}:{user_key}`) | ||
| 143 | +- **字段结构**: | ||
| 144 | + ```json | ||
| 145 | + { | ||
| 146 | + "size_preferences": { | ||
| 147 | + "tops": "M", | ||
| 148 | + "bottoms": "30", | ||
| 149 | + "shoes": "42" | ||
| 150 | + }, | ||
| 151 | + "style_preferences": ["休闲", "简约"], | ||
| 152 | + "color_preferences": ["白色", "黑色", "灰色"], | ||
| 153 | + "price_range": {"min": 50, "max": 300}, | ||
| 154 | + "brand_preferences": ["品牌A", "品牌B"], | ||
| 155 | + "last_updated": "2026-01-21T10:00:00Z" | ||
| 156 | + } | ||
| 157 | + ``` | ||
| 158 | + | ||
| 159 | +--- | ||
| 160 | + | ||
| 161 | +## 3. 主要能力清单 | ||
| 162 | + | ||
| 163 | +### 3.1 核心能力(MVP 必须) | ||
| 164 | + | ||
| 165 | +#### A. 意图理解与实体抽取 (NLU) | ||
| 166 | +- **能力**:从用户自然语言中提取: | ||
| 167 | + - **类目**:T恤、牛仔裤、运动鞋、外套... | ||
| 168 | + - **属性**:颜色、尺码、材质、风格(休闲/正式/运动) | ||
| 169 | + - **价格**:价格区间、预算 | ||
| 170 | + - **场合**:日常、通勤、运动、正式 | ||
| 171 | + - **季节**:春夏秋冬 | ||
| 172 | + - **其他**:品牌、折扣、新品等 | ||
| 173 | +- **实现**:LLM 做 NER(命名实体识别)+ 规则后处理(映射到商品库的 specifications) | ||
| 174 | + | ||
| 175 | +#### B. 偏好收集与确认 | ||
| 176 | +- **能力**: | ||
| 177 | + - 识别用户已表达的偏好 | ||
| 178 | + - 判断哪些关键信息缺失(如尺码、颜色) | ||
| 179 | + - 生成追问问题(基于店铺商品库的可用选项) | ||
| 180 | +- **实现**: | ||
| 181 | + - LLM 判断缺失项 | ||
| 182 | + - 从商品库分面(facets)获取可用选项(如"我们这里有白色、黑色、蓝色...") | ||
| 183 | + - 生成自然语言问题 | ||
| 184 | + | ||
| 185 | +#### C. 商品检索与筛选 | ||
| 186 | +- **能力**: | ||
| 187 | + - 将对话偏好转换为搜索 API 的 `filters` / `range_filters` | ||
| 188 | + - 调用搜索 API 获取候选 | ||
| 189 | + - 结果排序(相关性 + 个性化 + 多样性) | ||
| 190 | +- **实现**: | ||
| 191 | + - 偏好 → filters 映射规则(可配置) | ||
| 192 | + - 复用现有 `/search/` API | ||
| 193 | + - 排序可结合用户档案(如优先推荐用户偏好的品牌/价格带) | ||
| 194 | + | ||
| 195 | +#### D. 结果展示与解释 | ||
| 196 | +- **能力**: | ||
| 197 | + - 生成推荐理由("根据您的尺码偏好和风格,我为您推荐...") | ||
| 198 | + - 展示商品卡片(图片/标题/价格/链接) | ||
| 199 | + - 支持继续对话("不满意?告诉我您想要什么样的") | ||
| 200 | +- **实现**:LLM 生成回复 + 前端渲染商品卡片 | ||
| 201 | + | ||
| 202 | +#### E. 用户档案管理 | ||
| 203 | +- **能力**: | ||
| 204 | + - 从对话中提取并持久化偏好(尺码/风格/价格/品牌) | ||
| 205 | + - 后续对话自动使用档案(减少重复询问) | ||
| 206 | + - 档案更新(用户纠正/新偏好) | ||
| 207 | +- **实现**: | ||
| 208 | + - 对话结束后更新 `user_profile` | ||
| 209 | + - 下次对话时作为上下文输入 LLM | ||
| 210 | + | ||
| 211 | +### 3.2 进阶能力(后续迭代) | ||
| 212 | + | ||
| 213 | +#### F. 合身推荐(Size Fit) | ||
| 214 | +- **能力**: | ||
| 215 | + - 基于用户历史购买/浏览,学习"合身偏好"(如"这个品牌M码偏大,建议选S") | ||
| 216 | + - 跨品牌尺码映射(如"您平时穿Nike 42码,这个品牌建议选41码") | ||
| 217 | +- **实现**: | ||
| 218 | + - 需要用户历史数据(购买/退货/评价) | ||
| 219 | + - 可做简单的"品牌-尺码映射表"(运营配置) | ||
| 220 | + - 或训练轻量模型(用户尺码偏好 → 商品推荐尺码) | ||
| 221 | + | ||
| 222 | +#### G. 搭配推荐(Outfit Recommendation) | ||
| 223 | +- **能力**: | ||
| 224 | + - 用户选中一件商品后,推荐搭配(如"这件T恤可以搭配这条牛仔裤") | ||
| 225 | + - 基于商品属性相似度 + 历史共购数据 | ||
| 226 | +- **实现**: | ||
| 227 | + - 商品 embedding(文本/图片) | ||
| 228 | + - 共购规则(FBT) | ||
| 229 | + - LLM 生成搭配理由 | ||
| 230 | + | ||
| 231 | +#### H. 多轮对话策略优化 | ||
| 232 | +- **能力**: | ||
| 233 | + - 智能判断"何时该问问题" vs "何时该展示结果" | ||
| 234 | + - 避免过度询问(用户流失) | ||
| 235 | +- **实现**: | ||
| 236 | + - 规则:关键信息(尺码/类目)缺失时必问;可选信息(颜色/风格)有足够候选时可不问 | ||
| 237 | + - 或训练策略模型(RL/bandit) | ||
| 238 | + | ||
| 239 | +#### I. 上下文记忆与纠错 | ||
| 240 | +- **能力**: | ||
| 241 | + - 记住对话中提到的商品("刚才那件白色的") | ||
| 242 | + - 支持用户纠正("不对,我要的是黑色") | ||
| 243 | +- **实现**: | ||
| 244 | + - 对话状态中维护 `mentioned_items` | ||
| 245 | + - LLM 理解指代消解(coreference resolution) | ||
| 246 | + | ||
| 247 | +--- | ||
| 248 | + | ||
| 249 | +## 4. 数据层需求 | ||
| 250 | + | ||
| 251 | +### 4.1 对话事件表(DWD 层) | ||
| 252 | + | ||
| 253 | +- `dwd_conversation_turn`:每次对话轮次 | ||
| 254 | + - `tenant_id`, `user_key`, `session_id`, `conversation_id` | ||
| 255 | + - `turn_number`, `role` (user/assistant), `content` | ||
| 256 | + - `intent`, `extracted_entities_json` | ||
| 257 | + - `action` (ask_question|show_results|clarify) | ||
| 258 | + - `created_at` | ||
| 259 | + | ||
| 260 | +- `dwd_conversation_result`:对话产生的推荐结果 | ||
| 261 | + - `tenant_id`, `conversation_id`, `turn_number` | ||
| 262 | + - `item_id`, `position`, `clicked` (是否被点击) | ||
| 263 | + - `exposed_at` | ||
| 264 | + | ||
| 265 | +### 4.2 用户档案表(DIM 层) | ||
| 266 | + | ||
| 267 | +- `dim_user_profile`:用户偏好档案 | ||
| 268 | + - `tenant_id`, `user_key` | ||
| 269 | + - `size_preferences_json` | ||
| 270 | + - `style_preferences_json` | ||
| 271 | + - `color_preferences_json` | ||
| 272 | + - `price_range_json` | ||
| 273 | + - `brand_preferences_json` | ||
| 274 | + - `last_updated`, `created_at` | ||
| 275 | + | ||
| 276 | +### 4.3 商品库元信息(用于 LLM 上下文) | ||
| 277 | + | ||
| 278 | +- **类目列表**:从商品库聚合 `category1_name`, `category2_name`, `category3_name` | ||
| 279 | +- **颜色列表**:从 `specifications` 聚合 `name="color"` 的所有 `value` | ||
| 280 | +- **尺码列表**:从 `specifications` 聚合 `name="size"` 的所有 `value` | ||
| 281 | +- **风格标签**:从 `tags` 或自定义字段聚合 | ||
| 282 | + | ||
| 283 | +> **注意**:这些元信息需要定期更新(如每天),作为 LLM 的"知识库"输入,确保推荐的商品确实存在。 | ||
| 284 | + | ||
| 285 | +--- | ||
| 286 | + | ||
| 287 | +## 5. 技术实现要点 | ||
| 288 | + | ||
| 289 | +### 5.1 LLM Prompt 设计(关键) | ||
| 290 | + | ||
| 291 | +**系统提示词模板**: | ||
| 292 | +``` | ||
| 293 | +你是一个专业的服饰导购助手,帮助用户在店铺中找到合适的商品。 | ||
| 294 | + | ||
| 295 | +店铺信息: | ||
| 296 | +- 类目:{category_list} | ||
| 297 | +- 颜色:{color_list} | ||
| 298 | +- 尺码:{size_list} | ||
| 299 | +- 风格:{style_list} | ||
| 300 | + | ||
| 301 | +用户档案(如有): | ||
| 302 | +{user_profile_json} | ||
| 303 | + | ||
| 304 | +当前对话状态: | ||
| 305 | +{conversation_state_json} | ||
| 306 | + | ||
| 307 | +任务: | ||
| 308 | +1. 理解用户意图 | ||
| 309 | +2. 提取商品偏好(类目/颜色/尺码/价格/风格/场合等) | ||
| 310 | +3. 判断缺失的关键信息 | ||
| 311 | +4. 生成自然语言回复 | ||
| 312 | + | ||
| 313 | +输出格式(JSON): | ||
| 314 | +{ | ||
| 315 | + "intent": "find_product|refine_search|ask_question|...", | ||
| 316 | + "extracted_entities": { | ||
| 317 | + "category": "...", | ||
| 318 | + "color": [...], | ||
| 319 | + "size": "...", | ||
| 320 | + "price_range": {"min": ..., "max": ...}, | ||
| 321 | + "style": "...", | ||
| 322 | + "occasion": "..." | ||
| 323 | + }, | ||
| 324 | + "action": "ask_question|show_results|clarify", | ||
| 325 | + "missing_info": ["size", "color"], // 缺失的关键信息 | ||
| 326 | + "reply": "自然语言回复" | ||
| 327 | +} | ||
| 328 | +``` | ||
| 329 | + | ||
| 330 | +### 5.2 偏好 → 搜索 Filters 映射规则 | ||
| 331 | + | ||
| 332 | +```python | ||
| 333 | +def preferences_to_filters(collected_preferences, user_profile): | ||
| 334 | + filters = {} | ||
| 335 | + range_filters = {} | ||
| 336 | + | ||
| 337 | + # 类目 | ||
| 338 | + if collected_preferences.get("category"): | ||
| 339 | + filters["category_name"] = collected_preferences["category"] | ||
| 340 | + | ||
| 341 | + # 颜色(specifications) | ||
| 342 | + if collected_preferences.get("color"): | ||
| 343 | + filters["specifications"] = [ | ||
| 344 | + {"name": "color", "value": c} | ||
| 345 | + for c in collected_preferences["color"] | ||
| 346 | + ] | ||
| 347 | + | ||
| 348 | + # 尺码(specifications) | ||
| 349 | + if collected_preferences.get("size"): | ||
| 350 | + # 优先用对话中的,否则用用户档案 | ||
| 351 | + size = collected_preferences.get("size") or user_profile.get("size_preferences", {}).get("tops") | ||
| 352 | + if size: | ||
| 353 | + filters.setdefault("specifications", []).append({"name": "size", "value": size}) | ||
| 354 | + | ||
| 355 | + # 价格区间 | ||
| 356 | + if collected_preferences.get("price_range"): | ||
| 357 | + pr = collected_preferences["price_range"] | ||
| 358 | + range_filters["min_price"] = {"gte": pr.get("min", 0), "lte": pr.get("max", 9999)} | ||
| 359 | + | ||
| 360 | + # 风格(tags 或自定义字段) | ||
| 361 | + if collected_preferences.get("style"): | ||
| 362 | + filters["tags"] = collected_preferences["style"] | ||
| 363 | + | ||
| 364 | + return filters, range_filters | ||
| 365 | +``` | ||
| 366 | + | ||
| 367 | +### 5.3 对话流程控制(状态机) | ||
| 368 | + | ||
| 369 | +``` | ||
| 370 | +初始状态: waiting_for_intent | ||
| 371 | + ↓ 用户输入 | ||
| 372 | +意图理解: extract_intent_and_entities | ||
| 373 | + ↓ | ||
| 374 | +判断缺失信息: | ||
| 375 | + - 关键信息缺失(如尺码)→ ask_question | ||
| 376 | + - 有足够信息 → show_results | ||
| 377 | + ↓ | ||
| 378 | +展示结果后: | ||
| 379 | + - 用户满意 → end_conversation | ||
| 380 | + - 用户不满意/继续提问 → refine_search (更新偏好,重新检索) | ||
| 381 | +``` | ||
| 382 | + | ||
| 383 | +--- | ||
| 384 | + | ||
| 385 | +## 6. 产品形态建议 | ||
| 386 | + | ||
| 387 | +### 6.1 前端交互方式 | ||
| 388 | + | ||
| 389 | +- **Web Chat Widget**:右下角悬浮聊天框(类似客服) | ||
| 390 | +- **全屏对话页**:独立页面,适合移动端 | ||
| 391 | +- **搜索结果页集成**:在搜索结果页提供"智能导购"入口 | ||
| 392 | + | ||
| 393 | +### 6.2 回复展示格式 | ||
| 394 | + | ||
| 395 | +- **纯文本回复**:LLM 生成的自然语言 | ||
| 396 | +- **商品卡片**:展示推荐结果(图片/标题/价格/加购按钮) | ||
| 397 | +- **选项按钮**:快速选择(如"白色"、"黑色"、"蓝色") | ||
| 398 | +- **追问输入框**:用户继续输入 | ||
| 399 | + | ||
| 400 | +--- | ||
| 401 | + | ||
| 402 | +## 7. 评估指标 | ||
| 403 | + | ||
| 404 | +### 7.1 对话质量 | ||
| 405 | +- **平均对话轮次**:完成一次推荐的平均轮次(越少越好,但需平衡信息收集) | ||
| 406 | +- **用户满意度**:对话结束后的反馈(如"有用"/"无用") | ||
| 407 | +- **推荐点击率**:对话推荐的商品被点击的比例 | ||
| 408 | + | ||
| 409 | +### 7.2 业务指标 | ||
| 410 | +- **转化率**:对话用户 → 加购/购买的转化 | ||
| 411 | +- **AOV 提升**:对话用户的平均订单价值 vs 非对话用户 | ||
| 412 | +- **档案完善率**:用户档案被更新的比例 | ||
| 413 | + | ||
| 414 | +--- | ||
| 415 | + | ||
| 416 | +## 8. 实施优先级 | ||
| 417 | + | ||
| 418 | +### Phase 1 (MVP - 2-3个月) | ||
| 419 | +- ✅ 基础对话能力(LLM + 意图理解 + 实体抽取) | ||
| 420 | +- ✅ 偏好收集(尺码/颜色/类目/价格) | ||
| 421 | +- ✅ 商品检索(偏好 → filters → 搜索 API) | ||
| 422 | +- ✅ 结果展示(商品卡片) | ||
| 423 | +- ✅ 用户档案(基础字段:尺码/颜色/价格) | ||
| 424 | + | ||
| 425 | +### Phase 2 (增强 - 3-4个月) | ||
| 426 | +- ✅ 合身推荐(尺码映射) | ||
| 427 | +- ✅ 搭配推荐 | ||
| 428 | +- ✅ 多轮对话策略优化 | ||
| 429 | +- ✅ 上下文记忆 | ||
| 430 | + | ||
| 431 | +### Phase 3 (优化 - 持续) | ||
| 432 | +- ✅ 个性化排序(结合用户历史) | ||
| 433 | +- ✅ A/B 测试(不同 prompt/策略) | ||
| 434 | +- ✅ 跨租户知识共享(可选) | ||
| 435 | + | ||
| 436 | +--- | ||
| 437 | + | ||
| 438 | +## 9. 技术栈建议 | ||
| 439 | + | ||
| 440 | +- **LLM 服务**:OpenAI API / Anthropic API / 私有化部署(Qwen/GLM) | ||
| 441 | +- **对话状态存储**:Redis | ||
| 442 | +- **用户档案存储**:MySQL + Redis(缓存) | ||
| 443 | +- **商品检索**:复用现有搜索 API | ||
| 444 | +- **后端框架**:Python FastAPI(与现有 API 保持一致) | ||
| 445 | +- **前端**:React/Vue + WebSocket(实时对话) | ||
| 446 | + | ||
| 447 | +--- | ||
| 448 | + | ||
| 449 | +## 10. 注意事项 | ||
| 450 | + | ||
| 451 | +1. **成本控制**:LLM API 调用成本(每次对话可能 3-10 轮),建议: | ||
| 452 | + - 简单场景用轻量模型(GPT-4o-mini) | ||
| 453 | + - 复杂场景用强模型(GPT-4o) | ||
| 454 | + - 缓存常见问题回复 | ||
| 455 | + | ||
| 456 | +2. **延迟**:LLM 生成回复通常 1-3 秒,需要: | ||
| 457 | + - 前端显示"正在思考..." | ||
| 458 | + - 考虑流式输出(streaming) | ||
| 459 | + | ||
| 460 | +3. **商品库同步**:确保 LLM 的"知识库"(类目/颜色/尺码列表)与商品库实时同步 | ||
| 461 | + | ||
| 462 | +4. **多语言**:如店铺支持多语言,LLM 需要支持对应语言 | ||
| 463 | + | ||
| 464 | +5. **隐私合规**:用户档案数据需符合 GDPR/CCPA 等要求 |
| @@ -0,0 +1,353 @@ | @@ -0,0 +1,353 @@ | ||
| 1 | + | ||
| 2 | +## 1)BI 怎么设计(对标 Shopify/独立站“搜索/推荐”常见报表) | ||
| 3 | +Shopify 的 Search & Discovery/行为报表里,搜索常看的就是 **query、no results、no clicks、click rate、purchase rate**(会话口径漏斗)。推荐系统的看板更强调 **曝光→点击→加购→购买→收入**,并按**推荐位/算法/实验**拆解。 | ||
| 4 | + | ||
| 5 | +### 1.1 搜索 BI(核心看板) | ||
| 6 | +- **搜索总览(KPI + 漏斗)** | ||
| 7 | + - **搜索 PV/UV**:search request 次数 / 搜索用户数 | ||
| 8 | + - **Click rate(会话口径,Shopify 常用)**:有点击的搜索会话 / 搜索会话 | ||
| 9 | + - **ATC rate**:有加购的搜索会话 / 搜索会话 | ||
| 10 | + - **Purchase rate(会话口径,Shopify 常用)**:有购买的搜索会话 / 搜索会话 | ||
| 11 | + - **结果曝光 CTR(结果口径)**:点击次数 / 结果曝光次数(更适合诊断排序/样式) | ||
| 12 | + - **延迟&稳定性**:P50/P95 搜索耗时、错误率、空响应率 | ||
| 13 | + | ||
| 14 | +- **Query 分析(运营/选品/词库最常用)** | ||
| 15 | + - **高热搜索词**:按搜索 UV、曝光 UV、点击 UV、购买 UV 排序 | ||
| 16 | + - **飙升词/新词**:与过去 \(7/14/28\) 天对比的增速 | ||
| 17 | + - **无结果 query**(no results) | ||
| 18 | + - **有结果但无点击 query**(no clicks)——强信号:相关性差/图片价位不吸引/首屏无货等 | ||
| 19 | + - **建议词(suggestion)**:suggest 点击发起的搜索占比、suggest→点击/购买转化 | ||
| 20 | + | ||
| 21 | +- **结果质量诊断(给算法/工程用)** | ||
| 22 | + - **点击位置分布**:position=1/2/3… 的点击占比(反推排序质量) | ||
| 23 | + - **类目/品牌/价格带分布**:哪些 query 对哪些类目贡献转化 | ||
| 24 | + - **筛选/排序使用率**:filter 使用率、排序切换率、filter 后的 CTR/CVR 变化 | ||
| 25 | + | ||
| 26 | +- **用户与渠道拆解(增长/产品用)** | ||
| 27 | + - **新/老客**、地域、语言、设备、来源(自然/广告/社媒) | ||
| 28 | + - **页面维度**:首页搜索框 / 搜索页 / 分类页搜索 / 详情页内搜索等 | ||
| 29 | + | ||
| 30 | +- **Session/链路回放(你提到的 track_id/trace_id)** | ||
| 31 | + - 单用户或单 trace:**搜索→换词→曝光→点击→详情→加购→支付** | ||
| 32 | + - 支持按 **trace_id** 串联一次“搜索请求”与后续行为(你们文档也强调这一点) | ||
| 33 | + | ||
| 34 | +> 参考:Shopify 官方对搜索分析的核心指标包括 click rate、purchase rate、queries、no results、no clicks(见 [Shopify Search & Discovery analytics](https://help.shopify.com/en/manual/online-store/storefront-search/search-and-discovery-analytics))。 | ||
| 35 | + | ||
| 36 | +### 1.2 推荐 BI(核心看板) | ||
| 37 | +- **推荐位总览(按 placement/module)** | ||
| 38 | + - **Impressions/Clicks/CTR** | ||
| 39 | + - **ATC、Purchase、Revenue** | ||
| 40 | + - **Revenue per impression / per click** | ||
| 41 | + - **AOV(推荐归因订单)** | ||
| 42 | + - **覆盖与多样性**:覆盖商品数、长尾曝光占比、重复率(避免“只推爆款”) | ||
| 43 | + | ||
| 44 | +- **按算法/实验拆解** | ||
| 45 | + - 算法版本(model_version/strategy_id) | ||
| 46 | + - 实验(experiment_id/variant_id) | ||
| 47 | + - **Lift(增量)**:相对对照组的 CTR/CVR/Revenue uplift(最好有 holdout) | ||
| 48 | + | ||
| 49 | +- **健康度与风控** | ||
| 50 | + - 缺货/下架命中率、被过滤原因分布(无库存/不可售/地域不发货) | ||
| 51 | + - 冷启动占比(新用户/新商品)与表现 | ||
| 52 | + | ||
| 53 | +--- | ||
| 54 | + | ||
| 55 | +## 2)数据层面的设计(埋点→数仓→指标→推荐特征) | ||
| 56 | +你们已有统一事件骨架的方向(`tenant_id / session_id / trace_id / event(oneof)`)。建议在数据层分成 **ODS(原始)→DWD(清洗明细)→DWS(汇总)→ADS(看板语义层)**,同时给推荐做 **离线+在线特征库**。 | ||
| 57 | + | ||
| 58 | +### 2.1 关键 ID 设计(决定 BI/推荐能否串起来) | ||
| 59 | +- **tenant_id**:多租户隔离必备(所有表分区/主键都带) | ||
| 60 | +- **user_key**:归一后的用户主键(优先 login/user_id;否则 anonymous_id/cookie_id) | ||
| 61 | +- **session_id**:会话(SDK 产生或用 30min inactivity 规则补齐) | ||
| 62 | +- **trace_id(最重要)**:一次搜索/一次推荐刷新生成一个,后续点击/详情/加购尽量继承 | ||
| 63 | +- **request_id**:后端请求日志关联(用于延迟、错误、召回/排序 debug) | ||
| 64 | +- **order_id/cart_id/item_id(sku/spu)**:交易与商品归因 | ||
| 65 | + | ||
| 66 | +### 2.2 ODS:原始事件(“可回放、可重放”) | ||
| 67 | +- `ods_ua_event_raw` | ||
| 68 | + - tenant_id, event_time, received_time | ||
| 69 | + - identities(原始字段:distinct_id/login_id/anonymous_id/cookie_id…) | ||
| 70 | + - page/device/geo/referrer | ||
| 71 | + - trace_id, session_id, experiment 信息 | ||
| 72 | + - event_name + properties(原始 JSON,保留全量) | ||
| 73 | + | ||
| 74 | +> 你现在的 `$pageview/$WebClick` 自动采集能覆盖点击和页面,但 **推荐/搜索“曝光”必须补埋点**(否则 CTR、归因、训练样本都会缺)。 | ||
| 75 | + | ||
| 76 | +### 2.3 DWD:清洗后的事实表(BI/训练统一口径) | ||
| 77 | +建议把“强分析对象”拆成事实表(便于 join、去重、做漏斗): | ||
| 78 | + | ||
| 79 | +- **搜索域** | ||
| 80 | + - `dwd_search_request`:一次“搜索结果刷新/请求”的主表(强烈建议:**每次请求一个 request_id,同时每次请求一个 trace_id**) | ||
| 81 | + - **用途**:search PV/UV、零结果、延迟、query 聚合、会话口径漏斗分母(search_session) | ||
| 82 | + - **主键建议**:`(tenant_id, request_id)` | ||
| 83 | + - **核心索引建议**: | ||
| 84 | + - `(tenant_id, created_at)` | ||
| 85 | + - `(tenant_id, user_key, created_at)` | ||
| 86 | + - `(tenant_id, query_normalized, created_at)` | ||
| 87 | + - `(tenant_id, trace_id)`(用于 join 曝光/点击) | ||
| 88 | + - `(tenant_id, search_session_id, created_at)` | ||
| 89 | + - **字段建议(MySQL 参考类型,可按你们存储调整)** | ||
| 90 | + - `tenant_id` VARCHAR(64) NOT NULL:店铺/租户 | ||
| 91 | + - `request_id` VARCHAR(64) NOT NULL:该次搜索请求唯一 ID(前端生成或后端返回) | ||
| 92 | + - `trace_id` VARCHAR(64) NOT NULL:该次搜索链路 ID(一次结果集 = 一个 trace) | ||
| 93 | + - `session_id` VARCHAR(64) NULL:访问会话(30min inactivity) | ||
| 94 | + - `search_session_id` VARCHAR(64) NULL:搜索会话(一次“找东西”的连续过程) | ||
| 95 | + - `user_id` VARCHAR(64) NULL:登录用户 ID | ||
| 96 | + - `anonymous_id` VARCHAR(64) NULL:匿名用户(cookie 级稳定) | ||
| 97 | + - `user_key` VARCHAR(64) NOT NULL:归一用户主键(ETL 生成) | ||
| 98 | + - `query` TEXT NOT NULL:原始 query | ||
| 99 | + - `query_normalized` VARCHAR(512) NOT NULL:归一化 query(聚合用) | ||
| 100 | + - `is_suggestion` TINYINT NOT NULL DEFAULT 0:是否由 suggestion 触发 | ||
| 101 | + - `suggestion_text` VARCHAR(512) NULL:命中的 suggestion(如有) | ||
| 102 | + - `page_number` INT NOT NULL DEFAULT 1:翻页页码(1-based) | ||
| 103 | + - `sort` VARCHAR(64) NULL:排序方式(如 relevance/price_asc/…) | ||
| 104 | + - `filters_json` JSON NULL:结构化筛选条件(原子字段更好;JSON 为起步方案) | ||
| 105 | + - `filters_hash` CHAR(32) NULL:filters 归一化后的 hash(便于聚合/去重) | ||
| 106 | + - `results_count` INT NOT NULL DEFAULT 0:总命中数 | ||
| 107 | + - `returned_count` INT NULL:本页返回数量(page size) | ||
| 108 | + - `latency_ms` INT NULL:搜索耗时 | ||
| 109 | + - `is_zero_result` TINYINT NOT NULL DEFAULT 0:是否零结果(可由 results_count=0 派生) | ||
| 110 | + - `is_error` TINYINT NOT NULL DEFAULT 0:是否错误 | ||
| 111 | + - `error_code` VARCHAR(64) NULL:错误码(如超时/限流等) | ||
| 112 | + - `page_type` VARCHAR(64) NULL:发生搜索的页面(home/search/pdp/collection…) | ||
| 113 | + - `referrer` TEXT NULL:来源页 | ||
| 114 | + - `device_type` VARCHAR(32) NULL:pc/mobile(可从 UA 解析) | ||
| 115 | + - `country` VARCHAR(8) NULL:国家(建议由 IP->Geo 派生;避免落全量 IP) | ||
| 116 | + - `created_at` DATETIME(3) NOT NULL:事件时间(毫秒级可选) | ||
| 117 | + | ||
| 118 | + - `dwd_search_impression_item`:搜索结果“曝光明细”(**必须补**,否则无法算 position CTR、训练负样本) | ||
| 119 | + - **用途**:曝光 PV/UV、位置 CTR、召回/排序诊断、训练样本(曝光未点为负样本) | ||
| 120 | + - **主键建议**:`(tenant_id, trace_id, item_id, position)`(或 `(tenant_id, request_id, item_id, position)`,二选一但全局统一) | ||
| 121 | + - **核心索引建议**: | ||
| 122 | + - `(tenant_id, trace_id)` | ||
| 123 | + - `(tenant_id, request_id)` | ||
| 124 | + - `(tenant_id, item_id, exposed_at)` | ||
| 125 | + - **字段建议** | ||
| 126 | + - `tenant_id` VARCHAR(64) NOT NULL | ||
| 127 | + - `request_id` VARCHAR(64) NOT NULL | ||
| 128 | + - `trace_id` VARCHAR(64) NOT NULL | ||
| 129 | + - `search_session_id` VARCHAR(64) NULL | ||
| 130 | + - `user_key` VARCHAR(64) NOT NULL | ||
| 131 | + - `query_normalized` VARCHAR(512) NOT NULL | ||
| 132 | + - `item_id` VARCHAR(64) NOT NULL:spu 或 sku,需统一 | ||
| 133 | + - `position` INT NOT NULL:在搜索结果中的排名(1-based) | ||
| 134 | + - `score` DOUBLE NULL:排序分(如 ES score/learning-to-rank score) | ||
| 135 | + - `price` DECIMAL(18,2) NULL:曝光时价格快照(可选但强烈建议) | ||
| 136 | + - `currency` VARCHAR(8) NULL | ||
| 137 | + - `in_stock` TINYINT NULL:曝光时库存可售快照(可选但强烈建议) | ||
| 138 | + - `exposed_at` DATETIME(3) NOT NULL:曝光时间 | ||
| 139 | + | ||
| 140 | + - `dwd_search_click_item`:搜索结果点击明细(点击必须能回链到曝光/请求) | ||
| 141 | + - **用途**:click UV、CTR(曝光口径/会话口径)、位置点击分布、归因触点 | ||
| 142 | + - **主键建议**:`(tenant_id, click_id)`(若无 click_id,可用 `(tenant_id, trace_id, item_id, clicked_at)` 近似) | ||
| 143 | + - **核心索引建议**: | ||
| 144 | + - `(tenant_id, trace_id)` | ||
| 145 | + - `(tenant_id, request_id)` | ||
| 146 | + - `(tenant_id, item_id, clicked_at)` | ||
| 147 | + - **字段建议** | ||
| 148 | + - `tenant_id` VARCHAR(64) NOT NULL | ||
| 149 | + - `click_id` VARCHAR(64) NULL:点击事件唯一 ID(推荐补) | ||
| 150 | + - `request_id` VARCHAR(64) NOT NULL | ||
| 151 | + - `trace_id` VARCHAR(64) NOT NULL | ||
| 152 | + - `search_session_id` VARCHAR(64) NULL | ||
| 153 | + - `user_key` VARCHAR(64) NOT NULL | ||
| 154 | + - `query_normalized` VARCHAR(512) NOT NULL | ||
| 155 | + - `item_id` VARCHAR(64) NOT NULL | ||
| 156 | + - `position` INT NULL:点击时该商品所在排名(无法取到则置空) | ||
| 157 | + - `clicked_at` DATETIME(3) NOT NULL | ||
| 158 | + - `target_url` TEXT NULL:点击跳转 URL(可选) | ||
| 159 | + | ||
| 160 | +- **推荐域** | ||
| 161 | + - `dwd_rec_request`:一次推荐请求/刷新主表(一次刷新 = 一个 trace_id) | ||
| 162 | + - **用途**:推荐 PV/UV、分推荐位指标、实验/版本拆解、延迟与稳定性 | ||
| 163 | + - **主键建议**:`(tenant_id, request_id)` | ||
| 164 | + - **核心索引建议**: | ||
| 165 | + - `(tenant_id, created_at)` | ||
| 166 | + - `(tenant_id, placement, created_at)` | ||
| 167 | + - `(tenant_id, trace_id)` | ||
| 168 | + - `(tenant_id, algo_id, model_version, created_at)` | ||
| 169 | + - **字段建议** | ||
| 170 | + - `tenant_id` VARCHAR(64) NOT NULL | ||
| 171 | + - `request_id` VARCHAR(64) NOT NULL | ||
| 172 | + - `trace_id` VARCHAR(64) NOT NULL | ||
| 173 | + - `session_id` VARCHAR(64) NULL | ||
| 174 | + - `user_key` VARCHAR(64) NOT NULL | ||
| 175 | + - `placement` VARCHAR(64) NOT NULL:推荐位(home/pdp/cart/checkout/search…) | ||
| 176 | + - `module_id` VARCHAR(128) NULL:页面内模块标识(一个页面多个推荐模块) | ||
| 177 | + - `trigger_item_id` VARCHAR(64) NULL:触发商品(PDP/Cart 场景常用) | ||
| 178 | + - `candidates_count` INT NULL:候选数量 | ||
| 179 | + - `returned_count` INT NULL:返回条数 | ||
| 180 | + - `algo_id` VARCHAR(64) NULL:策略/算法标识(rule/i2i/embedding/…) | ||
| 181 | + - `model_version` VARCHAR(64) NULL:模型版本 | ||
| 182 | + - `experiment_id` VARCHAR(64) NULL:实验 ID(如有) | ||
| 183 | + - `variant_id` VARCHAR(64) NULL:分桶/分组(如有) | ||
| 184 | + - `latency_ms` INT NULL | ||
| 185 | + - `is_error` TINYINT NOT NULL DEFAULT 0 | ||
| 186 | + - `error_code` VARCHAR(64) NULL | ||
| 187 | + - `created_at` DATETIME(3) NOT NULL | ||
| 188 | + | ||
| 189 | + - `dwd_rec_impression_item`:推荐曝光明细 | ||
| 190 | + - **主键建议**:`(tenant_id, trace_id, item_id, position)` | ||
| 191 | + - **核心索引建议**:`(tenant_id, trace_id)`、`(tenant_id, item_id, exposed_at)` | ||
| 192 | + - **字段建议** | ||
| 193 | + - `tenant_id` VARCHAR(64) NOT NULL | ||
| 194 | + - `request_id` VARCHAR(64) NOT NULL | ||
| 195 | + - `trace_id` VARCHAR(64) NOT NULL | ||
| 196 | + - `user_key` VARCHAR(64) NOT NULL | ||
| 197 | + - `placement` VARCHAR(64) NOT NULL | ||
| 198 | + - `module_id` VARCHAR(128) NULL | ||
| 199 | + - `trigger_item_id` VARCHAR(64) NULL | ||
| 200 | + - `item_id` VARCHAR(64) NOT NULL | ||
| 201 | + - `position` INT NOT NULL | ||
| 202 | + - `score` DOUBLE NULL | ||
| 203 | + - `price` DECIMAL(18,2) NULL | ||
| 204 | + - `currency` VARCHAR(8) NULL | ||
| 205 | + - `in_stock` TINYINT NULL | ||
| 206 | + - `exposed_at` DATETIME(3) NOT NULL | ||
| 207 | + | ||
| 208 | + - `dwd_rec_click_item`:推荐点击明细 | ||
| 209 | + - **主键建议**:`(tenant_id, click_id)`(或 `(tenant_id, trace_id, item_id, clicked_at)`) | ||
| 210 | + - **核心索引建议**:`(tenant_id, trace_id)`、`(tenant_id, item_id, clicked_at)` | ||
| 211 | + - **字段建议** | ||
| 212 | + - `tenant_id` VARCHAR(64) NOT NULL | ||
| 213 | + - `click_id` VARCHAR(64) NULL | ||
| 214 | + - `request_id` VARCHAR(64) NOT NULL | ||
| 215 | + - `trace_id` VARCHAR(64) NOT NULL | ||
| 216 | + - `user_key` VARCHAR(64) NOT NULL | ||
| 217 | + - `placement` VARCHAR(64) NOT NULL | ||
| 218 | + - `module_id` VARCHAR(128) NULL | ||
| 219 | + - `trigger_item_id` VARCHAR(64) NULL | ||
| 220 | + - `item_id` VARCHAR(64) NOT NULL | ||
| 221 | + - `position` INT NULL | ||
| 222 | + - `clicked_at` DATETIME(3) NOT NULL | ||
| 223 | + | ||
| 224 | +- **转化域(全站通用)** | ||
| 225 | + - `dwd_view_item`:商品详情页浏览(可承接 search/rec 的 trace,用于链路与兴趣序列) | ||
| 226 | + - **主键建议**:`(tenant_id, view_id)`(或 `(tenant_id, user_key, item_id, viewed_at)`) | ||
| 227 | + - **核心索引建议**:`(tenant_id, user_key, viewed_at)`、`(tenant_id, item_id, viewed_at)` | ||
| 228 | + - **字段建议** | ||
| 229 | + - `tenant_id` VARCHAR(64) NOT NULL | ||
| 230 | + - `view_id` VARCHAR(64) NULL | ||
| 231 | + - `session_id` VARCHAR(64) NULL | ||
| 232 | + - `user_key` VARCHAR(64) NOT NULL | ||
| 233 | + - `item_id` VARCHAR(64) NOT NULL | ||
| 234 | + - `source_type` VARCHAR(32) NULL:来源(search/rec/direct/ads/…) | ||
| 235 | + - `source_trace_id` VARCHAR(64) NULL:来源 trace(若来自 search/rec) | ||
| 236 | + - `dwell_time_ms` INT NULL | ||
| 237 | + - `viewed_at` DATETIME(3) NOT NULL | ||
| 238 | + | ||
| 239 | + - `dwd_add_to_cart`:加购事实表(全站统一) | ||
| 240 | + - **主键建议**:`(tenant_id, atc_id)`(或 `(tenant_id, user_key, item_id, added_at)`) | ||
| 241 | + - **核心索引建议**:`(tenant_id, user_key, added_at)`、`(tenant_id, item_id, added_at)` | ||
| 242 | + - **字段建议** | ||
| 243 | + - `tenant_id` VARCHAR(64) NOT NULL | ||
| 244 | + - `atc_id` VARCHAR(64) NULL | ||
| 245 | + - `session_id` VARCHAR(64) NULL | ||
| 246 | + - `cart_id` VARCHAR(64) NULL | ||
| 247 | + - `user_key` VARCHAR(64) NOT NULL | ||
| 248 | + - `item_id` VARCHAR(64) NOT NULL | ||
| 249 | + - `quantity` INT NOT NULL DEFAULT 1 | ||
| 250 | + - `price` DECIMAL(18,2) NULL | ||
| 251 | + - `currency` VARCHAR(8) NULL | ||
| 252 | + - `source_type` VARCHAR(32) NULL:来源(search/rec/direct/…) | ||
| 253 | + - `source_trace_id` VARCHAR(64) NULL:来源 trace(若来自 search/rec) | ||
| 254 | + - `added_at` DATETIME(3) NOT NULL | ||
| 255 | + - `cart_snapshot_json` JSON NULL:购物车快照(可选,体积大时可下沉到独立表) | ||
| 256 | + | ||
| 257 | + - `dwd_purchase`:订单事实表(全站统一) | ||
| 258 | + - **主键建议**:`(tenant_id, order_id)` | ||
| 259 | + - **核心索引建议**:`(tenant_id, user_key, paid_at)`、`(tenant_id, paid_at)` | ||
| 260 | + - **字段建议** | ||
| 261 | + - `tenant_id` VARCHAR(64) NOT NULL | ||
| 262 | + - `order_id` VARCHAR(64) NOT NULL | ||
| 263 | + - `user_key` VARCHAR(64) NOT NULL | ||
| 264 | + - `currency` VARCHAR(8) NULL | ||
| 265 | + - `total_amount` DECIMAL(18,2) NULL | ||
| 266 | + - `paid_at` DATETIME(3) NOT NULL | ||
| 267 | + | ||
| 268 | + - `dwd_purchase_item`:订单明细事实表(全站统一) | ||
| 269 | + - **主键建议**:`(tenant_id, order_id, item_id)`(若一单同商品可多行,则增加 `line_id`) | ||
| 270 | + - **核心索引建议**:`(tenant_id, item_id, paid_at)`、`(tenant_id, order_id)` | ||
| 271 | + - **字段建议** | ||
| 272 | + - `tenant_id` VARCHAR(64) NOT NULL | ||
| 273 | + - `order_id` VARCHAR(64) NOT NULL | ||
| 274 | + - `line_id` VARCHAR(64) NULL | ||
| 275 | + - `user_key` VARCHAR(64) NOT NULL | ||
| 276 | + - `item_id` VARCHAR(64) NOT NULL | ||
| 277 | + - `quantity` INT NOT NULL DEFAULT 1 | ||
| 278 | + - `price` DECIMAL(18,2) NULL | ||
| 279 | + - `paid_at` DATETIME(3) NOT NULL | ||
| 280 | + | ||
| 281 | + - **落库/分区建议(MySQL)** | ||
| 282 | + - 量小:按 `tenant_id` + `created_at` 索引即可 | ||
| 283 | + - 量大:建议按月分表/分区(`*_202601`),或落到 ClickHouse/ES/湖仓;DWD 保持明细可追溯,DWS/ADS 做聚合提速 | ||
| 284 | + | ||
| 285 | +- **归因桥(可选但强烈建议)** | ||
| 286 | + - `dwd_attribution_touch` | ||
| 287 | + - order_id, item_id, last_touch_trace_id, touch_type(search/rec), touch_time, window(如 7d click/1d view) | ||
| 288 | + - 这样 BI 里的“搜索带来收入/推荐带来收入”不会口径混乱。 | ||
| 289 | + | ||
| 290 | +### 2.4 DWS/ADS:面向看板的汇总层(高性能) | ||
| 291 | +- `dws_search_kpi_daily`(tenant_id, date, page_type/device/geo/new_vs_returning…) | ||
| 292 | + - search_pv, search_uv, sessions_with_click, sessions_with_atc, sessions_with_purchase | ||
| 293 | + - click_rate_session, purchase_rate_session, zero_result_rate, no_click_rate, p95_latency | ||
| 294 | +- `dws_query_daily`(tenant_id, date, normalized_query) | ||
| 295 | + - searches, exposure_uv, click_uv, purchase_uv, zero_cnt, no_click_cnt | ||
| 296 | +- `dws_rec_kpi_daily`(tenant_id, date, placement, algo_id/model_version) | ||
| 297 | + - impressions, clicks, ctr, atc, purchases, revenue, rpi(revenue per impression) | ||
| 298 | +- `dws_item_daily`(tenant_id, date, item_id) | ||
| 299 | + - search_impr/click/atc/purchase、rec_impr/click/…(用于“商品天级统计特征”与运营) | ||
| 300 | + | ||
| 301 | +--- | ||
| 302 | + | ||
| 303 | +## 3)推荐系统依赖的数据(“能训练、能实时、能解释”) | ||
| 304 | +把特征分成 **用户/商品/上下文**,再分 **静态/快照/统计/序列(实时)**。 | ||
| 305 | + | ||
| 306 | +### 3.1 用户基础信息(dim) | ||
| 307 | +- `dim_user`:user_key、注册时间、是否会员、国家/语言、获客渠道(如可得)、新老客标签 | ||
| 308 | +- 合规:只存业务需要的最小信息,敏感字段做脱敏/哈希 | ||
| 309 | + | ||
| 310 | +### 3.2 用户实时特征(在线序列 + 聚合) | ||
| 311 | +在线(Redis/Key-Value)建议保留: | ||
| 312 | +- **最近 N 次行为序列**(按时间衰减) | ||
| 313 | + - view_item_seq(最近浏览商品) | ||
| 314 | + - search_query_seq(最近搜索词/类目) | ||
| 315 | + - click_seq(点击的商品,区分来自 search/rec) | ||
| 316 | + - cart_seq、purchase_seq | ||
| 317 | +- **实时聚合** | ||
| 318 | + - 最近 1h/24h 搜索次数、点击次数、加购次数 | ||
| 319 | + - 最近一次偏好类目/品牌/价格带(从序列实时计算) | ||
| 320 | + | ||
| 321 | +离线(天/小时级)输出: | ||
| 322 | +- RFM、长期偏好向量(类目/品牌 embedding)、价格敏感度、复购周期等 | ||
| 323 | + | ||
| 324 | +### 3.3 商品基础特征(静态) | ||
| 325 | +- `dim_item`(sku/spu) | ||
| 326 | + - 类目、品牌、属性(颜色/尺码/材质…)、标题/描述、价格、币种、图片 | ||
| 327 | + - 文本/图片 embedding(你们已有 embeddings 模块可复用) | ||
| 328 | + | ||
| 329 | +### 3.4 商品快照(强业务约束,必须可回溯) | ||
| 330 | +- `item_snapshot`(tenant_id, item_id, snapshot_time) | ||
| 331 | + - 库存/可售、折扣、上新、发货国家限制、活动标签 | ||
| 332 | +- 推荐/搜索曝光明细里最好**写入当时的关键快照字段**(至少 in_stock、price),避免事后回算失真。 | ||
| 333 | + | ||
| 334 | +### 3.5 商品天级统计特征(训练/排序最常用) | ||
| 335 | +- 由 `dws_item_daily` 派生: | ||
| 336 | + - 1d/7d/28d:search_ctr、search_cvr、rec_ctr、rec_cvr、atc_rate、purchase_rate | ||
| 337 | + - 热度、趋势(环比/同比)、退货/取消(如可得) | ||
| 338 | + - 分 placement 的表现(同一商品在“购物车推荐”与“首页推荐”差异巨大) | ||
| 339 | + | ||
| 340 | +### 3.6 标签(Label)与训练样本(建议一开始就定口径) | ||
| 341 | +- **曝光→点击**(CTR)标签:以 `impression_item` 为样本 | ||
| 342 | +- **点击→加购/购买**(CVR/GMV)标签:以 click 或 impression 为样本,设定归因窗口 | ||
| 343 | +- 负样本:同一 trace_id 下未被点击的曝光 item(更稳定) | ||
| 344 | + | ||
| 345 | +--- | ||
| 346 | + | ||
| 347 | +## 4)你提到的两项“未完成工作”,数据层如何补齐 | ||
| 348 | +- **商品曝光(搜索/推荐)**:必须新增 `ExposureEvent`(建议一屏/一次渲染批量上报 item 列表 + position + module/placement + trace_id) | ||
| 349 | +- **链路跟踪**:以 `trace_id` 为主串联;若购买跨会话严重,可在 purchase 上补 `attribution_trace_id` 或单独落 `attribution_touch` 表做归因 | ||
| 350 | + | ||
| 351 | +--- | ||
| 352 | + | ||
| 353 | +如果你愿意,我可以基于你们现有 `proto`/事件骨架,把“搜索请求/曝光/点击/加购/购买/推荐请求”这几类事件的**最小必填字段清单**列成一张表(直接给前端埋点和后端 ETL 用),并给出每个指标(CTR/CVR/zero/no-click)的**精确定义与 SQL 计算口径**。 | ||
| 0 | \ No newline at end of file | 354 | \ No newline at end of file |
frontend/static/js/tenant_facets_config.js
| @@ -33,32 +33,53 @@ const TENANT_FACETS_CONFIG = { | @@ -33,32 +33,53 @@ const TENANT_FACETS_CONFIG = { | ||
| 33 | // tenant_id=170: 使用首字母大写的规格名称(Color, Size),没有material | 33 | // tenant_id=170: 使用首字母大写的规格名称(Color, Size),没有material |
| 34 | "170": { | 34 | "170": { |
| 35 | specificationFields: [ | 35 | specificationFields: [ |
| 36 | - { | ||
| 37 | - field: "specifications.Color", | 36 | + { |
| 37 | + field: "specifications.Color", | ||
| 38 | label: "Color", | 38 | label: "Color", |
| 39 | containerId: "colorTags", | 39 | containerId: "colorTags", |
| 40 | - size: 20, | ||
| 41 | - type: "terms", | ||
| 42 | - disjunctive: true | 40 | + size: 20, |
| 41 | + type: "terms", | ||
| 42 | + disjunctive: true | ||
| 43 | }, | 43 | }, |
| 44 | - { | ||
| 45 | - field: "specifications.Size", | 44 | + { |
| 45 | + field: "specifications.Size", | ||
| 46 | label: "Size", | 46 | label: "Size", |
| 47 | containerId: "sizeTags", | 47 | containerId: "sizeTags", |
| 48 | - size: 15, | ||
| 49 | - type: "terms", | ||
| 50 | - disjunctive: true | 48 | + size: 15, |
| 49 | + type: "terms", | ||
| 50 | + disjunctive: true | ||
| 51 | } | 51 | } |
| 52 | // 示例:如果170还有其他规格,可以这样添加: | 52 | // 示例:如果170还有其他规格,可以这样添加: |
| 53 | - // { | ||
| 54 | - // field: "specifications.Weight", | 53 | + // { |
| 54 | + // field: "specifications.Weight", | ||
| 55 | // label: "Weight", | 55 | // label: "Weight", |
| 56 | // containerId: "weightTags", | 56 | // containerId: "weightTags", |
| 57 | - // size: 15, | ||
| 58 | - // type: "terms", | ||
| 59 | - // disjunctive: true | 57 | + // size: 15, |
| 58 | + // type: "terms", | ||
| 59 | + // disjunctive: true | ||
| 60 | // } | 60 | // } |
| 61 | ] | 61 | ] |
| 62 | + }, | ||
| 63 | + // tenant_id=171: 与170配置相同 | ||
| 64 | + "171": { | ||
| 65 | + specificationFields: [ | ||
| 66 | + { | ||
| 67 | + field: "specifications.Color", | ||
| 68 | + label: "Color", | ||
| 69 | + containerId: "colorTags", | ||
| 70 | + size: 20, | ||
| 71 | + type: "terms", | ||
| 72 | + disjunctive: true | ||
| 73 | + }, | ||
| 74 | + { | ||
| 75 | + field: "specifications.Size", | ||
| 76 | + label: "Size", | ||
| 77 | + containerId: "sizeTags", | ||
| 78 | + size: 15, | ||
| 79 | + type: "terms", | ||
| 80 | + disjunctive: true | ||
| 81 | + } | ||
| 82 | + ] | ||
| 62 | } | 83 | } |
| 63 | }; | 84 | }; |
| 64 | 85 |