Commit Graph

14 Commits

Author SHA1 Message Date
Mavis
764de4e85c fix(worker): enrichment_loop 改并发 + 加大 batch
之前每轮只跑 3 篇串行,587 篇待 enrich 队列要 4.7h 才清完。

改动:
- ENRICHMENT_BATCH_SIZE: 3 -> 8
- ENRICHMENT_INTERVAL_SEC: 5 -> 2
- 处理 todo_ids 改 asyncio.gather 并发 3 篇
- LlmClient 内部 interval_sec 限速不变,这里只加并发上限

效果:每分钟 ~7 篇 -> 587 篇约 84 分钟清完。

排查过程中还发现根因: llm_settings 表空行导致 enrichment 静默跳过,
已手动 INSERT 默认 LlmSetting(id=1, enabled=true) 触发循环。
2026-06-10 17:20:53 +08:00
Mavis
3e56fed541 feat(translate): 接入腾讯 MaaS u2 作为 TMT 备用翻译通道
新通道:腾讯 MaaS u2 模型(云知声),OpenAI 兼容协议
- 端点:https://maas-api.hivoice.cn/v1
- 模型:u2(翻译专用,实测 + 锁定 prompt 后译文质量稳定)
- 备用链路:TMT 配额耗尽 / TMT 失败时自动降级到 MaaS

关键 prompt 工程(锁定):
- 必须用 user 提供的固定中文 prompt,否则 u2 会把译文放进 reasoning_content 而 content 返乱码
- 限定只接 EN/JA → ZH
- 中文输入固定返回拒绝文案

新增/改动:
- backend/app/services/translation/tencent_maas.py: 新建
- backend/app/services/translation/service.py: 备用链 maas → local,初始化失败友好降级
- backend/app/config.py: 加 tencent_maas_* 4 个配置
- .env.example: 文档化
2026-06-09 17:33:45 +08:00
Mavis
a5bfb7d49a fix(worker): enrichment_loop 永远只扫老文章(已 enrich),新文章被排到最后
bug 复现:
- order_by translated_at asc nullslast
- 老文章已 enrich,translated_at 有值,排前
- 新文章 translated_at=NULL,nullslast 排最后,limit5 永远拿不到

修复:
- order_by 改为 Article.id.asc()(新文章 id 大)
- ENRICHMENT_BATCH_SIZE 1→3(并发候选)
- 文章间 sleep 0.5→0.2s

效果:enrichment_loop 现在会持续 enrich 新进文章,首页列表会逐步有分类/评论
2026-06-09 17:07:07 +08:00
Mavis
76e95908e8 fix(llm): _safe_format 防 ValueError,模板里示例 JSON 也能正常 format
bug: classify_prompt 默认值里含示例 JSON {\\"categories\\": [...]},str.format
     看到花括号就试图解析为 placeholder/format spec,遇到 \\" 时抛:
       ValueError: Invalid format specifier ' [\\"时政\\"]' for object of type 'str'

修复:
- 引入 placeholder_re 提取所有合法 {varname} 占位符,stash 成 sentinel
- 剩余的 { / } 一律 escape 成 {{ / }},str.format 自然还原
- 用户显式写的 {{ / }}(标准转义)单独 stash,不被重复 escape
- 极端情况(KeyError/IndexError/ValueError)兜底:按原文返回,记录 warn

