我给 AI Agent 做了一个网络故障排查实验:服务器无法上网,它会先查 IP,还是先查路由?
前面刚把 AI Agent、Workflow、Tool Calling 这些概念理了一遍,我一直有个想法:
能不能不要再拿“查天气”举例,直接让 Agent 干一点我熟悉的事情?
于是我挑了一个网络工程里再普通不过的故障:
一台 Linux 服务器访问不了互联网,让 AI 自己判断应该怎么查。
这个实验不追求做出什么“全自动运维平台”。
我反而想看一个更基础的问题:
如果我只告诉它:
“服务器无法上网”
它会先查 IP?
先查路由?
先 Ping 网关?
还是上来就查 DNS?
这件事特别适合拿来理解 Agent 和普通 Workflow 到底有什么区别。
一、如果是人来排查,我脑子里大概会怎么想?
“服务器无法上网”其实是一个很模糊的描述。
可能是:
网卡没起来
IP 没配置
掩码错了
默认路由没了
网关不通
公网不通
DNS 解析失败
目标端口被拦
应用自己挂了
所以正常情况下不会一上来就改配置。
我一般会先把问题缩小:
1. 网卡和 IP 正常吗?
2. 默认路由有没有?
3. 网关能不能到?
4. 公网 IP 能不能到?
5. 只有域名不行,还是 IP 也不行?
6. DNS 配置是什么?
7. 如果网络都正常,再看目标 TCP 端口和应用。
但这只是一个经验顺序。
如果 ip -br addr 一看服务器压根没有业务 IP,后面的 DNS、HTTPS、应用端口就暂时没必要查。
反过来,如果网关能 Ping,公网 IP 也能 Ping,但是域名解析失败,问题范围其实已经非常小了。
这正是我想让 Agent 做的事情:
不给它写死完整步骤,只给它几个诊断工具,让它根据上一步结果自己决定下一步。
二、这次我没有直接给 AI 一个 Shell
最省事的方式其实是给 Agent 一个:
shell_exec()
然后让它随便执行 Linux 命令。
但我越想越觉得这不适合第一版。
因为只要 Agent 能执行任意 Shell,它理论上就不只是能运行:
ip route
也可能有能力运行:
ip route del default
systemctl stop NetworkManager
所以我这次故意把权限收得很窄。
只给它几个明确的、只读的网络诊断工具:
get_interfaces()
查看网卡和 IP
get_routes()
查看路由表
ping_host()
测试基本三层连通性
resolve_domain()
测试 DNS
read_dns_config()
读取 DNS 配置
tcp_check()
测试 TCP 端口
Agent 可以决定:
先用谁
后用谁
要不要继续用
但它不能直接修改:
IP
路由
DNS
网卡状态
防火墙
我觉得这才比较像一个可以继续往真实运维方向扩展的第一版。
三、Tool 本身并不智能,决定“什么时候用”才是 Agent 的工作
比如:
get_routes()
说到底只是一个工具。
真实 Linux 上,它做的事情大概就是:
ip route
真正有意思的是:
模型什么时候觉得“现在应该看路由”?
整个结构可以理解成:
用户故障描述
│
↓
┌────────────┐
│ AI Agent │
│ LLM 决策 │
└─────┬──────┘
│
下一步查什么?
│
┌─────────┼──────────┐
↓ ↓ ↓
IP工具 路由工具 Ping工具
│ │ │
└─────────┼──────────┘
↓
返回结果
↓
Agent 再次判断
↓
DNS?TCP?还是结束?
这就是一个很小的 Agent Loop。
四、我没有真的把自己的网络改坏
为了写篇文章把默认路由删掉、DNS 改坏,没什么必要。
所以代码里先放了:
DEMO_MODE = True
它会模拟一台存在 DNS 故障的 Linux 服务器:
eth0:UP
IP:192.168.10.20/24
默认网关:
192.168.10.1
Ping 网关:
成功
Ping 公网 IP 1.1.1.1:
成功
解析 example.com:
失败
/etc/resolv.conf:
nameserver 192.168.10.253
我作为实验设计者当然知道答案:
问题大概率集中在 DNS。
但 Agent 一开始只知道:
一台 Linux 服务器访问 example.com 失败。
我要看的不是它能不能背出“DNS 是什么”,而是:
它能不能靠工具一步步把范围缩到 DNS。
五、准备一个最小 Agent 环境
这次示例使用 OpenAI Agents SDK。
安装:
pip install openai-agents
配置 API Key:
export OPENAI_API_KEY="你的 API Key"
Python 里先引入:
from agents import Agent, Runner, function_tool
我现在对这三个东西的理解很简单:
Agent
定义这个 AI 的身份、指令和可用工具
function_tool
把普通 Python 函数包装成 Agent 可调用工具
Runner
真正运行 Agent Loop
六、第一个工具:看网卡和 IP
@function_tool
def get_interfaces() -> str:
"""Show Linux interface state and IP addresses."""
if DEMO_MODE:
return """lo UNKNOWN 127.0.0.1/8
eth0 UP 192.168.10.20/24"""
return run_readonly(["ip", "-br", "addr"])
真实命令就是:
ip -br addr
它很适合排障时先快速确认:
网卡状态
IPv4 地址
接口名称
如果这里就发现:
eth0 DOWN
或者:
根本没有业务 IP
后面的排障方向会马上变化。
七、第二个工具:看路由
@function_tool
def get_routes() -> str:
"""Show the Linux routing table."""
真实模式里对应:
ip route
我主要关心:
有没有默认路由?
默认网关是谁?
从哪个接口出去?
例如:
default via 192.168.10.1 dev eth0
至少说明服务器已经有一条默认出口。
如果连 default 都没有,访问互联网失败就很值得先查路由配置。
八、Ping 其实可以提供不同层次的信息
我又给它一个:
ping_host(host)
这个工具可以测试:
网关
公网 IP
域名
这三种结果的意义并不一样。
例如:
网关都不通
先怀疑:
本机地址
二层链路
VLAN
ARP
网关接口
网关通,公网 IP 不通
可能继续看:
上游路由
NAT
ACL
防火墙
运营商路径
公网 IP 通,域名不通
DNS 的优先级就会迅速上升。
所以 Agent 调同一个 Ping 工具,目标不同,得到的信息量也不同。
九、再给它两个 DNS 工具
第一个:
resolve_domain(domain)
真实模式调用:
getent hosts example.com
回答:
域名到底能不能解析?
第二个:
read_dns_config()
读取:
/etc/resolv.conf
回答:
这台机器目前准备把 DNS 请求发给谁?
这两个问题是不一样的。
一个是验证现象,一个是查看配置。
十、我还加了一个 TCP 检查
网络排障特别容易在一句话上结束:
Ping 通了,所以网络没问题。
其实不够。
能 Ping:
只证明了一部分网络连通性。
并不等于:
TCP 22
TCP 80
TCP 443
一定正常。
所以我还给 Agent 一个:
tcp_check(host, port)
底层使用:
socket.create_connection()
这样后面如果做 Web、SSH、数据库等问题,就可以继续区分:
IP 可达
和:
应用端口可达
十一、最关键的地方:我没有把排障步骤写死
如果我这样写:
第一步检查 IP
第二步检查 Route
第三步 Ping 网关
第四步 Ping 公网
第五步检查 DNS
模型只是按照固定步骤跑。
这更像:
Workflow
所以我给 Agent 的指令反而是:
只做诊断,不修改配置。
不要为了走流程把所有工具调用一遍。
根据故障描述和上一步结果,选择最有价值的下一项检查。
所有结论必须引用工具结果。
如果公网 IP 正常但域名失败,应优先考虑 DNS。
最后给出故障定位、证据链、排除项和下一步建议。
然后把工具交给它。
十二、所以它应该先查 IP,还是先查路由?
答案是:
不一定。
这反而是这个实验最有意思的地方。
如果用户只说:
服务器完全无法访问任何网络。
Agent 很可能优先确认:
IP
路由
因为本机网络配置都没有任何已知信息。
但如果用户一开始已经告诉它:
网关能 Ping
1.1.1.1 也能 Ping
但是域名打不开
这时候再重新检查:
eth0 是不是 UP
信息增益就不大。
一个合理 Agent 应该更快走向:
resolve_domain()
read_dns_config()
所以真正的价值不是:
“AI 永远先查哪个命令。”
而是:
它能不能利用已经拿到的信息,减少没必要的检查。
十三、这次模拟故障,一个合理的证据链应该长这样
eth0 UP
192.168.10.20/24
↓
本机接口和 IP 基本正常
default via 192.168.10.1
↓
存在默认路由
Ping 192.168.10.1 成功
↓
本机到网关正常
Ping 1.1.1.1 成功
↓
公网三层连通性正常
example.com 解析失败
↓
问题开始集中到 DNS
resolv.conf:
nameserver 192.168.10.253
↓
DNS Server 配置和可达性值得重点检查
最后一个比较靠谱的结论应该是:
服务器并不是“完全断网”,而是公网 IP 连通正常、域名解析失败。现有证据把问题范围集中到了 DNS 配置或 DNS Server 可达性。
这和一句:
可能是 DNS。
区别很大。
前者有证据链。
十四、不要看到 DNS 地址陌生,就直接说它错了
比如模拟环境中:
nameserver 192.168.10.253
是我故意设计的故障点。
但在真实企业网络里,不能因为这个地址:
看起来不像 8.8.8.8
就宣布:
“DNS 配错了。”
企业内部使用私网 DNS 太正常了。
真实环境还应该继续验证:
192.168.10.253 能不能到?
53/UDP 是否可达?
53/TCP 是否可达?
DNS 服务是否正常?
是不是 systemd-resolved?
是不是内部域名系统?
所以更严谨的输出应该是:
当前证据高度指向 DNS,下一步建议验证 DNS Server 192.168.10.253 的可达性和服务状态。
而不是:
已确认 192.168.10.253 是错误地址。
十五、为什么我要求 Agent “不能编”?
因为模型很容易从:
DNS 解析失败
直接脑补成:
DNS Server 宕机。
但证据还远远不够。
也可能是:
到 DNS 的路由有问题
ACL 拦截
本地 resolver 异常
配置写错
DNS 服务端故障
所以 Agent 做运维时,我觉得最重要的一条指令反而是:
证据到哪里,结论就到哪里。
不要把“可能”写成“确定”。
十六、代码里我还刻意不用 shell=True
真实模式里执行:
subprocess.run(
["ip", "-br", "addr"],
...
)
而不是把模型生成的字符串直接扔进:
shell=True
因为如果模型可以任意拼 Shell,它拥有的就不只是:
网络诊断能力
而可能演变成:
任意命令执行能力
所以我更喜欢:
Agent
↓
预先定义好的工具
↓
固定且可审核的能力
而不是:
Agent
↓
root shell
十七、从模拟模式切到真实 Linux 很简单
代码默认:
DEMO_MODE = True
先跑文章里的 DNS 故障模拟。
真正想在自己的实验 Linux 上读取网络状态时,改成:
DEMO_MODE = False
这些 Tool 就会调用真实的只读操作:
ip -br addr
ip route
ping
getent hosts
读取 /etc/resolv.conf
TCP Connect
第一遍一定建议在:
自己的虚拟机
测试服务器
实验环境
使用。
目前这版没有:
修改 IP
删除 Route
修改 DNS
重启 NetworkManager
关闭网卡
修改防火墙
它只负责:
看
判断
给建议
十八、我反而觉得“只诊断”比“自动修复”更适合第一版
因为网络运维里的查询和变更风险完全不同。
例如:
ip route
只是查看。
但:
ip route del default
可能直接让远程服务器失联。
同样:
cat /etc/resolv.conf
只是查看。
但直接重写 DNS 配置就可能影响真实业务。
所以以后即使继续升级,我也更愿意按照:
第一阶段
AI 只读诊断
第二阶段
AI 给修复方案,人确认
第三阶段
低风险操作审批后执行
第四阶段
高风险操作始终人工确认
慢慢走。
十九、这次我终于真正理解了 Workflow 和 Agent 的区别
以前只看概念时,我会背:
Workflow 固定
Agent 动态
但没有很强的感觉。
放到网络排障里一下就清楚了。
如果程序永远执行:
check_ip()
check_route()
ping_gateway()
ping_public()
check_dns()
无论什么故障都从头跑到底,
这就是很典型的:
Workflow / Script
而如果我只给模型:
IP Tool
Route Tool
Ping Tool
DNS Tool
TCP Tool
然后说:
根据证据自己决定下一步。
它才开始有 Agent 的味道。
当然,这不意味着 Agent 一定更好。
比如每天固定巡检:
CPU
内存
磁盘
服务状态
我反而更倾向 Workflow。
但:
为什么这台服务器无法访问外网?
这种后续路径不固定的问题,就比较适合试 Agent。
二十、这个 Agent 后面还能继续加什么?
现在只有:
IP
Route
Ping
DNS
TCP
还很基础。
以后可以继续增加:
ip neigh
查看 ARP / 邻居表
ss -lntup
看监听端口
tracepath / traceroute
看路径
ethtool
看物理链路
nmcli
看 NetworkManager
tcpdump
抓包
nft / iptables
看本机防火墙
curl
验证 HTTP / HTTPS
再往数通方向走,甚至可以加入:
交换机接口状态
VLAN
ARP
MAC Table
OSPF
BGP
ACL
到这里它就开始从:
Linux Network Agent
慢慢往:
网络自动化故障排查助手
靠近了。
二十一、以后甚至可以接真实交换机
例如后面通过:
SSH
Netmiko
NAPALM
厂商 API
给 Agent 提供:
get_device_interface()
get_arp_table()
get_mac_table()
get_ospf_neighbor()
get_bgp_peer()
用户只描述:
PC 访问服务器失败。
Agent 可以逐步检查:
终端
↓
接入交换机
↓
VLAN
↓
网关
↓
路由
↓
服务器
这就比单纯让 AI:
“解释一下 OSPF 邻居状态。”
有意思多了。
因为 AI 开始真的和网络技能产生交集。
二十二、但我暂时不会让 AI 自动改交换机
比如:
undo shutdown
改 VLAN
下发 ACL
修改 OSPF Cost
重启接口
风险明显高一个等级。
真要做,至少得考虑:
命令白名单
设备权限限制
人工审批
配置备份
变更前检查
回滚
日志审计
因为 Agent 一旦拥有 Tool,它犯错时的影响已经不只是:
“答错一道题”
而可能变成:
“真的改错了设备”
这也是我现在觉得 Agent 工程里非常值得关注的一点。
二十三、完整代码怎么运行?
环境:
Linux
Python 3.10+
安装:
pip install openai-agents
配置:
export OPENAI_API_KEY="你的 API Key"
运行:
python network_diagnosis_agent.py
第一次:
DEMO_MODE = True
观察模拟故障。
理解以后,再在自己的实验环境切:
DEMO_MODE = False
二十四、最后把这次实验压缩成一张逻辑图
用户:
服务器访问 example.com 失败
│
↓
Network Agent
│
自己选择 Tool
│
┌────────┼────────┐
↓ ↓ ↓
网卡/IP Route Ping
│
公网正常?
│
↓
DNS
│
解析是否成功?
│
↓
DNS Config
│
↓
证据链
│
↓
“不是完全断网,更像 DNS 问题”
这就是这篇文章最想表达的东西。
写在最后
这次我最想验证的其实不是:
AI 知不知道
ip route是干什么的。
普通大模型当然知道。
真正让我觉得 Agent 有意思的地方是:
当它拥有几个真实工具以后,能不能根据环境返回的结果自己决定下一步查什么。
网络故障排查本来就是一个不断缩小范围的过程:
现象
↓
获取证据
↓
排除一部分原因
↓
继续验证
↓
再次缩小范围
↓
定位
Agent Loop 则是:
观察
↓
判断
↓
行动
↓
拿到新结果
↓
再次判断
两者放在一起,其实非常自然。
这个 Demo 离真正的生产网络自动化还很远。
但它至少让我第一次感觉:
AI Agent 不再只是一个“会调用天气 API 的聊天机器人”。
它真的可以和原来学的:
Linux
网络
路由
DNS
服务器排障
接起来。
下一步我更想做的是:
给它继续加
ip neigh、ss、tracepath甚至tcpdump,看看它能不能把一次“网络不通”继续排得更深。
代码在这里:
import socket
import subprocess
from pathlib import Path
from agents import Agent, Runner, function_tool
DEMO_MODE = True
def run_readonly(cmd: list[str]) -> str:
"""Execute a read-only command without a shell."""
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=8,
check=False,
)
output = (result.stdout + result.stderr).strip()
return output or f"command finished, exit code={result.returncode}"
except Exception as e:
return f"failed: {type(e).__name__}: {e}"
@function_tool
def get_interfaces() -> str:
"""Show Linux interface state and IP addresses."""
if DEMO_MODE:
return """lo UNKNOWN 127.0.0.1/8
eth0 UP 192.168.10.20/24"""
return run_readonly(["ip", "-br", "addr"])
@function_tool
def get_routes() -> str:
"""Show the Linux routing table."""
if DEMO_MODE:
return """default via 192.168.10.1 dev eth0
192.168.10.0/24 dev eth0 proto kernel scope link src 192.168.10.20"""
return run_readonly(["ip", "route"])
@function_tool
def ping_host(host: str) -> str:
"""Ping an IP address or hostname."""
if DEMO_MODE:
demo = {
"192.168.10.1": "3 packets transmitted, 3 received, 0% packet loss",
"1.1.1.1": "3 packets transmitted, 3 received, 0% packet loss",
"example.com": "ping: example.com: Temporary failure in name resolution",
}
return demo.get(host, f"demo target not configured: {host}")
return run_readonly(["ping", "-c", "3", "-W", "1", host])
@function_tool
def resolve_domain(domain: str) -> str:
"""Test DNS resolution."""
if DEMO_MODE:
if domain in {"example.com", "www.baidu.com"}:
return f"getent: {domain}: Temporary failure in name resolution"
return f"demo domain not configured: {domain}"
return run_readonly(["getent", "hosts", domain])
@function_tool
def read_dns_config() -> str:
"""Read /etc/resolv.conf."""
if DEMO_MODE:
return """# /etc/resolv.conf
nameserver 192.168.10.253
options timeout:1 attempts:2"""
try:
return Path("/etc/resolv.conf").read_text(encoding="utf-8", errors="ignore")
except Exception as e:
return f"failed: {type(e).__name__}: {e}"
@function_tool
def tcp_check(host: str, port: int) -> str:
"""Try to establish a TCP connection."""
if not (1 <= port <= 65535):
return "port must be in 1..65535"
if DEMO_MODE:
if host == "1.1.1.1" and port == 443:
return "TCP 1.1.1.1:443 connected"
return f"demo TCP target not configured: {host}:{port}"
try:
with socket.create_connection((host, port), timeout=3):
return f"TCP {host}:{port} connected"
except Exception as e:
return f"TCP {host}:{port} failed: {type(e).__name__}: {e}"
agent = Agent(
name="Linux 网络故障排查助手",
instructions="""
你是一名 Linux 网络故障排查 Agent。
只做诊断,不修改系统配置。
不要为了走流程把所有工具都调用一遍,要根据现象和上一步结果选择最有价值的下一步。
任何结论都必须引用工具结果,不能编造。
如果公网 IP 正常但域名失败,应优先怀疑 DNS。
最终输出:故障定位、证据链、排除项、下一步建议。
""",
tools=[
get_interfaces,
get_routes,
ping_host,
resolve_domain,
read_dns_config,
tcp_check,
],
)
if __name__ == "__main__":
result = Runner.run_sync(
agent,
"""
一台 Linux 服务器现在“无法上网”:访问 example.com 失败。
请你自己选择排查步骤,尽量用最少的检查定位问题。
""",
)
print(result.final_output)
更多推荐



所有评论(0)