Agent响应慢?别只砍Prompt,系统性性能优化才是关键
2026/9/7 2:22:34 网站建设 项目流程

当一个 Agent 响应从 3 秒变成 30 秒,群里第一句话往往是:prompt 太长了,砍一砍。这句话听着在理,其实很像骑车放屁——动静不小,对前进没有本质帮助,偶尔还把自己憋得够呛。更现实的是,很多团队把大量时间花在逐字删 prompt 上,删完发现 Agent 还是慢,甚至因为删掉了关键约束,输出质量直接跳水。

问题的根源在于:Agent 的响应耗时是一个系统工程指标,而 prompt 长度只是其中一个输入维度。与其盯着字数做“文案减肥”,不如先把链路拆开,搞清楚慢在哪个环节,再对症下药。本文将围绕“Agent 性能优化”这个话题,拆解延迟构成、分析 prompt 优化与性能之间的真实关系,并给出可落地的排查和优化方案。文章适合正在做 Agent 应用开发、调试响应延迟、或者在架构设计阶段做性能预判的开发者。


1. 为什么“Agent 慢”不能只怪 Prompt

先澄清标题里的比喻。骑车时放屁,产生的推力微乎其微,既不能加速,也不解决问题,反而可能因为姿势变化影响踩踏节奏。Agent 性能优化里,盲目砍 prompt 就是这种“伪动作”:看起来在做事,实际效果有限,还容易引入新问题。

1.1 一个反直觉的模型认知

很多开发者默认“prompt 越长,模型处理越慢”。这个认知对了一半,但另一半很关键:大语言模型(LLM)的推理延迟主要由两部分组成。

第一部分是预填充(prefill)阶段,即模型读取并理解输入的 prompt。输入 token 越多,预填充耗时确实越长,这就是“prompt 越长越慢”这一直觉的出处。

第二部分是解生成(decode)阶段,即模型逐个生成输出 token。这个阶段的耗时与输出长度成正比,通常比预填充更慢,因为生成是串行的,一个 token 一个 token 地往外蹦。

于是问题就来了:假设一个 Agent 任务本来就需要模型做复杂推理、输出结构化结果,你把 800 字 prompt 删到 200 字,预填充时间可能只减少了不到 200 毫秒;但如果输出阶段要生成 500 个 token,这部分耗时按照单 token 生成速度来算,才是真正的大头。前面省 200 毫秒,后面一个工具调用多等 3 秒,整体优化收益趋近于零。

1.2 Agent 慢的真正延迟构成

与单次 LLM 调用不同,Agent 的完整响应链路通常包含多个 LLM 调用、工具调用、记忆检索、上下文拼接等步骤。以常见的 Agent 执行流程为例:

用户提问 → 意图识别(LLM 调用 ①) → 工具选择(LLM 调用 ②) → 执行工具(外部 API / 数据库查询) → 结果摘要(LLM 调用 ③) → 回复用户

在这个链路里,如果每一步都携带完整的系统提示词、历史对话、工具描述,那么几十 KB 的上下文会被重复计费,延迟也会叠加。此时 Agent 慢,本质上不是“prompt 太长”,而是“重复的 prompt 太多 + 串行步骤太多 + 工具调用太慢 + 上下文过大”等多个因素叠加后的结果。

只盯着 prompt 长度优化,相当于看到汽车油耗高,不去检查发动机和胎压,而是把后备箱里的矿泉水拿出来两瓶——有用,但杯水车薪。

1.3 核心结论:先定位,再优化

不反对优化 prompt,反对的是不加诊断地盲砍。正确的做法是先把 Agent 的延迟拆成可量化的指标:LLM 调用次数、输入 token 数、输出 token 数、工具调用耗时、网络往返时间。哪个环节占比高,就优化哪个环节。下面的章节会分别讲清楚方法和工具。


2. 先搞清楚:你的 Agent 慢在哪一段

2.1 建立性能基线的四类指标

