Skip to content

ThingJS 文档 RAG(2025.07 – 2025.10)

独立负责 ThingJS API 文档知识库。编辑器 MCP 与网页共用一套检索;文档更新后重建索引即可用,不必等模型训练。

栈:TypeScript + LangChain + LanceDB + MCP。离线用本地 Qwen3-Embedding-4B;在线只 embed 查询,生成走阿里云 Chat。

在线链路

意图分流 → 问句规划 → LanceDB + BM25 → RRF → 按类采用 → /search 原文或 /ask 生成。

  • 意图分流:纯规则。有类名、别名、THING.、领域词才当文档题;你好 / 闲聊 / 天气直接固定话术,0 次 embed、0 次 chat。
  • 问句规划:别名扩写(摄像机 → Camera / lookAt),近邻类名按编辑距离替换进问句。不用 LLM 改写,避免改出文档里没有的类。
  • 双路检索:向量整句 embed 一次,LanceDB Top28;BM25 最多 6 路各 Top28,按 chunk_id 去重。
  • RRF:只按名次加分,1 / (60 + 名次 + 1)。同一 chunk 两路分相加。
  • 采用:单类仍 5 条;问句里 2 个及以上类,每类最多 3 条、按类轮流发。

/search 只返回片段;/ask 再拼 prompt 生成,超时 60 秒。空检索、熔断都不调 chat。

切片

知识主要是 Markdown API 页:类下面挂成员。

  • 先按 ### 切到成员级,一块通常就是一个方法。
  • 同一成员超过 800 字再分段,相邻段重叠 200;不同成员不重叠。
  • 代码块整块留下,不从中间切。图片挂在对应切片末尾,不单独成片。
  • 块上补类名、成员名、标题;检索用富化 search_text,生成仍用原文。
  • 成员 ≥ 8 时额外打一条 section-summary(成员名列表)。

800 和 200 写进切块指纹,改了要全量重嵌。要不要改,看门禁,不当旋钮拧。

发布与门禁

索引按 embedding 模型分版本,不是在同一份库上原地改。

  • 文档或别名变了:建新版本 → 校验 → 切 active 指针;当前检索打完再热加载。
  • 默认留最近 5 个版本方便回滚;再老的 prune。
  • 换 embedding 是新 profile,旧维度不混用。
  • 检索门禁:可答题 Top5 要有期望类名(Class Hit@5);有期望方法的,前 3 条要出现(Member Hit@3)。还看 MRR 和误空率。
  • 生成门禁:schema、引用必须落在本次检索、类名方法有没有提到、幻觉为 0。过不了回滚上一版。

召回差看采用列表里有没有金标;生成差看引用和是否编了检索里没有的内容。两段日志分开看。

鉴权与额度

  • MCP:公司 Token 换 LDAP 身份和部门额度,按团队扣减。
  • 网页:独立日池,不占部门配额。
  • LDAP 管「谁能调」,不管「谁能看哪份文档」。现在是一套索引、语料打标,不是按租户拆库。

两道必答题

摄像机看向某点。 字面没有 Camera / lookAt。纯向量会漂,纯 BM25 对中文也打不中。检索前做规划:别名把「摄像机」扩成 Camera 和关键字;类目录纠拼写。没有用大模型改写。

Box、Sphere、Cylinder 三个怎么 new。 融合池里都有,采用写死 5 条时先到的类占满格子,Cylinder 进不了采用列表,模型只能说「文档未提供」。改成按类留席:单类 5 条,多类每类最多 3 条轮流发。