1. 远程运维的痛点与新一代方案的整体设计思路

干了十多年运维,我越来越觉得传统远程运维这套东西快撑不住了。不是说 SSH、VNC、跳板机这些工具不好用,而是业务形态变了——以前运维就是敲命令、看日志、重启服务,现在呢?业务跑在混合云上,微服务几百个,故障现场稍纵即逝,一线值班的兄弟往往不是最懂那个系统的人,等专家到场,黄花菜都凉了。更别提很多现场根本没有专业运维,只有个“懂点电脑”的同事,你让他敲 kubectl describe pod ,他连终端在哪都不知道。

这就是我动手做这套“实时音视频 + AI Agent”远程运维系统的出发点。核心想法很朴素: 让专家不用到现场,让现场的人不用懂技术,中间靠一个能听、能看、能查、能指挥的 AI Agent 把两边接起来。 实时音视频负责“看”和“说”,AI Agent 负责“理解”和“执行”,ASR 把语音转成文字,RAG 把企业知识库和实时日志变成可检索的上下文,最后 Agent 给出操作建议甚至直接执行。

这套东西适合谁?我总结了三类人:一是中小团队里“什么都要管”的全栈运维,二是大厂里负责 IT 服务台、工单分派的值班同学,三是做企业级 Agent 产品的开发者。哪怕你只是想搞明白 AI Agent 怎么跟实时音视频结合,这里面的架构思路也能直接抄。

先说整体设计。我没有一上来就堆大模型,而是先画了一张“能力分层图”。最底层是 实时音视频通道 ,用 WebRTC 做,为什么不用 RTMP?因为运维场景要双向低延迟,现场摄像头画面和专家语音必须同步,RTMP 那几秒延迟在故障处理里是致命的。中间层是 多模态感知层 ,ASR 负责把现场语音转文字,视频帧抽出来做 OCR 或者画面理解,日志流做结构化解析。再往上才是 Agent 决策层 ,这里我用了 LangGraph 做状态机,因为运维流程本质是有向图:先诊断、再确认、后执行、最后验证,不是简单的问答。最上面是 知识层 ,RAG 知识库加 pgvector 向量检索,把历史工单、设备手册、故障案例都塞进去。

为什么这么分?我踩过一个坑:一开始把 ASR 和 Agent 耦合在一起,结果现场口音重一点,ASR 错几个字,Agent 就完全跑偏。后来把感知层独立出来,加了一个“置信度过滤”环节,ASR 结果低于阈值就触发“请再说一遍”的交互,而不是硬着头皮往下走。这个设计后来救了我很多次。

还有一个关键选型: Agent 框架用 LangChain + LangGraph,不用纯 LLM 调用。 原因很简单,运维操作有严格顺序和回滚需求。比如“重启数据库”这个动作,必须先检查主从状态、再确认无大事务、然后才执行,执行完还要验证连接数。这种流程用 LangGraph 的节点和边来表达非常自然,每个节点可以挂工具调用、条件判断和人工确认。纯 LLM 调用做不到这种可控性。

至于 ASR,我试过好几个方案。Whisper 系列准确率不错,但实时性在普通 CPU 上吃力;后来换成流式 ASR,牺牲一点准确率换低延迟,配合 RAG 做上下文纠错,效果反而更好。这里有个经验: 运维场景的 ASR 不需要通用识别,需要领域识别。 你把“kubectl”“nginx”“OOM”这些词加到热词表里,准确率立刻上一个台阶。

2. 核心细节解析:ASR、RAG 与 Agent 的协同机制

2.1 实时音视频通道的搭建要点与延迟控制

WebRTC 的坑我踩了不少。第一个坑是 信令服务器 。很多人以为 WebRTC 点对点就完事了,但运维场景往往是一对多:一个专家看多个现场,或者多个专家会诊一个现场。这时候必须有个信令层来管理房间和角色。我用 FastAPI 写了一个轻量信令服务,配合 WebSocket 做 SDP 交换,实测下来比直接上 Janus 简单,维护成本低。

第二个坑是 NAT 穿透 。企业内网环境复杂,有时候 STUN 打不通,TURN 又不想自建。我的做法是:优先走 STUN,失败后自动降级到 TURN,TURN 服务器用 coturn,配置里把 external-ip 写死,避免多网卡识别错误。这里有个细节: turnserver.conf 里的 listening-port 和 tls-listening-port 要分开,TLS 那个用于加密通道,运维数据敏感,能加密就加密。

