Coverage for agentos/tools/async_executor.py: 30%
248 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-06 10:59 +0800
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-06 10:59 +0800
1"""
2v1.15.1 — 异步工具执行优化:并发控制 + 超时熔断 + 性能监控。
4核心功能:
51. 并发执行控制:限制同时执行的工具数量
62. 超时熔断:工具执行超时自动中断
73. 性能监控:记录工具执行时间、成功率
84. 智能重试:根据错误类型自动重试
9"""
11from __future__ import annotations
13import asyncio
14import time
15from dataclasses import dataclass, field
16from enum import Enum
17from typing import Any, Dict, List, Optional, Set, Tuple
19from .base import BaseTool, ToolResult
20from .validation import ToolErrorClassifier, ErrorCategory
23class ExecutionStatus(str, Enum):
24 """工具执行状态。"""
25 PENDING = "pending"
26 RUNNING = "running"
27 SUCCESS = "success"
28 TIMEOUT = "timeout"
29 FAILED = "failed"
30 CANCELLED = "cancelled"
33class CircuitBreakerState(str, Enum):
34 """熔断器状态。"""
35 CLOSED = "closed" # 正常状态,允许执行
36 OPEN = "open" # 熔断状态,拒绝执行
37 HALF_OPEN = "half_open" # 半开状态,尝试恢复
40@dataclass
41class ExecutionMetrics:
42 """工具执行性能指标。"""
43 tool_name: str
44 execution_count: int = 0
45 success_count: int = 0
46 failure_count: int = 0
47 timeout_count: int = 0
48 total_execution_time: float = 0.0
49 last_execution_time: float = 0.0
50 last_error: Optional[str] = None
52 @property
53 def success_rate(self) -> float:
54 if self.execution_count == 0:
55 return 0.0
56 return self.success_count / self.execution_count
58 @property
59 def average_execution_time(self) -> float:
60 if self.execution_count == 0:
61 return 0.0
62 return self.total_execution_time / self.execution_count
64 def record_success(self, execution_time: float) -> None:
65 self.execution_count += 1
66 self.success_count += 1
67 self.total_execution_time += execution_time
68 self.last_execution_time = execution_time
69 self.last_error = None
71 def record_failure(self, execution_time: float, error: str) -> None:
72 self.execution_count += 1
73 self.failure_count += 1
74 self.total_execution_time += execution_time
75 self.last_execution_time = execution_time
76 self.last_error = error
78 def record_timeout(self, execution_time: float) -> None:
79 self.execution_count += 1
80 self.timeout_count += 1
81 self.total_execution_time += execution_time
82 self.last_execution_time = execution_time
83 self.last_error = "timeout"
86@dataclass
87class CircuitBreaker:
88 """熔断器:防止工具持续失败。"""
90 failure_threshold: int = 5 # 连续失败次数阈值
91 reset_timeout: float = 30.0 # 熔断恢复时间(秒)
92 half_open_max_attempts: int = 3 # 半开状态最大尝试次数
94 _state: CircuitBreakerState = field(default=CircuitBreakerState.CLOSED)
95 _failure_count: int = 0
96 _last_failure_time: float = 0.0
97 _half_open_attempts: int = 0
99 def can_execute(self) -> bool:
100 """检查是否允许执行。"""
101 current_time = time.time()
103 if self._state == CircuitBreakerState.OPEN:
104 # 检查是否应该进入半开状态
105 if current_time - self._last_failure_time >= self.reset_timeout:
106 self._state = CircuitBreakerState.HALF_OPEN
107 self._half_open_attempts = 0
108 self._failure_count = 0 # 重置失败计数
109 return True
110 return False
112 elif self._state == CircuitBreakerState.HALF_OPEN:
113 if self._half_open_attempts >= self.half_open_max_attempts:
114 return False
115 return True
117 return True # CLOSED 状态
119 def record_success(self) -> None:
120 """记录成功执行。"""
121 if self._state == CircuitBreakerState.HALF_OPEN:
122 # 半开状态成功,恢复正常
123 self._state = CircuitBreakerState.CLOSED
124 self._failure_count = 0
125 self._half_open_attempts = 0
126 else:
127 self._failure_count = 0
129 def record_failure(self) -> None:
130 """记录失败执行。"""
131 self._failure_count += 1
132 self._last_failure_time = time.time()
134 if self._state == CircuitBreakerState.HALF_OPEN:
135 self._half_open_attempts += 1
136 # 半开状态失败,重新熔断
137 if self._half_open_attempts >= self.half_open_max_attempts:
138 self._state = CircuitBreakerState.OPEN
139 elif self._failure_count >= self.failure_threshold:
140 self._state = CircuitBreakerState.OPEN
142 @property
143 def state(self) -> CircuitBreakerState:
144 return self._state
146 @property
147 def time_until_reset(self) -> float:
148 """距离熔断恢复的剩余时间。"""
149 if self._state != CircuitBreakerState.OPEN:
150 return 0.0
151 elapsed = time.time() - self._last_failure_time
152 return max(0.0, self.reset_timeout - elapsed)
155class AsyncToolExecutor:
156 """异步工具执行器,支持并发控制和熔断。"""
158 def __init__(
159 self,
160 max_concurrent: int = 10,
161 default_timeout: float = 30.0,
162 enable_circuit_breaker: bool = True
163 ):
164 """
165 初始化异步工具执行器。
167 Args:
168 max_concurrent: 最大并发执行数
169 default_timeout: 默认执行超时时间(秒)
170 enable_circuit_breaker: 是否启用熔断器
171 """
172 self.max_concurrent = max_concurrent
173 self.default_timeout = default_timeout
174 self.enable_circuit_breaker = enable_circuit_breaker
176 # 并发控制
177 self._semaphore = asyncio.Semaphore(max_concurrent)
178 self._active_tasks: Set[asyncio.Task] = set()
180 # 性能监控
181 self._metrics: Dict[str, ExecutionMetrics] = {}
182 self._circuit_breakers: Dict[str, CircuitBreaker] = {}
184 # 工具超时配置
185 self._tool_timeouts: Dict[str, float] = {}
187 def set_tool_timeout(self, tool_name: str, timeout: float) -> None:
188 """为特定工具设置超时时间。"""
189 self._tool_timeouts[tool_name] = timeout
191 def get_tool_timeout(self, tool_name: str) -> float:
192 """获取工具的超时时间。"""
193 return self._tool_timeouts.get(tool_name, self.default_timeout)
195 def _get_or_create_metrics(self, tool_name: str) -> ExecutionMetrics:
196 """获取或创建性能指标。"""
197 if tool_name not in self._metrics:
198 self._metrics[tool_name] = ExecutionMetrics(tool_name=tool_name)
199 return self._metrics[tool_name]
201 def _get_or_create_circuit_breaker(self, tool_name: str) -> CircuitBreaker:
202 """获取或创建熔断器。"""
203 if tool_name not in self._circuit_breakers:
204 self._circuit_breakers[tool_name] = CircuitBreaker()
205 return self._circuit_breakers[tool_name]
207 async def execute(
208 self,
209 tool: BaseTool,
210 arguments: Dict[str, Any],
211 call_id: Optional[str] = None,
212 timeout: Optional[float] = None
213 ) -> ToolResult:
214 """
215 异步执行工具。
217 Args:
218 tool: 要执行的工具
219 arguments: 工具参数
220 call_id: 调用ID(可选)
221 timeout: 超时时间(可选,覆盖默认值)
223 Returns:
224 ToolResult: 工具执行结果
225 """
226 if call_id is None:
227 call_id = f"call_{int(time.time() * 1000)}"
229 tool_name = tool.name or tool.__class__.__name__
231 # 检查熔断器
232 if self.enable_circuit_breaker:
233 circuit_breaker = self._get_or_create_circuit_breaker(tool_name)
234 if not circuit_breaker.can_execute():
235 return ToolResult.fail(
236 call_id=call_id,
237 error=f"Circuit breaker is OPEN for tool '{tool_name}'. "
238 f"Try again in {circuit_breaker.time_until_reset:.1f}s."
239 )
241 # 获取超时时间
242 exec_timeout = timeout or self.get_tool_timeout(tool_name)
244 # 获取性能指标
245 metrics = self._get_or_create_metrics(tool_name)
247 # 创建任务
248 task = asyncio.create_task(
249 self._execute_with_semaphore(
250 tool=tool,
251 arguments=arguments,
252 call_id=call_id,
253 timeout=exec_timeout,
254 tool_name=tool_name,
255 metrics=metrics
256 )
257 )
259 self._active_tasks.add(task)
260 task.add_done_callback(self._active_tasks.discard)
262 try:
263 return await task
264 except asyncio.CancelledError:
265 return ToolResult.fail(call_id=call_id, error="Execution cancelled")
267 async def _execute_with_semaphore(
268 self,
269 tool: BaseTool,
270 arguments: Dict[str, Any],
271 call_id: str,
272 timeout: float,
273 tool_name: str,
274 metrics: ExecutionMetrics
275 ) -> ToolResult:
276 """使用信号量控制并发执行。"""
277 start_time = time.time()
279 async with self._semaphore:
280 try:
281 # 执行工具(带超时)
282 result = await asyncio.wait_for(
283 tool.execute(arguments),
284 timeout=timeout
285 )
287 execution_time = time.time() - start_time
289 # 检查结果是否失败
290 if result.error is not None:
291 # 工具执行失败
292 metrics.record_failure(execution_time, result.error)
293 if self.enable_circuit_breaker:
294 circuit_breaker = self._get_or_create_circuit_breaker(tool_name)
295 circuit_breaker.record_failure()
296 else:
297 # 工具执行成功
298 metrics.record_success(execution_time)
299 if self.enable_circuit_breaker:
300 circuit_breaker = self._get_or_create_circuit_breaker(tool_name)
301 circuit_breaker.record_success()
303 return result
305 except asyncio.TimeoutError:
306 execution_time = time.time() - start_time
307 metrics.record_timeout(execution_time)
309 if self.enable_circuit_breaker:
310 circuit_breaker = self._get_or_create_circuit_breaker(tool_name)
311 circuit_breaker.record_failure()
313 return ToolResult.fail(
314 call_id=call_id,
315 error=f"Tool '{tool_name}' execution timed out after {timeout}s"
316 )
318 except Exception as e:
319 execution_time = time.time() - start_time
320 error_msg = str(e)
321 metrics.record_failure(execution_time, error_msg)
323 if self.enable_circuit_breaker:
324 circuit_breaker = self._get_or_create_circuit_breaker(tool_name)
325 circuit_breaker.record_failure()
327 return ToolResult.fail(call_id=call_id, error=error_msg)
329 async def execute_batch(
330 self,
331 tool_calls: List[Tuple[BaseTool, Dict[str, Any]]],
332 max_batch_size: Optional[int] = None,
333 timeout_per_tool: Optional[float] = None
334 ) -> List[ToolResult]:
335 """
336 批量执行工具。
338 Args:
339 tool_calls: 工具调用列表 [(tool, arguments), ...]
340 max_batch_size: 最大批量大小(None表示无限制)
341 timeout_per_tool: 每个工具的超时时间
343 Returns:
344 List[ToolResult]: 工具执行结果列表
345 """
346 if max_batch_size is not None:
347 # 分批执行
348 results = []
349 for i in range(0, len(tool_calls), max_batch_size):
350 batch = tool_calls[i:i + max_batch_size]
351 batch_results = await asyncio.gather(*[
352 self.execute(tool, args, timeout=timeout_per_tool)
353 for tool, args in batch
354 ])
355 results.extend(batch_results)
356 return results
357 else:
358 # 并发执行所有工具
359 tasks = [
360 self.execute(tool, args, timeout=timeout_per_tool)
361 for tool, args in tool_calls
362 ]
363 return await asyncio.gather(*tasks)
365 def get_metrics(self, tool_name: Optional[str] = None) -> Dict[str, ExecutionMetrics]:
366 """获取性能指标。"""
367 if tool_name:
368 return {tool_name: self._metrics.get(tool_name)}
369 return self._metrics.copy()
371 def get_circuit_breaker_state(self, tool_name: str) -> Optional[CircuitBreakerState]:
372 """获取熔断器状态。"""
373 if tool_name in self._circuit_breakers:
374 return self._circuit_breakers[tool_name].state
375 return None
377 def reset_circuit_breaker(self, tool_name: str) -> bool:
378 """重置指定工具的熔断器。"""
379 if tool_name in self._circuit_breakers:
380 self._circuit_breakers[tool_name] = CircuitBreaker()
381 return True
382 return False
384 def reset_all_circuit_breakers(self) -> None:
385 """重置所有熔断器。"""
386 self._circuit_breakers.clear()
388 async def shutdown(self, timeout: float = 5.0) -> None:
389 """优雅关闭执行器。"""
390 # 取消所有正在执行的任务
391 for task in self._active_tasks.copy():
392 task.cancel()
394 # 等待任务完成或超时
395 if self._active_tasks:
396 try:
397 await asyncio.wait_for(
398 asyncio.gather(*self._active_tasks, return_exceptions=True),
399 timeout=timeout
400 )
401 except asyncio.TimeoutError:
402 pass
404 @property
405 def active_task_count(self) -> int:
406 """当前活跃任务数量。"""
407 return len(self._active_tasks)
409 @property
410 def available_slots(self) -> int:
411 """可用并发槽位数量。"""
412 return self.max_concurrent - self.active_task_count
415class SmartRetryExecutor:
416 """智能重试执行器:根据错误类型自动重试。"""
418 def __init__(
419 self,
420 max_retries: int = 3,
421 retry_delay: float = 1.0,
422 backoff_factor: float = 2.0,
423 retryable_categories: Optional[List[ErrorCategory]] = None
424 ):
425 """
426 初始化智能重试执行器。
428 Args:
429 max_retries: 最大重试次数
430 retry_delay: 初始重试延迟(秒)
431 backoff_factor: 退避因子
432 retryable_categories: 可重试的错误类别
433 """
434 self.max_retries = max_retries
435 self.retry_delay = retry_delay
436 self.backoff_factor = backoff_factor
438 if retryable_categories is None:
439 self.retryable_categories = [
440 ErrorCategory.NETWORK,
441 ErrorCategory.TIMEOUT,
442 ErrorCategory.RATE_LIMIT,
443 ErrorCategory.UNKNOWN
444 ]
445 else:
446 self.retryable_categories = retryable_categories
448 async def execute_with_retry(
449 self,
450 tool: BaseTool,
451 arguments: Dict[str, Any],
452 call_id: Optional[str] = None,
453 base_executor: Optional[AsyncToolExecutor] = None
454 ) -> ToolResult:
455 """
456 带智能重试的工具执行。
458 Args:
459 tool: 要执行的工具
460 arguments: 工具参数
461 call_id: 调用ID
462 base_executor: 基础执行器(可选)
464 Returns:
465 ToolResult: 最终执行结果
466 """
467 if call_id is None:
468 call_id = f"retry_{int(time.time() * 1000)}"
470 if base_executor is None:
471 base_executor = AsyncToolExecutor()
473 last_result = None
474 delay = self.retry_delay
476 for attempt in range(self.max_retries + 1):
477 if attempt > 0:
478 # 等待重试延迟
479 await asyncio.sleep(delay)
480 delay *= self.backoff_factor # 指数退避
482 # 执行工具
483 result = await base_executor.execute(
484 tool=tool,
485 arguments=arguments,
486 call_id=f"{call_id}_attempt{attempt}"
487 )
489 if result.error is None:
490 # 执行成功
491 return result
493 # 检查是否可重试
494 last_result = result
495 error_category = ToolErrorClassifier.classify(result)
497 if error_category not in self.retryable_categories:
498 # 不可重试的错误
499 break
501 if attempt == self.max_retries:
502 # 达到最大重试次数
503 break
505 # 返回最后一次失败的结果
506 return last_result or ToolResult.fail(
507 call_id=call_id,
508 error="Execution failed after retries"
509 )
512# 便捷函数
513async def execute_tool_with_retry(
514 tool: BaseTool,
515 arguments: Dict[str, Any],
516 max_retries: int = 3,
517 call_id: Optional[str] = None
518) -> ToolResult:
519 """
520 带重试的工具执行便捷函数。
522 Args:
523 tool: 要执行的工具
524 arguments: 工具参数
525 max_retries: 最大重试次数
526 call_id: 调用ID
528 Returns:
529 ToolResult: 执行结果
530 """
531 executor = SmartRetryExecutor(max_retries=max_retries)
532 return await executor.execute_with_retry(tool, arguments, call_id)
535async def execute_tools_concurrently(
536 tool_calls: List[Tuple[BaseTool, Dict[str, Any]]],
537 max_concurrent: int = 10,
538 timeout_per_tool: Optional[float] = None
539) -> List[ToolResult]:
540 """
541 并发执行多个工具的便捷函数。
543 Args:
544 tool_calls: 工具调用列表
545 max_concurrent: 最大并发数
546 timeout_per_tool: 每个工具的超时时间
548 Returns:
549 List[ToolResult]: 执行结果列表
550 """
551 executor = AsyncToolExecutor(max_concurrent=max_concurrent)
552 return await executor.execute_batch(
553 tool_calls=tool_calls,
554 timeout_per_tool=timeout_per_tool
555 )