构建自定义知识库

Alauda Hyperflux 插件自带一个内置的 system 知识库,涵盖 Alauda Container Platform (ACP)和 Alauda 产品文档。除此之外,Hyperflux 还维护一个独立的 user 知识库, 你可以将自己的内容填充进去——内部 runbook、SRE playbook、设计文档、未包含在打包语料中的 Alauda 产品文档版本,或者任何其他私有 Git 仓库。Hyperflux 在每次提问时都会同时从 两个知识库中检索,并合并结果,因此添加自定义语料会对打包的产品知识进行增强, 而不是替换它。

本指南介绍由管理员驱动的路径:使用 pod 内置 builder,将 Git 仓库列表批量导入 user 知识库。 (对于终端用户通过 chat UI 上传文件,请使用 v1.3.1 新增的 BYO Knowledge 工具——目标相同, 入口不同。)

该工作流在部署好的 smart-doc pod 内部运行。镜像中已经包含 builder 源码、 embedding model 以及正确的 Python 环境,并且 pod 已经具备到 user knowledge-base PostgreSQL 的连接。 你只需要一个待导入的仓库清单,以及对 global cluster 执行 kubectl exec 的权限。

embedding 管道的工作方式

smart-doc builder 分两阶段将 Git 仓库列表转换为向量化知识库:

  1. Prepare —— 克隆仓库,按标题和 chunk 大小拆分每个 .md / .mdx 文档,然后调用 LLM (与运行中的 Hyperflux 插件已配置的那个相同)为每个文档生成一段摘要和若干代表性问题。 输出:documents.json。
  2. Embed —— 加载 gte-multilingual-base embedding model,分别对 chunk 和按文档的摘要进行 embedding,并将它们写入 user-knowledge-base PostgreSQL(docvec_user_kb),使用运行中的服务端 读取的 collection 名称(默认是 user_defined_kb)。

完成 embedding 后,运行中的服务端会在下一次查询时获取新内容——无需修改 chart,也无需重启 pod。 system 知识库不会受到影响。

注意: 混合检索使用的 BM25 index 不会由 embed 步骤构建。打包的 system-KB dump 中包含 预先构建好的 BM25 index,但 user-KB 数据库在安装时是空的。如果你的部署启用了混合检索(默认如此), 你必须为 user-KB collection 一次性创建 BM25 index——请参见第 5 步。

前提条件

  • 对运行 Hyperflux 的集群(global cluster)拥有 kubectl 访问权限,并且对 cpaas-system 命名空间中 的 smart-doc pod 具备 exec 权限。
  • 对你要导入的每个 Git 仓库具有读权限。如果是私有仓库,请准备好 HTTPS 用户/令牌对。
  • 一个配额足够的 LLM endpoint。 prepare 阶段会针对每个源文档调用一次 LLM(对于 ACP 规模的知识库, 大约需要 1,000–3,000 次调用)。默认情况下,pod 内命令会复用已为 Hyperflux 配置的 LLM endpoint (即安装时通过 LLM Base URL / LLM API Key / LLM Model Name 设置的那个)。 如果你的账号有速率限制,请准备一个配额更高的独立 endpoint,并在 exec 会话中覆盖环境变量(第 3 步)。

你不需要单独的工作站 Python 环境、下载好的 gte-multilingual-base 副本、独立的 PostgreSQL 实例, 也不需要克隆 smart-doc 仓库——smart-doc 容器里已经内置了所有这些内容。

第 1 步 — 描述你的语料

创建一个 JSON manifest,列出你要导入的每个 Git 仓库。将其保存在本地机器上,命名为 my-kb.json(名称可自定义——第 2 步会将其复制到 pod 中)。

{
  "kb_version": "1.0",
  "repos": [
    {
      "git_repo": "https://github.com/myorg/internal-docs",
      "branch": "main",
      "doc_version": "1.0",
      "title": "Internal Docs",
      "doc_url_template": "/internal-docs/{{ABSOLUTE_URL.split('/docs/en/')[-1].replace('.mdx','.html').replace('.md','.html')}}",
      "origin": "internal",
      "sub_dirs": ["docs/en"],
      "doc_type": "md,mdx"
    }
  ]
}

