实时音视频+AI Agent远程运维系统:ASR、RAG与LangGraph实战
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 定义了一个 运维状态机 ,核心节点有五个:
- 意图识别节点 :判断用户是要“诊断问题”“执行操作”还是“查询知识”。
- 上下文组装节点 :把 ASR 文本、视频帧描述、实时日志、RAG 检索结果拼成 Prompt。
- 决策节点 :LLM 决定下一步是调用工具、请求人工确认,还是直接回答。
- 工具执行节点 :执行具体操作,比如查日志、重启服务、发工单。
- 验证节点 :操作后检查结果,失败则回滚或告警。
为什么用状态机而不是 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 的问题。我的排查清单:
-
看切块内容
:直接查数据库,看检索出来的 chunk 是不是完整的语义单元。如果切碎了,调
chunk_size和分隔符。 - 看 embedding 质量 :拿一个已知问题,手动算 embedding,看相似度最高的文档是不是对的。如果不对,换 embedding 模型。
- 看混合检索权重 :如果关键词匹配更重要,把 BM25 权重调高。我有个场景是查错误码,BM25 权重调到 0.5 才准。
- 加重排序 :检索出 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%。运维场景里,多问一句比自作聪明强一万倍。
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)