Skip to content

在线唯一真源模式(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 调用结束后,批次里的所有条目会被合并:

  1. 若 ID 已存在:更新 title/url/source 非空字段,并刷新 updatedAt
  2. 若 ID 不存在:插入并填充 createdAt/updatedAt = 当前时间
  3. 写回 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

说明:

  1. 回填脚本与 fast-ingest 使用同一套文件扫描与去重策略(中文优先、博客去重、仅选 about/contact/community 中文),并已额外包含 book 目录(中英文章节全部纳入,不做去重)。
  2. chunk 切分规则保持一致(chunkText 800 字符),因此生成的 ID 与原始 ingest 时一致。
  3. 回填生成的 manifest 会将所有条目 createdAtupdatedAt 设为执行时间;之后新增或更新向量时会被正常增量更新。
  4. 如果之前已经部分生成 manifest,再执行 backfill 并强制覆盖会丢失旧的 createdAt 时间戳,请谨慎使用 --force
  5. 若需要针对更多内容类型(例如 docs、podcast、trans 等)回填,可修改 scripts/backfill-manifest.ts 中的 patterns 数组后重新生成。
  6. 运行后你将看到类似输出:扫描文件: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

说明:

  1. 仅为匹配到的 ID 生成条目,避免扩大覆盖范围。
  2. missing_vector_ids.txt 可用于后续:清理失效向量 / 排查规则变化 / 回收孤儿数据。
  3. 该方式与 backfill-manifest.ts 可二选一;若先做 ID 精确回填,再进行新增内容 ingest 即可保持一致。
  4. 如需忽略 missing 输出,可设置 OUTPUT_MISSING=false 环境变量。
  5. 若后续添加 docs/podcast/trans 等内容源,可扩展脚本内 patterns 并重新执行。

故障排除

如果遇到环境变量错误:

错误:缺少必要的环境变量 ADMIN_TOKEN 和 WORKER_URL

请参考 环境配置指南 正确设置所有必需的环境变量。