如需在一次运行中导入多个仓库,继续添加更多 repos 条目即可。

字段说明:

字段说明
git_repoGit URL。HTTPS 认证使用 GIT_USER / GIT_TOKEN(对于 github.com,则使用 GITHUB_USER / GITHUB_TOKEN)。
branch / tag分支(默认 main)或 tag(如果两者都设置,则 tag 生效)。
doc_version存储在每个 chunk 上的版本标签;会在检索过滤器中展示。
title人类可读的语料名称;会显示在答案引用中。
doc_url_template生成每个文档相对 URL 的 Jinja 模板;在检索时会与 ONLINE_DOC_BASE_URL 拼接。ABSOLUTE_URL 变量是该文档的本地绝对路径。
origin自由格式的分类标签(例如 internal、ACP)。
sub_dirs可选——仅导入该仓库中的这些子目录。
doc_typemd、mdx 或 md,mdx。
kb_version写入每个 chunk 的 metadata.version;用于检查一个 dump 中的所有 chunk 是否来自同一次 prepare 运行。

第 2 步 — 将 manifest 复制到 smart-doc pod 中

# Find the smart-doc pod (single-replica by default).
POD=$(kubectl -n cpaas-system get pod -l app=smart-doc \
  -o jsonpath='{.items[0].metadata.name}')

# Copy your manifest into the pod's /tmp (the only reliably writable path
# under runAsUser=697; /workspace-smart-doc is read-only).
kubectl -n cpaas-system cp my-kb.json "${POD}:/tmp/my-kb.json" -c serve

第 3 步 — 在 pod 内运行 prepare

在 serve 容器中打开交互式 shell,并运行 prepare 阶段:

kubectl -n cpaas-system exec -it "${POD}" -c serve -- bash

# --- inside the pod ---
cd /tmp                           # writable; outputs land here

# Required for doc_summary vectors. Read by split_doc.py at PREPARE time —
# setting it later (at embed time) has no effect.
export ENABLE_GEN_QUESTIONS=true

# Only if your repos are private:
export GIT_USER=<your-git-user>
export GIT_TOKEN=<your-git-token>
# For github.com use these instead:
# export GITHUB_USER=<your-github-user>
# export GITHUB_TOKEN=<your-github-token>

# Optionally override the LLM endpoint for this session if the chart-default
# one is rate-limited. The builder uses langchain's AzureChatOpenAI, so the
# Azure-style env vars below are honoured; AZURE_OPENAI_API_VERSION defaults
# to "2024-12-01-preview" if you omit it.
# export LLM_BASE_URL=...
# export LLM_API_KEY=...
# export LLM_MODEL_NAME=...
# export AZURE_OPENAI_DEPLOYMENT_NAME=...
# export AZURE_OPENAI_API_VERSION=...      # optional

python /opt/packages/emb_builder/smart_doc_builder.py prepare \
  --config /tmp/my-kb.json
# → writes /tmp/documents.json

有用的标志:

  • --dryrun —— 克隆仓库并打印文档列表,但不调用 LLM。请先使用此选项确认你的 sub_dirs 和 doc_type 过滤条件符合预期。

注意: prepare 不会在多次运行之间缓存 LLM 响应——每次调用都会为每个源文档重新发起一次 LLM 调用。 请为每次重跑预留完整的按文档成本,并且只有在源语料实际发生变化时才重新运行 prepare(否则只需基于现有的 documents.json 重新运行 embed)。

第 4 步 — 在 pod 内运行 embed

保持在同一个 shell 中(这样第 3 步设置的环境变量仍然有效):

python /opt/packages/emb_builder/smart_doc_builder.py embed \
  --from-json /tmp/documents.json \
  --pg-conn-str "${PG_USER_KB_CONN_STR}" \
  --collection-name user_defined_kb \
  --vector-types chunk,doc_summary \
  --min-chunk-size 600 --max-chunk-size 2000 --chunk-overlap 200

chart 已经导出 EMB_MODEL_NAME=/opt/gte-multilingual-base、DEVICE=cpu,以及 PG_USER_KB_CONN_STR(指向 user-KB 数据库 docvec_user_kb),因此你不需要传入 --emb-model 或 --device。不过请显式传入 --pg-conn-str "${PG_USER_KB_CONN_STR}", 因为脚本默认使用的是 system-KB 连接字符串(PG_SYS_KB_CONN_STR)。