第三个坑是 带宽自适应 。现场网络往往很差,4G 信号飘忽。WebRTC 默认的拥塞控制有时候太激进,画面糊成马赛克。我在 RTCPeerConnection 里手动设置了 degradationPreference 为 maintain-framerate ,优先保帧率,因为运维看的是操作步骤,不是高清画质。同时把视频分辨率上限压到 720p,码率上限 1.5Mbps,实测在 4G 下也能稳定。

注意:WebRTC 的 iceServers 配置里,STUN 和 TURN 的 URL 格式不一样,STUN 是 stun:host:port ,TURN 是 turn:host:port?transport=udp ,写错了不会报错,但穿透会静默失败,排查起来很痛苦。

2.2 ASR 在运维场景的领域适配与流式处理

通用 ASR 模型在运维场景下有个致命问题: 专业术语识别率低。 我测试过,一段“检查一下 nginx 的 upstream 健康检查”的语音,通用模型识别成“检查一下 n g x 的 up stream 健康检查”,Agent 拿到这种文本直接懵了。解决方案有三层:

第一层是 热词表 。把企业常用的技术栈词汇、设备型号、内部系统代号全部整理成热词,ASR 引擎支持热词加权就加权,不支持就用后处理替换。我维护了一个 hotwords.txt ,大概 2000 多个词,每周更新一次。

第二层是 流式分片 。运维对话往往很长,一次性识别延迟太高。我用流式 ASR,每 500ms 出一个中间结果,Agent 可以先基于中间结果做意图预判,等最终结果出来再确认。这样用户感觉“刚说完就有反应”,体验好很多。

第三层是 RAG 纠错 。ASR 输出后,先过一遍 RAG 检索,如果检索到的知识库文档里有相似发音的专业词,就用文档里的词替换。比如 ASR 输出“卡夫卡”,RAG 检索到“Kafka 集群故障处理”,自动纠正为“Kafka”。这个机制我称为“知识库引导的 ASR 后处理”,实测能把术语准确率从 70% 拉到 92% 以上。

代码层面,我用的是 faster-whisper 做基础模型,配合 webrtcvad 做静音检测,避免把背景噪音也送去识别。核心逻辑大概是这样:

from faster_whisper import WhisperModel
import webrtcvad

model = WhisperModel("base", device="cpu", compute_type="int8")
vad = webrtcvad.Vad(2)  # 激进度 2,平衡灵敏度和误触发

def process_audio_stream(audio_chunk):
    if not vad.is_speech(audio_chunk, sample_rate=16000):
        return None
    segments, info = model.transcribe(audio_chunk, language="zh", hotwords=load_hotwords())
    text = "".join([seg.text for seg in segments])
    return rag_correct(text)  # RAG 纠错

提示: faster-whisper 的 compute_type 选 int8 在 CPU 上速度最快,但准确率会掉一点。如果服务器有 GPU,选 float16 ,准确率和速度都更好。我实测 base 模型在 int8 下,一段 10 秒的语音大概 1.2 秒出结果,够用了。

2.3 RAG 知识库的切块策略与检索优化

RAG 这块我踩的坑最多。一开始直接把 PDF 手册扔进去切块,结果检索出来的全是目录页和页眉页脚。后来总结了一套 运维知识库切块策略 :

  • 按语义切,不按字数切。 运维文档往往有明确的操作步骤,一个步骤就是一个完整语义单元。我用 LangChain 的 RecursiveCharacterTextSplitter ,但分隔符不是简单的换行,而是 ["\n\n步骤", "\n\n注意", "\n\n警告", "\n\n"] ,优先按步骤和警告切。
  • 保留上下文头。 每个切块前面加上文档标题和章节路径,比如“《Kafka 运维手册》> 第三章 故障处理 > 3.2 消费者滞后”。这样检索出来的时候,Agent 知道这个知识来自哪里,可信度判断更准。
  • 表格单独处理。 运维文档里大量参数表格,直接切会切碎。我的做法是把表格转成 Markdown,然后整表作为一个切块,检索时用关键词匹配表格标题。

