常见的代理循环故障
无限循环、重复进行相同的工具调用,以及始终无法得到最终答案。
常见的代理循环故障 是 CoddyKit 上的免费 AI Agents 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 AI Agents 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 AI Agents 课程共包含 4 节课。
代理循环及其失败模式
代理循环会反复执行:推理 → 调用工具 → 观察结果 → 再次推理。这个循环功能强大,但也很脆弱。多个广为人知的失败模式可能会困住代理、浪费令牌,并且无法产生有用的输出。
了解这些失败模式,是做好防范的第一步。
失败 1:无限循环
当代理反复使用相同参数调用同一工具,却没有取得进展时,就会发生无限循环。如果工具返回了无帮助的结果,而代理无法通过推理摆脱困境,就可能出现这种情况。
# Example of an agent in an infinite loop:
# Step 1: reasoning='Need to search for Python docs'
# tool='search_web', args={'query': 'Python documentation'}
# Step 2: reasoning='Search result was unhelpful, try again'
# tool='search_web', args={'query': 'Python documentation'}
# Step 3: reasoning='Search result was unhelpful, try again'
# tool='search_web', args={'query': 'Python documentation'}
# ... repeats until max_iterations or token budget is exhausted
print('Symptom: same tool + same arguments appearing repeatedly in steps')
print('Fix: detect repeated (tool, args) pairs and break the loop')失败 2:卡住状态
卡住状态是无限循环的一种更隐蔽的形式。代理会持续推理并调用不同的工具,却无法收敛到最终答案。它在不同方法之间来回切换,却没有取得进展。
# Example of a stuck agent:
# Step 1: tool='search_web', args={'query': 'topic A'}
# Step 2: tool='search_web', args={'query': 'topic B'} # different args
# Step 3: tool='search_web', args={'query': 'topic A'} # back to first
# Step 4: tool='read_document', args={'url': '...'}
# Step 5: tool='search_web', args={'query': 'topic A'}
# ... no FINAL_ANSWER ever produced
print('Symptom: agent takes many steps but never calls FINAL_ANSWER')
print('Fix: max_iterations guard + force final answer if limit is near')失败 3:缺少最终答案
有些代理会一直循环,始终不判断任务是否已完成。它们收集信息,却从未停下来整合并返回结果。这会浪费令牌和时间。
# An agent that never concludes:
def run_agent_bad(query: str, max_steps: int = 20) -> str:
for step in range(max_steps):
action = llm_decide_action(query, history)
if action['type'] == 'tool':
result = execute_tool(action)
history.append(result)
# BUG: No check for 'final_answer' type!
# The agent loops until max_steps, returning None
return None # never actually returns an answer
# Fix: explicitly check for final_answer signal
def run_agent_good(query: str, max_steps: int = 20) -> str:
for step in range(max_steps):
action = llm_decide_action(query, history)
if action['type'] == 'final_answer':
return action['answer'] # exit cleanly
execute_tool(action)
return 'Reached step limit without a conclusion.'失败 4:工具调用解析错误
当 LLM 为函数调用生成格式错误的 JSON 时,工具执行器无法解析它。编写不佳的代理会崩溃,或静默地跳过该步骤。健壮的代理会捕获解析错误,并将错误反馈给 LLM。
import json
def safe_parse_tool_call(arguments_str: str) -> dict:
try:
return json.loads(arguments_str)
except json.JSONDecodeError as e:
print(f'Failed to parse tool arguments: {e}')
print(f'Raw: {arguments_str}')
return None
def execute_step(tool_call) -> str:
args = safe_parse_tool_call(tool_call.function.arguments)
if args is None:
# Feed the error back to the LLM in the next step
return f'ERROR: Could not parse tool arguments. Raw: {tool_call.function.arguments}'
return run_tool(tool_call.function.name, args)失败 5:工具未返回有用数据
工具在技术上可能执行成功(没有异常),却返回空数据或无用数据。代理必须处理这种情况,不能假定每次工具调用都会返回可供采取行动的信息。
def run_agent_with_empty_result_handling(query: str) -> str:
for step in range(20):
action = decide_next_action(query, history)
if action['type'] == 'final_answer':
return action['answer']
result = execute_tool(action['tool'], action['args'])
# Detect empty results and provide context
if not result or result.strip() == '':
observation = f'Tool {action["tool"]} returned no data. Try a different approach or different arguments.'
elif 'error' in result.lower():
observation = f'Tool error: {result}. Consider a different tool or query.'
else:
observation = result
history.append({'tool': action['tool'], 'result': observation})
return 'Could not complete task within step limit.'失败 6:虚构的工具名称
LLM 有时会生成不存在的工具名称。尝试调用之前,务必根据已注册的工具验证工具名称。发生这种情况时,请向代理返回信息明确的错误。
REGISTERED_TOOLS = {
'search_web': search_web_function,
'get_weather': get_weather_function,
'calculate': calculate_function
}
def dispatch_tool(tool_name: str, args: dict) -> str:
if tool_name not in REGISTERED_TOOLS:
available = ', '.join(REGISTERED_TOOLS.keys())
return (
f'ERROR: Unknown tool "{tool_name}". '
f'Available tools: {available}. '
f'Please use one of the available tools.'
)
tool_fn = REGISTERED_TOOLS[tool_name]
return tool_fn(**args)失败 7:令牌预算耗尽
长时间运行且将完整工具结果存入上下文的代理,可能会达到 LLM 的上下文窗口上限。在将大型工具结果添加到历史记录之前,请先进行摘要或截断。
def truncate_tool_result(result: str, max_chars: int = 2000) -> str:
if len(result) <= max_chars:
return result
truncated = result[:max_chars]
return f'{truncated}\n... [result truncated to {max_chars} chars]'
def add_observation_to_history(history: list, tool_name: str, result: str):
safe_result = truncate_tool_result(result, max_chars=2000)
history.append({
'role': 'tool',
'content': safe_result,
'tool_name': tool_name
})
print(f'[Step] Tool={tool_name}, Result length={len(result)} (stored {len(safe_result)})')
if __name__ == '__main__':
demo_history = []
add_observation_to_history(demo_history, 'search_web', 'x' * 3000)
通过程序检测失败模式
请编写一个诊断函数,分析代理的步骤历史,以识别发生了哪种失败模式。这在调试期间极其有价值。
def diagnose_agent_failure(steps: list) -> str:
if not steps:
return 'No steps recorded'
# Check for infinite loop: same (tool, args) repeated
seen = {}
for s in steps:
key = (s.get('tool'), str(s.get('args')))
seen[key] = seen.get(key, 0) + 1
repeated = {k: v for k, v in seen.items() if v > 2}
if repeated:
return f'INFINITE_LOOP: repeated actions: {repeated}'
# Check for missing final answer
has_answer = any(s.get('type') == 'final_answer' for s in steps)
if not has_answer and len(steps) >= 15:
return 'STUCK_STATE: many steps taken but no final answer'
# Check for parse errors
errors = [s for s in steps if 'ERROR' in str(s.get('result', ''))]
if len(errors) > 2:
return f'TOOL_ERROR: {len(errors)} tool errors in pipeline'
return 'OK'
if __name__ == '__main__':
demo_steps = [{'tool': 'search_web', 'args': {'q': 'weather'}} for _ in range(3)]
print('Diagnosis:', diagnose_agent_failure(demo_steps))
实现简单的步骤预算
每个生产环境的代理循环都必须设置硬性步骤上限。这是最重要的安全机制——无论 LLM 作何决定,它都能保证循环终止。
def run_agent_with_budget(query: str, max_steps: int = 15) -> dict:
history = []
for step in range(1, max_steps + 1):
print(f'[Step {step}/{max_steps}]')
action = decide_next_action(query, history)
if action['type'] == 'final_answer':
return {
'status': 'success',
'answer': action['answer'],
'steps_taken': step
}
result = execute_tool(action['tool'], action['args'])
history.append({'step': step, 'tool': action['tool'], 'result': result})
if step == max_steps - 1:
# Warn the agent it must conclude
history.append({'role': 'system',
'content': 'You must provide a FINAL_ANSWER on the next step.'})
return {'status': 'timeout', 'answer': None, 'steps_taken': max_steps}快速参考:失败模式及修复方法
以下是六种代理循环失败模式及其修复方法:
- 无限循环:检测重复的(工具、参数)对;通过错误反馈中断循环
- 卡住状态:设置最大迭代次数防护机制;接近上限时强制返回最终答案
- 缺少最终答案:明确检查操作中是否存在最终答案信号
- 解析错误:将 JSON 解析放入异常捕获结构中;将错误反馈给 LLM
- 空工具结果:检测空字符串;提供“无数据”反馈
- 虚构的工具名称:根据已注册的工具进行验证;返回错误消息
知识检查:代理循环失败模式
请测试您对常见代理循环失败模式的理解。
回顾:常见的代理循环失败模式
您现在可以识别并防范主要的代理循环失败模式:
- 无限循环、卡住状态和缺少最终答案,都需要最大迭代次数防护机制
- 工具调用解析错误需要在 JSON 解析外层加入异常捕获结构
- 空工具结果需要进行检测,并向 LLM 提供信息明确的反馈
- 虚构的工具名称需要根据已注册的工具列表进行验证
- 令牌预算耗尽需要截断结果
健壮的代理循环会预先考虑所有这些失败模式,并以恰当的方式进行处理。
常见问题解答
「常见的代理循环故障」课时是免费的吗?
是的 — 「常见的代理循环故障」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Agents 课程的其余内容,请升级到 CoddyKit PRO。 AI Agents 课程共包含 4 节课。
「常见的代理循环故障」这节课中我会学到什么?
无限循环、重复进行相同的工具调用,以及始终无法得到最终答案。 你通过在浏览器中直接运行的动手代码来练习 AI Agents,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 AI Agents 需要有经验吗?
无需任何先前经验。CoddyKit 上的 AI Agents 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「常见的代理循环故障」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 AI Agents 课中编写并运行代码吗?
能。每节 AI Agents 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。