前端仔写了个 AI Agent,才发现大模型只干了 10% 的活

前言

作为一个写了 5 年 Vue + React 的前端,我用 Python + LangGraph 做了一个企业级 AIOps 排障 Agent。过程中最大的感悟是:真正难的不是调大模型,而是让大模型少干活。


这篇文章讲什么

我不会从头教你代码怎么写,因为这个年代工程思维才是价值。我要分享的是一个前端工程师做 AI Agent 项目的完整心路历程------从"LLM 是万能的"到"LLM 只在刀刃上用"的认知转变,以及这个过程中我踩的坑和总结的方法论。

如果你也是前端,也在考虑往 AI/全栈方向转,这篇文章可能对你有用。

文章结构:

  1. 为什么一个前端要做 Agent
  2. 架构设计:9 个节点只有 1 个用 LLM
  3. 最值钱的不是 LLM,是收敛窗口和限流器
  4. Redis 不是加分项,是必需品
  5. 前端思维在 Agent 开发中的意外优势
  6. 给想转型的前端小伙伴的建议

一、为什么一个前端要做 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会写代码。

相关推荐
setmoon2142 小时前
使用Scikit-learn构建你的第一个机器学习模型
jvm·数据库·python
发现一只大呆瓜2 小时前
前端模块化:CommonJS、AMD、ES Module三大规范全解析
前端·面试·vite
IT_陈寒2 小时前
一文搞懂JavaScript的核心概念
前端·人工智能·后端
IT_陈寒2 小时前
Java开发者必看!5个提升开发效率的隐藏技巧,你用过几个?
前端·人工智能·后端
前端Hardy2 小时前
Wails v3 正式发布:用 Go 写桌面应用,体积仅 12MB,性能飙升 40%!
前端·javascript·go
Laurence2 小时前
Qt 前后端通信(QWebChannel Js / C++ 互操作):原理、示例、步骤解说
前端·javascript·c++·后端·交互·qwebchannel·互操作
Pu_Nine_92 小时前
JavaScript 字符串与数组核心方法详解
前端·javascript·ecmascript
2401_833197732 小时前
为你的Python脚本添加图形界面(GUI)
jvm·数据库·python
码云数智-园园2 小时前
从输入 URL 到页面展示:一场精密的互联网交响乐
前端