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 用量""" passProviderRegistry 提供插件式注册:
_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 |
|---|---|---|
openai | OpenAIProvider | openai.OpenAI(base_url=..., api_key=...) |
gemini | GeminiProvider | google.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 | 仅使用 openai 和 gemini |
| 重试风暴 | 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 的选择 | ✅ 使用 | ❌ 未采用 |