AI 辅助性能优化——从性能剖析到压测验证的完整闭环

核心论点:性能优化的瓶颈不在"怎么优化"------索引怎么建、缓存怎么加这些都有标准答案。瓶颈在"优化哪里"------你不知道哪个函数是热点、哪个查询是元凶。AI 的真正价值不是写出更快的代码,而是帮你从性能剖析数据中找出瓶颈,并按优先级给出优化方案。


优化直觉 vs 优化数据

Shop-Agent 的工具选择环节------每次用户发消息,Agent 都要从多个业务工具中选出最匹配的那个。上线初期,工具选择延迟 P95 达到 800-1200ms。你凭直觉:

直觉判断 :"把模型换成更快的,或者精简 tool description 减少 token。"

→ 换更快模型 + tool description 从 200 字压缩到 50 字 → P95 降到 950ms(改善有限)

为什么改善有限?因为再怎么换模型、压缩 prompt,调用一次云端 LLM 的固有延迟就是 800ms+------你优化的只是在"LLM 本身更快",而不是"少调或不调 LLM"。

真实性能剖析数据:

工具选择延迟构成(P95 = 950ms):

环节 耗时 占比 说明
云端 LLM function calling 910ms 96% 无论如何都要等一次网络往返 + 推理
tool description 拼接 15ms 2% 你已经从 200 字压到 50 字
其他 25ms 2%

性能剖析暴露了一个结构性问题:96% 的耗时来自"每次请求都调 LLM"这个设计本身 ,而不是 LLM 不够快。你最初优化的是 tool description(2%),真正该优化的是"什么时候不该调 LLM"(96%)。这和优化 Redis 还是优化数据库的区别一样------不是怎么优化,而是优化哪里

而"优化哪里"这个决策,需要性能剖析数据 + 代码 + 业务数据交叉分析------正是 AI 能做的。下面走一遍完整的四步闭环,看 AI 在每个环节具体做了什么。


性能优化的四步闭环

