Compare commits
5 Commits
7c4c435c8b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
daec6a376d | ||
|
|
4c2ee40ba6 | ||
|
|
1dc354ef71 | ||
|
|
588226515e | ||
|
|
0b8aadc61c |
454
docs/bot-comparison-analysis.md
Normal file
454
docs/bot-comparison-analysis.md
Normal file
@@ -0,0 +1,454 @@
|
|||||||
|
# 飞书机器人对比分析报告
|
||||||
|
|
||||||
|
> 深度验证:kb-bot vs bot-v2 的真实状态、功能对比、配合方案
|
||||||
|
> 验证时间:2026-07-16
|
||||||
|
> 验证方式:逐行读代码 + 对话记录提取 + 依赖检查
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、核心结论(先说结论)
|
||||||
|
|
||||||
|
### kb-bot(知识库里的Python版)
|
||||||
|
|
||||||
|
**状态判定**:✅ **代码 100% 完整实现,理论上可运行,但未实际部署验证过**
|
||||||
|
|
||||||
|
**我之前的判断错误**:
|
||||||
|
- 最初我说它是"空壳/骨架" —— **这是错的,向你认错**
|
||||||
|
- 错误原因:当时只看了文件大小没读内容,草率判断
|
||||||
|
- 实际情况:三个文件(subscribe.sh、handle.py、send.sh)都是**完整实现**,逻辑清晰,依赖齐全
|
||||||
|
|
||||||
|
**核心逻辑**(已验证):
|
||||||
|
```
|
||||||
|
1. subscribe.sh 起 WebSocket 长连接
|
||||||
|
→ lark-cli event +subscribe 监听飞书群消息
|
||||||
|
→ 事件落到 events/xxx.json
|
||||||
|
|
||||||
|
2. handle.py 轮询 events/
|
||||||
|
→ 解析 JSON 提取问题
|
||||||
|
→ 调用: subprocess.run(["claude", "-p", "/kb-ask 问题"], cwd=知识库根)
|
||||||
|
→ 调用: lark-cli im +messages-reply 回复到群
|
||||||
|
|
||||||
|
3. .seen-msg-ids 去重机制
|
||||||
|
```
|
||||||
|
|
||||||
|
**依赖验证**(✓ 全部齐全):
|
||||||
|
- Python 3.12.8 ✓
|
||||||
|
- lark-cli 1.0.69 ✓(你本地已装)
|
||||||
|
- claude CLI ✓(你本地已装)
|
||||||
|
- /kb-ask 命令 ✓(.claude/commands/kb-ask.md 存在)
|
||||||
|
|
||||||
|
**未验证的风险点**:
|
||||||
|
- `claude -p "/kb-ask xxx"` headless 模式能否正确工作(需实测)
|
||||||
|
- 是否真的能在群里触发并回复(需实测)
|
||||||
|
|
||||||
|
### bot-v2(阿里云 ECS 的 Node.js 版)
|
||||||
|
|
||||||
|
**状态判定**:✅ **已上线运行,生产环境主力机器人**
|
||||||
|
|
||||||
|
**位置**:阿里云 ECS `47.110.48.113:/opt/bot-v2`
|
||||||
|
|
||||||
|
**技术栈**:Node.js + lark-cli + SQLite
|
||||||
|
|
||||||
|
**核心逻辑**(从对话记录提取):
|
||||||
|
```
|
||||||
|
1. lark-cli event +subscribe 起长连接
|
||||||
|
→ 消息进入 router.js 路由判断意图
|
||||||
|
|
||||||
|
2. 根据意图分发到不同 handler:
|
||||||
|
- "整理" → chat-history.js 拉历史+AI分析
|
||||||
|
- "存一下" → save-knowledge.sh 存多维表格
|
||||||
|
- 其他 → 快速问答(调 Claude API)
|
||||||
|
|
||||||
|
3. SQLite 数据库存储:
|
||||||
|
- conversations 表:上下文记忆
|
||||||
|
- task_queue 表:异步任务队列
|
||||||
|
```
|
||||||
|
|
||||||
|
**9大功能**(对话记录提到的):
|
||||||
|
1. 快速问答(Claude API)
|
||||||
|
2. 整理聊天历史(拉取+分析+摘要)
|
||||||
|
3. 存知识库(推送到飞书多维表格)
|
||||||
|
4. 生图(Gemini API)
|
||||||
|
5. RAG 检索(SQLite + 知识库)
|
||||||
|
6. 会议纪要自动生成
|
||||||
|
7. 任务队列异步处理
|
||||||
|
8. 上下文记忆
|
||||||
|
9. 多群白名单配置
|
||||||
|
|
||||||
|
**已验证**:你的截图显示它在群里成功回复了(拉到了135条消息)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、详细对比
|
||||||
|
|
||||||
|
| 维度 | kb-bot(知识库Python版) | bot-v2(阿里云Node.js版) |
|
||||||
|
|------|------------------------|-------------------------|
|
||||||
|
| **部署位置** | 未部署(代码在知识库 tools/kb-bot/) | 已部署(阿里云 ECS /opt/bot-v2) |
|
||||||
|
| **技术栈** | Python 3 + lark-cli + Claude Code CLI | Node.js + lark-cli + SQLite |
|
||||||
|
| **核心能力** | 单一:查知识库问答 | 9大功能(问答+整理+存储+生图+...) |
|
||||||
|
| **问答方式** | Claude Code headless (`claude -p "/kb-ask"`) | Claude API 直接调用 |
|
||||||
|
| **上下文记忆** | 无 | SQLite conversations 表 |
|
||||||
|
| **任务队列** | 无 | SQLite task_queue 表 |
|
||||||
|
| **聊天历史** | 不处理 | chat-history.js 拉取+分析 |
|
||||||
|
| **知识库整合** | ✅ 深度整合(直接查 projects/) | ✅ 也整合(但通过多维表格) |
|
||||||
|
| **代码量** | ~200行(3个文件) | ~数千行(多文件模块化) |
|
||||||
|
| **运行状态** | 未实测 | ✅ 生产运行中 |
|
||||||
|
| **可靠性** | 未知(未部署验证) | ✅ 已验证(你的截图) |
|
||||||
|
| **维护性** | 简单,代码少 | 复杂,功能多 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、功能对比详表
|
||||||
|
|
||||||
|
### kb-bot 的功能(1个)
|
||||||
|
|
||||||
|
| 功能 | 实现方式 | 验证状态 |
|
||||||
|
|------|---------|---------|
|
||||||
|
| 查知识库问答 | `claude -p "/kb-ask 问题"` → 读 projects/ → 溯源回答 | ✅ 代码完整,未实测 |
|
||||||
|
|
||||||
|
### bot-v2 的功能(9个)
|
||||||
|
|
||||||
|
| 功能 | 实现方式 | 验证状态 |
|
||||||
|
|------|---------|---------|
|
||||||
|
| 1. 快速问答 | Claude API | ✅ 截图证明能跑 |
|
||||||
|
| 2. 整理聊天历史 | lark-api.js fetchMessages() + AI分析 | ✅ 截图显示拉到135条消息 |
|
||||||
|
| 3. 存知识库 | 推送到飞书多维表格 | ✅ 对话记录提到 |
|
||||||
|
| 4. 生图 | Gemini API | 对话记录提到 |
|
||||||
|
| 5. RAG 检索 | SQLite + embedding | 对话记录提到 |
|
||||||
|
| 6. 会议纪要 | 自动生成 | 对话记录提到 |
|
||||||
|
| 7. 任务队列 | SQLite + node-cron | 对话记录提到 |
|
||||||
|
| 8. 上下文记忆 | SQLite conversations 表 | 对话记录提到 |
|
||||||
|
| 9. 多群白名单 | config/bot-config.json | 对话记录提到 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、实现逻辑对比
|
||||||
|
|
||||||
|
### kb-bot 的实现链路
|
||||||
|
|
||||||
|
```
|
||||||
|
群里 @机器人 "万牛会L1怎么安排的?"
|
||||||
|
↓
|
||||||
|
subscribe.sh(WebSocket长连接)
|
||||||
|
↓
|
||||||
|
events/msg-12345.json
|
||||||
|
↓
|
||||||
|
handle.py 轮询到新消息
|
||||||
|
↓
|
||||||
|
extract() 解析 → 问题:"万牛会L1怎么安排的?"
|
||||||
|
↓
|
||||||
|
ask_kb():
|
||||||
|
subprocess.run([
|
||||||
|
"claude", "-p",
|
||||||
|
"/kb-ask 万牛会L1怎么安排的?"
|
||||||
|
], cwd=知识库根目录)
|
||||||
|
↓
|
||||||
|
Claude Code headless 模式:
|
||||||
|
1. cd 到知识库根
|
||||||
|
2. 读 .claude/commands/kb-ask.md
|
||||||
|
3. 执行命令逻辑:
|
||||||
|
- 扫描 projects/ 找相关页
|
||||||
|
- 读 frontmatter 筛选
|
||||||
|
- 语义检索内容
|
||||||
|
- 综合回答 + 挂 source_link
|
||||||
|
↓
|
||||||
|
返回答案:"根据 [[课程大纲v1]]..."
|
||||||
|
↓
|
||||||
|
reply():
|
||||||
|
lark-cli im +messages-reply
|
||||||
|
--message-id xxx
|
||||||
|
--markdown "答案"
|
||||||
|
--as bot
|
||||||
|
↓
|
||||||
|
答案回到群里
|
||||||
|
```
|
||||||
|
|
||||||
|
### bot-v2 的实现链路(以"整理聊天"为例)
|
||||||
|
|
||||||
|
```
|
||||||
|
群里 @机器人 "整理一下最近3天的讨论"
|
||||||
|
↓
|
||||||
|
lark-cli event +subscribe(长连接)
|
||||||
|
↓
|
||||||
|
router.js 判断意图:
|
||||||
|
if (msg.includes("整理")) → chatHistoryHandler
|
||||||
|
↓
|
||||||
|
chat-history.js:
|
||||||
|
1. lark-api.fetchMessages(chatId, 3天)
|
||||||
|
→ 调飞书API拉历史消息
|
||||||
|
→ 拉到135条消息(你的截图)
|
||||||
|
2. 解析 content 字段(JSON → text)
|
||||||
|
3. 分类:文本/图片/文件/富文本/系统消息
|
||||||
|
4. 调 Claude API 分析:
|
||||||
|
"这135条消息的核心主题是什么?
|
||||||
|
关键决策有哪些?
|
||||||
|
待办事项是什么?"
|
||||||
|
5. 生成结构化摘要
|
||||||
|
↓
|
||||||
|
reply():
|
||||||
|
lark-cli im +messages-reply --markdown "摘要"
|
||||||
|
↓
|
||||||
|
存入 SQLite conversations 表(上下文记忆)
|
||||||
|
↓
|
||||||
|
答案回到群里
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、能否配合?配合方案
|
||||||
|
|
||||||
|
### 场景 1:知识库专属问答(kb-bot)
|
||||||
|
|
||||||
|
**定位**:轻量、专注、深度整合知识库
|
||||||
|
|
||||||
|
**优势**:
|
||||||
|
- 直接读 projects/ 目录,不经过中间层
|
||||||
|
- 溯源铁律(source_link 强制挂)
|
||||||
|
- 代码简单,易维护
|
||||||
|
|
||||||
|
**适用场景**:
|
||||||
|
- 公司内部知识库问答
|
||||||
|
- 需要严格溯源的场景
|
||||||
|
- 不需要聊天历史、任务队列等复杂功能
|
||||||
|
|
||||||
|
**部署方案**:
|
||||||
|
- 可以部署在 NAS 上(常驻运行)
|
||||||
|
- 也可以和 bot-v2 部署在同一台 ECS(不冲突)
|
||||||
|
- 用不同的机器人账号(一个叫"知识库助手",一个叫"全能助手")
|
||||||
|
|
||||||
|
### 场景 2:全能助手(bot-v2)
|
||||||
|
|
||||||
|
**定位**:重量级、多功能、生产主力
|
||||||
|
|
||||||
|
**优势**:
|
||||||
|
- 9大功能,覆盖日常协作
|
||||||
|
- 任务队列异步处理
|
||||||
|
- 上下文记忆
|
||||||
|
- 已在生产验证
|
||||||
|
|
||||||
|
**适用场景**:
|
||||||
|
- 日常群聊协作
|
||||||
|
- 需要整理聊天、生图、会议纪要等
|
||||||
|
- 多群管理
|
||||||
|
|
||||||
|
**现状**:已部署在阿里云,正常运行
|
||||||
|
|
||||||
|
### 配合方案 A:双机器人协作(推荐)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ 飞书群(公司知识交流群) │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ @知识库助手 万牛会L1怎么安排的? │
|
||||||
|
│ ↓ │
|
||||||
|
│ kb-bot(NAS)→ 查知识库 → 溯源回答 │
|
||||||
|
│ │
|
||||||
|
│ @全能助手 整理一下最近3天的讨论 │
|
||||||
|
│ ↓ │
|
||||||
|
│ bot-v2(ECS)→ 拉历史 → 分析摘要 │
|
||||||
|
│ │
|
||||||
|
│ @全能助手 帮我生成会议纪要 │
|
||||||
|
│ ↓ │
|
||||||
|
│ bot-v2 → 任务队列 → 生成 → 推送 │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**优点**:
|
||||||
|
- 各司其职,不互相干扰
|
||||||
|
- kb-bot 专注知识库(深度整合 projects/)
|
||||||
|
- bot-v2 覆盖其他协作场景
|
||||||
|
|
||||||
|
**缺点**:
|
||||||
|
- 两个机器人要维护(但 kb-bot 代码简单)
|
||||||
|
- 用户要记住@哪个
|
||||||
|
|
||||||
|
### 配合方案 B:bot-v2 整合 kb-bot 逻辑
|
||||||
|
|
||||||
|
把 kb-bot 的 `/kb-ask` 逻辑整合到 bot-v2 的 router.js:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// bot-v2/router.js 新增
|
||||||
|
if (msg.intent === 'query_kb') {
|
||||||
|
// 调用 kb-bot 的 ask_kb 逻辑
|
||||||
|
const answer = await execClaude([
|
||||||
|
'claude', '-p', `/kb-ask ${question}`
|
||||||
|
], { cwd: KB_ROOT });
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**优点**:
|
||||||
|
- 只维护一个机器人
|
||||||
|
- 用户无需区分
|
||||||
|
|
||||||
|
**缺点**:
|
||||||
|
- bot-v2 要依赖知识库目录(耦合)
|
||||||
|
- Claude Code CLI 要在 ECS 上装
|
||||||
|
|
||||||
|
### 配合方案 C:各管各的(现状)
|
||||||
|
|
||||||
|
**bot-v2(ECS)**:管日常协作、生图、整理、会议纪要
|
||||||
|
|
||||||
|
**kb-bot(未部署)**:暂不部署,知识库问答通过 `/kb-ask` 命令手动在 Claude Code 里查
|
||||||
|
|
||||||
|
**优点**:最简单,不增加维护负担
|
||||||
|
|
||||||
|
**缺点**:知识库问答不能在群里自动触发
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、在知识库框架里的实现方案
|
||||||
|
|
||||||
|
### 方案 1:kb-bot 部署到 NAS(推荐)
|
||||||
|
|
||||||
|
**步骤**:
|
||||||
|
1. 把 `tools/kb-bot/` 复制到 NAS:`/volume1/scripts/kb-bot/`
|
||||||
|
2. 配置 systemd 服务(或 DSM 任务计划):
|
||||||
|
```bash
|
||||||
|
# subscribe 长连接
|
||||||
|
nohup bash /volume1/scripts/kb-bot/subscribe.sh &
|
||||||
|
|
||||||
|
# handle 消费者
|
||||||
|
nohup python3 /volume1/scripts/kb-bot/handle.py --watch &
|
||||||
|
```
|
||||||
|
3. 在飞书创建第二个机器人账号:"知识库助手"
|
||||||
|
4. 配置 lark-cli 认证(bot 身份)
|
||||||
|
5. 测试:群里 @知识库助手 提问
|
||||||
|
|
||||||
|
**优点**:
|
||||||
|
- NAS 上常驻,和知识库 Git 仓库在一起
|
||||||
|
- 不影响 bot-v2
|
||||||
|
|
||||||
|
### 方案 2:kb-bot 也部署到 ECS(和 bot-v2 共存)
|
||||||
|
|
||||||
|
**步骤**:
|
||||||
|
1. SSH 到 ECS
|
||||||
|
2. 在 `/opt/kb-bot/` 部署
|
||||||
|
3. 配置 systemd 服务
|
||||||
|
4. 用不同端口/不同机器人账号
|
||||||
|
|
||||||
|
**优点**:
|
||||||
|
- ECS 更稳定(NAS 可能重启)
|
||||||
|
|
||||||
|
**缺点**:
|
||||||
|
- kb-bot 要能访问知识库(需要 Git clone 到 ECS)
|
||||||
|
|
||||||
|
### 方案 3:不部署 kb-bot,bot-v2 加一个"查知识库"功能
|
||||||
|
|
||||||
|
在 bot-v2 里新增一个 handler:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// bot-v2/handlers/kb-query.js
|
||||||
|
async function handleKBQuery(question) {
|
||||||
|
// 选项A:调 Claude Code CLI
|
||||||
|
const answer = await exec(`claude -p "/kb-ask ${question}"`, {
|
||||||
|
cwd: '/opt/company-kb'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 选项B:用 Claude API + 直接读 projects/
|
||||||
|
const kbFiles = await readDir('/opt/company-kb/projects');
|
||||||
|
const relevant = await searchRelevant(kbFiles, question);
|
||||||
|
const answer = await claudeAPI(relevant);
|
||||||
|
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**优点**:
|
||||||
|
- 只维护一个机器人
|
||||||
|
- 功能集中
|
||||||
|
|
||||||
|
**缺点**:
|
||||||
|
- bot-v2 要依赖知识库
|
||||||
|
- 增加复杂度
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、最终建议
|
||||||
|
|
||||||
|
### 立刻可以做的(0成本验证)
|
||||||
|
|
||||||
|
1. **测试 kb-bot 能否真跑**:
|
||||||
|
```bash
|
||||||
|
cd ~/work/company-kb/tools/kb-bot/
|
||||||
|
|
||||||
|
# 测试 subscribe(前台跑,看能否收到消息)
|
||||||
|
bash subscribe.sh
|
||||||
|
|
||||||
|
# 另开终端,去群里 @机器人 说句话
|
||||||
|
# 看 events/ 是否生成 JSON 文件
|
||||||
|
|
||||||
|
# 测试 handle(处理一条消息)
|
||||||
|
python3 handle.py
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **如果能跑通** → 部署到 NAS(方案1)
|
||||||
|
|
||||||
|
3. **如果跑不通** → 记录错误,修bug(我帮你)
|
||||||
|
|
||||||
|
### 中期方案(1-2天)
|
||||||
|
|
||||||
|
- **双机器人并行**(方案 A):
|
||||||
|
- kb-bot 专注知识库问答(深度整合)
|
||||||
|
- bot-v2 覆盖其他协作场景
|
||||||
|
- 各司其职,不冲突
|
||||||
|
|
||||||
|
### 长期方案(可选)
|
||||||
|
|
||||||
|
- bot-v2 整合 kb-bot 的核心逻辑(方案 B)
|
||||||
|
- 或者继续分离,但 kb-bot 加上下文记忆、任务队列等能力(进化成 kb-bot-v2)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、风险点与注意事项
|
||||||
|
|
||||||
|
### kb-bot 的风险
|
||||||
|
|
||||||
|
1. **未实测**:代码写得很完整,但没有实际部署验证过
|
||||||
|
2. **headless 模式不确定**:`claude -p "/kb-ask xxx"` 能否在无交互环境下正确工作(需实测)
|
||||||
|
3. **错误处理**:代码里有 try-except,但边界情况可能不全
|
||||||
|
|
||||||
|
### bot-v2 的风险
|
||||||
|
|
||||||
|
1. **代码分散在 ECS**:本地没备份,如果 ECS 挂了数据可能丢
|
||||||
|
2. **复杂度高**:9大功能,维护成本高
|
||||||
|
3. **文档缺失**:对话记录里只有部分代码,完整文档不全
|
||||||
|
|
||||||
|
### 配合的风险
|
||||||
|
|
||||||
|
1. **双机器人混淆**:用户可能不知道该@哪个
|
||||||
|
2. **维护成本**:两套代码要同时维护
|
||||||
|
3. **功能重复**:两个机器人都能问答,但实现不同
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、总结
|
||||||
|
|
||||||
|
### kb-bot 真实状态
|
||||||
|
|
||||||
|
✅ **代码 100% 完整,逻辑清晰,依赖齐全,理论上可运行**
|
||||||
|
❓ **但未实际部署验证,存在不确定性**
|
||||||
|
|
||||||
|
**我之前说它是"空壳"是错的,向你认错。**
|
||||||
|
|
||||||
|
### bot-v2 真实状态
|
||||||
|
|
||||||
|
✅ **已上线,生产运行,功能强大,你的截图证明它能跑**
|
||||||
|
|
||||||
|
### 两者关系
|
||||||
|
|
||||||
|
- **不是一个东西**:技术栈不同、功能不同、定位不同
|
||||||
|
- **可以并存**:双机器人各司其职
|
||||||
|
- **也可以合并**:bot-v2 整合 kb-bot 逻辑
|
||||||
|
|
||||||
|
### 下一步
|
||||||
|
|
||||||
|
**建议:先测 kb-bot 能否跑**(0成本验证),跑通了再决定部署方案。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**验证人**: Claude (Opus 4.8)
|
||||||
|
**验证方式**: 逐行读代码 + 依赖检查 + 对话记录提取 + 逻辑推导
|
||||||
|
**置信度**: 95%(代码逻辑确认,但未实测运行)
|
||||||
467
docs/kb-bot-usage-guide.md
Normal file
467
docs/kb-bot-usage-guide.md
Normal file
@@ -0,0 +1,467 @@
|
|||||||
|
# kb-bot 完整使用指南(从零到运行)
|
||||||
|
|
||||||
|
> 解决:"在哪里、怎么用、机器人在哪"的所有困惑
|
||||||
|
> 更新时间:2026-07-16
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 问题:你现在卡在哪里?
|
||||||
|
|
||||||
|
截图显示:
|
||||||
|
```bash
|
||||||
|
cd ~/work/company-kb/tools/kb-bot/
|
||||||
|
cd: no such file or directory: /Users/qiyu/work/company-kb/tools/kb-bot/
|
||||||
|
```
|
||||||
|
|
||||||
|
**原因**:你本地没有 `~/work/company-kb/` 这个目录。
|
||||||
|
|
||||||
|
**真相**:知识库实际在这里:
|
||||||
|
```
|
||||||
|
/Users/qiyu/Library/CloudStorage/SynologyDrive-zhishiku/知识库投递区/公司资产信息/company-kb-v0.1-mvp-20260707
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 完整链路:从代码到运行(5个关键位置)
|
||||||
|
|
||||||
|
```
|
||||||
|
位置1:知识库代码(你有)
|
||||||
|
/Users/qiyu/Library/.../company-kb-v0.1-mvp-20260707/
|
||||||
|
└── tools/kb-bot/ ← kb-bot 的代码在这里
|
||||||
|
├── subscribe.sh
|
||||||
|
├── handle.py
|
||||||
|
└── send.sh
|
||||||
|
|
||||||
|
位置2:飞书机器人账号(需要创建)
|
||||||
|
→ 飞书开放平台创建 Bot 应用
|
||||||
|
→ 获得 App ID 和 App Secret
|
||||||
|
→ 加入到群里
|
||||||
|
|
||||||
|
位置3:本地 lark-cli 认证(需要配置)
|
||||||
|
→ 运行 lark-cli auth login
|
||||||
|
→ 用 Bot 身份登录
|
||||||
|
|
||||||
|
位置4:运行环境(选一个)
|
||||||
|
选项A:你的 Mac 本地运行
|
||||||
|
选项B:NAS 上运行
|
||||||
|
选项C:阿里云 ECS 运行
|
||||||
|
|
||||||
|
位置5:飞书群(你有)
|
||||||
|
→ @机器人 提问
|
||||||
|
→ 机器人自动回复
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第一步:环境检查(确认依赖都齐)
|
||||||
|
|
||||||
|
### 1.1 检查 lark-cli
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查是否安装
|
||||||
|
lark-cli --version
|
||||||
|
|
||||||
|
# 如果没装,安装
|
||||||
|
npm install -g @larksuiteoapi/lark-cli
|
||||||
|
|
||||||
|
# 检查认证状态
|
||||||
|
lark-cli auth status
|
||||||
|
```
|
||||||
|
|
||||||
|
**预期输出**:显示当前登录的身份(用户或 bot)
|
||||||
|
|
||||||
|
### 1.2 检查 claude CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查是否安装
|
||||||
|
claude --version
|
||||||
|
|
||||||
|
# 如果没装,安装
|
||||||
|
npm install -g @anthropic-ai/claude-cli
|
||||||
|
|
||||||
|
# 检查能否运行
|
||||||
|
claude --help
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 检查 Python 3
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 --version
|
||||||
|
# 预期:Python 3.12+
|
||||||
|
```
|
||||||
|
|
||||||
|
**如果三个都 OK,继续下一步。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第二步:创建飞书机器人(如果还没有)
|
||||||
|
|
||||||
|
### 2.1 飞书开放平台创建应用
|
||||||
|
|
||||||
|
1. 打开:https://open.feishu.cn/app
|
||||||
|
2. 点"创建企业自建应用"
|
||||||
|
3. 填写:
|
||||||
|
- 应用名称:`知识库助手`
|
||||||
|
- 应用描述:`查询公司知识库`
|
||||||
|
- 应用图标:随便传一个
|
||||||
|
4. 创建完成,记下:
|
||||||
|
- **App ID**
|
||||||
|
- **App Secret**
|
||||||
|
|
||||||
|
### 2.2 配置机器人权限
|
||||||
|
|
||||||
|
在应用管理页面:
|
||||||
|
1. **权限管理** → 添加权限:
|
||||||
|
- `im:message`(接收消息)
|
||||||
|
- `im:message:send_as_bot`(发送消息)
|
||||||
|
- `im:chat`(获取群信息)
|
||||||
|
2. **事件订阅** → 添加事件:
|
||||||
|
- `im.message.receive_v1`(接收消息)
|
||||||
|
3. **机器人** → 启用机器人
|
||||||
|
|
||||||
|
### 2.3 把机器人加入测试群
|
||||||
|
|
||||||
|
1. 飞书客户端,找到你的测试群
|
||||||
|
2. 群设置 → 群机器人 → 添加机器人
|
||||||
|
3. 搜索"知识库助手",加入
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第三步:配置 lark-cli 认证(关键)
|
||||||
|
|
||||||
|
### 3.1 用 Bot 身份登录
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 清除旧的认证
|
||||||
|
lark-cli auth logout
|
||||||
|
|
||||||
|
# 用 Bot 身份登录
|
||||||
|
lark-cli auth login --type bot
|
||||||
|
|
||||||
|
# 会提示输入 App ID 和 App Secret
|
||||||
|
# → 输入第二步创建的那两个值
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 验证认证
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lark-cli auth status
|
||||||
|
|
||||||
|
# 预期输出:
|
||||||
|
# ✓ Logged in as bot (app_id: cli_xxxx)
|
||||||
|
```
|
||||||
|
|
||||||
|
**如果这步没通过,后面全都跑不起来。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第四步:测试 kb-bot(本地运行)
|
||||||
|
|
||||||
|
### 4.1 进入 kb-bot 目录(正确路径)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 用实际路径(不是 ~/work/company-kb/)
|
||||||
|
cd "/Users/qiyu/Library/CloudStorage/SynologyDrive-zhishiku/知识库投递区/公司资产信息/company-kb-v0.1-mvp-20260707/tools/kb-bot"
|
||||||
|
|
||||||
|
# 或者设个快捷变量
|
||||||
|
KB="/Users/qiyu/Library/CloudStorage/SynologyDrive-zhishiku/知识库投递区/公司资产信息/company-kb-v0.1-mvp-20260707"
|
||||||
|
cd "$KB/tools/kb-bot"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 测试 subscribe.sh(收消息)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 前台运行,看输出
|
||||||
|
bash subscribe.sh
|
||||||
|
|
||||||
|
# 预期输出:
|
||||||
|
# [kb-bot] 订阅飞书群消息事件 → /path/to/events
|
||||||
|
# [kb-bot] 只收 im.message.receive_v1;Ctrl-C 停止。
|
||||||
|
# (长连接建立,等待消息...)
|
||||||
|
```
|
||||||
|
|
||||||
|
**现在去飞书群里 @知识库助手 说句话**,比如:
|
||||||
|
```
|
||||||
|
@知识库助手 你好
|
||||||
|
```
|
||||||
|
|
||||||
|
**回到终端,应该看到**:
|
||||||
|
```
|
||||||
|
[lark-cli] 收到消息: events/msg-xxx.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**检查 events/ 目录**:
|
||||||
|
```bash
|
||||||
|
ls events/
|
||||||
|
# 应该有一个 msg-xxx.json 文件
|
||||||
|
```
|
||||||
|
|
||||||
|
**如果能收到消息,说明 subscribe.sh 正常。按 Ctrl-C 停止。**
|
||||||
|
|
||||||
|
### 4.3 测试 handle.py(处理消息)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 单次处理(不常驻)
|
||||||
|
python3 handle.py
|
||||||
|
|
||||||
|
# 预期:
|
||||||
|
# - 读 events/ 里的消息
|
||||||
|
# - 调 claude -p "/kb-ask 你好"
|
||||||
|
# - 用 lark-cli 回复到群
|
||||||
|
|
||||||
|
# 如果成功,群里会看到机器人回复
|
||||||
|
```
|
||||||
|
|
||||||
|
**如果群里机器人回复了,说明 handle.py 正常。**
|
||||||
|
|
||||||
|
### 4.4 常驻运行(测试通过后)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 开两个终端
|
||||||
|
|
||||||
|
# 终端1:起 subscribe(收消息)
|
||||||
|
cd "$KB/tools/kb-bot"
|
||||||
|
nohup bash subscribe.sh > /tmp/kb-bot-sub.log 2>&1 &
|
||||||
|
|
||||||
|
# 终端2:起 handle(处理消息)
|
||||||
|
cd "$KB/tools/kb-bot"
|
||||||
|
nohup python3 handle.py --watch > /tmp/kb-bot-handle.log 2>&1 &
|
||||||
|
|
||||||
|
# 查看进程
|
||||||
|
ps aux | grep -E "subscribe|handle" | grep -v grep
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
tail -f /tmp/kb-bot-sub.log
|
||||||
|
tail -f /tmp/kb-bot-handle.log
|
||||||
|
```
|
||||||
|
|
||||||
|
**现在去群里 @机器人 提问,应该自动回复。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第五步:部署到 NAS(如果本地测试通过)
|
||||||
|
|
||||||
|
### 5.1 把代码复制到 NAS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# SSH 登录 NAS
|
||||||
|
ssh 你的用户名@192.168.0.101
|
||||||
|
|
||||||
|
# 在 NAS 上创建目录
|
||||||
|
mkdir -p /volume1/scripts/kb-bot
|
||||||
|
cd /volume1/scripts/
|
||||||
|
|
||||||
|
# 把知识库也 clone 到 NAS(因为 kb-bot 要读 .claude/commands/)
|
||||||
|
git clone http://192.168.0.101:3002/robert/company-kb.git
|
||||||
|
|
||||||
|
# 把 kb-bot 代码复制过来
|
||||||
|
cp company-kb/tools/kb-bot/* kb-bot/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 配置 NAS 上的 lark-cli
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# NAS 上也要装 lark-cli 和 claude CLI
|
||||||
|
npm install -g @larksuiteoapi/lark-cli
|
||||||
|
npm install -g @anthropic-ai/claude-cli
|
||||||
|
|
||||||
|
# 用 Bot 身份登录
|
||||||
|
lark-cli auth login --type bot
|
||||||
|
# 输入 App ID 和 App Secret
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 配置 NAS 定时任务(DSM 控制面板)
|
||||||
|
|
||||||
|
1. 打开 DSM → 控制面板 → 任务计划
|
||||||
|
2. 新增 → 用户定义的脚本
|
||||||
|
3. 任务名称:`kb-bot-subscribe`
|
||||||
|
4. 用户账号:选你的用户
|
||||||
|
5. 计划:开机时运行
|
||||||
|
6. 脚本内容:
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
cd /volume1/scripts/kb-bot
|
||||||
|
nohup bash subscribe.sh > /tmp/kb-bot-sub.log 2>&1 &
|
||||||
|
```
|
||||||
|
7. 保存
|
||||||
|
|
||||||
|
重复创建第二个任务:`kb-bot-handle`,脚本内容:
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
cd /volume1/scripts/kb-bot
|
||||||
|
nohup python3 handle.py --watch > /tmp/kb-bot-handle.log 2>&1 &
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q1:subscribe.sh 报错 "lark-cli 不在 PATH"
|
||||||
|
|
||||||
|
**答**:
|
||||||
|
```bash
|
||||||
|
# 找到 lark-cli 的路径
|
||||||
|
which lark-cli
|
||||||
|
|
||||||
|
# 把路径加到 subscribe.sh 的 PATH 里
|
||||||
|
# 编辑 subscribe.sh,在开头加一行:
|
||||||
|
export PATH="/你的路径/bin:$PATH"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q2:handle.py 报错 "claude 不在 PATH"
|
||||||
|
|
||||||
|
**答**:同上,编辑 handle.py,在开头加:
|
||||||
|
```python
|
||||||
|
os.environ["PATH"] = "/你的路径/bin:" + os.environ["PATH"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q3:机器人不回复
|
||||||
|
|
||||||
|
**检查清单**:
|
||||||
|
1. `lark-cli auth status` → 确认 bot 身份登录
|
||||||
|
2. `ls events/` → 确认有消息文件
|
||||||
|
3. `cat events/msg-xxx.json` → 确认消息格式对
|
||||||
|
4. `tail -f /tmp/kb-bot-handle.log` → 看错误日志
|
||||||
|
5. 手动跑一次 `python3 handle.py`,看输出
|
||||||
|
|
||||||
|
### Q4:claude -p "/kb-ask xxx" 报错
|
||||||
|
|
||||||
|
**可能原因**:
|
||||||
|
- 工作目录不对(handle.py 的 cwd=ROOT 变量)
|
||||||
|
- claude CLI 版本不对
|
||||||
|
- /kb-ask 命令文件不存在
|
||||||
|
|
||||||
|
**验证**:
|
||||||
|
```bash
|
||||||
|
cd "$KB" # 知识库根目录
|
||||||
|
claude -p "/kb-ask 测试问题"
|
||||||
|
# 看能否正常执行
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 全链路流程图(清晰版)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌────────────────────────────────────────────────┐
|
||||||
|
│ 飞书群里 @知识库助手 "万牛会L1怎么安排的?" │
|
||||||
|
└───────────────────┬────────────────────────────┘
|
||||||
|
│
|
||||||
|
↓
|
||||||
|
┌────────────────────────────────────────────────┐
|
||||||
|
│ 位置1:飞书服务器 │
|
||||||
|
│ - 收到消息 │
|
||||||
|
│ - 推送到 WebSocket 长连接 │
|
||||||
|
└───────────────────┬────────────────────────────┘
|
||||||
|
│
|
||||||
|
↓
|
||||||
|
┌────────────────────────────────────────────────┐
|
||||||
|
│ 位置2:你的 Mac / NAS(subscribe.sh 在跑) │
|
||||||
|
│ - lark-cli event +subscribe │
|
||||||
|
│ - 收到推送,解析消息 │
|
||||||
|
│ - 写入 events/msg-12345.json │
|
||||||
|
└───────────────────┬────────────────────────────┘
|
||||||
|
│
|
||||||
|
↓
|
||||||
|
┌────────────────────────────────────────────────┐
|
||||||
|
│ 位置3:你的 Mac / NAS(handle.py 在跑) │
|
||||||
|
│ - 轮询 events/ 发现新文件 │
|
||||||
|
│ - 解析 JSON 提取问题:"万牛会L1怎么安排的?" │
|
||||||
|
│ - 调用:subprocess.run([ │
|
||||||
|
│ "claude", "-p", │
|
||||||
|
│ "/kb-ask 万牛会L1怎么安排的?" │
|
||||||
|
│ ], cwd=知识库根目录) │
|
||||||
|
└───────────────────┬────────────────────────────┘
|
||||||
|
│
|
||||||
|
↓
|
||||||
|
┌────────────────────────────────────────────────┐
|
||||||
|
│ 位置4:知识库目录(Claude Code 在这里执行) │
|
||||||
|
│ - cd 到知识库根 │
|
||||||
|
│ - 读 .claude/commands/kb-ask.md │
|
||||||
|
│ - 执行命令逻辑: │
|
||||||
|
│ 1. 扫描 projects/ 找相关页 │
|
||||||
|
│ 2. 读 frontmatter 筛选 │
|
||||||
|
│ 3. 语义检索内容 │
|
||||||
|
│ 4. 综合回答 + 挂 source_link │
|
||||||
|
│ - 返回答案:"根据 [[课程大纲v1]]..." │
|
||||||
|
└───────────────────┬────────────────────────────┘
|
||||||
|
│
|
||||||
|
↓
|
||||||
|
┌────────────────────────────────────────────────┐
|
||||||
|
│ 位置5:handle.py 拿到答案 │
|
||||||
|
│ - 调用:lark-cli im +messages-reply │
|
||||||
|
│ --message-id 12345 │
|
||||||
|
│ --markdown "根据[[课程大纲v1]]..." │
|
||||||
|
│ --as bot │
|
||||||
|
└───────────────────┬────────────────────────────┘
|
||||||
|
│
|
||||||
|
↓
|
||||||
|
┌────────────────────────────────────────────────┐
|
||||||
|
│ 位置6:飞书服务器 │
|
||||||
|
│ - 收到回复请求 │
|
||||||
|
│ - 推送到群里 │
|
||||||
|
└───────────────────┬────────────────────────────┘
|
||||||
|
│
|
||||||
|
↓
|
||||||
|
┌────────────────────────────────────────────────┐
|
||||||
|
│ 飞书群里显示机器人回复 │
|
||||||
|
│ "根据 [[课程大纲v1]](来源:飞书文档)..." │
|
||||||
|
└────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 关键位置总结
|
||||||
|
|
||||||
|
| 位置 | 路径 / 地址 | 作用 |
|
||||||
|
|------|------------|------|
|
||||||
|
| **知识库代码** | `/Users/qiyu/Library/.../company-kb-v0.1-mvp-20260707/` | kb-bot 代码在这里 |
|
||||||
|
| **飞书机器人** | 飞书开放平台 App | 接收消息、发送回复 |
|
||||||
|
| **lark-cli 认证** | `~/.lark-cli/` | Bot 身份凭据 |
|
||||||
|
| **运行环境** | Mac 本地 / NAS / ECS | subscribe.sh + handle.py 常驻运行 |
|
||||||
|
| **飞书测试群** | 你的飞书群 | @机器人 提问的地方 |
|
||||||
|
| **events/ 目录** | `kb-bot/events/` | 消息事件临时存放 |
|
||||||
|
| **日志文件** | `/tmp/kb-bot-*.log` | 调试用 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 下一步行动
|
||||||
|
|
||||||
|
### 立刻做(10分钟)
|
||||||
|
|
||||||
|
1. 检查依赖:
|
||||||
|
```bash
|
||||||
|
lark-cli --version
|
||||||
|
claude --version
|
||||||
|
python3 --version
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 配置 lark-cli:
|
||||||
|
```bash
|
||||||
|
lark-cli auth login --type bot
|
||||||
|
# 输入 App ID 和 App Secret
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 测试 subscribe:
|
||||||
|
```bash
|
||||||
|
KB="/Users/qiyu/Library/CloudStorage/SynologyDrive-zhishiku/知识库投递区/公司资产信息/company-kb-v0.1-mvp-20260707"
|
||||||
|
cd "$KB/tools/kb-bot"
|
||||||
|
bash subscribe.sh
|
||||||
|
# 去群里 @机器人,看能否收到消息
|
||||||
|
```
|
||||||
|
|
||||||
|
### 今天完成(1小时)
|
||||||
|
|
||||||
|
- subscribe 能收消息 ✓
|
||||||
|
- handle 能处理并回复 ✓
|
||||||
|
- 本地常驻运行 ✓
|
||||||
|
|
||||||
|
### 本周完成(可选)
|
||||||
|
|
||||||
|
- 部署到 NAS 常驻
|
||||||
|
- 配置开机自启
|
||||||
|
- 写使用文档给同事
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**有问题随时问我,我逐步帮你排查!**
|
||||||
342
tools/kb-bridge.py
Normal file
342
tools/kb-bridge.py
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""kb-bridge.py —— 原始物料 → 知识页(桥接脚本)
|
||||||
|
|
||||||
|
核心功能:
|
||||||
|
1. 读取原始文件(PDF/CSV/markdown/图片)
|
||||||
|
2. 调用 Claude API 分析内容
|
||||||
|
3. 判断 type(doc/decision/conversation)
|
||||||
|
4. 生成完整 frontmatter(符合 kb-contract.yaml)
|
||||||
|
5. 保存到 projects/<项目>/docs|decisions|conversations/
|
||||||
|
6. 调用 kb-lint-fm.py 校验
|
||||||
|
|
||||||
|
用法:
|
||||||
|
# 处理单个文件
|
||||||
|
python3 tools/kb-bridge.py --input 文件路径 [--project 项目代号]
|
||||||
|
|
||||||
|
# 批量处理目录
|
||||||
|
python3 tools/kb-bridge.py --input-dir 目录路径
|
||||||
|
|
||||||
|
# 示例
|
||||||
|
python3 tools/kb-bridge.py --input ../飞书同步/AI工作流.csv --project ai-workflow
|
||||||
|
python3 tools/kb-bridge.py --input ../会议记录/0713会议.pdf
|
||||||
|
|
||||||
|
依赖:
|
||||||
|
pip3 install anthropic pyyaml pdfplumber pandas
|
||||||
|
|
||||||
|
环境变量:
|
||||||
|
ANTHROPIC_API_KEY - Claude API Key(必需)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import hashlib
|
||||||
|
import argparse
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except ImportError:
|
||||||
|
print("ERROR: 需要 PyYAML。运行:pip3 install pyyaml", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import anthropic
|
||||||
|
except ImportError:
|
||||||
|
print("ERROR: 需要 anthropic。运行:pip3 install anthropic", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 定位库根
|
||||||
|
ROOT = Path(__file__).parent.parent.resolve()
|
||||||
|
|
||||||
|
# ========== 文件提取 ==========
|
||||||
|
|
||||||
|
def extract_content(file_path):
|
||||||
|
"""提取文件内容(支持 PDF/CSV/markdown/txt)"""
|
||||||
|
path = Path(file_path)
|
||||||
|
ext = path.suffix.lower()
|
||||||
|
|
||||||
|
if ext == '.pdf':
|
||||||
|
try:
|
||||||
|
import pdfplumber
|
||||||
|
except ImportError:
|
||||||
|
print("WARN: PDF 提取需要 pdfplumber。运行:pip3 install pdfplumber", file=sys.stderr)
|
||||||
|
return f"[PDF文件:{path.name},无法提取文本]"
|
||||||
|
|
||||||
|
try:
|
||||||
|
with pdfplumber.open(file_path) as pdf:
|
||||||
|
text = "\n\n".join(page.extract_text() or "" for page in pdf.pages)
|
||||||
|
return text.strip() or "[PDF无文本内容]"
|
||||||
|
except Exception as e:
|
||||||
|
return f"[PDF提取失败:{e}]"
|
||||||
|
|
||||||
|
elif ext == '.csv':
|
||||||
|
try:
|
||||||
|
import pandas as pd
|
||||||
|
except ImportError:
|
||||||
|
print("WARN: CSV 处理需要 pandas。运行:pip3 install pandas", file=sys.stderr)
|
||||||
|
return f"[CSV文件:{path.name},无法解析]"
|
||||||
|
|
||||||
|
try:
|
||||||
|
df = pd.read_csv(file_path)
|
||||||
|
# 限制行数,避免太长
|
||||||
|
if len(df) > 100:
|
||||||
|
preview = df.head(50).to_string()
|
||||||
|
return f"{preview}\n\n... (共{len(df)}行,仅显示前50行)"
|
||||||
|
return df.to_string()
|
||||||
|
except Exception as e:
|
||||||
|
return f"[CSV解析失败:{e}]"
|
||||||
|
|
||||||
|
elif ext in ['.md', '.txt']:
|
||||||
|
try:
|
||||||
|
return path.read_text(encoding='utf-8')
|
||||||
|
except Exception as e:
|
||||||
|
return f"[文件读取失败:{e}]"
|
||||||
|
|
||||||
|
else:
|
||||||
|
return f"[不支持的文件类型:{ext}]"
|
||||||
|
|
||||||
|
|
||||||
|
# ========== Claude API 分析 ==========
|
||||||
|
|
||||||
|
def analyze_with_claude(content, file_path):
|
||||||
|
"""调用 Claude API 分析文件内容"""
|
||||||
|
api_key = os.environ.get("ANTHROPIC_API_KEY")
|
||||||
|
if not api_key:
|
||||||
|
print("ERROR: 需要设置 ANTHROPIC_API_KEY 环境变量", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
client = anthropic.Anthropic(api_key=api_key)
|
||||||
|
|
||||||
|
# 限制内容长度(避免超token)
|
||||||
|
content_preview = content[:8000] if len(content) > 8000 else content
|
||||||
|
|
||||||
|
prompt = f"""分析这个文件,返回 JSON 格式的分析结果。
|
||||||
|
|
||||||
|
文件路径:{file_path}
|
||||||
|
内容(前8000字符):
|
||||||
|
{content_preview}
|
||||||
|
|
||||||
|
请判断:
|
||||||
|
1. **type**(必须是以下之一):
|
||||||
|
- "doc":外部文档、参考资料、飞书文档
|
||||||
|
- "decision":决策记录、方案选择、重要决定
|
||||||
|
- "conversation":会议纪要、聊天记录、讨论总结
|
||||||
|
|
||||||
|
2. **title**:一句话标题(中文,20字内)
|
||||||
|
|
||||||
|
3. **description**:一句话描述(中文,50字内,说清楚这是什么内容)
|
||||||
|
|
||||||
|
4. **tags**:3-5个关键词(中文,用于分类和检索)
|
||||||
|
|
||||||
|
5. **key_points**:提炼3-5条核心要点(markdown格式,每条1-2句话)
|
||||||
|
|
||||||
|
6. **project**:建议的项目代号(英文/拼音,kebab-case,如 ai-workflow、meeting-2024q3)
|
||||||
|
- 如果是会议记录 → meeting-YYYY-qN
|
||||||
|
- 如果是某个主题的文档 → 主题-拼音
|
||||||
|
- 如果无法判断 → misc
|
||||||
|
|
||||||
|
返回格式(纯JSON,不要markdown代码块):
|
||||||
|
{{
|
||||||
|
"type": "doc",
|
||||||
|
"title": "...",
|
||||||
|
"description": "...",
|
||||||
|
"tags": ["...", "...", "..."],
|
||||||
|
"key_points": "- 要点1\\n- 要点2\\n- 要点3",
|
||||||
|
"project": "..."
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = client.messages.create(
|
||||||
|
model="claude-sonnet-3-5-20240620",
|
||||||
|
max_tokens=2000,
|
||||||
|
messages=[{"role": "user", "content": prompt}]
|
||||||
|
)
|
||||||
|
|
||||||
|
result_text = response.content[0].text.strip()
|
||||||
|
|
||||||
|
# 移除可能的 markdown 代码块标记
|
||||||
|
if result_text.startswith("```"):
|
||||||
|
lines = result_text.split("\n")
|
||||||
|
result_text = "\n".join(lines[1:-1])
|
||||||
|
|
||||||
|
return json.loads(result_text)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR: Claude API 调用失败:{e}", file=sys.stderr)
|
||||||
|
# 返回默认值
|
||||||
|
return {
|
||||||
|
"type": "doc",
|
||||||
|
"title": Path(file_path).stem,
|
||||||
|
"description": "自动导入的文档",
|
||||||
|
"tags": ["待分类"],
|
||||||
|
"key_points": "[AI分析失败,需要手动补充]",
|
||||||
|
"project": "misc"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ========== 生成 frontmatter ==========
|
||||||
|
|
||||||
|
def generate_frontmatter(analysis, source_file):
|
||||||
|
"""生成完整 frontmatter(符合 kb-contract.yaml)"""
|
||||||
|
|
||||||
|
# 判断 source(来源)
|
||||||
|
source_path = Path(source_file).resolve()
|
||||||
|
if "飞书同步" in str(source_path):
|
||||||
|
source = "飞书文档"
|
||||||
|
elif "会议记录" in str(source_path):
|
||||||
|
source = "会议"
|
||||||
|
else:
|
||||||
|
source = "NAS"
|
||||||
|
|
||||||
|
fm = {
|
||||||
|
"type": analysis["type"],
|
||||||
|
"title": analysis["title"],
|
||||||
|
"description": analysis["description"],
|
||||||
|
"tags": analysis["tags"],
|
||||||
|
"timestamp": str(date.today()),
|
||||||
|
"source": source,
|
||||||
|
"source_link": source_file if source_file.startswith("http") else f"file://{source_path}",
|
||||||
|
"status": "seed",
|
||||||
|
"created": str(date.today()),
|
||||||
|
"ingested": str(date.today()),
|
||||||
|
}
|
||||||
|
|
||||||
|
return "---\n" + yaml.dump(fm, allow_unicode=True, default_flow_style=False) + "---\n"
|
||||||
|
|
||||||
|
|
||||||
|
# ========== 保存知识页 ==========
|
||||||
|
|
||||||
|
def save_to_kb(content, analysis, source_file, project_override=None):
|
||||||
|
"""保存到知识库 projects/"""
|
||||||
|
|
||||||
|
project = project_override or analysis["project"]
|
||||||
|
|
||||||
|
# type → 子目录映射
|
||||||
|
type_dir = {
|
||||||
|
"doc": "docs",
|
||||||
|
"decision": "decisions",
|
||||||
|
"conversation": "conversations"
|
||||||
|
}.get(analysis["type"], "docs")
|
||||||
|
|
||||||
|
# 项目目录
|
||||||
|
project_dir = ROOT / "projects" / project
|
||||||
|
|
||||||
|
# 如果项目不存在,用 kb-init.sh 创建
|
||||||
|
if not project_dir.exists():
|
||||||
|
print(f"[创建项目] {project}")
|
||||||
|
result = subprocess.run(
|
||||||
|
["bash", str(ROOT / "tools" / "kb-init.sh"), project],
|
||||||
|
cwd=ROOT,
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"ERROR: kb-init.sh 失败:{result.stderr}", file=sys.stderr)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 生成文件名(slug化)
|
||||||
|
def slugify(text):
|
||||||
|
import re
|
||||||
|
# 移除特殊字符,保留中文、字母、数字
|
||||||
|
text = re.sub(r'[^\w\s一-鿿-]', '', text)
|
||||||
|
# 空格替换为 -
|
||||||
|
text = re.sub(r'[\s]+', '-', text)
|
||||||
|
return text.strip('-')[:50] # 限制长度
|
||||||
|
|
||||||
|
filename = slugify(analysis["title"]) + ".md"
|
||||||
|
output_path = project_dir / type_dir / filename
|
||||||
|
|
||||||
|
# 生成完整内容
|
||||||
|
frontmatter = generate_frontmatter(analysis, source_file)
|
||||||
|
full_content = f"{frontmatter}\n{analysis['key_points']}\n\n## 原始文件\n\n文件:`{Path(source_file).name}`\n\n"
|
||||||
|
|
||||||
|
# 保存
|
||||||
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
output_path.write_text(full_content, encoding='utf-8')
|
||||||
|
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
|
||||||
|
# ========== 主函数 ==========
|
||||||
|
|
||||||
|
def process_file(input_path, project=None):
|
||||||
|
"""处理单个文件"""
|
||||||
|
print(f"\n===== 处理文件:{input_path} =====")
|
||||||
|
|
||||||
|
# 1. 提取内容
|
||||||
|
print("[1/5] 提取文件内容...")
|
||||||
|
content = extract_content(input_path)
|
||||||
|
if not content or len(content) < 10:
|
||||||
|
print(f"WARN: 文件内容为空或太短,跳过")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 2. Claude 分析
|
||||||
|
print("[2/5] Claude API 分析...")
|
||||||
|
analysis = analyze_with_claude(content, input_path)
|
||||||
|
print(f" - type: {analysis['type']}")
|
||||||
|
print(f" - title: {analysis['title']}")
|
||||||
|
print(f" - project: {analysis['project']}")
|
||||||
|
|
||||||
|
# 3. 保存到知识库
|
||||||
|
print("[3/5] 保存到知识库...")
|
||||||
|
output_path = save_to_kb(content, analysis, input_path, project)
|
||||||
|
if not output_path:
|
||||||
|
return None
|
||||||
|
print(f" ✓ 已保存:{output_path.relative_to(ROOT)}")
|
||||||
|
|
||||||
|
# 4. Lint 校验
|
||||||
|
print("[4/5] frontmatter 校验...")
|
||||||
|
result = subprocess.run(
|
||||||
|
["python3", str(ROOT / "tools" / "kb-lint-fm.py"), str(output_path)],
|
||||||
|
cwd=ROOT,
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
print(" ✓ 校验通过")
|
||||||
|
else:
|
||||||
|
print(f" ✗ 校验失败:\n{result.stdout}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 5. 提示 Git 提交
|
||||||
|
print("[5/5] 提示:")
|
||||||
|
print(f" git add {output_path.relative_to(ROOT)}")
|
||||||
|
print(f" git commit -m 'Add {analysis['title']}'")
|
||||||
|
print(f" git push")
|
||||||
|
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="原始物料 → 知识页")
|
||||||
|
parser.add_argument("--input", help="输入文件路径")
|
||||||
|
parser.add_argument("--input-dir", help="输入目录路径(批量处理)")
|
||||||
|
parser.add_argument("--project", help="指定项目代号(可选,否则 AI 自动判断)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not args.input and not args.input_dir:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 单文件处理
|
||||||
|
if args.input:
|
||||||
|
process_file(args.input, args.project)
|
||||||
|
|
||||||
|
# 批量处理
|
||||||
|
elif args.input_dir:
|
||||||
|
input_dir = Path(args.input_dir)
|
||||||
|
files = list(input_dir.glob("*"))
|
||||||
|
print(f"发现 {len(files)} 个文件")
|
||||||
|
|
||||||
|
for i, file_path in enumerate(files, 1):
|
||||||
|
if file_path.is_file():
|
||||||
|
print(f"\n[{i}/{len(files)}] {file_path.name}")
|
||||||
|
process_file(str(file_path), args.project)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
510
完整落地链路.md
Normal file
510
完整落地链路.md
Normal file
@@ -0,0 +1,510 @@
|
|||||||
|
# 公司知识库完整落地链路(端到端)
|
||||||
|
|
||||||
|
> 从现状到最终可用的完整路径,不是散点方案
|
||||||
|
> 更新时间:2026-07-16 18:50
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 最终目标
|
||||||
|
|
||||||
|
**一句话验证标准**:
|
||||||
|
```bash
|
||||||
|
cd /知识库目录
|
||||||
|
/kb-ask 万牛会L1怎么安排的?
|
||||||
|
# AI 返回:根据 [[课程大纲v1]](来源:飞书文档),万牛会L1...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📍 当前状态矩阵
|
||||||
|
|
||||||
|
| 组件 | 状态 | 位置 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| **知识库框架** | ✅ 完整 | company-kb-v0.1-mvp-20260707/ | 目录结构、契约、工具、命令都有 |
|
||||||
|
| **Git 真相源** | ✅ 已建 | http://192.168.0.101:3002/qianqian/company-kb | Gitea仓库,可push/pull |
|
||||||
|
| **本地工作副本** | ✅ 已建 | Synology Drive同步盘/company-kb/ | 就是知识库目录,有.git/ |
|
||||||
|
| **原始物料** | ⚠️ 未处理 | 飞书同步/(564文件)+ 会议记录/(4文件) | 没frontmatter,AI读不到 |
|
||||||
|
| **知识页** | ⚠️ 只有示例 | projects/wanniu-l1/(7页)+ _example/ | 真实内容很少 |
|
||||||
|
| **AI查询** | ⚠️ 能跑但没内容 | /kb-ask命令存在 | 能执行,但查不到东西(内容太少) |
|
||||||
|
| **自动化** | ❌ 不存在 | 无 | 飞书→知识库没有自动链路 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 完整数据流(端到端)
|
||||||
|
|
||||||
|
### 阶段 0:环境搭建(已完成✅)
|
||||||
|
|
||||||
|
```
|
||||||
|
NAS Gitea 真相源
|
||||||
|
↕ git push/pull
|
||||||
|
本地 company-kb/(工作副本)
|
||||||
|
↕ Synology Drive
|
||||||
|
NAS 物理存储
|
||||||
|
```
|
||||||
|
|
||||||
|
**验证**:
|
||||||
|
```bash
|
||||||
|
cd company-kb目录
|
||||||
|
git status # 能看到状态
|
||||||
|
git push # 能推送
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段 1:物料汇入(核心,当前卡在这里❌)
|
||||||
|
|
||||||
|
```
|
||||||
|
原始物料(飞书同步/、会议记录/)
|
||||||
|
↓
|
||||||
|
【处理脚本】kb-bridge.py
|
||||||
|
├─ 读取原始文件
|
||||||
|
├─ 提取文本(PDF用pdfplumber,CSV用pandas)
|
||||||
|
├─ 调用Claude API分析
|
||||||
|
│ └─ 判断type(doc/decision/conversation)
|
||||||
|
│ └─ 生成title、description、tags
|
||||||
|
│ └─ 提炼关键内容
|
||||||
|
├─ 生成完整frontmatter
|
||||||
|
│ ├─ type: doc
|
||||||
|
│ ├─ title: ...
|
||||||
|
│ ├─ source: 飞书文档
|
||||||
|
│ ├─ source_link: file://...
|
||||||
|
│ ├─ status: seed
|
||||||
|
│ ├─ created: 2026-xx-xx
|
||||||
|
│ └─ ingested: 2026-07-16
|
||||||
|
└─ 保存到 projects/xxx/docs/或decisions/或conversations/
|
||||||
|
↓
|
||||||
|
合规知识页(.md + frontmatter)
|
||||||
|
↓
|
||||||
|
【校验】kb-lint-fm.py
|
||||||
|
└─ 检查frontmatter是否符合契约
|
||||||
|
↓
|
||||||
|
【提交】git add + git commit + git push
|
||||||
|
↓
|
||||||
|
Git 真相源(Gitea)
|
||||||
|
```
|
||||||
|
|
||||||
|
**验证**:
|
||||||
|
```bash
|
||||||
|
# 处理一个测试文件
|
||||||
|
python3 tools/kb-bridge.py \
|
||||||
|
--input "../飞书同步/AI内容反推工作流.csv" \
|
||||||
|
--output projects/ai-workflow/docs/
|
||||||
|
|
||||||
|
# 检查
|
||||||
|
python3 tools/kb-lint-fm.py projects/ai-workflow/
|
||||||
|
# 期望:错误 0
|
||||||
|
|
||||||
|
# 查看生成的文件
|
||||||
|
cat projects/ai-workflow/docs/AI内容反推工作流.md
|
||||||
|
# 期望:有完整frontmatter + 内容
|
||||||
|
|
||||||
|
# 提交
|
||||||
|
git add projects/ai-workflow/
|
||||||
|
git commit -m "Add AI工作流文档(测试)"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段 2:AI 查询(自然就能用✅)
|
||||||
|
|
||||||
|
```
|
||||||
|
你在本地工作副本
|
||||||
|
↓
|
||||||
|
cd company-kb目录
|
||||||
|
↓
|
||||||
|
/kb-ask 问题
|
||||||
|
↓
|
||||||
|
【Claude Code 执行】
|
||||||
|
├─ 扫描 projects/ 下所有 .md 文件
|
||||||
|
├─ 解析每个文件的 frontmatter
|
||||||
|
├─ 根据 type、tags 筛选相关文件
|
||||||
|
├─ 语义检索文件内容
|
||||||
|
├─ 综合答案
|
||||||
|
└─ 挂 source_link(从frontmatter读取)
|
||||||
|
↓
|
||||||
|
AI 返回答案 + 溯源链接
|
||||||
|
```
|
||||||
|
|
||||||
|
**验证**:
|
||||||
|
```bash
|
||||||
|
cd company-kb目录
|
||||||
|
/kb-ask AI内容反推工作流是什么?
|
||||||
|
# 期望:AI能找到刚才生成的知识页,返回答案 + 溯源
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段 3:自动化(可选,手动跑通后再建)
|
||||||
|
|
||||||
|
```
|
||||||
|
【定时任务】每天 10:00
|
||||||
|
↓
|
||||||
|
【扫描】知识库投递区/
|
||||||
|
├─ 发现新文件
|
||||||
|
├─ 调用 kb-bridge.py 处理
|
||||||
|
├─ lint 检查
|
||||||
|
├─ git commit + git push
|
||||||
|
└─ 记录日志
|
||||||
|
↓
|
||||||
|
知识库自动更新
|
||||||
|
```
|
||||||
|
|
||||||
|
**验证**:
|
||||||
|
```bash
|
||||||
|
# 在投递区丢个新文件
|
||||||
|
cp 新文件.pdf 知识库投递区/
|
||||||
|
|
||||||
|
# 第二天看
|
||||||
|
cd company-kb目录
|
||||||
|
git pull
|
||||||
|
ls projects/ # 应该能看到新项目
|
||||||
|
|
||||||
|
/kb-ask 新文件里的内容
|
||||||
|
# AI能查到
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 分阶段实施计划
|
||||||
|
|
||||||
|
### Phase 0:环境准备(已完成✅,今天)
|
||||||
|
|
||||||
|
- [x] NAS Gitea 真相源
|
||||||
|
- [x] 本地 Git 工作副本
|
||||||
|
- [x] 能 git push/pull
|
||||||
|
|
||||||
|
**用时**:已完成
|
||||||
|
**交付物**:Git仓库可用
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 1A:开发处理脚本(明天上午,3小时)
|
||||||
|
|
||||||
|
**任务**:
|
||||||
|
1. 创建 kb-bridge.py(核心脚本)
|
||||||
|
2. 实现功能:
|
||||||
|
- 读取文件(支持 PDF、CSV、markdown)
|
||||||
|
- 调用 Claude API 分析
|
||||||
|
- 生成 frontmatter
|
||||||
|
- 保存到 projects/
|
||||||
|
|
||||||
|
**交付物**:
|
||||||
|
- `tools/kb-bridge.py`(完整可运行)
|
||||||
|
- 依赖安装说明
|
||||||
|
- 使用文档
|
||||||
|
|
||||||
|
**验证标准**:
|
||||||
|
```bash
|
||||||
|
python3 tools/kb-bridge.py --input test.pdf --output projects/test/
|
||||||
|
ls projects/test/docs/test.md # 文件生成
|
||||||
|
python3 tools/kb-lint-fm.py projects/test/ # 错误 0
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 1B:测试处理流程(明天下午,2小时)
|
||||||
|
|
||||||
|
**任务**:
|
||||||
|
1. 选 5 个代表性文件测试:
|
||||||
|
- 1 个会议记录 PDF
|
||||||
|
- 1 个飞书同步 CSV
|
||||||
|
- 1 个 markdown 文件
|
||||||
|
- 1 个图片(测试 assets)
|
||||||
|
- 1 个长文档(测试分段)
|
||||||
|
|
||||||
|
2. 逐个处理 → lint → git push
|
||||||
|
|
||||||
|
3. AI 查询验证
|
||||||
|
|
||||||
|
**交付物**:
|
||||||
|
- 5 个测试项目生成
|
||||||
|
- Git 提交记录
|
||||||
|
- AI 查询测试通过
|
||||||
|
|
||||||
|
**验证标准**:
|
||||||
|
```bash
|
||||||
|
/kb-ask 会议纪要里的决策
|
||||||
|
# AI能找到并回答
|
||||||
|
|
||||||
|
/kb-ask AI工作流
|
||||||
|
# AI能找到并回答
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 1C:批量处理(明天晚上或后天,按需)
|
||||||
|
|
||||||
|
**任务**:
|
||||||
|
1. 批量处理所有 564 个飞书同步文件
|
||||||
|
2. 批量处理所有 4 个会议记录
|
||||||
|
3. 处理公司资产信息里的 md 文件
|
||||||
|
|
||||||
|
**方式**:
|
||||||
|
- 选项A:一次性全跑(API成本 ~$20-50)
|
||||||
|
- 选项B:分批跑(每天处理 50 个)
|
||||||
|
- 选项C:只处理重要的(手动挑选)
|
||||||
|
|
||||||
|
**交付物**:
|
||||||
|
- 所有原始物料变成知识页
|
||||||
|
- 全部 lint 通过
|
||||||
|
- 全部 git push
|
||||||
|
|
||||||
|
**验证标准**:
|
||||||
|
```bash
|
||||||
|
find projects/ -name "*.md" | wc -l
|
||||||
|
# 应该有几百个文件
|
||||||
|
|
||||||
|
/kb-ask 随机问题
|
||||||
|
# AI 能从大量知识页中找到答案
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 2:飞书机器人打通(下周)
|
||||||
|
|
||||||
|
**任务**:
|
||||||
|
1. bot-v2(阿里云)clone 知识库
|
||||||
|
2. 配置定期 git pull
|
||||||
|
3. 群里 @机器人 → 调 /kb-ask → 回复
|
||||||
|
|
||||||
|
**验证标准**:
|
||||||
|
```
|
||||||
|
飞书群里:@机器人 万牛会L1怎么安排?
|
||||||
|
机器人回复:根据[[课程大纲v1]](来源:飞书文档),...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 3:自动化(按需)
|
||||||
|
|
||||||
|
**任务**:
|
||||||
|
1. 创建定时任务脚本
|
||||||
|
2. 监控投递区新文件
|
||||||
|
3. 自动处理 + git push
|
||||||
|
|
||||||
|
**验证标准**:
|
||||||
|
- 丢个新文件到投递区
|
||||||
|
- 第二天自动出现在知识库
|
||||||
|
- AI 能查到
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 关键组件详细设计
|
||||||
|
|
||||||
|
### kb-bridge.py 核心逻辑
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
知识桥接脚本:原始物料 → 知识页
|
||||||
|
"""
|
||||||
|
import anthropic
|
||||||
|
import yaml
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import pdfplumber # PDF提取
|
||||||
|
import pandas as pd # CSV处理
|
||||||
|
|
||||||
|
def process_file(input_path, output_dir, kb_root):
|
||||||
|
"""处理单个文件"""
|
||||||
|
# 1. 读取文件内容
|
||||||
|
content = extract_content(input_path)
|
||||||
|
|
||||||
|
# 2. 调用 Claude API 分析
|
||||||
|
analysis = analyze_with_claude(content, input_path)
|
||||||
|
# 返回:{
|
||||||
|
# "type": "doc",
|
||||||
|
# "title": "...",
|
||||||
|
# "description": "...",
|
||||||
|
# "tags": ["...", "..."],
|
||||||
|
# "key_points": "...",
|
||||||
|
# "project": "ai-workflow" # AI建议的项目归属
|
||||||
|
# }
|
||||||
|
|
||||||
|
# 3. 生成 frontmatter
|
||||||
|
frontmatter = generate_frontmatter(analysis, input_path)
|
||||||
|
|
||||||
|
# 4. 生成内容(原文 + AI提炼)
|
||||||
|
content_md = f"{frontmatter}\n\n{analysis['key_points']}\n\n## 原始内容\n\n{content}"
|
||||||
|
|
||||||
|
# 5. 保存
|
||||||
|
save_to_kb(content_md, analysis, output_dir, kb_root)
|
||||||
|
|
||||||
|
return analysis
|
||||||
|
|
||||||
|
def extract_content(file_path):
|
||||||
|
"""提取文件内容"""
|
||||||
|
ext = Path(file_path).suffix.lower()
|
||||||
|
|
||||||
|
if ext == '.pdf':
|
||||||
|
with pdfplumber.open(file_path) as pdf:
|
||||||
|
return "\n".join(page.extract_text() for page in pdf.pages)
|
||||||
|
|
||||||
|
elif ext == '.csv':
|
||||||
|
df = pd.read_csv(file_path)
|
||||||
|
return df.to_string()
|
||||||
|
|
||||||
|
elif ext in ['.md', '.txt']:
|
||||||
|
return Path(file_path).read_text()
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ValueError(f"不支持的文件类型:{ext}")
|
||||||
|
|
||||||
|
def analyze_with_claude(content, source_file):
|
||||||
|
"""Claude API 分析"""
|
||||||
|
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
|
||||||
|
|
||||||
|
prompt = f"""分析这个文件,返回 JSON:
|
||||||
|
|
||||||
|
文件:{source_file}
|
||||||
|
内容(前5000字符):
|
||||||
|
{content[:5000]}
|
||||||
|
|
||||||
|
返回格式:
|
||||||
|
{{
|
||||||
|
"type": "doc|decision|conversation", // 判断文档类型
|
||||||
|
"title": "一句话标题",
|
||||||
|
"description": "一句话描述",
|
||||||
|
"tags": ["关键词1", "关键词2"],
|
||||||
|
"key_points": "3-5条关键要点(markdown格式)",
|
||||||
|
"project": "建议的项目代号(kebab-case)"
|
||||||
|
}}
|
||||||
|
|
||||||
|
判断规则:
|
||||||
|
- type=doc:外部文档、参考资料
|
||||||
|
- type=decision:决策记录、方案选择
|
||||||
|
- type=conversation:会议纪要、聊天记录
|
||||||
|
"""
|
||||||
|
|
||||||
|
response = client.messages.create(
|
||||||
|
model="claude-sonnet-3-5-20240620",
|
||||||
|
max_tokens=2000,
|
||||||
|
messages=[{"role": "user", "content": prompt}]
|
||||||
|
)
|
||||||
|
|
||||||
|
return parse_json(response.content[0].text)
|
||||||
|
|
||||||
|
def generate_frontmatter(analysis, source_file):
|
||||||
|
"""生成 frontmatter"""
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
fm = {
|
||||||
|
"type": analysis["type"],
|
||||||
|
"title": analysis["title"],
|
||||||
|
"description": analysis["description"],
|
||||||
|
"tags": analysis["tags"],
|
||||||
|
"timestamp": str(date.today()),
|
||||||
|
"source": guess_source(source_file), # 飞书文档/会议/NAS
|
||||||
|
"source_link": f"file://{os.path.abspath(source_file)}",
|
||||||
|
"status": "seed",
|
||||||
|
"created": str(date.today()),
|
||||||
|
"ingested": str(date.today()),
|
||||||
|
}
|
||||||
|
|
||||||
|
return "---\n" + yaml.dump(fm, allow_unicode=True) + "---\n"
|
||||||
|
|
||||||
|
def save_to_kb(content, analysis, output_dir, kb_root):
|
||||||
|
"""保存到知识库"""
|
||||||
|
project = analysis["project"]
|
||||||
|
type_dir = {
|
||||||
|
"doc": "docs",
|
||||||
|
"decision": "decisions",
|
||||||
|
"conversation": "conversations"
|
||||||
|
}[analysis["type"]]
|
||||||
|
|
||||||
|
# 创建项目目录(如果不存在)
|
||||||
|
project_dir = Path(kb_root) / "projects" / project
|
||||||
|
if not project_dir.exists():
|
||||||
|
# 用 kb-init.sh 创建骨架
|
||||||
|
os.system(f"cd {kb_root} && bash tools/kb-init.sh {project}")
|
||||||
|
|
||||||
|
# 保存文件
|
||||||
|
output_path = project_dir / type_dir / f"{slugify(analysis['title'])}.md"
|
||||||
|
output_path.write_text(content)
|
||||||
|
|
||||||
|
print(f"✓ Saved: {output_path}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import argparse
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--input", required=True, help="输入文件路径")
|
||||||
|
parser.add_argument("--output", help="输出目录(可选,默认自动判断)")
|
||||||
|
parser.add_argument("--kb-root", default=".", help="知识库根目录")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
process_file(args.input, args.output, args.kb_root)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 成本估算
|
||||||
|
|
||||||
|
### API 成本(Claude API)
|
||||||
|
- 每个文件处理:~$0.05-0.10(取决于大小)
|
||||||
|
- 564 个飞书文件:~$30-60
|
||||||
|
- 4 个会议记录:~$1-2
|
||||||
|
- **总计**:~$30-70
|
||||||
|
|
||||||
|
### 时间成本
|
||||||
|
- Phase 1A(开发脚本):3 小时
|
||||||
|
- Phase 1B(测试):2 小时
|
||||||
|
- Phase 1C(批量处理):API调用时间 + 1小时监控
|
||||||
|
- **总计**:1-2 天
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ 验收标准(怎么算完成)
|
||||||
|
|
||||||
|
### Phase 1 完成标准
|
||||||
|
```bash
|
||||||
|
# 1. 能处理各种文件
|
||||||
|
python3 tools/kb-bridge.py --input test.pdf # 成功
|
||||||
|
|
||||||
|
# 2. 生成的文件合规
|
||||||
|
python3 tools/kb-lint-fm.py projects/ # 错误 0
|
||||||
|
|
||||||
|
# 3. AI 能查到
|
||||||
|
cd company-kb目录
|
||||||
|
/kb-ask 随机问题 # 能返回答案 + 溯源
|
||||||
|
|
||||||
|
# 4. Git 有记录
|
||||||
|
git log # 能看到提交历史
|
||||||
|
|
||||||
|
# 5. 同事能用
|
||||||
|
# 同事 git pull → /kb-ask → 也能查到
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 风险与对策
|
||||||
|
|
||||||
|
### 风险 1:API 成本超预算
|
||||||
|
**对策**:先测试 10 个文件,评估成本,再决定是否全量处理
|
||||||
|
|
||||||
|
### 风险 2:AI 分析不准确
|
||||||
|
**对策**:Phase 1B 测试时人工核查,调整 prompt
|
||||||
|
|
||||||
|
### 风险 3:文件格式太复杂
|
||||||
|
**对策**:复杂文件先跳过,手动处理
|
||||||
|
|
||||||
|
### 风险 4:知识页太多,AI 检索不准
|
||||||
|
**对策**:先做到 P1(100页内),检索不准时再考虑 P4/P5(编译层/向量库)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📌 立刻开始(今晚或明天)
|
||||||
|
|
||||||
|
**今晚**:
|
||||||
|
1. 我生成完整的 kb-bridge.py
|
||||||
|
2. 你配置依赖(`pip3 install anthropic pdfplumber pandas pyyaml`)
|
||||||
|
3. 测试处理 1 个文件
|
||||||
|
|
||||||
|
**明天**:
|
||||||
|
1. 测试 5 个代表性文件
|
||||||
|
2. 验证 AI 能查到
|
||||||
|
3. 决定是否批量处理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**这次是完整链路,不是散点。你觉得清楚了吗?**
|
||||||
Reference in New Issue
Block a user