多平台运维经常遇到同一个问题:资料明明保存过,却不知道位于厂商 PDF、项目文件夹、复盘文档还是聊天记录中。问答助手首先解决“找到正确资料并给出出处”,再逐步增加更复杂的功能。
READING NOTES
本文要点
- 第一版先支持 Markdown/PDF 转文本后的知识入库与问答
- 平台、版本、组件和风险级别必须作为元数据保存
- 回答必须带来源;检索不到时直接说不知道
01|第一版的功能边界
第一版只收录三类平台:Nutanix、VMware 和通用 KVM。语料只使用公开厂商文档、经过整理的 SOP 和脱敏后的故障复盘。聊天记录和含客户信息的原始文档不直接入库。
问题类型先做四类:安装部署、日常巡检、故障定位和变更前检查。不做自动执行,也不让模型读取生产账号密码。这样范围可控,也容易准备测试题。
02|目录和数据结构
原始资料与处理后的数据分开保存。每次入库记录文件哈希,内容没有变化就不重复切分。后续更新某份手册时,可以只删除旧版本片段再重新写入。
infra-kb/
├── data/
│ ├── raw/ # 原始 PDF / Markdown
│ ├── clean/ # 脱敏、去页眉后的文本
│ └── index/ # 向量或全文索引
├── ingest/
│ ├── loader.py
│ ├── cleaner.py
│ ├── splitter.py
│ └── metadata.py
├── retrieval/
│ ├── keyword.py
│ ├── vector.py
│ └── rerank.py
├── answer/
│ └── generator.py
└── tests/
└── questions.json03|先把元数据做好
如果只使用向量检索,查询 VMware HA 时可能混入 Nutanix AHV 或普通 KVM 的片段。解决方法是给每个文档片段添加 platform、component、scenario、version_scope、risk 和 source。用户明确平台后,检索先进行元数据过滤。
from dataclasses import dataclass
@dataclass
class Chunk:
id: str
text: str
platform: str # vmware / nutanix / kvm
component: str # compute / storage / network / management
scenario: str # deploy / inspect / troubleshoot / change
version_scope: str
risk: str # read_only / change / destructive
source: str
reviewed_at: str04|文档不是按固定字数硬切
如果简单地每 800 字切分一次,经常会把“前提条件”和“操作步骤”拆开。更合适的方法是优先按标题切分,再把过长段落按空行拆分,并保留父标题。命令块和前置说明应尽量放在一起。
def split_markdown(text: str, max_chars: int = 1200) -> list[str]:
chunks, current = [], []
size = 0
for block in text.split("\n\n"):
if current and size + len(block) > max_chars:
chunks.append("\n\n".join(current))
current, size = [], 0
current.append(block)
size += len(block)
if current:
chunks.append("\n\n".join(current))
return chunksFIELD NOTE切分质量直接影响检索。模型再强,如果拿到的是半条步骤或没有适用版本的片段,回答也很难可靠。
05|混合检索比只用向量更稳
运维资料中包含大量精确词:错误码、服务名、命令和告警 ID。向量检索擅长寻找语义相近内容,关键词检索更容易命中精确字符串。将两边结果合并后,再按平台、版本和文档可信度重排。
def hybrid_search(question, filters, limit=6):
keyword_hits = keyword_index.search(question, filters, limit=10)
vector_hits = vector_index.search(question, filters, limit=10)
merged = {}
for rank, hit in enumerate(keyword_hits):
merged.setdefault(hit.id, {"chunk": hit, "score": 0})
merged[hit.id]["score"] += 1 / (60 + rank)
for rank, hit in enumerate(vector_hits):
merged.setdefault(hit.id, {"chunk": hit, "score": 0})
merged[hit.id]["score"] += 1 / (60 + rank)
results = sorted(merged.values(), key=lambda x: x["score"], reverse=True)
return [item["chunk"] for item in results[:limit]]06|回答生成必须带引用
每个检索片段使用 K01、K02 等编号。模型回答每个判断后必须给出引用编号,页面再将编号链接回原始文档和标题。检索结果不足时,不允许模型凭常识补出一套操作步骤。
def build_context(chunks):
return "\n\n".join(
f"[K{index:02d}] {chunk.source}\n{chunk.text}"
for index, chunk in enumerate(chunks, start=1)
)
RULES = """
只根据知识片段回答。
每个结论后标出引用编号,例如 [K01]。
不同平台的命令和组件不能混用。
涉及删除、重启、迁移、扩容时,先写风险和前置检查。
资料不足就回答“现有知识库无法确认”,并列出需要补充的信息。
"""07|怎样测试是否出现错误回答
测试集需要同时包含能够回答的问题和故意缺少资料的问题。评估不看语气是否流畅,主要检查检索文档是否正确、引用是否支持结论、是否混淆平台,以及高风险操作是否给出提醒。
| 指标 | 测试方法 | 当前目标 |
|---|---|---|
| 检索命中 | 正确文档是否进入前 5 条 | 常见问题优先保证 |
| 引用准确 | 引用片段是否真的支持结论 | 不能只引用“相关”内容 |
| 平台混淆 | VMware 问题是否混入 AHV/KVM 操作 | 必须接近零 |
| 拒答 | 资料缺失时是否停止猜测 | 宁可少答,不编答案 |
| 风险识别 | 删除、重启、迁移前是否提示前置检查 | 高风险问题全部标记 |
08|这个项目下一步怎么做
当前先通过命令行验证入库、检索和回答链路。下一步可以增加一个简单 Web 页面:左侧选择平台和场景,中间显示回答,右侧展开引用原文。管理员功能先只处理文档上传后的预览和审核,不直接自动入库。
更长期的方向是把故障复盘接进来。每次问题解决后,按统一模板写现象、证据、根因、动作与验证,审核后再加入知识库。这样知识库不是一次性导入,而是跟着运维工作持续增长。
FIELD NOTE项目目标不是构建一个什么都敢回答的机器人,而是提供一个能够快速找到正确资料并提醒操作风险的运维助手。
SOURCES / 资料核对
相关官方资料
产品功能会随版本和授权变化。下面列出撰写时核对的资料, 实际交付仍以项目版本的兼容矩阵和正式文档为准。