wagent-framework 1.4.4__tar.gz → 1.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/PKG-INFO +16 -3
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/README.md +15 -2
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/setup.py +1 -1
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/__init__.py +10 -3
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/__main__.py +1 -1
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/aop/aspects.py +161 -161
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/cli.py +108 -108
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/container/bean_factory.py +7 -2
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/core/agent.py +7 -7
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/core/decorators.py +138 -138
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/core/doctor.py +3 -1
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/core/event_bus.py +6 -3
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/exceptions/framework_errors.py +8 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/lifecycle/manager.py +8 -8
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/observability/logging.py +74 -74
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/observability/metrics.py +98 -98
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/observability/tracing.py +58 -58
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/scanner/parallel_scanner.py +21 -18
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/skills/skill.py +188 -187
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/tools/langchain_adapter.py +12 -2
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/wagent_framework.egg-info/PKG-INFO +16 -3
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/LICENSE +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/setup.cfg +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/tests/test.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/tests/test_aop.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/tests/test_config.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/tests/test_container.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/tests/test_distributed_lock.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/tests/test_event_bus.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/tests/test_lifecycle.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/tests/test_sandbox.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/tests/test_scanner.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/aop/__init__.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/aop/joinpoint.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/aop/pointcut.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/aop/proxy_factory.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/config/__init__.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/config/dynamic_config.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/container/__init__.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/container/reflection_cache.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/core/__init__.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/deployment/__init__.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/deployment/fastapi_depends.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/distributed/__init__.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/distributed/lock.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/distributed/lock_pool.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/exceptions/__init__.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/lifecycle/__init__.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/lifecycle/graceful_shutdown.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/lifecycle/order.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/migration/__init__.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/migration/migrate_previous.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/observability/__init__.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/observability/health.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/resilience/__init__.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/resilience/bulkhead.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/resilience/timeout.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/scanner/__init__.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/scanner/cache.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/security/__init__.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/security/mcp_auth.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/skills/__init__.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/skills/sandbox/__init__.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/skills/sandbox/nsjail_sandbox.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/skills/sandbox/wasm_sandbox.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/skills/signature.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/testing/__init__.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/testing/mock_utils.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/w_agent/tools/__init__.py +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/wagent_framework.egg-info/SOURCES.txt +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/wagent_framework.egg-info/dependency_links.txt +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/wagent_framework.egg-info/entry_points.txt +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/wagent_framework.egg-info/requires.txt +0 -0
- {wagent_framework-1.4.4 → wagent_framework-1.5.2}/wagent_framework.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wagent-framework
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.2
|
|
4
4
|
Summary: Python enterprise agent framework with AOP, IOC, and sandbox security
|
|
5
5
|
Home-page: https://github.com/LuckyStar2456/W-Agent-FrameWork
|
|
6
6
|
Author: LuckyStar2456
|
|
@@ -58,7 +58,7 @@ Dynamic: requires-dist
|
|
|
58
58
|
Dynamic: requires-python
|
|
59
59
|
Dynamic: summary
|
|
60
60
|
|
|
61
|
-
# W-Agent v1.
|
|
61
|
+
# W-Agent v1.5.0
|
|
62
62
|
|
|
63
63
|
[English](./README_EN.md) | 简体中文
|
|
64
64
|
|
|
@@ -670,12 +670,25 @@ export W_AGENT_RESILIENCE_RETRY_MAX_ATTEMPTS=5
|
|
|
670
670
|
## 📦 PyPI 包
|
|
671
671
|
|
|
672
672
|
- **包名**: `wagent-framework`
|
|
673
|
-
- **版本**: 1.
|
|
673
|
+
- **版本**: 1.5.1
|
|
674
674
|
- **安装**: `pip install wagent-framework`
|
|
675
675
|
- **PyPI 地址**: [https://pypi.org/project/wagent-framework/](https://pypi.org/project/wagent-framework/)
|
|
676
676
|
|
|
677
677
|
## 📝 更新日志
|
|
678
678
|
|
|
679
|
+
### v1.5.1 (2026-04-27)
|
|
680
|
+
- **版本更新**: 版本号更新至 1.5.1
|
|
681
|
+
- **文档更新**: 完善了更新日志内容
|
|
682
|
+
- **Bug 修复**: 修复了 LangChain 适配器在未安装 LangChain 时的导入错误
|
|
683
|
+
- **Bug 修复**: 修复了生命周期方法绑定问题
|
|
684
|
+
|
|
685
|
+
### v1.5.0 (2026-04-27)
|
|
686
|
+
- **版本更新**: 版本号更新至 1.5.0
|
|
687
|
+
- **Bug 修复**: 修复了 LangChain 适配器在未安装 LangChain 时的导入错误
|
|
688
|
+
- **Bug 修复**: 修复了生命周期方法绑定问题
|
|
689
|
+
- **文档更新**: 统一更新所有文档的导入路径
|
|
690
|
+
- **文档修复**: 修复了 chat-agent 文档中的 Python 版本要求
|
|
691
|
+
|
|
679
692
|
### v1.4.4 (2026-04-26)
|
|
680
693
|
- **框架优化**: 完善了核心功能的稳定性和性能
|
|
681
694
|
- **代码质量**: 提升了代码的可维护性和可读性
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# W-Agent v1.
|
|
1
|
+
# W-Agent v1.5.0
|
|
2
2
|
|
|
3
3
|
[English](./README_EN.md) | 简体中文
|
|
4
4
|
|
|
@@ -610,12 +610,25 @@ export W_AGENT_RESILIENCE_RETRY_MAX_ATTEMPTS=5
|
|
|
610
610
|
## 📦 PyPI 包
|
|
611
611
|
|
|
612
612
|
- **包名**: `wagent-framework`
|
|
613
|
-
- **版本**: 1.
|
|
613
|
+
- **版本**: 1.5.1
|
|
614
614
|
- **安装**: `pip install wagent-framework`
|
|
615
615
|
- **PyPI 地址**: [https://pypi.org/project/wagent-framework/](https://pypi.org/project/wagent-framework/)
|
|
616
616
|
|
|
617
617
|
## 📝 更新日志
|
|
618
618
|
|
|
619
|
+
### v1.5.1 (2026-04-27)
|
|
620
|
+
- **版本更新**: 版本号更新至 1.5.1
|
|
621
|
+
- **文档更新**: 完善了更新日志内容
|
|
622
|
+
- **Bug 修复**: 修复了 LangChain 适配器在未安装 LangChain 时的导入错误
|
|
623
|
+
- **Bug 修复**: 修复了生命周期方法绑定问题
|
|
624
|
+
|
|
625
|
+
### v1.5.0 (2026-04-27)
|
|
626
|
+
- **版本更新**: 版本号更新至 1.5.0
|
|
627
|
+
- **Bug 修复**: 修复了 LangChain 适配器在未安装 LangChain 时的导入错误
|
|
628
|
+
- **Bug 修复**: 修复了生命周期方法绑定问题
|
|
629
|
+
- **文档更新**: 统一更新所有文档的导入路径
|
|
630
|
+
- **文档修复**: 修复了 chat-agent 文档中的 Python 版本要求
|
|
631
|
+
|
|
619
632
|
### v1.4.4 (2026-04-26)
|
|
620
633
|
- **框架优化**: 完善了核心功能的稳定性和性能
|
|
621
634
|
- **代码质量**: 提升了代码的可维护性和可读性
|
|
@@ -8,7 +8,7 @@ with open(os.path.join(here, 'README.md'), 'r', encoding='utf-8') as f:
|
|
|
8
8
|
|
|
9
9
|
setup(
|
|
10
10
|
name="wagent-framework",
|
|
11
|
-
version="1.
|
|
11
|
+
version="1.5.2",
|
|
12
12
|
description="Python enterprise agent framework with AOP, IOC, and sandbox security",
|
|
13
13
|
long_description=long_description,
|
|
14
14
|
long_description_content_type="text/markdown",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""W-Agent 框架包"""
|
|
2
2
|
|
|
3
|
-
__version__ = "1.
|
|
3
|
+
__version__ = "1.5.1"
|
|
4
4
|
__author__ = "LuckyStar2456"
|
|
5
5
|
__license__ = "MIT"
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ from w_agent.container.bean_factory import BeanFactory, BeanDefinition, Scope
|
|
|
10
10
|
from w_agent.config.dynamic_config import DynamicConfigManager
|
|
11
11
|
from w_agent.core.decorators import (
|
|
12
12
|
AgentComponent, ServiceComponent, ToolComponent,
|
|
13
|
-
RepositoryComponent, ControllerComponent,
|
|
13
|
+
RepositoryComponent, ControllerComponent, Component,
|
|
14
14
|
PostConstruct, PreDestroy, Autowired, Qualifier,
|
|
15
15
|
Retry, CircuitBreaker
|
|
16
16
|
)
|
|
@@ -31,6 +31,9 @@ from w_agent.skills.sandbox.wasm_sandbox import WasmSkillSandbox
|
|
|
31
31
|
from w_agent.skills.sandbox.nsjail_sandbox import NsJailSkillSandbox
|
|
32
32
|
from w_agent.skills.skill import Skill
|
|
33
33
|
from w_agent.exceptions.framework_errors import BeanNotFoundError, InjectionError
|
|
34
|
+
from w_agent.scanner.parallel_scanner import ParallelASTScanner
|
|
35
|
+
from w_agent.lifecycle.graceful_shutdown import GracefulShutdownManager
|
|
36
|
+
from w_agent.tools.langchain_adapter import LangChainToolAdapter
|
|
34
37
|
|
|
35
38
|
__all__ = [
|
|
36
39
|
"BaseAgent",
|
|
@@ -43,6 +46,7 @@ __all__ = [
|
|
|
43
46
|
"ToolComponent",
|
|
44
47
|
"RepositoryComponent",
|
|
45
48
|
"ControllerComponent",
|
|
49
|
+
"Component",
|
|
46
50
|
"PostConstruct",
|
|
47
51
|
"PreDestroy",
|
|
48
52
|
"Autowired",
|
|
@@ -55,6 +59,7 @@ __all__ = [
|
|
|
55
59
|
"Doctor",
|
|
56
60
|
"LifecycleManager",
|
|
57
61
|
"LifecycleOrder",
|
|
62
|
+
"GracefulShutdownManager",
|
|
58
63
|
"ResilienceManager",
|
|
59
64
|
"AspectJPointcut",
|
|
60
65
|
"BeforeAdvice",
|
|
@@ -74,5 +79,7 @@ __all__ = [
|
|
|
74
79
|
"NsJailSkillSandbox",
|
|
75
80
|
"Skill",
|
|
76
81
|
"BeanNotFoundError",
|
|
77
|
-
"InjectionError"
|
|
82
|
+
"InjectionError",
|
|
83
|
+
"ParallelASTScanner",
|
|
84
|
+
"LangChainToolAdapter"
|
|
78
85
|
]
|
|
@@ -1,162 +1,162 @@
|
|
|
1
|
-
"""AOP切面实现"""
|
|
2
|
-
|
|
3
|
-
from typing import Callable, Any
|
|
4
|
-
import asyncio
|
|
5
|
-
import time
|
|
6
|
-
from w_agent.aop.pointcut import AroundAdvice
|
|
7
|
-
from w_agent.core.event_bus import EventBus, Event
|
|
8
|
-
from w_agent.observability.tracing import global_tracer
|
|
9
|
-
|
|
10
|
-
class RetryAspect:
|
|
11
|
-
"""重试切面"""
|
|
12
|
-
def __init__(self, event_bus: EventBus = None):
|
|
13
|
-
self.event_bus = event_bus
|
|
14
|
-
|
|
15
|
-
def create_advice(self, func: Callable) -> AroundAdvice:
|
|
16
|
-
"""创建重试通知"""
|
|
17
|
-
max_attempts = getattr(func, "__retry_max_attempts__", 3)
|
|
18
|
-
delay = getattr(func, "__retry_delay__", 0.1)
|
|
19
|
-
backoff = getattr(func, "__retry_backoff__", 2.0)
|
|
20
|
-
retry_exceptions = getattr(func, "__retry_exceptions__", (Exception,))
|
|
21
|
-
|
|
22
|
-
async def advice(joinpoint, proceed):
|
|
23
|
-
span = global_tracer.start_span("aop.retry", attributes={"function": f"{func.__module__}.{func.__name__}", "max_attempts": max_attempts})
|
|
24
|
-
attempts = 0
|
|
25
|
-
current_delay = delay
|
|
26
|
-
fallback_method = getattr(func, "__retry_fallback__", None)
|
|
27
|
-
|
|
28
|
-
try:
|
|
29
|
-
while attempts < max_attempts:
|
|
30
|
-
try:
|
|
31
|
-
if self.event_bus:
|
|
32
|
-
await self.event_bus.emit(Event(
|
|
33
|
-
name="retry.attempt",
|
|
34
|
-
payload={"attempt": attempts + 1, "max_attempts": max_attempts}
|
|
35
|
-
))
|
|
36
|
-
|
|
37
|
-
sub_span = global_tracer.start_span("aop.retry.attempt", attributes={"attempt": attempts + 1})
|
|
38
|
-
result = await proceed()
|
|
39
|
-
global_tracer.end_span(sub_span)
|
|
40
|
-
return result
|
|
41
|
-
except retry_exceptions as e:
|
|
42
|
-
attempts += 1
|
|
43
|
-
if attempts >= max_attempts:
|
|
44
|
-
if self.event_bus:
|
|
45
|
-
await self.event_bus.emit(Event(
|
|
46
|
-
name="retry.failed",
|
|
47
|
-
payload={"attempts": attempts, "error": str(e)}
|
|
48
|
-
))
|
|
49
|
-
# 尝试调用fallback方法
|
|
50
|
-
if fallback_method and hasattr(joinpoint.target, fallback_method):
|
|
51
|
-
try:
|
|
52
|
-
fallback = getattr(joinpoint.target, fallback_method)
|
|
53
|
-
return fallback(*joinpoint.args, **joinpoint.kwargs)
|
|
54
|
-
except Exception as fallback_error:
|
|
55
|
-
raise Exception(f"Retry failed after {max_attempts} attempts, and fallback method {fallback_method} also failed: {str(fallback_error)}") from e
|
|
56
|
-
raise Exception(f"Retry failed after {max_attempts} attempts: {str(e)}") from e
|
|
57
|
-
|
|
58
|
-
if self.event_bus:
|
|
59
|
-
await self.event_bus.emit(Event(
|
|
60
|
-
name="retry.retrying",
|
|
61
|
-
payload={"attempt": attempts, "delay": current_delay, "error": str(e)}
|
|
62
|
-
))
|
|
63
|
-
|
|
64
|
-
await asyncio.sleep(current_delay)
|
|
65
|
-
current_delay *= backoff
|
|
66
|
-
finally:
|
|
67
|
-
global_tracer.end_span(span)
|
|
68
|
-
|
|
69
|
-
return AroundAdvice(advice)
|
|
70
|
-
|
|
71
|
-
class CircuitBreakerAspect:
|
|
72
|
-
"""断路器切面"""
|
|
73
|
-
def __init__(self, event_bus: EventBus = None):
|
|
74
|
-
self.event_bus = event_bus
|
|
75
|
-
self._state = {}
|
|
76
|
-
|
|
77
|
-
def create_advice(self, func: Callable) -> AroundAdvice:
|
|
78
|
-
"""创建断路器通知"""
|
|
79
|
-
failure_threshold = getattr(func, "__circuit_breaker_failure_threshold__", 5)
|
|
80
|
-
recovery_timeout = getattr(func, "__circuit_breaker_recovery_timeout__", 30.0)
|
|
81
|
-
fallback_method = getattr(func, "__circuit_breaker_fallback_method__", None)
|
|
82
|
-
|
|
83
|
-
func_key = f"{func.__module__}.{func.__name__}"
|
|
84
|
-
|
|
85
|
-
# 初始化状态
|
|
86
|
-
if func_key not in self._state:
|
|
87
|
-
self._state[func_key] = {
|
|
88
|
-
"state": "CLOSED", # CLOSED, OPEN, HALF_OPEN
|
|
89
|
-
"failure_count": 0,
|
|
90
|
-
"last_failure_time": 0,
|
|
91
|
-
"consecutive_successes": 0
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async def advice(joinpoint, proceed):
|
|
95
|
-
span = global_tracer.start_span("aop.circuit_breaker", attributes={"function": func_key, "state": self._state[func_key]["state"]})
|
|
96
|
-
state = self._state[func_key]
|
|
97
|
-
|
|
98
|
-
try:
|
|
99
|
-
# 检查是否应该从OPEN状态切换到HALF_OPEN
|
|
100
|
-
if state["state"] == "OPEN":
|
|
101
|
-
if time.time() - state["last_failure_time"] > recovery_timeout:
|
|
102
|
-
state["state"] = "HALF_OPEN"
|
|
103
|
-
if self.event_bus:
|
|
104
|
-
await self.event_bus.emit(Event(
|
|
105
|
-
name="circuit_breaker.half_open",
|
|
106
|
-
payload={"function": func_key}
|
|
107
|
-
))
|
|
108
|
-
else:
|
|
109
|
-
# 快速失败
|
|
110
|
-
if self.event_bus:
|
|
111
|
-
await self.event_bus.emit(Event(
|
|
112
|
-
name="circuit_breaker.open",
|
|
113
|
-
payload={"function": func_key}
|
|
114
|
-
))
|
|
115
|
-
if fallback_method and hasattr(joinpoint.target, fallback_method):
|
|
116
|
-
try:
|
|
117
|
-
fallback = getattr(joinpoint.target, fallback_method)
|
|
118
|
-
return fallback(*joinpoint.args, **joinpoint.kwargs)
|
|
119
|
-
except Exception as fallback_error:
|
|
120
|
-
raise Exception(f"Circuit breaker is open, and fallback method {fallback_method} also failed: {str(fallback_error)}")
|
|
121
|
-
raise Exception(f"Circuit breaker is open for {func_key}")
|
|
122
|
-
|
|
123
|
-
try:
|
|
124
|
-
sub_span = global_tracer.start_span("aop.circuit_breaker.execute")
|
|
125
|
-
result = await proceed()
|
|
126
|
-
global_tracer.end_span(sub_span)
|
|
127
|
-
|
|
128
|
-
# 处理成功
|
|
129
|
-
if state["state"] == "HALF_OPEN":
|
|
130
|
-
state["consecutive_successes"] += 1
|
|
131
|
-
if state["consecutive_successes"] >= 3: # 连续成功3次后关闭
|
|
132
|
-
state["state"] = "CLOSED"
|
|
133
|
-
state["failure_count"] = 0
|
|
134
|
-
state["consecutive_successes"] = 0
|
|
135
|
-
if self.event_bus:
|
|
136
|
-
await self.event_bus.emit(Event(
|
|
137
|
-
name="circuit_breaker.closed",
|
|
138
|
-
payload={"function": func_key}
|
|
139
|
-
))
|
|
140
|
-
else:
|
|
141
|
-
state["failure_count"] = 0
|
|
142
|
-
|
|
143
|
-
return result
|
|
144
|
-
except Exception as e:
|
|
145
|
-
# 处理失败
|
|
146
|
-
state["failure_count"] += 1
|
|
147
|
-
state["last_failure_time"] = time.time()
|
|
148
|
-
state["consecutive_successes"] = 0
|
|
149
|
-
|
|
150
|
-
if state["state"] == "CLOSED" and state["failure_count"] >= failure_threshold:
|
|
151
|
-
state["state"] = "OPEN"
|
|
152
|
-
if self.event_bus:
|
|
153
|
-
await self.event_bus.emit(Event(
|
|
154
|
-
name="circuit_breaker.opened",
|
|
155
|
-
payload={"function": func_key, "failure_count": state["failure_count"]}
|
|
156
|
-
))
|
|
157
|
-
|
|
158
|
-
raise
|
|
159
|
-
finally:
|
|
160
|
-
global_tracer.end_span(span)
|
|
161
|
-
|
|
1
|
+
"""AOP切面实现"""
|
|
2
|
+
|
|
3
|
+
from typing import Callable, Any
|
|
4
|
+
import asyncio
|
|
5
|
+
import time
|
|
6
|
+
from w_agent.aop.pointcut import AroundAdvice
|
|
7
|
+
from w_agent.core.event_bus import EventBus, Event
|
|
8
|
+
from w_agent.observability.tracing import global_tracer
|
|
9
|
+
|
|
10
|
+
class RetryAspect:
|
|
11
|
+
"""重试切面"""
|
|
12
|
+
def __init__(self, event_bus: EventBus = None):
|
|
13
|
+
self.event_bus = event_bus
|
|
14
|
+
|
|
15
|
+
def create_advice(self, func: Callable) -> AroundAdvice:
|
|
16
|
+
"""创建重试通知"""
|
|
17
|
+
max_attempts = getattr(func, "__retry_max_attempts__", 3)
|
|
18
|
+
delay = getattr(func, "__retry_delay__", 0.1)
|
|
19
|
+
backoff = getattr(func, "__retry_backoff__", 2.0)
|
|
20
|
+
retry_exceptions = getattr(func, "__retry_exceptions__", (Exception,))
|
|
21
|
+
|
|
22
|
+
async def advice(joinpoint, proceed):
|
|
23
|
+
span = global_tracer.start_span("aop.retry", attributes={"function": f"{func.__module__}.{func.__name__}", "max_attempts": max_attempts})
|
|
24
|
+
attempts = 0
|
|
25
|
+
current_delay = delay
|
|
26
|
+
fallback_method = getattr(func, "__retry_fallback__", None)
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
while attempts < max_attempts:
|
|
30
|
+
try:
|
|
31
|
+
if self.event_bus:
|
|
32
|
+
await self.event_bus.emit(Event(
|
|
33
|
+
name="retry.attempt",
|
|
34
|
+
payload={"attempt": attempts + 1, "max_attempts": max_attempts}
|
|
35
|
+
))
|
|
36
|
+
|
|
37
|
+
sub_span = global_tracer.start_span("aop.retry.attempt", attributes={"attempt": attempts + 1})
|
|
38
|
+
result = await proceed()
|
|
39
|
+
global_tracer.end_span(sub_span)
|
|
40
|
+
return result
|
|
41
|
+
except retry_exceptions as e:
|
|
42
|
+
attempts += 1
|
|
43
|
+
if attempts >= max_attempts:
|
|
44
|
+
if self.event_bus:
|
|
45
|
+
await self.event_bus.emit(Event(
|
|
46
|
+
name="retry.failed",
|
|
47
|
+
payload={"attempts": attempts, "error": str(e)}
|
|
48
|
+
))
|
|
49
|
+
# 尝试调用fallback方法
|
|
50
|
+
if fallback_method and hasattr(joinpoint.target, fallback_method):
|
|
51
|
+
try:
|
|
52
|
+
fallback = getattr(joinpoint.target, fallback_method)
|
|
53
|
+
return fallback(*joinpoint.args, **joinpoint.kwargs)
|
|
54
|
+
except Exception as fallback_error:
|
|
55
|
+
raise Exception(f"Retry failed after {max_attempts} attempts, and fallback method {fallback_method} also failed: {str(fallback_error)}") from e
|
|
56
|
+
raise Exception(f"Retry failed after {max_attempts} attempts: {str(e)}") from e
|
|
57
|
+
|
|
58
|
+
if self.event_bus:
|
|
59
|
+
await self.event_bus.emit(Event(
|
|
60
|
+
name="retry.retrying",
|
|
61
|
+
payload={"attempt": attempts, "delay": current_delay, "error": str(e)}
|
|
62
|
+
))
|
|
63
|
+
|
|
64
|
+
await asyncio.sleep(current_delay)
|
|
65
|
+
current_delay *= backoff
|
|
66
|
+
finally:
|
|
67
|
+
global_tracer.end_span(span)
|
|
68
|
+
|
|
69
|
+
return AroundAdvice(advice)
|
|
70
|
+
|
|
71
|
+
class CircuitBreakerAspect:
|
|
72
|
+
"""断路器切面"""
|
|
73
|
+
def __init__(self, event_bus: EventBus = None):
|
|
74
|
+
self.event_bus = event_bus
|
|
75
|
+
self._state = {}
|
|
76
|
+
|
|
77
|
+
def create_advice(self, func: Callable) -> AroundAdvice:
|
|
78
|
+
"""创建断路器通知"""
|
|
79
|
+
failure_threshold = getattr(func, "__circuit_breaker_failure_threshold__", 5)
|
|
80
|
+
recovery_timeout = getattr(func, "__circuit_breaker_recovery_timeout__", 30.0)
|
|
81
|
+
fallback_method = getattr(func, "__circuit_breaker_fallback_method__", None)
|
|
82
|
+
|
|
83
|
+
func_key = f"{func.__module__}.{func.__name__}"
|
|
84
|
+
|
|
85
|
+
# 初始化状态
|
|
86
|
+
if func_key not in self._state:
|
|
87
|
+
self._state[func_key] = {
|
|
88
|
+
"state": "CLOSED", # CLOSED, OPEN, HALF_OPEN
|
|
89
|
+
"failure_count": 0,
|
|
90
|
+
"last_failure_time": 0,
|
|
91
|
+
"consecutive_successes": 0
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async def advice(joinpoint, proceed):
|
|
95
|
+
span = global_tracer.start_span("aop.circuit_breaker", attributes={"function": func_key, "state": self._state[func_key]["state"]})
|
|
96
|
+
state = self._state[func_key]
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
# 检查是否应该从OPEN状态切换到HALF_OPEN
|
|
100
|
+
if state["state"] == "OPEN":
|
|
101
|
+
if time.time() - state["last_failure_time"] > recovery_timeout:
|
|
102
|
+
state["state"] = "HALF_OPEN"
|
|
103
|
+
if self.event_bus:
|
|
104
|
+
await self.event_bus.emit(Event(
|
|
105
|
+
name="circuit_breaker.half_open",
|
|
106
|
+
payload={"function": func_key}
|
|
107
|
+
))
|
|
108
|
+
else:
|
|
109
|
+
# 快速失败
|
|
110
|
+
if self.event_bus:
|
|
111
|
+
await self.event_bus.emit(Event(
|
|
112
|
+
name="circuit_breaker.open",
|
|
113
|
+
payload={"function": func_key}
|
|
114
|
+
))
|
|
115
|
+
if fallback_method and hasattr(joinpoint.target, fallback_method):
|
|
116
|
+
try:
|
|
117
|
+
fallback = getattr(joinpoint.target, fallback_method)
|
|
118
|
+
return fallback(*joinpoint.args, **joinpoint.kwargs)
|
|
119
|
+
except Exception as fallback_error:
|
|
120
|
+
raise Exception(f"Circuit breaker is open, and fallback method {fallback_method} also failed: {str(fallback_error)}")
|
|
121
|
+
raise Exception(f"Circuit breaker is open for {func_key}")
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
sub_span = global_tracer.start_span("aop.circuit_breaker.execute")
|
|
125
|
+
result = await proceed()
|
|
126
|
+
global_tracer.end_span(sub_span)
|
|
127
|
+
|
|
128
|
+
# 处理成功
|
|
129
|
+
if state["state"] == "HALF_OPEN":
|
|
130
|
+
state["consecutive_successes"] += 1
|
|
131
|
+
if state["consecutive_successes"] >= 3: # 连续成功3次后关闭
|
|
132
|
+
state["state"] = "CLOSED"
|
|
133
|
+
state["failure_count"] = 0
|
|
134
|
+
state["consecutive_successes"] = 0
|
|
135
|
+
if self.event_bus:
|
|
136
|
+
await self.event_bus.emit(Event(
|
|
137
|
+
name="circuit_breaker.closed",
|
|
138
|
+
payload={"function": func_key}
|
|
139
|
+
))
|
|
140
|
+
else:
|
|
141
|
+
state["failure_count"] = 0
|
|
142
|
+
|
|
143
|
+
return result
|
|
144
|
+
except Exception as e:
|
|
145
|
+
# 处理失败
|
|
146
|
+
state["failure_count"] += 1
|
|
147
|
+
state["last_failure_time"] = time.time()
|
|
148
|
+
state["consecutive_successes"] = 0
|
|
149
|
+
|
|
150
|
+
if state["state"] == "CLOSED" and state["failure_count"] >= failure_threshold:
|
|
151
|
+
state["state"] = "OPEN"
|
|
152
|
+
if self.event_bus:
|
|
153
|
+
await self.event_bus.emit(Event(
|
|
154
|
+
name="circuit_breaker.opened",
|
|
155
|
+
payload={"function": func_key, "failure_count": state["failure_count"]}
|
|
156
|
+
))
|
|
157
|
+
|
|
158
|
+
raise
|
|
159
|
+
finally:
|
|
160
|
+
global_tracer.end_span(span)
|
|
161
|
+
|
|
162
162
|
return AroundAdvice(advice)
|