Files
get_wechat/自动定时生成报告-方案设计.md
yuanzhipeng 646efa132e ```
feat(api): 添加万川平台模型配置获取和同步功能

- 新增 getWanchuanModelConfig 函数,按模型编码获取平台模型配置
- 新增 syncWanchuanModelToSettings 函数,从万川平台拉取模型配置并写入后端 AI 设置
- 支持按用途分多个模型编码(generic/vision/voice)分别同步配置
- 配置失败时跳过对应字段,不影响其他模型同步

feat(settings): 重构AI模型配置界面支持多模块分组

- 将AI配置按话题分析、报告生成、视觉、语音四个模块分组展示
- 每个模块独立配置接口地址、密钥和模型名称
- 添加从万川平台获取配置的按钮和同步功能
- 优化配置状态指示和错误提示信息

refactor(config): 扩展AI配置支持独立的语音视觉报告网关

- 新增 voice_base_url/voice_api_key 配置项
- 新增 vision_base_url/vision_api_key 配置项
- 新增 summary_base_url/summary_api_key 配置项
- 留空时回退到 ai_base_url/ai_api_key 兼容单网关场景

refactor(http): 统一使用共享HTTP客户端减少连接开销

- 替换各处 httpx.AsyncClient 为 shared_client
- 在 lifespan 中正确关闭共享客户端资源
- 优化 get_current_wxid 和 health 检查中的HTTP请求

refactor(ai): 按用途缓存AI客户端支持不同网关配置

- 重构 get_openai_client 支持按(base_url, api_key)缓存
- 新增 get_client_for 函数按用途获取对应客户端
- 支持语音、视觉、报告等不同用途使用独立网关和密钥
```
2026-06-24 20:34:10 +08:00