向量库用 pgvector,为什么不用专门的向量数据库?因为运维数据本身就在 PostgreSQL 里,工单、资产、日志都是关系型数据,用 pgvector 可以一张 SQL 同时查向量和结构化条件。比如“查一下最近三天关于 Kafka 的工单,同时语义相似度大于 0.8 的知识库文档”,一条 SQL 搞定。

检索策略上,我用了 混合检索 :向量相似度 + 关键词 BM25。纯向量检索有时候会漏掉精确匹配,比如用户说“错误码 503”,向量检索可能返回一堆“服务不可用”的文档,但用户要的是 503 的具体处理步骤。混合检索把 BM25 的权重调到 0.3,向量权重 0.7,实测召回率提升明显。

-- pgvector 混合检索示例
SELECT id, content, 
       1 - (embedding <=> query_embedding) AS vector_score,
       ts_rank(to_tsvector(content), plainto_tsquery('错误码 503')) AS keyword_score,
       (0.7 * (1 - (embedding <=> query_embedding)) + 0.3 * ts_rank(...)) AS final_score
FROM knowledge_chunks
WHERE final_score > 0.5
ORDER BY final_score DESC
LIMIT 5;

注意:pgvector 的 <=> 是余弦距离,值越小越相似。 1 - 距离 才是相似度。这个转换很容易搞反,我一开始就写错了,检索结果全是反的。

2.4 Agent 状态机设计与工具调用规范

Agent 这块我用 LangGraph 定义了一个 运维状态机 ,核心节点有五个:

  1. 意图识别节点 :判断用户是要“诊断问题”“执行操作”还是“查询知识”。
  2. 上下文组装节点 :把 ASR 文本、视频帧描述、实时日志、RAG 检索结果拼成 Prompt。
  3. 决策节点 :LLM 决定下一步是调用工具、请求人工确认,还是直接回答。
  4. 工具执行节点 :执行具体操作,比如查日志、重启服务、发工单。
  5. 验证节点 :操作后检查结果,失败则回滚或告警。

为什么用状态机而不是 ReAct?因为运维操作 不能自由发挥 。ReAct 模式下 LLM 可能自己决定“先重启再说”,这在生产环境是灾难。状态机把每一步都锁死,LLM 只能在预定义的边里选择,安全得多。

工具调用我定了一套规范:每个工具必须有 name 、 description 、 parameters 和 rollback 字段。 rollback 是关键,比如“重启服务”的 rollback 是“如果重启后健康检查失败,自动回滚到上一版本”。这个字段让 Agent 在决策时能评估风险,高风险操作自动触发人工确认。

from langgraph.graph import StateGraph, END

class OpsState(TypedDict):
    user_input: str
    asr_text: str
    rag_context: list
    action: str
    confirmed: bool
    result: str

def should_confirm(state: OpsState):
    if state["action"] in HIGH_RISK_ACTIONS:
        return "confirm"
    return "execute"

graph = StateGraph(OpsState)
graph.add_node("intent", intent_node)
graph.add_node("context", context_node)
graph.add_node("decide", decide_node)
graph.add_node("confirm", confirm_node)
graph.add_node("execute", execute_node)
graph.add_node("verify", verify_node)

graph.add_conditional_edges("decide", should_confirm, {"confirm": "confirm", "execute": "execute"})
graph.add_edge("confirm", "execute")
graph.add_edge("execute", "verify")
graph.add_edge("verify", END)

提示:LangGraph 的 StateGraph 里,状态字段尽量用简单类型,别塞复杂对象。我一开始把整个 WebRTC 连接对象塞进 state,结果序列化出问题,排查了半天。后来改成只存 connection_id ,用的时候再查。

3. 实操过程:从零搭建一套可运行的远程运维 Agent

3.1 环境准备与依赖安装

这套系统我建议用 Docker Compose 起,因为组件多,手动装容易乱。核心依赖有:

  • FastAPI :信令服务和 Agent API
  • PostgreSQL + pgvector :知识库和工单数据
  • Redis :会话状态和 ASR 中间结果缓存
  • coturn :TURN 服务
  • faster-whisper :ASR 推理
  • LangChain + LangGraph :Agent 编排

docker-compose.yml 关键片段:

