Files
JARVIS/development-doc/plan/tool-update/phase-t-4-advanced.md

17 KiB
Raw Blame History

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 运行时基类

# 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 运行时

# 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 运行时

# 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 原生运行时

# 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 运行时管理器

# 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 协作协议

# 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 定时任务服务

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