统一 voc_llm 密钥解析与默认模型;向量化改为本地 mlx 模型;更新 README、gitignore 与流水线文档。 Co-authored-by: Cursor <cursoragent@cursor.com>
308 lines
14 KiB
Markdown
308 lines
14 KiB
Markdown
# 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` 七步流程一致。*
|