amz_review_analyse/main_voc分析.md
OnesvmWhoops 91e6c47fc0 迁移 DeepSeek Chat API,并支持本地 Qwen3 Embedding 向量化。
统一 voc_llm 密钥解析与默认模型;向量化改为本地 mlx 模型;更新 README、gitignore 与流水线文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 16:15:03 +08:00

308 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# main_voc分析.py 说明文档
VOC(Voice of Customer)分析**全流程编排入口**:合并原始 CSV → 清洗 → LLM 结构化 → 向量化 →(聚类 + 词频)→ 生成 HTML 报告。
> 本文档与 `main_voc分析.py` 同目录放置,风格对齐 `prompts/README.md`(表格 + 可执行示例 + 路径约定)。
---
## 1. 流程总览
```mermaid
flowchart LR
S1[步骤1 合并CSV] --> S2[步骤2 清洗]
S2 --> S3[步骤3 结构化 LLM]
S3 --> S4[步骤4 向量化 API]
S4 --> S5[步骤5 聚类 UMAP+HDBSCAN]
S4 --> S6[步骤6 词频 spaCy+LLM]
S5 --> S7[步骤7 报告 HTML]
S6 --> S7
```
| 步骤 | 模块 | 是否调用 DashScope API |
|------|------|------------------------|
| 1 | `合并评论数据.py` | 否 |
| 2 | `content清洗.py` | 否 |
| 3 | `结构化_server.py` | **是**(Chat 结构化;默认 8 路批间并行) |
| 4 | `向量化.py` | **是**(Embedding;默认 8 路批间并行) |
| 5–6 | `聚类.py` ∥ `词频.py` | **是**(聚类调参评估 + 词频术语提取) |
| 7 | `voc_report.py` | **是**(报告撰写、词频分类、翻译等) |
步骤 5 与 6 在 `from_step ≤ 5` 且两步均需执行时,由 `ThreadPoolExecutor(max_workers=2)` **并行**运行。
---
## 2. 命令行输入 / 输出
### 2.1 输入(CLI 参数)
| 参数 | 必填条件 | 默认值 | 说明 |
|------|----------|--------|------|
| `--input-dir` | 是 | — | 原始 VOC评论数据文件 CSV 目录(目录内的所有`*.csv` 表头须一致) |
| `--industry` | 是 | `Pet Supplies` | 行业名,写入结构化任务 |
| `--product` | 是 | — | 产品名 |
| `--merged-csv` | 否 | `merged_reviews.csv` | 步骤 1 输出路径 |
| `--cleaned-csv` | 否 | `merged_reviews_cleaned.csv` | 步骤 2 输出路径 |
| `--from-step N` | 否 | `1` | 从第 N 步执行到结束(N=1…7) |
| `--only-step N` | 否 | — | 仅执行第 N 步(需已有前置产物) |
| `--keep-db` | 否 | 默认**会**清理库 | 保留已有三个 `.sqlite`,不覆盖删除 |
| `--clean-intermediates` | 否 | 关闭 | 步骤 7 成功后删除中间 csv/sqlite,仅保留报告目录 |
| `--skip-wordfreq-llm` | 否 | 关闭 | 词频复用已有 `output/voc_terms.json`,跳过 LLM 术语提取 |
| `--filter-small-clusters` | 否 | 关闭 | 报告仅纳入簇内去重评论占比 ≥ 10% 的簇 |
| `--save-llm-raw` | 否 | 关闭 | 将报告 LLM 完整原文写入 `{product}_voc_report.html` 同目录 `report_llm_raw.txt` |
| `--struct-workers` | 否 | `8`(`VOC_STRUCT_WORKERS`) | 步骤 3 结构化**批间并行** Chat 请求数 |
| `--embed-workers` | 否 | `8`(`VOC_EMBED_WORKERS`) | 步骤 4 向量化**批间并行** Embedding 请求数 |
### 2.2 输出
**标准输出(stdout)**:流程结束后打印 JSON 字典(`ensure_ascii=False`),键随实际执行步骤变化,常见字段:
| 键 | 含义 |
|----|------|
| `merged_csv` | 合并后 CSV 路径 |
| `cleaned_csv` | 清洗后 CSV 路径 |
| `structured_job_id` | 结构化任务 ID |
| `structured_db` | `voc_structured.sqlite` |
| `embed` | 向量化模块返回信息(含 `job_id` 等) |
| `clustering` | 聚类模块返回信息 |
| `wordfreq` | 词频模块返回信息 |
| `report_html` | 报告 HTML 绝对/相对路径 |
| `report` | `generate_report` 返回值 |
**磁盘产物(项目根为 `PROJECT_ROOT`,即本文件所在目录)**:
| 路径 | 产生步骤 | 说明 |
|------|----------|------|
| `merged_reviews.csv` | 1 | 多文件纵向合并 |
| `merged_reviews_cleaned.csv` | 2 | 清洗、去重后的评论 |
| `voc_structured.sqlite` | 3 | 结构化任务与逐条提取结果 |
| `voc_embeddings.sqlite` | 4 | 各实体短语 256 维向量 |
| `voc_clustering.sqlite` | 5 | 多阶段 UMAP+HDBSCAN 簇标签 |
| `output/voc_terms.json` | 6 | LLM 归纳的产品专有名词 / 停用词 |
| `output/word_freq.csv` | 6 | 全量词频表(`word`, `count`) |
| `output/{product}/{product}_voc_report.html` | 7 | 最终 VOC 分析报告(产品名经路径安全化) |
`{product}` 目录名由 `_safe_product_dir_name` 生成:去除 `<>:"/\|?*` 等非法字符。
### 2.3 常用命令
```bash
cd "/Users/onesvmwhoops/Cursor_Project/VOC_LLM结构化"
# 全流程(默认写入 sqlite 前会清理旧库;加 --keep-db 则保留)
./310py/bin/python main_voc分析.py --input-dir "某目录" --product "产品名"
# 从步骤 4 续跑(product 可省略)
./310py/bin/python main_voc分析.py --from-step 4 --keep-db
# 仅重跑词频 LLM 之前的 spaCy 统计
./310py/bin/python main_voc分析.py --from-step 6 --skip-wordfreq-llm
# 仅生成报告
./310py/bin/python main_voc分析.py --only-step 7
```
### 2.4 程序式调用
```python
from main_voc分析 import run_voc_analysis # 别名 run_pipeline
result = run_voc_analysis(
input_dir=Path("reviews_export"),
industry="Pet Supplies",
product_name="cat deterrent indoor",
merged_csv=Path("merged_reviews.csv"),
cleaned_csv=Path("merged_reviews_cleaned.csv"),
from_step=1,
clean_databases=True, # 对应 CLI 未加 --keep-db
)
```
---
## 3. 七步数据流与数学 / 算法
### 步骤 1:合并 CSV(`合并评论数据.py`)
- **输入**:目录内表头一致的 `*.csv`。
- **方法**:`pandas.concat` 纵向合并;表头不一致则报错。
- **输出**:单表 `merged_reviews.csv`(`utf-8-sig`)。
### 步骤 2:清洗(`content清洗.py`)
- **输入**:合并 CSV,正文列 `content`(或兼容列名由下游结构化处理)。
- **方法**(规则型,无机器学习):
- 去空行、亚马逊媒体占位文案、HTML 实体、常见噪声前缀;
- 保留含字母/数字的行的词数 ≥ 4;
- 按 `content` 去重(`keep="first"`)。
- **输出**:`merged_reviews_cleaned.csv`。
### 步骤 3:结构化(`结构化_server.py` + `结构化_Prompt.py` + `prompts/`)
- **输入**:清洗 CSV、`industry`、`product_name`。
- **方法**:DeepSeek **Chat Completions**(默认 `deepseek-v4-pro`,思考关闭),按**输入 token** 与**条数**动态分批(默认单批估算输入 ≤ 200K、最多 **200** 条/批,`VOC_STRUCT_BATCH_MAX_REVIEWS` 可覆盖);从评论中提取 JSON 字段(以 `prompts/schema.yaml` 为准)。
- **输出**:`voc_structured.sqlite`(`analysis_jobs`、`comment_extractions`)。
### 步骤 4:向量化(`向量化.py` + `local_embedding.py`)
- **输入**:最新或指定 `job_id` 的结构化实体;`embed_text` 由 audience / pain_point / aspect / opinion / aspect_opinion 等展开。
- **方法**:
- 本地 MLX:`Qwen3-Embedding-4B-mxfp8`(默认 **2560 维**,L2 归一化);
- 存储:`float32` 打包为 BLOB(`struct.pack`);
- 默认批大小 **16**、串行推理(`VOC_EMBED_BATCH_SIZE`);M4 16GB 实测峰值约 1.5GB。
- **输出**:`voc_embeddings.sqlite`(`embedding_items`)。
- **运行**:推荐 `./310py/bin/python`(Python 3.10+)。
### 步骤 5:聚类(`聚类.py`)
- **输入**:`voc_embeddings.sqlite` 中向量与元数据。
- **核心数学管线**(对每个聚类 stage 的子集):
1. **UMAP 降维**(`umap-learn`)
- `n_components = min(30, n-2)`
- `metric = cosine`
- `min_dist = 0.1`
- `random_state = 42`
- `n_neighbors` 由自动调参循环递增(初值 10,上限 45)
2. **HDBSCAN**(`hdbscan`)
- `min_samples = 1`,`cluster_selection_method = eom`
- 初始 `min_cluster_size = max(2, n // 20)`,若簇数 > 20 则增大 `min_cluster_size` 直至 ≤ 20 或无法再增
- 标签 `-1` 表示离群点(各 stage 是否参与后续见模块内注释)
3. **轮廓系数**(`sklearn.metrics.silhouette_score`)
- 在 UMAP 空间、非离群点上计算;用于早停:连续 8 轮中后 7 轮均低于窗口首值则回退轮廓最高的一轮。
4. **LLM 聚类质量评估**(非传统指标,辅助调参)
- 对各簇抽样短语,统计「跨簇语义相似」比例;> 10% 则继续增大 `n_neighbors`;≤ 10% 且轮廓 > 0.6 则停止。
- **特例**:子集样本数 `n < 10` 时不跑 HDBSCAN,每条独立簇 `0..n-1`。
- **输出**:`voc_clustering.sqlite`(多 stage:如 audience、簇内 pain_point、按情感分桶的 aspect_opinion 等)。
### 步骤 6:词频(`词频.py`)
- **第 1 步(LLM)**:固定种子 `SAMPLE_SEED=42` 随机 **25** 条 `content`,归纳产品专有名词与专属停用词 → `output/voc_terms.json`。
- **第 2 步(spaCy)**:
- 英文分词与停用词过滤(含 EN stop words + 自定义停用词);
- 专有名词按完整短语计数(protected spans);
- 词形归并:`_merge_word_forms` 将复数/时态等 lemmatize 后合并计数;
- 纯数字词剔除。
- **输出**:`output/word_freq.csv`。
### 步骤 7:报告(`voc_report.py` + `prompts/report/`)
- **输入**:`voc_clustering.sqlite`、`voc_embeddings.sqlite`、`merged_reviews_cleaned.csv`、`word_freq.csv`、可选 `voc_structured.sqlite`。
- **方法**:
- 从库中聚合各 stage 簇与代表短语;
- 多次 LLM 调用生成分析正文、词频六类归类、短语翻译等;
- 词云字号:`count^0.5` 映射到 `[14, 96]` px;
- 可选 `min_cluster_review_ratio`(默认 0.10)过滤小簇。
- **输出**:`output/{product}/{product}_voc_report.html`(内嵌词云、词频表、AI 报告、分簇表述)。
---
## 4. API Key 与模型
### Chat(步骤 3 / 5 / 6 / 7)
统一经 `voc_llm.py`,默认 **`deepseek-v4-pro`**(`chat_extra_body` 关闭思考),`https://api.deepseek.com`。步骤 7 主报告在 `voc_report.py` 单独使用 Pro + `reasoning_effort=max`。
密钥优先级:
1. `DEEPSEEK_API_KEY`
2. `DEEPSEEK_API_KEY_FILE`
3. 项目根 `.deepseek_key`
可选:`DEEPSEEK_MODEL`、`DEEPSEEK_BASE_URL`。
| 模块 | 用途 |
|------|------|
| `结构化_server.py` | 评论结构化 |
| `聚类.py` | 簇质量评估与调参 |
| `词频.py` | 专有名词 / 停用词 |
| `voc_report.py` | 报告、词频分类、翻译 |
### Embedding(步骤 4)
**无需 API Key**。本地目录 `Qwen3-Embedding-4B-mxfp8`(或 `VOC_EMBED_MODEL_PATH`)。
**仓库安全规范**(见 `.gitignore`):
- **禁止提交** `.deepseek_key`、`.dashscope_key`、`voc_structured.sqlite` 及含真实评论/密钥的敏感导出;
- 密钥仅通过环境变量或本机未跟踪文件提供;
- 文档与代码中勿写入真实 `sk-` 密钥。
---
## 5. 各模块职责
| 文件 | 职责 |
|------|------|
| `main_voc分析.py` | 七步编排、断点续跑、库清理策略、步骤 5/6 并行、CLI |
| `合并评论数据.py` | 目录多 CSV 合并为一张表 |
| `content清洗.py` | 评论正文清洗、过滤、去重 |
| `结构化_server.py` | LLM 结构化入库、job 管理、库清理钩子 |
| `结构化_Prompt.py` | 组装结构化 prompt(被 server 动态加载) |
| `向量化.py` | 结构化实体 → 256 维向量库 |
| `聚类.py` | UMAP + HDBSCAN 多阶段聚类 + LLM 调参 |
| `词频.py` | LLM 术语 + spaCy 全量词频 |
| `voc_report.py` | 聚类/词频/原文 → 单页 HTML 报告 |
| `prompts/` | 可编辑 prompt 与 `schema.yaml`(见 `prompts/README.md`) |
| `main_voc分析_jieba.py` | 可选变体入口:词频走 `词频_jieba.py`(jieba 分词),其余步骤与主流程类似 |
### main 内主要函数
| 函数 | 作用 |
|------|------|
| `run_voc_analysis` / `run_pipeline` | 核心流水线 |
| `_should_run` | 根据 `--from-step` / `--only-step` 判断是否执行某步 |
| `_latest_job_meta` | 从结构化库读最新 `job_id`、`industry`、`product_name` |
| `_resolve_industry_product` | CLI 下 product 自动补全 |
| `_clean_intermediates` | `--clean-intermediates` 时删除中间文件 |
| `_report_html_path` | 计算报告 HTML 路径 |
---
## 6. SQLite 与路径约定
| 库文件 | 写入步骤 | 主要内容 |
|--------|----------|----------|
| `voc_structured.sqlite` | 3 | `analysis_jobs`、`comment_extractions` |
| `voc_embeddings.sqlite` | 4 | `embedding_items`(向量 BLOB + 实体元数据) |
| `voc_clustering.sqlite` | 5 | 各 `stage` 簇标签、调参日志、过滤元数据 |
**默认清理策略**(未加 `--keep-db`):
- 步骤 3 运行 `run_analysis(clean_databases=True)` 时清理三个 sqlite(结构化写入逻辑见 `结构化_server.py`);
- 步骤 4 若未重跑步骤 3,main 会单独删除 `voc_embeddings.sqlite` 与 `voc_clustering.sqlite` 再向量化;
- 步骤 5/6 的 `reset_db` 与 `clean_databases` 联动。
**行号溯源**:结构化与向量化的 `source_row` 为 CSV **首条数据行为 1**;对应 `merged_reviews_cleaned.csv` 物理行号 = `source_row + 1`(第 1 行为表头)。
---
## 7. 依赖与环境
```bash
cd "/Users/onesvmwhoops/Cursor_Project/VOC_LLM结构化"
uv venv 310py --python 3.12 # 与 .gitignore 中 310py/ 一致
uv pip install --python 310py/bin/python -r requirements.txt
# uv 虚拟环境无 pip,勿用「python -m spacy download」;直接装模型 wheel:
uv pip install --python 310py/bin/python "en-core-web-sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl"
export DEEPSEEK_API_KEY="sk-xxx" # 或配置 .deepseek_key(勿提交)
# 向量化推荐:./310py/bin/python main_voc分析.py ...
```
`requirements.txt` 中与数学/ NLP 相关的主要包:`numpy`、`umap-learn`、`hdbscan`、`scikit-learn`、`spacy`、`openai`、`pyyaml`。
---
## 8. 维护说明
- 修改 LLM 话术:编辑 `prompts/` 下对应 `.md`,**勿改** `schema.yaml` 中 `report.markers` 四段标记名(见 `prompts/README.md`)。
- 修改主流程步骤顺序或默认路径:改 `main_voc分析.py` 后请同步更新**本文档**。
- 验收 prompt 加载:`./310py/bin/python prompts/smoke.py`(无需 Key);联调模型:`./310py/bin/python prompts/smoke.py --live`。
---
*文档版本:与仓库 `main_voc分析.py` 七步流程一致。*