共计 1716 个字符,预计需要花费 5 分钟才能阅读完成。
问题定义:什么是 Agent 工具调用死循环?
想象你设计了一个自动客服 Agent,当用户说 ” 转人工 ” 时触发人工服务调用,但人工服务模块又错误地调用了同一个 Agent——这就形成了典型的 调用死循环。用状态机表示就是:

stateDiagram
[*] --> Agent 处理
Agent 处理 --> 人工服务: 满足转接条件
人工服务 --> Agent 处理: 错误回调
常见触发场景包括:
- 递归函数缺少终止条件
- 消息队列的重复消费
- 微服务间的循环依赖
技术方案对比
方案 A:调用深度限制
给调用链加上 ” 保险丝 ”,就像递归函数的深度限制:
from functools import wraps
def max_depth(max_level: int):
def decorator(func):
func._current_depth = 0
@wraps(func)
def wrapper(*args, **kwargs):
if wrapper._current_depth >= max_level:
raise RecursionError(f"Max depth {max_level} exceeded")
wrapper._current_depth += 1
try:
return func(*args, **kwargs)
finally:
wrapper._current_depth -= 1
wrapper._current_depth = 0
return wrapper
return decorator
@max_depth(3)
def agent_call():
return agent_call() # 触发 RecursionError
时间复杂度 :O(1) 的深度检查开销
方案 B:DAG 任务编排
用有向无环图明确执行顺序,避免循环依赖:
import networkx as nx
# 构建任务图
task_graph = nx.DiGraph()
task_graph.add_edges_from([('数据预处理', '模型推理'),
('模型推理', '结果输出')
])
# 检查循环
if not nx.is_directed_acyclic_graph(task_graph):
raise ValueError("任务图存在循环依赖")
可视化效果:
flowchart LR
数据预处理 --> 模型推理 --> 结果输出
方案 C:超时熔断
对于异步场景,用超时机制防止无限等待:
import asyncio
from contextlib import suppress
async def agent_work():
try:
async with asyncio.timeout(5.0):
await some_io_operation()
except TimeoutError:
print("操作超时,触发熔断")
生产实践要点
必现条件检查清单
- 调用链深度超过 3 层未做限制
- 任务编排未进行 DAG 验证
- 缺少超时和重试上限配置
- 未隔离第三方服务调用
- 日志中连续出现相同调用模式
线程安全注意事项
- Python 的 GIL 会导致线程切换时的调用状态不一致
- 推荐用
threading.local()保存调用深度等状态 - I/ O 密集型场景建议直接使用 asyncio
验证环节
单元测试示例
import pytest
from unittest.mock import patch
@pytest.mark.timeout(2) # 测试超时设置
def test_recursion_detection():
with pytest.raises(RecursionError):
agent_call() # 应触发深度限制
内存泄漏检测
python -m memory_profiler your_script.py
典型内存泄漏模式:
- 每次循环内存增长固定大小
- 未被回收的回调函数引用
延伸思考
可观测性设计
- Prometheus 计数器记录调用次数
- 在日志中注入 TraceID 串联整个调用链
- 关键节点打点记录时间戳
分布式防扩散策略
- 通过 Redis 实现全局调用计数
- 消息队列启用去重 ID
- 服务网格配置熔断规则
写在最后
实际开发中最容易忽略的是 跨服务的隐式循环调用。建议在新服务上线前,用测试流量完整跑通所有可能的调用路径。记住:任何回调接口都应该默认视为危险操作,除非你能证明它绝对不会形成闭环。
正文完
