auto-agent-kit 0.1.0__py3-none-any.whl
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/__init__.py +18 -0
- auto_agent_kit/core/__init__.py +1 -0
- auto_agent_kit/core/access_control.py +202 -0
- auto_agent_kit/core/dashboard.py +145 -0
- auto_agent_kit/core/error_reflection.py +210 -0
- auto_agent_kit/core/mcp_server.py +239 -0
- auto_agent_kit/core/plan_mode.py +174 -0
- auto_agent_kit/core/tool_router.py +130 -0
- auto_agent_kit/examples/demo.py +181 -0
- auto_agent_kit-0.1.0.dist-info/METADATA +264 -0
- auto_agent_kit-0.1.0.dist-info/RECORD +14 -0
- auto_agent_kit-0.1.0.dist-info/WHEEL +5 -0
- auto_agent_kit-0.1.0.dist-info/licenses/LICENSE +21 -0
- auto_agent_kit-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""MCPServer — MCP 协议服务器
|
|
2
|
+
|
|
3
|
+
JSON-RPC 2.0 + SSE 传输,用于 Agent 工具暴露和远程调用。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import time
|
|
11
|
+
import uuid
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Any, Callable, Optional
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger("auto_agent_kit.mcp")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class MCPTool:
|
|
20
|
+
"""MCP 工具定义"""
|
|
21
|
+
name: str
|
|
22
|
+
description: str
|
|
23
|
+
handler: Callable
|
|
24
|
+
parameters: dict = field(default_factory=lambda: {"type": "object", "properties": {}})
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class MCPRequest:
|
|
29
|
+
"""JSON-RPC 请求"""
|
|
30
|
+
jsonrpc: str = "2.0"
|
|
31
|
+
method: str = ""
|
|
32
|
+
params: Any = None
|
|
33
|
+
id: Optional[str] = None
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def from_dict(cls, data: dict) -> "MCPRequest":
|
|
37
|
+
return cls(
|
|
38
|
+
jsonrpc=data.get("jsonrpc", "2.0"),
|
|
39
|
+
method=data.get("method", ""),
|
|
40
|
+
params=data.get("params"),
|
|
41
|
+
id=data.get("id"),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def to_response(self, result: Any = None, error: Optional[dict] = None) -> dict:
|
|
45
|
+
resp = {"jsonrpc": "2.0", "id": self.id}
|
|
46
|
+
if error:
|
|
47
|
+
resp["error"] = error
|
|
48
|
+
else:
|
|
49
|
+
resp["result"] = result
|
|
50
|
+
return resp
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class MCPServer:
|
|
54
|
+
"""MCP 协议服务器 — JSON-RPC 2.0 + SSE"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, host: str = "0.0.0.0", port: int = 8901):
|
|
57
|
+
self.host = host
|
|
58
|
+
self.port = port
|
|
59
|
+
self._tools: dict[str, MCPTool] = {}
|
|
60
|
+
self._request_log: list[dict] = []
|
|
61
|
+
self._max_log: int = 500
|
|
62
|
+
self._app: Any = None # FastAPI app (lazy init)
|
|
63
|
+
self._started: bool = False
|
|
64
|
+
|
|
65
|
+
def register_tool(self, name: str, description: str, handler: Callable,
|
|
66
|
+
parameters: Optional[dict] = None):
|
|
67
|
+
"""注册一个 MCP 工具"""
|
|
68
|
+
self._tools[name] = MCPTool(
|
|
69
|
+
name=name,
|
|
70
|
+
description=description,
|
|
71
|
+
handler=handler,
|
|
72
|
+
parameters=parameters or {"type": "object", "properties": {}},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def register_tools(self, tools: list[dict]):
|
|
76
|
+
"""批量注册工具"""
|
|
77
|
+
for t in tools:
|
|
78
|
+
self.register_tool(
|
|
79
|
+
name=t["name"],
|
|
80
|
+
description=t.get("description", ""),
|
|
81
|
+
handler=t["handler"],
|
|
82
|
+
parameters=t.get("parameters"),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def handle_request(self, raw: dict) -> dict:
|
|
86
|
+
"""处理 JSON-RPC 请求"""
|
|
87
|
+
try:
|
|
88
|
+
req = MCPRequest.from_dict(raw)
|
|
89
|
+
except Exception as e:
|
|
90
|
+
return {"jsonrpc": "2.0", "id": None, "error": {
|
|
91
|
+
"code": -32700, "message": f"Parse error: {e}"
|
|
92
|
+
}}
|
|
93
|
+
|
|
94
|
+
# 记录请求
|
|
95
|
+
self._request_log.append({
|
|
96
|
+
"timestamp": time.time(),
|
|
97
|
+
"method": req.method,
|
|
98
|
+
"id": req.id,
|
|
99
|
+
})
|
|
100
|
+
if len(self._request_log) > self._max_log:
|
|
101
|
+
self._request_log = self._request_log[-self._max_log:]
|
|
102
|
+
|
|
103
|
+
# 内置方法
|
|
104
|
+
if req.method == "list_tools":
|
|
105
|
+
return req.to_response(result={
|
|
106
|
+
"tools": [
|
|
107
|
+
{"name": t.name, "description": t.description, "parameters": t.parameters}
|
|
108
|
+
for t in self._tools.values()
|
|
109
|
+
]
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
if req.method == "ping":
|
|
113
|
+
return req.to_response(result={"pong": True, "timestamp": time.time()})
|
|
114
|
+
|
|
115
|
+
# 工具调用
|
|
116
|
+
if req.method == "call_tool":
|
|
117
|
+
tool_name = req.params.get("name", "") if req.params else ""
|
|
118
|
+
tool_args = req.params.get("arguments", {}) if req.params else {}
|
|
119
|
+
|
|
120
|
+
tool = self._tools.get(tool_name)
|
|
121
|
+
if not tool:
|
|
122
|
+
return req.to_response(error={
|
|
123
|
+
"code": -32601,
|
|
124
|
+
"message": f"Tool not found: {tool_name}",
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
result = tool.handler(**tool_args)
|
|
129
|
+
return req.to_response(result={
|
|
130
|
+
"content": [{"type": "text", "text": str(result)}],
|
|
131
|
+
})
|
|
132
|
+
except Exception as e:
|
|
133
|
+
logger.error(f"Tool {tool_name} failed: {e}")
|
|
134
|
+
return req.to_response(error={
|
|
135
|
+
"code": -32000,
|
|
136
|
+
"message": f"Tool execution error: {e}",
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
# 未知方法
|
|
140
|
+
return req.to_response(error={
|
|
141
|
+
"code": -32601,
|
|
142
|
+
"message": f"Method not found: {req.method}",
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
def build_app(self):
|
|
146
|
+
"""构建 FastAPI 应用(可选依赖)"""
|
|
147
|
+
try:
|
|
148
|
+
from fastapi import FastAPI, Request
|
|
149
|
+
from fastapi.responses import JSONResponse, StreamingResponse
|
|
150
|
+
except ImportError:
|
|
151
|
+
raise ImportError("FastAPI is required for MCPServer HTTP mode. "
|
|
152
|
+
"Install: pip install auto-agent-kit[mcp]")
|
|
153
|
+
|
|
154
|
+
app = FastAPI(title="AutoAgentKit MCP Server", version="0.1.0")
|
|
155
|
+
|
|
156
|
+
@app.get("/health")
|
|
157
|
+
async def health():
|
|
158
|
+
return {"status": "ok", "tools": len(self._tools)}
|
|
159
|
+
|
|
160
|
+
@app.get("/tools")
|
|
161
|
+
async def list_tools():
|
|
162
|
+
return {
|
|
163
|
+
"tools": [
|
|
164
|
+
{"name": t.name, "description": t.description}
|
|
165
|
+
for t in self._tools.values()
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@app.post("/rpc")
|
|
170
|
+
async def rpc(request: Request):
|
|
171
|
+
body = await request.json()
|
|
172
|
+
result = self.handle_request(body)
|
|
173
|
+
return JSONResponse(result)
|
|
174
|
+
|
|
175
|
+
@app.get("/sse")
|
|
176
|
+
async def sse(request: Request):
|
|
177
|
+
"""SSE 端点"""
|
|
178
|
+
async def event_generator():
|
|
179
|
+
yield f"data: {json.dumps({'type': 'connected', 'tools': len(self._tools)})}\n\n"
|
|
180
|
+
# 保持连接
|
|
181
|
+
while True:
|
|
182
|
+
await asyncio.sleep(30)
|
|
183
|
+
yield f"data: {json.dumps({'type': 'heartbeat'})}\n\n"
|
|
184
|
+
|
|
185
|
+
return StreamingResponse(
|
|
186
|
+
event_generator(),
|
|
187
|
+
media_type="text/event-stream",
|
|
188
|
+
headers={
|
|
189
|
+
"Cache-Control": "no-cache",
|
|
190
|
+
"Connection": "keep-alive",
|
|
191
|
+
},
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
self._app = app
|
|
195
|
+
return app
|
|
196
|
+
|
|
197
|
+
def start(self, host: Optional[str] = None, port: Optional[int] = None):
|
|
198
|
+
"""启动 MCP 服务器"""
|
|
199
|
+
import asyncio
|
|
200
|
+
import uvicorn
|
|
201
|
+
|
|
202
|
+
host = host or self.host
|
|
203
|
+
port = port or self.port
|
|
204
|
+
|
|
205
|
+
app = self.build_app()
|
|
206
|
+
self._started = True
|
|
207
|
+
logger.info(f"MCP Server starting on {host}:{port}")
|
|
208
|
+
uvicorn.run(app, host=host, port=port, log_level="info")
|
|
209
|
+
|
|
210
|
+
def start_async(self, host: Optional[str] = None, port: Optional[int] = None):
|
|
211
|
+
"""异步启动(在已有事件循环中使用)"""
|
|
212
|
+
import asyncio
|
|
213
|
+
import uvicorn
|
|
214
|
+
|
|
215
|
+
host = host or self.host
|
|
216
|
+
port = port or self.port
|
|
217
|
+
|
|
218
|
+
app = self.build_app()
|
|
219
|
+
self._started = True
|
|
220
|
+
|
|
221
|
+
config = uvicorn.Config(app, host=host, port=port, log_level="info")
|
|
222
|
+
server = uvicorn.Server(config)
|
|
223
|
+
return server.serve()
|
|
224
|
+
|
|
225
|
+
def get_stats(self) -> dict:
|
|
226
|
+
"""获取服务器统计"""
|
|
227
|
+
return {
|
|
228
|
+
"started": self._started,
|
|
229
|
+
"host": self.host,
|
|
230
|
+
"port": self.port,
|
|
231
|
+
"tools_registered": len(self._tools),
|
|
232
|
+
"requests_handled": len(self._request_log),
|
|
233
|
+
"tool_names": list(self._tools.keys()),
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
def stop(self):
|
|
237
|
+
"""停止服务器"""
|
|
238
|
+
self._started = False
|
|
239
|
+
logger.info("MCP Server stopped")
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""PlanMode — 计划执行模式
|
|
2
|
+
|
|
3
|
+
将复杂任务分解为可执行步骤,支持 Plan/Act 分离。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import time
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, Callable, Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class StepStatus(Enum):
|
|
16
|
+
PENDING = "pending"
|
|
17
|
+
IN_PROGRESS = "in_progress"
|
|
18
|
+
COMPLETED = "completed"
|
|
19
|
+
FAILED = "failed"
|
|
20
|
+
SKIPPED = "skipped"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class PlanStep:
|
|
25
|
+
"""单个计划步骤"""
|
|
26
|
+
id: str
|
|
27
|
+
description: str
|
|
28
|
+
status: StepStatus = StepStatus.PENDING
|
|
29
|
+
dependencies: list[str] = field(default_factory=list)
|
|
30
|
+
result: Optional[Any] = None
|
|
31
|
+
error: Optional[str] = None
|
|
32
|
+
started_at: Optional[float] = None
|
|
33
|
+
completed_at: Optional[float] = None
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def duration(self) -> Optional[float]:
|
|
37
|
+
if self.started_at and self.completed_at:
|
|
38
|
+
return self.completed_at - self.started_at
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class ExecutionPlan:
|
|
44
|
+
"""完整的执行计划"""
|
|
45
|
+
goal: str
|
|
46
|
+
steps: list[PlanStep] = field(default_factory=list)
|
|
47
|
+
created_at: float = field(default_factory=time.time)
|
|
48
|
+
context: dict[str, Any] = field(default_factory=dict)
|
|
49
|
+
|
|
50
|
+
def add_step(self, description: str, dependencies: Optional[list[str]] = None) -> PlanStep:
|
|
51
|
+
step_id = f"step_{len(self.steps) + 1}"
|
|
52
|
+
step = PlanStep(
|
|
53
|
+
id=step_id,
|
|
54
|
+
description=description,
|
|
55
|
+
dependencies=dependencies or [],
|
|
56
|
+
)
|
|
57
|
+
self.steps.append(step)
|
|
58
|
+
return step
|
|
59
|
+
|
|
60
|
+
def get_next_ready(self) -> Optional[PlanStep]:
|
|
61
|
+
"""获取下一个可执行的步骤(依赖已完成的步骤)"""
|
|
62
|
+
completed_ids = {s.id for s in self.steps if s.status == StepStatus.COMPLETED}
|
|
63
|
+
for step in self.steps:
|
|
64
|
+
if step.status != StepStatus.PENDING:
|
|
65
|
+
continue
|
|
66
|
+
if all(dep in completed_ids for dep in step.dependencies):
|
|
67
|
+
return step
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def progress(self) -> float:
|
|
72
|
+
if not self.steps:
|
|
73
|
+
return 0.0
|
|
74
|
+
done = sum(1 for s in self.steps if s.status in (StepStatus.COMPLETED, StepStatus.SKIPPED))
|
|
75
|
+
return done / len(self.steps)
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def summary(self) -> str:
|
|
79
|
+
total = len(self.steps)
|
|
80
|
+
done = sum(1 for s in self.steps if s.status == StepStatus.COMPLETED)
|
|
81
|
+
failed = sum(1 for s in self.steps if s.status == StepStatus.FAILED)
|
|
82
|
+
return f"[{done}/{total} 完成, {failed} 失败, {self.progress*100:.0f}%]"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class PlanMode:
|
|
86
|
+
"""计划执行模式 — 将复杂任务分解为可执行步骤"""
|
|
87
|
+
|
|
88
|
+
def __init__(self, max_retries: int = 2):
|
|
89
|
+
self.max_retries = max_retries
|
|
90
|
+
self.current_plan: Optional[ExecutionPlan] = None
|
|
91
|
+
self._history: list[dict] = []
|
|
92
|
+
|
|
93
|
+
def create_plan(self, goal: str, steps: Optional[list[str]] = None) -> ExecutionPlan:
|
|
94
|
+
"""创建执行计划"""
|
|
95
|
+
plan = ExecutionPlan(goal=goal)
|
|
96
|
+
if steps:
|
|
97
|
+
for desc in steps:
|
|
98
|
+
plan.add_step(desc)
|
|
99
|
+
self.current_plan = plan
|
|
100
|
+
self._history.append({"action": "create_plan", "goal": goal, "steps": len(steps or [])})
|
|
101
|
+
return plan
|
|
102
|
+
|
|
103
|
+
def start_step(self, step_id: str) -> Optional[PlanStep]:
|
|
104
|
+
"""开始执行一个步骤"""
|
|
105
|
+
if not self.current_plan:
|
|
106
|
+
return None
|
|
107
|
+
for step in self.current_plan.steps:
|
|
108
|
+
if step.id == step_id and step.status == StepStatus.PENDING:
|
|
109
|
+
step.status = StepStatus.IN_PROGRESS
|
|
110
|
+
step.started_at = time.time()
|
|
111
|
+
return step
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
def complete_step(self, step_id: str, result: Any = None) -> Optional[PlanStep]:
|
|
115
|
+
"""完成一个步骤"""
|
|
116
|
+
if not self.current_plan:
|
|
117
|
+
return None
|
|
118
|
+
for step in self.current_plan.steps:
|
|
119
|
+
if step.id == step_id and step.status == StepStatus.IN_PROGRESS:
|
|
120
|
+
step.status = StepStatus.COMPLETED
|
|
121
|
+
step.completed_at = time.time()
|
|
122
|
+
step.result = result
|
|
123
|
+
return step
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
def fail_step(self, step_id: str, error: str) -> Optional[PlanStep]:
|
|
127
|
+
"""标记步骤失败"""
|
|
128
|
+
if not self.current_plan:
|
|
129
|
+
return None
|
|
130
|
+
for step in self.current_plan.steps:
|
|
131
|
+
if step.id == step_id and step.status == StepStatus.IN_PROGRESS:
|
|
132
|
+
step.status = StepStatus.FAILED
|
|
133
|
+
step.completed_at = time.time()
|
|
134
|
+
step.error = error
|
|
135
|
+
return step
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
def execute_sequentially(self, executor: Callable[[str], Any]) -> list[dict]:
|
|
139
|
+
"""顺序执行所有步骤(无依赖)"""
|
|
140
|
+
if not self.current_plan:
|
|
141
|
+
return []
|
|
142
|
+
results = []
|
|
143
|
+
for step in self.current_plan.steps:
|
|
144
|
+
self.start_step(step.id)
|
|
145
|
+
for attempt in range(self.max_retries + 1):
|
|
146
|
+
try:
|
|
147
|
+
result = executor(step.description)
|
|
148
|
+
self.complete_step(step.id, result)
|
|
149
|
+
results.append({"step": step.id, "status": "ok", "result": result})
|
|
150
|
+
break
|
|
151
|
+
except Exception as e:
|
|
152
|
+
if attempt < self.max_retries:
|
|
153
|
+
continue
|
|
154
|
+
self.fail_step(step.id, str(e))
|
|
155
|
+
results.append({"step": step.id, "status": "failed", "error": str(e)})
|
|
156
|
+
return results
|
|
157
|
+
|
|
158
|
+
def get_plan_status(self) -> dict:
|
|
159
|
+
"""获取计划状态摘要"""
|
|
160
|
+
if not self.current_plan:
|
|
161
|
+
return {"status": "no_plan"}
|
|
162
|
+
return {
|
|
163
|
+
"goal": self.current_plan.goal,
|
|
164
|
+
"progress": self.current_plan.progress,
|
|
165
|
+
"summary": self.current_plan.summary,
|
|
166
|
+
"steps": [
|
|
167
|
+
{"id": s.id, "description": s.description, "status": s.status.value}
|
|
168
|
+
for s in self.current_plan.steps
|
|
169
|
+
],
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
def reset(self):
|
|
173
|
+
"""重置计划"""
|
|
174
|
+
self.current_plan = None
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""ToolRouter — 语义工具路由器
|
|
2
|
+
|
|
3
|
+
阶段性工具暴露,每阶段 ≤ 8 工具,防止上下文膨胀。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Any, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ToolPhase(Enum):
|
|
14
|
+
"""工具阶段"""
|
|
15
|
+
INIT = "init" # 初始阶段 — 核心工具
|
|
16
|
+
EXPLORE = "explore" # 探索阶段 — 搜索/读取工具
|
|
17
|
+
EXECUTE = "execute" # 执行阶段 — 全部工具
|
|
18
|
+
REVIEW = "review" # 审查阶段 — 验证/检查工具
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ToolDef:
|
|
23
|
+
"""工具定义"""
|
|
24
|
+
name: str
|
|
25
|
+
description: str
|
|
26
|
+
phase: ToolPhase = ToolPhase.INIT
|
|
27
|
+
usage_count: int = 0
|
|
28
|
+
last_used: Optional[float] = None
|
|
29
|
+
is_active: bool = True
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ToolRouter:
|
|
33
|
+
"""语义工具路由器 — 阶段性暴露工具,防止上下文膨胀"""
|
|
34
|
+
|
|
35
|
+
PHASE_LIMITS = {
|
|
36
|
+
ToolPhase.INIT: 5,
|
|
37
|
+
ToolPhase.EXPLORE: 6,
|
|
38
|
+
ToolPhase.EXECUTE: 8,
|
|
39
|
+
ToolPhase.REVIEW: 5,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def __init__(self):
|
|
43
|
+
self._tools: dict[str, ToolDef] = {}
|
|
44
|
+
self._current_phase: ToolPhase = ToolPhase.INIT
|
|
45
|
+
self._phase_history: list[ToolPhase] = []
|
|
46
|
+
|
|
47
|
+
def register(self, name: str, description: str, phase: ToolPhase = ToolPhase.INIT) -> ToolDef:
|
|
48
|
+
"""注册一个工具"""
|
|
49
|
+
tool = ToolDef(name=name, description=description, phase=phase)
|
|
50
|
+
self._tools[name] = tool
|
|
51
|
+
return tool
|
|
52
|
+
|
|
53
|
+
def register_batch(self, tools: list[dict]) -> list[ToolDef]:
|
|
54
|
+
"""批量注册工具"""
|
|
55
|
+
results = []
|
|
56
|
+
for t in tools:
|
|
57
|
+
results.append(self.register(
|
|
58
|
+
name=t["name"],
|
|
59
|
+
description=t["description"],
|
|
60
|
+
phase=ToolPhase(t.get("phase", "init")),
|
|
61
|
+
))
|
|
62
|
+
return results
|
|
63
|
+
|
|
64
|
+
def set_phase(self, phase: ToolPhase) -> list[ToolDef]:
|
|
65
|
+
"""切换阶段,返回当前阶段可用工具"""
|
|
66
|
+
self._phase_history.append(self._current_phase)
|
|
67
|
+
self._current_phase = phase
|
|
68
|
+
return self.get_active_tools()
|
|
69
|
+
|
|
70
|
+
def get_active_tools(self) -> list[ToolDef]:
|
|
71
|
+
"""获取当前阶段可用工具"""
|
|
72
|
+
active = [
|
|
73
|
+
t for t in self._tools.values()
|
|
74
|
+
if t.phase == self._current_phase and t.is_active
|
|
75
|
+
]
|
|
76
|
+
limit = self.PHASE_LIMITS.get(self._current_phase, 8)
|
|
77
|
+
return active[:limit]
|
|
78
|
+
|
|
79
|
+
def get_all_tools(self) -> list[ToolDef]:
|
|
80
|
+
"""获取所有注册工具"""
|
|
81
|
+
return list(self._tools.values())
|
|
82
|
+
|
|
83
|
+
def use_tool(self, name: str) -> Optional[ToolDef]:
|
|
84
|
+
"""记录工具使用"""
|
|
85
|
+
import time
|
|
86
|
+
tool = self._tools.get(name)
|
|
87
|
+
if tool:
|
|
88
|
+
tool.usage_count += 1
|
|
89
|
+
tool.last_used = time.time()
|
|
90
|
+
return tool
|
|
91
|
+
|
|
92
|
+
def deactivate(self, name: str) -> bool:
|
|
93
|
+
"""停用工具"""
|
|
94
|
+
tool = self._tools.get(name)
|
|
95
|
+
if tool:
|
|
96
|
+
tool.is_active = False
|
|
97
|
+
return True
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
def activate(self, name: str) -> bool:
|
|
101
|
+
"""启用工具"""
|
|
102
|
+
tool = self._tools.get(name)
|
|
103
|
+
if tool:
|
|
104
|
+
tool.is_active = True
|
|
105
|
+
return True
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
def get_stats(self) -> dict:
|
|
109
|
+
"""获取工具使用统计"""
|
|
110
|
+
return {
|
|
111
|
+
"total_tools": len(self._tools),
|
|
112
|
+
"current_phase": self._current_phase.value,
|
|
113
|
+
"active_tools": len(self.get_active_tools()),
|
|
114
|
+
"phase_history": [p.value for p in self._phase_history],
|
|
115
|
+
"usage": {
|
|
116
|
+
name: {"count": t.usage_count, "phase": t.phase.value}
|
|
117
|
+
for name, t in self._tools.items()
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
def find_dead_tools(self, threshold_days: float = 30) -> list[str]:
|
|
122
|
+
"""查找长期未使用的工具"""
|
|
123
|
+
import time
|
|
124
|
+
now = time.time()
|
|
125
|
+
threshold_seconds = threshold_days * 86400
|
|
126
|
+
dead = []
|
|
127
|
+
for name, tool in self._tools.items():
|
|
128
|
+
if tool.last_used and (now - tool.last_used) > threshold_seconds:
|
|
129
|
+
dead.append(name)
|
|
130
|
+
return dead
|