各标志的作用如下:

标志建议
--pg-conn-str "${PG_USER_KB_CONN_STR}"指向 user knowledge-base 数据库。不要指向 PG_SYS_KB_CONN_STR,除非你确实想替换打包的产品知识库(见下文“替换 system 知识库”)。
--collection-name user_defined_kb运行中的服务端从 docvec_user_kb 读取的 collection 名称。请使用这个确切值——chart 目前不提供更改服务端读取的 user-KB collection 名称的方法。
--vector-types chunk,doc_summary多向量:每个文档同时贡献 chunk 向量和文档级摘要向量。与生产环境的检索形态一致——请保持启用。
--min-chunk-size / --max-chunk-size / --chunk-overlap默认值为 600 / 0 / 200(来自 embed 的 --help 输出);--max-chunk-size 0 表示仅按标题拆分,没有上限。如果你希望将所有长文档重新拆分为更小的 chunk,可以设置一个类似 2000–3000 的上限。更大的 chunk 对长文档的召回略有帮助,但每次回答会使用更多 token。

警告: 当 --vector-types 包含 doc_summary,但 documents.json 是在未设置 ENABLE_GEN_QUESTIONS=true 的 prepare 运行中生成时,embed 会悄无声息地输出零个摘要向量, 只会在最后记录 no_summary=N。如果你看到较高的 no_summary 数值,请先使用 ENABLE_GEN_QUESTIONS=true 重新运行 prepare,然后再重新运行 embed。

注意: serve 容器默认在 CPU 上运行(DEVICE=cpu)。在 CPU 上 embedding 5,000 个 chunk 大约需要一小时; 对于较小的内部语料(几百个文档),则只需几分钟。如果你需要 GPU 吞吐量,请在 GPU 调度的 pod 上运行构建 (不在本指南范围内),并使用后文描述的多集群 dump-and-restore 路径。

embed 步骤不会调用 LLM;你可以基于同一个 documents.json 低成本地重跑它,以尝试不同的 chunk 大小, 或为现有 collection 追加新文档。

第 5 步 — 在 user 知识库上创建 BM25 index(一次性)

user-KB 数据库在安装时是空的——不同于打包的 system-KB dumps,它不会预先包含 BM25 index。 如果启用了混合检索(默认如此),请在首次 embed 运行后创建一次 index:

psql "${PG_USER_KB_CONN_STR}" -c "
  CREATE INDEX IF NOT EXISTS langchain_pg_embedding_bm25_idx
    ON langchain_pg_embedding
    USING bm25 (id, document)
    WITH (key_field='id');
"

如果没有这个 index,混合检索路径会在 user KB 上静默回退为仅 dense 检索(仍然会返回结果,只是没有精确关键词召回)。

就是这样——不需要修改 chart,也不需要重启 pod。运行中的服务端已经在每次查询时从 docvec_user_kb / user_defined_kb 读取内容,因此你的自定义语料会立即开始参与回答。你可以通过 chat UI 发起一个代表性问题来确认。

(可选)多集群:dump 和 restore

如果你希望只构建一次语料并将其推送到多个集群,可以在第 4 步之后对 user KB 执行 pg_dump, 然后在每个目标集群上恢复:

# --- on the source cluster, inside the smart-doc pod ---
pg_dump "${PG_USER_KB_CONN_STR}" -Fc -f /tmp/user_kb.dump

# --- on the workstation ---
kubectl -n cpaas-system cp \
  "${POD}:/tmp/user_kb.dump" \
  ./user_kb.dump

# --- for each target cluster ---
TARGET_POD=$(kubectl -n cpaas-system get pod -l app=smart-doc \
  -o jsonpath='{.items[0].metadata.name}' --context <target-context>)

kubectl -n cpaas-system --context <target-context> cp \
  ./user_kb.dump \
  "${TARGET_POD}:/tmp/user_kb.dump" -c serve

kubectl -n cpaas-system --context <target-context> exec -it \
  "${TARGET_POD}" -c serve -- bash -c \
  "pg_restore -d \"\${PG_USER_KB_CONN_STR}\" /tmp/user_kb.dump"

