运维文档喂给AI,新人上手快3倍?知识库这么搭
标签: AI应用 | RAG | 知识库 | 运维文档 | 向量数据库 | 团队协作我们组去年来了个新人,第一个月我至少被问了 200 次「这个服务在哪台机器上」「这个流程找谁审批」「上次那个故障是怎么解决的」。
不是他懒,是我们的文档散在 Confluence、Git 仓库、几百个聊天记录和三个人的脑子里。搜索框搜出来 40 条结果,没一条对得上。
后来我花了两天搭了个 RAG 知识库,现在新人有问题先问 AI,问不出来再问我。我的打扰次数掉了大概三分之二。
今天讲讲怎么搭,以及那些踩过才知道的坑。
RAG 到底是个啥
一句话:先从你的文档里搜出相关段落,再连同问题一起喂给大模型,让它基于这些内容回答。
它和「微调模型」的区别很关键:
- 微调:把知识焊进模型里,改一次文档要重训,贵且慢
- RAG:文档放外面,改了立刻生效,便宜且实时
运维文档天天在变,RAG 才是对的选择,别被人忽悠去微调。
第一步:把文档收拢
这一步最脏最累,但决定了后面 70% 的效果。
我的做法是全部转成 Markdown 塞进一个 Git 仓库:
mkdir -p /data/kb/{架构,流程,故障复盘,巡检,应急预案}
Confluence 导出的 HTML 批量转 md
find ./confluence_export -name "*.html" -exec sh -c \
'pandoc "$1" -f html -t markdown -o "/data/kb/架构/$(basename "$1" .html).md"' _ {} \;---
title: 订单服务部署架构
system: order-service
owner: 张伟
updated: 2026-07-15
env: 生产
---为什么重要?因为检索出来的片段如果不带这些信息,AI 就分不清「这段说的是测试环境还是生产环境」,然后一本正经地告诉新人一个错误的 IP。
第二步:切片,这是最大的坑
新手最容易在这翻车——按固定字数切。
比如按 500 字一刀切,结果一个部署步骤被从中间劈开,检索出来是「……第三步,然后」,AI 拿着半句话开始编。
正确做法是按标题层级切,保证语义完整:
import re
def split_by_heading(md_text, source, max_len=1200):
"""按 ## 二级标题切分,超长的再按段落二次切"""
parts = re.split(r'\n(?=##\s)', md_text)
chunks = []
for p in parts:
p = p.strip()
if not p:
continue
head = p.split('\n')[0][:60]
if len(p) <= max_len:
chunks.append({'text': p, 'source': source, 'section': head})
else:
# 超长按空行二次切,但每块都补回标题作为上下文
for sub in re.split(r'\n\n+', p):
if sub.strip():
chunks.append({
'text': f"{head}\n{sub.strip()}",
'source': source,
'section': head
})
return chunks注意最后那个 f"{head}\n{sub}"——二次切分时把标题补回每一块。这个小动作让我的检索准确率提升了非常明显的一截。
第三步:向量化 + 存起来
轻量方案直接上 Chroma,不用单独起数据库:
pip install chromadb sentence-transformersimport chromadb
from sentence_transformers import SentenceTransformer
中文向量模型,这个在中文技术文档上表现最稳
model = SentenceTransformer('BAAI/bge-small-zh-v1.5')
client = chromadb.PersistentClient(path="/data/kb_vec")
col = client.get_or_create_collection("ops", metadata={"hnsw:space": "cosine"})
def index(chunks):
embeds = model.encode(
[c['text'] for c in chunks],
normalize_embeddings=True # 必须归一化,否则余弦相似度算不准
).tolist()
col.upsert(
ids=[f"{c['source']}#{i}" for i, c in enumerate(chunks)],
documents=[c['text'] for c in chunks],
embeddings=embeds,
metadatas=[{'source': c['source'], 'section': c['section']} for c in chunks]
)用 upsert 不用 add,这样文档更新重跑一遍就是覆盖,不会重复堆积。
# 每天凌晨2点重建索引
0 2 * * * cd /data/kb && git pull && /usr/bin/python3 /opt/kb/reindex.py >> /var/log/kb.log 2>&1第四步:查询 + 回答
import requests
SYS = """你是运维知识库助手。只能依据下面提供的【参考资料】回答问题。
规则:
【参考资料】 {context} """
def ask(question, top_k=5): q_vec = model.encode([question], normalize_embeddings=True).tolist() res = col.query(query_embeddings=q_vec, n_results=top_k)
ctx = "\n\n---\n\n".join( f"[来源: {m['source']} / {m['section']}]\n{d}" for d, m in zip(res['documents'][0], res['metadatas'][0]) )
temperature三个必须知道的坑
哪怕知识库里根本没有相关内容,向量检索也一定会返回 5 条「最像的」。然后 AI 拿着不相关的资料硬答。
加个阈值过滤:
hits = [
(d, m) for d, m, dist in zip(
res['documents'][0], res['metadatas'][0], res['distances'][0]
) if dist < 0.45 # cosine距离,越小越像
]
if not hits:
return "知识库里没有相关内容,建议直接问运维组。"用户搜 order-svc-prod-03 这种机器名,向量模型根本理解不了,反而是关键词匹配更准。
kw_hits = col.query(query_texts=[question], n_results=3,
where_document={"$contains": question.split()[0]})答错了没人纠正,就会一直错。我在问答界面加了个「这个回答有用吗」,点了「没用」的问题自动记到一个文件里,我每周看一眼,补文档。
真实收益
搭完三个月,说点实在的:
最后这条其实才是最大的价值。以前写文档没人看,现在写了 AI 会用、新人会问到,大家反而有动力写了。
你们团队的运维文档是什么状态?是「有但没人看」,还是「压根就没有」?
评论区聊聊你踩过的文档坑。想要我这套完整脚本的,留言「知识库」,攒够 50 个我整理成开箱即用的仓库发出来。
夜雨聆风