OLAV vs 日志 AI 系统:为什么网络运维需要的不是更聪明的 grep
大多数'AI for operations'工具不过是在日志搜索上加了一层 LLM。OLAV 走了相反的路:将网络工程师的完整工作流——采集、解析、分层排障、快照对比、仿真验证、变更规划——建模为一个 AI 专家团队。本文将剖析两者的架构本质差异。
日志陷阱
目前市场上大多数 “AI for operations” 工具做的是这样一件事:
- 把日志灌入向量数据库
- 在前面放一个 LLM
- 出问题时,LLM 搜相关日志行,总结一下
这有用,但它的架构和 2023 年的文档问答聊天机器人一模一样——只是把 PDF 换成了日志文件。凌晨 3 点网络断了,你得到的是这样的答案:
“在 02:47 到 03:12 之间,BGP 邻居 10.0.0.2 四次进入 Idle 状态。R3 上出现 ‘BGP-5-ADJCHANGE’ 事件。这可能表示网络连通性问题。建议:检查 R3 与 BGP 邻居之间的物理连通性。”
这是一个更聪明的 grep。它告诉了你已经知道的事,建议了你本来就会做的事,却无法告诉你为什么邻居进入了 Idle——因为它只有一个数据源(日志)和一种分析手段(模式匹配)。它在回答表象,而不是状态。
真正的网络工程师怎么做排障
当一个真正的网络工程师排查 “R3 BGP 邻居 down” 时,他不会从读日志开始。他遵循的是分层、证据驱动的过程:
- 状态检查——R3 当前接口状态如何?物理链路 up 吗?
show ip bgp summary现在输出什么? - 拓扑检查——R3 连接了谁?通过哪些接口?用的什么发现协议(LLDP/CDP)?
- L1→L4 自底向上诊断——接口是否被管理性关闭?MTU 是否匹配?到邻居 loopback 的 IGP 路由还在吗?ACL 是否阻断了 TCP 179?
- 历史关联——BGP 震荡前是否有拓扑变更?接口 last-change 时间戳是否与 BGP down 时间吻合?
- 跨快照对比——上一个已知正常的快照和现在之间,有什么变化?新配置行?不同的邻居列表?
- 根因定位——不是 “BGP 邻居 down 了”,而是 “04:00 维护窗口移除了 R3 Gi0/2 链路,导致到邻居 loopback 的 IGP 路径丢失,进而使 BGP 转为 Idle”。
这本质上是一个多数据源、跨层、时序关联问题。日志是其中一个关键输入——但单独远不够用。工程师需要结构化的设备状态、拓扑、路由表、配置文本,以及跨时间对比所有这些的能力。
两种根本不同的架构
flowchart LR
subgraph A["日志 AI 系统"]
L[日志 / Syslog] --> VDB[向量数据库]
Q[用户查询] --> LLM1[LLM]
VDB --> LLM1
LLM1 --> R1["'BGP down——检查连通性'"]
end
subgraph B["OLAV NetOps"]
direction TB
SSH[Nornir SSH 采集] --> P[TextFSM / PaC 解析]
P --> DB[(DuckDB<br/>12+ 结构化视图)]
S[Syslog Parquet] --> EVD[query_evidence]
DB --> AG[NetOps 编排器]
EVD --> AG
AG --> RP[Reporter<br/>L1→L4 调查]
AG --> AN[Analyzer<br/>变更规划]
AG --> SM[Simulator<br/>Batfish 仿真验证]
AG --> TP[Topology Engine<br/>CDP/LLDP/BGP]
RP --> R2["根因:<br/>'R3 Gi0/2 04:00被移除 →<br/>IGP 路径丢失 → BGP Idle'"]
AN --> R2
SM --> R2
TP --> R2
end
style A fill:#1a1a2e,stroke:#e94560,color:#eee
style B fill:#0f3460,stroke:#16c79a,color:#eee
日志系统是一个单代理 RAG 管道:嵌入、检索、总结。它完全运行在文本相似度空间里。它无法回答 “昨天和今天有什么不同”,因为昨天的结构化状态根本不存在于它的向量数据库中。
OLAV 是一个网络工程的多人操作系统:每个专家代理负责一个能力域,编排器负责意图路由,每个代理都同时扎根于结构化状态(DuckDB,设备 show 输出的 12+ 自动视图)和非结构化证据(syslog、命令输出、配置文本)。
数据层:结构化状态,而非日志行
这是最深层的差异,也是凌晨 3 点最关键的差异。以下是两个系统各自拥有的数据维度:
| 数据层 | 日志 AI 系统 | OLAV NetOps |
|---|---|---|
| 设备清单 | ❌ 无 | netops.devices — 主机名、平台、厂商、角色、站点、管理 IP |
| 接口状态 | ❌ 无 | v_show_interfaces_auto / v_show_interfaces_terse_auto — admin/oper 状态、IP、MTU、last-change |
| BGP 状态 | ❌ 无 | v_bgp_neighbors_auto — 邻居、状态、接收前缀数、运行时间、AS |
| OSPF 状态 | ❌ 无 | v_ospf_neighbors_auto — 邻居、状态、区域、接口 |
| 拓扑 | ❌ 无 | topology_links — CDP/LLDP 邻接关系、源/目的设备+接口 |
| 路由表 | ❌ 无 | v_show_ip_route_auto — 目的网段、下一跳、协议、出接口 |
| 日志(syslog) | ✅ 主要数据源 | query_evidence(source="syslog") — 辅助证据,与结构化状态交叉验证 |
| 配置文本 | ⚠️ 如果被采集 | query_evidence(source="config") — running/startup 配置搜索 |
| CLI 输出 | ❌ 无 | query_evidence(source="command_output") — 原始 show 命令输出文本搜索 |
| 快照(时序) | ❌ 无 | parsed_outputs 中按快照保存历史;diff_snapshots 做行级对比 |
当 BGP 邻居震荡时,日志系统只能告诉你 “它震荡了”。OLAV 可以告诉你:
- 接口全程是 UP 的(
v_show_interfaces_terse_auto) - 但到邻居 loopback 的 IGP 路由在两个快照之间消失了(
v_show_ip_route_auto+diff_snapshots) - 拓扑显示 R3 的 Gi0/2 链路是到该邻居子网的唯一条 L2 路径(
topology_links) - 04:00 的维护窗口与 Gi0/2 的
updated_at时间戳吻合(netops.devices)
这就是告警和根因之间的差距。
知识注入:专家团队并非每次从零开始
还有一个结构性缺口。日志 AI agent 每次查询都从零开始:它只有 prompt、用户问题、以及检索到的日志行。仅此而已。
OLAV 的 agent 在每次查询开始前,都会自动注入三层预制知识:
flowchart TD
subgraph Inject["知识注入(Agent 执行前)"]
direction TB
L1["第一层:guide.yaml<br/>领域专家知识<br/>例:'BGP Idle → 先查接口状态'<br/>例:'L1→L4 自底向上诊断序列'"]
L2["第二层:memory_primer<br/>入库时预计算的数据画像<br/>例:'v_bgp_neighbors_auto 的列:peer, state, ...'<br/>例:'state 列取值分布:Established(8), Idle(1)'"]
L3["第三层:AutoRecall<br/>历史失败教训<br/>例:'上次:过滤条件太宽 → 限制 LIMIT 20'<br/>例:'本拓扑:R3-R4 链路是冗余的'"]
end
subgraph Agent["子代理执行"]
AG[Reporter / Analyzer / Simulator]
end
L1 --> AG
L2 --> AG
L3 --> AG
Q[用户查询] --> AG
style Inject fill:#16213e,stroke:#0f3460,color:#eee
style Agent fill:#0f3460,stroke:#16c79a,color:#eee
第一层——guide.yaml 文件是编码为可检索、作用域过滤指令的领域专业知识。当用户问 “为什么 BGP 在震荡”,fault_analysis_workflow.guide.yaml 自动注入。它告诉 agent:“先收集 L1 证据(接口状态),再 L3(到邻居的路由),然后 L4(TCP 179)。只有在 L1-L4 都正常后才检查协议层属性。”
第二层——memory_primer 在数据入库时运行,而非查询时。每次采集设备数据后,它预计算每个自动视图的 schema 和每个状态列的值分布,写入 LanceDB。当 agent 醒来回答问题,它已经知道有哪些表、表里有哪列、哪些值是正常的——无需执行一次 DESCRIBE TABLE。
第三层——AutoRecall 捕获历史运行的失败模式并注入为约束。如果 agent 上周跑了一个过滤太宽的查询,返回了 12000 行(撑爆上下文),下周同类查询时会携带教训:“上次在此拓扑上,WHERE state='Established' 配合 LIMIT 20 返回 8 行。沿用此限制。”
这不是 RAG。RAG 检索的是相似文档。OLAV 的知识注入检索的是可执行的约束——告诉 agent 如何思考的指南、告诉 agent 存在什么数据的 schema、告诉 agent 不要再犯什么错误的经验反思。
“团队”架构:一个编排器,七个专家
一个拿着 30 个工具的 agent 在本地小模型上根本不能工作——光是工具定义就吃掉了半个上下文窗口,选错工具的概率倍增。OLAV 反其道而行之:
flowchart TD
U["用户:'R3 的 BGP 为什么 down 了?'"] --> ORCH["NetOps 编排器<br/>3 工具:memory, search, delegate"]
ORCH --> R["task('reporter', ...)<br/>L1→L4 调查"]
ORCH --> A["task('analyzer', ...)<br/>变更规划草案"]
ORCH --> S["task('simulator', ...)<br/>Batfish 仿真验证"]
ORCH --> C["task('collector', ...)<br/>SSH 采集"]
ORCH --> I["task('importer', ...)<br/>离线包导入"]
ORCH --> T["task('topology', ...)<br/>拓扑查询"]
ORCH --> L["task('learner', ...)<br/>解析器学习"]
R --> DB[(DuckDB<br/>结构化状态)]
R --> SL[Syslog Parquet]
A --> DB
S --> DB
style ORCH fill:#1a1a2e,stroke:#e94560,color:#eee
style R fill:#0f3460,stroke:#16c79a,color:#eee
style A fill:#0f3460,stroke:#16c79a,color:#eee
style S fill:#0f3460,stroke:#16c79a,color:#eee
每个专家只持有 4-7 个工具——正好是其领域所需的。Reporter 有 execute_sql、inspect_blast_radius、format_and_export,以及 execute_skill_script——后者用来运行 query_evidence、diff_snapshots 这类领域脚本。它不能 SSH 到设备。它不能写变更计划。Analyzer 有另一套工具。Simulator 又是另一套。这是最小权限原则在 AI agent 上的应用。
编排器是一个纯路由器:只持有记忆(olav_recall_memory / olav_store_memory)与 web_search(网络搜索),外加框架注入的 task()(委派)。它的全部工作就是分类意图、分派到一个专家。它不推理问题,不合成结果。它把专家的输出原样返回。
对比日志 AI 系统:一个 agent,一个 prompt,一套工具——“搜日志,然后解释”。那是一个拿着手电筒的单兵。OLAV 是一整个 NOC 值班团队。
自改进闭环:每次调查都让系统变得更聪明
这是日志系统从根本上做不到的事:从自己的错误中学习,并自动应用教训。
sequenceDiagram
participant U as 用户
participant A as Agent
participant TL as trace_learner
participant M as LanceDB 记忆库
participant AR as AutoRecall
U->>A: "R3 的 BGP 为什么震荡?"
A->>A: 执行查询:LIMIT 100 → 返回 12000 行 → 上下文溢出
A-->>U: 超时 / 部分回答
Note over TL: 事后分析:捕获失败模式
TL->>M: 写入 reflection:<br/>"此舰队 BGP 查询必须用 LIMIT 20,<br/>按 device_name 过滤。LIMIT 100 返回了 12K 行。"
U->>A: "R3 的 BGP 为什么震荡?"(下周)
AR->>M: AutoRecall:拉取相关 reflection
M-->>AR: "BGP 查询:LIMIT 20,按 device_name 过滤"
AR-->>A: 在执行前注入 prompt
A->>A: 执行查询:LIMIT 20, device_name='R3' → 8 行
A-->>U: 根因分析完成
这就是 trace_learner → reflection → AutoRecall 闭环。每一次失败的工具调用、每一次上下文溢出、每一次超时都会留下痕迹。learner 将其提炼为紧凑、可执行的约束(每行一条,30 天 TTL)。下一次类似查询时,AutoRecall 在 agent 开始工作之前就注入约束——错误永不重演。
这个闭环无需任何人工干预。不需要改 prompt,不需要”收集错误样例做微调”。系统观察失败 → 捕获教训 → 自动应用。这才是 AI 原生的运维工程,不是 RAG 加装饰。
当日志不够用:Parser-as-Code 的解法
还有一个硬差距。当传统日志 AI 系统遇到一台 show 输出格式无法识别的设备时,会发生什么?
它失败了。可能优雅地失败,但终究失败——“未知格式”或”无可用解析器”。
OLAV 的 learner 子代理选择了不同的路。当一台新设备的 show 命令输出无法解析时:
- 多设备样本训练——learner 按 (平台, 命令) 汇总所有设备的原始输出,一次性喂给 LLM
- PaC(Parser-as-Code)生成——LLM 输出的是一个 Python 函数,而非配置文件
- AST 白名单沙箱——生成的代码被验证:不能
import os、不能subprocess、不能open()——只能使用re、json、ipaddress等少数安全模块 - 多设备交叉验证——解析器必须在所有设备上产生 ≥70% 预期数据行数的有效记录
- 单样本隔离——仅在一台设备上验证通过的解析器进入
_quarantine/,等待第二台设备的确认样本
这意味着 OLAV 无需人工编写 TextFSM 模板就能适配新硬件。系统通过观察多个样本学习格式,用机械手段验证解析器,对不确定的结果进行隔离。日志系统做不到这一点——它没有将非结构化设备输出转化为结构化状态的机制。
总结:完整能力矩阵
| 能力 | 日志 AI 系统 | OLAV NetOps |
|---|---|---|
| 数据源 | Syslog | Syslog + 12+ 结构化 show 命令视图 + 拓扑 + 设备配置 |
| 结构化状态 | 无 | 完整 DuckDB schema:设备、接口、BGP、OSPF、路由、拓扑、原始/解析输出 |
| 时序分析 | 时间范围日志搜索 | 跨快照行级 diff(diff_snapshots) |
| 根因方法 | 模式匹配 → LLM 总结 | L1→L4 分层诊断 → 跨层矛盾检测 → 证据链 |
| 知识管理 | RAG(检索相似日志行) | 三层注入:guide.yaml + memory_primer + AutoRecall 反思 |
| Agent 架构 | 单 agent,所有工具暴露 | 1 编排器 + 7 专家,每个 4-7 工具,最小权限原则 |
| 未知格式处理 | ”无可用解析器” | PaC 自学习解析器(AST 沙箱 + 多设备验证 + 隔离区) |
| 变更规划 | 无 | 完整管线:分析状态 → 逐设备 CLI + 回滚 → Batfish 仿真 → ContainerLab 验证 |
| 自我改进 | 无 | trace_learner → reflection → AutoRecall:失败被捕获为约束,自动应用 |
| 上下文预算控制 | 无(全量日志倾泻) | 按模型分级:small=8K / medium=32K / large=200K,自动截断,50% 预算时触发压缩 |
| 写操作安全 | 无 | 7 层纵深防御:默认只读、按服务控制、预演审核、人工确认、沙箱、网络隔离、审计追踪 |
结语
日志 AI 系统是一个搜索引擎上面加了一层礼貌的摘要。它告诉你发生了什么,有时猜测可能哪里不对,但始终局限在日志模式的狭隘视角里。
OLAV 是一个 AI 原生的网络工程操作系统。它采集的是结构化状态,而不只是日志行。它做到跨层诊断,而不只是模式匹配。它做跨时间对比,而不只看当下。它从失败中学习,并自动应用教训。而且——它在你自己已有的硬件上运行,不需要云端 GPU 集群。
这之间的差距不是增量式的。是告警和诊断之间的差距。是表象和根因之间的差距。是一个更聪明的 grep,和一个永不休眠的网络工程师之间的差距。