要定位慢的环节,第一步是采集指标。建议在 Agent 的每个关键节点打点,至少记录以下四类数据:

指标类别具体指标说明
LLM 调用指标prompt_tokens、completion_tokens、total_tokens每次调用的 token 消耗量
LLM 耗时指标prefill 耗时、decode 耗时、首 token 延迟区分输入处理和输出生成的时间
工具调用指标工具名、请求耗时、响应体大小外部 API、数据库查询的耗时
编排指标步骤数、调用次数、总耗时整个 Agent 链路的拓扑和耗时分布

以 Python 代码为例,可以在工具函数和 LLM 调用外层包一个简单的计时器:

import time import json from functools import wraps def track_execution(func): """打印函数执行耗时(示例用,生产环境建议接入链路追踪)""" @wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) elapsed = (time.perf_counter() - start) * 1000 print(f"[TRACE] {func.__name__} 耗时 {elapsed:.2f} ms") return result return wrapper @track_execution def call_weather_api(city: str) -> dict: """模拟外部天气 API 调用""" time.sleep(0.8) # 模拟网络延迟 return {"city": city, "temperature": 26} @track_execution def call_llm(messages: list, model: str = "qwen-plus") -> str: """模拟 LLM 调用(示例用,实际按你使用的 SDK 接入)""" time.sleep(1.2) # 模拟模型推理延迟 return "根据天气 API 结果,建议穿短袖。"

实际生产环境不建议用 print,建议接入 OpenTelemetry、SkyWalking、Jaeger 等链路追踪工具,把每个 span 的耗时和属性上报到监控系统,这样才能看到整条链路的耗时瀑布图。

2.2 一次“慢请求”的链路拆解

为了直观理解延迟分布,下面模拟一次带工具调用的 Agent 请求:

步骤 耗时(毫秒) 说明 用户输入解析 50 规则解析,不涉及 LLM 意图识别(LLM 调用①) 2500 输入 5000 token,输出 80 token 工具选择(LLM 调用②) 3000 输入 6000 token,输出 120 token 天气 API 调用 800 外部接口 结果摘要(LLM 调用③) 3200 输入 6500 token,输出 300 token 回复拼装 30 纯字符串操作 总耗时 9580

此时砍掉 500 字的 prompt,三个 LLM 调用的输入 token 各减少约 100 个,预填充时间可能总共减少 300 毫秒左右。但 LLM 调用本身的生成阶段耗时、工具调用耗时、多轮串行开销依然存在,总耗时依然在 9 秒以上。

这就是“砍 prompt 是骑车放屁”这句话背后的数据逻辑:它优化的是整个链路里占比很小的一个子环节,而不是主要矛盾。

真正值得做的优化,可能是把三个 LLM 调用缩减为两个、把工具调用结果缓存、或对历史对话做压缩摘要。这些都直接影响链路结构和次数,优化空间远大于删 prompt 本身。


3. 优化 Prompt 的正确姿势:不是“砍”,而是“翻译”

前面说了不能盲目砍 prompt,但 prompt 依然需要优化。重点不是“删多少字”,而是“让模型更快、更稳地给出所需格式和内容”。

3.1 不省白不省的 Token:减少输出 token 比减少输入 token 更有效

输出 token 是逐个生成的,直接决定 decode 阶段耗时。如果可以让模型用 100 个 token 输出结构化结果,而不是用 300 个 token 输出长篇解释,那么每次调用能节省的耗时非常可观。

举一个例子,下面这段指令希望模型返回“工具是否可用”的判断:

请阅读用户问题,并结合你的知识,判断是否需要调用天气查询工具。 如果用户问题中包含城市名且意图是查询天气,那么请调用天气查询工具。 如果用户没有明确提到城市,或意图不是天气相关,那么不要调用工具。 并且请在回复中说明你的理由,以及你对用户意图的判断依据。

这段 prompt 本身没问题,但模型输出时倾向于写一大段解释。更好的做法是给模型一个“最小输出模板”:

