产品 3.0 发布后的第一个周一,老李就接到了销售总监的电话。对方语气不善:“你们那个智能助手怎么回事?客户问怎么开通新上线的‘实时数据看板’,AI 说‘该功能尚在规划中’。人家客户直接截图产品界面甩过来——功能都有了,你们 AI 还在梦游?”
老李挂了电话,径直走到小王工位前。“我们知识库不是上个月才搭好吗?怎么这么快就过时了?”
小王从代码中抬起头,无奈地说:“老李,知识库搭好只是个开始。世界每天都在变——产品更新、制度调整、人员变动。你建了个静止的照片,但现实是个流动的电影。”
老李一屁股坐下,拧开保温杯:“那你说怎么办?难道要我每周手动更新一次?我可没那个人力。”
“不用手动。让知识库自己长耳朵——听着外面的变化,自动更新。”
三种同步模式:闹钟、门铃和影子
“怎么个‘长耳朵’法?”老李追问道。
小王走到白板前,画了三个图标。
“第一种,定时同步。就像你设了个闹钟,每天晚上两点,系统自动把文档仓库拉一遍,有新版本就更新向量。优点是简单,缺点是有延迟——下午两点更新的文档,用户可能要等到凌晨才能查到。对于紧急的安全漏洞公告,这种延迟可能出事。”
老李点头:“那第二种呢?”
“事件驱动同步。不设闹钟了,而是装个门铃。文档系统那边一有保存操作,就主动敲门通知知识库:‘喂,我更新了,你快来重新处理一下。’这就是 Webhook 机制——实时、精准,但要求源系统能主动推送。”
“第三种,CDC——变更数据捕获。这是最高级的玩法。你不在源头加任何代码,而是在数据库层面监听日志。MySQL 的 binlog、Postgres 的 WAL,任何增删改都会被一个监控程序捕获,然后转化成同步事件。就像你在墙上凿了个洞,能看见隔壁房间的一举一动,但隔壁甚至不知道你在看。”
图:知识同步的三种模式——从被动到主动
老李思索片刻:“那咱们公司的情况,产品文档全在 Git 上,用事件驱动应该最合适。Git 本身就支持 Webhook,有人 push 代码就触发同步。”
“正解。其实就是一个简单的流水线。”
增量更新:别每次都把房子推倒重建
“不过,Webhook 只解决了‘何时触发’的问题,还有个更要紧的:同步什么?”小王打开终端,调出一段示意图。
“最粗暴的做法是全量重建——每次文档有更新,就把整个知识库的向量全部删掉,重新分块、重新编码。小规模数据还能忍,但咱们现在上万份文档,全量重建一次要四十多分钟。这期间检索服务基本不可用。”
“聪明的方法是增量更新:只处理真正变了的那几篇文档。Git 的 commit diff 能精确告诉你哪些文件被修改、新增或删除。你只针对这些文件做重新向量化,不影响其余部分。”
老李眼睛一亮:“这个我熟。就跟数据库索引一样,改一行数据不用重建整个 B+ 树,只需要更新相关节点。”
小王点头,写了段示意代码:
import subprocess
import json
def get_changed_files(git_repo_path, last_sync_commit):
"""从 Git 仓库获取上次同步以来的变更文件列表"""
diff = subprocess.run(
["git", "diff", "--name-status", last_sync_commit, "HEAD"],
cwd=git_repo_path, capture_output=True, text=True
)
changes = []
for line in diff.stdout.strip().split("\n"):
if line:
status, _, filename = line.partition("\t")
changes.append({"status": status, "file": filename})
return changes
def incremental_update(changes, vector_db, embedding_model):
"""增量更新向量数据库"""
for change in changes:
if change["status"] == "D":
# 文件被删除,移除对应向量
vector_db.delete(where={"source": change["file"]})
else:
# 新增或修改,重新向量化后 upsert
content = read_file(change["file"])
chunks = split_text(content)
embeddings = embedding_model.encode(chunks)
vector_db.upsert(ids=[f"{change['file']}_chunk{i}" for i in range(len(chunks))],
embeddings=embeddings,
metadata={"source": change["file"]})
# 更新最后同步的 commit 指针
update_sync_pointer(get_head_commit(git_repo_path))“看到没有?只处理变更的文件,不影响其他文档的检索。增量同步一次通常几秒钟就能完成。”
老李指着 last_sync_commit 问:“这个指针存哪?”
“数据库里存一条记录,记录‘上次已同步到 commit abc123’。每次同步完就更新,下次从这个 commit 开始 diff。”
冲突处理:当两个来源同时修改了同一个文档
“那如果两个人在不同分支上改同一个文档,然后合并,会不会出乱子?”老李展现了架构师的担忧。
“确实会。这就是知识冲突。”小王在白板上写下一个场景:“比如 HR 部门把‘年假政策’从 10 天改成 15 天,同时行政部发了个修正通知说‘年假仍是 10 天,之前为笔误’。两份文档都 push 了,Webhook 触发两次同步,最终向量库里可能两条都留着。AI 检索时随机命中其中一个,用户就得到了矛盾的答案。”
“解决办法呢?”
“分两层。第一层,自动化冲突检测——上次我们搞的质检管道,矛盾检测模型可以在这里复用。新文档入库时,自动扫描是否与已有知识矛盾。发现冲突,暂时挂起,标记为‘待裁决’。”
“第二层,人工裁判规则。你可以预设优先级——比如‘以产品发布平台上的文档为准’、‘以最后修改时间为准’。不是所有矛盾都需要人工,很多可以被规则自动化解。”
老李若有所思:“所以事件驱动同步管道里,不止是更新向量,还要跑一遍质量检查。”
“对。同步管道 = 变更捕获 → 增量更新 → 质检门禁 → 确认入库。缺一步都不完整。”
同步监控:别让管道悄悄断了
“那么问题来了,”老李习惯性地拧开保温杯,“如果 Webhook 那边的服务器挂了,或者 Git 仓库的权限改了,导致同步静悄悄停了一周——我们怎么知道?”
“这就要靠同步监控了。”小王调出一个仪表盘示意图。
“每个知识源都配一个‘心跳检查’。比如每隔十分钟,系统向 Git 仓库发一次 API 请求,看看是否能连通。如果连续失败三次,发告警到群里。同时,记录‘最后同步时间戳’,如果某个知识源超过 24 小时没有新同步事件,就亮黄灯。”
“另外,你还可以设置一个‘基准对比’——随机抽取一些在线 AI 的回答,跟知识源里的原文做自动对比,如果发现 AI 回答的信息版本明显落后于源文档,说明同步可能出了延迟。”
老李看着那一连串的监控指标,点了点头:“这个监控面板,跟上次我们搞的 RAG 质量仪表盘,能合在一起吗?”
“已经合了。你看这个大屏——”小王把屏幕转过来,“左边是知识质量分数,右边是同步状态。绿色表示所有管道健康,黄色表示有延迟,红色表示断了。”
老李沉默了一会儿,拧开保温杯喝了一口,枸杞已经凉透,但他没在意。
“这么说,知识库不是一次性工程,而是个持续运转的系统。”
“对。就像你要保持身体健康,不能只在年初体检一次,而要每天吃饭、运动、规律作息。知识库的健康也一样——需要持续的更新、监控和纠错。”
老李站起来,走到窗边,看着楼下马路上来往的车辆。半晌,他转身说:“下周把文档库的 Webhook 同步管道搭好。先上产品文档和 FAQ,再逐步接入其他源。每条管道配心跳监控。”
他走到门口,又停住:“那什么,你刚才说 CDC 监听数据库日志,那如果我们未来把员工信息也接入知识库,是不是可以用 CDC 来同步组织架构表的变更?你讲讲 binlog 监听器怎么写的,明天午饭时间给我画一下。”
小王咧嘴笑了:“老李,你已经开始主动想同步方案了。行,明天我带上笔记本。”
老李摆摆手走了,保温杯里的枸杞晃晃悠悠,像那些正在被实时同步的知识碎片,终于不再静止,而是随着世界的脉搏一起跳动。

