多平台运维经常遇到同一个问题:资料明明保存过,却不知道位于厂商 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.json

03|先把元数据做好

如果只使用向量检索,查询 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: str

04|文档不是按固定字数硬切

如果简单地每 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 chunks
FIELD 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 / 资料核对

相关官方资料

产品功能会随版本和授权变化。下面列出撰写时核对的资料, 实际交付仍以项目版本的兼容矩阵和正式文档为准。