feat(agents): Phase 8.4-10.5 built-in plugins, bundled skills, coordinator

This commit is contained in:
2026-04-04 23:24:34 +08:00
parent 88955ed550
commit d18167826e
105 changed files with 14780 additions and 15685 deletions

View File

@@ -0,0 +1,173 @@
# Jarvis Tools 升级计划索引
本目录用于存放 Jarvis 工具系统的分阶段升级规划文档。
## 文档说明
| 文件 | 说明 |
|------|------|
| `README.md` | 总览、阶段关系、实施顺序 |
| `phase-t-0-current-state.md` | 当前现状、问题、目标架构、VCPToolBox 借鉴 |
| `phase-t-1-manifest-system.md` | Manifest 驱动系统 |
| `phase-t-2-tool-registry.md` | 工具注册中心 |
| `phase-t-3-tool-implementation.md` | 核心工具实现 |
| `phase-t-4-advanced.md` | 高级特性(多运行时/Agent协作 |
| `checklist.md` | 执行清单 |
## 推荐阅读顺序
1. 先读 `phase-t-0-current-state.md`
2. 再按顺序阅读 phase t-1 ~ t-4
3. 实施时严格按阶段推进
4. 参考 `checklist.md` 进行任务追踪
---
## 总体升级原则
1. **Manifest 驱动** - 声明式工具定义,热插拔
2. **标准契约** - 统一的调用格式和返回结构
3. **多运行时** - 支持 Python/JS/原生
4. **类型安全** - Pydantic Schema 验证
5. **可观测性** - 调用日志、耗时统计
---
## 阶段总览图
```
T.0 ──────────────────────────────────────────────────────────────┐
│ 现状与目标 │
│ - 当前工具系统分析 │
│ - 短板识别 │
│ - VCPToolBox 工具系统借鉴 │
└────────────────────────────────────────────────────────────────────┘
T.1 ──────────────────────────────────────────────────────────────┐
│ Manifest 驱动系统 │
│ - 工具 manifest 定义 │
│ - 标准化契约 │
│ - Schema 验证 │
│ │
│ 核心文件: tools/manifests/, tools/schemas/ │
│ 工作量: 3 天 │
└────────────────────────────────────────────────────────────────────┘
T.2 ──────────────────────────────────────────────────────────────┐
│ 工具注册中心 │
│ - 工具发现机制 │
│ - 动态注册 │
│ - 工具描述生成 │
│ │
│ 核心文件: tools/registry.py │
│ 依赖: T.1 │
│ 工作量: 2 天 │
└────────────────────────────────────────────────────────────────────┘
T.3 ──────────────────────────────────────────────────────────────┐
│ 核心工具实现 │
│ - 文件操作工具 │
│ - 搜索工具 │
│ - 网页抓取工具 │
│ - 任务管理工具 │
│ │
│ 核心文件: tools/implementations/ │
│ 依赖: T.2 │
│ 工作量: 5 天 │
└────────────────────────────────────────────────────────────────────┘
T.4 ──────────────────────────────────────────────────────────────┐
│ 高级特性 │
│ - 多运行时支持 │
│ - Agent 间协作 │
│ - 定时任务 │
│ │
│ 核心文件: tools/runtime/, agents/tools/ │
│ 依赖: T.3 │
│ 工作量: 4 天 │
└────────────────────────────────────────────────────────────────────┘
```
---
## VCPToolBox 工具系统核心借鉴
| 借鉴点 | 实现位置 | 难度 |
|--------|---------|------|
| Manifest 驱动 | T.1 | 🟢 低 |
| 标准化契约 | T.1 | 🟢 低 |
| configSchema 配置 | T.1 | 🟢 低 |
| 工具注册中心 | T.2 | 🟡 中 |
| 动态发现 | T.2 | 🟡 中 |
| 文件操作工具 | T.3 | 🟢 低 |
| 搜索工具 | T.3 | 🟡 中 |
| 网页抓取 | T.3 | 🟡 中 |
| 多运行时支持 | T.4 | 🟡 中 |
| Agent 间协作 | T.4 | 🟡 中 |
---
## 实施顺序
```
T.0 → T.1 → T.2 → T.3 → T.4
│ │ │ │ │
│ │ │ │ └── 多运行时/Agent协作
│ │ │ └── 核心工具
│ │ └── 注册中心
│ └── Manifest系统
└── 现状与目标
```
**注意:** T.1 是基础,后续阶段都依赖 T.1。
---
## 文件变更追踪
| Phase | 新增文件 | 修改文件 |
|-------|---------|---------|
| T.1 | `tools/manifests/*.yaml`, `tools/schemas/` | `pyproject.toml` |
| T.2 | `tools/registry.py`, `tools/base.py` | `services/agent_service.py` |
| T.3 | `tools/implementations/*.py` | `tools/registry.py` |
| T.4 | `tools/runtime/`, `agents/tools/` | `agents/graph.py` |
---
## 与 Agent Phase 1-5 的关系
| Agent Phase | Tools 协作内容 |
|-------------|---------------|
| Phase 1 | Task Schema 追踪工具调用 |
| Phase 2 | 工具可委托给执行 Agent |
| Phase 3 | 动态选择最优工具 |
| Phase 4 | 工具调用可视化 |
| Phase 5 | 多 Agent 工具协作 |
| **Phase T** | **工具系统升级,与 Phase 1-5 协同** |
---
## 注意事项
| 注意事项 | 说明 |
|---------|------|
| T.1 是基础 | T.2-T.4 都依赖 T.1 的 Manifest 系统 |
| 兼容性优先 | 保持现有 Agent 工具调用方式 |
| 安全第一 | 严格权限控制,防止滥用 |
| 测试优先 | 每个工具都要配套测试 |
---
## 总工作量
| Phase | 工作量 |
|-------|--------|
| T.1 | 3 天 |
| T.2 | 2 天 |
| T.3 | 5 天 |
| T.4 | 4 天 |
| **总计** | **14 天** |

View File

@@ -0,0 +1,251 @@
# Tools 升级执行清单
本清单用于追踪 Tools 升级计划的执行进度。
---
## 总进度
| Phase | 名称 | 状态 | 工作量 |
|-------|------|------|--------|
| T.0 | 现状与目标 | ✅ 完成 | - |
| T.1 | Manifest 驱动系统 | ⬜ 待开始 | 3 天 |
| T.2 | 工具注册中心 | ⬜ 待开始 | 2 天 |
| T.3 | 核心工具实现 | ⬜ 待开始 | 5 天 |
| T.4 | 高级特性 | ⬜ 待开始 | 4 天 |
| **总计** | | | **14 天** |
---
## Phase T.1Manifest 驱动系统
### 目标
建立 Jarvis 的 Manifest 驱动工具系统,定义标准化工件声明。
### 任务清单
#### Schema 定义
- [ ] 创建 `tools/schemas/manifest.py`
- [ ] 定义 `ToolManifest` Schema
- [ ] 定义 `ToolType` 枚举
- [ ] 定义 `RuntimeType` 枚举
- [ ] 定义 `InvocationCommand` Schema
- [ ] 创建 `tools/schemas/tool_call.py`
- [ ] 定义 `ToolCallRequest` Schema
- [ ] 定义 `ToolCallResponse` Schema
- [ ] 定义 `ToolExecutionLog` Schema
#### 验证器
- [ ] 创建 `tools/schemas/validator.py`
- [ ] 实现 `validate_manifest` 函数
- [ ] 实现 `validate_tool_call` 函数
- [ ] 实现错误类
#### 配置系统
- [ ] 创建 `tools/configs/loader.py`
- [ ] 实现 `ConfigLoader`
- [ ] 实现配置缓存
- [ ] 实现配置重载
#### Manifest 文件
- [ ] 创建 `tools/manifests/file_operator.yaml`
- [ ] 创建 `tools/manifests/web_search.yaml`
- [ ] 创建其他工具 Manifest
#### 测试
- [ ] 单元测试
### 产出文件
- `tools/schemas/manifest.py`
- `tools/schemas/tool_call.py`
- `tools/schemas/validator.py`
- `tools/configs/loader.py`
- `tools/manifests/*.yaml`
### 验收
- [ ] Schema 验证正常工作
- [ ] 配置加载器正常工作
- [ ] Manifest 文件格式正确
- [ ] 单元测试通过
---
## Phase T.2:工具注册中心
### 目标
实现工具的动态注册、发现和管理。
### 任务清单
#### 注册中心
- [ ] 创建 `tools/registry.py`
- [ ] 实现 `ToolMetadata` dataclass
- [ ] 实现 `ToolRegistry`
- [ ] 实现注册/注销方法
- [ ] 实现查询方法
- [ ] 实现统计方法
#### 工具发现
- [ ] 创建 `tools/discovery.py`
- [ ] 实现 `ToolDiscovery`
- [ ] 实现自动发现
- [ ] 实现热重载
#### 描述生成
- [ ] 创建 `tools/description.py`
- [ ] 实现 AI 友好描述生成
- [ ] 实现工具列表生成
#### 权限控制
- [ ] 创建 `tools/permissions.py`
- [ ] 实现 `ToolPermission` 枚举
- [ ] 实现 `ToolPermissionChecker`
#### LangChain 集成
- [ ] 创建 `tools/langchain_adapter.py`
- [ ] 实现适配器
- [ ] 集成到 Agent
#### 测试
- [ ] 单元测试
### 产出文件
- `tools/registry.py`
- `tools/discovery.py`
- `tools/description.py`
- `tools/permissions.py`
- `tools/langchain_adapter.py`
### 验收
- [ ] 注册中心正常工作
- [ ] 工具发现正常工作
- [ ] 权限检查正常工作
- [ ] LangChain 适配器正常工作
- [ ] 单元测试通过
---
## Phase T.3:核心工具实现
### 目标
实现文件操作、搜索、网页抓取等核心工具。
### 任务清单
#### 文件操作工具
- [ ] 创建 `tools/implementations/file_operator.py`
- [ ] 实现 `FileOperator`
- [ ] 实现 read_file
- [ ] 实现 write_file
- [ ] 实现 list_directory
- [ ] 实现 search_files
- [ ] 实现路径安全检查
- [ ] 实现多格式支持PDF/DOCX/XLSX
#### 搜索工具
- [ ] 创建 `tools/implementations/web_search.py`
- [ ] 实现 `WebSearch`
- [ ] 实现 search 方法
- [ ] 实现 deep_search 方法
#### 网页抓取工具
- [ ] 创建 `tools/implementations/web_fetch.py`
- [ ] 实现 `WebFetch`
- [ ] 实现 fetch 方法
- [ ] 实现 screenshot 方法
#### 任务管理工具
- [ ] 创建 `tools/implementations/task_manager.py`
- [ ] 实现 `TaskManager`
- [ ] 实现任务 CRUD
#### Manifest 绑定
- [ ] 更新 Manifest 文件
- [ ] 注册到工具中心
#### 测试
- [ ] 单元测试
### 产出文件
- `tools/implementations/file_operator.py`
- `tools/implementations/web_search.py`
- `tools/implementations/web_fetch.py`
- `tools/implementations/task_manager.py`
### 验收
- [ ] 文件操作工具正常工作
- [ ] 搜索工具正常工作
- [ ] 网页抓取工具正常工作
- [ ] 任务管理工具正常工作
- [ ] 单元测试通过
---
## Phase T.4:高级特性
### 目标
实现多运行时支持、Agent 协作和定时任务。
### 任务清单
#### 运行时系统
- [ ] 创建 `tools/runtime/base.py`
- [ ] 定义 `BaseRuntime` 抽象基类
- [ ] 创建 `tools/runtime/python_runtime.py`
- [ ] 创建 `tools/runtime/js_runtime.py`
- [ ] 创建 `tools/runtime/native_runtime.py`
- [ ] 创建 `tools/runtime/manager.py`
#### Agent 协作
- [ ] 创建 `agents/tools/collaboration.py`
- [ ] 定义 `CollaborationMessage`
- [ ] 实现 `CollaborationProtocol`
- [ ] 实现请求/响应机制
#### 定时任务
- [ ] 创建 `tools/scheduler.py`
- [ ] 实现 `ScheduledTask`
- [ ] 实现 `ToolScheduler`
- [ ] 实现多种调度类型
#### 测试
- [ ] 单元测试
### 产出文件
- `tools/runtime/*.py`
- `agents/tools/collaboration.py`
- `tools/scheduler.py`
### 验收
- [ ] 多运行时正常工作
- [ ] Agent 协作正常工作
- [ ] 定时任务正常工作
- [ ] 单元测试通过
---
## 完成标准
- [ ] 所有 Phase T.1-T.4 任务完成
- [ ] 所有单元测试通过
- [ ] API 文档更新完成
- [ ] 部署验证通过
---
## 风险与注意事项
| 风险 | 影响 | 缓解措施 |
|------|------|----------|
| 多运行时复杂性 | 开发成本增加 | 优先实现 Python 运行时 |
| JS 运行时依赖 Node | 部署环境要求 | 提供降级方案 |
| 定时任务精度 | 任务延迟 | 使用专业调度库 |
| 安全漏洞 | 系统风险 | 严格权限控制 |
---
## 更新日志
| 日期 | Phase | 变更内容 |
|------|-------|----------|
| 2026-04-04 | T.0 | 创建文档 |

View File

@@ -0,0 +1,192 @@
# Phase T.0Tools 现状与目标
日期2026-04-04
状态:已完成
借鉴来源VCPToolBox Plugin 系统
---
## 1. 本阶段目的
本文件用于统一背景认知,明确:
- Jarvis 当前工具系统处于什么水平
- 主要短板是什么
- 为什么要升级
- 升级后的目标形态是什么
- VCPToolBox 给我们什么启发
---
## 2. 当前 Jarvis Tools 架构
### 2.1 核心流程
```
Agent 决策 → 工具调用 → LLM API → 返回结果
```
### 2.2 当前工具实现
Jarvis 当前的工具以 LangChain Tools 形式存在:
```python
# agents/tools/
base.py # 工具基类
file_tools.py # 文件操作
search_tools.py # 搜索
web_tools.py # 网页相关
```
### 2.3 当前工具列表
| 工具 | 功能 | 实现方式 |
|------|------|---------|
| `read_file` | 读取文件 | Python |
| `write_file` | 写入文件 | Python |
| `run_python` | 执行代码 | Python |
| `search_knowledge` | 知识库检索 | LangChain |
| `search_web` | 联网搜索 | API |
### 2.4 当前问题
| 问题 | 影响 |
|------|------|
| 硬编码工具 | 新增工具需改代码 |
| 无 manifest | 无法热插拔 |
| 无标准化契约 | 调用格式不统一 |
| 无配置分离 | 敏感信息易泄露 |
| 无类型安全 | 验证缺失 |
| 无权限控制 | 安全隐患 |
---
## 3. VCPToolBox 工具系统分析
### 3.1 六大插件类型
```javascript
const PLUGIN_TYPES = {
static: "静态占位符,自动注入系统提示词",
synchronous: "同步执行stdio 协议",
asynchronous: "异步执行,后台处理",
service: "持续运行服务",
hybridservice: "混合服务Agent间通讯",
messagePreprocessor: "消息预处理"
};
```
### 3.2 Manifest 标准契约
```javascript
{
"manifestVersion": "1.0.0",
"name": "PluginName",
"displayName": "中文显示名",
"description": "功能描述",
"pluginType": "synchronous",
"version": "1.0.0",
"entryPoint": {
"type": "nodejs",
"command": "node Plugin.js",
"timeout": 300000
},
"communication": {
"protocol": "stdio",
"timeout": 300000
},
"configSchema": {
"API_KEY": {
"type": "string",
"description": "API 密钥"
}
},
"capabilities": {
"invocationCommands": [
{
"commandIdentifier": "CommandName",
"description": "详细描述+调用格式示例",
"example": "调用示例"
}
]
}
}
```
### 3.3 核心设计理念
1. **声明式** - 工具通过 manifest 声明,框架自动发现
2. **标准化** - 统一的调用协议stdio/websocket
3. **可配置** - configSchema 声明配置项
4. **可观测** - 调用日志、超时控制
5. **安全隔离** - 沙箱执行、权限控制
### 3.4 关键文件
| 文件 | 作用 |
|------|------|
| `Plugin.js` | 插件加载与执行引擎 |
| `plugin-manifest.json` | 插件声明契约 |
| `config.env` | 插件配置 |
| `routes/` | API 路由层 |
---
## 4. 目标架构
```
┌─────────────────────────────────────────────────────────────┐
│ Tool Manifests │
│ - YAML/JSON 声明式定义 │
│ - 版本管理 │
│ - Schema 验证 │
└─────────────────────────┬───────────────────────────────────┘
┌─────────────────────────┴───────────────────────────────────┐
│ Tool Registry │
│ - 动态发现 │
│ - 权限控制 │
│ - 调用统计 │
└─────────────────────────┬───────────────────────────────────┘
┌─────────────────────────┴───────────────────────────────────┐
│ Tool Executor │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Python RT │ │ JS RT │ │ Native RT │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────┬───────────────────────────────────┘
┌─────────────────────────┴───────────────────────────────────┐
│ Tool Output │
│ - 类型化返回 │
│ - 错误处理 │
│ - 调用日志 │
└─────────────────────────────────────────────────────────────┘
```
---
## 5. 借鉴点映射
| VCPToolBox 借鉴点 | Jarvis 实现位置 | 优先级 |
|-------------------|---------------|--------|
| Manifest 驱动 | `tools/manifests/` | 🟢 高 |
| 标准化契约 | `tools/schemas/` | 🟢 高 |
| configSchema | `tools/base.py` | 🟢 高 |
| 工具注册中心 | `tools/registry.py` | 🟡 中 |
| 动态发现 | `tools/discovery.py` | 🟡 中 |
| 调用日志 | `tools/logging.py` | 🟡 中 |
| 超时控制 | `tools/executor.py` | 🟡 中 |
| 多运行时 | `tools/runtime/` | 🟡 中 |
---
## 6. 本阶段产出要求
- [x] 团队对 Jarvis 当前工具系统和目标方向达成一致
- [x] VCPToolBox 工具系统借鉴点已映射到具体 Phase
- [x] 后续 phase 文档能够在这个认知基础上展开

View File

@@ -0,0 +1,484 @@
# Phase T.1Manifest 驱动系统
日期2026-04-04
状态:待开始
依赖T.0(已完成)
---
## 1. 本阶段目的
建立 Jarvis 的 Manifest 驱动工具系统:
- 定义工具 Manifest Schema
- 实现 Schema 验证
- 创建核心工具的 Manifest 文件
- 实现配置分离
---
## 2. Manifest Schema 设计
### 2.1 目录结构
```
backend/app/tools/
├── manifests/ # 工具 manifest
│ ├── file_operator.yaml
│ ├── search.yaml
│ └── web_fetch.yaml
├── schemas/ # Schema 定义
│ ├── __init__.py
│ ├── manifest.py # Manifest Schema
│ ├── tool_call.py # 工具调用 Schema
│ └── config.py # 配置 Schema
└── configs/ # 配置分离
└── .tool.example # 工具配置模板
```
### 2.2 ToolManifest Schema
```python
# tools/schemas/manifest.py
from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any
from enum import Enum
class ToolType(str, Enum):
"""工具类型"""
SYNC = "sync" # 同步执行
ASYNC = "async" # 异步执行
SERVICE = "service" # 持续服务
class RuntimeType(str, Enum):
"""运行时类型"""
PYTHON = "python"
JAVASCRIPT = "javascript"
NATIVE = "native"
class InvocationCommand(BaseModel):
"""调用命令定义"""
name: str = Field(..., description="命令名称")
description: str = Field(..., description="命令描述(给 AI 看)")
parameters: Optional[Dict[str, Any]] = Field(
default=None,
description="参数 JSON Schema"
)
required: Optional[List[str]] = Field(
default=None,
description="必需参数列表"
)
example: Optional[str] = Field(
default=None,
description="调用示例"
)
class ToolManifest(BaseModel):
"""工具 Manifest"""
manifest_version: str = Field(
default="1.0.0",
description="Manifest 版本"
)
name: str = Field(..., description="工具名称(英文,唯一)")
display_name: str = Field(..., description="显示名称(中文)")
description: str = Field(..., description="工具描述")
author: Optional[str] = Field(default=None, description="作者")
version: str = Field(default="1.0.0", description="版本号")
# 执行配置
type: ToolType = Field(default=ToolType.SYNC, description="工具类型")
runtime: RuntimeType = Field(default=RuntimeType.PYTHON, description="运行时")
entry: str = Field(..., description="执行入口(文件路径或命令)")
timeout: int = Field(default=30000, description="超时时间(毫秒)")
# 配置
config_schema: Optional[Dict[str, Any]] = Field(
default=None,
description="配置项 Schema"
)
# 能力
commands: List[InvocationCommand] = Field(
default_factory=list,
description="可用命令列表"
)
# 元数据
tags: Optional[List[str]] = Field(default=None, description="标签")
dependencies: Optional[List[str]] = Field(default=None, description="依赖工具")
enabled: bool = Field(default=True, description="是否启用")
class Config:
use_enum_values = True
```
### 2.3 ToolCall Schema
```python
# tools/schemas/tool_call.py
from pydantic import BaseModel, Field
from typing import Optional, Dict, Any, List
from datetime import datetime
class ToolCallRequest(BaseModel):
"""工具调用请求"""
tool_name: str = Field(..., description="工具名称")
command: str = Field(..., description="命令名称")
parameters: Dict[str, Any] = Field(default_factory=dict, description="参数")
timeout: Optional[int] = Field(default=None, description="超时时间")
context: Optional[Dict[str, Any]] = Field(
default=None,
description="上下文信息"
)
class ToolCallResponse(BaseModel):
"""工具调用响应"""
status: str = Field(..., description="状态: success/error")
result: Optional[Any] = Field(default=None, description="执行结果")
error: Optional[str] = Field(default=None, description="错误信息")
message: Optional[str] = Field(default=None, description="AI 友好消息")
base64: Optional[str] = Field(default=None, description="Base64 数据")
duration_ms: Optional[int] = Field(default=None, description="执行耗时")
timestamp: datetime = Field(default_factory=datetime.utcnow)
class ToolExecutionLog(BaseModel):
"""工具执行日志"""
id: str
tool_name: str
command: str
parameters: Dict[str, Any]
status: str
duration_ms: int
error: Optional[str]
user_id: Optional[str]
agent_id: Optional[str]
created_at: datetime
```
---
## 3. Manifest 示例
### 3.1 file_operator.yaml
```yaml
manifest_version: "1.0.0"
name: file_operator
display_name: 文件操作器
description: 强大的文件系统操作工具,支持读写、搜索、下载等功能
author: Jarvis
version: "1.0.0"
type: sync
runtime: python
entry: tools/implementations/file_operator.py
timeout: 30000
config_schema:
allowed_directories:
type: string
description: 允许操作的目录列表,逗号分隔
default: ""
max_file_size:
type: integer
description: 最大文件大小(字节)
default: 10485760
commands:
- name: read_file
description: |
读取指定路径文件的内容。支持 PDF、DOCX、XLSX 等格式自动解析。
参数:
- filePath (必需): 文件绝对路径
- encoding (可选): 编码格式,默认 utf8
parameters:
type: object
properties:
filePath:
type: string
description: 文件绝对路径
encoding:
type: string
default: utf8
required: [filePath]
- name: write_file
description: |
将内容写入文件。如果文件存在,自动创建新文件避免覆盖。
参数:
- filePath (必需): 文件绝对路径
- content (必需): 文件内容
parameters:
type: object
properties:
filePath:
type: string
content:
type: string
required: [filePath, content]
- name: list_directory
description: |
列出目录内容。
参数:
- directoryPath (必需): 目录绝对路径
- showHidden (可选): 是否显示隐藏文件
parameters:
type: object
properties:
directoryPath:
type: string
showHidden:
type: boolean
default: false
required: [directoryPath]
- name: search_files
description: |
递归搜索匹配模式的文件。
参数:
- searchPath (必需): 搜索起始目录
- pattern (必需): 文件模式,如 *.txt
parameters:
type: object
properties:
searchPath:
type: string
pattern:
type: string
required: [searchPath, pattern]
tags: [file, system, essential]
enabled: true
```
### 3.2 search.yaml
```yaml
manifest_version: "1.0.0"
name: web_search
display_name: 联网搜索
description: 语义级并发搜索引擎,支持多源搜索和结果聚合
author: Jarvis
version: "1.0.0"
type: sync
runtime: python
entry: tools/implementations/web_search.py
timeout: 60000
config_schema:
api_key:
type: string
description: 搜索引擎 API 密钥
required: true
max_results:
type: integer
description: 最大返回结果数
default: 10
commands:
- name: search
description: |
执行语义级搜索。
参数:
- query (必需): 搜索关键词
- max_results (可选): 最大结果数
- sources (可选): 搜索源列表
parameters:
type: object
properties:
query:
type: string
max_results:
type: integer
default: 10
sources:
type: array
items:
type: string
required: [query]
- name: deep_search
description: |
深度搜索,带摘要生成。
参数:
- query (必需): 研究主题
- keywords (必需): 关键词列表
parameters:
type: object
properties:
query:
type: string
keywords:
type: array
items:
type: string
required: [query, keywords]
tags: [search, web, research]
enabled: true
```
---
## 4. Schema 验证
### 4.1 验证器
```python
# tools/schemas/validator.py
from pydantic import ValidationError
from tools.schemas.manifest import ToolManifest
def validate_manifest(data: dict) -> ToolManifest:
"""验证 Manifest 数据"""
try:
return ToolManifest(**data)
except ValidationError as e:
raise ManifestValidationError(str(e))
def validate_tool_call(data: dict) -> ToolCallRequest:
"""验证工具调用请求"""
from tools.schemas.tool_call import ToolCallRequest
try:
return ToolCallRequest(**data)
except ValidationError as e:
raise ToolCallValidationError(str(e))
class ManifestValidationError(Exception):
"""Manifest 验证错误"""
pass
class ToolCallValidationError(Exception):
"""工具调用验证错误"""
pass
```
---
## 5. 配置分离
### 5.1 工具配置模板
```yaml
# tools/configs/.tool.example
# 文件操作器
file_operator:
allowed_directories: ""
max_file_size: 10485760
# 联网搜索
web_search:
api_key: ""
max_results: 10
```
### 5.2 配置加载器
```python
# tools/configs/loader.py
import yaml
from pathlib import Path
from typing import Dict, Any
class ConfigLoader:
"""工具配置加载器"""
def __init__(self, config_dir: Path):
self.config_dir = config_dir
self._cache: Dict[str, Any] = {}
def load(self, tool_name: str) -> Dict[str, Any]:
"""加载指定工具的配置"""
if tool_name in self._cache:
return self._cache[tool_name]
config_file = self.config_dir / f"{tool_name}.yaml"
if not config_file.exists():
return {}
with open(config_file) as f:
config = yaml.safe_load(f) or {}
self._cache[tool_name] = config
return config
def reload(self, tool_name: str) -> Dict[str, Any]:
"""重新加载配置"""
if tool_name in self._cache:
del self._cache[tool_name]
return self.load(tool_name)
def get(self, tool_name: str, key: str, default: Any = None) -> Any:
"""获取配置项"""
config = self.load(tool_name)
return config.get(key, default)
```
---
## 6. 实现步骤
| 步骤 | 任务 | 优先级 |
|------|------|--------|
| 1 | 创建目录结构 | 🟢 高 |
| 2 | 实现 ToolManifest Schema | 🟢 高 |
| 3 | 实现 ToolCall Schema | 🟢 高 |
| 4 | 实现 Schema 验证器 | 🟢 高 |
| 5 | 创建配置加载器 | 🟢 高 |
| 6 | 创建 file_operator.yaml | 🟢 高 |
| 7 | 创建 search.yaml | 🟡 中 |
| 8 | 创建其他工具 Manifest | 🟡 中 |
| 9 | 单元测试 | 🟡 中 |
---
## 7. 核心文件变更
| 文件 | 变更 |
|------|------|
| `tools/__init__.py` | 模块初始化 |
| `tools/schemas/__init__.py` | Schema 导出 |
| `tools/schemas/manifest.py` | 新增 |
| `tools/schemas/tool_call.py` | 新增 |
| `tools/schemas/validator.py` | 新增 |
| `tools/configs/loader.py` | 新增 |
| `tools/manifests/file_operator.yaml` | 新增 |
| `tools/manifests/search.yaml` | 新增 |
---
## 8. 工作量估算
| 任务 | 工作量 |
|------|--------|
| Schema 定义 | 1 天 |
| 验证器 | 0.5 天 |
| 配置加载器 | 0.5 天 |
| Manifest 文件 | 0.5 天 |
| 单元测试 | 0.5 天 |
| **总计** | **3 天** |
---
## 9. 验收标准
- [ ] ToolManifest Schema 可正确验证 Manifest
- [ ] ToolCall Schema 可正确验证调用请求
- [ ] 配置加载器可正确加载配置
- [ ] Manifest 文件格式正确
- [ ] Schema 验证器可捕获错误
- [ ] 单元测试覆盖核心逻辑

View File

@@ -0,0 +1,476 @@
# 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 可使用注册的工具
- [ ] 单元测试通过

View File

@@ -0,0 +1,586 @@
# Phase T.3:核心工具实现
日期2026-04-04
状态:待开始
依赖T.2(待完成)
---
## 1. 本阶段目的
实现 Jarvis 的核心工具:
- 文件操作工具
- 搜索工具
- 网页抓取工具
- 任务管理工具
---
## 2. 文件操作工具
### 2.1 实现
```python
# tools/implementations/file_operator.py
import os
import shutil
import asyncio
from pathlib import Path
from typing import Optional, List, Dict, Any
class FileOperator:
"""文件操作工具"""
def __init__(self, config: dict):
self.allowed_dirs = self._parse_allowed_dirs(
config.get("allowed_directories", "")
)
self.max_file_size = config.get("max_file_size", 10 * 1024 * 1024)
def _parse_allowed_dirs(self, dirs_str: str) -> Optional[List[str]]:
"""解析允许目录"""
if not dirs_str:
return None
return [d.strip() for d in dirs_str.split(",") if d.strip()]
def _check_path(self, path: str) -> bool:
"""检查路径是否允许"""
if not self.allowed_dirs:
return True
resolved = Path(path).resolve()
return any(
str(resolved).startswith(allowed)
for allowed in self.allowed_dirs
)
async def read_file(
self,
filePath: str,
encoding: str = "utf-8",
) -> Dict[str, Any]:
"""读取文件"""
if not self._check_path(filePath):
return {"status": "error", "error": "路径不在允许范围内"}
path = Path(filePath)
if not path.exists():
return {"status": "error", "error": "文件不存在"}
if path.stat().st_size > self.max_file_size:
return {"status": "error", "error": "文件过大"}
# 根据扩展名处理
suffix = path.suffix.lower()
if suffix in [".pdf", ".docx", ".xlsx", ".xls", ".csv"]:
return await self._read_binary_file(path)
try:
content = path.read_text(encoding=encoding)
return {"status": "success", "result": content}
except Exception as e:
return {"status": "error", "error": str(e)}
async def _read_binary_file(self, path: Path) -> Dict[str, Any]:
"""读取二进制文件"""
suffix = path.suffix.lower()
if suffix == ".pdf":
return await self._read_pdf(path)
elif suffix in [".docx", ".doc"]:
return await self._read_docx(path)
elif suffix in [".xlsx", ".xls"]:
return await self._read_xlsx(path)
elif suffix == ".csv":
return await self._read_csv(path)
return {"status": "error", "error": "不支持的文件格式"}
async def write_file(
self,
filePath: str,
content: str,
) -> Dict[str, Any]:
"""写入文件"""
if not self._check_path(filePath):
return {"status": "error", "error": "路径不在允许范围内"}
path = Path(filePath)
# 如果文件存在,自动创建新文件名
if path.exists():
path = self._get_unique_path(path)
try:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
return {
"status": "success",
"result": f"文件已保存: {path.name}",
"path": str(path),
}
except Exception as e:
return {"status": "error", "error": str(e)}
def _get_unique_path(self, path: Path) -> Path:
"""获取唯一路径"""
if not path.exists():
return path
stem = path.stem
suffix = path.suffix
parent = path.parent
counter = 1
while True:
new_path = parent / f"{stem}({counter}){suffix}"
if not new_path.exists():
return new_path
counter += 1
async def list_directory(
self,
directoryPath: str,
showHidden: bool = False,
) -> Dict[str, Any]:
"""列出目录"""
if not self._check_path(directoryPath):
return {"status": "error", "error": "路径不在允许范围内"}
path = Path(directoryPath)
if not path.exists():
return {"status": "error", "error": "目录不存在"}
if not path.is_dir():
return {"status": "error", "error": "不是目录"}
items = []
for item in path.iterdir():
if not showHidden and item.name.startswith("."):
continue
items.append({
"name": item.name,
"type": "directory" if item.is_dir() else "file",
"size": item.stat().st_size if item.is_file() else None,
})
return {"status": "success", "result": items}
async def search_files(
self,
searchPath: str,
pattern: str,
**options,
) -> Dict[str, Any]:
"""搜索文件"""
import fnmatch
if not self._check_path(searchPath):
return {"status": "error", "error": "路径不在允许范围内"}
path = Path(searchPath)
if not path.exists():
return {"status": "error", "error": "路径不存在"}
case_sensitive = options.get("caseSensitive", False)
file_type = options.get("fileType", "all")
include_hidden = options.get("includeHidden", False)
results = []
for item in path.rglob("*"):
if not include_hidden and item.name.startswith("."):
continue
if not fnmatch.fnmatch(item.name, pattern):
continue
if file_type == "file" and item.is_dir():
continue
if file_type == "directory" and item.is_file():
continue
results.append(str(item))
return {"status": "success", "result": results[:100]} # 限制结果数
```
### 2.2 Manifest 绑定
```python
# tools/implementations/__init__.py
from tools.implementations.file_operator import FileOperator
def create_file_operator_executor(config: dict):
"""创建文件操作执行器"""
operator = FileOperator(config)
async def execute(command: str, parameters: dict) -> dict:
if command == "read_file":
return await operator.read_file(**parameters)
elif command == "write_file":
return await operator.write_file(**parameters)
elif command == "list_directory":
return await operator.list_directory(**parameters)
elif command == "search_files":
return await operator.search_files(**parameters)
else:
return {"status": "error", "error": f"未知命令: {command}"}
return execute
```
---
## 3. 搜索工具
### 3.1 实现
```python
# tools/implementations/web_search.py
import asyncio
from typing import Dict, Any, List, Optional
class WebSearch:
"""联网搜索工具"""
def __init__(self, config: dict):
self.api_key = config.get("api_key")
self.max_results = config.get("max_results", 10)
async def search(
self,
query: str,
max_results: Optional[int] = None,
) -> Dict[str, Any]:
"""执行搜索"""
try:
# 实现搜索逻辑
results = await self._do_search(
query,
max_results or self.max_results,
)
return {"status": "success", "result": results}
except Exception as e:
return {"status": "error", "error": str(e)}
async def _do_search(self, query: str, limit: int) -> List[dict]:
"""实际搜索"""
# TODO: 接入搜索 API
return []
async def deep_search(
self,
query: str,
keywords: List[str],
) -> Dict[str, Any]:
"""深度搜索"""
try:
# 并发执行多个搜索
tasks = [
self._do_search(kw, 5)
for kw in [query] + keywords
]
results = await asyncio.gather(*tasks)
# 聚合结果
aggregated = self._aggregate_results(results)
return {"status": "success", "result": aggregated}
except Exception as e:
return {"status": "error", "error": str(e)}
def _aggregate_results(self, results: List[List[dict]]) -> dict:
"""聚合搜索结果"""
# TODO: 实现结果聚合
return {"summary": "聚合结果", "sources": []}
```
---
## 4. 网页抓取工具
### 4.1 实现
```python
# tools/implementations/web_fetch.py
import asyncio
from typing import Dict, Any, Optional
from dataclasses import dataclass
@dataclass
class FetchResult:
"""抓取结果"""
url: str
title: Optional[str]
content: str
images: List[str]
links: List[str]
status: int
class WebFetch:
"""网页抓取工具"""
def __init__(self, config: dict):
self.timeout = config.get("timeout", 30)
self.user_agent = config.get(
"user_agent",
"Mozilla/5.0 (compatible; Jarvis/1.0)"
)
async def fetch(
self,
url: str,
include_images: bool = True,
) -> Dict[str, Any]:
"""抓取网页"""
try:
result = await self._do_fetch(url, include_images)
return {
"status": "success",
"result": {
"url": result.url,
"title": result.title,
"content": result.content,
"images": result.images,
"status": result.status,
}
}
except Exception as e:
return {"status": "error", "error": str(e)}
async def _do_fetch(
self,
url: str,
include_images: bool,
) -> FetchResult:
"""实际抓取"""
import httpx
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.get(
url,
headers={"User-Agent": self.user_agent},
)
response.raise_for_status()
# TODO: 解析 HTML 提取内容
return FetchResult(
url=url,
title=None,
content=response.text,
images=[],
links=[],
status=response.status_code,
)
async def screenshot(
self,
url: str,
) -> Dict[str, Any]:
"""截取网页截图"""
# TODO: 接入截图服务
return {"status": "error", "error": "未实现"}
```
---
## 5. 任务管理工具
### 5.1 实现
```python
# tools/implementations/task_manager.py
from typing import Dict, Any, List, Optional
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
class TaskStatus(str, Enum):
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
@dataclass
class Task:
"""任务"""
id: str
name: str
description: str
status: TaskStatus = TaskStatus.PENDING
created_at: datetime = field(default_factory=datetime.utcnow)
scheduled_at: Optional[datetime] = None
result: Optional[Any] = None
error: Optional[str] = None
class TaskManager:
"""任务管理工具"""
def __init__(self, config: dict):
self._tasks: Dict[str, Task] = {}
async def create_task(
self,
name: str,
description: str,
scheduled_at: Optional[datetime] = None,
) -> Dict[str, Any]:
"""创建任务"""
import uuid
task_id = str(uuid.uuid4())[:8]
task = Task(
id=task_id,
name=name,
description=description,
scheduled_at=scheduled_at,
)
self._tasks[task_id] = task
return {
"status": "success",
"result": {
"id": task_id,
"name": task.name,
"status": task.status.value,
}
}
async def list_tasks(
self,
status: Optional[str] = None,
) -> Dict[str, Any]:
"""列出任务"""
tasks = list(self._tasks.values())
if status:
tasks = [t for t in tasks if t.status.value == status]
return {
"status": "success",
"result": [
{
"id": t.id,
"name": t.name,
"status": t.status.value,
"created_at": t.created_at.isoformat(),
}
for t in tasks
]
}
async def get_task(self, task_id: str) -> Dict[str, Any]:
"""获取任务"""
task = self._tasks.get(task_id)
if not task:
return {"status": "error", "error": "任务不存在"}
return {
"status": "success",
"result": {
"id": task.id,
"name": task.name,
"description": task.description,
"status": task.status.value,
"result": task.result,
"error": task.error,
}
}
async def complete_task(
self,
task_id: str,
result: Any,
) -> Dict[str, Any]:
"""完成任务"""
task = self._tasks.get(task_id)
if not task:
return {"status": "error", "error": "任务不存在"}
task.status = TaskStatus.COMPLETED
task.result = result
return {"status": "success"}
async def fail_task(
self,
task_id: str,
error: str,
) -> Dict[str, Any]:
"""标记任务失败"""
task = self._tasks.get(task_id)
if not task:
return {"status": "error", "error": "任务不存在"}
task.status = TaskStatus.FAILED
task.error = error
return {"status": "success"}
```
---
## 6. 实现步骤
| 步骤 | 任务 | 优先级 |
|------|------|--------|
| 1 | 实现 FileOperator | 🟢 高 |
| 2 | 实现 WebSearch | 🟡 中 |
| 3 | 实现 WebFetch | 🟡 中 |
| 4 | 实现 TaskManager | 🟡 中 |
| 5 | 创建 Manifest 文件 | 🟢 高 |
| 6 | 注册到工具中心 | 🟢 高 |
| 7 | 单元测试 | 🟡 中 |
---
## 7. 核心文件变更
| 文件 | 变更 |
|------|------|
| `tools/implementations/__init__.py` | 新增 |
| `tools/implementations/file_operator.py` | 新增 |
| `tools/implementations/web_search.py` | 新增 |
| `tools/implementations/web_fetch.py` | 新增 |
| `tools/implementations/task_manager.py` | 新增 |
| `tools/manifests/file_operator.yaml` | 更新 |
| `tools/manifests/web_search.yaml` | 新增 |
| `tools/manifests/web_fetch.yaml` | 新增 |
| `tools/manifests/task_manager.yaml` | 新增 |
---
## 8. 工作量估算
| 任务 | 工作量 |
|------|--------|
| FileOperator | 1.5 天 |
| WebSearch | 1 天 |
| WebFetch | 1 天 |
| TaskManager | 0.5 天 |
| Manifest + 注册 | 0.5 天 |
| 单元测试 | 0.5 天 |
| **总计** | **5 天** |
---
## 9. 验收标准
- [ ] FileOperator 可正确读写文件
- [ ] FileOperator 支持多种格式解析
- [ ] FileOperator 路径安全检查正常
- [ ] WebSearch 可执行搜索
- [ ] WebFetch 可抓取网页
- [ ] TaskManager 可管理任务
- [ ] 所有工具注册到工具中心
- [ ] 单元测试通过

View File

@@ -0,0 +1,642 @@
# Phase T.4:高级特性
日期2026-04-04
状态:待开始
依赖T.3(待完成)
---
## 1. 本阶段目的
实现 Jarvis 工具系统的高级特性:
- 多运行时支持Python/JS/原生)
- Agent 间协作
- 定时任务
---
## 2. 多运行时支持
### 2.1 运行时架构
```
┌─────────────────────────────────────────────────────────────┐
│ Runtime Manager │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Python │ │ JS │ │ Native │ │ WASM │ │
│ │ Runtime │ │ Runtime │ │ Runtime │ │ Runtime │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │ │
│ └─────────────┴──────┬──────┴─────────────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ │ Tool Executor │ │
│ │ (统一接口) │ │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 2.2 运行时基类
```python
# tools/runtime/base.py
from abc import ABC, abstractmethod
from typing import Any, Dict, Optional
class BaseRuntime(ABC):
"""运行时基类"""
@abstractmethod
async def execute(
self,
entry: str,
command: str,
parameters: Dict[str, Any],
timeout: int,
) -> Dict[str, Any]:
"""执行工具"""
pass
@abstractmethod
async def validate(self, entry: str) -> bool:
"""验证工具是否可用"""
pass
@abstractmethod
def get_name(self) -> str:
"""获取运行时名称"""
pass
```
### 2.3 Python 运行时
```python
# tools/runtime/python_runtime.py
import asyncio
from pathlib import Path
from tools.runtime.base import BaseRuntime
class PythonRuntime(BaseRuntime):
"""Python 运行时"""
def __init__(self):
self._executors: Dict[str, Callable] = {}
def get_name(self) -> str:
return "python"
async def validate(self, entry: str) -> bool:
path = Path(entry)
return path.exists() and path.suffix == ".py"
async def execute(
self,
entry: str,
command: str,
parameters: Dict[str, Any],
timeout: int,
) -> Dict[str, Any]:
# 动态加载并执行
# 或者通过 subprocess 调用
pass
```
### 2.4 JavaScript 运行时
```python
# tools/runtime/js_runtime.py
import asyncio
import subprocess
import json
from tools.runtime.base import BaseRuntime
class JavaScriptRuntime(BaseRuntime):
"""JavaScript 运行时"""
def __init__(self):
self.node_path = "node" # 可配置
def get_name(self) -> str:
return "javascript"
async def validate(self, entry: str) -> bool:
# 检查 node 是否可用
try:
result = await asyncio.create_subprocess_exec(
self.node_path, "--version",
stdout=asyncio.subprocess.PIPE,
)
return result.returncode == 0
except:
return False
async def execute(
self,
entry: str,
command: str,
parameters: Dict[str, Any],
timeout: int,
) -> Dict[str, Any]:
# 通过 stdio 调用 Node.js 脚本
input_data = json.dumps({
"command": command,
"parameters": parameters,
})
process = await asyncio.create_subprocess_exec(
self.node_path, entry,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await asyncio.wait_for(
process.communicate(input=input_data.encode()),
timeout=timeout / 1000,
)
if process.returncode != 0:
return {
"status": "error",
"error": stderr.decode(),
}
return json.loads(stdout.decode())
```
### 2.5 原生运行时
```python
# tools/runtime/native_runtime.py
import asyncio
import subprocess
from tools.runtime.base import BaseRuntime
class NativeRuntime(BaseRuntime):
"""原生二进制运行时"""
def get_name(self) -> str:
return "native"
async def validate(self, entry: str) -> bool:
from pathlib import Path
path = Path(entry)
return path.exists() and path.stat().st_mode & 0o111
async def execute(
self,
entry: str,
command: str,
parameters: Dict[str, Any],
timeout: int,
) -> Dict[str, Any]:
# 调用原生可执行文件
args = [entry, command] + self._format_args(parameters)
process = await asyncio.create_subprocess_exec(
*args,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await asyncio.wait_for(
process.communicate(),
timeout=timeout / 1000,
)
if process.returncode != 0:
return {
"status": "error",
"error": stderr.decode(),
}
return {
"status": "success",
"result": stdout.decode(),
}
def _format_args(self, parameters: Dict[str, Any]) -> list:
"""格式化参数"""
args = []
for key, value in parameters.items():
args.extend([f"--{key}", str(value)])
return args
```
### 2.6 运行时管理器
```python
# tools/runtime/manager.py
from tools.runtime.base import BaseRuntime
from tools.runtime.python_runtime import PythonRuntime
from tools.runtime.js_runtime import JavaScriptRuntime
from tools.runtime.native_runtime import NativeRuntime
class RuntimeManager:
"""运行时管理器"""
def __init__(self):
self._runtimes: Dict[str, BaseRuntime] = {
"python": PythonRuntime(),
"javascript": JavaScriptRuntime(),
"native": NativeRuntime(),
}
def get_runtime(self, name: str) -> BaseRuntime:
return self._runtimes.get(name)
async def execute(
self,
runtime_name: str,
entry: str,
command: str,
parameters: Dict[str, Any],
timeout: int,
) -> Dict[str, Any]:
runtime = self.get_runtime(runtime_name)
if not runtime:
return {
"status": "error",
"error": f"未知运行时: {runtime_name}",
}
return await runtime.execute(entry, command, parameters, timeout)
```
---
## 3. Agent 间协作
### 3.1 协作协议
```python
# agents/tools/collaboration.py
from typing import Dict, Any, Optional, List
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
class MessageType(str, Enum):
REQUEST = "request" # 请求协作
RESPONSE = "response" # 响应结果
PROGRESS = "progress" # 进度更新
CANCEL = "cancel" # 取消请求
@dataclass
class CollaborationMessage:
"""协作消息"""
id: str
type: MessageType
from_agent: str
to_agent: str
content: Any
metadata: Dict[str, Any]
timestamp: datetime = None
def __post_init__(self):
if self.timestamp is None:
self.timestamp = datetime.utcnow()
class CollaborationProtocol:
"""Agent 协作协议"""
def __init__(self):
self._pending_requests: Dict[str, CollaborationMessage] = {}
self._handlers: Dict[str, callable] = {}
def register_handler(self, tool_name: str, handler: callable) -> None:
"""注册工具处理器"""
self._handlers[tool_name] = handler
async def request_collaboration(
self,
from_agent: str,
to_agent: str,
tool_name: str,
parameters: Dict[str, Any],
timeout: int = 30000,
) -> Dict[str, Any]:
"""请求协作"""
import uuid
request_id = str(uuid.uuid4())
message = CollaborationMessage(
id=request_id,
type=MessageType.REQUEST,
from_agent=from_agent,
to_agent=to_agent,
content={
"tool": tool_name,
"parameters": parameters,
},
metadata={"timeout": timeout},
)
self._pending_requests[request_id] = message
# 发送请求
await self._send_message(message)
# 等待响应
try:
response = await self._wait_for_response(
request_id,
timeout,
)
return response
except TimeoutError:
return {
"status": "error",
"error": "协作请求超时",
}
async def handle_request(
self,
message: CollaborationMessage,
) -> CollaborationMessage:
"""处理协作请求"""
tool_name = message.content["tool"]
parameters = message.content["parameters"]
handler = self._handlers.get(tool_name)
if not handler:
return CollaborationMessage(
id=str(uuid.uuid4()),
type=MessageType.RESPONSE,
from_agent=message.to_agent,
to_agent=message.from_agent,
content={
"status": "error",
"error": f"未知工具: {tool_name}",
},
metadata={},
)
try:
result = await handler(**parameters)
return CollaborationMessage(
id=str(uuid.uuid4()),
type=MessageType.RESPONSE,
from_agent=message.to_agent,
to_agent=message.from_agent,
content={"status": "success", "result": result},
metadata={},
)
except Exception as e:
return CollaborationMessage(
id=str(uuid.uuid4()),
type=MessageType.RESPONSE,
from_agent=message.to_agent,
to_agent=message.from_agent,
content={"status": "error", "error": str(e)},
metadata={},
)
async def _send_message(self, message: CollaborationMessage) -> None:
"""发送消息"""
# TODO: 实现消息发送WebSocket/消息队列)
pass
async def _wait_for_response(
self,
request_id: str,
timeout: int,
) -> Dict[str, Any]:
"""等待响应"""
# TODO: 实现等待逻辑
pass
```
---
## 4. 定时任务
### 4.1 定时任务服务
```python
# tools/scheduler.py
import asyncio
from typing import Dict, Any, Callable, Optional
from datetime import datetime, timedelta
from dataclasses import dataclass, field
from enum import Enum
class ScheduleType(str, Enum):
ONCE = "once" # 单次
INTERVAL = "interval" # 间隔
CRON = "cron" # Cron 表达式
@dataclass
class ScheduledTask:
"""定时任务"""
id: str
name: str
schedule_type: ScheduleType
schedule_value: str # 时间/间隔/cron
tool_name: str
parameters: Dict[str, Any]
enabled: bool = True
last_run: Optional[datetime] = None
next_run: Optional[datetime] = None
run_count: int = 0
callback: Optional[Callable] = field(default=None)
class ToolScheduler:
"""工具定时调度器"""
def __init__(self):
self._tasks: Dict[str, ScheduledTask] = {}
self._running = False
self._loop_task = None
async def schedule(
self,
name: str,
schedule_type: ScheduleType,
schedule_value: str,
tool_name: str,
parameters: Dict[str, Any],
callback: Optional[Callable] = None,
) -> str:
"""创建定时任务"""
import uuid
task_id = str(uuid.uuid4())[:8]
task = ScheduledTask(
id=task_id,
name=name,
schedule_type=schedule_type,
schedule_value=schedule_value,
tool_name=tool_name,
parameters=parameters,
callback=callback,
)
task.next_run = self._calculate_next_run(task)
self._tasks[task_id] = task
# 启动调度器
if not self._running:
await self.start()
return task_id
def _calculate_next_run(self, task: ScheduledTask) -> datetime:
"""计算下次运行时间"""
now = datetime.utcnow()
if task.schedule_type == ScheduleType.ONCE:
return datetime.fromisoformat(task.schedule_value)
elif task.schedule_type == ScheduleType.INTERVAL:
seconds = int(task.schedule_value)
return now + timedelta(seconds=seconds)
elif task.schedule_type == ScheduleType.CRON:
# 解析 cron 表达式
# TODO: 实现 cron 解析
return now + timedelta(hours=1)
return now
async def start(self) -> None:
"""启动调度器"""
self._running = True
self._loop_task = asyncio.create_task(self._run_loop())
async def stop(self) -> None:
"""停止调度器"""
self._running = False
if self._loop_task:
self._loop_task.cancel()
async def _run_loop(self) -> None:
"""调度循环"""
while self._running:
now = datetime.utcnow()
for task in self._tasks.values():
if not task.enabled:
continue
if task.next_run and task.next_run <= now:
await self._execute_task(task)
await asyncio.sleep(1) # 每秒检查一次
async def _execute_task(self, task: ScheduledTask) -> None:
"""执行任务"""
# 调用工具
executor = get_executor(task.tool_name)
result = await executor(
command=task.parameters.get("command"),
parameters=task.parameters,
)
# 更新状态
task.last_run = datetime.utcnow()
task.run_count += 1
# 计算下次运行
if task.schedule_type != ScheduleType.ONCE:
task.next_run = self._calculate_next_run(task)
else:
task.enabled = False
# 调用回调
if task.callback:
await task.callback(task, result)
async def cancel(self, task_id: str) -> bool:
"""取消任务"""
if task_id in self._tasks:
del self._tasks[task_id]
return True
return False
async def list_tasks(self) -> list:
"""列出所有任务"""
return [
{
"id": t.id,
"name": t.name,
"type": t.schedule_type.value,
"enabled": t.enabled,
"next_run": t.next_run.isoformat() if t.next_run else None,
"run_count": t.run_count,
}
for t in self._tasks.values()
]
```
---
## 5. 实现步骤
| 步骤 | 任务 | 优先级 |
|------|------|--------|
| 1 | 实现运行时基类 | 🟢 高 |
| 2 | 实现 Python 运行时 | 🟢 高 |
| 3 | 实现 JS 运行时 | 🟡 中 |
| 4 | 实现原生运行时 | 🟡 中 |
| 5 | 实现运行时管理器 | 🟢 高 |
| 6 | 实现协作协议 | 🟡 中 |
| 7 | 实现定时调度器 | 🟡 中 |
| 8 | 单元测试 | 🟡 中 |
---
## 6. 核心文件变更
| 文件 | 变更 |
|------|------|
| `tools/runtime/__init__.py` | 新增 |
| `tools/runtime/base.py` | 新增 |
| `tools/runtime/python_runtime.py` | 新增 |
| `tools/runtime/js_runtime.py` | 新增 |
| `tools/runtime/native_runtime.py` | 新增 |
| `tools/runtime/manager.py` | 新增 |
| `agents/tools/collaboration.py` | 新增 |
| `tools/scheduler.py` | 新增 |
---
## 7. 工作量估算
| 任务 | 工作量 |
|------|--------|
| 运行时基类 | 0.5 天 |
| Python 运行时 | 0.5 天 |
| JS 运行时 | 0.5 天 |
| 原生运行时 | 0.5 天 |
| 运行时管理器 | 0.5 天 |
| 协作协议 | 1 天 |
| 定时调度器 | 0.5 天 |
| 单元测试 | 0.5 天 |
| **总计** | **4 天** |
---
## 8. 验收标准
- [ ] Python 运行时可正常执行工具
- [ ] JS 运行时可通过 stdio 调用
- [ ] 原生运行时可执行二进制
- [ ] 运行时管理器正确路由
- [ ] 协作协议可正常请求/响应
- [ ] 定时调度器可按计划执行任务
- [ ] 单元测试通过