refactor(kb-workflow): 重构为真正的编排者,使用sessions_spawn调用子agent

- kb_workflow.py: 重构为编排者,使用sessions_spawn调用子技能
- skill_main.py: 新增技能入口,处理用户消息
- SKILL.md: 更新文档,明确架构和流程
- SkillBug.md: 新增问题记录文档

设计原则:
- workflow不读文件内容,从文件名提取平台
- 使用sessions_spawn调用子agent
- 子技能自己定义输入输出格式
This commit is contained in:
OpenClaw Agent
2026-02-10 20:31:33 +08:00
parent 49ebbdff0a
commit 11c62dd383
4 changed files with 1049 additions and 1031 deletions
+176 -180
View File
@@ -13,7 +13,7 @@ KB Workflow 是一个完整的知识库管理系统,包含:
**核心功能**: **核心功能**:
- 📥 **收件箱扫描** - 扫描四个子目录 (pending, metadata, content, analyzed) - 📥 **收件箱扫描** - 扫描四个子目录 (pending, metadata, content, analyzed)
- 🔄 **内容处理** - 调用技能2-4处理内容 - 🔄 **内容处理** - 调用子agent处理各阶段
- 📦 **Git版本管理** - 归档后自动提交并推送 - 📦 **Git版本管理** - 归档后自动提交并推送
- 🔔 **订阅管理** - 跟踪订阅源更新 - 🔔 **订阅管理** - 跟踪订阅源更新
- 🧹 **自动清理** - 定期清理临时文件 - 🧹 **自动清理** - 定期清理临时文件
@@ -27,218 +27,213 @@ KB Workflow 是一个完整的知识库管理系统,包含:
| 触发词 | 行为 | 示例 | | 触发词 | 行为 | 示例 |
|--------|------|------| |--------|------|------|
| `保存到知识库` + 链接 | 添加到收件箱并处理 | "保存到知识库 https://www.bilibili.com/video/BVxxx" |
| `添加到知识库` + 链接 | 添加到收件箱并处理 | "添加到知识库 https://www.bilibili.com/video/BVxxx" | | `添加到知识库` + 链接 | 添加到收件箱并处理 | "添加到知识库 https://www.bilibili.com/video/BVxxx" |
| `订阅到知识库` + 链接 | 添加到订阅列表 | "订阅到知识库 https://www.bilibili.com/video/BVxxx" | | `保存到知识库` + 链接 | 添加到收件箱并处理 | "保存到知识库 https://www.bilibili.com/video/BVxxx" |
| `订阅知识库` + 链接 | 添加到订阅列表 | "订阅知识库 https://www.bilibili.com/video/BVxxx" |
### 支持的平台 ### 支持的平台
| 平台 | 保存 | 订阅 | | 平台 | 子技能 |
|------|------|------| |------|--------|
| Bilibili | ✅ | ✅ | | Bilibili | kb-bilibili-metadata, kb-bilibili-content |
| YouTube | ✅ | ❌ | | 知乎 | kb-zhihu-metadata |
| 知乎 | ✅ | ❌ |
## 架构设计
```
用户: "添加到知识库 https://www.bilibili.com/video/BV1ci4y1V7tb"
│
▼
┌─────────────────────────────────────────────────────────┐
│ kb-workflow (主代理) │
│ │
│ 职责: │
│ 1. 接收用户消息,识别触发词和链接 │
│ 2. 查重(根据URL/文件名) │
│ 3. 调用子agent处理各阶段 │
│ 4. 文件流转(mv) │
│ 5. Git版本管理 │
│ 6. 归档和清理 │
└─────────────────────────────────────────────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ kb-link-parser│ │kb-bilibili- │ │kb-bilibili- │ │ kb-deep- │
│ (子agent) │ │ metadata │ │ content │ │ analyzer │
│ │ │ (子agent) │ │ (子agent) │ │ (子agent) │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
```
## 处理流程
### 完整流程(6个阶段)
```
用户发送"添加到知识库 + 链接"
│
▼
kb-workflow:
1. 查重(根据URL/文件名)
2. 确认目录存在
3. 调用 kb-link-parser (sessions_spawn)
│
▼
kb-link-parser (子agent):
解析URL
输出: pending/bilibili_BV1ci4y1V7tb.link.json
返回: {"success": true/false, "output_file": "pending/..."}
│
▼
kb-workflow:
从文件名提取平台 "bilibili" → 调用 kb-bilibili-metadata
│
▼
kb-bilibili-metadata (子agent):
获取元数据
输出: metadata/bilibili_BV1ci4y1V7tb.meta.json
返回: {"success": true/false, "output_file": "metadata/..."}
│
▼
kb-workflow:
调用 kb-bilibili-content
│
▼
kb-bilibili-content (子agent):
下载视频+字幕
输出: content/downloads/*.mp4, *.srt
返回: {"success": true/false, "output_file": "...", "subtitles": "..."}
│
▼
kb-workflow:
拿到字幕文件 → 调用 kb-ocr-processor
│
▼
kb-ocr-processor (子agent):
OCR处理
输出: content/bilibili_BV1ci4y1V7tb.txt
返回: {"success": true/false, "output_file": "content/..."}
│
▼
kb-workflow:
拿到txt文件 → 调用 kb-deep-analyzer
│
▼
kb-deep-analyzer (子agent):
深度分析
输出: analyzed/bilibili_BV1ci4y1V7tb.md
返回: {"success": true/false, "output_file": "analyzed/..."}
│
▼
kb-workflow:
mv → archive/YYYY-MM-DD/
git add → commit → push
汇报完成
```
## 目录结构 ## 目录结构
``` ```
knowledge-base/ 知识库根目录 KB_BASE_PATH/
├── kb.config.yaml # 配置文件 ├── archive/ # Git归档目录
├── .gitignore # Git忽略规则 │ ├── YYYY-MM-DD/ # 按日期归档
├── pending/ # 待处理内容 (*.md) │ └── subscriptions/ # 订阅数据
├── metadata/ # 链接解析结果 (*.link.json) ├── pending/ # 链接解析结果 (*.link.json)
├── metadata/ # 元数据 (*.meta.json)
├── content/ ├── content/
│ ├── json/ # 元数据 (*.meta.json) │ ├── downloads/ # 视频文件 (*.mp4) - 7天后清理
│ ├── downloads/ # 视频文件 (*.mp4/*.flv) - 7天后清理 │ └── subtitles/ # 字幕文件 (*.srt) - 7天后清理
│ └── subtitles/ # 字幕文件 (*.srt/*.lrc) - 7天后清理 ├── frames/ # 视频截帧 (*.jpg) - 7天后清理
├── frames/ # 视频截帧 (*.jpg) - 7天后清理 └── analyzed/ # 分析报告 (*.md)
├── analyzed/ # 分析报告 (*.md)
└── archive/ # Git归档目录
├── YYYY-MM-DD/ # 按日期归档
└── subscriptions/ # 订阅数据
├── subscriptions.json # 订阅配置
└── processed.json # 已处理项目
``` ```
## Git版本管理 ## 设计原则
### 配置 ### workflow 职责
设置环境变量: | 操作 | 说明 |
```bash |------|------|
export KB_GIT_ROOT="/path/to/archive" # Git仓库目录 | 查重 | 根据URL/文件名查重 |
export KB_GIT_REMOTE="git@github.com:user/repo.git" # 远程仓库 | 创建目录 | 确保 base_path 下目录存在 |
export KB_GIT_USER="Username" # 用户名 | 调用子agent | 使用 sessions_spawn 调用子技能 |
export KB_GIT_EMAIL="user@example.com" # 邮箱 | 提取文件名 | 从子agent返回的output_file提取 |
| 提取平台 | 从文件名第一个下划线之前提取(不读内容) |
| 传递信息 | 给子agent提示词 + 需要的路径 |
| 文件流转 | 最后归档 |
| Git | commit + push |
### workflow 不管
- 不读文件内容
- 不关心子技能内部格式
- 子技能自己定义输入输出规范
### 子agent 调用方式
```python
# kb-workflow 调用子agent
result = sessions_spawn(
agentId="子技能名称",
task="提示词 + 需要的路径信息",
timeoutSeconds=120
)
``` ```
### 工作流程 ### 子技能返回格式
``` 所有子技能应返回JSON:
归档项目 → Git add → Git commit → Git push → 清理临时文件
```
- 每次归档生成一个commit
- 自动推送到远程仓库(重试3次)
- 汇报中体现Git状态
## 订阅管理
### 数据结构
```json ```json
{ {
"version": "1.0", "success": true,
"subscriptions": [ "output_file": "完整文件路径",
{ "subtitles": "字幕文件路径(可选)"
"id": "sub_001",
"platform": "bilibili",
"type": "series",
"identifier": "123456",
"name": "UE5教程",
"subscribed_at": "2026-02-10T12:00:00",
"last_checked": null,
"last_update": null,
"status": "active"
}
],
"processed_items": [
{
"id": "BVxxx",
"title": "视频标题",
"processed_at": "2026-02-10T14:00:00",
"archived": true
}
]
} }
``` ```
### 命令 ## 环境变量
```bash ```bash
# 添加订阅 export KB_BASE_PATH="/path/to/knowledge-base" # 知识库根目录
python kb_workflow.py --trigger subscriptions --add "bilibili" "series" "123456" "教程名称" export KB_GIT_ROOT="/path/to/archive" # Git仓库目录
export KB_GIT_REMOTE="git@github.com:user/repo.git" # 远程仓库
# 检查更新 export KB_GIT_USER="Username" # 用户名
python kb_workflow.py --trigger subscriptions --check export KB_GIT_EMAIL="user@example.com" # 邮箱
# 查看状态
python kb_workflow.py --trigger subscriptions --status
```
## 自动清理策略
### 清理规则
| 类型 | 保留 | 清理周期 |
|------|------|---------|
| 分析报告 | ✅ 永久 | - |
| 链接信息 | ✅ 永久 | - |
| 元数据 | ✅ 永久 | - |
| 订阅数据 | ✅ Git管理 | - |
| 视频文件 | ❌ | 7天 |
| 截帧文件 | ❌ | 7天 |
### 命令
```bash
# 清理30天前的临时文件
python kb_workflow.py --trigger cleanup --days 30
# 清理单个项目
python kb_workflow.py --trigger cleanup --project "项目名称"
``` ```
## Usage ## Usage
### 命令行用法 ### 作为 OpenClaw 技能
当用户发送消息时自动触发:
```
用户: "添加到知识库 https://www.bilibili.com/video/BVxxx"
```
### 作为子代理
```python
# 从其他技能调用
result = sessions_spawn(
agentId="kb-workflow",
task="添加到知识库 https://www.bilibili.com/video/BVxxx"
)
```
### 命令行
```bash ```bash
# 查看状态 # 处理单个URL
python kb_workflow.py --trigger status python kb_workflow.py --url "https://www.bilibili.com/video/BVxxx"
# 处理待处理内容
python kb_workflow.py --trigger manual --scope pending --limit 5
# 初始化Git
python kb_workflow.py --trigger git-init
# 查看Git状态
python kb_workflow.py --trigger git-status
# 清理过期文件
python kb_workflow.py --trigger cleanup --days 30
``` ```
### 参数说明 ## 子技能集成
| 参数 | 说明 | 默认值 | | 阶段 | 子技能 | 输出文件 |
|------|------|--------| |------|--------|----------|
| `--trigger` | 触发类型 | `status` | | 链接解析 | kb-link-parser | pending/{platform}_{id}.link.json |
| `--scope` | 处理范围 | `pending` | | 元数据 | kb-bilibili-metadata | metadata/{platform}_{id}.meta.json |
| `--limit` | 处理数量限制 | 无限制 | | 内容 | kb-bilibili-content | content/downloads/{platform}_{id}.mp4, content/subtitles/{platform}_{id}.srt |
| `--days` | 清理天数 | 30 | | OCR | kb-ocr-processor | content/{platform}_{id}.txt |
| 分析 | kb-deep-analyzer | analyzed/{platform}_{id}.md |
## Integration
### 技能2-4 集成
KB Workflow 协调以下子技能:
1. **Skill 1: Link Parser** - 解析 URL/链接
2. **Skill 2: Metadata** - 提取元数据
3. **Skill 3: Content** - 获取内容
4. **Skill 4: Deep Analyzer** - 深度分析
## Configuration
### 配置文件 (kb.config.yaml)
```yaml
git:
enabled: true
remote: "git@github.com:user/repo.git"
user: "Username"
email: "user@example.com"
cleanup:
video_retention_days: 7
frame_retention_days: 7
auto_cleanup: true
subscriptions:
check_interval_hours: 24
```
## Troubleshooting
### Git未初始化
```bash
# 检查配置
python kb_workflow.py --trigger git-status
# 初始化
export KB_GIT_ROOT="/path/to/archive"
python kb_workflow.py --trigger git-init
```
### 推送失败
- 检查网络连接
- 验证远程仓库权限
- 查看错误日志
### 存储空间过大
```bash
# 清理过期文件
python kb_workflow.py --trigger cleanup --days 7
# 查看存储使用情况
python kb_workflow.py --trigger status
```
## Related Skills ## Related Skills
@@ -246,3 +241,4 @@ python kb_workflow.py --trigger status
- **kb-bilibili-metadata** - B站元数据 - **kb-bilibili-metadata** - B站元数据
- **kb-bilibili-content** - B站内容获取 - **kb-bilibili-content** - B站内容获取
- **kb-deep-analyzer** - 深度分析 - **kb-deep-analyzer** - 深度分析
- **kb-ocr-processor** - OCR处理
+99
View File
@@ -0,0 +1,99 @@
# SkillBug.md
## 问题记录
### 2026-02-10: 缺少自动写入pending目录的功能
**状态:** ✅ 已修复
**解决方案:**
1. 创建 `skill_main.py` 作为技能入口
2. 创建 `kb_workflow.py` 实现完整流程
3. 接收用户消息后自动处理
---
### 2026-02-10: kb-workflow 自己实现链接解析而非调用子技能
**状态:** ✅ 已修复
**解决方案:**
重构 `kb_workflow.py`:
1. 使用 `sessions_spawn` 调用子agent
2. 不自己实现链接解析,调用 `kb-link-parser`
3. 从文件名提取平台(不读内容)
```python
# 调用子agent
result = sessions_spawn(
agentId="kb-link-parser",
task="解析URL,输出到pending目录"
)
```
---
### 2026-02-10: _extract_metadata 是空壳
**状态:** ✅ 已修复
**解决方案:**
实现真正的子agent调用:
```python
def call_metadata_agent(platform, link_file, metadata_dir):
agent_id = METADATA_AGENTS.get(platform) # bilibili → kb-bilibili-metadata
return call_subagent(agent_id, task, timeout=120)
```
---
## 重构总结
### 架构变化
**之前(错误):**
```
kb_workflow.py (自己造轮子)
├── _extract_link() - 自己解析URL
├── _extract_metadata() - 空壳
```
**之后(正确):**
```
kb-workflow (主代理)
├── sessions_spawn() 调用子agent
│ ├── kb-link-parser → pending/*.link.json
│ ├── kb-bilibili-metadata → metadata/*.meta.json
│ ├── kb-bilibili-content → content/downloads/*.mp4
│ ├── kb-ocr-processor → content/*.txt
│ └── kb-deep-analyzer → analyzed/*.md
└── 文件流转 + Git归档
```
### 设计原则
| workflow 管 | workflow 不管 |
|-------------|---------------|
| 查重 | 读文件内容 |
| 调用子agent | 子技能内部格式 |
| 提取文件名(从返回结果) | 子技能逻辑 |
| 提取平台(从文件名) | |
| 传递路径信息 | |
| Git归档 | |
### 文件变更
| 文件 | 操作 | 说明 |
|------|------|------|
| `kb_workflow.py` | 重构 | 使用 sessions_spawn 调用子agent |
| `skill_main.py` | 新增 | 技能入口 |
| `SKILL.md` | 更新 | 明确架构和流程 |
---
## 待办
- [ ] 测试完整的处理流程
- [ ] 验证 sessions_spawn 调用是否正常
- [ ] 完善错误处理
- [ ] 添加单元测试
File diff suppressed because it is too large Load Diff
+133
View File
@@ -0,0 +1,133 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
KB Workflow Skill Main Entry
当用户发送包含触发词的消息时自动触发:
- "添加到知识库 + 链接"
- "保存到知识库 + 链接"
职责:
1. 接收用户消息
2. 识别触发词和链接
3. 调用 kb_workflow.process_url 处理
4. 汇报结果
"""
import re
import sys
import json
from pathlib import Path
# 触发词模式
TRIGGER_PATTERNS = [
r'添加到知识库\s+(https?://[^\s]+)',
r'保存到知识库\s+(https?://[^\s]+)',
]
def extract_link(message: str) -> str | None:
"""从消息中提取链接"""
for pattern in TRIGGER_PATTERNS:
match = re.search(pattern, message)
if match:
return match.group(1)
return None
def main(message: str) -> Dict[str, Any]:
"""
主入口函数 - 当 kb-workflow 作为子代理被调用时执行
"""
result = {
"success": False,
"url": None,
"workflow_result": None,
"error": None
}
try:
# 1. 提取链接
url = extract_link(message)
if not url:
result["error"] = "未找到链接"
return result
result["url"] = url
# 2. 调用workflow处理
from kb_workflow import process_url
import os
base_path_str = os.environ.get('KB_BASE_PATH', '')
base_path = Path(base_path_str) if base_path_str else None
# 处理
workflow_result = process_url(url, base_path)
result["workflow_result"] = workflow_result
if workflow_result.get("success"):
result["success"] = True
except Exception as e:
result["error"] = str(e)
import traceback
traceback.print_exc()
return result
def print_result(result: Dict[str, Any]):
"""打印结果"""
print("\n" + "="*50)
print("KB Workflow Result")
print("="*50)
print(f"\nURL: {result.get('url', 'N/A')}")
wr = result.get('workflow_result', {})
if wr:
print(f"\n处理状态: {'成功' if wr.get('success') else '失败'}")
if wr.get('stages'):
print(f"\n处理阶段:")
for stage in wr['stages']:
stage_name = stage.get('stage', 'unknown')
print(f" ✓ {stage_name}")
if wr.get('final_file'):
print(f"\n最终文件: {wr['final_file']}")
archive = wr.get('archive', {})
if archive:
print(f"\n归档:")
print(f" 归档路径: {archive.get('archive_path', 'N/A')}")
print(f" Git提交: {'是' if archive.get('git_commit') else '否'}")
push = archive.get('git_push', {})
if push:
print(f" Git推送: {'成功' if push.get('success') else '失败'}")
if wr.get('error'):
print(f"\n错误: {wr['error']}")
if result.get('error'):
print(f"\n错误: {result['error']}")
print()
if __name__ == "__main__":
# 从参数获取消息(作为子代理时)
if len(sys.argv) > 1:
message = sys.argv[1]
else:
message = sys.stdin.read()
if not message:
print("请提供消息内容")
sys.exit(1)
result = main(message)
print_result(result)
sys.exit(0 if result.get('success') else 1)