请判断是否需要调用天气查询工具。 要求: - 只需要返回 JSON,不要输出任何解释。 - 格式:{"need_tool": true/false, "tool_name": "weather_api", "params": {"city": "北京"}} - 如果不需要调用工具,params 字段返回空对象。

同样是“判断意图”的任务,第二种 prompt 通过约束输出格式,把输出 token 从 150 降到 30,decode 耗时可能减少三分之一以上。这就是典型“不砍输入、砍输出”的优化思路。

3.2 结构化提示词:用编号、分段替代长篇描述

很多 prompt 之所以长,是因为内容重复且层次不清。例如:

你是一个智能助手,你需要帮助用户解答问题。你必须保持礼貌。你需要在回答时考虑用户的感受。 同时,你必须严格遵守安全限制。当用户问你敏感问题时,你必须拒绝回答并且给出理由。 你不需要提及这些安全规则。你的回答需要简洁、准确。

这段 100 字左右的指令里,“必须”重复了五次,信息密度低。改成结构化形式后,信息量不变但长度减半:

你是一个智能助手。回答时遵循以下规则: 1. 礼貌、简洁。 2. 涉及违法、危险、隐私等敏感话题时,只回复“抱歉,我无法回答”。 3. 不主动提及规则内容。

这种“翻译”式优化,不损失约束效果,但减少了模型需要读取的 token 数,预填充耗时也随之降低。核心思路是:把“散文式 prompt”翻译成“清单式 prompt”

3.3 案例对比:一个调优前后的 System Prompt

下面是一个实际调优对比示例。

优化前:

你是公司的客户服务助手。请根据用户的问题给出回复。 回复时语气要专业、友好。 如果用户询问订单状态,请查询订单系统。 如果用户要退货,请查询退货政策。 如果用户要求人工服务,请转接人工客服。 如果遇到不懂的问题,不要乱编,要承诺进一步确认。 回复要给出明确的信息,不要含糊。

优化后:

你是客户服务助手。使用以下规则处理问题: - 订单查询 → 调用 order_api 获取状态。 - 退货咨询 → 读取退货政策文档。 - 人工客服 → 返回人工客服工单链接。 - 其他/不确定 → 回复“已记录,将尽快答复”,不要编造信息。

优化后的 prompt 从 150 字降到 90 字,并且把“业务动作”和“响应方式”直接绑定,模型更容易理解,也更依赖工具调用而非自由发挥。这里的收益不只是输入 token 减少,还包括决策准确率的提升。

3.4 需要避免的误区

  • 删除必要的安全约束:为了省 token 把安全规则删掉,模型可能输出越界内容,生产事故成本远高于省下的几毫秒。
  • 压缩到模型无法理解:过度缩写导致模型不理解指令,反而增加重试次数,整体延迟上升。
  • 只关注 System Prompt,不关注用户消息:用户消息往往占据更多 token,需要做的是截断、摘要、检索,而不是跟着用户消息无限增长。

4. 真正的性能抓手:缓存、并行与上下文工程

定位到慢的环节以后,常见的性能优化手段集中在四个方面:缓存、上下文管理、并行执行和模型/参数选型。

4.1 缓存层:同一个问题不要问两次

Agent 应用中,很多 token 消耗和耗时来自重复计算。缓存可以大大减少这类浪费。

第一层:完整请求缓存。如果用户的问题完全相同,直接返回历史答案。适合 FAQ、操作手册查询等场景。代码示例:

from functools import lru_cache @lru_cache(maxsize=128) def get_agent_response(user_query: str) -> str: """根据用户问题获取 Agent 回复(示例用缓存)""" # 这里实际会调用 Agent 编排逻辑,为了演示直接返回固定内容 return f"关于「{user_query}」的标准答复"

第二层:语义缓存。用户问题可能措辞不同,但语义一致。通过向量化用户问题,在向量数据库中查找最相似的历史问答,命中则直接返回。这比完整请求缓存更智能,适合客服场景。