8 个本地单测全过(含示例 JSON 模板 / 老 prompt 缺变量 / 用户显式 {{ 场景)
2026-06-09 15:14:53 +08:00
Mavis
da895c2c5f feat(llm): classify 前置 + 黑名单 drop 删文章 + 排版用 .diary-para
- enrichment._enrich_classify 前置,返回 (drop, categories)
  - 注入 {blocklist} 占位符到 prompt(全局 + per-source 合并)
  - drop=True → 整篇 DELETE,后 3 步直接 skip
  - 兜底:即使 LLM 没正确返回 drop 字段,本地也匹配一次
- enrichment._enrich_format 排版段落 class 名固定为 diary-para
  - CSS 仍内联到 style,前端 .diary-para 兜底
- enrichment._merge_blocklist: 全局 + per-source 合并去重保序
- schemas/llm.LlmSettingOut/Update 暴露 blocklist_tags
- DEFAULT_PROMPTS.classify_prompt 加 {blocklist} + drop 字段说明
2026-06-09 14:34:18 +08:00
Mavis
8d73f4fb28 fix(llm+worker+deploy): 兼容老 prompt 模板 + 消除 startup_run 日志噪音
- enrichment: 新增 _safe_format (基于 _SafeDict),缺失占位符保留原样不抛 KeyError。
  _enrich_format / _enrich_classify / _enrich_image / _enrich_commentary
  全部走 _safe_format,数据库里老 prompt(不支持 {body})不再让整条 article 卡住。
  复现: 388183 classify 一直 KeyError,enrichment_loop 反复重试它,316 篇全卡在 n/a。
- workers.__main__: startup_run 从 IntervalTrigger(minutes=0) 改成 DateTrigger
  (只跑一次),消除 'maximum number of running instances reached' 刷屏 WARNING。
- deploy_pull: 改 _connect 自动识别 RSA / Ed25519 / ECDSA key(原硬编码 Ed25519Key)
2026-06-08 21:20:43 +08:00
Mavis
380e8b124e feat(llm): 排版容器固定CSS + 插图用正文第一段 + 适中尺寸
- enrichment._enrich_format:把排版好的段落包到带固定 CSS 的 <div class=article-body> 里
  (font: system-ui / 17px / line-height 1.7 / color #3e3e3e / p margin-bottom 1.5em)
  CSS 同时内联到 style 属性,前端 .article-body 全局类做兑底
- enrichment._enrich_image:prompt 改用 body_zh_text 的第一段(原为 title);
  新增 {body} 占位符,image_prompt_template 默认模板同步改写
- 插图尺寸写死为 768x512(适中);image_size 字段保留供用户手改但默认行为不依赖它
- 分类明确多标签(2-5 个),提示词加 {body} 变量,容错读 categories/tags 两种 key
- AdminLlmSettings.vue:placeholder / 变量说明同步更新
2026-06-08 20:53:21 +08:00
Mavis
a5548d6e64 fix(fetcher): fulltext 抓取用真实浏览器 UA,绕过 NHK 等 403 2026-06-08 15:55:30 +08:00
Mavis
6b5828c1c0 fix(translation): 规范化 BCP-47 lang_src(避免 en-gb/zh-cn 等被 TMT 拒) 2026-06-08 15:49:03 +08:00
Mavis
ffd667f0dc feat(llm): 新增 LLM 智能增强服务(Agnes client + 4 项 enrichment 任务 + admin API + migration) 2026-06-08 14:24:00 +08:00
Mavis
639562593e fix: 翻译失败/降级文本不再写 cache(避免 30 天污染)
之前 service.translate 写 cache 无条件,导致:
- 第一次翻译失败时,'[翻译失败: ...]' 占位符被写进 cache
- 30 天内相同文本的请求(新文章 title 与老文章 title 相同时)全部返回占位符
- 触发 200+ 文章 title_zh 字段被永久污染

修法:仅在 engine ∈ {tencent, nllb, cache} 且文本不含错误标记时,才写 cache。
2026-06-08 00:48:36 +08:00
Mavis
9862a92423 perf: 翻译独立后台循环(1 篇/秒)+ Semaphore 1
之前 fetch_one_source 入库后立即调翻译(可能并发触发腾讯 TMT 限速)
改为独立 translation_loop 后台循环:
- 完全不和 RSS 抓取并行
- 1 篇/秒节拍(Semaphore 1 + sleep 1.0)
- 没活时空闲 5 秒再轮询
- pending/failed 都重试
2026-06-08 00:27:09 +08:00
Mavis
cc02d39d29 fix: 翻译主流程失败时 raise(不再返回占位符); add_usage TTL 用 replace(day=1) 防 0 TTL 2026-06-07 23:58:13 +08:00
Mavis
60b062daf2 feat: initial MVP - FastAPI backend + Vue3 frontend + docker-compose
- backend: FastAPI + SQLAlchemy 2.0(async) + asyncpg + Alembic
- 7 API routes: auth/me/articles/sources/bookmarks/subscriptions/admin
- models: User/Source/Article/Bookmark/Subscription/ApiToken
- services: RSS fetcher (feedparser) + Tencent TMT translator with quota + cache + local NLLB fallback
- workers: APScheduler + asyncio pipeline (fetch -> dedupe -> insert -> translate)
- seed scripts: create_user, seed_sources (5 RSS: Reuters/BBC/Al Jazeera/NHK/DW)
- frontend: Vue 3 + Vite + Naive UI + Pinia + vue-router
- pages: Login, Feed (24h), ArticleDetail, Sources, Bookmarks, AdminSources
- deploy: docker-compose (postgres/redis/api/worker/frontend/caddy)
- docs: README, DEPLOY, architecture, acceptance
2026-06-07 21:51:01 +08:00