# 工具表（Resource）与知识库对接文档

## 文档维护规范

0. **先改文档，再动代码** - 新功能或重大修改需先完成文档更新、并完成审阅后，再进行代码实现；除非改动较小、不被文档涵盖
1. **文档分层，链接代码** - 重要或复杂设计可以另有详细文档；关键实现需标注代码文件路径；格式：`module/file.py:function_name`
2. **简洁快照，日志分离** - 只记录最重要的、与代码准确对应的或者明确的已完成的设计的信息，避免推测、建议、决策历史、修改日志、大量代码；决策依据或修改日志若有必要，可在 `knowhub/docs/decisions.md` 另行记录

---

## 一、核心概念

知识库（KnowHub）中的工具表存储在 **Resource** 模块里，与 **Knowledge**（知识条目）通过双向索引关联。

| 模块 | 存储后端 | 主键格式 |
|------|----------|----------|
| Resource（工具表） | SQLite | `tools/{category}/{slug}` |
| Knowledge（知识） | Milvus（向量库） | `knowledge-{日期}-{随机}` |

---

## 二、Resource 数据结构

### 工具表字段

```json
{
  "id": "tools/plugin/ip_adapter",
  "title": "IP-Adapter",
  "body": "",
  "content_type": "text",
  "metadata": {
    "category": "plugin",
    "tool_slug": "ip_adapter",
    "status": "未接入",
    "version": "v1.0",
    "description": "22M参数的轻量适配器，通过CLIP ViT-H图像编码器实现图像提示",
    "usage": "scale=1.0用于纯图像提示，scale=0.5用于多模态（图像+文本）",
    "scenarios": ["人物一致性控制", "风格迁移", "图像引导生成"],
    "input": "参考图像 + 文本提示（可选）",
    "output": "生成图像",
    "source": "https://github.com/tencent-ailab/IP-Adapter",
    "knowledge_ids": ["knowledge-20260309-215835-a699"],
    "toolhub_items": [
      {"launch_comfy_env": "启动云端 ComfyUI Docker 环境，返回 server_id 和 comfy_url"},
      {"runcomfy_workflow_executor": "在已就绪的 RunComfy 机器上提交 ComfyUI 工作流，上传输入文件，监听执行状态，下载结果图片"},
      {"runcomfy_stop_env": "停止并删除 RunComfy 机器实例，释放资源"}
    ],
    "migrated_from": "knowhub_old"
  },
  "submitted_by": "migrate_script"
}
```

### metadata 字段说明

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `category` | string | ✅ | 分类：`image_gen` / `image_process` / `model` / `plugin` / `workflow` / `other` |
| `tool_slug` | string | ✅ | 小写英文短名，空格换下划线，如 `controlnet`、`ip_adapter` |
| `status` | string | ✅ | 接入状态：`未接入` / `已接入` / `测试中` |
| `version` | string\|null | — | 版本号 |
| `description` | string\|null | — | 一句话功能介绍 |
| `usage` | string\|null | — | 核心用法说明 |
| `scenarios` | array | — | 应用场景列表 |
| `input` | string\|null | — | 输入类型描述 |
| `output` | string\|null | — | 输出类型描述 |
| `source` | string\|null | — | 来源/文档链接 |
| `knowledge_ids` | array | — | 关联的知识 ID 列表（双向索引） |
| `toolhub_items` | array | — | 关联的 Tool Agent 工具列表，每项为 `{tool_id: description}`，表示该工具在 Tool Agent 中对应的已接入工具 |

### ID 命名规则

```
tools/{category}/{slug}

示例：
tools/plugin/ip_adapter
tools/plugin/controlnet
tools/image_gen/midjourney
tools/model/sdxl
```

---

## 三、双向索引机制

```
Resource（工具）                     Knowledge（知识）
┌─────────────────────────┐         ┌──────────────────────────────┐
│ id: tools/plugin/ip_adapter │◄────►│ id: knowledge-20260309-xxx   │
│ metadata.knowledge_ids:  │         │ resource_ids:                │
│   ["knowledge-xxx"]      │         │   ["tools/plugin/ip_adapter"] │
└─────────────────────────┘         │ tags: {"tool": true}         │
                                     └──────────────────────────────┘
```

- **Resource → Knowledge**：`metadata.knowledge_ids` 存储关联知识的 ID 列表
- **Knowledge → Resource**：`resource_ids` 存储关联工具的 ID 列表，同时打 `tags.tool = true` 标记

### 重要：双向索引是手动维护的

**目前没有任何自动同步机制**，两侧的索引相互独立存储，需要调用方自己负责保持一致。

两侧的写入时机：

| 方向 | 存在哪 | 写入时机 |
|------|--------|----------|
| Resource → Knowledge | SQLite `resources.metadata.knowledge_ids` | 创建/更新 Resource 时手动 PATCH |
| Knowledge → Resource | Milvus `resource_ids` 字段 | Knowledge 创建时传入，**创建后无法通过 API 修改** |

> Knowledge 的 `resource_ids` 字段目前**不在 PATCH 接口的支持范围内**，一旦 Knowledge 创建，只能通过删除重建来更改其关联的工具。