#mermaid-svg-kq5Y6uhpcW5k4WgU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kq5Y6uhpcW5k4WgU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kq5Y6uhpcW5k4WgU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kq5Y6uhpcW5k4WgU .error-icon{fill:#552222;}#mermaid-svg-kq5Y6uhpcW5k4WgU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kq5Y6uhpcW5k4WgU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kq5Y6uhpcW5k4WgU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kq5Y6uhpcW5k4WgU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kq5Y6uhpcW5k4WgU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kq5Y6uhpcW5k4WgU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kq5Y6uhpcW5k4WgU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kq5Y6uhpcW5k4WgU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kq5Y6uhpcW5k4WgU .marker.cross{stroke:#333333;}#mermaid-svg-kq5Y6uhpcW5k4WgU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kq5Y6uhpcW5k4WgU p{margin:0;}#mermaid-svg-kq5Y6uhpcW5k4WgU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kq5Y6uhpcW5k4WgU .cluster-label text{fill:#333;}#mermaid-svg-kq5Y6uhpcW5k4WgU .cluster-label span{color:#333;}#mermaid-svg-kq5Y6uhpcW5k4WgU .cluster-label span p{background-color:transparent;}#mermaid-svg-kq5Y6uhpcW5k4WgU .label text,#mermaid-svg-kq5Y6uhpcW5k4WgU span{fill:#333;color:#333;}#mermaid-svg-kq5Y6uhpcW5k4WgU .node rect,#mermaid-svg-kq5Y6uhpcW5k4WgU .node circle,#mermaid-svg-kq5Y6uhpcW5k4WgU .node ellipse,#mermaid-svg-kq5Y6uhpcW5k4WgU .node polygon,#mermaid-svg-kq5Y6uhpcW5k4WgU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kq5Y6uhpcW5k4WgU .rough-node .label text,#mermaid-svg-kq5Y6uhpcW5k4WgU .node .label text,#mermaid-svg-kq5Y6uhpcW5k4WgU .image-shape .label,#mermaid-svg-kq5Y6uhpcW5k4WgU .icon-shape .label{text-anchor:middle;}#mermaid-svg-kq5Y6uhpcW5k4WgU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kq5Y6uhpcW5k4WgU .rough-node .label,#mermaid-svg-kq5Y6uhpcW5k4WgU .node .label,#mermaid-svg-kq5Y6uhpcW5k4WgU .image-shape .label,#mermaid-svg-kq5Y6uhpcW5k4WgU .icon-shape .label{text-align:center;}#mermaid-svg-kq5Y6uhpcW5k4WgU .node.clickable{cursor:pointer;}#mermaid-svg-kq5Y6uhpcW5k4WgU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kq5Y6uhpcW5k4WgU .arrowheadPath{fill:#333333;}#mermaid-svg-kq5Y6uhpcW5k4WgU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kq5Y6uhpcW5k4WgU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kq5Y6uhpcW5k4WgU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kq5Y6uhpcW5k4WgU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kq5Y6uhpcW5k4WgU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kq5Y6uhpcW5k4WgU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kq5Y6uhpcW5k4WgU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kq5Y6uhpcW5k4WgU .cluster text{fill:#333;}#mermaid-svg-kq5Y6uhpcW5k4WgU .cluster span{color:#333;}#mermaid-svg-kq5Y6uhpcW5k4WgU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kq5Y6uhpcW5k4WgU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kq5Y6uhpcW5k4WgU rect.text{fill:none;stroke-width:0;}#mermaid-svg-kq5Y6uhpcW5k4WgU .icon-shape,#mermaid-svg-kq5Y6uhpcW5k4WgU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kq5Y6uhpcW5k4WgU .icon-shape p,#mermaid-svg-kq5Y6uhpcW5k4WgU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kq5Y6uhpcW5k4WgU .icon-shape .label rect,#mermaid-svg-kq5Y6uhpcW5k4WgU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kq5Y6uhpcW5k4WgU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kq5Y6uhpcW5k4WgU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kq5Y6uhpcW5k4WgU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ✅ 达标
❌ 未达标
🔍 性能剖析
🎯 瓶颈识别
📐 方案设计
⚡ 压测验证
上线


第一步:性能剖析------别猜,让数据说话

用 AI 分析延迟分布

在工具选择的关键路径上埋好计时点(time.perf_counter),把延迟分布喂给 AI:

复制代码
@ 工具选择模块
这是工具选择函数的延迟分布数据,分析性能瓶颈:

[粘贴各环节延迟数据]

要求:
1. 找最大的耗时来源
2. 区分"固有开销"(网络 I/O)和"算法开销"(可替换的计算逻辑)
3. 按优化收益排序给建议

AI 的分析输出

复制代码
延迟分析(工具选择,采样 1000 次):

| 环节 | P50 | P95 | 占比(P95) | 优化潜力 |
|------|-----|-----|----------|---------|
| LLM function calling | 820ms | 910ms | 96% | 🔴 结构瓶颈 |
| tool description 拼接 | 12ms | 15ms | 2% | 🟢 低 |
| 结果解析 | 3ms | 5ms | <1% | 🟢 低 |

关键发现:LLM function calling 的 P50 已经 820ms------这不是模型慢,
是"每次请求都调云端 API"的设计瓶颈。优化 prompt 最多省几十 ms,
但结构不改,延迟就降不到 100ms 以下。

建议方向:在调 LLM 之前加前置过滤层,让大部分请求不经过 LLM。

"加前置过滤层"只是方向,还没回答最关键的问题:哪些请求可以不调 LLM?覆盖多少? 这需要把代码和业务数据一起喂给 AI 做交叉验证------进入第二步。


第二步:瓶颈识别------AI 读代码 + 业务数据,交叉验证

从热点到根因

