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

12 KiB
Raw Permalink Blame History

自动定时生成报告 — 方案设计

需求(已锁定):前端提供「手动 / 自动」两种模式。

  • 手动:保持现状不变 —— 用户选时间段,点一下,跑 AI 分析 + 生成报告。
  • 自动:用户设置一个间隔(如每 30 分钟 / 每小时 / 每天),系统到点自动跑「AI 分析 + 生成报告」全流程,无需人工点。

一、关键结论:地基已经存在,不用从零搭

调研现有代码后发现,自动模式的基础设施已经埋好了,只是之前被主动关掉

已有的东西 位置 现状
后台调度器 APScheduler scheduler.py 已在 lifespan 启动(main.py:43),目前只做账号/数据库切换检测
groups.poll_interval 字段 database.py:117 默认 300 秒,建群时可传(groups.py:12),但目前未被使用 —— 正好用来存「多久自动一次」
groups.cursor_seq 字段 database.py:115 为增量游标预留,目前未被使用
register_poll_job 轮询注册 scheduler.py:16 已废弃成空函数

最关键的一条线索 —— 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

_classify_lock = asyncio.Lock()
_classifying_group: int | None = None

groups.py:70-72:只要有群在分析,手动 /init 直接返回 409 已有群正在分析

影响:多个群各自定时,会互相撞锁。 对策:自动调度串成一条队列,并且自动任务要让位于手动(用户手点时不被自动任务卡住)。

约束 ② 分类是「全量重跑」,不是增量

topic_engine.py:1033:每次分类先 _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 数据库 schemadatabase.py

groups 表加一个字段(用现有 PRAGMA table_info 迁移模式补列):

auto_enabled  INTEGER DEFAULT 0   -- 是否开启该群的自动分析
last_run_at   DATETIME            -- 上次自动跑的时间(判断是否到期)

poll_interval(间隔)、cursor_seq(增量游标)已存在,直接复用。

6.2 调度器(scheduler.py

  • 新增 _auto_analyze_tick() jobinterval 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

新增 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)。
  • 不调用 _delete_ai_topics(这是和手动模式的关键区别)。
  • 把新消息走「补充分配」逻辑归入已有话题或新建话题(复用 _supplement_assignments_coalesce_device_issue_topics)。
  • 收集本轮被改动 / 新建的 topic_id 列表。
  • 更新 cursor_seqlast_run_at

6.4 自动报告(summary_engine.py

  • run_auto_analyze 分类完后,对「本轮有变动的话题」逐个 await run_summarize(topic_id, topic)
  • run_summarize 已支持「已存在则更新」(summary_engine.py:444-454),直接复用。

6.5 接口(groups.py

  • GroupPatch 增加 auto_enabledpoll_intervalpatch_group 支持更新。
  • (可选)GET /api/groups/{id}/auto-status:返回 auto_enabled / poll_interval / last_run_at / 下次预计运行时间,给前端展示状态。

6.6 前端(群管理 / SettingsPage.jsx

  • 每个群一个「自动分析」开关 + 间隔下拉(如 30 分钟 / 1 小时 / 3 小时 / 每天)。
  • 手动模式 UI 保持不变(现有「查询时间段 + 手动分析」照旧)。
  • 调用 patchGroup(groupId, { auto_enabled, poll_interval })api/index.js:232 已有 patchGroup)。
  • (可选)展示「上次自动分析时间 / 下次预计时间」。

七、风险与注意点

  1. LLM 成本是头号风险:间隔越短、群越多,成本越高。方案 A 增量 + 报告只刷变动话题已是最省的组合;前端间隔下拉建议最小 30 分钟,不要给「每 5 分钟」这类档位。
  2. 自动要让位手动tick 处理前必须检查 get_classifying_group(),避免占着锁让用户手动分析报 409。
  3. chatlog 服务可能未就绪tick 要捕获 MessageIndexNotReadychatlog_client.py:17),跳过本轮、不更新游标,下轮重试。
  4. 账号切换cursor_seqgroups 表存,数据库随微信账号切换(database.py:80),游标天然隔离。但要确认 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。

本文档为方案设计,未改动任何代码。确认第四节窗口策略后即可进入实现。