auto-agent-kit 0.1.0__tar.gz → 0.2.0__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.
Files changed (22) hide show
  1. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/PKG-INFO +1 -1
  2. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit/__init__.py +7 -3
  3. auto_agent_kit-0.2.0/auto_agent_kit/core/async_plan.py +177 -0
  4. auto_agent_kit-0.2.0/auto_agent_kit/core/plugin.py +275 -0
  5. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit.egg-info/PKG-INFO +1 -1
  6. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit.egg-info/SOURCES.txt +2 -0
  7. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/pyproject.toml +1 -1
  8. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/tests/test_all.py +98 -0
  9. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/LICENSE +0 -0
  10. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/README.md +0 -0
  11. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit/core/__init__.py +0 -0
  12. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit/core/access_control.py +0 -0
  13. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit/core/dashboard.py +0 -0
  14. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit/core/error_reflection.py +0 -0
  15. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit/core/mcp_server.py +0 -0
  16. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit/core/plan_mode.py +0 -0
  17. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit/core/tool_router.py +0 -0
  18. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit/examples/demo.py +0 -0
  19. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit.egg-info/dependency_links.txt +0 -0
  20. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit.egg-info/requires.txt +0 -0
  21. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit.egg-info/top_level.txt +0 -0
  22. {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: auto-agent-kit
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A practical Python toolkit for building production-grade AI agents with Plan-Execute, Error Reflection, Tool Routing, Dashboard, Access Control, and MCP Server.
5
5
  Author-email: AoLongZhiZun <2480528492@qq.com>
6
6
  License: MIT
@@ -1,18 +1,22 @@
1
1
  """AutoAgentKit — 生产级 AI Agent 工具包"""
2
- __version__ = "0.1.0"
2
+ __version__ = "0.2.0"
3
3
 
4
- from auto_agent_kit.core.plan_mode import PlanMode
4
+ from auto_agent_kit.core.plan_mode import PlanMode, ExecutionPlan, PlanStep, StepStatus
5
5
  from auto_agent_kit.core.error_reflection import ErrorReflection, ErrorCategory, RecoveryStrategy
6
6
  from auto_agent_kit.core.tool_router import ToolRouter, ToolPhase
7
7
  from auto_agent_kit.core.dashboard import Dashboard
8
8
  from auto_agent_kit.core.access_control import AccessControl, PermissionLevel
9
9
  from auto_agent_kit.core.mcp_server import MCPServer
10
+ from auto_agent_kit.core.plugin import Plugin, PluginManager, LoggingPlugin, MetricsPlugin
11
+ from auto_agent_kit.core.async_plan import AsyncPlanMode, AsyncStepResult, run_plan
10
12
 
11
13
  __all__ = [
12
- "PlanMode",
14
+ "PlanMode", "ExecutionPlan", "PlanStep", "StepStatus",
13
15
  "ErrorReflection", "ErrorCategory", "RecoveryStrategy",
14
16
  "ToolRouter", "ToolPhase",
15
17
  "Dashboard",
16
18
  "AccessControl", "PermissionLevel",
17
19
  "MCPServer",
20
+ "Plugin", "PluginManager", "LoggingPlugin", "MetricsPlugin",
21
+ "AsyncPlanMode", "AsyncStepResult", "run_plan",
18
22
  ]
@@ -0,0 +1,177 @@
1
+ """AsyncPlan — 异步计划执行模式
2
+
3
+ 支持异步步骤执行、并发步骤、超时控制。
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import asyncio
9
+ import time
10
+ from dataclasses import dataclass, field
11
+ from typing import Any, Callable, Optional
12
+
13
+ from auto_agent_kit.core.plan_mode import ExecutionPlan, PlanStep, PlanMode, StepStatus
14
+
15
+
16
+ @dataclass
17
+ class AsyncStepResult:
18
+ """异步步骤执行结果"""
19
+ step_id: str
20
+ status: str
21
+ result: Any = None
22
+ error: Optional[str] = None
23
+ duration_ms: float = 0.0
24
+
25
+
26
+ class AsyncPlanMode(PlanMode):
27
+ """异步计划执行模式 — 支持异步步骤和并发执行"""
28
+
29
+ def __init__(self, max_retries: int = 2, concurrency_limit: int = 3):
30
+ super().__init__(max_retries)
31
+ self.concurrency_limit = concurrency_limit
32
+ self._semaphore = asyncio.Semaphore(concurrency_limit)
33
+
34
+ async def execute_step_async(
35
+ self,
36
+ step: PlanStep,
37
+ executor: Callable[[str], Any],
38
+ ) -> AsyncStepResult:
39
+ """异步执行单个步骤"""
40
+ start = time.time()
41
+ self.start_step(step.id)
42
+
43
+ for attempt in range(self.max_retries + 1):
44
+ try:
45
+ async with self._semaphore:
46
+ if asyncio.iscoroutinefunction(executor):
47
+ result = await executor(step.description)
48
+ else:
49
+ result = executor(step.description)
50
+ self.complete_step(step.id, result)
51
+ duration_ms = (time.time() - start) * 1000
52
+ return AsyncStepResult(
53
+ step_id=step.id,
54
+ status="ok",
55
+ result=result,
56
+ duration_ms=duration_ms,
57
+ )
58
+ except Exception as e:
59
+ if attempt < self.max_retries:
60
+ await asyncio.sleep(2 ** attempt) # 指数退避
61
+ continue
62
+ self.fail_step(step.id, str(e))
63
+ duration_ms = (time.time() - start) * 1000
64
+ return AsyncStepResult(
65
+ step_id=step.id,
66
+ status="failed",
67
+ error=str(e),
68
+ duration_ms=duration_ms,
69
+ )
70
+
71
+ async def execute_sequentially_async(
72
+ self,
73
+ executor: Callable[[str], Any],
74
+ plan: Optional[ExecutionPlan] = None,
75
+ ) -> list[AsyncStepResult]:
76
+ """异步顺序执行所有步骤"""
77
+ plan = plan or self.current_plan
78
+ if not plan:
79
+ return []
80
+
81
+ results = []
82
+ for step in plan.steps:
83
+ result = await self.execute_step_async(step, executor)
84
+ results.append(result)
85
+ return results
86
+
87
+ async def execute_concurrently_async(
88
+ self,
89
+ executor: Callable[[str], Any],
90
+ plan: Optional[ExecutionPlan] = None,
91
+ ) -> list[AsyncStepResult]:
92
+ """并发执行无依赖的步骤
93
+
94
+ 按依赖关系分批执行:同一批次的步骤可以并发运行。
95
+ """
96
+ plan = plan or self.current_plan
97
+ if not plan:
98
+ return []
99
+
100
+ results: list[AsyncStepResult] = []
101
+ completed_ids: set[str] = set()
102
+ pending = list(plan.steps)
103
+
104
+ while pending:
105
+ # 找出当前批次可执行的步骤(依赖已完成的)
106
+ batch = []
107
+ remaining = []
108
+ for step in pending:
109
+ deps = set(step.dependencies)
110
+ if deps.issubset(completed_ids):
111
+ batch.append(step)
112
+ else:
113
+ remaining.append(step)
114
+
115
+ if not batch:
116
+ # 死锁检测:有步骤但都不满足依赖
117
+ for step in remaining:
118
+ results.append(AsyncStepResult(
119
+ step_id=step.id,
120
+ status="failed",
121
+ error=f"Deadlock: dependencies {step.dependencies} never completed",
122
+ ))
123
+ break
124
+
125
+ # 并发执行批次
126
+ batch_results = await asyncio.gather(*[
127
+ self.execute_step_async(step, executor) for step in batch
128
+ ])
129
+
130
+ for r in batch_results:
131
+ results.append(r)
132
+ if r.status == "ok":
133
+ completed_ids.add(r.step_id)
134
+
135
+ pending = remaining
136
+
137
+ return results
138
+
139
+ async def execute_with_timeout(
140
+ self,
141
+ executor: Callable[[str], Any],
142
+ step_description: str,
143
+ timeout_seconds: float = 30.0,
144
+ ) -> AsyncStepResult:
145
+ """带超时控制的步骤执行"""
146
+ step = PlanStep(id="timeout_step", description=step_description)
147
+ self.current_plan = ExecutionPlan(goal=step_description)
148
+ self.current_plan.steps = [step]
149
+
150
+ try:
151
+ result = await asyncio.wait_for(
152
+ self.execute_step_async(step, executor),
153
+ timeout=timeout_seconds,
154
+ )
155
+ return result
156
+ except asyncio.TimeoutError:
157
+ return AsyncStepResult(
158
+ step_id=step.id,
159
+ status="timeout",
160
+ error=f"Step timed out after {timeout_seconds}s",
161
+ duration_ms=timeout_seconds * 1000,
162
+ )
163
+
164
+
165
+ # ── 辅助函数 ──
166
+
167
+ async def run_plan(
168
+ plan_mode: AsyncPlanMode,
169
+ steps: list[str],
170
+ executor: Callable[[str], Any],
171
+ concurrent: bool = False,
172
+ ) -> list[AsyncStepResult]:
173
+ """便捷函数:创建计划并执行"""
174
+ plan = plan_mode.create_plan("async_plan", steps)
175
+ if concurrent:
176
+ return await plan_mode.execute_concurrently_async(executor, plan)
177
+ return await plan_mode.execute_sequentially_async(executor, plan)
@@ -0,0 +1,275 @@
1
+ """Plugin — 插件系统
2
+
3
+ 支持插件注册、生命周期钩子、事件系统。
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import time
9
+ from dataclasses import dataclass, field
10
+ from typing import Any, Callable, Optional
11
+
12
+
13
+ class Plugin:
14
+ """插件基类"""
15
+
16
+ name: str = ""
17
+ version: str = "0.1.0"
18
+ description: str = ""
19
+
20
+ def on_register(self, kit: "PluginManager"):
21
+ """注册时调用"""
22
+ pass
23
+
24
+ def on_unregister(self):
25
+ """注销时调用"""
26
+ pass
27
+
28
+ def on_before_step(self, step: dict) -> dict:
29
+ """步骤执行前调用,可修改步骤"""
30
+ return step
31
+
32
+ def on_after_step(self, step: dict, result: Any) -> None:
33
+ """步骤执行后调用"""
34
+ pass
35
+
36
+ def on_error(self, error: Exception, context: dict) -> Optional[dict]:
37
+ """错误发生时调用,返回恢复建议或 None"""
38
+ return None
39
+
40
+ def on_metric(self, name: str, value: float) -> None:
41
+ """指标记录时调用"""
42
+ pass
43
+
44
+
45
+ @dataclass
46
+ class Hook:
47
+ """钩子定义"""
48
+ name: str
49
+ handler: Callable
50
+ priority: int = 0 # 数字越小优先级越高
51
+ plugin_name: str = ""
52
+
53
+
54
+ class PluginManager:
55
+ """插件管理器"""
56
+
57
+ def __init__(self):
58
+ self._plugins: dict[str, Plugin] = {}
59
+ self._hooks: dict[str, list[Hook]] = {}
60
+ self._events: list[dict] = []
61
+ self._max_events: int = 200
62
+
63
+ # ── 插件生命周期 ──
64
+
65
+ def register(self, plugin: Plugin) -> bool:
66
+ """注册插件"""
67
+ if plugin.name in self._plugins:
68
+ return False
69
+ self._plugins[plugin.name] = plugin
70
+ plugin.on_register(self)
71
+ self._record_event("plugin_register", {"name": plugin.name, "version": plugin.version})
72
+ return True
73
+
74
+ def unregister(self, name: str) -> bool:
75
+ """注销插件"""
76
+ plugin = self._plugins.pop(name, None)
77
+ if plugin is None:
78
+ return False
79
+ plugin.on_unregister()
80
+ # 清理该插件的钩子
81
+ for hook_list in self._hooks.values():
82
+ hook_list[:] = [h for h in hook_list if h.plugin_name != name]
83
+ self._record_event("plugin_unregister", {"name": name})
84
+ return True
85
+
86
+ def get_plugin(self, name: str) -> Optional[Plugin]:
87
+ """获取插件"""
88
+ return self._plugins.get(name)
89
+
90
+ def list_plugins(self) -> list[dict]:
91
+ """列出所有插件"""
92
+ return [
93
+ {"name": p.name, "version": p.version, "description": p.description}
94
+ for p in self._plugins.values()
95
+ ]
96
+
97
+ def is_registered(self, name: str) -> bool:
98
+ """检查插件是否已注册"""
99
+ return name in self._plugins
100
+
101
+ # ── 钩子系统 ──
102
+
103
+ def add_hook(self, hook_name: str, handler: Callable, plugin_name: str = "", priority: int = 0):
104
+ """添加钩子"""
105
+ if hook_name not in self._hooks:
106
+ self._hooks[hook_name] = []
107
+ self._hooks[hook_name].append(Hook(
108
+ name=hook_name,
109
+ handler=handler,
110
+ priority=priority,
111
+ plugin_name=plugin_name,
112
+ ))
113
+ # 按优先级排序
114
+ self._hooks[hook_name].sort(key=lambda h: h.priority)
115
+
116
+ def remove_hook(self, hook_name: str, handler: Callable) -> bool:
117
+ """移除钩子"""
118
+ hook_list = self._hooks.get(hook_name, [])
119
+ before = len(hook_list)
120
+ hook_list[:] = [h for h in hook_list if h.handler != handler]
121
+ return len(hook_list) < before
122
+
123
+ def emit(self, hook_name: str, **kwargs) -> list[Any]:
124
+ """触发钩子,返回所有处理结果"""
125
+ results = []
126
+ for hook in self._hooks.get(hook_name, []):
127
+ try:
128
+ result = hook.handler(**kwargs)
129
+ results.append(result)
130
+ except Exception as e:
131
+ self._record_event("hook_error", {
132
+ "hook": hook_name,
133
+ "plugin": hook.plugin_name,
134
+ "error": str(e),
135
+ })
136
+ return results
137
+
138
+ def has_hooks(self, hook_name: str) -> bool:
139
+ """检查是否有钩子"""
140
+ return bool(self._hooks.get(hook_name))
141
+
142
+ # ── 内置钩子快捷方式 ──
143
+
144
+ def on_before_step(self, step: dict) -> dict:
145
+ """触发 before_step 钩子"""
146
+ if self.has_hooks("before_step"):
147
+ results = self.emit("before_step", step=step)
148
+ for r in results:
149
+ if isinstance(r, dict):
150
+ step = r
151
+ return step
152
+
153
+ def on_after_step(self, step: dict, result: Any):
154
+ """触发 after_step 钩子"""
155
+ if self.has_hooks("after_step"):
156
+ self.emit("after_step", step=step, result=result)
157
+
158
+ def on_error(self, error: Exception, context: dict) -> Optional[dict]:
159
+ """触发 error 钩子,返回第一个非 None 的恢复建议"""
160
+ if self.has_hooks("error"):
161
+ results = self.emit("error", error=error, context=context)
162
+ for r in results:
163
+ if r is not None:
164
+ return r
165
+ return None
166
+
167
+ # ── 事件记录 ──
168
+
169
+ def _record_event(self, event_type: str, data: dict):
170
+ self._events.append({
171
+ "timestamp": time.time(),
172
+ "type": event_type,
173
+ "data": data,
174
+ })
175
+ if len(self._events) > self._max_events:
176
+ self._events = self._events[-self._max_events:]
177
+
178
+ def get_events(self, limit: int = 20) -> list[dict]:
179
+ """获取最近事件"""
180
+ return self._events[-limit:]
181
+
182
+ def get_stats(self) -> dict:
183
+ """获取插件统计"""
184
+ return {
185
+ "total_plugins": len(self._plugins),
186
+ "total_hooks": sum(len(h) for h in self._hooks.values()),
187
+ "hook_names": list(self._hooks.keys()),
188
+ "plugins": self.list_plugins(),
189
+ }
190
+
191
+
192
+ # ── 内置插件 ──
193
+
194
+ class LoggingPlugin(Plugin):
195
+ """日志插件 — 记录所有步骤和错误"""
196
+
197
+ name = "logging"
198
+ version = "1.0.0"
199
+ description = "记录所有步骤执行和错误信息"
200
+
201
+ def __init__(self):
202
+ self.logs: list[dict] = []
203
+ self._max_logs = 500
204
+
205
+ def on_before_step(self, step: dict) -> dict:
206
+ self.logs.append({
207
+ "time": time.time(),
208
+ "type": "step_start",
209
+ "step_id": step.get("id", ""),
210
+ "description": step.get("description", ""),
211
+ })
212
+ if len(self.logs) > self._max_logs:
213
+ self.logs = self.logs[-self._max_logs:]
214
+ return step
215
+
216
+ def on_after_step(self, step: dict, result: Any):
217
+ self.logs.append({
218
+ "time": time.time(),
219
+ "type": "step_end",
220
+ "step_id": step.get("id", ""),
221
+ "result": str(result)[:200],
222
+ })
223
+
224
+ def on_error(self, error: Exception, context: dict) -> None:
225
+ self.logs.append({
226
+ "time": time.time(),
227
+ "type": "error",
228
+ "error": str(error),
229
+ "context": context,
230
+ })
231
+
232
+ def get_recent_logs(self, limit: int = 10) -> list[dict]:
233
+ return self.logs[-limit:]
234
+
235
+
236
+ class MetricsPlugin(Plugin):
237
+ """指标插件 — 自动收集 Dashboard 指标"""
238
+
239
+ name = "metrics"
240
+ version = "1.0.0"
241
+ description = "自动收集步骤耗时和错误率指标"
242
+
243
+ def __init__(self):
244
+ self.step_durations: list[float] = []
245
+ self.error_count = 0
246
+ self.step_count = 0
247
+
248
+ def on_after_step(self, step: dict, result: Any):
249
+ self.step_count += 1
250
+ if "duration" in step:
251
+ self.step_durations.append(step["duration"])
252
+
253
+ def on_error(self, error: Exception, context: dict) -> None:
254
+ self.error_count += 1
255
+
256
+ @property
257
+ def avg_duration(self) -> float:
258
+ if not self.step_durations:
259
+ return 0.0
260
+ return sum(self.step_durations) / len(self.step_durations)
261
+
262
+ @property
263
+ def error_rate(self) -> float:
264
+ if self.step_count == 0:
265
+ return 0.0
266
+ return self.error_count / self.step_count
267
+
268
+ def get_report(self) -> dict:
269
+ return {
270
+ "total_steps": self.step_count,
271
+ "total_errors": self.error_count,
272
+ "error_rate": self.error_rate,
273
+ "avg_duration_ms": self.avg_duration * 1000,
274
+ "total_duration_ms": sum(self.step_durations) * 1000,
275
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: auto-agent-kit
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A practical Python toolkit for building production-grade AI agents with Plan-Execute, Error Reflection, Tool Routing, Dashboard, Access Control, and MCP Server.
5
5
  Author-email: AoLongZhiZun <2480528492@qq.com>
6
6
  License: MIT
@@ -9,10 +9,12 @@ auto_agent_kit.egg-info/requires.txt
9
9
  auto_agent_kit.egg-info/top_level.txt
10
10
  auto_agent_kit/core/__init__.py
11
11
  auto_agent_kit/core/access_control.py
12
+ auto_agent_kit/core/async_plan.py
12
13
  auto_agent_kit/core/dashboard.py
13
14
  auto_agent_kit/core/error_reflection.py
14
15
  auto_agent_kit/core/mcp_server.py
15
16
  auto_agent_kit/core/plan_mode.py
17
+ auto_agent_kit/core/plugin.py
16
18
  auto_agent_kit/core/tool_router.py
17
19
  auto_agent_kit/examples/demo.py
18
20
  tests/test_all.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "auto-agent-kit"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "A practical Python toolkit for building production-grade AI agents with Plan-Execute, Error Reflection, Tool Routing, Dashboard, Access Control, and MCP Server."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -7,9 +7,14 @@ from auto_agent_kit import (
7
7
  PlanMode, ErrorReflection, ErrorCategory, RecoveryStrategy,
8
8
  ToolRouter, ToolPhase, Dashboard, AccessControl, PermissionLevel,
9
9
  MCPServer,
10
+ Plugin, PluginManager, LoggingPlugin, MetricsPlugin,
11
+ AsyncPlanMode, AsyncStepResult, run_plan,
10
12
  )
11
13
 
12
14
 
15
+ import asyncio
16
+
17
+
13
18
  def test_plan_mode():
14
19
  p = PlanMode()
15
20
  plan = p.create_plan("test", ["step1", "step2"])
@@ -151,6 +156,96 @@ def test_mcp_server():
151
156
  print(" ✅ MCPServer")
152
157
 
153
158
 
159
+ def test_plugin_system():
160
+ pm = PluginManager()
161
+
162
+ # 注册插件
163
+ log_plugin = LoggingPlugin()
164
+ assert pm.register(log_plugin) is True
165
+ assert pm.register(log_plugin) is False # 重复注册
166
+ assert pm.is_registered("logging") is True
167
+
168
+ # 插件列表
169
+ plugins = pm.list_plugins()
170
+ assert len(plugins) == 1
171
+ assert plugins[0]["name"] == "logging"
172
+
173
+ # 钩子系统
174
+ def custom_hook(step: dict):
175
+ step["modified"] = True
176
+ return step
177
+
178
+ pm.add_hook("before_step", custom_hook, "test", priority=10)
179
+ assert pm.has_hooks("before_step") is True
180
+
181
+ # 触发钩子
182
+ result = pm.on_before_step({"id": "1", "description": "test"})
183
+ assert result["modified"] is True
184
+
185
+ # 错误钩子
186
+ pm.on_error(Exception("test error"), {"step": "1"})
187
+
188
+ # 移除钩子
189
+ assert pm.remove_hook("before_step", custom_hook) is True
190
+
191
+ # 统计
192
+ stats = pm.get_stats()
193
+ assert stats["total_plugins"] == 1
194
+ assert stats["total_hooks"] >= 0
195
+
196
+ # 注销
197
+ assert pm.unregister("logging") is True
198
+ assert pm.is_registered("logging") is False
199
+ print(" ✅ PluginSystem")
200
+
201
+
202
+ def test_metrics_plugin():
203
+ mp = MetricsPlugin()
204
+ mp.on_after_step({"id": "1", "duration": 0.1}, "ok")
205
+ mp.on_after_step({"id": "2", "duration": 0.2}, "ok")
206
+ mp.on_error(Exception("fail"), {})
207
+
208
+ report = mp.get_report()
209
+ assert report["total_steps"] == 2
210
+ assert report["total_errors"] == 1
211
+ assert report["error_rate"] == 0.5
212
+ assert abs(report["avg_duration_ms"] - 150.0) < 0.001
213
+ print(" ✅ MetricsPlugin")
214
+
215
+
216
+ def test_async_plan():
217
+ async def run():
218
+ ap = AsyncPlanMode(max_retries=1, concurrency_limit=2)
219
+
220
+ def executor(desc: str) -> str:
221
+ return f"done: {desc}"
222
+
223
+ results = await run_plan(ap, ["step1", "step2", "step3"], executor)
224
+ assert len(results) == 3
225
+ assert all(r.status == "ok" for r in results)
226
+ assert results[0].result == "done: step1"
227
+
228
+ # 并发执行
229
+ ap2 = AsyncPlanMode()
230
+ plan = ap2.create_plan("concurrent", ["a", "b", "c"])
231
+ concurrent_results = await ap2.execute_concurrently_async(executor, plan)
232
+ assert len(concurrent_results) == 3
233
+ assert all(r.status == "ok" for r in concurrent_results)
234
+
235
+ # 超时测试
236
+ async def slow_executor(desc: str) -> str:
237
+ await asyncio.sleep(10)
238
+ return "slow"
239
+
240
+ timeout_result = await ap2.execute_with_timeout(slow_executor, "slow", 0.1)
241
+ assert timeout_result.status == "timeout"
242
+
243
+ return True
244
+
245
+ assert asyncio.run(run()) is True
246
+ print(" ✅ AsyncPlan")
247
+
248
+
154
249
  if __name__ == "__main__":
155
250
  print("AutoAgentKit 测试\n")
156
251
  test_plan_mode()
@@ -159,4 +254,7 @@ if __name__ == "__main__":
159
254
  test_dashboard()
160
255
  test_access_control()
161
256
  test_mcp_server()
257
+ test_plugin_system()
258
+ test_metrics_plugin()
259
+ test_async_plan()
162
260
  print("\n全部测试通过 ✅")
File without changes
File without changes
File without changes