自动同步: 2026-03-20 02:10

This commit is contained in:
2026-03-20 02:10:10 +08:00
parent d2a5e9a6ad
commit d9a5daca0a
67 changed files with 1526 additions and 220 deletions
+116 -123
View File
@@ -715,8 +715,8 @@
"memoryFlushCompactionCount": 0
},
"agent:haonan:cron:41396aeb-6e3a-47b2-9bd3-9156a348c1dc": {
"sessionId": "13cf89c7-ae35-4b61-935d-4b76542f1c28",
"updatedAt": 1773856800006,
"sessionId": "971b07ad-12bb-4041-879c-97906de4bf18",
"updatedAt": 1773943200005,
"systemSent": true,
"model": "glm-5",
"contextTokens": 200000,
@@ -757,11 +757,11 @@
"version": 0
},
"modelProvider": "zhipu",
"inputTokens": 10815,
"outputTokens": 343,
"totalTokens": 16891,
"inputTokens": 13549,
"outputTokens": 371,
"totalTokens": 16884,
"totalTokensFresh": true,
"cacheRead": 39360,
"cacheRead": 19840,
"cacheWrite": 0
},
"agent:haonan:cron:f03b184f-5f1d-4093-ae1b-fc9fe0b59eef": {
@@ -807,9 +807,9 @@
"version": 0
},
"modelProvider": "zhipu",
"inputTokens": 42108,
"outputTokens": 2001,
"totalTokens": 24584,
"inputTokens": 55959,
"outputTokens": 3146,
"totalTokens": 156912,
"deliveryContext": {
"channel": "webchat"
},
@@ -818,7 +818,7 @@
"sessionFile": "/home/starmen1265/.openclaw/agents/haonan/sessions/ccc40ca7-7876-4d82-ab84-7b790feddc19.jsonl",
"abortedLastRun": false,
"totalTokensFresh": true,
"cacheRead": 39613,
"cacheRead": 100953,
"cacheWrite": 0
},
"agent:haonan:cron:3245fca7-045b-4977-8fab-020f6df2e453": {
@@ -861,8 +861,8 @@
"cacheWrite": 0
},
"agent:haonan:cron:aa4d2de9-79be-4262-83ee-af8824793bfc": {
"sessionId": "26e8d6da-1d38-4fc7-9ddb-a1c14c23fde4",
"updatedAt": 1773857400007,
"sessionId": "3659e428-8612-437c-9c2b-1ce2f4a0b33d",
"updatedAt": 1773943800004,
"systemSent": true,
"label": "Cron: openclaw-git-sync",
"skillsSnapshot": {
@@ -910,113 +910,6 @@
"cacheRead": 32768,
"cacheWrite": 0
},
"agent:haonan:cron:f03b184f-5f1d-4093-ae1b-fc9fe0b59eef:run:742f547d-e84f-486c-99fe-66589180c4a7": {
"sessionId": "742f547d-e84f-486c-99fe-66589180c4a7",
"updatedAt": 1773771600017,
"systemSent": true,
"model": "glm-4.7-FlashX",
"contextTokens": 200000,
"label": "Cron: capability-evolver-daily",
"skillsSnapshot": {
"prompt": "\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n<available_skills>\n <skill>\n <name>capability-evolver</name>\n <description>A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.</description>\n <location>~/.openclaw/skills/capability-evolver/SKILL.md</location>\n </skill>\n <skill>\n <name>clawlist</name>\n <description>MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.</description>\n <location>~/.openclaw/skills/clawlist/SKILL.md</location>\n </skill>\n</available_skills>",
"skills": [
{
"name": "capability-evolver"
},
{
"name": "clawlist"
}
],
"skillFilter": [
"clawlist",
"capability-evolver",
"self-improving-agent"
],
"resolvedSkills": [
{
"name": "capability-evolver",
"description": "A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.",
"filePath": "/home/starmen1265/.openclaw/skills/capability-evolver/SKILL.md",
"baseDir": "/home/starmen1265/.openclaw/skills/capability-evolver",
"source": "openclaw-managed",
"disableModelInvocation": false
},
{
"name": "clawlist",
"description": "MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.",
"filePath": "/home/starmen1265/.openclaw/skills/clawlist/SKILL.md",
"baseDir": "/home/starmen1265/.openclaw/skills/clawlist",
"source": "openclaw-managed",
"disableModelInvocation": false
}
],
"version": 0
},
"modelProvider": "zhipu",
"inputTokens": 42108,
"outputTokens": 2001,
"totalTokens": 24584,
"deliveryContext": {
"channel": "webchat"
},
"lastChannel": "webchat",
"channel": "webchat",
"sessionFile": "/home/starmen1265/.openclaw/agents/haonan/sessions/ccc40ca7-7876-4d82-ab84-7b790feddc19.jsonl",
"abortedLastRun": false,
"totalTokensFresh": true,
"cacheRead": 39613,
"cacheWrite": 0
},
"agent:haonan:cron:41396aeb-6e3a-47b2-9bd3-9156a348c1dc:run:13cf89c7-ae35-4b61-935d-4b76542f1c28": {
"sessionId": "13cf89c7-ae35-4b61-935d-4b76542f1c28",
"updatedAt": 1773856800006,
"systemSent": true,
"model": "glm-5",
"contextTokens": 200000,
"label": "Cron: backup-openclaw-json",
"skillsSnapshot": {
"prompt": "\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n<available_skills>\n <skill>\n <name>capability-evolver</name>\n <description>A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.</description>\n <location>~/.openclaw/skills/capability-evolver/SKILL.md</location>\n </skill>\n <skill>\n <name>clawlist</name>\n <description>MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.</description>\n <location>~/.openclaw/skills/clawlist/SKILL.md</location>\n </skill>\n</available_skills>",
"skills": [
{
"name": "capability-evolver"
},
{
"name": "clawlist"
}
],
"skillFilter": [
"clawlist",
"capability-evolver",
"self-improving-agent"
],
"resolvedSkills": [
{
"name": "capability-evolver",
"description": "A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.",
"filePath": "/home/starmen1265/.openclaw/skills/capability-evolver/SKILL.md",
"baseDir": "/home/starmen1265/.openclaw/skills/capability-evolver",
"source": "openclaw-managed",
"disableModelInvocation": false
},
{
"name": "clawlist",
"description": "MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.",
"filePath": "/home/starmen1265/.openclaw/skills/clawlist/SKILL.md",
"baseDir": "/home/starmen1265/.openclaw/skills/clawlist",
"source": "openclaw-managed",
"disableModelInvocation": false
}
],
"version": 0
},
"modelProvider": "zhipu",
"inputTokens": 10815,
"outputTokens": 343,
"totalTokens": 16891,
"totalTokensFresh": true,
"cacheRead": 39360,
"cacheWrite": 0
},
"agent:haonan:cron:aa4d2de9-79be-4262-83ee-af8824793bfc:run:26e8d6da-1d38-4fc7-9ddb-a1c14c23fde4": {
"sessionId": "26e8d6da-1d38-4fc7-9ddb-a1c14c23fde4",
"updatedAt": 1773857400007,
@@ -1110,9 +1003,9 @@
"version": 0
},
"modelProvider": "zhipu",
"inputTokens": 42108,
"outputTokens": 2001,
"totalTokens": 24584,
"inputTokens": 55959,
"outputTokens": 3146,
"totalTokens": 156912,
"deliveryContext": {
"channel": "webchat"
},
@@ -1121,7 +1014,107 @@
"sessionFile": "/home/starmen1265/.openclaw/agents/haonan/sessions/ccc40ca7-7876-4d82-ab84-7b790feddc19.jsonl",
"abortedLastRun": false,
"totalTokensFresh": true,
"cacheRead": 39613,
"cacheRead": 100953,
"cacheWrite": 0
},
"agent:haonan:cron:41396aeb-6e3a-47b2-9bd3-9156a348c1dc:run:971b07ad-12bb-4041-879c-97906de4bf18": {
"sessionId": "971b07ad-12bb-4041-879c-97906de4bf18",
"updatedAt": 1773943200005,
"systemSent": true,
"model": "glm-5",
"contextTokens": 200000,
"label": "Cron: backup-openclaw-json",
"skillsSnapshot": {
"prompt": "\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n<available_skills>\n <skill>\n <name>capability-evolver</name>\n <description>A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.</description>\n <location>~/.openclaw/skills/capability-evolver/SKILL.md</location>\n </skill>\n <skill>\n <name>clawlist</name>\n <description>MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.</description>\n <location>~/.openclaw/skills/clawlist/SKILL.md</location>\n </skill>\n</available_skills>",
"skills": [
{
"name": "capability-evolver"
},
{
"name": "clawlist"
}
],
"skillFilter": [
"clawlist",
"capability-evolver",
"self-improving-agent"
],
"resolvedSkills": [
{
"name": "capability-evolver",
"description": "A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.",
"filePath": "/home/starmen1265/.openclaw/skills/capability-evolver/SKILL.md",
"baseDir": "/home/starmen1265/.openclaw/skills/capability-evolver",
"source": "openclaw-managed",
"disableModelInvocation": false
},
{
"name": "clawlist",
"description": "MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.",
"filePath": "/home/starmen1265/.openclaw/skills/clawlist/SKILL.md",
"baseDir": "/home/starmen1265/.openclaw/skills/clawlist",
"source": "openclaw-managed",
"disableModelInvocation": false
}
],
"version": 0
},
"modelProvider": "zhipu",
"inputTokens": 13549,
"outputTokens": 371,
"totalTokens": 16884,
"totalTokensFresh": true,
"cacheRead": 19840,
"cacheWrite": 0
},
"agent:haonan:cron:aa4d2de9-79be-4262-83ee-af8824793bfc:run:3659e428-8612-437c-9c2b-1ce2f4a0b33d": {
"sessionId": "3659e428-8612-437c-9c2b-1ce2f4a0b33d",
"updatedAt": 1773943800004,
"systemSent": true,
"label": "Cron: openclaw-git-sync",
"skillsSnapshot": {
"prompt": "\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n<available_skills>\n <skill>\n <name>capability-evolver</name>\n <description>A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.</description>\n <location>~/.openclaw/skills/capability-evolver/SKILL.md</location>\n </skill>\n <skill>\n <name>clawlist</name>\n <description>MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.</description>\n <location>~/.openclaw/skills/clawlist/SKILL.md</location>\n </skill>\n</available_skills>",
"skills": [
{
"name": "capability-evolver"
},
{
"name": "clawlist"
}
],
"skillFilter": [
"clawlist",
"capability-evolver",
"self-improving-agent"
],
"resolvedSkills": [
{
"name": "capability-evolver",
"description": "A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.",
"filePath": "/home/starmen1265/.openclaw/skills/capability-evolver/SKILL.md",
"baseDir": "/home/starmen1265/.openclaw/skills/capability-evolver",
"source": "openclaw-managed",
"disableModelInvocation": false
},
{
"name": "clawlist",
"description": "MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.",
"filePath": "/home/starmen1265/.openclaw/skills/clawlist/SKILL.md",
"baseDir": "/home/starmen1265/.openclaw/skills/clawlist",
"source": "openclaw-managed",
"disableModelInvocation": false
}
],
"version": 0
},
"modelProvider": "zhipu",
"model": "glm-5",
"contextTokens": 200000,
"inputTokens": 1734,
"outputTokens": 413,
"totalTokens": 18003,
"totalTokensFresh": true,
"cacheRead": 32768,
"cacheWrite": 0
}
}
@@ -0,0 +1,164 @@
# Session: 2026-03-19 15:00:36 UTC
- **Session Key**: agent:knowledge-base:main
- **Session ID**: 731aff7c-67da-45ff-957f-7940849d0aae
- **Source**: feishu
## Conversation Summary
user: Conversation info (untrusted metadata):
```json
{
"message_id": "om_x100b548be4a02ca4b33d43c7a3208be",
"sender_id": "ou_9351c287f42069fa30366ac641d24df7",
"sender": "张青",
"timestamp": "Thu 2026-03-19 21:31 GMT+8"
}
```
Sender (untrusted metadata):
```json
{
"label": "张青 (ou_9351c287f42069fa30366ac641d24df7)",
"id": "ou_9351c287f42069fa30366ac641d24df7",
"name": "张青"
}
```
[message_id: om_x100b548be4a02ca4b33d43c7a3208be]
张青: 记一下,可可想要一个里面有小人的公交车
assistant: 已记录到速记本:**可可想要一个里面有小人的公交车** (2026-03-19 21:31)
user: Conversation info (untrusted metadata):
```json
{
"message_id": "om_x100b548be25df0b0b22cdf708250b00",
"sender_id": "ou_9351c287f42069fa30366ac641d24df7",
"sender": "张青",
"timestamp": "Thu 2026-03-19 21:31 GMT+8"
}
```
Sender (untrusted metadata):
```json
{
"label": "张青 (ou_9351c287f42069fa30366ac641d24df7)",
"id": "ou_9351c287f42069fa30366ac641d24df7",
"name": "张青"
}
```
[message_id: om_x100b548be25df0b0b22cdf708250b00]
张青: 还要带语音控制的
assistant: 已更新:**可可想要一个里面有小人的公交车(带语音控制)** (2026-03-19 21:31)
user: Conversation info (untrusted metadata):
```json
{
"message_id": "om_x100b548be03aaca4b393638a734446f",
"sender_id": "ou_9351c287f42069fa30366ac641d24df7",
"sender": "张青",
"timestamp": "Thu 2026-03-19 21:32 GMT+8"
}
```
Sender (untrusted metadata):
```json
{
"label": "张青 (ou_9351c287f42069fa30366ac641d24df7)",
"id": "ou_9351c287f42069fa30366ac641d24df7",
"name": "张青"
}
```
[message_id: om_x100b548be03aaca4b393638a734446f]
张青: 还要能放音乐的
assistant: 已更新:**可可想要一个里面有小人的公交车(带语音控制、能放音乐)** (2026-03-19 21:31)
user: Conversation info (untrusted metadata):
```json
{
"message_id": "om_x100b548bfc0824a4b364c8c0c8977d1",
"sender_id": "ou_9351c287f42069fa30366ac641d24df7",
"sender": "张青",
"timestamp": "Thu 2026-03-19 21:33 GMT+8"
}
```
Sender (untrusted metadata):
```json
{
"label": "张青 (ou_9351c287f42069fa30366ac641d24df7)",
"id": "ou_9351c287f42069fa30366ac641d24df7",
"name": "张青"
}
```
[message_id: om_x100b548bfc0824a4b364c8c0c8977d1]
张青: 乐乐想要一个库洛米城堡,里面有很多摆件,这些摆件呢,可以动
assistant: 已记录:**乐乐想要一个库洛米城堡,里面有很多摆件,这些摆件可以动** (2026-03-19 21:33)
user: Conversation info (untrusted metadata):
```json
{
"message_id": "om_x100b548bfd70e48cb22d0cbc00050f8",
"sender_id": "ou_9351c287f42069fa30366ac641d24df7",
"sender": "张青",
"timestamp": "Thu 2026-03-19 21:33 GMT+8"
}
```
Sender (untrusted metadata):
```json
{
"label": "张青 (ou_9351c287f42069fa30366ac641d24df7)",
"id": "ou_9351c287f42069fa30366ac641d24df7",
"name": "张青"
}
```
[message_id: om_x100b548bfd70e48cb22d0cbc00050f8]
张青: 通过按按钮来控制里面的家具或者家电呃运动
assistant: 已更新:**乐乐想要一个库洛米城堡,里面有很多摆件,通过按按钮控制家具或家电运动** (2026-03-19 21:33)
user: Conversation info (untrusted metadata):
```json
{
"message_id": "om_x100b548bf82dbca0b2c751a1b709843",
"sender_id": "ou_9351c287f42069fa30366ac641d24df7",
"sender": "张青",
"timestamp": "Thu 2026-03-19 21:34 GMT+8"
}
```
Sender (untrusted metadata):
```json
{
"label": "张青 (ou_9351c287f42069fa30366ac641d24df7)",
"id": "ou_9351c287f42069fa30366ac641d24df7",
"name": "张青"
}
```
[message_id: om_x100b548bf82dbca0b2c751a1b709843]
张青: 可可还想要一个可以涂颜色的石膏娃娃
assistant: 已记录:**可可想要一个可以涂颜色的石膏娃娃** (2026-03-19 21:34)
user: Conversation info (untrusted metadata):
```json
{
"message_id": "om_x100b548b8e22dcacb2d8d587bb7b14e",
"sender_id": "ou_9351c287f42069fa30366ac641d24df7",
"sender": "张青",
"timestamp": "Thu 2026-03-19 21:37 GMT+8"
}
```
Sender (untrusted metadata):
```json
{
"label": "张青 (ou_9351c287f42069fa30366ac641d24df7)",
"id": "ou_9351c287f42069fa30366ac641d24df7",
"name": "张青"
}
```
[message_id: om_x100b548b8e22dcacb2d8d587bb7b14e]
张青: 嗯,可可想要一个空的城堡,里面有个绿色按钮。
绿色按钮按一下,城堡的顶上就会出现一个美乐蒂,再按一下按钮美乐蒂就会变成一朵花
assistant: 已记录:**可可想要一个空的城堡,里面有个绿色按钮:按一下城堡顶上出现美乐蒂,再按一下美乐蒂变成一朵花** (2026-03-19 21:37)
@@ -0,0 +1,67 @@
# Session: 2026-03-19 16:12:12 UTC
- **Session Key**: agent:knowledge-base:main
- **Session ID**: b11228d2-b3ca-444f-837c-c0a787d3a0cb
- **Source**: feishu
## Conversation Summary
assistant: ✅ New session started · model: zhipu/glm-5
user: A new session was started via /new or /reset. Execute your Session Startup sequence now - read the required files before responding to the user. Then greet the user in your configured persona, if one is provided. Be yourself - use your defined voice, mannerisms, and mood. Keep it to 1-3 sentences and ask what they want to do. If the runtime model differs from default_model in the system prompt, mention the default model. Do not mention internal steps, files, tools, or reasoning.
assistant: 📚 蒋先生您好,我是达康书记。已为您准备好知识库与工作区维护服务。
今天有什么需要我帮您处理的?
user: Conversation info (untrusted metadata):
```json
{
"message_id": "om_x100b54f5454c14a4c42e9b6c6ea2cd3",
"sender_id": "ou_9351c287f42069fa30366ac641d24df7",
"sender": "张青",
"timestamp": "Thu 2026-03-19 23:05 GMT+8"
}
```
Sender (untrusted metadata):
```json
{
"label": "张青 (ou_9351c287f42069fa30366ac641d24df7)",
"id": "ou_9351c287f42069fa30366ac641d24df7",
"name": "张青"
}
```
[message_id: om_x100b54f5454c14a4c42e9b6c6ea2cd3]
张青: https://www.bilibili.com/video/BV1NwwqzeE55/?t=6&spm_id_from=333.1007.tianma.1-1-1.click&vd_source=1deaab00c5de8885422a7a7690170eba 添加知识库
assistant: 收到,蒋先生。我立即按照知识库工作流技能处理这个B站视频链接。
assistant: 好的,蒋先生。我按照知识库工作流技能处理这个B站视频链接。
**步骤 1:检查环境变量**
assistant: 好的,蒋先生。工作流已经开始处理这个B站视频。我看到它正在:
1. ✅ 解析 URL
2. ⚠️ 字幕获取失败(可能视频没有字幕)
3. 🔄 正在进行 AI 深度分析...
让我继续监控进度:
assistant: ✅ **处理完成!**
**视频信息:**
- **标题:** 爱因斯坦又对了!科学家首次观测到物质从真空中诞生,【科学快报】265期
- **作者:** 科学透镜
- **BV号:** BV1NwwqzeE55
**处理流程:**
- ✅ Parse - URL 解析成功
- ✅ Download - 下载音频和元数据
- ✅ ASR - 语音识别完成(Whisper)
- ✅ Analyze - AI 深度分析完成(耗时 110.78秒)
- ✅ Archive - 归档成功
**核心内容摘要:**
本视频报道了三项颠覆性科学发现:
1. **物质从真空中诞生** - 布鲁克海文国家实验室首次观测到Lambda超子与反粒子的自旋纠缠,证实虚粒子可转化为真实物质
2. **电子运动延迟** - ETH测得电子响应原子核运动存在30飞秒延迟,打破波恩-奥本海默近似
3. **半莫比乌斯分子** - 人类首次合成并精准操控具有拓扑结构的分子
**归档位置:**
`~/work/zq/30.资源/AI分析/bilibili/20260319_BV1NwwqzeE55_analysis.md`
@@ -0,0 +1,8 @@
# 2026-03-19 每日记录
## 定时任务执行
### 02:30 - 每日回顾整理(daily-review-cleanup)
- 执行脚本:`~/.openclaw/cron/scripts/daily-review.sh`
- 处理日期:2026-03-18
- 结果:从 NAS 拉取更新完成,无已完成任务需要归档
+127 -13
View File
@@ -1,7 +1,7 @@
{
"agent:ui-designer:main": {
"sessionId": "4caf7900-b5bc-4f76-a39d-80e506bcea84",
"updatedAt": 1773758631721,
"sessionId": "60827bbf-d985-4a29-bd62-1783159bb38e",
"updatedAt": 1773937164358,
"systemSent": true,
"abortedLastRun": false,
"chatType": "direct",
@@ -22,7 +22,7 @@
"to": "user:ou_9847ce84b960736c14554730b41e4957",
"accountId": "feishu-art"
},
"sessionFile": "/home/starmen1265/.openclaw/agents/ui-designer/sessions/4caf7900-b5bc-4f76-a39d-80e506bcea84.jsonl",
"sessionFile": "/home/starmen1265/.openclaw/agents/ui-designer/sessions/60827bbf-d985-4a29-bd62-1783159bb38e.jsonl",
"compactionCount": 0,
"skillsSnapshot": {
"prompt": "\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n<available_skills>\n <skill>\n <name>capability-evolver</name>\n <description>A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.</description>\n <location>~/.openclaw/skills/capability-evolver/SKILL.md</location>\n </skill>\n</available_skills>",
@@ -51,8 +51,8 @@
"model": "glm-5",
"systemPromptReport": {
"source": "run",
"generatedAt": 1773758614151,
"sessionId": "4caf7900-b5bc-4f76-a39d-80e506bcea84",
"generatedAt": 1773937122951,
"sessionId": "60827bbf-d985-4a29-bd62-1783159bb38e",
"sessionKey": "agent:ui-designer:main",
"provider": "zhipu",
"model": "glm-5",
@@ -64,8 +64,8 @@
"sandboxed": false
},
"systemPrompt": {
"chars": 24499,
"projectContextChars": 12014,
"chars": 36876,
"projectContextChars": 24391,
"nonProjectContextChars": 12485
},
"injectedWorkspaceFiles": [
@@ -73,8 +73,8 @@
"name": "AGENTS.md",
"path": "/home/starmen1265/.openclaw/agents/ui-designer/workspace/AGENTS.md",
"missing": false,
"rawChars": 2617,
"injectedChars": 2617,
"rawChars": 2663,
"injectedChars": 2663,
"truncated": false
},
{
@@ -187,6 +187,120 @@
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
"injectedChars": 616,
"truncated": false
},
{
"path": "SELF_IMPROVEMENT_REMINDER.md",
"rawChars": 616,
@@ -414,13 +528,13 @@
]
}
},
"cacheRead": 9408,
"cacheRead": 23872,
"cacheWrite": 0,
"totalTokensFresh": true,
"contextTokens": 200000,
"inputTokens": 7026,
"outputTokens": 464,
"totalTokens": 16434
"inputTokens": 311770,
"outputTokens": 332,
"totalTokens": 170338
},
"agent:ui-designer:cron:78b64c2f-7f91-47ff-9250-d13f546eb21f": {
"sessionId": "03e44b46-c707-4a9f-bf3d-1818aafdcde2",
+28 -27
View File
@@ -7,7 +7,7 @@
"name": "backup-openclaw-json",
"enabled": true,
"createdAtMs": 1772261581796,
"updatedAtMs": 1773856817101,
"updatedAtMs": 1773943214562,
"schedule": {
"kind": "cron",
"expr": "0 2 * * *",
@@ -23,10 +23,10 @@
"mode": "announce"
},
"state": {
"nextRunAtMs": 1773943200000,
"lastRunAtMs": 1773856800004,
"nextRunAtMs": 1774029600000,
"lastRunAtMs": 1773943200004,
"lastStatus": "ok",
"lastDurationMs": 17097,
"lastDurationMs": 14558,
"consecutiveErrors": 0,
"lastRunStatus": "ok",
"lastDelivered": true,
@@ -59,7 +59,8 @@
"consecutiveErrors": 0,
"lastRunStatus": "ok",
"lastDeliveryStatus": "delivered",
"lastDelivered": true
"lastDelivered": true,
"runningAtMs": 1773943800002
},
"delivery": {
"mode": "announce"
@@ -71,7 +72,7 @@
"name": "capability-evolver-daily",
"enabled": true,
"createdAtMs": 1772550743528,
"updatedAtMs": 1773771643135,
"updatedAtMs": 1773858232095,
"schedule": {
"kind": "cron",
"expr": "20 2 * * *",
@@ -92,16 +93,16 @@
"to": "feishu-pm"
},
"state": {
"nextRunAtMs": 1773858000000,
"lastRunAtMs": 1773771600014,
"nextRunAtMs": 1773944400000,
"lastRunAtMs": 1773858000004,
"lastStatus": "error",
"lastDurationMs": 43121,
"consecutiveErrors": 5,
"lastDurationMs": 232091,
"consecutiveErrors": 6,
"lastRunStatus": "error",
"lastDeliveryStatus": "not-delivered",
"lastDelivered": false,
"lastDeliveryError": "⚠️ ✉️ Message: 蒋先生 failed",
"runningAtMs": 1773858000003
"lastDeliveryError": "⚠️ ✉️ Message failed",
"lastError": "⚠️ ✉️ Message failed"
}
},
{
@@ -111,7 +112,7 @@
"description": "每天凌晨2:30整理昨天的已完成任务到每日回顾",
"enabled": true,
"createdAtMs": 1741484880000,
"updatedAtMs": 1773772248827,
"updatedAtMs": 1773858626341,
"schedule": {
"kind": "cron",
"expr": "30 2 * * *",
@@ -129,11 +130,11 @@
"channel": "last"
},
"state": {
"nextRunAtMs": 1773858600000,
"lastRunAtMs": 1773772200010,
"nextRunAtMs": 1773945000000,
"lastRunAtMs": 1773858600004,
"lastRunStatus": "ok",
"lastStatus": "ok",
"lastDurationMs": 48817,
"lastDurationMs": 26337,
"lastDelivered": true,
"lastDeliveryStatus": "delivered",
"consecutiveErrors": 0
@@ -145,7 +146,7 @@
"name": "zq_workspace_sync",
"enabled": true,
"createdAtMs": 1772551807455,
"updatedAtMs": 1773772800011,
"updatedAtMs": 1773859200006,
"schedule": {
"expr": "40 2 * * *",
"kind": "cron",
@@ -158,8 +159,8 @@
"text": "执行 zq 工作区同步:bash ~/.openclaw/cron/scripts/zq_workspace_sync.sh"
},
"state": {
"nextRunAtMs": 1773859200000,
"lastRunAtMs": 1773772800010,
"nextRunAtMs": 1773945600000,
"lastRunAtMs": 1773859200005,
"lastStatus": "skipped",
"lastDurationMs": 1,
"consecutiveErrors": 0,
@@ -175,7 +176,7 @@
"description": "每天早上8点整理与各个 AI 代理的对话记录",
"enabled": true,
"createdAtMs": 1772776369645,
"updatedAtMs": 1773792029087,
"updatedAtMs": 1773878422623,
"schedule": {
"kind": "cron",
"expr": "0 8 * * *",
@@ -193,11 +194,11 @@
"channel": "last"
},
"state": {
"nextRunAtMs": 1773878400000,
"lastRunAtMs": 1773792000007,
"nextRunAtMs": 1773964800000,
"lastRunAtMs": 1773878400004,
"lastRunStatus": "ok",
"lastStatus": "ok",
"lastDurationMs": 29080,
"lastDurationMs": 22619,
"lastDelivered": true,
"lastDeliveryStatus": "delivered",
"consecutiveErrors": 0
@@ -210,7 +211,7 @@
"description": "每天7点同步提醒到CalDAV日历",
"enabled": true,
"createdAtMs": 1772791528528,
"updatedAtMs": 1773788430000,
"updatedAtMs": 1773874820139,
"schedule": {
"kind": "cron",
"expr": "0 7 * * *",
@@ -228,11 +229,11 @@
"channel": "last"
},
"state": {
"nextRunAtMs": 1773874800000,
"lastRunAtMs": 1773788400005,
"nextRunAtMs": 1773961200000,
"lastRunAtMs": 1773874800006,
"lastRunStatus": "ok",
"lastStatus": "ok",
"lastDurationMs": 29995,
"lastDurationMs": 20133,
"lastDeliveryStatus": "delivered",
"consecutiveErrors": 0,
"lastDelivered": true
+26 -26
View File
@@ -7,7 +7,7 @@
"name": "backup-openclaw-json",
"enabled": true,
"createdAtMs": 1772261581796,
"updatedAtMs": 1773856817101,
"updatedAtMs": 1773943214562,
"schedule": {
"kind": "cron",
"expr": "0 2 * * *",
@@ -23,10 +23,10 @@
"mode": "announce"
},
"state": {
"nextRunAtMs": 1773943200000,
"lastRunAtMs": 1773856800004,
"nextRunAtMs": 1774029600000,
"lastRunAtMs": 1773943200004,
"lastStatus": "ok",
"lastDurationMs": 17097,
"lastDurationMs": 14558,
"consecutiveErrors": 0,
"lastRunStatus": "ok",
"lastDelivered": true,
@@ -71,7 +71,7 @@
"name": "capability-evolver-daily",
"enabled": true,
"createdAtMs": 1772550743528,
"updatedAtMs": 1773771643135,
"updatedAtMs": 1773858232095,
"schedule": {
"kind": "cron",
"expr": "20 2 * * *",
@@ -92,16 +92,16 @@
"to": "feishu-pm"
},
"state": {
"nextRunAtMs": 1773858000000,
"lastRunAtMs": 1773771600014,
"nextRunAtMs": 1773944400000,
"lastRunAtMs": 1773858000004,
"lastStatus": "error",
"lastDurationMs": 43121,
"consecutiveErrors": 5,
"lastDurationMs": 232091,
"consecutiveErrors": 6,
"lastRunStatus": "error",
"lastDeliveryStatus": "not-delivered",
"lastDelivered": false,
"lastDeliveryError": "⚠️ ✉️ Message: 蒋先生 failed",
"lastError": "⚠️ ✉️ Message: 蒋先生 failed"
"lastDeliveryError": "⚠️ ✉️ Message failed",
"lastError": "⚠️ ✉️ Message failed"
}
},
{
@@ -111,7 +111,7 @@
"description": "每天凌晨2:30整理昨天的已完成任务到每日回顾",
"enabled": true,
"createdAtMs": 1741484880000,
"updatedAtMs": 1773772248827,
"updatedAtMs": 1773858626341,
"schedule": {
"kind": "cron",
"expr": "30 2 * * *",
@@ -129,11 +129,11 @@
"channel": "last"
},
"state": {
"nextRunAtMs": 1773858600000,
"lastRunAtMs": 1773772200010,
"nextRunAtMs": 1773945000000,
"lastRunAtMs": 1773858600004,
"lastRunStatus": "ok",
"lastStatus": "ok",
"lastDurationMs": 48817,
"lastDurationMs": 26337,
"lastDelivered": true,
"lastDeliveryStatus": "delivered",
"consecutiveErrors": 0
@@ -145,7 +145,7 @@
"name": "zq_workspace_sync",
"enabled": true,
"createdAtMs": 1772551807455,
"updatedAtMs": 1773772800011,
"updatedAtMs": 1773859200006,
"schedule": {
"expr": "40 2 * * *",
"kind": "cron",
@@ -158,8 +158,8 @@
"text": "执行 zq 工作区同步:bash ~/.openclaw/cron/scripts/zq_workspace_sync.sh"
},
"state": {
"nextRunAtMs": 1773859200000,
"lastRunAtMs": 1773772800010,
"nextRunAtMs": 1773945600000,
"lastRunAtMs": 1773859200005,
"lastStatus": "skipped",
"lastDurationMs": 1,
"consecutiveErrors": 0,
@@ -175,7 +175,7 @@
"description": "每天早上8点整理与各个 AI 代理的对话记录",
"enabled": true,
"createdAtMs": 1772776369645,
"updatedAtMs": 1773792029087,
"updatedAtMs": 1773878422623,
"schedule": {
"kind": "cron",
"expr": "0 8 * * *",
@@ -193,11 +193,11 @@
"channel": "last"
},
"state": {
"nextRunAtMs": 1773878400000,
"lastRunAtMs": 1773792000007,
"nextRunAtMs": 1773964800000,
"lastRunAtMs": 1773878400004,
"lastRunStatus": "ok",
"lastStatus": "ok",
"lastDurationMs": 29080,
"lastDurationMs": 22619,
"lastDelivered": true,
"lastDeliveryStatus": "delivered",
"consecutiveErrors": 0
@@ -210,7 +210,7 @@
"description": "每天7点同步提醒到CalDAV日历",
"enabled": true,
"createdAtMs": 1772791528528,
"updatedAtMs": 1773788430000,
"updatedAtMs": 1773874820139,
"schedule": {
"kind": "cron",
"expr": "0 7 * * *",
@@ -228,11 +228,11 @@
"channel": "last"
},
"state": {
"nextRunAtMs": 1773874800000,
"lastRunAtMs": 1773788400005,
"nextRunAtMs": 1773961200000,
"lastRunAtMs": 1773874800006,
"lastRunStatus": "ok",
"lastStatus": "ok",
"lastDurationMs": 29995,
"lastDurationMs": 20133,
"lastDeliveryStatus": "delivered",
"consecutiveErrors": 0,
"lastDelivered": true
@@ -0,0 +1,21 @@
{
"id": "bc3f3807-8735-4bef-bb42-3efd115c3f7e",
"enqueuedAt": 1773858231892,
"channel": "feishu",
"to": "feishu-pm",
"accountId": "feishu-pm",
"payloads": [
{
"text": "我遇到了 Feishu 目标格式的问题。让我直接在当前会话中向您汇报:"
}
],
"threadId": null,
"mirror": {
"sessionKey": "agent:haonan:main",
"agentId": "haonan",
"text": "我遇到了 Feishu 目标格式的问题。让我直接在当前会话中向您汇报:"
},
"retryCount": 1,
"lastAttemptAt": 1773858232086,
"lastError": "Request failed with status code 400"
}
+29 -3
View File
@@ -1,5 +1,31 @@
{
"om_x100b54b68a9658a8c3597587eaea165": 1773677638200,
"om_x100b54b69ae988acc4e308675a1599b": 1773677890863,
"om_x100b54a2c799a0b0c2ea37cee3ecec2": 1773758613782
"om_x100b5480d5bba4b4b2c9a4e8bf5b189": 1773881784103,
"om_x100b54808ff17c90b374531d1cf7751": 1773882388481,
"om_x100b5481441a38a4b20013813040b59": 1773883566093,
"om_x100b54816cbffcb8b36dfe46fd84a26": 1773883944086,
"om_x100b54817240a0a0b49ad1ecc4230d1": 1773884361569,
"om_x100b548114fc64a4b34dda488c6c4cf": 1773884835671,
"om_x100b548129129490b4b1770d7f3efcc": 1773885054622,
"om_x100b5481243380b0b10b5bef1b92ca3": 1773885104561,
"om_x100b54813d7144bcb2da847227b06a8": 1773885244462,
"om_x100b548139e42d10b3ffcf9447ed48c": 1773885299355,
"om_x100b548133fa3ca4b219d22fcf41e18": 1773885396226,
"om_x100b5481ce0418a0b26dc9b2f68f007": 1773885453209,
"om_x100b548c9b6dcc78b49c9ec428e44f3": 1773899098605,
"om_x100b548ca39e00acc3362a9bf3873de": 1773899477853,
"om_x100b548d47a538b0c2c282968797cea": 1773899927180,
"om_x100b5489ba663ca0c4c8bc1d9b97937": 1773920075261,
"om_x100b548a50d788b8b2c1f705241dbc5": 1773920738205,
"om_x100b548a72e7f4a8b286f0d7e9aea31": 1773921219213,
"om_x100b548a83211ca8c44bcbf00eb049e": 1773923551363,
"om_x100b548a95d45530c29580a9d1bad9a": 1773923762295,
"om_x100b548b1fa8c8a4c42e95eb381a75c": 1773925655047,
"om_x100b54f49f9f20b0b2d58df353d8a5b": 1773931798109,
"om_x100b54f51931bca0c361d933da09d2b": 1773933952255,
"om_x100b54f52b61f0a4c3650d280bf0dad": 1773934171242,
"om_x100b54f53b8678b0c3188a42d675405": 1773934421286,
"om_x100b54f533291ca4c2cf74f54da005d": 1773934558803,
"om_x100b54f5b9ebf8a0c33674c32a8da6c": 1773936499279,
"om_x100b54f64c3844acc14333ab25a59e5": 1773936688149,
"om_x100b54f650ef80a0c2d3c76a3fbe9be": 1773937122637
}
+16 -8
View File
@@ -1,10 +1,18 @@
{
"om_x100b54a3ae6304a0b28ab7519a1acf1": 1773764107460,
"om_x100b54a3a3782938b379c9a1da0010b": 1773764315828,
"om_x100b54a3b60634a0b2910ed3595f0aa": 1773764493305,
"om_x100b54955fb54964c4e180ded6a68b3": 1773801752225,
"om_x100b54955a75ccb4c11c3b4070aa4fd": 1773801804425,
"om_x100b54951cb128bcc361c4fb7da8c71": 1773802792386,
"om_x100b5498aaee84a0c451ce70c732daf": 1773850178765,
"om_x100b5498a8e104a8c37682c5c9f2e15": 1773850211313
"om_x100b548be4a02ca4b33d43c7a3208be": 1773927079628,
"om_x100b548be25df0b0b22cdf708250b00": 1773927113615,
"om_x100b548be03aaca4b393638a734446f": 1773927152061,
"om_x100b548bfc0824a4b364c8c0c8977d1": 1773927212950,
"om_x100b548bfd70e48cb22d0cbc00050f8": 1773927228439,
"om_x100b548bf82dbca0b2c751a1b709843": 1773927278588,
"om_x100b548b8e22dcacb2d8d587bb7b14e": 1773927439644,
"om_x100b54f4b7f294a0c45989f17b70fb7": 1773932436549,
"om_x100b54f5454c14a4c42e9b6c6ea2cd3": 1773932728615,
"om_x100b54f64b72d4b4c44dfcb4a5b55ab": 1773936732479,
"om_x100b54f644fc18a4c3e0e0a4d345c50": 1773936803575,
"om_x100b54f654df8534c4ee3346acd1bcd": 1773937057577,
"om_x100b54f6091f78a4c4d71668a805f7d": 1773937789758,
"om_x100b54f6cf31b4a0c453538202af189": 1773938720210,
"om_x100b54f6d65a48a4c4e7a9045781192": 1773939082098,
"om_x100b54f6d48980a0c2cc7e00d22cd8e": 1773939108667
}
+1 -1
View File
@@ -10,7 +10,7 @@
"operator.approvals",
"operator.pairing"
],
"updatedAtMs": 1773857416774
"updatedAtMs": 1773943213908
}
}
}
+2
View File
@@ -60,3 +60,5 @@
{"timestamp":"2026-03-17T16:06:09.290Z","action":"new","sessionKey":"agent:knowledge-base:main","senderId":"ou_9351c287f42069fa30366ac641d24df7","source":"feishu"}
{"timestamp":"2026-03-18T02:42:32.742Z","action":"new","sessionKey":"agent:knowledge-base:main","senderId":"ou_9351c287f42069fa30366ac641d24df7","source":"feishu"}
{"timestamp":"2026-03-18T16:09:39.151Z","action":"new","sessionKey":"agent:knowledge-base:main","senderId":"ou_9351c287f42069fa30366ac641d24df7","source":"feishu"}
{"timestamp":"2026-03-19T15:00:36.886Z","action":"new","sessionKey":"agent:knowledge-base:main","senderId":"ou_9351c287f42069fa30366ac641d24df7","source":"feishu"}
{"timestamp":"2026-03-19T16:12:12.817Z","action":"new","sessionKey":"agent:knowledge-base:main","senderId":"ou_9351c287f42069fa30366ac641d24df7","source":"feishu"}
+3
View File
@@ -12,3 +12,6 @@ export KB_BASE_PATH="~/work/knowledge-base"
# 可选: Git 配置
# export KB_GIT_USER="Your Name"
# export KB_GIT_EMAIL="your@email.com"
# 可选: 工作流行为配置
# export KB_OVERWRITE_ARCHIVES="false" # 非交互模式下是否覆盖已存在的归档文件(true/false)
Binary file not shown.
+5 -4
View File
@@ -49,7 +49,7 @@ def process_command(
from .pipeline.pipeline import Pipeline
from .pipeline.processors import (
ASRProcessor, AnalyzeProcessor, ArchiveProcessor,
DownloadProcessor, ParseProcessor
DownloadProcessor, ParseProcessor, SRTProcessor
)
from .pipeline.schemas import PipelineConfig
@@ -63,13 +63,14 @@ def process_command(
ParseProcessor(),
DownloadProcessor(),
ASRProcessor(),
AnalyzeProcessor(),
SRTProcessor(), # B站视频使用
AnalyzeProcessor(), # 知乎/文章使用
ArchiveProcessor(),
]
# Create pipeline config
config = PipelineConfig(
steps=["parse", "download", "asr", "analyze", "archive"],
steps=["parse", "download", "asr", "srt", "analyze", "archive"],
output_dir=out_dir,
skip_completed=skip_completed,
)
@@ -165,7 +166,7 @@ def login_command(
result = login.login_by_qrcode()
if result.success and result.cookie_str:
manager.save_cookie(result.cookie_str)
return result.model_dump()
return result.model_dump(mode='json')
except Exception as e:
return {"success": False, "error": str(e)}
+28 -8
View File
@@ -90,22 +90,35 @@ class SimpleAIClient:
def _call_zhipu(self, prompt: str, max_tokens: int, api_key: str, model: str, base_url: str) -> str:
import requests
import sys
base_url = base_url or "https://open.bigmodel.cn/api/coding/paas/v4"
url = f"{base_url}/chat/completions"
headers = {"Authorization": f"Bearer {api_key}"}
payload = {"model": model, "messages": [{"role": "user", "content": prompt}], "max_tokens": max_tokens}
# GLM-4.5/4.6/4.7/5 默认启用thinking mode,使用 thinking.type 参数禁用
payload = {
"model": model,
"messages": [{"role": "user", "content": prompt}],
"max_tokens": max_tokens,
"thinking": {
"type": "disabled" # 禁用思考模式,获取直接响应
}
}
response = requests.post(url, headers=headers, json=payload, timeout=600)
response.raise_for_status()
data = response.json()
message = data.get('choices', [{}])[0].get('message', {})
content = message.get('content', '')
reasoning = message.get('reasoning_content', '')
if not content and reasoning:
sentences = reasoning.strip().split('。')
useful = [s.strip() for s in sentences if s.strip() and len(s.strip()) > 10]
if useful:
return '。'.join(useful[-3:]) + '。'
return reasoning.strip()[:500]
if not content:
# 如果仍然为空,检查是否有reasoning_content(用于调试)
reasoning = message.get('reasoning_content', '')
if reasoning:
print(f"[DEBUG] Zhipu API返回空的content,但有reasoning_content。", file=sys.stderr)
print(f"[DEBUG] 这可能表示thinking.type参数未生效或模型不支持。", file=sys.stderr)
print(f"[DEBUG] reasoning_content前200字符: {reasoning[:200]}", file=sys.stderr)
raise ValueError(f"Zhipu API返回空内容。请检查API配置和模型参数。响应数据: {data}")
return content
def _call_minimax(self, prompt: str, max_tokens: int, api_key: str, model: str, base_url: str) -> str:
@@ -129,6 +142,13 @@ class DeepAnalyzer:
def __init__(self, ai_client: SimpleAIClient = None):
self.ai_client = ai_client or SimpleAIClient()
# 弃用警告:B站视频应使用 SRTProcessor
import logging
logger = logging.getLogger(__name__)
logger.warning(
"DeepAnalyzer is deprecated for Bilibili video processing. "
"Use SRTProcessor instead."
)
def analyze(self, metadata: Dict[str, Any], content: Dict[str, Any], content_type: str = "video") -> AnalysisResult:
"""一次性调用 AI 生成完整分析报告
+5
View File
@@ -58,6 +58,11 @@ class KBSettings(BaseSettings):
default=SecretStr(""), description="B站 Cookie (for video download)"
)
# 工作流行为配置
overwrite_archives: bool = Field(
default=False, description="覆盖已存在的归档文件(非交互模式)"
)
@lru_cache
def get_settings() -> KBSettings:
+46 -5
View File
@@ -21,7 +21,7 @@ from typing import Dict, List, Optional
from kb_workflow.pipeline.config import get_settings
from kb_workflow.pipeline.link_parser import LinkParser, ParsedLink
from kb_workflow.pipeline.processors.base import BaseProcessor
from kb_workflow.pipeline.schemas import PipelineConfig, PipelineInput, PipelineOutput
from kb_workflow.pipeline.schemas import PipelineConfig, PipelineInput, PipelineOutput, SRTInput
logger = logging.getLogger(__name__)
@@ -174,6 +174,20 @@ class Pipeline:
logger.info(f"Skipping ASR for text content type: {parsed.content_type}")
continue
# B站视频:跳过 analyze 步骤,使用 srt 步骤替代
is_bilibili_video = (
parsed.platform == "bilibili" and
parsed.content_type == "video"
)
if step_name == "analyze" and is_bilibili_video:
logger.info(f"Skipping analyze for Bilibili video, using SRT processor instead")
continue
# B站文章/知乎等:跳过 srt 步骤
if step_name == "srt" and not is_bilibili_video:
logger.info(f"Skipping SRT for non-Bilibili-video content: {parsed.platform}/{parsed.content_type}")
continue
processor = self.processors[step_name]
# Mark step as started
@@ -228,10 +242,26 @@ class Pipeline:
content_type=parsed.content_type,
output_file=str(content_dir / "analysis.md"),
)
elif step_name == "srt":
# SRT needs srt file from ASR step and metadata
download_result = results.get("download", {})
asr_result = results.get("asr", {})
metadata_file = download_result.get("metadata_file", "")
# SRT file from ASR output
srt_file = asr_result.get("output_files", {}).get("srt", "")
step_input = SRTInput(
content_id=parsed.content_id,
platform=parsed.platform,
srt_file=srt_file,
metadata_file=metadata_file,
output_file=str(content_dir / "analysis.md"),
content_type=parsed.content_type,
)
elif step_name == "archive":
# Archive needs analysis file from analyze step
analyze_result = results.get("analyze", {})
analysis_file = analyze_result.get("analysis_file", "")
# Archive needs analysis file from analyze step (or srt step for Bilibili video)
analyze_result = results.get("analyze", {}) or results.get("srt", {})
analysis_file = analyze_result.get("analysis_file", "") or analyze_result.get("output_file", "")
step_input = ArchiveInput(
content_id=parsed.content_id,
@@ -261,7 +291,7 @@ class Pipeline:
# Add output fields to results
for field_name in ["metadata_file", "content_file", "title", "author",
"analysis_file", "summary", "output_files",
"archive_file", "skipped"]:
"archive_file", "skipped", "output_file", "paragraphs", "tags", "corrected"]:
if hasattr(step_output, field_name):
results[step_name][field_name] = getattr(step_output, field_name)
@@ -397,3 +427,14 @@ class Pipeline:
"analysis_file": str(analysis_path),
}
logger.info(f"Found existing analysis: {analysis_path}")
elif step_name == "srt":
# Scan for SRT output (same file as analyze - analysis.md)
analysis_path = content_dir / "analysis.md"
if analysis_path.exists():
results["srt"] = {
"success": True,
"error_message": None,
"output_file": str(analysis_path),
}
logger.info(f"Found existing SRT output: {analysis_path}")
@@ -5,6 +5,7 @@ This module contains processors for various content transformation tasks:
- ParseProcessor: URL parsing for various platforms
- DownloadProcessor: Metadata and content downloading
- ASRProcessor: Audio-to-text conversion (subtitle download + Whisper.cpp)
- SRTProcessor: SRT subtitle processing (parse, segment, correct, markdown)
- AnalyzeProcessor: AI-powered content analysis using DeepAnalyzer
- ArchiveProcessor: Copy analysis reports to archive directory
"""
@@ -13,6 +14,7 @@ from kb_workflow.pipeline.processors.base import BaseProcessor
from kb_workflow.pipeline.processors.parse import ParseProcessor
from kb_workflow.pipeline.processors.download import DownloadProcessor
from kb_workflow.pipeline.processors.asr import ASRProcessor
from kb_workflow.pipeline.processors.srt import SRTProcessor
from kb_workflow.pipeline.processors.analyze import AnalyzeProcessor
from kb_workflow.pipeline.processors.archive import ArchiveProcessor
@@ -21,6 +23,7 @@ __all__ = [
"ParseProcessor",
"DownloadProcessor",
"ASRProcessor",
"SRTProcessor",
"AnalyzeProcessor",
"ArchiveProcessor",
]
@@ -46,6 +46,8 @@ class ArchiveProcessor(BaseProcessor):
Archive 处理结果
"""
try:
import sys
# Step 1: 检查是否已完成(幂等执行)
if self.state_manager:
state = self.state_manager.load()
@@ -73,7 +75,9 @@ class ArchiveProcessor(BaseProcessor):
platform_dir, input_data.content_id
)
if existing_file:
should_overwrite = self._ask_overwrite(existing_file)
# 检测是否为非交互式环境
is_non_interactive = not sys.stdin.isatty()
should_overwrite = self._ask_overwrite(existing_file, is_non_interactive)
if not should_overwrite:
logger.info(f"User chose not to overwrite, skipping archive")
return ArchiveOutput(
@@ -143,17 +147,28 @@ class ArchiveProcessor(BaseProcessor):
matches = list(platform_dir.glob(pattern))
return matches[0] if matches else None
def _ask_overwrite(self, existing_file: Path) -> bool:
def _ask_overwrite(self, existing_file: Path, is_non_interactive: bool = False) -> bool:
"""询问用户是否覆盖已存在的文件
使用 rich.prompt.Confirm 模式(与 Pipeline._ask_rerun_step 相同)
Args:
existing_file: 已存在的文件路径
is_non_interactive: 是否为非交互式环境(stdin不是tty)
Returns:
True 如果用户选择覆盖
"""
# 非交互式模式:使用配置决定
if is_non_interactive:
if self.settings.overwrite_archives:
logger.info(f"Non-interactive mode: Overwriting existing archive (KB_OVERWRITE_ARCHIVES=true): {existing_file.name}")
return True
else:
logger.info(f"Non-interactive mode: Skipping existing archive (KB_OVERWRITE_ARCHIVES=false): {existing_file.name}")
return False
# 交互式模式:询问用户
try:
from rich.console import Console
from rich.prompt import Confirm
@@ -0,0 +1,333 @@
"""SRTProcessor - B站字幕处理处理器
将 B站 SRT 字幕文件转换为简化的 Markdown 文档。
处理流程:
1. 解析 SRT 文件(调用 parser.parse_srt_file)
2. 语义分段(调用 segmenter.segment)
3. ASR 纠错(调用 corrector.correct,可选)
4. 生成 Markdown 文件
简化版:不做深度观点分析,只保留字幕内容和元数据。
"""
from __future__ import annotations
import json
import logging
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, List, Optional
from kb_workflow.pipeline.config import get_settings
from kb_workflow.pipeline.processors.base import BaseProcessor
from kb_workflow.pipeline.schemas import SRTInput, SRTOutput
from kb_workflow.pipeline.state import WorkflowStateManager
from kb_workflow.srt.parser import parse_srt_file
from kb_workflow.srt.segmenter import SemanticSegmenter
logger = logging.getLogger(__name__)
class SRTProcessor(BaseProcessor):
"""SRT 字幕处理器
将 SRT 字幕文件转换为简化的 Markdown 文档。
继承 BaseProcessor,遵循 Pipeline 处理器模式。
Attributes:
STEP_NAME: 步骤名称 "srt"
settings: KB 配置
state_manager: 状态管理器
segmenter: 语义分段器
corrector: ASR 纠错器(可选)
"""
STEP_NAME = "srt"
def __init__(
self,
state_manager: Optional[WorkflowStateManager] = None,
enable_correction: bool = True
):
"""初始化 SRT 处理器
Args:
state_manager: 状态管理器
enable_correction: 是否启用 ASR 纠错(默认 True)
"""
super().__init__(state_manager)
self.enable_correction = enable_correction
self.segmenter = SemanticSegmenter()
self.corrector = None
# 延迟初始化纠错器
if enable_correction:
try:
from kb_workflow.srt.corrector import ASRCorrector
from kb_workflow.deep_analyzer import SimpleAIClient
# 尝试创建 AI 客户端
ai_client = SimpleAIClient()
if ai_client.api_key:
self.corrector = ASRCorrector(ai_client=ai_client)
logger.info("ASR corrector initialized")
else:
logger.info("AI API key not configured, ASR correction disabled")
except Exception as e:
logger.warning(f"Failed to initialize ASR corrector: {e}")
async def process(self, input_data: SRTInput) -> SRTOutput:
"""处理 SRT 文件
Args:
input_data: SRT 输入数据
Returns:
SRT 处理结果
Raises:
Exception: 处理失败时抛出异常
"""
try:
# 记录开始状态
if self.state_manager:
state = self.state_manager.load()
if state:
state.mark_step_started(self.STEP_NAME)
self.state_manager.save(state)
# Step 1: 解析 SRT 文件
logger.info(f"Parsing SRT file: {input_data.srt_file}")
segments = parse_srt_file(Path(input_data.srt_file))
logger.info(f"Parsed {len(segments)} segments")
# Step 2: 语义分段
logger.info("Segmenting subtitles into paragraphs")
paragraphs = self.segmenter.segment(segments)
logger.info(f"Generated {len(paragraphs)} paragraphs")
# Step 3: ASR 纠错(可选)
corrected = False
if self.corrector and paragraphs:
logger.info("Applying ASR correction")
corrected_paragraphs = []
for para in paragraphs:
corrected_para = self.corrector.correct(para)
corrected_paragraphs.append(corrected_para)
if corrected_para != para:
corrected = True
paragraphs = corrected_paragraphs
logger.info(f"ASR correction applied: {corrected}")
# Step 4: 加载元数据
metadata = self._load_metadata(input_data.metadata_file)
# Step 5: 生成 Markdown
logger.info(f"Generating Markdown: {input_data.output_file}")
tags = self._extract_tags(paragraphs)
self._generate_markdown(
output_file=Path(input_data.output_file),
metadata=metadata,
paragraphs=paragraphs,
tags=tags
)
# 记录完成状态
if self.state_manager:
state = self.state_manager.load()
if state:
summary = f"paragraphs={len(paragraphs)}, corrected={corrected}"
state.mark_step_completed(self.STEP_NAME, summary)
self.state_manager.save(state)
return SRTOutput(
success=True,
output_file=str(input_data.output_file),
paragraphs=paragraphs,
tags=tags,
corrected=corrected
)
except Exception as e:
logger.error(f"SRT processing failed: {e}")
# 记录失败状态
if self.state_manager:
state = self.state_manager.load()
if state:
state.mark_step_failed(self.STEP_NAME, str(e))
self.state_manager.save(state)
raise
def _load_metadata(self, metadata_file: str) -> Dict[str, Any]:
"""加载元数据文件
Args:
metadata_file: 元数据文件路径
Returns:
元数据字典
"""
path = Path(metadata_file)
if not path.exists():
logger.warning(f"Metadata file not found: {metadata_file}")
return {}
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
def _extract_tags(self, paragraphs: List[str]) -> List[str]:
"""从段落中提取标签
简单实现:不使用 LLM,只返回默认标签。
未来可以增强为 LLM 提取。
Args:
paragraphs: 段落列表
Returns:
标签列表
"""
# 简单实现:返回空列表
# TODO: 可以使用 LLM 提取标签
return []
def _generate_markdown(
self,
output_file: Path,
metadata: Dict[str, Any],
paragraphs: List[str],
tags: List[str]
) -> None:
"""生成 Markdown 文件
Args:
output_file: 输出文件路径
metadata: 元数据
paragraphs: 段落列表
tags: 标签列表
"""
# 确保输出目录存在
output_file.parent.mkdir(parents=True, exist_ok=True)
# 生成 frontmatter
frontmatter = self._generate_frontmatter(metadata, tags)
# 构建内容
content_parts = [frontmatter, ""]
# 添加摘要(可选)
if paragraphs:
content_parts.append("## 摘要")
content_parts.append("")
# 使用第一段作为简单摘要
first_para = paragraphs[0][:200] if len(paragraphs[0]) > 200 else paragraphs[0]
content_parts.append(first_para + ("..." if len(paragraphs[0]) > 200 else ""))
content_parts.append("")
# 添加内容
content_parts.append("## 内容")
content_parts.append("")
for para in paragraphs:
content_parts.append(para)
content_parts.append("")
# 写入文件
output_file.write_text('\n'.join(content_parts), encoding='utf-8')
logger.info(f"Markdown file generated: {output_file}")
def _generate_frontmatter(self, metadata: Dict[str, Any], tags: List[str]) -> str:
"""生成 YAML frontmatter
Args:
metadata: 元数据
tags: 标签列表
Returns:
YAML frontmatter 字符串
"""
lines = ["---"]
# 标题
title = metadata.get('title', '未命名')
lines.append(f'title: "{self._escape_yaml(title)}"')
# 日期
pubtime = metadata.get('pubtime', '') or metadata.get('published_at', '')
if pubtime:
date_str = str(pubtime)[:10]
else:
date_str = datetime.now().strftime('%Y-%m-%d')
lines.append(f'date: {date_str}')
# 作者
author = metadata.get('author', '未知')
lines.append(f'author: {self._escape_yaml(author)}')
# 平台
platform = metadata.get('platform', 'bilibili')
lines.append(f'platform: {platform}')
# 时长(视频)
duration = metadata.get('duration', 0)
if duration:
duration_str = self._format_duration(duration)
lines.append(f'duration: {duration_str}')
# BVID
bvid = metadata.get('bvid', '')
if bvid:
lines.append(f'bvid: {bvid}')
# URL
url = metadata.get('url', '')
if url:
lines.append(f'url: {url}')
# 标签
if tags:
tags_str = ', '.join(f'"{t}"' for t in tags)
lines.append(f'tags: [{tags_str}]')
lines.append("---")
return '\n'.join(lines)
def _escape_yaml(self, text: str) -> str:
"""转义 YAML 特殊字符
Args:
text: 原始文本
Returns:
转义后的文本
"""
# 移除零宽度字符
invisible_chars = ['\u200b', '\u200c', '\u200d', '\ufeff', '\u00ad']
for char in invisible_chars:
text = text.replace(char, '')
# 移除换行符
text = text.replace('\n', ' ').strip()
# 如果包含特殊字符,用引号包裹
if any(c in text for c in [':', '"', "'", '\n', '#']):
text = text.replace('"', '\\"')
return text
return text
def _format_duration(self, seconds: int) -> str:
"""格式化时长
Args:
seconds: 秒数
Returns:
格式化的时长字符串 (HH:MM:SS 或 MM:SS)
"""
h = seconds // 3600
m = (seconds % 3600) // 60
s = seconds % 60
if h > 0:
return f"{h}:{m:02d}:{s:02d}"
return f"{m}:{s:02d}"
+39
View File
@@ -444,3 +444,42 @@ class ArchiveOutput(StepOutput):
archive_file: str = Field(default="", description="Path to archived file")
skipped: bool = Field(default=False, description="Whether archiving was skipped")
platform: str = Field(default="", description="Platform name")
# =============================================================================
# SRT Schemas (Phase 12)
# =============================================================================
class SRTInput(StepInput):
"""SRT 步骤输入
用于 SRTProcessor 处理 B站字幕。
Attributes:
srt_file: SRT 字幕文件路径
metadata_file: 元数据 JSON 文件路径
output_file: 输出 Markdown 文件路径
content_type: 内容类型(默认 video)
"""
srt_file: str = Field(..., min_length=1, description="Path to SRT subtitle file")
metadata_file: str = Field(..., min_length=1, description="Path to metadata JSON file")
output_file: str = Field(..., min_length=1, description="Path to output Markdown file")
content_type: str = Field(default="video", description="Content type")
class SRTOutput(StepOutput):
"""SRT 步骤输出
继承 StepOutput 的基础字段,添加 SRT 处理特定字段。
Attributes:
output_file: 生成的 Markdown 文件路径
paragraphs: 生成的段落列表
tags: 提取的标签列表
corrected: 是否进行了 ASR 纠错
"""
output_file: str = Field(..., description="Path to generated Markdown file")
paragraphs: List[str] = Field(default_factory=list, description="Generated paragraphs")
tags: List[str] = Field(default_factory=list, description="Extracted tags")
corrected: bool = Field(default=False, description="Whether ASR correction was applied")
+27
View File
@@ -0,0 +1,27 @@
"""SRT Processing Module
B站字幕处理模块,包含:
- SRT 解析器 (parser)
- 语义分段器 (segmenter)
- ASR 纠错器 (corrector)
"""
from kb_workflow.srt.parser import parse_srt, parse_srt_file, Segment
__all__ = [
"parse_srt",
"parse_srt_file",
"Segment",
"SemanticSegmenter",
"ASRCorrector",
]
# 延迟导入,避免循环依赖
def __getattr__(name: str):
if name == "SemanticSegmenter":
from kb_workflow.srt.segmenter import SemanticSegmenter
return SemanticSegmenter
if name == "ASRCorrector":
from kb_workflow.srt.corrector import ASRCorrector
return ASRCorrector
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+157
View File
@@ -0,0 +1,157 @@
"""ASR Corrector Module
使用 LLM 纠正语音识别错误。复用 SimpleAIClient。
功能:
- 纠正明显的语音识别错误(同音字、漏字、错字)
- 分批处理长文本
- AI 不可用时返回原文
"""
from __future__ import annotations
import logging
from typing import Optional, Protocol
logger = logging.getLogger(__name__)
class AIClientProtocol(Protocol):
"""AI 客户端协议"""
def generate(self, prompt: str, max_tokens: int = 16000) -> str:
"""生成文本"""
...
# 纠错 Prompt 模板
CORRECTION_PROMPT = """你是一个文本校对专家。以下是语音识别生成的字幕,可能包含识别错误。
请只纠正明显的错误(如同音字、漏字、错字),不要改变原意和风格。
原文:
{text}
纠正后的文本(只输出纠正后的文本,不要解释):"""
class ASRCorrector:
"""ASR 纠错器
使用 LLM 纠正语音识别中的明显错误。
Attributes:
ai_client: AI 客户端(必须实现 generate 方法)
batch_size: 分批处理的字符数,默认 5000
"""
def __init__(
self,
ai_client: Optional[AIClientProtocol] = None,
batch_size: int = 5000
):
"""初始化纠错器
Args:
ai_client: AI 客户端实例,如果为 None 则不进行纠错
batch_size: 分批处理的字符数
"""
self.ai_client = ai_client
self.batch_size = batch_size
def correct(self, text: str) -> str:
"""纠正文本中的语音识别错误
Args:
text: 原始文本
Returns:
纠正后的文本。如果 AI 不可用或文本为空,返回原文。
Example:
>>> corrector = ASRCorrector(ai_client=my_client)
>>> result = corrector.correct("什么是机器学习")
>>> print(result)
什么是机器学习
"""
# 空文本直接返回
if not text or not text.strip():
return text or ""
# AI 不可用,返回原文
if self.ai_client is None:
logger.info("AI client not available, returning original text")
return text
# 短文本直接处理
if len(text) <= self.batch_size:
return self._correct_batch(text)
# 长文本分批处理
return self._correct_long_text(text)
def _correct_batch(self, text: str) -> str:
"""处理单个批次的文本
Args:
text: 文本内容
Returns:
纠正后的文本
"""
try:
prompt = CORRECTION_PROMPT.format(text=text)
result = self.ai_client.generate(prompt, max_tokens=len(text) + 500)
return result.strip()
except Exception as e:
logger.warning(f"ASR correction failed: {e}, returning original text")
return text
def _correct_long_text(self, text: str) -> str:
"""分批处理长文本
按句子分割,每批不超过 batch_size 字符。
Args:
text: 长文本内容
Returns:
纠正后的文本
"""
# 按句子分割(简单分割,基于句号、问号、感叹号)
sentences = []
current = ""
for char in text:
current += char
if char in '。!?\n':
sentences.append(current)
current = ""
# 添加最后一个不完整的句子
if current:
sentences.append(current)
# 分批处理
batches = []
current_batch = []
current_length = 0
for sentence in sentences:
if current_length + len(sentence) > self.batch_size:
if current_batch:
batches.append(''.join(current_batch))
current_batch = [sentence]
current_length = len(sentence)
else:
current_batch.append(sentence)
current_length += len(sentence)
if current_batch:
batches.append(''.join(current_batch))
# 处理每个批次
corrected_batches = []
for batch in batches:
corrected = self._correct_batch(batch)
corrected_batches.append(corrected)
return ''.join(corrected_batches)
+161
View File
@@ -0,0 +1,161 @@
"""SRT Parser Module
解析 SRT 字幕文件格式,将字幕转换为结构化的 Segment 对象。
SRT 格式示例:
1
00:00:00,000 --> 00:00:05,000
第一句字幕内容
2
00:00:05,000 --> 00:00:10,000
第二句字幕内容
"""
from __future__ import annotations
import re
from dataclasses import dataclass
from pathlib import Path
from typing import List
@dataclass
class Segment:
"""字幕分段数据结构
Attributes:
id: 字幕序号
start_time: 开始时间(秒)
end_time: 结束时间(秒)
text: 字幕文本
"""
id: int
start_time: float
end_time: float
text: str
# SRT 块正则表达式
# 格式: 序号\n时间码\n文本(可能多行)\n\n
SRT_BLOCK_PATTERN = re.compile(
r'(\d+)\s*\n'
r'(\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2},\d{3})\s*\n'
r'(.*?)(?=\n\n|\Z)',
re.DOTALL
)
def parse_srt_time(time_str: str) -> float:
"""解析 SRT 时间格式 (HH:MM:SS,mmm) 为秒数
Args:
time_str: SRT 时间字符串,如 "00:05:30,500"
Returns:
秒数,如 330.5
Example:
>>> parse_srt_time("00:05:30,500")
330.5
>>> parse_srt_time("01:02:03,004")
3723.004
"""
# 分割时间和毫秒
main_part, millis = time_str.rsplit(',', 1)
hours, minutes, seconds = main_part.split(':')
total_seconds = (
int(hours) * 3600 +
int(minutes) * 60 +
int(seconds) +
int(millis) / 1000
)
return total_seconds
def parse_srt(content: str) -> List[Segment]:
"""解析 SRT 格式字符串
将 SRT 格式的字幕内容解析为 Segment 对象列表。
Args:
content: SRT 格式的字符串内容
Returns:
Segment 对象列表
Raises:
ValueError: 内容为空或格式无效
Example:
>>> content = "1\\n00:00:00,000 --> 00:00:05,000\\n第一句\\n\\n"
>>> segments = parse_srt(content)
>>> len(segments)
1
>>> segments[0].text
'第一句'
"""
# 检查空内容
if not content or not content.strip():
raise ValueError("Empty SRT content")
segments = []
# 查找所有 SRT 块
matches = SRT_BLOCK_PATTERN.findall(content)
if not matches:
raise ValueError("Invalid SRT format: no valid blocks found")
for match in matches:
try:
segment_id = int(match[0])
start_time = parse_srt_time(match[1])
end_time = parse_srt_time(match[2])
# 清理文本:移除前后空白,合并多行为单行
text = ' '.join(match[3].strip().split())
segment = Segment(
id=segment_id,
start_time=start_time,
end_time=end_time,
text=text
)
segments.append(segment)
except (ValueError, IndexError) as e:
# 跳过解析失败的块
continue
if not segments:
raise ValueError("Invalid SRT format: no valid segments parsed")
return segments
def parse_srt_file(file_path: Path) -> List[Segment]:
"""从文件解析 SRT 格式
读取文件内容并解析为 Segment 对象列表。
Args:
file_path: SRT 文件路径
Returns:
Segment 对象列表
Raises:
FileNotFoundError: 文件不存在
ValueError: 内容格式无效
Example:
>>> from pathlib import Path
>>> segments = parse_srt_file(Path("subtitles.srt"))
>>> len(segments)
10
"""
if not file_path.exists():
raise FileNotFoundError(f"SRT file not found: {file_path}")
content = file_path.read_text(encoding='utf-8')
return parse_srt(content)
+97
View File
@@ -0,0 +1,97 @@
"""Semantic Segmenter Module
将字幕分段按语义合并为段落。基于简单启发式规则:
1. 时间间隔超过阈值时分段
2. 段落长度限制
不使用 LLM 进行分段判断,保持简单和快速。
"""
from __future__ import annotations
from typing import List
from kb_workflow.srt.parser import Segment
class SemanticSegmenter:
"""语义分段器
将字幕分段转换为语义段落。基于时间和长度规则:
- 长时间停顿(>gap_threshold 秒)作为分段点
- 段落长度限制(max_paragraph_chars 字符)
Attributes:
gap_threshold: 时间间隔阈值(秒),默认 3.0
max_paragraph_chars: 单段落最大字符数,默认 500
"""
def __init__(
self,
gap_threshold: float = 3.0,
max_paragraph_chars: int = 500
):
"""初始化分段器
Args:
gap_threshold: 时间间隔阈值(秒),超过此值时分段
max_paragraph_chars: 单段落最大字符数
"""
self.gap_threshold = gap_threshold
self.max_paragraph_chars = max_paragraph_chars
def segment(self, segments: List[Segment]) -> List[str]:
"""将字幕分段转换为语义段落
Args:
segments: 字幕分段列表
Returns:
段落文本列表(已去除时间戳)
Example:
>>> segmenter = SemanticSegmenter()
>>> segments = [Segment(1, 0, 5, "第一句"), Segment(2, 5, 10, "第二句")]
>>> paragraphs = segmenter.segment(segments)
>>> len(paragraphs)
1
"""
if not segments:
return []
paragraphs = []
current_paragraph = []
current_length = 0
last_end_time = None
for seg in segments:
# 检查是否需要分段
should_start_new = False
# 规则1: 时间间隔超过阈值
if last_end_time is not None:
gap = seg.start_time - last_end_time
if gap > self.gap_threshold:
should_start_new = True
# 规则2: 段落长度超过限制
if current_length + len(seg.text) > self.max_paragraph_chars:
if current_paragraph: # 只有当前段落有内容时才分段
should_start_new = True
# 执行分段
if should_start_new and current_paragraph:
paragraphs.append(' '.join(current_paragraph))
current_paragraph = []
current_length = 0
# 添加当前分段
current_paragraph.append(seg.text)
current_length += len(seg.text)
last_end_time = seg.end_time
# 添加最后一个段落
if current_paragraph:
paragraphs.append(' '.join(current_paragraph))
return paragraphs