AI 拿到"LLM function calling 占比 96%"之后,不会满足于"那就换更快的模型"------它会进一步验证"是不是每次请求都必须调 LLM"。喂给它代码 + 一批真实的用户问法样本:

复制代码
@ 工具选择模块
分析工具选择函数和意图-工具映射表的关系。
附上 100 条真实的用户问法样本。
回答:
1. 哪些问法可以用规则匹配直接确定工具,不需要 LLM?
2. 如果加一层关键词过滤,能覆盖多少比例的请求?
3. 剩下的模糊问法,除了 LLM 还有没有更轻量的方案?

AI 读代码 + 数据后输出:

复制代码
根因分析:

当前设计:
  每个请求 → 把所有工具 + 描述打包成 prompt → 调 LLM function calling
  → 无论用户说"查订单 OR123"还是"我要退款",都走同样的路径

业务数据分析(100 条用户问法):
  - 85% 包含明显关键词:订单号、退款、物流、优惠券、余额
  - 10% 语义明确但无关键词:"我买的东西到哪了"→ 查物流
  - 5% 模糊:"帮我看看"(需要 LLM 理解上下文)

优化方向:三层过滤,逐层递进
  关键词匹配(<1ms)→ 覆盖 85%
  语义匹配(~20ms,本地模型)→ 覆盖到 94%
  本地小模型确认(~50ms)→ 覆盖到 99%
  兜底: 云端 LLM function calling → 仅 <1% 请求触发

除了分析业务数据,AI 还能系统性地扫描代码中的反模式------"无条件远程调用"。这种模式人在 Code Review 时最容易忽略:代码逻辑本身没问题,只是架构没考虑"是否每次都需要"。让 AI 扫描整个模块,它能同时揪出工具选择、实体提取、向量索引加载等多处同类问题------不只是看单个函数热点,而是看设计模式。

有了瓶颈清单,接下来要把它转化为可落地的方案------不是随意提几个优化建议,而是对照 agent 内置的规范逐层设计。


第三步:方案设计------AI 基于 Agent 规范给方案

performance-optimizer agent 内置了优化技术规范(分层过滤架构、降级策略、缓存预热)。让 AI 对照这个规范设计方案:

复制代码
@ 工具选择模块
基于上述瓶颈分析(LLM function calling 无条件触发),设计多层过滤方案。

要求:
- 对照 performance-optimizer agent 的优化规范
- 每层给出:技术选型、预期延迟、预期覆盖率、降级策略
- 不改业务逻辑,只改工具选择函数

AI 输出带对照 agent 规范的方案:

复制代码
优化方案(对照 performance-optimizer 规范):

方案 1:关键词匹配(预期:90% 请求 <1ms 完成)
  技术:INTENT_TOOL_MAP 确定性映射 + 长关键词优先匹配
  延迟:<1ms
  覆盖率:85%
  降级:无关键词命中 → 进入语义匹配层
  风险:低(纯 Python 字符串操作,无外部依赖)

方案 2:语义匹配(预期:前两层覆盖 94%)
  技术:本地语义匹配模型 + 余弦相似度
  延迟:~20ms(含 embedding 编码)
  降级:相似度 < 阈值 → 进入本地模型层
  风险:中(需选型/部署本地模型,建议压测后按需调整阈值和权重)

方案 3:本地小模型确认(预期:仅 5-10% 请求触发)
  技术:本地部署的轻量 LLM(量化推理)
  关键设计:交叉校验------语义匹配层的 Top-1 被本地模型丢弃时强制补回
  延迟:~50ms(GPU p50)
  降级:本地模型不可用 → 云端 LLM function calling
  风险:中(需 GPU/内存资源,首次加载 3-5 秒需预热)

另外,方案设计过程中顺带发现了第二步提到的向量索引每次从磁盘加载问题(每次约 8ms 磁盘 I/O)------
这是一行代码的改动(启动时预加载到模块级变量),零风险,顺手修掉。

