643 lines
17 KiB
Markdown
643 lines
17 KiB
Markdown
# 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 调用
|
||
- [ ] 原生运行时可执行二进制
|
||
- [ ] 运行时管理器正确路由
|
||
- [ ] 协作协议可正常请求/响应
|
||
- [ ] 定时调度器可按计划执行任务
|
||
- [ ] 单元测试通过
|