前言
作为一个写了 5 年 Vue + React 的前端,我用 Python + LangGraph 做了一个企业级 AIOps 排障 Agent。过程中最大的感悟是:真正难的不是调大模型,而是让大模型少干活。
这篇文章讲什么
我不会从头教你代码怎么写,因为这个年代工程思维才是价值。我要分享的是一个前端工程师做 AI Agent 项目的完整心路历程------从"LLM 是万能的"到"LLM 只在刀刃上用"的认知转变,以及这个过程中我踩的坑和总结的方法论。
如果你也是前端,也在考虑往 AI/全栈方向转,这篇文章可能对你有用。
文章结构:
- 为什么一个前端要做 Agent
- 架构设计:9 个节点只有 1 个用 LLM
- 最值钱的不是 LLM,是收敛窗口和限流器
- Redis 不是加分项,是必需品
- 前端思维在 Agent 开发中的意外优势
- 给想转型的前端小伙伴的建议
一、为什么一个前端要做 Agent
先说背景:我写了 5 年前端,技术栈以 Vue + React + TypeScript 为主,做的是 B 端企业级工具平台------从零搭建过组件库、主导过多个复杂中后台系统的架构设计,日常处理的就是大数据量表格虚拟滚动、复杂表单联动、权限路由体系、微前端接入这些硬骨头。随着AI浪潮发展,这几年也不是只写前端,因为做的是内部工具平台,经常需要自己写 Node.js 的 BFF 层对接后端微服务接口,所以对后端的 API 设计、数据库查询、中间件这些概念并不陌生。因此绝对不是后端零基础。
去年做了我的第一个 Agent 项目------AI 智能客服。那个项目让我入了门:学会了 Prompt 工程、RAG 检索增强、对话状态管理。后端部分用的 Python + FastAPI,也是那个项目让我正式从 Node.js 过渡到了 Python 技术栈。但说实话,智能客服的 Agent 逻辑相对简单------用户提问 → 检索知识库 → LLM 生成回答,基本是单轮或多轮对话,工程复杂度不高。
做完客服 Agent 后我一直在想:有没有更复杂的场景,能把 Agent 的工程化能力真正体现出来?
机会来了------一场普通的会议中:"每天 86 个服务的告警,光是看一遍就要 1 小时,分析根因又要半小时。"
我一听就知道,这个场景比客服复杂太多了:多数据源(指标 + 日志 + 机器状态)、多步推理(收敛 → 分类 → 采集 → 诊断)、成本敏感(每天几百次 LLM 调用)。正好是我想要的"第二个 Agent 项目"。
于是我主动请缨做了这个 AIOps 排障 Agent。第一版 MVP 花了两周跑通核心流程(收敛 → 分类 → 采集 → 诊断),先在 5 个核心服务上灰度验证。之后又用了一个多月做工程化优化(Redis 缓存层、事件驱动改造、限流和成本控制),逐步扩到全量 86 个微服务、2100 台服务器。最终 MTTR(平均修复时间)从 45 分钟降到 12 分钟。
下面我把第二个 Agent 项目中最核心的设计思路分享出来------很多是从第一个客服 Agent 踩坑后总结的教训。
二、架构设计:9 个节点只有 1 个用 LLM
第一版:全靠 LLM(客服 Agent 的惯性思维)
做客服 Agent 时,流程就是"用户问什么 → LLM 答什么",LLM 承担了几乎所有智能决策。我一开始做排障 Agent 时延续了这个思路:
告警进来 → 丢给 LLM → LLM 告诉我根因是什么 → 完事
一跑起来就出问题了------客服场景一天几百次对话还 hold 得住,运维场景一天几千条告警直接炸了:
- 太贵:一条告警要消耗 4000+ token,86 个服务每天几百条告警,一天 ¥85
- 太慢:每次 LLM 调用 3-5 秒,所有环节都调 LLM 的话要 30 秒
- 不可控:LLM 偶尔抽风给个完全离谱的分类,后面的诊断全跑偏
第二版:规则优先,LLM 只做规则做不了的事
客服 Agent 教会了我怎么跟 LLM 打交道,但排障 Agent 教会了我什么时候不该用 LLM。
我重新审视了整个流程,发现一个关键事实:
80% 的决策是确定性的,不需要 LLM。
比如告警分类------如果 3 条告警里 2 条是 latency 类型,那事件类型就是"延迟异常",这用 Python 的 Counter.most_common 一行代码就能搞定,为什么要花钱让 LLM 来选?
最终架构是 LangGraph 状态机,9 个节点这样分工:
erlang
correlate → 纯规则(按服务+依赖链收敛告警)
parse_input → 纯规则(提取字段)
classify → 80%规则 + 20%LLM 兜底
build_plan → LLM 建议 + 规则强校验
run_tools → 纯工具调用(查 Prometheus/ES/CMDB)
diagnose → ⭐ 100% LLM(唯一核心)
risk_check → 纯规则(关键词匹配"重启""回滚")
finalize → 纯规则(置信度校准)
settle_case → 纯规则(高置信度自动沉淀案例)
LLM 只在 diagnose 节点真正不可替代------因为"从错误率飙升 + ConnectionRefused 日志 + 刚部署新版本"这些线索推导出"新版本连接池配置错误",需要跨领域关联推理,规则写不了。
分类节点的代码,简单到让我意外
python
from collections import Counter
TYPE_MAP = {"error_rate": "error_rate", "latency": "latency",
"cpu": "resource", "memory": "resource"}
def classify_incident(state):
# 投票:统计每种告警类型出现几次,取最多的
counter = Counter(state["alert_types"])
dominant = counter.most_common(1)[0][0] # 比如 "latency"
result = TYPE_MAP.get(dominant) # 查规则表
if result:
return {"incident_type": result} # 命中,不调 LLM
return {"incident_type": _llm_classify(state)} # 兜底
前端小伙伴应该觉得很眼熟------这跟条件渲染是一个逻辑:
tsx
if (type === 'error') return <ErrorIcon />; // 规则命中
if (type === 'warning') return <WarningIcon />; // 规则命中
return <DefaultIcon />; // 兜底
效果对比
| 全 LLM 方案 | 规则 + LLM 混合 | |
|---|---|---|
| LLM 调用次数/诊断 | 5-9 次 | 1-2 次 |
| Token 消耗 | ~10,000 | ~2,500 |
| 延迟 | 15-30 秒 | 3-5 秒 |
| 日均成本 | ¥85 | ¥32 |
一句话总结:不是 LLM 不好,是你不该让它做它不擅长的事。
三、最值钱的不是 LLM,是收敛窗口和限流器
告警风暴问题
上线第一天就出事了。
凌晨 2 点,一个服务挂了,5 分钟内产生了 47 条告警(error_rate、latency、cpu 同时飙)。按原来的设计,每条告警触发一次诊断,47 次 LLM 调用,一算要 ¥15------就这一个故障。
而且 47 次诊断结论都是一样的,因为根因就一个。
解决方案:2 分钟收敛窗口
我做了一个 AlertBuffer------同一个服务的告警先攒着,2 分钟后一起诊断:
python
def add(self, alert):
key = (alert["project_id"], alert["service_name"])
# 追加到 Redis 列表
r.rpush(buffer_key, json.dumps(alert))
# SETNX:只有第一条告警才启动定时器
is_first = r.set(timer_key, "1", nx=True, ex=120)
if is_first:
# 120 秒后自动 flush
Timer(120, self._flush, args=[key]).start()
前端小伙伴应该秒懂------这就是 debounce 的服务端版本:
javascript
// 前端 debounce:停止输入 300ms 后才搜索
const debouncedSearch = debounce(search, 300);
// 后端 AlertBuffer:同服务告警 120s 后才诊断
alert_buffer.add(alert) // 不立即诊断,等窗口到期
区别是前端 debounce 每次按键会重置计时器,而告警缓冲是固定窗口 ------第一条告警启动 120 秒倒计时,后续告警只追加不重置。更像 throttle + batch。
47 条告警 → 1 次 LLM 调用。省了 46 次。
限流器:滑动窗口
LLM API 有 rate limit(每分钟 30 次),我用 Redis Sorted Set 做了滑动窗口限流:
python
def acquire(self, r):
now = time.time()
# ① 清理 60 秒前的记录
r.zremrangebyscore(key, "-inf", now - 60)
# ② 数当前窗口有多少次
count = r.zcard(key)
# ③ 没超限就放行
if count < 30:
r.zadd(key, {f"{now}:{thread_id}": now})
return True
return False
为什么不用简单计数器?因为固定窗口有边界突发问题 :第 59 秒来 30 个请求 + 第 61 秒又来 30 个 = 2 秒内 60 个请求,但两个窗口各自都没超限。Sorted Set 滑动窗口保证任意连续 60 秒内不超过 30 次。
四、Redis 不是加分项,是必需品
一开始我什么都存在内存里------缓冲队列用 dict,缓存用 dict,限流用 deque。
看起来能跑,直到我问自己:
"服务重启了,正在收敛的告警怎么办?" "多部署一个实例,限流计数器各算各的,总量不就超了?"
答案是:必须用 Redis。
但我做了一个关键设计------Redis 优先,自动降级到内存:
python
def add(self, alert):
r = get_redis() # 尝试获取 Redis 连接
if r is not None:
self._add_to_redis(r, alert) # Redis 可用 → 用 Redis
else:
self._add_to_memory(alert) # Redis 挂了 → 降级到内存
为什么不直接强依赖 Redis?
AIOps 排障场景,可用性比一致性重要。 Redis 挂了,宁可用内存顶着(可能重启丢数据),也不能因为缓存不可用就不诊断。
最终 Redis 用了 4 个场景:
| 场景 | Redis 数据结构 | 为什么 |
|---|---|---|
| 告警缓冲队列 | List (RPUSH) |
重启不丢、多实例共享 |
| LLM 结果缓存 | String + TTL |
相同告警不重复调 LLM |
| 调用限流 | Sorted Set |
多实例共享计数 |
| 每日 Token 预算 | INCRBY |
原子累加、次日自动清零 |
五、前端思维在 Agent 开发中的意外优势
做完这个项目,我发现前端经验给了我几个别人没有的优势:
1. 状态管理思维 → Agent 状态机
LangGraph 的 AgentState 本质就是一个全局状态树,9 个节点像 reducer 一样处理状态:
python
# Agent 的 state 流转
{"alert_messages": [...], "incident_type": None}
→ correlate → {"alert_messages": [...更多], ...}
→ classify → {"incident_type": "latency", ...}
→ diagnose → {"root_causes": [...], ...}
这跟 Redux 一模一样:
javascript
// Redux 的 state 流转
{alerts: [], type: null}
→ FETCH_ALERTS → {alerts: [...], ...}
→ CLASSIFY → {type: 'latency', ...}
→ DIAGNOSE → {rootCauses: [...], ...}
2. 组件化思维 → 节点解耦
前端写组件讲究"单一职责、props 进 events 出"。Agent 节点也一样------每个节点只从 state 里取自己需要的字段,处理完返回新字段,不关心其他节点。
这让我天然就把节点写得很解耦,后来加新功能(比如 risk_check)只需要新增一个节点文件 + 在图里加一条边。
3. 降级思维 → 容错设计
前端天天跟"接口挂了怎么办"打交道(loading → error → empty 三态)。写 Agent 时我本能地给每个节点都加了降级:
- LLM 挂了 → 返回保守结论(confidence: 0.3)
- Redis 挂了 → 降级到内存
- 工具查询失败 → 跳过,用已有证据继续
有些纯后端/AI 背景的人会忽略这些,因为他们习惯"调不通就报错退出"。
4. Debounce/Throttle → 收敛窗口
上面讲的告警收敛,本质就是 debounce。前端经常写这个,我看到"告警风暴"的问题第一反应就是"加个 debounce",而后端小伙伴可能会想到 Kafka 之类的重方案。
六、给想转型的前端小伙伴的建议
1. 不要从零学 Python,从"翻译"开始
我学 Python 的方式不是看教程,而是把我熟悉的 JS 逻辑翻译成 Python:
javascript
// JS: 数组去重
const unique = [...new Set(arr)];
// JS: 条件渲染
const icon = type === 'error' ? <ErrorIcon /> : <DefaultIcon />;
// JS: debounce
const debounced = debounce(fn, 300);
翻译成 Python:
python
# Python: 列表去重
unique = list(set(arr))
# Python: 条件分支
icon = ErrorIcon if type == 'error' else DefaultIcon
# Python: Timer(类似 setTimeout)
Timer(120, fn).start()
语法不同,但编程思维是通用的。
2. Agent 的核心不是 LLM,是工程化
很多人觉得做 AI Agent 就是"调 LLM API"。不是的。
我这个项目里 LLM 相关代码大概占 15%。另外 85% 是:
- 告警收敛逻辑(状态管理)
- Redis 缓存和限流(中间件)
- 数据源抽象层(设计模式)
- 并发调度(线程池)
- 错误处理和降级(容错)
这些全是工程能力,跟 LLM 无关,跟前端经验高度相关。
3. 选一个你熟悉的业务场景做 Agent
不要凭空造需求。找一个你日常工作中有的痛点:
- 做运维平台的 → AIOps 排障 Agent(就是我做的这个)
- 做客服系统的 → 智能客服 Agent
- 做数据平台的 → 自动化数据分析 Agent
- 做文档系统的 → RAG 知识库 Agent
业务理解 + 工程能力 + AI 能力 = 你的不可替代性。
小结
做完这个项目,我最大的感悟是:
AI Agent 开发 = 10% 的 LLM + 90% 的工程化。
LLM 是那个做"最后一公里"推理的天才,但天才需要一个靠谱的团队帮他准备好数据、控制好成本、兜住错误。这个"团队"就是你写的工程代码。
而前端工程师天然擅长这些------状态管理、组件化、降级容错、性能优化------只是以前这些能力用在了浏览器里,现在换到了服务端而已。
如果你也是前端,也想往 AI 方向试试,别犹豫。工程能力比纯写代码能力值钱得多了,因为你永远没有AI会写代码。