我把 MES Agent 从 55 秒优化到 1 秒:一次 Trace 驱动的性能排查

0 阅读6分钟
今年4月我开始做一个面向制造业 MES 领域的业务分析 Agent。用户可以用自然语言问「工单 WO-xxx 什么状态」、「8D 报告 D4 怎么做」,Agent 会从知识库或 MES 系统返回答案。

项目跑通之后,我以为最大的挑战是 Prompt 调优和 RAG 准确率。结果第一周就被打脸——**响应延迟 55 秒**。

用户点一下问题,盯着空白屏幕 55 秒,体验非常糟糕。更离谱的是,我一开始以为是 LLM 推理慢,差点去换更快的模型。

后来发现 96% 的时间根本没花在 LLM 上。

这篇文章记录我用 Trace 工具定位真正瓶颈、把延迟压到 1 秒的完整过程。


一、先别猜,用数据定位

遇到性能问题,最忌讳的就是猜。

  • 是不是模型太慢?
  • 是不是 Prompt 太长?
  • 是不是网络问题?

这些猜测会让你做出错误的优化决策——比如花三天换模型,结果发现延迟一点没变。

我用 LangSmith 的 Trace 功能,把一次请求里每个节点的耗时拆解出来。

结果长这样(简化版): planner_node 0.82s ← LLM 调用 tool_decide_node 0.71s ← LLM 调用 tool_exec_node 57.57s ← ⚠️ 问题在这里 task_execute_node 3.69s ← LLM 调用 reflect_node 1.12s ← LLM 调用 summary_node 2.14s ← LLM 调用 ───────────────────────────── 总计 约 66s tool_exec_node 一个人吃掉了 57 秒,占了 86% 的时间。

而其他 5 个节点的 LLM 调用加起来才 8.5 秒——问题根本不是 LLM 慢。

二、定位到具体动作:模型冷加载

tool_exec_node 里做了三件事:

  1. 加载 BGE Embedding 模型(约 25M 参数)
  2. 加载 Reranker 模型(bge-reranker-v2-m3,约 568M 参数)
  3. 执行向量检索 + Rerank 精排

加一行 print 实测: 【懒加载】首次调用,加载 BGE Embedding 和 Chroma 向量库... Loading weights: 100%|██████| 71/71 【懒加载】加载 Reranker 模型 bge-reranker-v2-m3... Loading weights: 100%|██████| 393/393 【懒加载】Reranker 加载完成 (后续检索 + 精排) 模型加载本身占了大头——约 50 秒。

根因找到了: 代码用了「懒加载」——第一次调用时加载模型到内存。听起来很聪明,实际上每次进程重启都要重新加载 50 秒。

更糟糕的是,我最初在 Streamlit 里跑这个 Agent——Streamlit 每次交互都 rerun 整个脚本,导致每次点击问题都要重新加载模型。

这就是 55 秒的真相:不是算法慢,是架构问题。

三、三个优化动作

动作 1:模型预热

把加载从「用户请求时懒加载」挪到「服务启动时预热」。 def _warmup_models(): """服务启动时预热,用户请求全部享受稳态性能""" print("【模块预热】开始加载模型...") _ = _get_vector_db() # 加载 Embedding + Chroma _ = get_reranker() # 加载 Reranker _ = chroma_search("预热查询") # 真跑一次,吃掉 JIT 编译开销 print("【模块预热】完成")

模块 import 时自动触发

_warmup_models() 效果:

  • 服务启动慢 50 秒(一次性,运维等)
  • 用户请求全部享受稳态性能

动作 2:双进程架构

Streamlit 不适合做重计算。 改成 FastAPI 后端 + Streamlit 前端双进程架构:

服务职责启动速度特点
Streamlit(前端)UI 渲染、登录、对话交互秒开不加载任何模型
FastAPI(后端)Agent 编排、模型推理50s 预热模型常驻内存

数据流:用户浏览器 → Streamlit (8501) → HTTP → FastAPI (8000) → 返回结果

模型加载隔离到 FastAPI 常驻进程,Streamlit 重启不影响。

动作 3:快速模式跳过 Reranker

这一步是质变。

Reranker(bge-reranker-v2-m3)是一个 568M 参数的大模型,作用是"把粗召回的 10 篇文档精排到 Top 3"。

问题在于——它在 CPU 上跑得极慢。 每次精排 10 对文档要 30 秒。

但我实测发现:向量 Top-2 的精度对 90% 的查询已经够用。

于是我做了个决策:快速模式直接跳过 Reranker。

深度模式:完整 RAG

docs = vector_db.similarity_search(query, k=10) # 粗召回 10 条 scores = reranker.compute_score(pairs) # 精排(慢,30s) top_docs = ranked[:3]

快速模式:直接 Top-2

docs = vector_db.similarity_search(query, k=2) # 直接取 2 条

不做 Reranker

代价是 RAG 召回精度略降(从 1.0 降到 0.7~0.8),但 Top-2 仍准确。

36 秒 → 1 秒。

四、最终效果

指标优化前优化后
快速模式响应55s1.06s
首字延迟—0.74s
深度模式响应55s44s
缓存命中时—0.01s

优化幅度 98%。

而且我保留了深度模式(完整 Reranker + 六节点 Reflection),用于复杂分析任务——简单问题快速,复杂分析深度。


五、几条反思

1. 定位比调参重要 100 倍

如果我一开始就去"换更快的 LLM",可能会花三天,性能一点没变——因为 LLM 只占总耗时的 14% 。

先用工具剖分细节,再动手优化。

2. 懒加载不一定好

"懒加载"听起来优雅——按需加载,不浪费时间。但实际上:

  • 服务端场景:流量稳定,早晚要加载,不如预热
  • 用户请求时加载:每次都要等一次,体验极差

判断懒加载好坏的标准是:这个"第一次"多久发生一次? 只发生一次(服务启动)→ 预热更好;经常发生(不同用户不同模型)→ 懒加载更好。

3. 架构问题比代码问题更隐蔽

我花了两天才意识到"Streamlit rerun 导致模型重加载"这个问题——因为代码逻辑本身是对的,只是 Streamlit 的执行模型和它不兼容。

同样的代码,换个执行环境可能完全不一样。

4. 不要追求"完美",要追求"合适"

Reranker 确实能提升精度。但对于一个快速问答场景,1 秒响应比 0.2 的精度提升更重要。

所以我保留了两种模式——用户根据场景选择。


六、延伸:这个项目后来做了什么

性能优化只是开始。后来项目还做了不少工程化改造:

  • BOM 版本守卫:用代码级前置校验解决 LLM 幻觉(比 Prompt 规则更可靠)
  • MES 实时数据集成:封装 6 个工具(工单/库存/设备/质量),让 Agent 能查实时数据
  • JWT 认证 + 多会话隔离:从"个人 Demo"到"能交付"
  • Prometheus + Grafana:6 个业务指标实时监控
  • Docker Compose 容器化:5 容器一键启动
  • 单元测试 + CI/CD:15 个测试 + GitHub Actions

项目开源在 GitHub:
https://github.com/Wang-qi-git/langgraph-mes-business-agent

感兴趣的同学可以看看,欢迎交流。

最后

这个项目让我最深的体会是:做 AI 应用,工程能力比算法能力更重要。

Prompt 调得再好、模型选得再对,如果架构是错的,用户体验依然是灾难。

55 秒到 1 秒,靠的不是更聪明的模型,而是更清醒的架构判断。