建议实施顺序:1 → 2 → 3(先做零风险的,逐步加层)

> 注:三层既指流水线层次,也指实施优先级------先落地能覆盖 85% 的,再逐层补尾。

注意,三层方案不是一次 prompt 生成的。实际过程是:AI 先给关键词匹配 → 压测跑完发现覆盖率不够 → 追问"剩下的 15% 怎么覆盖" → AI 给语义匹配 → 再追问 → 给本地模型。每一层是上一轮压测结果驱动的增量决策,不是一次性规划出来的。

同样,方案 3 的风险评估也不是 AI 凭空判断的------你追问"本地模型有什么风险",AI 才知道要标注资源开销和预热策略。这个风险标签让人能在"延迟收益"和"运维成本"之间做决策。

综上,AI 辅助设计出的最终方案------三层工具选择流水线:

技术 延迟 覆盖
关键词匹配 长关键词优先 + 意图→工具映射 <1ms 85%
语义匹配 本地语义匹配模型 ~20ms 94%(累计)
本地模型兜底 本地部署的轻量 LLM ~50ms 5-10% 模糊请求
云端 LLM 兜底 仅前三层都搞不定时才调 800ms <1%

预期效果:90% 的请求在前两层完成,工具选择延迟从 950ms 降到 <50ms------不是"让 LLM 更快了",而是"根本就没调 LLM"。下一步压测验证这个预期。


第四步:压测验证------AI 生成压测脚本 + 分析结果

AI 生成压测脚本

复制代码
基于工具选择函数的方法签名,生成压测脚本:
- 100 条测试用例,覆盖 5 种意图 × 3 种难度(关键词明确/语义模糊/需推理)
- 指标:各层的覆盖率、延迟(P50/P95)、LLM 调用次数
- 输出 Python 脚本,可直接运行

AI 生成(关键片段):

python 复制代码
bench_cases = [
    # 关键词明确 ------ 期望关键词命中
    ("查一下订单 ORD123456 的物流状态", "query-order"),
    ("我要退款,订单号 REF987654", "request-return"),
    # 语义模糊 ------ 期望语义匹配命中
    ("我买的东西到哪了", "check-shipping"),
    ("上次那笔钱退了吗", "request-return"),
    # 需推理 ------ 期望本地模型确认
    ("帮我看看上次那个", "query-order"),   # "上次那个" 指最近一次订单
]

for query, expected_tool in bench_cases:
    t0 = time.perf_counter()
    tools = await tool_selector.select(intent, query)
    elapsed = (time.perf_counter() - t0) * 1000
    layer = "keyword" if elapsed < 1 else "semantic" if elapsed < 50 else "local_model" if elapsed < 100 else "llm"
    results.append({
        "query": query,
        "expected": expected_tool,
        "actual": tools[0].name if tools else None,
        "latency_ms": elapsed,
        "hit_layer": layer,  # 根据延迟反推命中了哪一层
    })

AI 分析压测结果

把脚本跑完后的原始数据喂给 AI:

复制代码
这是三层工具选择流水线的压测结果,分析是否达标、有无退化:

优化后实测(100 条用例):
| 层 | P50 | P95 | 覆盖率 | 触发率 |
|----|-----|-----|--------|--------|
| 关键词匹配 | <1ms | <1ms | 85% | 85% |
| 语义匹配 | 18ms | 22ms | 94%(累计) | 15% |
| 本地模型 | 48ms | 94ms | 99% | 5% |
| LLM 兜底 | 800ms | 950ms | 100% | <1% |

对比优化前基线(第一步剖析数据,P95=950ms, API 调用率 100%):
- P95 延迟:950ms → 94ms(↓90%)
- P50 延迟:820ms → <1ms(↓99.9%)
- API 调用率:100% → <6%(↓94%),近零 token 消耗
分层延迟分布:90% 请求 <50ms,9% 50-100ms,<1% 触发 LLM 兜底。

