构建自定义知识库

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

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

该工作流在部署后的 smart-doc pod 内部运行。该镜像已经包含 builder 源码、 embedding 模型以及正确的 Python 环境,并且 pod 已经具备到用户知识库 PostgreSQL 的连通性。 你只需要一个要导入的仓库清单,以及对 global 集群的 kubectl exec 访问权限。

embedding 流水线的工作方式

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

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

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

注意: 用于 hybrid retrieval 的 BM25 索引不是由 embed 步骤构建的。内置的系统 KB dump 包含预先构建好的 BM25 索引,但用户 KB 数据库在安装时是空创建的。如果你的部署启用了 hybrid retrieval(默认开启),你必须为用户 KB collection 额外创建一次 BM25 索引——见 第 5 步。

前提条件

  • 具备对运行 Hyperflux 的集群(global 集群)的 kubectl 访问权限,并拥有 cpaas-system 命名空间下 smart-doc pod 的 exec 权限。
  • 对你想导入的每个 Git 仓库都有读取权限。如果是私有仓库,请准备好 HTTPS 用户名/令牌 对。
  • 一个配额足够的 LLM endpoint。 prepare 阶段会对每个源文档调用一次 LLM(对于 ACP 规模的 知识库,大约需要 1,000–3,000 次调用)。默认情况下,容器内命令会复用已为 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_templateJinja 模板,用于生成每篇文档的相对 URL;在检索时会与 ONLINE_DOC_BASE_URL 拼接。ABSOLUTE_URL 变量是文档的本地绝对路径。
origin自由格式的分类标签(例如 internalACP)。
sub_dirs可选——仅导入仓库中的这些子目录。
doc_typemdmdxmd,mdx
kb_version写入每个 chunk 的 metadata.version;用于核对 dump 中的所有 chunks 是否来自同一次 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_dirsdoc_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-baseDEVICE=cpuPG_USER_KB_CONN_STR(指向用户 KB 数据库 docvec_user_kb),因此你不需要传递 --emb-model--device。请显式传递 --pg-conn-str "${PG_USER_KB_CONN_STR}",因为脚本 的默认值是系统 KB 的连接字符串(PG_SYS_KB_CONN_STR)。

各参数的作用:

参数建议
--pg-conn-str "${PG_USER_KB_CONN_STR}"指向用户知识库数据库。不要指向 PG_SYS_KB_CONN_STR,除非你有意替换内置产品知识库(见下文“替换系统知识库”)。
--collection-name user_defined_kb运行中的服务从 docvec_user_kb 读取的 collection 名称。请使用这个确切值——当前 chart 还没有提供修改服务读取的用户 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 表示仅按标题拆分,没有上限。如果你希望将所有长文档重新拆分为更小的 chunks,可设置一个 20003000 左右的上限。更大的 chunks 在长文档召回上略有帮助,但每次回答会消耗更多 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 上嵌入 5,000 个 chunks 大约需要一小时;对于较小的内部语料库(几百篇文档),则只需几分钟。如果你需要 GPU 吞吐量, 请在 GPU 调度的 pod 上运行构建(本文不展开),并使用后文描述的多集群 dump-and-restore 路径。

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

第 5 步 — 为用户知识库创建 BM25 索引(一次性)

用户 KB 数据库在安装时是空创建的——与内置的系统 KB dump 不同,它不包含预构建的 BM25 索引。如果启用了 hybrid retrieval(默认开启),请在首次 embed 运行后创建一次索引:

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');
"

如果没有这个索引,hybrid-retrieval 路径会静默退回为仅 dense 的用户 KB 检索(仍然会返回结果, 只是不会有精确关键词召回)。

就这样——不需要修改 chart,也不需要重启 pod。运行中的服务已经会在每次查询时从 docvec_user_kb / user_defined_kb 读取,因此你的自定义语料库会立即开始影响回答。你可以在 聊天 UI 中提交一个代表性问题来确认。

(可选)多集群:dump 和 restore

如果你希望只构建一次语料库并将其推送到多个集群,可以在第 4 步之后对用户 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 索引)——索引是按数据库生成的,单独恢复到空数据库时 不会自己保留下来(如果恢复到一个已有内容的数据库,且 dump 中包含 BM25,则会进行合并;对于 全新的用户 KB,请在 restore 之后运行 CREATE INDEX)。

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

替换内置系统知识库(高级)

上述工作流会将内容添加到用户 KB,而不会影响内置的产品知识。如果你的需求不同——例如, 内置的 Alauda 产品文档与你的环境不符,而你希望用内部版本替换它们——则可以重复同样的 容器内工作流,但目标改为系统 KB:

  • --pg-conn-str "${PG_SYS_KB_CONN_STR}"
  • --collection-name <your-system-kb-collection-name>
  • 在 chart 中更新 pgconnect.pgCollectionName 以指向你的 collection 名称,然后重新发布。

此路径需要修改 chart,并会在未来的 Hyperflux 版本升级中禁用自动系统 KB 升级(自动交换规则 扫描不会匹配你的自定义 collection 名称)。仅在用户 KB 增强不足以满足需求时才使用它。

重新发布节奏

自定义知识库的变化速度通常比产品文档更快。请根据你的源仓库制定重新运行 prepare → embed 的计划:

  • 每日变化的 runbook → 每晚 cron(一个 Kubernetes CronJob,以一次性任务方式运行相同的 容器内命令)重新嵌入到同一个 user_defined_kb collection。
  • 每季度更新的架构文档 → 在每个发布边界手动重新发布。

针对同一个 collection 名称重新运行 embed 时,会使用确定性的 row ID(smart_doc_builder.py:38-48 中的 make_row_id),因此未变更的 chunks 会幂等地 upsert,而变更的 chunks 会覆盖——但 从 manifest 中移除的文档对应的行会变成孤儿。若要进行干净替换,请在重新嵌入前删除并重建 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 索引会自动继续作用于新 行,因此你不需要重新运行第 5 步——除非你也删除了数据库本身(例如为了实现上文多集群 pg_restore 覆盖)。

注意事项

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