---

## 四、API 接口

服务地址由环境变量 `KNOWHUB_API` 指定（如 `http://localhost:9999`）。

### 4.1 工具表（Resource）

#### 创建工具
```http
POST /api/resource
Content-Type: application/json

{
  "id": "tools/plugin/controlnet",
  "title": "ControlNet",
  "body": "",
  "content_type": "text",
  "metadata": {
    "category": "plugin",
    "tool_slug": "controlnet",
    "status": "已接入",
    "description": "...",
    "usage": "...",
    "scenarios": [...],
    "knowledge_ids": []
  },
  "submitted_by": "your_name"
}
```

#### 更新工具（局部更新，推荐）
```http
PATCH /api/resource/tools/plugin/controlnet
Content-Type: application/json

{
  "metadata": {
    "status": "已接入",
    "knowledge_ids": ["knowledge-xxx", "knowledge-yyy"]
  }
}
```
> ⚠️ PATCH metadata 会**整体替换** metadata 字段，需带上所有已有字段。

#### 获取工具详情
```http
GET /api/resource/tools/plugin/controlnet
```

返回额外的导航字段：`toc`（根节点）、`children`（子节点）、`prev`/`next`（同级导航）。

#### 列出所有工具
```http
GET /api/resource?limit=1000
```

过滤出工具（客户端过滤）：
```javascript
const tools = results.filter(r => r.id.startsWith("tools/"));
```

#### 删除工具
```http
DELETE /api/resource/tools/plugin/controlnet
```

---

### 4.2 知识（Knowledge）

#### 搜索工具相关知识（语义搜索）
```http
GET /api/knowledge/search?q=IP-Adapter人物一致性&top_k=5&min_score=3
```

#### 列出所有工具相关知识
```http
GET /api/knowledge?tags=tool&status=approved,checked&page_size=200
```

#### 获取单条知识
```http
GET /api/knowledge/knowledge-20260309-215835-a699
```

#### 批量删除知识
```http
POST /api/knowledge/batch_delete
Content-Type: application/json

["knowledge-xxx", "knowledge-yyy"]
```

---

## 五、典型操作示例（Python）

### 新增一个工具并关联知识

```python
import httpx

KNOWHUB_API = "http://your-server:9999"

# 1. 创建工具
tool = {
    "id": "tools/plugin/controlnet",
    "title": "ControlNet",
    "body": "",
    "content_type": "text",
    "metadata": {
        "category": "plugin",
        "tool_slug": "controlnet",
        "status": "已接入",
        "version": "v1.1",
        "description": "基于条件图像控制SD生成的插件",
        "usage": "提供边缘图/骨骼图/深度图等条件，精确控制生成结果",
        "scenarios": ["姿势控制", "边缘控制", "深度控制"],
        "input": "条件图像（canny/pose/depth等）+ 文本提示",
        "output": "生成图像",
        "source": "https://github.com/lllyasviel/ControlNet",
        "knowledge_ids": []
    },
    "submitted_by": "your_name"
}

resp = httpx.post(f"{KNOWHUB_API}/api/resource", json=tool)
print(resp.json())  # {"status": "ok", "id": "tools/plugin/controlnet"}

# 2. 回填知识关联（PATCH 更新 knowledge_ids）
patch = {
    "metadata": {
        **tool["metadata"],  # 保留所有已有字段
        "knowledge_ids": ["knowledge-20260309-215835-a699"]
    }
}

resp = httpx.patch(f"{KNOWHUB_API}/api/resource/tools/plugin/controlnet", json=patch)
print(resp.json())  # {"status": "ok"}
```

### 更新工具接入状态

```python
# 先获取当前 metadata，再更新
resp = httpx.get(f"{KNOWHUB_API}/api/resource/tools/plugin/controlnet")
tool = resp.json()
meta = tool["metadata"]

meta["status"] = "已接入"

httpx.patch(
    f"{KNOWHUB_API}/api/resource/tools/plugin/controlnet",
    json={"metadata": meta}
)
```

---

## 六、前端查看

访问 KnowHub 管理界面，点击右上角 **🔧 工具表** 按钮：

- 按 `category` Tab 切换分类
- 左侧列表点击工具查看详情
- 详情页显示：基础概览 / 使用指南 / 技术规格 / 消息信源 / 关联知识（可跳转）
- 知识卡片底部有工具 Tag，点击可跳回工具表对应条目

---

## 七、注意事项

1. **PATCH metadata 是整体替换**：更新前先 GET 拿到当前值，合并后再 PATCH，避免覆盖已有字段（尤其是 `knowledge_ids`）。
2. **Resource ID 含斜杠**：URL 调用时需注意路径编码（框架已支持 `{resource_id:path}`，直接拼接即可，无需手动 encode）。
3. **双向索引需手动维护**：创建/删除工具时，需同步更新关联 Knowledge 的 `resource_ids` 字段（PATCH `/api/knowledge/{id}`）。
4. **工具筛选靠客户端**：`GET /api/resource` 返回所有 resource，需在客户端过滤 `id.startsWith("tools/")` 得到工具列表。
