#给 AnythingLLM 实现本地文件夹自动同步

16 阅读3分钟

背景

AnythingLLM 是一个本地部署的 RAG(检索增强生成)知识库工具,支持将文档上传后进行向量化,然后通过对话的方式检索知识。但它本身不支持监控本地文件夹——每次新增或修改文档都需要手动上传,用起来很不方便。

我的需求很简单:在本地维护一个 Obsidian 笔记文件夹,文件有任何变化时自动同步到 AnythingLLM 的指定工作区,不需要任何手动操作。

方案

基于 watchdog 的实时文件监控守护进程,彻底解决重复上传问题。

核心架构

文件系统事件 → 防抖(2s) → MD5 对比(SQLite) → 清理旧向量 → 上传新文件 → 更新 SQLite
     ↑
  启动时追赶扫描(处理离线期间的变更)

关键设计决策

1. watchdog 替代轮询

watchdog 是一个 Python 库,封装了 macOS 的 FSEvents / Linux 的 inotify API,让操作系统内核在文件变化时主动推送事件,不需要周期性扫描。进程绝大多数时间在睡眠,CPU 和 I/O 开销近乎为零。

2. SQLite 替代 JSON 状态文件

存储结构:(rel_path TEXT PK, hash TEXT, mtime REAL, doc_name TEXT, doc_location TEXT)

SQLite 的原子事务保证了进程崩溃时状态不会损坏。上传成功后记录 AnythingLLM 返回的 doc_location,下次更新时用它精确删除旧版本,不再依赖模糊的文件名匹配。

3. 2 秒防抖

Obsidian 等编辑器保存文件时会触发多次写入事件,防抖机制将其合并,只处理最后一次。

4. 正确的 API 调用链

研究了 AnythingLLM 源码后,确认正确的更新流程是:

① 从工作区移除旧嵌入(向量数据库)
   POST /api/v1/workspace/:slug/update-embeddings
   { "deletes": ["custom-documents/xxx.json"] }

② 删除旧文档文件
   DELETE /api/v1/system/remove-documents
   { "names": ["custom-documents/xxx.json"] }

③ 上传新文件并自动嵌入到工作区
   POST /api/v1/document/upload
   multipart: file + addToWorkspaces=slug

步骤 ① 是关键——不清除旧向量的话,LLM 检索到的仍然是旧内容。

5. 启动追赶扫描

守护进程启动时对比本地文件和 SQLite 记录,处理离线期间的所有变更,保证即使进程临时停止,重启后也不会漏掉任何变化。

6. LaunchAgent 守护进程

macOS 用 KeepAlive: true 的 LaunchAgent,崩溃自动重启,开机登录后自动启动,不依赖终端。

<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>

坑:macOS 文件夹访问权限

macOS Catalina 以后,对"文稿"等目录的 FSEvents 监控需要显式授权。进程没有权限时 watchdog 会静默失败——不报错,但完全收不到任何文件事件。

解决方法:在「系统设置 → 隐私与安全性 → 完全磁盘访问」中添加 /usr/bin/python3 或终端 App。

配置说明

[anythingllm]
base_url = http://localhost:3001      # AnythingLLM 服务地址
api_key = ...                         # API 密钥
workspace_slug = ...                  # 目标工作区标识

[sync]
watch_folder = /path/to/folder        # 监控的本地文件夹(绝对路径)
delete_removed = false                # 本地删除文件后是否同步删除远程文档

api_key 获取方式

在 AnythingLLM 界面:设置 → 工具 → 开发者 API → 生成新的 API 密钥

workspace_slug 获取方式

workspace_slug 不是工作区名称,需要通过 API 查询:

curl -s http://localhost:3001/api/v1/workspaces \
  -H "Authorization: Bearer 你的API-Key"

返回结果中的 slug 字段即为所需值:

{
  "workspaces": [
    {
      "name": "我的知识库",
      "slug": "my-kb-a1b2c3",
      "id": 1
    }
  ]
}

效果

  • 保存文件后约 2 秒内自动同步到 AnythingLLM
  • 不再出现重复文档
  • 更新文档后 LLM 能正确检索到新内容
  • 后台常驻,内存占用约 25MB,平时 CPU 占用 0%

依赖

  • Python 3(系统自带)
  • requests:HTTP 请求
  • watchdog:文件系统事件监听
  • sqlite3:Python 标准库自带,无需安装
pip3 install requests watchdog