218 lines
12 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.
# 自动定时生成报告 — 方案设计
> **需求(已锁定)**:前端提供「手动 / 自动」两种模式。
> - **手动**:保持现状不变 —— 用户选时间段,点一下,跑 AI 分析 + 生成报告。
> - **自动**:用户设置一个间隔(如每 30 分钟 / 每小时 / 每天),系统到点**自动**跑「AI 分析 + 生成报告」全流程,无需人工点。
---
## 一、关键结论:地基已经存在,不用从零搭
调研现有代码后发现,自动模式的基础设施**已经埋好了,只是之前被主动关掉**
| 已有的东西 | 位置 | 现状 |
|-----------|------|------|
| 后台调度器 APScheduler | [scheduler.py](chatlog_fastAPI/scheduler.py) | 已在 `lifespan` 启动([main.py:43](chatlog_fastAPI/main.py#L43)),目前只做账号/数据库切换检测 |
| `groups.poll_interval` 字段 | [database.py:117](chatlog_fastAPI/database.py#L117) | 默认 300 秒,建群时可传([groups.py:12](chatlog_fastAPI/routers/groups.py#L12)),但**目前未被使用** —— 正好用来存「多久自动一次」 |
| `groups.cursor_seq` 字段 | [database.py:115](chatlog_fastAPI/database.py#L115) | 为增量游标预留,**目前未被使用** |
| `register_poll_job` 轮询注册 | [scheduler.py:16](chatlog_fastAPI/scheduler.py#L16) | **已废弃成空函数** |
**最关键的一条线索** —— [scheduler.py](chatlog_fastAPI/scheduler.py) 顶部注释:
> APScheduler — 仅保留 wxid/数据库切换检测。
> (不再运行任何 AI 分类轮询AI 分析改为用户手动按时间窗口触发)
这说明自动轮询曾经跑过,后来被撤掉、改成了现在的「手动」模式。**撤掉的原因,正是新自动模式必须解决的坑。**
---
## 二、手动模式现在是怎么跑的(自动模式要复用它)
理解现状是设计自动模式的前提。手动模式的完整链路:
```
前端 ChatlogPage 选时间段 → POST /api/groups/{id}/init {start_time, end_time}
└─ run_classify_window(group_id, start_ts, end_ts) [topic_engine.py:960]
1. 全量拉取该时间段的消息
2. _delete_ai_topics 删掉旧 AI 话题 ← 注意这里是「全删重建」
3. 分批 LLM 分类 → 合并 → 补充分配 → 落库 topics
(报告仍需用户在前端对每个话题点「生成」)
└─ run_summarize(topic_id) [summary_engine.py:471]
从 chatlog 拉回话题消息原文 → LLM 生成 Markdown → 写 knowledge_docs
```
> 注意:手动模式目前**分类和报告是分两步**的。分类出话题后,报告要用户在前端逐个点。
> 自动模式的「全自动」就体现在:分类完,自动把需要的话题也一并生成报告,不用人点。
---
## 三、必须正视的三个约束(也是当年撤掉自动轮询的原因)
### 约束 ① 分类是全局串行的,一次只能跑一个群
[topic_engine.py:26-27](chatlog_fastAPI/services/topic_engine.py#L26-L27)
```python
_classify_lock = asyncio.Lock()
_classifying_group: int | None = None
```
[groups.py:70-72](chatlog_fastAPI/routers/groups.py#L70-L72):只要有群在分析,手动 `/init` 直接返回 `409 已有群正在分析`
**影响**:多个群各自定时,会互相撞锁。
**对策**:自动调度**串成一条队列**,并且**自动任务要让位于手动**(用户手点时不被自动任务卡住)。
### 约束 ② 分类是「全量重跑」,不是增量
[topic_engine.py:1033](chatlog_fastAPI/services/topic_engine.py#L1033):每次分类先 `_delete_ai_topics` 把旧 AI 话题**全删重建**。
**影响**:自动模式如果每轮都对一个大窗口(如最近 7 天)重跑,每轮都把这些消息**全部重新喂 LLM**。间隔越短,成本越爆。**这几乎可以肯定是当年撤掉自动轮询的主因。**
**对策**:见第四节的窗口策略 —— 这是本方案唯一需要你拍板的关键点。
### 约束 ③ 自动跑报告 = 话题数 × LLM 调用
全自动生成报告时,每个有更新的话题都要调一次 LLM。
**对策**:报告只对**本轮真正有新消息变动的话题**生成/更新,不要把所有历史话题每轮重刷。
---
## 四、唯一需要你拍板的点:自动模式每轮分析「哪段消息」
这是整个方案的核心,直接决定**成本**和**改动量**。三个候选:
### 方案 A自上次运行至今增量按时间—— **推荐**
每轮窗口 = `[上次运行时间, 现在]`,只分析这段时间的新消息。
- **成本**:低且稳定,只跟「新消息量」相关,跟历史总量无关。
- **要解决的冲突**:现有 `run_classify_window``_delete_ai_topics` 全删。增量模式下**不能删历史话题**,要把新消息**补充归并**到已有话题或新建话题(复用现有 `_supplement_assignments` / `_coalesce_device_issue_topics` 的思路)。
- **改动量**:中。是本方案最核心的改造。
### 方案 B固定回看窗口每轮全量重跑最近 N 天)
每轮窗口 = `[现在 - N 天, 现在]`,直接复用现有 `run_classify_window`(含全删重建)。
- **成本**:随间隔频率线性上升,且每轮重复分析窗口内所有消息。
- **优点**:几乎不用改分类逻辑,最快跑通。
- **代价**:① 成本高;② 超出窗口的历史话题会被删掉(因为全删重建只覆盖窗口内消息)。**不适合做知识库沉淀。**
### 方案 C混合增量为主 + 每天全量重跑一次)
平时走方案 A 增量;每天凌晨全量重跑一次,重组当天话题、提升分类精度。
- **成本**:介于 A、B 之间。
- **优点**:兼顾成本与分类质量(增量归并精度略低于全量重组,每天校正一次)。
- **改动量**最大A 的全部 + 一个定时全量任务)。
> **我的建议**:先按**方案 A** 落地(成本可控、能沉淀知识库、符合"定时增量出报告"的直觉)。
> 若后续发现增量归并的分类精度不够,再加方案 C 的每日全量校正。
> **方案 B 不建议**,除非你只想快速验证、且能接受历史被覆盖。
---
## 五、推荐架构(按方案 A增量 + 全自动 + 每群独立间隔)
核心思路:**一个 tick 调度器扫表,挑到期的群丢进串行队列,逐个增量处理并出报告。**
```
APScheduler 每 60 秒 tick 一次(单个 job不是每群一个
├─ 扫描所有 groups挑出满足条件的群
│ auto_enabled = 1 且 now - last_run_at >= poll_interval
└─ 把到期的群按顺序逐个 await 处理(串行,复用 _classify_lock
└─ 对每个群 run_auto_analyze(group_id)
1. 读 groups.cursor_seq只拉游标之后的新消息get_messages 已支持 min_seq
2. 无新消息 → 更新 last_run_at结束
3. 增量分类:把新消息归并进已有话题 / 新建话题(不删历史)
4. 对「本轮有新增消息的话题」逐个 run_summarize 生成/更新报告
5. 更新 cursor_seq = 本轮最大 seqlast_run_at = now
```
### 为什么这样设计
| 设计点 | 原因 |
|--------|------|
| **单 tick job 扫表**,而非每群一个 APScheduler job | 新增/删群、改间隔都不用动调度器;天然串行,避开并发撞锁 |
| **用 `cursor_seq` 增量** | 字段现成,从根本上解决「全量重跑」的成本问题 |
| **串行队列 + 自动让位手动** | 符合现有 `_classify_lock` 全局串行约束,用户手点时优先 |
| **报告只生成有变动的话题** | 避免每轮把所有历史话题重刷 LLM |
| **间隔存 `groups.poll_interval`** | 字段现成,天然支持「每群单独设多久自动一次」 |
---
## 六、具体改动清单
### 6.1 数据库 schema[database.py](chatlog_fastAPI/database.py)
`groups` 表加一个字段(用现有 `PRAGMA table_info` 迁移模式补列):
```sql
auto_enabled INTEGER DEFAULT 0 -- 是否开启该群的自动分析
last_run_at DATETIME -- 上次自动跑的时间(判断是否到期)
```
`poll_interval`(间隔)、`cursor_seq`(增量游标)已存在,直接复用。
### 6.2 调度器([scheduler.py](chatlog_fastAPI/scheduler.py)
- 新增 `_auto_analyze_tick()` job`interval` 60 秒。
- tick 内扫 `groups`,挑 `auto_enabled=1 AND (last_run_at IS NULL OR now - last_run_at >= poll_interval)`
- 到期的群**逐个 `await`** 处理(串行,不用 `create_task` 并发)。
- 处理前检查 `get_classifying_group()`,若手动任务在跑则本轮跳过,下轮再来(让位手动)。
### 6.3 增量分析逻辑([topic_engine.py](chatlog_fastAPI/services/topic_engine.py)
新增 `run_auto_analyze(group_id)`(与现有 `run_classify_window` 并存,不破坏手动):
-`cursor_seq`,调 `chatlog_client.get_messages(talker, min_seq=cursor_seq+1, ...)` 只拉新消息(`get_messages` 已支持 `min_seq`,见 [chatlog_client.py:77](chatlog_fastAPI/services/chatlog_client.py#L77))。
- **不调用** `_delete_ai_topics`(这是和手动模式的关键区别)。
- 把新消息走「补充分配」逻辑归入已有话题或新建话题(复用 `_supplement_assignments``_coalesce_device_issue_topics`)。
- 收集本轮被改动 / 新建的 `topic_id` 列表。
- 更新 `cursor_seq``last_run_at`
### 6.4 自动报告([summary_engine.py](chatlog_fastAPI/services/summary_engine.py)
- `run_auto_analyze` 分类完后,对「本轮有变动的话题」逐个 `await run_summarize(topic_id, topic)`
- `run_summarize` 已支持「已存在则更新」([summary_engine.py:444-454](chatlog_fastAPI/services/summary_engine.py#L444-L454)),直接复用。
### 6.5 接口([groups.py](chatlog_fastAPI/routers/groups.py)
- `GroupPatch` 增加 `auto_enabled``poll_interval``patch_group` 支持更新。
- (可选)`GET /api/groups/{id}/auto-status`:返回 `auto_enabled / poll_interval / last_run_at / 下次预计运行时间`,给前端展示状态。
### 6.6 前端(群管理 / [SettingsPage.jsx](chatlab-web/frontend/src/pages/SettingsPage.jsx)
- 每个群一个「自动分析」开关 + 间隔下拉(如 30 分钟 / 1 小时 / 3 小时 / 每天)。
- 手动模式 UI 保持不变(现有「查询时间段 + 手动分析」照旧)。
- 调用 `patchGroup(groupId, { auto_enabled, poll_interval })`[api/index.js:232](chatlab-web/frontend/src/api/index.js#L232) 已有 `patchGroup`)。
- (可选)展示「上次自动分析时间 / 下次预计时间」。
---
## 七、风险与注意点
1. **LLM 成本是头号风险**:间隔越短、群越多,成本越高。方案 A 增量 + 报告只刷变动话题已是最省的组合;前端间隔下拉**建议最小 30 分钟**,不要给「每 5 分钟」这类档位。
2. **自动要让位手动**tick 处理前必须检查 `get_classifying_group()`,避免占着锁让用户手动分析报 409。
3. **chatlog 服务可能未就绪**tick 要捕获 `MessageIndexNotReady`[chatlog_client.py:17](chatlog_fastAPI/services/chatlog_client.py#L17)),跳过本轮、**不更新游标**,下轮重试。
4. **账号切换**`cursor_seq``groups` 表存,数据库随微信账号切换([database.py:80](chatlog_fastAPI/database.py#L80)),游标天然隔离。但要确认 tick 用的是 `get_active_db_path()` 当前库。
5. **增量分类精度权衡**:增量归并(新消息往已有话题靠)比全量重组精度略低。若要求高,用方案 C 每日全量校正一次。
6. **首次开启自动的冷启动**:群刚开自动且 `cursor_seq=0` 时,第一轮会把全部历史拉进来分析。建议开启自动时让用户先手动跑一次(或把 `cursor_seq` 初始化为当前最新 seq避免首轮巨量分析。
---
## 八、建议的落地顺序
1. **先确认第四节的窗口策略**(推荐方案 A
2. schema 加字段 + `groups.py` 接口打通(小改动,先让「开关 + 间隔」能存能读)。
3. 调度器接 tick + 串行队列骨架(先只 `log`「本应处理 group X」不真跑 LLM验证调度和让位逻辑正确
4. 接增量分类 `run_auto_analyze`(先不出报告,验证话题增量归并正确)。
5. 接自动报告。
6. 前端配置 UI自动开关 + 间隔下拉)。
7. 小间隔实测 LLM 成本与分类质量,再决定前端开放哪些间隔档位、是否需要方案 C。
---
*本文档为方案设计,未改动任何代码。确认第四节窗口策略后即可进入实现。*