# Phase T.1：Manifest 驱动系统

日期：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 验证器可捕获错误
- [ ] 单元测试覆盖核心逻辑