services:
  api:
    build: ./api
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/ops
      - REDIS_URL=redis://redis:6379
    depends_on:
      - db
      - redis

  db:
    image: pgvector/pgvector:pg16
    environment:
      - POSTGRES_PASSWORD=pass
    volumes:
      - pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine

  turn:
    image: coturn/coturn
    network_mode: host
    command: -n --log-file=stdout --external-ip=你的公网IP --user=ops:密码

注意:coturn 用 network_mode: host 是为了让 TURN 能正确识别客户端 IP,用 bridge 模式经常穿透失败。 external-ip 必须写公网 IP,否则 TURN 返回的候选地址是内网 IP,客户端连不上。

3.2 知识库初始化与向量化流程

知识库初始化分三步: 采集、切块、向量化 。

采集阶段,我把企业内部的 Confluence、GitLab Wiki、历史工单系统、设备手册 PDF 全部导出。PDF 用 pdfplumber 提取文本,表格单独用 camelot 提取。工单数据从数据库直接读,每条工单作为一个文档,标题和解决方案作为主要内容。

切块阶段,用前面说的语义切块策略。这里有个细节: 切块大小控制在 300-500 字。 太小了上下文不够,太大了检索精度下降。我实测 400 字左右最平衡。

向量化用 text-embedding-3-small ,维度 1536。为什么不用本地 embedding?因为运维知识库更新频繁,本地模型每次更新都要重新部署,API 方式更灵活。成本也不高,一万个切块大概几毛钱。

from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
import psycopg2

splitter = RecursiveCharacterTextSplitter(
    chunk_size=400,
    chunk_overlap=50,
    separators=["\n\n步骤", "\n\n注意", "\n\n警告", "\n\n", "\n"]
)

embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

def ingest_document(doc_title, doc_content):
    chunks = splitter.split_text(doc_content)
    for i, chunk in enumerate(chunks):
        # 添加上下文头
        enriched = f"《{doc_title}》第{i+1}段:{chunk}"
        vector = embeddings.embed_query(enriched)
        cur.execute(
            "INSERT INTO knowledge_chunks (title, content, embedding) VALUES (%s, %s, %s)",
            (doc_title, enriched, vector)
        )

提示: chunk_overlap 设 50 是为了避免步骤被切断。比如“先停止服务,再备份数据”被切成两段,重叠部分能让检索时至少召回一段完整的。

3.3 Agent 工具链配置与权限控制

Agent 能调用的工具必须严格管控。我分了三个权限等级:

等级 工具示例 确认要求 回滚机制
只读 查日志、查状态、查知识库 无需确认 无
低风险 重启单个服务、清理临时文件 Agent 自动确认 自动回滚
高风险 重启数据库、修改配置、扩缩容 人工二次确认 手动回滚预案

工具定义用 JSON Schema,每个工具一个文件,放在 tools/ 目录下。Agent 启动时自动加载,根据权限等级决定是否需要人工确认。

{
  "name": "restart_service",
  "description": "重启指定服务,适用于服务无响应场景",
  "parameters": {
    "service_name": {"type": "string", "description": "服务名称"},
    "namespace": {"type": "string", "description": "K8s 命名空间"}
  },
  "risk_level": "low",
  "rollback": "如果重启后 30 秒内健康检查失败,自动执行 kubectl rollout undo"
}

注意: risk_level 为 high 的工具,Agent 在决策节点会强制跳转到 confirm 节点,等待人工在音视频界面点击确认。这个确认按钮我做了防误触,需要长按 2 秒,避免现场手抖。

3.4 实时音视频与 Agent 的联动实现

联动核心是 事件总线 。WebRTC 通道里,现场语音经过 ASR 变成文本,通过 WebSocket 推给 Agent API;Agent 的决策结果和工具执行状态,再通过 WebSocket 推回前端,前端用 TTS 播报给现场人员。

前端我用 React + simple-peer 做 WebRTC 客户端,Agent 消息用 socket.io 接收。关键代码:

const peer = new SimplePeer({ initiator: true, trickle: false });

peer.on('signal', data => {
  socket.emit('webrtc_signal', { roomId, signal: data });
});

socket.on('agent_message', msg => {
  if (msg.type === 'tts') {
    speak(msg.text);  // 浏览器 TTS 播报
  } else if (msg.type === 'confirm') {
    showConfirmDialog(msg.action);  // 弹出确认框
  }
});

