Files
JARVIS/development-doc/plan/tool-update/phase-t-2-tool-registry.md

477 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase T.2:工具注册中心
日期2026-04-04
状态:待开始
依赖T.1(待完成)
---
## 1. 本阶段目的
建立 Jarvis 的工具注册中心:
- 工具动态发现
- 工具注册与管理
- 工具描述生成
- 调用统计与监控
---
## 2. 工具注册中心架构
### 2.1 核心类
```python
# tools/registry.py
from typing import Dict, List, Optional, Callable
from dataclasses import dataclass, field
from datetime import datetime
import asyncio
@dataclass
class ToolMetadata:
"""工具元数据"""
name: str
display_name: str
description: str
version: str
author: Optional[str] = None
tags: List[str] = field(default_factory=list)
dependencies: List[str] = field(default_factory=list)
enabled: bool = True
registered_at: datetime = field(default_factory=datetime.utcnow)
# 统计
call_count: int = 0
error_count: int = 0
total_duration_ms: int = 0
@property
def avg_duration_ms(self) -> int:
if self.call_count == 0:
return 0
return self.total_duration_ms // self.call_count
@property
def error_rate(self) -> float:
if self.call_count == 0:
return 0.0
return self.error_count / self.call_count
class ToolRegistry:
"""工具注册中心"""
def __init__(self):
self._tools: Dict[str, ToolMetadata] = {}
self._executors: Dict[str, Callable] = {}
self._configs: Dict[str, dict] = {}
self._lock = asyncio.Lock()
# === 注册方法 ===
async def register(
self,
manifest_path: str,
executor: Callable,
config: Optional[dict] = None,
) -> ToolMetadata:
"""注册工具"""
from tools.schemas.validator import validate_manifest
import yaml
with open(manifest_path) as f:
data = yaml.safe_load(f)
manifest = validate_manifest(data)
metadata = ToolMetadata(
name=manifest.name,
display_name=manifest.display_name,
description=manifest.description,
version=manifest.version,
author=manifest.author,
tags=manifest.tags or [],
dependencies=manifest.dependencies or [],
enabled=manifest.enabled,
)
async with self._lock:
self._tools[manifest.name] = metadata
self._executors[manifest.name] = executor
if config:
self._configs[manifest.name] = config
return metadata
async def unregister(self, name: str) -> bool:
"""注销工具"""
async with self._lock:
if name in self._tools:
del self._tools[name]
del self._executors[name]
self._configs.pop(name, None)
return True
return False
async def enable(self, name: str) -> None:
"""启用工具"""
async with self._lock:
if name in self._tools:
self._tools[name].enabled = True
async def disable(self, name: str) -> None:
"""禁用工具"""
async with self._lock:
if name in self._tools:
self._tools[name].enabled = False
# === 查询方法 ===
async def get(self, name: str) -> Optional[ToolMetadata]:
"""获取工具元数据"""
return self._tools.get(name)
async def get_executor(self, name: str) -> Optional[Callable]:
"""获取工具执行器"""
return self._executors.get(name)
async def get_config(self, name: str) -> dict:
"""获取工具配置"""
return self._configs.get(name, {})
async def list_all(self) -> List[ToolMetadata]:
"""列出所有工具"""
return list(self._tools.values())
async def list_enabled(self) -> List[ToolMetadata]:
"""列出已启用的工具"""
return [t for t in self._tools.values() if t.enabled]
async def list_by_tag(self, tag: str) -> List[ToolMetadata]:
"""按标签筛选工具"""
return [t for t in self._tools.values() if tag in t.tags]
async def search(self, query: str) -> List[ToolMetadata]:
"""搜索工具"""
query = query.lower()
return [
t for t in self._tools.values()
if query in t.name.lower()
or query in t.description.lower()
or query in t.display_name.lower()
]
# === 统计方法 ===
async def record_call(
self,
name: str,
duration_ms: int,
error: bool = False,
) -> None:
"""记录调用"""
async with self._lock:
if name in self._tools:
tool = self._tools[name]
tool.call_count += 1
tool.total_duration_ms += duration_ms
if error:
tool.error_count += 1
async def get_stats(self) -> dict:
"""获取统计信息"""
tools = list(self._tools.values())
return {
"total_tools": len(tools),
"enabled_tools": sum(1 for t in tools if t.enabled),
"total_calls": sum(t.call_count for t in tools),
"total_errors": sum(t.error_count for t in tools),
"avg_error_rate": sum(t.error_rate for t in tools) / len(tools) if tools else 0,
}
```
---
## 3. 工具发现机制
### 3.1 自动发现
```python
# tools/discovery.py
from pathlib import Path
from typing import List
class ToolDiscovery:
"""工具自动发现"""
def __init__(self, manifest_dir: Path):
self.manifest_dir = manifest_dir
def discover(self) -> List[Path]:
"""发现所有 Manifest 文件"""
manifests = list(self.manifest_dir.glob("**/*.yaml"))
manifests.extend(self.manifest_dir.glob("**/*.yml"))
manifests.extend(self.manifest_dir.glob("**/*.json"))
return manifests
def discover_by_tag(self, tag: str) -> List[Path]:
"""按标签发现"""
# 读取所有 manifest筛选标签
pass
async def hot_reload(self, registry: ToolRegistry) -> None:
"""热重载工具"""
for manifest_path in self.discover():
# 重新注册
pass
```
### 3.2 启动时注册
```python
# tools/loader.py
from pathlib import Path
async def load_all_tools(registry: ToolRegistry) -> None:
"""加载所有工具"""
manifest_dir = Path(__file__).parent / "manifests"
discovery = ToolDiscovery(manifest_dir)
for manifest_path in discovery.discover():
tool_name = manifest_path.stem
# 加载 executor
executor = load_executor(manifest_path)
# 加载配置
config = load_config(tool_name)
# 注册
await registry.register(manifest_path, executor, config)
def load_executor(manifest_path: Path) -> Callable:
"""加载工具执行器"""
import yaml
with open(manifest_path) as f:
manifest = yaml.safe_load(f)
# 根据运行时类型加载
runtime = manifest.get("runtime", "python")
if runtime == "python":
return load_python_executor(manifest)
elif runtime == "javascript":
return load_js_executor(manifest)
else:
return load_native_executor(manifest)
```
---
## 4. 工具描述生成
### 4.1 AI 友好的工具描述
```python
# tools/description.py
from typing import Dict, List
def generate_tool_description(manifest: dict) -> str:
"""生成 AI 友好的工具描述"""
lines = [
f"## {manifest['display_name']}",
f"{manifest['description']}",
"",
"### 可用命令:",
]
for cmd in manifest.get("commands", []):
lines.append(f"#### {cmd['name']}")
lines.append(cmd["description"])
lines.append("")
if cmd.get("example"):
lines.append("**示例:**")
lines.append(f"```\n{cmd['example']}\n```")
lines.append("")
return "\n".join(lines)
def generate_tools_for_llm(registry: ToolRegistry) -> str:
"""生成给 LLM 的工具列表"""
tools = registry.list_enabled()
sections = ["## 可用工具\n"]
for tool in tools:
manifest = load_manifest(tool.name)
sections.append(generate_tool_description(manifest))
sections.append("\n---\n")
return "\n".join(sections)
```
---
## 5. 权限控制
### 5.1 工具权限
```python
# tools/permissions.py
from enum import Enum
from typing import Set
class ToolPermission(str, Enum):
"""工具权限"""
EXECUTE = "tool:execute"
CONFIGURE = "tool:configure"
ENABLE = "tool:enable"
DISABLE = "tool:disable"
class ToolPermissionChecker:
"""工具权限检查"""
def __init__(self):
self._user_permissions: Dict[str, Set[ToolPermission]] = {}
self._tool_roles: Dict[str, Set[str]] = {} # tool_name -> required_roles
def set_user_permissions(
self,
user_id: str,
permissions: Set[ToolPermission],
) -> None:
"""设置用户权限"""
self._user_permissions[user_id] = permissions
def set_tool_roles(
self,
tool_name: str,
required_roles: Set[str],
) -> None:
"""设置工具所需角色"""
self._tool_roles[tool_name] = required_roles
def can_execute(self, user_id: str, tool_name: str) -> bool:
"""检查用户是否可以执行工具"""
# 检查全局权限
if ToolPermission.EXECUTE in self._user_permissions.get(user_id, set()):
return True
# 检查工具特定角色
required_roles = self._tool_roles.get(tool_name, set())
if not required_roles:
return True
# TODO: 检查用户是否有所需角色
return False
```
---
## 6. 集成到 Agent
### 6.1 LangChain 集成
```python
# tools/langchain_adapter.py
from typing import List, Optional
from langchain.tools import BaseTool
from pydantic import BaseModel
class LangChainToolAdapter:
"""LangChain 工具适配器"""
def __init__(self, registry: ToolRegistry):
self.registry = registry
def to_langchain_tools(self) -> List[BaseTool]:
"""转换为 LangChain 工具"""
tools = []
for metadata in self.registry.list_enabled():
executor = self.registry.get_executor(metadata.name)
config = self.registry.get_config(metadata.name)
tool = self._create_tool(metadata, executor, config)
tools.append(tool)
return tools
def _create_tool(
self,
metadata: ToolMetadata,
executor: Callable,
config: dict,
) -> BaseTool:
"""创建单个 LangChain 工具"""
# 根据 manifest 创建工具
pass
```
---
## 7. 实现步骤
| 步骤 | 任务 | 优先级 |
|------|------|--------|
| 1 | 实现 ToolRegistry 类 | 🟢 高 |
| 2 | 实现 ToolDiscovery | 🟢 高 |
| 3 | 实现工具描述生成 | 🟡 中 |
| 4 | 实现权限检查 | 🟡 中 |
| 5 | 实现 LangChain 适配器 | 🟡 中 |
| 6 | 集成到 Agent | 🟢 高 |
| 7 | 单元测试 | 🟡 中 |
---
## 8. 核心文件变更
| 文件 | 变更 |
|------|------|
| `tools/registry.py` | 新增 |
| `tools/discovery.py` | 新增 |
| `tools/description.py` | 新增 |
| `tools/permissions.py` | 新增 |
| `tools/langchain_adapter.py` | 新增 |
| `tools/__init__.py` | 更新导出 |
---
## 9. 工作量估算
| 任务 | 工作量 |
|------|--------|
| ToolRegistry | 0.5 天 |
| ToolDiscovery | 0.5 天 |
| 描述生成 | 0.3 天 |
| 权限检查 | 0.3 天 |
| LangChain 适配 | 0.3 天 |
| 集成 | 0.5 天 |
| 单元测试 | 0.5 天 |
| **总计** | **2.5 天** |
---
## 10. 验收标准
- [ ] ToolRegistry 可正确注册/注销工具
- [ ] ToolDiscovery 可发现所有 Manifest
- [ ] 工具描述生成正确
- [ ] 权限检查正常工作
- [ ] LangChain 工具可正常转换
- [ ] Agent 可使用注册的工具
- [ ] 单元测试通过