AI 结论:

复制代码
三层流水线全覆盖率达到 99%,各层延迟和覆盖率均在设计预期内。
关键词命中率 85% 稳定,语义匹配阈值当前设置合理------未出现大量语义
模糊问法穿透到 LLM 层的情况。建议按优先级上线关键词匹配和语义匹配层(1→2),
本地小模型层作为下个 sprint 的可选增强。

⚠️ 注意:压测的 100 条用例覆盖了三种难度级别(关键词明确/语义模糊/需推理)。
如果只测"查订单 ORD123"这种关键词明确的用例,覆盖率看起来是 100%,
上线后就会发现大量模糊问法掉到 LLM 兜底------压测用例的难度分布要模拟线上真实流量。

如果语义匹配覆盖率没到预期(比如 <90%)------不是"再调调参数",而是回到第二步瓶颈识别:把未命中的用例重新分类,看是关键词库缺词还是向量索引阈值问题,针对性地改。


优化决策矩阵:什么时候该优化

不是所有性能问题都值得修。AI 可以帮你做这个判断:

问题 P95 影响 优化成本 风险 推荐
向量索引预加载 省 8ms/次 改 1 行 极低 ✅ 立即做
关键词匹配 -1100ms(85%请求) 1d ✅ 立即做
语义匹配 -80ms(部分请求) 2d ✅ 立即做
本地小模型 -750ms(长尾请求) 3d 中(资源+预热) 🟡 下个 sprint

核心要点

  1. 不要凭直觉优化------先性能剖析。

    ❌ "LLM 太慢了" → 换更快模型/压缩 prompt → 可能只省了几十 ms

    ✅ 性能剖析 → 发现 96% 耗时在"每次调 LLM"这个设计上 → 改架构:大部分请求不调 LLM

    你优化的可能是 tool description 的 2%,忽略的却是 LLM 调用的 96%。

  2. AI 的价值是"读数据 + 读代码 + 读业务"的交叉分析。 延迟分布告诉你 LLM 占了 96%,读代码告诉你每次请求都无条件调 LLM,读业务数据告诉你 85% 的问法有关键词------三条线交叉,才得出"加关键词前置过滤"的方案。任何一条线单独看都不够。

  3. 一次只加一层,加完就压测验证。

    ❌ "把关键词匹配+语义匹配+本地模型+索引预加载+权重全加上" → 命中率异常无法定位是哪个设计导致的

    ✅ 先上关键词匹配 → 压测 → 确认覆盖率 → 再上语义匹配 → 压测 → 再上本地模型

    逐层叠加等于保留精准的回滚能力。

  4. 不要只看平均延迟------看覆盖率 + 分位值。

    ❌ "P50 降了 99%,优化成功!" ------但 P95 可能因为本地模型推理不稳反而升高

    ✅ 对比各层覆盖率、P50/P95/P99 延迟、API 调用率

  5. performance-optimizer agent 的规范是"检查清单",不是"行动指令"。 AI 对照规范给出分层方案,人评估每层的成本、风险、运维复杂度,决定实施顺序和深度。

  6. 分层优化要留降级路径。 关键词没命中 → 自动进语义匹配,语义匹配不够 → 自动进本地模型,本地模型不可用 → 回云端 LLM。每一层都是上一层的安全网,不能因为加了一层就丢了兜底。


持续监控:让压测定期跑

性能优化不是一次性的。可以让压测脚本定期执行,AI 对比本周和上周基线,检测性能退化:

复制代码
cbc --agent performance-optimizer --permission-mode acceptEdits \
    --prompt "对比本周三层流水线压测结果与上周基线,
             输出:各层覆盖率变化、延迟退化检测、新增未命中用例分析"

尤其要关注关键词命中率------如果产品新增了业务场景(如"积分兑换"),关键词库没同步更新,覆盖率会悄悄下降。