在线唯一真源模式(Online Source of Truth)
当前系统采用“线上向量数据库 (Vectorize) 作为唯一真源”策略:
- 不再提供任何离线回填 / 诊断 / 差异对账脚本
- manifest 的产生与演进仅依赖实时的
/admin/upsert
与/admin/delete
操作 - 若 manifest 丢失或损坏,可通过一次幂等初始化 + (重新 ingest 必要内容)恢复
推荐操作:
# 首次/灾难恢复:初始化空 manifest(若已存在返回 created=false)
## 向量 Manifest (vectors/manifest.json)
# 日常新增 / 更新内容:使用 fast-ingest 或目录 ingest,内部会调用 /admin/upsert 并刷新 manifest
# 批量删除:准备一个包含要删除向量 ID 的文本文件(每行一个)
系统会在 R2 bucket `website-rag-config` 中维护一个 `vectors/manifest.json` 文件,记录已写入向量索引的基础元数据,便于:
- 调试或审计(列出有哪些向量及其来源)
- 后续增量重建、失效清理或对接外部分析
- 快速根据 URL/标题查找对应 vector id
审计或外部同步:直接读取 R2 上的 vectors/manifest.json
。
若需重新获取全文 ID 列表用于人工分析,应从 Vectorize 后端(或提供的管理 API)导出,而不是本地重算。
结构示例¶
{
"version": 1,
"generatedAt": "2025-08-27T08:00:00.000Z",
"total": 12345,
"vectors": {
"abc123ef4567-0": {
"id": "abc123ef4567-0",
"title": "示例文章标题",
"url": "https://jimmysong.io/blog/example/",
"source": "zh/blog/example/index.md",
"createdAt": "2025-08-26T12:34:56.000Z",
"updatedAt": "2025-08-27T08:00:00.000Z"
}
}
}
字段说明¶
字段 | 说明 |
---|---|
version | Manifest 版本号(向后兼容保留扩展) |
generatedAt | 最近一次写入(此次批量 upsert 后时间) |
total | 当前 vectors 中的去重条目数量 |
vectors | id -> 向量条目映射 |
id | 向量唯一 ID(由 URL+ 源路径+chunk 序号哈希生成) |
title | 原内容标题(截断处理) |
url | 页面访问 URL(语言处理后的最终地址) |
source | 原始 markdown 相对路径(content 下) |
createdAt | 首次出现时间(不会被后续更新覆盖) |
updatedAt | 最近一次 upsert 触达该 ID 的时间 |
更新策略¶
一次 /admin/upsert 调用结束后,批次里的所有条目会被合并:
- 若 ID 已存在:更新 title/url/source 非空字段,并刷新 updatedAt
- 若 ID 不存在:插入并填充 createdAt/updatedAt = 当前时间
- 写回 manifest.json(失败不会影响主向量写入)
注意事项¶
- 当前实现为简单读 - 改 - 写,适合低并发(典型 ingest 场景)。如需高并发可改为分片或引入 ETag 乐观锁。
- 不存储向量内容或文本片段,仅保存检索需要的轻量级索引信息。
- 可后续扩展字段:
language
,hash
,chunkIndex
,size
等。
初始化 (必需一次)¶
首次部署后可先显式创建空 manifest(幂等,可重复调用):
curl -X POST \
-H "Authorization: Bearer $ADMIN_TOKEN" \
"$WORKER_URL/admin/init-manifest"
返回:
{ "ok": true, "created": true }
再次调用:
{ "ok": true, "created": false }
既有向量一次性回填(Backfill)¶
如果在引入 manifest 功能前已经向 Vectorize 写入了大量向量(例如 5000+),可以使用回填脚本基于确定性 ID 规则重建 manifest(不会重新生成 embeddings,也不会访问向量索引):
# 进入 rag-worker 目录
cd tools/rag-worker
# 生成本地 manifest.backfill.json
npx tsx scripts/backfill-manifest.ts
# 预览条目数量(可选)
grep -c '"id"' manifest.backfill.json
# 上传到 R2(替换 <bucket> 为你的 R2 bucket 名称,如 website-rag-config)
wrangler r2 object put <bucket>/vectors/manifest.json --file manifest.backfill.json --force
说明:
- 回填脚本与
fast-ingest
使用同一套文件扫描与去重策略(中文优先、博客去重、仅选 about/contact/community 中文),并已额外包含book
目录(中英文章节全部纳入,不做去重)。 - chunk 切分规则保持一致(chunkText 800 字符),因此生成的 ID 与原始 ingest 时一致。
- 回填生成的 manifest 会将所有条目
createdAt
与updatedAt
设为执行时间;之后新增或更新向量时会被正常增量更新。 - 如果之前已经部分生成 manifest,再执行 backfill 并强制覆盖会丢失旧的 createdAt 时间戳,请谨慎使用
--force
。 - 若需要针对更多内容类型(例如 docs、podcast、trans 等)回填,可修改
scripts/backfill-manifest.ts
中的patterns
数组后重新生成。 - 运行后你将看到类似输出:
扫描文件:718
与完成生成:条目 4645
,表示已覆盖博客 + 书籍 + 关键静态页面。
基于线上向量 ID 精确回填¶
如果已经获取了线上真实向量 ID 列表(文件 all_vector_ids.txt
),可使用 ID 精确回填 方式,仅生成线上现存向量对应的 manifest 条目,并输出无法匹配的“孤儿”ID:
cd tools/rag-worker
npx tsx scripts/backfill-manifest-from-ids.ts
# 或使用 package.json 脚本
npm run backfill-manifest-from-ids
# 查看统计
grep -c '"id"' manifest.ids.backfill.json
wc -l missing_vector_ids.txt # 未匹配 ID 数量
# 上传覆盖
wrangler r2 object put <bucket>/vectors/manifest.json --file manifest.ids.backfill.json --force
脚本输出示例:
[ids-backfill] 载入线上向量 ID 数量:5905
[ids-backfill] 扫描 markdown 文件:1495
[ids-backfill] 匹配成功条目:5493
[ids-backfill] 未匹配(可能来源文件已删除或规则改变)条目:412
说明:
- 仅为匹配到的 ID 生成条目,避免扩大覆盖范围。
missing_vector_ids.txt
可用于后续:清理失效向量 / 排查规则变化 / 回收孤儿数据。- 该方式与
backfill-manifest.ts
可二选一;若先做 ID 精确回填,再进行新增内容 ingest 即可保持一致。 - 如需忽略 missing 输出,可设置
OUTPUT_MISSING=false
环境变量。 - 若后续添加 docs/podcast/trans 等内容源,可扩展脚本内 patterns 并重新执行。
故障排除¶
如果遇到环境变量错误:
错误:缺少必要的环境变量 ADMIN_TOKEN 和 WORKER_URL
请参考 环境配置指南 正确设置所有必需的环境变量。