然后在每个目标集群上执行第 5 步(BM25 index)——该 index 是按数据库维护的,单独恢复到空数据库时不会自动保留 (如果恢复到一个已有内容的数据库,且 dump 中包含 BM25,则会合并 BM25;对于全新的 user KB,请在恢复后执行 CREATE INDEX)。

注意: 将 user-KB dump 使用 pg_restore 恢复到一个已经有 user-KB 内容的目标集群(例如之前的 BYO 上传) 时,会在 langchain 表的 CREATE 操作上发生冲突。若要进行干净覆盖,建议使用 chart 的 swap 机制,或者先在目标端 删除并重新创建 docvec_user_kb。只有在目标 user KB 为空时,直接 pg_restore 才是最安全的方式。

替换打包的 system 知识库(高级)

上面的工作流是向 user KB 追加内容,并保留打包的产品知识不变。如果你的需求不同——例如,打包的 Alauda 产品文档 与你的环境不符,你希望用内部版本替换它们——请重复相同的 pod 内工作流,但将目标指向 system KB:

  • --pg-conn-str "${PG_SYS_KB_CONN_STR}"
  • --collection-name <your-system-kb-collection-name>
  • 在 chart 中更新 pgconnect.pgCollectionName,使其指向你的 collection 名称,并重新部署。

这一路径需要修改 chart,并会禁用未来 Hyperflux 版本中的 system-KB 自动升级(自动 swap 规则扫描将无法匹配你的自定义 collection 名称)。只有在 user-KB 扩展不足以满足需求时才使用它。

重新生成频率

自定义知识库比产品文档变化更快。请根据源仓库的变更节奏,规划定期重新运行 prepare → embed:

  • 每日变化的 runbook → 每晚通过 cron(一个运行相同 pod 内命令的一次性 Kubernetes CronJob)重新 embedding 到 同一个 user_defined_kb collection。
  • 每季度更新的架构文档 → 在每个发布边界手动重新生成。

对同一个 collection 名称重新运行 embed 时,会使用确定性的行 ID(smart_doc_builder.py:38-48 中的 make_row_id),因此未变化的 chunk 会幂等 upsert,而变化的 chunk 会覆盖——但对于在 manifest 中被移除 的文档,其对应行会变成孤儿行。若要进行干净替换,请在重新 embedding 之前删除并重新创建 collection:

psql "${PG_USER_KB_CONN_STR}" -c "
  DELETE FROM langchain_pg_embedding
  WHERE collection_id = (SELECT uuid FROM langchain_pg_collection WHERE name='user_defined_kb');
  DELETE FROM langchain_pg_collection WHERE name='user_defined_kb';
"

然后重新运行第 4 步进行重建。langchain_pg_embedding 上的 BM25 index 会自动继续应用于新行,因此你不需要 重新运行第 5 步——除非你同时删除了数据库本身(例如用于上文的多集群 pg_restore 覆盖场景)。

注意事项

  • 自定义语料与 BYO Knowledge 工具上传共享同一个 user KB。 终端用户通过 chat UI 上传的文件(v1.3.1 引入) 会落入与你管理员整理的语料相同的 user_defined_kb collection。只要 URL 不冲突,它们可以共存;实际上这通常没问题, 因为 BYO 上传使用的是不同的 URL 方案。如果你确实删除并重新创建了 collection(见“重新生成频率”部分),该 collection 中的 BYO 上传也会与你的语料一起被清除——如果你在意这些内容,请先备份。
  • system KB 的版本过滤不适用于 user KB。 当用户提出与版本相关的问题(例如 ACP 4.3)时,system KB 会被过滤到该 版本,但 user KB 会返回跨所有版本的匹配。如果你的自定义语料包含多个版本,回答中可能会出现跨版本的 chunk。
  • embedding model 不匹配 是最常见的静默失败原因:使用除 pod 内 /opt/gte-multilingual-base 之外的模型构建 的向量虽然可检索,但得分会始终接近 0,因此答案会退化为“我没有足够信息”,而不会出现明显错误。这里描述的 pod 内工作流默认使用内置模型——只有在你明确知道自己在做什么时才覆盖 EMB_MODEL_NAME。