第三层:工具调用缓存。天气查询、股票价格、订单状态等外部 API 的返回结果可以缓存一段时间,避免同一个数据被反复请求。代码示例:

import time cache = {} def get_weather_with_cache(city: str, ttl: int = 600) -> dict: """带 TTL 的天气查询缓存,600 秒内重复请求直接返回缓存值""" now = time.time() if city in cache: data, expire_at = cache[city] if now < expire_at: return data data = call_weather_api(city) # 真实调用外部接口 cache[city] = (data, now + ttl) return data

4.2 上下文管理:别把整个对话历史都塞给模型

Agent 的多轮对话中,上下文会不断增长。最直接的优化方案有两个。

方案一:滑动窗口截断。只保留最近 N 条消息,更早的消息丢弃。适合对历史记忆要求不高的场景。

def truncate_messages(messages: list, max_messages: int = 10) -> list: """保留最近 max_messages 条消息,保证上下文不无限增长""" if len(messages) <= max_messages: return messages # 保留系统提示词 + 最近 max_messages - 1 条消息 system_prompts = [m for m in messages if m.get("role") == "system"] recent = messages[-max_messages:] return system_prompts + recent

方案二:摘要压缩。对较早期的对话调用一次 LLM,生成摘要,后续只携带摘要,而不是完整原文。这个方法能大幅减少输入 token,但需要注意摘要调用本身的耗时和成本。

方案三:RAG 检索。如果 Agent 需要依赖大量文档知识,不要把所有文档塞进 prompt,而是先用向量检索找到最相关的片段,再拼接进上下文。这是目前最通用的大上下文解决方案。

def build_context_with_retrieval(query: str, top_k: int = 3) -> str: """从向量数据库中检索与 query 最相关的 top_k 个片段,拼接为上下文""" # 假设 vector_db 是已配置好的向量数据库客户端 results = vector_db.similarity_search(query, k=top_k) context_blocks = [] for idx, doc in enumerate(results, 1): context_blocks.append(f"[文档{idx}]\n{doc.page_content}") return "\n\n".join(context_blocks)

4.3 并行与异步:能并行的步骤不要串行

在 Agent 链路中,有些步骤天然可以并行。例如,如果用户一次问了三个问题,分别对应三个不同的工具调用,理论上可以同时发起三个 LLM 调用,而不是等待上一个完成再执行下一个。

以 Python 的asyncio为例:

import asyncio async def call_tool_async(tool_name: str, param: str) -> str: """模拟异步工具调用""" await asyncio.sleep(1) # 模拟耗时操作 return f"{tool_name} 处理结果: {param}" async def run_parallel_tools(params: list) -> list: """并行执行多个工具调用,总耗时约等于最慢的一个""" tasks = [call_tool_async("tool_a", p) for p in params] return await asyncio.gather(*tasks) # 示例调用 # results = asyncio.run(run_parallel_tools(["参数1", "参数2"]))

需要注意的是,并行调用可能增加整体 token 消耗峰值,也可能触发 API 的限流策略。并行度需要根据实际 API 配额和限流情况做调整,不要盲目开大。

4.4 模型选型与参数调优

不同模型的推理速度和成本差异巨大。一个负责任的性能优化过程,应当对模型本身做选型评估。

  • 轻量任务用小模型:意图识别、实体抽取、格式分类等任务,用参数量较小的模型即可,耗时可显著降低。
  • 复杂推理用大模型:代码生成、多步推理、复杂指令遵循,仍然建议使用更强的大模型,避免因回答质量下降而反复重试。
  • 调整 max_tokens 参数:如果业务场景不需要超长输出,可以限制max_tokens,防止模型在生成阶段“自由发挥”。
  • 合理设置 temperature:低 temperature 更适合事实型问答,高 temperature 适合创意型写作。过高的 temperature 可能导致模型绕圈子,增加输出长度和耗时。

示例如下:

