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.
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/PKG-INFO +1 -1
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit/__init__.py +7 -3
- auto_agent_kit-0.2.0/auto_agent_kit/core/async_plan.py +177 -0
- auto_agent_kit-0.2.0/auto_agent_kit/core/plugin.py +275 -0
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit.egg-info/PKG-INFO +1 -1
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit.egg-info/SOURCES.txt +2 -0
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/pyproject.toml +1 -1
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/tests/test_all.py +98 -0
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/LICENSE +0 -0
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/README.md +0 -0
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit/core/__init__.py +0 -0
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit/core/access_control.py +0 -0
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit/core/dashboard.py +0 -0
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit/core/error_reflection.py +0 -0
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit/core/mcp_server.py +0 -0
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit/core/plan_mode.py +0 -0
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit/core/tool_router.py +0 -0
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit/examples/demo.py +0 -0
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit.egg-info/dependency_links.txt +0 -0
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit.egg-info/requires.txt +0 -0
- {auto_agent_kit-0.1.0 → auto_agent_kit-0.2.0}/auto_agent_kit.egg-info/top_level.txt +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|