TTS 我用的是浏览器原生 SpeechSynthesis ,虽然音色一般,但零延迟、零依赖。如果对音色有要求,可以换成云服务 TTS,但要注意网络延迟。

提示: simple-peer 在 Safari 上有兼容性问题,需要加 config: { iceServers: [...] } 并且处理 iceconnectionstatechange 事件。我实测 Safari 下 TURN 连接容易断,加了自动重连逻辑才稳定。

4. 常见问题与排查技巧实录

4.1 ASR 识别不准的排查路径

ASR 不准是最常见的问题,排查顺序我总结成一张表:

现象 可能原因 排查方法 解决
专业术语错 热词表未覆盖 对比 ASR 输出和实际术语 补充热词表
断句错误 VAD 太激进 调低 VAD 激进度 从 3 调到 2
延迟高 模型太大 看推理耗时 换 base 或 int8
背景噪音干扰 未做降噪 听原始音频 加 RNNoise 预处理

我遇到最诡异的一次:ASR 总是把“负载均衡”识别成“负载军横”。查了半天发现是现场麦克风有共振,特定频率被放大。后来在音频采集端加了 highpass 滤波器,问题解决。

4.2 RAG 检索结果不相关的优化手段

RAG 检索不相关,八成是切块或 embedding 的问题。我的排查清单:

  1. 看切块内容 :直接查数据库,看检索出来的 chunk 是不是完整的语义单元。如果切碎了,调 chunk_size 和分隔符。
  2. 看 embedding 质量 :拿一个已知问题,手动算 embedding,看相似度最高的文档是不是对的。如果不对,换 embedding 模型。
  3. 看混合检索权重 :如果关键词匹配更重要,把 BM25 权重调高。我有个场景是查错误码,BM25 权重调到 0.5 才准。
  4. 加重排序 :检索出 Top 20,再用一个小的 cross-encoder 模型重排序,取 Top 5。这个我实测能提升 15% 的准确率。

提示:pgvector 的索引类型选 ivfflat 还是 hnsw ?数据量小于 10 万用 ivfflat ,大于 10 万用 hnsw 。 hnsw 查询快但建索引慢, ivfflat 反过来。我生产环境用 hnsw ,因为查询频繁。

4.3 Agent 决策跑偏的兜底策略

Agent 跑偏在运维场景是致命的。我设了三道防线:

第一道是 意图白名单 。Agent 只能处理预定义的意图,比如“诊断”“重启”“查询”“扩容”。用户说“把数据库删了”,意图识别节点直接拒绝,返回“不支持该操作”。

第二道是 工具参数校验 。每个工具的参数都有类型和范围校验,比如 service_name 必须匹配 ^[a-z0-9-]+$ ,防止注入。

第三道是 人工确认兜底 。高风险操作必须人工确认,而且确认界面会显示 Agent 的完整推理链,让确认的人知道 Agent 为什么这么决策。

我踩过最大的坑是:Agent 把“重启 nginx”理解成“重启整个节点”,因为 RAG 检索到一篇“节点重启流程”的文档,LLM 被带偏了。后来在 Prompt 里加了强约束:“只操作明确指定的服务,不得扩大范围”,并且把工具参数校验加严,才解决。

4.4 音视频卡顿与延迟的现场处理

现场网络差是常态。我的处理优先级是: 保音频 > 保操作指令 > 保视频 。因为运维沟通主要靠语音,视频只是辅助看现场。

具体做法:

  • 音频用 Opus 编码,码率压到 24kbps,牺牲音质保流畅。
  • 操作指令走 WebSocket 文本通道,不依赖音视频通道,即使视频断了,Agent 指令还能送达。
  • 视频动态降码率,从 1.5Mbps 降到 300kbps,分辨率从 720p 降到 360p。

注意:WebRTC 的 setParameters 可以动态调码率,但要在 RTCRtpSender 上调用,而且不是所有浏览器都支持。Chrome 支持,Safari 部分支持。我做了降级方案:不支持动态调码率的浏览器,直接重新协商 SDP。

最后分享一个我实际用下来的小技巧: 在 Agent 的 Prompt 里加一句“如果不确定,先问再动”。 这句话看起来简单,但能大幅降低误操作率。我统计过,加了这句话之后,Agent 主动询问的比例从 5% 升到 30%,而误操作率降了 80%。运维场景里,多问一句比自作聪明强一万倍。

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