from openai import OpenAI client = OpenAI( api_key="your-api-key", base_url="https://your-llm-gateway.example.com/v1" ) response = client.chat.completions.create( model="qwen-turbo", # 轻量模型,适合意图识别 messages=[ {"role": "system", "content": "判断用户意图,只输出 JSON。"}, {"role": "user", "content": "北京明天天气怎么样?"} ], max_tokens=50, # 限制输出长度,减少 decode 耗时 temperature=0.1 # 低温度,输出更稳定 )

5. 一套可落地的 Agent 性能优化清单

结合前面的分析,整理出一套可以直接套用的优化动作清单。建议按照优先级顺序执行。

第一优先级:先建立可观测性。

没有监控就没有优化。至少记录每个 LLM 调用的 token 消耗和耗时,记录每个工具调用的耗时和成功率。这一步是后续所有优化的前提。

第二优先级:减少步骤数,而不是减少字数。

检查 Agent 链路中是否有不必要的 LLM 调用,比如“意图识别”和“工具选择”是否可以合并为一个调用。每减少一次 LLM 调用,节省的是整个步骤的耗时,效果远大于删 prompt。

第三优先级:控制输入 token,用检索替代全量上下文。

历史对话无限增长时,优先做截断或摘要;知识库内容较大时,优先用 RAG 检索相关片段,而不是全量拼接。这一步能降低预填充耗时,也能减少 API 费用。

第四优先级:控制输出 token。

输出 token 是 decode 阶段耗时的直接来源。优先使用格式约束、输出模板、低位采样参数,让模型少说废话,输出更紧凑。

第五优先级:加缓存。

完整请求缓存、语义缓存、工具调用缓存,都能在不同程度上减少重复计算。缓存命中率是关键指标,建议周期性统计。

第六优先级:并行化。

检查串行链路中是否存在可以并行的工具调用,通过异步方式并发执行。注意限流和配额,避免触发 API 429。


6. 常见问题排查表

常见错误排除思路如下:

问题现象常见原因解决思路
首字延迟高输入 token 过大,预填充耗时长检查 LLM 调用日志中的 prompt_tokens,做上下文截断或摘要
整体响应慢,但单次 LLM 调用很快Agent 串行步骤过多用链路追踪查看调用次数,尝试合并或并行化
总是调用同一个工具,重复查询缺少工具结果缓存为外部 API 增加 TTL 缓存
删除 prompt 后回答质量下降删掉了必要的约束信息用结构化、编号方式压缩,而不是删除语义
并发调用频繁报错触发 API 限流降低并行度,增加退避重试机制
模型生成大段无关内容输出未加格式约束,max_tokens 过大强制 JSON 输出,限制 max_tokens
多轮对话后上下文快速膨胀历史消息全部保留使用滑动窗口截断或摘要压缩
prompt 明明很短,还是慢慢的根因不在 prompt,而在工具调用检查工具 API 响应耗时、网络延迟

这里需要特别强调:不要因为某个方法在别人项目里有效,就直接套用。每个 Agent 的瓶颈不同,正确的做法是先看监控数据,再决定优化动作。


7. 不要做一个“骑在车上放屁”的优化者

回到标题。Agent 慢,砍 prompt,为什么说是骑车放屁?不是因为 prompt 不能优化,而是因为它只是整个链路中一个局部因素,且容易被误当作万能手段。真正的 Agent 性能优化,是一套工程方法,需要数据、链路追踪、上下文工程和架构调整,而不是文案删减。

在 Agent 开发实践中,更推荐把性能优化看作一个持续迭代的过程:先建立可观测性,再量化瓶颈,然后针对性地做上下文压缩、步骤合并、缓存和并行化。Prompt 本身当然要优化,但优化目标是提高信息密度和输出稳定性,而不是单纯追求字数减少。

如果能把这套思路用在你的 Agent 项目里,你大概率会遇到这样的情况:prompt 并没有变短多少,但整个 Agent 从 20 秒跑进了 5 秒。到那时候,你就可以自信地说,真正解决慢的问题的,不是砍 prompt,而是把 Agent 当成一个需要认真对待的系统来优化。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询