跳转到内容

Agent 节点执行器:Provider 抽象 + 工具调用 + 重试机制

Agent 节点执行器:Provider 抽象 + 工具调用 + 重试机制

学习目标

  • 理解 AgentNodeExecutor 的完整执行管线
  • 掌握 ModelProvider 抽象与 ProviderRegistry 注册机制
  • 了解 tenacity 重试策略的配置与应用
  • 能够扩展新的模型 Provider

项目实践

执行管线概览

AgentNodeExecutor.execute() 的完整管线:

Provider 注册机制

ModelProvider 是一个抽象基类:

class ModelProvider(ABC):
def __init__(self, config: AgentConfig):
self.config = config
self.base_url = config.base_url
self.api_key = config.api_key
self.model_name = config.name
self.provider = config.provider
self.params = config.params or {}
@abstractmethod
def create_client(self):
"""创建对应 provider 的客户端"""
pass
@abstractmethod
def call_model(self, client, conversation, timeline, tool_specs=None, **kwargs):
"""调用模型并返回 ModelResponse"""
pass
@abstractmethod
def extract_token_usage(self, response):
"""从 API 响应中提取 Token 用量"""
pass

ProviderRegistry 提供插件式注册:

_provider_registry = Registry("agent_provider")
class ProviderRegistry:
@classmethod
def register(cls, name, provider_class, *, label=None, summary=None):
metadata = {"label": label, "summary": summary}
metadata = {k: v for k, v in metadata.items() if v is not None}
_provider_registry.register(name, target=provider_class, metadata=metadata)
register_model_provider_schema(name, label=label, summary=summary)
@classmethod
def get_provider(cls, name):
return _provider_registry.get(name)

内置 Provider:

Provider实现Client
openaiOpenAIProvideropenai.OpenAI(base_url=..., api_key=...)
geminiGeminiProvidergoogle.genai.Client(...)

重试机制

使用 tenacity 库实现可配置的重试策略:

from tenacity import Retrying, retry_if_exception, stop_after_attempt, wait_random_exponential
retry_config = agent_config.retry or AgentRetryConfig()
if retry_config.enabled:
retryer = Retrying(
stop=stop_after_attempt(retry_config.max_attempts or 5),
wait=wait_random_exponential(
min=retry_config.min_wait_seconds or 1.0,
max=retry_config.max_wait_seconds or 6.0,
),
retry=retry_if_exception(
lambda exc: should_retry(exc, retry_config)
),
)

重试判断逻辑:

def should_retry(exc, retry_config):
# 不重试的异常类型
if type(exc).__name__ in retry_config.non_retry_exception_types:
return False
# 基于状态码
if hasattr(exc, "status_code") and exc.status_code in retry_config.retry_on_status_codes:
return True
# 基于异常类型
if any(type(exc).__name__ == t for t in retry_config.retry_on_exception_types):
return True
# 基于错误子串
if any(s in str(exc) for s in retry_config.retry_on_error_substrings):
return True
return False

工具调用循环

Agent 执行器实现标准的工具调用循环:

while True:
# 1. 调用模型
response = self._invoke_provider(provider, client, conversation, ...)
# 2. 检查是否有工具调用
if response.tool_calls:
for tool_call in response.tool_calls:
result = self.tool_manager.call_tool(tool_call)
conversation.append(FunctionCallOutputEvent(tool_call.id, result))
else:
# 3. 无工具调用,结束循环
break

上下文窗口管理

Agent 节点配置 context_window 控制保留的消息数量:

行为
-1保留所有历史消息
0仅保留 System Prompt
N > 0保留最近的 N 条消息

实现:

if node.context_window > 0:
conversation = conversation[-node.context_window:]
elif node.context_window == 0:
conversation = [msg for msg in conversation if msg.role == MessageRole.SYSTEM]

问题与规避

问题表现规避策略
Provider 未注册Provider 'xxx' not found仅使用 openaigemini
重试风暴API 持续失败 → 指数退避浪费时间配置 non_retry_exception_types
工具调用超时工具执行慢于 timeout增大 tooling[].config.timeout
上下文窗口截断context_window: 3 丢失历史关键任务使用 -1 保留全部
Token 泄露API Key 硬编码在 YAML使用 ${API_KEY} 环境变量

设计取舍

抽象 Provider vs 硬编码 API

ChatDev 选择抽象 ModelProvider

  • 优势:新增模型只需实现 3 个抽象方法,无需修改执行器
  • 代价:每个 Provider 需要适配 create_client()call_model()extract_token_usage()

tenacity vs 自定义重试

维度tenacity自定义重试
功能完整性高(指数退避、条件重试、回调)需手动实现
配置灵活性高(YAML 可直接映射)
依赖引入+1 个外部依赖
ChatDev 的选择✅ 使用❌ 未采用

参考来源