Agent 慢,不是模型慢,是问得太多
做 AI Agent 应用的人,大概率都经历过这样的场景:模型明明已经升级了,推理速度也提升了,但你的 Agent 应用却还是“一步三卡”。用户提一个简单问题,Agent 要在后台翻来覆去地调用模型好几轮,每一轮都要等几百毫秒甚至几秒,最终一个原本一两秒能回答的问题,硬生生拖成了十几秒。
如果把耗时拆开看,你会发现真正让人难以接受的不是模型单次推理有多慢,而是 Agent 一直在“问”——问模型怎么理解、问工具返回什么、问要不要换个方案、问下一步做什么。问得越多,上下文越长,每一轮推理的代价也越高,最后整体延迟呈指数级恶化。这个问题在接入 DeepSeek 这类高性价比模型时尤为明显,因为很多团队被模型单价吸引,却忽略了调用次数和上下文长度带来的总成本。
这篇文章想聊清楚一件事:Agent 慢的真正瓶颈是什么,以及如何用工程手段把“问得太多”变成“问得准、问得少”。我们会从延迟构成、上下文膨胀、工具调用设计、Harness 约束层等角度展开,最后给出可落地的优化思路和验证方法。
1. 这篇文章真正要解决的问题
先说结论:Agent 系统的性能瓶颈,主要不在单次模型推理,而在推理次数和上下文长度。
一个典型的 Agent 请求生命周期大概是这样:
- 用户提问。
- Agent 规划:调用一次模型,让模型决定调用哪个工具。
- 工具执行:调用搜索、查数据库、调 API。
- 观察结果:把工具返回结果拼回上下文,再次调用模型。
- 循环以上过程,直到模型认为任务完成。
如果一次任务要经过 5 轮模型调用,每轮上下文有 8000 token,那么实际产生的模型处理量是 5 次推理请求的总和。相比之下,单次推理即使慢一点,也只有一两次请求的延迟。真正拖垮整体响应的是轮次,不是单次速度。
这个问题在 RAG 类 Agent 上尤其明显。用户问一个问题,Agent 先要判断要不要检索,检索完要判断要不要二次检索,二次检索完还要判断要不要重新组织答案。每一步都需要模型推理,每一步都会引入额外延迟。很多时候,用户只是想查一个事实,Agent 却花了三次工具调用去确认。
另一个隐蔽的问题是上下文膨胀。每调用一次工具,工具的返回结果都会塞进上下文。工具返回的内容通常包含大量冗余字段,比如时间戳、日志信息、完整 JSON。到了第三轮、第四轮,上下文里已经堆了几千甚至上万的无关 token。上下文越长,模型处理这些 token 的时间就越长,注意力计算的开销也越大。哪怕模型本身很快,面对 2 万 token 的上下文,单次推理时间也会显著上升。
所以,真正适合读这篇文章的人,是那些已经在做 Agent 应用、被延迟和成本困扰的开发者。如果你正准备接入 DeepSeek 或其他高性价比模型做 Agent,这篇文章能帮你在设计阶段就避开“问得太多”的坑。如果你只是好奇 Agent 的优化方向,这篇文章也会给你一个清晰的排查思路。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心概念:Agent 延迟从哪里来
要理解 Agent 为什么慢,先要拆清楚 Agent 的完整工作流程。一个标准的 ReAct 风格 Agent,循环过程如下:
code复制用户输入 -> 模型推理(思考+行动) -> 工具调用 -> 结果回填 -> 模型再推理 -> ... -> 最终回答
每一步的时间开销可以用一个公式近似表示:
Agent 总延迟 ≈ 模型单次推理延迟 × 推理次数 + 工具执行延迟 × 工具调用次数 + 上下文传输和处理开销
其中,模型单次推理延迟由两个因素决定:输入序列长度和输出序列长度。输入越长,模型需要处理的 attention 计算量越大;输出越长,生成阶段需要的解码步数越多。工具执行延迟一般远小于模型推理延迟,因为大部分工具调用是毫秒到几百毫秒级别的 API 请求。
从公式可以看出,优化方向无非三个:
- 降低推理次数:让 Agent 一次推理就能决定正确动作,减少来回试探。
- 降低上下文长度:避免把无关信息塞进历史,保持上下文精简。
- 降低单次推理成本:换更快的模型、更短的输出约束、结构化生成。
很多团队一上来就盯着第 3 点,觉得“模型换快一点就行了”。但实际上,如果推理次数从 3 次降到 1 次,延迟直接减少 2/3,比换模型的收益更明显。而且推理次数减少,意味着每次推理的上下文也更短,两个因素互相强化。
还有一个容易忽略的点:模型输出格式。如果 Agent 的输出是自由文本,模型可能用 200 个 token 来描述“我要调用搜索工具”,而如果用结构化约束输出,可能只需要 30 个 token。输出 token 数量直接影响生成阶段的延迟,因为解码是逐 token 进行的。约束模型只输出必要字段,是削减延迟最直接的手段之一。
3. “问得太多”的典型症状
“问得太多”不是一个抽象说法,它在实际系统里有非常具体的表现。排查 Agent 慢的问题,先对照下面这些症状。
3.1 规划阶段反复试错
Agent 在执行任务时,如果缺少足够的信息来决定调用哪个工具,就会反复尝试。比如用户问“今天北京适合穿什么衣服”,Agent 先调用天气查询,拿到温度后,发现不知道空气质量,再调用空气质量查询,然后还要调用穿衣建议生成……每一步试错都至少多一次模型推理。如果模型规划能力足够强,或者系统预先设定了明确的工具选择规则,这些试错是可以降低的。
3.2 上下文无限膨胀
工具返回结果默认全部塞入上下文,是大多数 Agent 实现的默认行为。比如查数据库返回了 50 条记录,但用户只需要其中 3 条;搜索接口返回了 10 个链接的摘要,但真正有用的只有 1 个。Agent 不会自动过滤,所有结果都进入下一轮推理。上下文越长,模型处理越慢,而且垃圾信息还会干扰模型判断,导致更多无效推理。
这类问题的排查方法是:在日志里打印每一轮推理的输入上下文长度。如果发现第二轮、第三轮的上下文长度呈线性甚至超线性增长,说明工具结果没有被有效压缩。
3.3 工具粒度太细
工具设计对推理次数的影响往往被严重低估。假设一个 Agent 需要完成“查询订单状态并通知用户”的任务,如果工具拆成“查询订单”“获取配送信息”“生成通知文案”三个独立工具,Agent 需要至少三轮推理来完成三个动作。而如果设计一个“查询订单完整状态”的工具,一次调用返回所有信息,Agent 一轮推理就能完成。
工具粒度不是越细越好,而是要在复用性和执行效率之间取平衡。超级细的工具让 Agent 更灵活,但也让 Agent 更容易问很多次。
3.4 错误恢复路径长
Agent 调用工具失败时,很多实现会让模型重新规划,并在上下文里追加错误信息。这本身没问题,但如果没有设置重试上限,Agent 可能在同一个工具上反复失败、反复重新规划,产生大量无效推理。合理做法是:工具调用失败时,在应用层重试一两次,再失败则直接短路,让 Agent 换一个工具或给出兜底回答,而不是让模型在错误的泥潭里反复打转。
4. 优化方向:从“多问”到“少问且问得准”
理解了延迟构成和症状,下面进入正题:如何让 Agent 少问,并且问得准。优化手段分四个层次,从应用层到模型层,可叠加使用。
4.1 工具结果压缩
这是最直接、见效最快的优化。工具返回结果在进入上下文之前,先在应用层做一次裁剪或摘要。
对于结构化数据(JSON、数据库结果),只保留模型决策需要的字段;对于长文本(搜索摘要、文档片段),用最大边际相关性(MMR)或简单的长度截断,只保留与当前任务最相关的段落。压缩的结果是上下文更短,单次推理更快,模型被无关信息干扰的概率也更低。
python复制# 文件路径:agent/compress_tool_result.py
# 示例:JSON 工具结果压缩,只保留指定字段
import json
def compress_tool_result(raw_result: str, keep_fields: list[str], max_items: int = 5) -> str:
"""压缩工具返回结果,只保留模型决策需要的字段"""
try:
data = json.loads(raw_result)
except json.JSONDecodeError:
# 非 JSON 数据,直接截断
return raw_result[:max_items * 200]
if isinstance(data, list):
# 列表类型:限制条数 + 裁剪字段
compressed = []
for item in data[:max_items]:
if isinstance(item, dict):
compressed.append({k: item[k] for k in keep_fields if k in item})
else:
compressed.append(item)
return json.dumps(compressed, ensure_ascii=False)
if isinstance(data, dict):
return json.dumps({k: data[k] for k in keep_fields if k in data}, ensure_ascii=False)
return str(data)[:max_items * 200]
这个函数的核心逻辑:列表数据限制条数,字典数据只保留指定字段。调用方式是在工具执行之后、上下文拼装之前执行一次。不要把这个逻辑交给模型自己判断,模型没有能力在生成之前就知道哪些字段有用,应用层硬过滤更可控。
4.2 工具合并与分层设计
工具设计决定了 Agent 的推理轮次。常见的优化思路有两种。
第一种是合并且聚合:把多个高频同时调用的工具合并为一个“聚合工具”。比如“查询订单”和“查询配送信息”合并为“查询订单全链路信息”,一次调用返回所有字段。Agent 只需要一次模型推理完成工具选择。
第二种是分层设计:高频基础工具保持小而精,低频复杂任务用高层工具。比如电商客服 Agent,底层提供“查询商品”“查询库存”“查询物流”三个原子工具,同时提供一个“处理售前咨询”的高层工具,Agent 优先选择高层工具,只有高层工具无法覆盖时才降级到原子工具。这样大部分请求只需要一次或两次模型推理。
python复制# 文件路径:agent/tool_registry.py
# 示例:从原子工具到聚合工具的注册方式
class Tool:
def __init__(self, name, description, handler):
self.name = name
self.description = description
self.handler = handler
# 原子工具:查询订单基本信息
get_order = Tool(
name="get_order",
description="根据订单ID查询订单基本信息,包括商品、金额、下单时间",
handler=lambda order_id: {"order_id": order_id, "items": [...], "amount": ...},
)
# 聚合工具:一次返回订单全链路信息
get_order_full = Tool(
name="get_order_full",
description="根据订单ID查询订单基本信息、支付状态、物流信息",
handler=lambda order_id: {
"order_id": order_id,
"basic": get_order.handler(order_id),
"payment": {"status": "paid"},
"logistics": {"tracking": "SF123456", "status": "in_transit"},
},
)
工具的描述信息(description)也要精心设计。模型根据描述来决定选择哪个工具,描述写得含糊,模型就更容易选错,选错就会多问一次。描述应该包含:工具能做什么、输入参数要求、什么场景下使用、什么场景下不要使用。
4.3 用 Harness 约束模型行为
Harness 在这里指的是围绕模型推理的外层控制机制。它的目标不是改模型参数,而是在推理时通过 prompt 结构、输出格式约束和决策流程限制,让模型按预设路径执行,减少自由发挥空间。
一个典型的 Harness 包含三部分:
- 系统提示词中的决策规则:明确告诉模型“一次调用只能选择一个工具”“如果信息足够,立即生成最终答案,不要多做一步”。很多 Agent 慢,是因为模型在信息足够后仍然选择再来一轮确认。决策规则可以直接切断这种行为。
- 输出格式约束:要求模型输出严格的 JSON 或 Markdown 结构,字段固定为
thought、action、action_input。这样模型不会输出大段无关推理,输出 token 数从 300 降到 50,生成时间大幅缩短。 - 循环终止条件:应用层强制限制最大推理轮数(比如 3 轮),超过后直接返回当前信息的最优回答。这个限制避免了病态循环。
DeepSeek 系列模型在结构化输出和理解复杂指令方面表现较好,但即便模型能力再强,没有 Harness 约束,推理轮次还是会失控。Harness 的价值在于:把模型的能力收敛到你需要它做的事上,而不是让它自己决定怎么做。
bash复制# 文件路径:agent/prompts/system.md
# 示例:用于约束模型决策行为的系统提示词(DeepSeek 适配版)
你是一个任务执行 Agent。你的目标是用最少步骤完成用户请求。
执行规则:
1. 如果当前信息足以回答用户问题,直接输出 final_answer,不要调用任何工具。
2. 每一步只能选择并调用一个工具。
3. 工具返回结果后,优先基于已有信息继续推进,不要重复调用相同工具。
4. 最多执行 3 轮工具调用。超过后必须输出 final_answer。
5. 输出必须是 JSON 格式,字段如下:
{"thought": "说明当前判断", "action": "工具名或 final_answer", "action_input": "工具参数或最终答案"}
这个提示词的要点是把“不要多问”显式化。模型天然倾向于稳妥、反复确认,这是训练数据的分布特征。你需要在系统提示词里用明确的规则对冲这种倾向。
4.4 并行化与预取
有些场景下,Agent 必须调用多个工具才能完成任务,那能不能让工具调用并行?可以,但需要谨慎处理。
如果多个工具之间没有依赖关系,可以设计为并行分支。比如用户问“对比 A 和 B 两款手机”,Agent 需要分别查询 A 的配置、B 的配置、价格对比。这里三个查询互不依赖,可以并行发起。实现方式是在 Harness 里支持多工具并发调用,模型一次决策就可以触发多个工具。
python复制# 文件路径:agent/parallel_tools.py
# 示例:并发执行无依赖的工具调用
import asyncio
async def run_tools_in_parallel(tool_calls: list[dict]):
"""并行执行多个工具调用,减少串行等待时间"""
tasks = []
for call in tool_calls:
tool_name = call["name"]
params = call["params"]
tool = TOOL_MAP[tool_name]
tasks.append(asyncio.create_task(tool.handler(**params)))
results = await asyncio.gather(*tasks)
return results
# 示例:一次决策触发两个无依赖查询
async def main():
results = await run_tools_in_parallel([
{"name": "get_phone_config", "params": {"brand": "A"}},
{"name": "get_phone_config", "params": {"brand": "B"}},
])
print(results)
判断并行安全性的标准很简单:两个工具调用之间是否存在数据依赖。如果 B 工具的参数需要 A 工具的输出,就不能并行。如果完全独立,并行能直接把工具执行时间减半。但如果工具调用本身只有几十毫秒,并行优化意义不大,真正的瓶颈仍然在模型推理轮次。
4.5 缓存与复用
“问得太多”的另一种解决思路是:已经问过的,短时间就别再问了。
对 Agent 系统来说,有两个层面的缓存值得做。
第一层是结果缓存。对于确定性工具(数据库查询、价格查询、天气查询),在输入参数一致且数据时效允许的情况下,直接复用上次结果,跳过工具调用和模型判断过程。第二层是规划缓存。如果用户反复问同一类问题(比如“我的订单到哪了”),可以跳过第一次的复杂规划步骤,直接按预设流程执行。这里的预设流程相当于把模型之前的推理结果固化下来。
python复制# 文件路径:agent/result_cache.py
# 示例:简单的时间窗口工具结果缓存
import time
class ToolResultCache:
def __init__(self, ttl_seconds: int = 60):
self.cache = {}
self.ttl = ttl_seconds
def get(self, tool_name: str, params: dict):
key = f"{tool_name}:{str(sorted(params.items()))}"
entry = self.cache.get(key)
if entry and time.time() - entry["ts"] < self.ttl:
return entry["data"]
return None
def set(self, tool_name: str, params: dict, data):
key = f"{tool_name}:{str(sorted(params.items()))}"
self.cache[key] = {"ts": time.time(), "data": data}
在实现缓存时要注意一件事:不是所有工具结果都适合缓存。涉及实时状态的结果(股票价格、订单物流)缓存时间要短;静态数据(产品参数、文档内容)缓存时间可以长一些。宁可保守,不要因为缓存过期导致用户看到错误信息。
5. 完整示例:用 DeepSeek + Harness 搭建一个低延迟 Agent
上面的优化方向比较分散,这里用一个完整的示例把它们串起来。我们构建一个“商品咨询 Agent”,基于 DeepSeek API,实现查询商品信息、库存、价格三个能力,并通过 Harness 约束推理轮次。
5.1 环境准备
本文示例使用 Python 3.10+,依赖 openai SDK 作为 DeepSeek API 的调用客户端。DeepSeek 兼容 OpenAI 接口格式,所以不需要引入额外的 SDK。
bash复制# 安装依赖
pip install openai
需要注意,DeepSeek API 的 base_url 和模型名称与 OpenAI 不同,请以 DeepSeek 官方文档提供的实际参数为准。下面的代码中模型名称写成 deepseek-chat,如果你的项目使用其他模型名称,请同步修改。
python复制# 文件路径:agent/config.py
# 配置文件:模型与 API 参数
import os
DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")
DEEPSEEK_BASE_URL = "https://api.deepseek.com/v1" # 以官方文档实际值为准
DEEPSEEK_MODEL = "deepseek-chat" # 以官方文档实际值为准
MAX_TOOL_ROUNDS = 3 # 最大工具调用轮次
MAX_OUTPUT_TOKENS = 300 # 限制输出长度,缩短生成时间
5.2 工具实现
定义三个原子工具:查询商品信息、查询库存、查询价格。这里用模拟数据代替真实数据库,方便演示逻辑。
python复制# 文件路径:agent/tools.py
# 工具定义与实现
def get_product_info(product_id: str) -> dict:
"""查询商品基本信息"""
# 实际项目中替换为数据库查询
products = {
"A100": {"name": "无线鼠标", "brand": "DemoBrand"},
"B200": {"name": "机械键盘", "brand": "DemoBrand"},
}
return products.get(product_id, {"error": "unknown product"})
def get_stock(product_id: str) -> dict:
"""查询库存"""
stock = {"A100": 120, "B200": 5}
return {"product_id": product_id, "stock": stock.get(product_id, 0)}
def get_price(product_id: str) -> dict:
"""查询价格"""
prices = {"A100": 99.0, "B200": 299.0}
return {"product_id": product_id, "price": prices.get(product_id, 0)}
TOOL_MAP = {
"get_product_info": get_product_info,
"get_stock": get_stock,
"get_price": get_price,
}
这三个工具覆盖了商品咨询的核心场景。注意一个细节:每个函数的 docstring 写清楚输入参数和作用,后续生成给模型的工具描述时会用到。
5.3 Harness 核心逻辑
Harness 的核心是一个循环:模型输出 → 解析 action → 执行工具 → 结果压缩回填 → 再次调用模型。循环结束条件有两个:模型输出 final_answer,或者达到最大轮数。
python复制# 文件路径:agent/harness.py
# Harness 核心控制逻辑
import json
from openai import OpenAI
from agent.config import DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, DEEPSEEK_MODEL, MAX_TOOL_ROUNDS, MAX_OUTPUT_TOKENS
from agent.tools import TOOL_MAP
from agent.compress_tool_result import compress_tool_result
SYSTEM_PROMPT = """你是一个商品咨询 Agent。你的目标是用最少步骤回答用户问题。
执行规则:
1. 如果已有信息足以回答用户问题,直接输出 final_answer。
2. 每一步只能调用一个工具。
3. 工具结果会返回 JSON,请基于其中字段判断是否信息足够。
4. 最多执行 3 轮工具调用,超过后必须输出 final_answer。
5. 输出必须为 JSON 格式:{"thought": "...", "action": "工具名或final_answer", "action_input": "..."}
"""
def build_tool_descriptions() -> str:
"""把工具注册表转换为模型可读的描述"""
descriptions = []
for name, func in TOOL_MAP.items():
descriptions.append(f"- {name}: {func.__doc__}")
return "\n".join(descriptions)
def run_agent(user_query: str) -> str:
client = OpenAI(api_key=DEEPSEEK_API_KEY, base_url=DEEPSEEK_BASE_URL)
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
messages.append({"role": "user", "content": user_query})
tool_descriptions = build_tool_descriptions()
for round_idx in range(MAX_TOOL_ROUNDS + 1):
# 每次调用前把工具描述附加到上下文
current_messages = messages + [
{"role": "user", "content": f"可用工具列表:\n{tool_descriptions}\n请根据当前信息决策下一步动作。"}
]
response = client.chat.completions.create(
model=DEEPSEEK_MODEL,
messages=current_messages,
max_tokens=MAX_OUTPUT_TOKENS,
temperature=0.1, # 低温度减少随机试错
)
content = response.choices[0].message.content
# 清理模型输出中的 Markdown 代码块标记
content = content.strip().strip("```json").strip("```").strip()
try:
decision = json.loads(content)
except json.JSONDecodeError:
# 模型输出非 JSON 时,直接把它当最终答案
return content
if decision["action"] == "final_answer":
return decision["action_input"]
tool_func = TOOL_MAP.get(decision["action"])
if not tool_func:
# 工具不存在时,避免让模型反复试错,直接回到模型要求重新决策
messages.append({"role": "assistant", "content": content})
messages.append({"role": "user", "content": "你选择的工具不存在,请重新选择。或直接输出 final_answer。"})
continue
# 执行工具并压缩结果
raw_result = tool_func(**json.loads(decision["action_input"]))
compressed_result = compress_tool_result(
json.dumps(raw_result, ensure_ascii=False),
keep_fields=["product_id", "name", "stock", "price", "brand"],
)
messages.append({"role": "assistant", "content": content})
messages.append({
"role": "user",
"content": f"工具 {decision['action']} 返回结果(已压缩):{compressed_result}。请继续决策。",
})
# 达到最大轮数,强制返回当前信息
return "抱歉,未能获取完整信息。当前已有的信息有限,请补充更多关键词后重试。"
这段代码的核心是循环控制。在每一轮里,我们做了几件事:
- 把工具描述重新附加到上下文中,确保模型每次都能看到完整工具列表。为了避免文本过长的工具列表反复出现,你也可以在 HARness 里把工具描述放到 System Prompt 中,只在一开始注入一次。
- 用
temperature=0.1降低模型的随机性,防止它在工具选择上反复横跳。 - 解析模型输出 JSON,提取
action与action_input。 - 工具结果经过
compress_tool_result压缩后再回填上下文。 - 如果模型输出了非法 JSON,直接返回原文作为最终答案,而不是让它重新生成,避免无效轮次。
5.4 调用入口
python复制# 文件路径:agent/main.py
# 启动入口
from agent.harness import run_agent
if __name__ == "__main__":
query = "A100 这款产品有货吗?价格是多少?"
answer = run_agent(query)
print("Agent 回答:", answer)
运行方式:
bash复制export DEEPSEEK_API_KEY="你的 API Key"
python -m agent.main
预期结果为 Agent 在一次或两次推理内返回包含库存和价格的答案。你可以在运行前给代码加上 round_idx 打印,直观看到实际推理轮次。
6. 运行结果与效果验证
跑通示例后,如何判断优化是否有效?不要只看“能回答问题”这一层,要从以下三个维度量化验证。
6.1 验证推理轮次
在 run_agent 里加一行日志,打印 round_idx 和每轮上下文长度。
python复制# 在 harness.py 循环内追加
print(f"[Round {round_idx}] 当前消息数: {len(messages)}, 估算 token: {sum(len(m.content) for m in messages)}")
判断标准:同样的用户问题,如果优化前需要 3 到 4 轮推理,优化后能稳定收敛到 1 到 2 轮,说明 Harness 约束和工具合并生效了。如果轮次没有下降,优先检查 System Prompt 的决策规则是否被模型遵守,以及工具描述是否足够清晰。
6.2 验证延迟变化
用 Python 的 time 模块记录总耗时:
bash复制time python -m agent.main
或者更精确地在代码里记录:
python复制import time
start = time.time()
answer = run_agent(query)
print(f"总耗时: {time.time() - start:.2f}s")
记录优化前后的数据对比。重点关注两个指标:总耗时和首 token 延迟(如果需要流式输出的话)。从工程经验看,上下文压缩通常能带来 30% 到 50% 的延迟下降,推理轮次控制能带来 50% 以上的延迟下降,两者叠加效果更明显。
6.3 验证成本变化
成本的计算方式也变了:原来 4 轮推理、每轮 5000 token 的输入,现在可能只需 2 轮推理、每轮 1500 token。直接按 token 计费的成本能下降一半以上。对 DeepSeek 这类低价模型来说,优化后的成本收益虽然没有极度夸张,但成本同样不是最终目的,延迟下降才是关键。
6.4 失败时的排查起点
如果优化后 Agent 反而变慢了,第一步看日志中的推理轮次记录,确认是不是模型在输出非法 JSON,导致代码走进了异常分支。第二步看上下文长度,确认 compress_tool_result 是否生效——如果每轮上下文长度仍在快速增长,说明压缩逻辑没有被正确调用。
7. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| Agent 总是超过最大轮次才回答 | System Prompt 约束不够显式 | 检查日志中每轮模型输出内容,确认模型是否无视了规则 | 增强提示词:加“不要重复调用相同工具”“信息足够后立即输出最终答案”;降低 temperature |
| 上下文长度快速增长 | 工具结果未压缩或压缩失效 | 打印进入上下文的原始数据和压缩后数据 | 在工具执行后立即调用压缩函数,丢弃无关字段 |
| 模型输出非法 JSON 频繁 | 输出格式约束不严格 | 检查 response 原始输出 | 在 System Prompt 中给出 JSON 示例;使用结构化输出或函数调用能力 |
| 工具调用经常选错 | 工具描述不清晰 | 检查工具描述是否包含使用场景和禁用边界 | 重写工具描述,加入“何时使用、何时不要用” |
| 工具执行报错导致重试 | 工具内部异常或参数不合法 | 查看工具执行日志 | 在应用层统一捕获异常,单工具失败不超过 2 次,失败即换方案 |
| 信息已经足够但模型还调用工具 | 模型过度谨慎 | 增加“已有信息足够立即输出 final_answer”的硬规则 | 在 agent 侧判断字段完整性,强行短路人 |
这里要特别提醒一个容易踩的坑:不要把最大轮数设置得太小。如果 MAX_TOOL_ROUNDS=1,而任务确实需要查两个工具才能完成,Agent 会被迫在信息不足的情况下生成答案。合理范围是 2 到 3 轮,同时配合足够的信息完整性判断条件。
8. 最佳实践与工程建议
8.1 从日志开始排查
任何 Agent 性能优化都从日志开始。至少记录三类信息:每轮推理的输入 token 数、推理轮次、每轮模型决策内容。没有这些数据,你无法判断瓶颈到底在模型、工具、还是上下文。建议在开发环境直接打印完整轨迹,在生产环境用结构化日志采集。
8.2 把 Harness 做薄,把工具做强
Harness 的主要价值是流程控制,不建议在 Harness 层堆积太多业务逻辑。业务规则尽量下沉到工具内部,让每个工具都能在“一条指令”下完成完整子任务。工具越强,Agent 问得越少。
8.3 每个工具都要有“信息完整性”判断
在压缩工具结果时,给返回数据加一个系统字段,比如 is_sufficient。模型可以根据这个字段判断是否继续。虽然在本文示例中没体现,但在生产级 Agent 中,这是降低轮次的可靠做法。
json复制{
"product_id": "A100",
"name": "无线鼠标",
"stock": 120,
"price": 99.0,
"_sufficient": true
}
8.4 小流量灰度验证
不要一次性把优化后的 Agent 全量上线。可以先让 10% 流量走新 Harness,对比延迟和用户满意度,确认稳定后再放大。优化的每一个改动(压缩策略、轮次限制、工具合并)都可能影响回答质量,灰度是必要的安全网。
8.5 安全边界
另一个不能忽略的点是安全与权限控制。Agent 的工具调用是真实执行动作的,不是模拟演示。在正式项目中,所有工具执行前一定要做权限校验、参数白名单校验和频率限制,避免恶意输入通过工具调用造成破坏。尤其在工具涉及数据库操作、文件写入或外部 API 触发时,必须在应用层明确校验调用来源和参数合法性。
9. 总结与后续学习方向
这篇文章的核心判断是:Agent 的性能瓶颈通常不在模型推理本身,而在推理轮次和上下文管理。解决了“问得太多”的问题,Agent 的延迟和成本都会显著下降。
可以把这次优化的思路沉淀为四句话:
- 无论模型多快,减少推理轮次永远是第一优先级。
- 工具返回结果进入上下文前,必须压缩。
- Harness 的核心价值是用规则约束模型的决策路径。
- 并行调用与缓存是降低延迟的辅助手段,但前提是不破坏依赖关系。
如果你用 DeepSeek、GPT 或其他大模型做 Agent,接下来的学习方向可以是:深入理解函数调用(Function Calling)的机制、学习流式输出的首 token 延迟优化、了解上下文缓存(Context Caching)在重复请求中的应用,以及在 Long Context 场景下如何做信息选择性召回。这些都是“问得少”之后的进阶话题。
最后建议把上面这个最小示例改成你自己的业务场景,加一点真实工具,跑一遍完整流程,记录优化前后的轮次和耗时。数据会让你对 Agent 系统的性能有更直观的判断,也会帮你找到下一步最值得优化的位置。
