fix: 飞书机器人群聊回复逻辑
This commit is contained in:
409
GETTING_STARTED.md
Normal file
409
GETTING_STARTED.md
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
# GenericAgent 新手上手指南
|
||||||
|
|
||||||
|
## 🎯 什么是 GenericAgent?
|
||||||
|
|
||||||
|
GenericAgent 是一个极简、可自我进化的自主 Agent 框架。它的核心只有 **~3,300 行代码**,通过 **7 个原子工具 + 92 行 Agent Loop**,就能赋予任意 LLM 对你本地计算机的系统级控制能力。
|
||||||
|
|
||||||
|
**它能做什么?**
|
||||||
|
- 控制浏览器(保留登录态)
|
||||||
|
- 执行终端命令
|
||||||
|
- 读写文件系统
|
||||||
|
- 模拟键盘鼠标
|
||||||
|
- 控制移动设备(ADB)
|
||||||
|
- 屏幕视觉识别
|
||||||
|
|
||||||
|
**最特别的是什么?**
|
||||||
|
|
||||||
|
GenericAgent 不预设技能,而是**靠进化获得能力**。每解决一个新任务,它就会自动将执行路径固化为 Skill,供后续直接调用。使用时间越长,沉淀的技能越多,最终形成一棵完全属于你的专属技能树。
|
||||||
|
|
||||||
|
> 🤖 **自举实证**: 本项目的所有 Git 操作,从 `git init` 到每一条 commit message,都是 GenericAgent 自主完成的。作者全程未打开过一次终端。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 5 分钟快速开始
|
||||||
|
|
||||||
|
### 第一步:安装部署
|
||||||
|
|
||||||
|
**方法一:标准安装(推荐开发者)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 克隆仓库
|
||||||
|
git clone https://github.com/lsdefine/GenericAgent.git
|
||||||
|
cd GenericAgent
|
||||||
|
|
||||||
|
# 2. 安装最小依赖
|
||||||
|
pip install streamlit pywebview
|
||||||
|
|
||||||
|
# 3. 配置 API Key
|
||||||
|
cp mykey_template.py mykey.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**方法二:Windows 便携版(推荐新手)**
|
||||||
|
|
||||||
|
1. [下载便携版](http://kw.fudan.edu.cn/resources/PC-Agent-Portable.zip) (19MB)
|
||||||
|
2. 解压到任意目录
|
||||||
|
3. 双击运行即可
|
||||||
|
|
||||||
|
**方法三:Android(Termux)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /sdcard/ga
|
||||||
|
python agentmain.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第二步:配置 API Key
|
||||||
|
|
||||||
|
编辑 `mykey.py` 文件,填入你的 LLM API Key:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 示例:使用 Claude
|
||||||
|
api_key = "sk-ant-xxx" # 你的 Anthropic API Key
|
||||||
|
model = "claude-3-5-sonnet-20241022"
|
||||||
|
|
||||||
|
# 或使用其他模型
|
||||||
|
# api_key = "your_openai_key"
|
||||||
|
# model = "gpt-4"
|
||||||
|
```
|
||||||
|
|
||||||
|
支持的模型:
|
||||||
|
- Claude (Anthropic)
|
||||||
|
- GPT-4 / GPT-3.5 (OpenAI)
|
||||||
|
- Gemini (Google)
|
||||||
|
- Kimi (Moonshot)
|
||||||
|
- 其他兼容 OpenAI API 的模型
|
||||||
|
|
||||||
|
### 第三步:启动界面
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python launch.pyw
|
||||||
|
```
|
||||||
|
|
||||||
|
启动后会出现一个桌面悬浮窗,你可以直接在里面输入任务指令。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 基础使用
|
||||||
|
|
||||||
|
### 你的第一个任务
|
||||||
|
|
||||||
|
启动后,试试这些简单的任务:
|
||||||
|
|
||||||
|
**文件操作:**
|
||||||
|
```
|
||||||
|
"帮我在桌面创建一个 hello.txt 文件,内容是 Hello World"
|
||||||
|
```
|
||||||
|
|
||||||
|
**网页浏览:**
|
||||||
|
```
|
||||||
|
"打开百度,搜索今天的天气"
|
||||||
|
```
|
||||||
|
|
||||||
|
**代码执行:**
|
||||||
|
```
|
||||||
|
"用 Python 计算 1 到 100 的和"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 理解 Agent 的工作方式
|
||||||
|
|
||||||
|
GenericAgent 的工作流程:
|
||||||
|
|
||||||
|
```
|
||||||
|
你的指令 → Agent 理解任务 → 调用工具执行 → 返回结果 → 沉淀经验
|
||||||
|
```
|
||||||
|
|
||||||
|
**核心循环(92 行代码):**
|
||||||
|
1. **感知**: 读取当前环境状态
|
||||||
|
2. **推理**: 分析任务,制定执行计划
|
||||||
|
3. **执行**: 调用工具完成操作
|
||||||
|
4. **记忆**: 将成功的执行路径写入记忆层
|
||||||
|
5. **循环**: 继续下一步,直到任务完成
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧬 进阶:Skill 系统
|
||||||
|
|
||||||
|
### 什么是 Skill?
|
||||||
|
|
||||||
|
Skill 是 GenericAgent 自动沉淀的任务执行模板。当你第一次完成某个任务时,Agent 会将整个执行流程固化为一个 Skill,下次遇到类似任务就能直接调用。
|
||||||
|
|
||||||
|
**举个例子:**
|
||||||
|
|
||||||
|
| 你说的话 | 第一次 Agent 做了什么 | 之后每次 |
|
||||||
|
|---------|---------------------|---------|
|
||||||
|
| "监控股票并提醒我" | 安装 mootdx → 构建选股流程 → 配置定时任务 → 保存 Skill | **一句话启动** |
|
||||||
|
| "用 Gmail 发这个文件" | 配置 OAuth → 编写发送脚本 → 保存 Skill | **直接可用** |
|
||||||
|
|
||||||
|
### 如何使用 Skill?
|
||||||
|
|
||||||
|
Skill 会自动保存在 `memory/` 目录下。你不需要手动管理,Agent 会在需要时自动调用。
|
||||||
|
|
||||||
|
**查看已有的 Skill:**
|
||||||
|
```bash
|
||||||
|
ls memory/*.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**手动触发 Skill:**
|
||||||
|
```
|
||||||
|
"使用之前保存的股票监控 Skill"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 培养你的专属 Agent
|
||||||
|
|
||||||
|
使用时间越长,你的 Agent 积累的 Skill 越多:
|
||||||
|
|
||||||
|
- **第 1 周**: 基础文件操作、简单网页浏览
|
||||||
|
- **第 1 个月**: 自动化工作流、数据处理脚本
|
||||||
|
- **第 3 个月**: 复杂的多步骤任务、跨平台操作
|
||||||
|
- **第 6 个月**: 一套任何人都没有的专属技能树
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎮 实用玩法
|
||||||
|
|
||||||
|
### 浏览器自动化
|
||||||
|
|
||||||
|
GenericAgent 注入真实浏览器,保留你的登录态:
|
||||||
|
|
||||||
|
```
|
||||||
|
"打开淘宝,搜索 iPhone 15,按价格排序"
|
||||||
|
"登录我的 Gmail,查看未读邮件"
|
||||||
|
"在美团上帮我点一杯奶茶"
|
||||||
|
```
|
||||||
|
|
||||||
|
**可用工具:**
|
||||||
|
- `web_scan`: 读取网页内容
|
||||||
|
- `web_execute_js`: 执行 JavaScript 控制页面
|
||||||
|
|
||||||
|
### 文件和代码处理
|
||||||
|
|
||||||
|
```
|
||||||
|
"分析这个 Python 项目的代码结构"
|
||||||
|
"把这个 CSV 文件转换成 Excel"
|
||||||
|
"批量重命名这个文件夹里的图片"
|
||||||
|
```
|
||||||
|
|
||||||
|
**可用工具:**
|
||||||
|
- `file_read`: 读取文件
|
||||||
|
- `file_write`: 写入文件
|
||||||
|
- `file_patch`: 精确修改文件
|
||||||
|
- `code_run`: 执行代码
|
||||||
|
|
||||||
|
### 移动设备控制(ADB)
|
||||||
|
|
||||||
|
通过 ADB 控制 Android 设备:
|
||||||
|
|
||||||
|
```
|
||||||
|
"打开支付宝,查看我的账单"
|
||||||
|
"在微信上给张三发消息"
|
||||||
|
"截取手机屏幕并保存"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 定时任务
|
||||||
|
|
||||||
|
```
|
||||||
|
"每天早上 8 点提醒我查看邮件"
|
||||||
|
"每小时检查一次股票价格"
|
||||||
|
"每周一生成上周的工作总结"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤖 接入聊天平台(可选)
|
||||||
|
|
||||||
|
### 为什么要接入 Bot?
|
||||||
|
|
||||||
|
将 GenericAgent 接入聊天平台后,你可以:
|
||||||
|
- 随时随地通过手机控制你的电脑
|
||||||
|
- 多人协作使用同一个 Agent
|
||||||
|
- 接收 Agent 的主动通知和提醒
|
||||||
|
|
||||||
|
### 支持的平台
|
||||||
|
|
||||||
|
- **QQ Bot**: WebSocket 长连接,无需公网 webhook
|
||||||
|
- **飞书(Lark)**: 支持富文本、图片、文件、音频
|
||||||
|
- **企业微信(WeCom)**: 企业内部使用
|
||||||
|
- **钉钉(DingTalk)**: 企业协作
|
||||||
|
- **Telegram**: 国际用户
|
||||||
|
|
||||||
|
### 快速配置指南
|
||||||
|
|
||||||
|
**以 QQ Bot 为例:**
|
||||||
|
|
||||||
|
1. 在 [QQ 开放平台](https://q.qq.com) 创建机器人
|
||||||
|
2. 获取 AppID 和 AppSecret
|
||||||
|
3. 在 `mykey.py` 中配置:
|
||||||
|
|
||||||
|
```python
|
||||||
|
qq_app_id = "YOUR_APP_ID"
|
||||||
|
qq_app_secret = "YOUR_APP_SECRET"
|
||||||
|
qq_allowed_users = ["YOUR_USER_OPENID"] # 或 ['*'] 公开访问
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 启动 Bot:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install qq-botpy
|
||||||
|
python qqapp.py
|
||||||
|
# 或与桌面窗口一起启动
|
||||||
|
python launch.pyw --qq
|
||||||
|
```
|
||||||
|
|
||||||
|
**其他平台配置详见:**
|
||||||
|
- 飞书: [assets/SETUP_FEISHU.md](assets/SETUP_FEISHU.md)
|
||||||
|
- 企业微信: `python launch.pyw --wecom`
|
||||||
|
- 钉钉: `python launch.pyw --dingtalk`
|
||||||
|
- Telegram: `python tgapp.py`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 记忆系统详解
|
||||||
|
|
||||||
|
### 三层记忆架构
|
||||||
|
|
||||||
|
GenericAgent 的记忆系统分为三层:
|
||||||
|
|
||||||
|
**L0 — 元规则(Meta Rules)**
|
||||||
|
- Agent 的基础行为规则
|
||||||
|
- 系统约束和安全边界
|
||||||
|
- 不可修改的核心逻辑
|
||||||
|
|
||||||
|
**L2 — 全局事实(Global Facts)**
|
||||||
|
- 长期运行中积累的稳定知识
|
||||||
|
- 环境配置、用户偏好
|
||||||
|
- 存储在 `memory/global_mem.txt`
|
||||||
|
|
||||||
|
**L3 — 任务 Skills(SOPs)**
|
||||||
|
- 完成特定任务的操作流程
|
||||||
|
- 自动沉淀的执行模板
|
||||||
|
- 存储在 `memory/*.md`
|
||||||
|
|
||||||
|
### 如何查看和管理记忆
|
||||||
|
|
||||||
|
**查看全局记忆:**
|
||||||
|
```bash
|
||||||
|
cat memory/global_mem.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
**查看所有 Skills:**
|
||||||
|
```bash
|
||||||
|
ls memory/*.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**手动编辑记忆:**
|
||||||
|
```bash
|
||||||
|
# 不建议新手操作,Agent 会自动管理
|
||||||
|
vim memory/global_mem.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 常见问题
|
||||||
|
|
||||||
|
### 安装问题
|
||||||
|
|
||||||
|
**Q: pip install 失败怎么办?**
|
||||||
|
|
||||||
|
A: 尝试使用国内镜像源:
|
||||||
|
```bash
|
||||||
|
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple streamlit pywebview
|
||||||
|
```
|
||||||
|
|
||||||
|
**Q: Windows 上启动失败?**
|
||||||
|
|
||||||
|
A: 建议使用便携版,或检查 Python 版本(需要 3.8+)
|
||||||
|
|
||||||
|
### 使用问题
|
||||||
|
|
||||||
|
**Q: Agent 执行任务失败?**
|
||||||
|
|
||||||
|
A: 检查:
|
||||||
|
1. API Key 是否正确配置
|
||||||
|
2. 网络连接是否正常
|
||||||
|
3. 查看 `temp/` 目录下的日志文件
|
||||||
|
|
||||||
|
**Q: 如何让 Agent 停止当前任务?**
|
||||||
|
|
||||||
|
A: 在界面中输入 "停止" 或 "取消"
|
||||||
|
|
||||||
|
**Q: Agent 的回复太慢?**
|
||||||
|
|
||||||
|
A: 可以尝试:
|
||||||
|
1. 切换到更快的模型(如 GPT-3.5)
|
||||||
|
2. 减少任务的复杂度
|
||||||
|
3. 检查网络延迟
|
||||||
|
|
||||||
|
### 进阶问题
|
||||||
|
|
||||||
|
**Q: 如何自定义 Agent 的行为?**
|
||||||
|
|
||||||
|
A: 编辑 `memory/global_mem.txt`,添加你的偏好设置
|
||||||
|
|
||||||
|
**Q: 如何备份我的 Skills?**
|
||||||
|
|
||||||
|
A: 直接复制 `memory/` 目录即可
|
||||||
|
|
||||||
|
**Q: 可以同时运行多个 Agent 实例吗?**
|
||||||
|
|
||||||
|
A: 可以,但需要使用不同的工作目录
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💪 最佳实践
|
||||||
|
|
||||||
|
### 新手建议
|
||||||
|
|
||||||
|
1. **从简单任务开始**: 先尝试文件操作、网页浏览等基础任务
|
||||||
|
2. **观察 Agent 的执行过程**: 理解它是如何调用工具的
|
||||||
|
3. **让 Agent 自己探索**: 不要过度干预,让它自主学习
|
||||||
|
4. **定期查看 Skills**: 了解 Agent 积累了哪些能力
|
||||||
|
|
||||||
|
### 进阶技巧
|
||||||
|
|
||||||
|
1. **组合使用多个工具**: 让 Agent 完成复杂的多步骤任务
|
||||||
|
2. **利用记忆系统**: 在 `global_mem.txt` 中记录常用的配置和偏好
|
||||||
|
3. **编写自定义脚本**: 通过 `code_run` 扩展 Agent 的能力
|
||||||
|
4. **接入多个聊天平台**: 实现跨平台的统一控制
|
||||||
|
|
||||||
|
### 安全注意事项
|
||||||
|
|
||||||
|
1. **不要在公共环境运行**: Agent 有系统级权限,注意安全
|
||||||
|
2. **谨慎授权**: 使用 Bot 时,设置 `allowed_users` 白名单
|
||||||
|
3. **定期备份**: 重要的 Skills 和配置要及时备份
|
||||||
|
4. **监控执行日志**: 定期检查 `temp/` 目录下的日志
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌟 社区和支持
|
||||||
|
|
||||||
|
### 获取帮助
|
||||||
|
|
||||||
|
- **GitHub Issues**: [提交问题](https://github.com/lsdefine/GenericAgent/issues)
|
||||||
|
- **微信交流群**: 扫描下方二维码加入
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="assets/images/wechat_group.jpg" width="280"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### 贡献指南
|
||||||
|
|
||||||
|
欢迎贡献代码、文档或 Skills!
|
||||||
|
|
||||||
|
1. Fork 本仓库
|
||||||
|
2. 创建你的特性分支
|
||||||
|
3. 提交你的改动
|
||||||
|
4. 发起 Pull Request
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📖 延伸阅读
|
||||||
|
|
||||||
|
- [完整 README](README.md) - 项目详细介绍
|
||||||
|
- [新用户欢迎文档](WELCOME_NEW_USER.md) - 详细的引导流程
|
||||||
|
- [快速开始指南](QUICK_START.pdf) - PDF 版本的快速入门
|
||||||
|
- [飞书配置指南](assets/SETUP_FEISHU.md) - 飞书 Bot 详细配置
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**🎉 现在,开始你的 GenericAgent 之旅吧!**
|
||||||
|
|
||||||
|
记住: GenericAgent 不是一个预设好的工具,而是一个会随着你的使用不断进化的伙伴。使用时间越长,它就越懂你,越强大。
|
||||||
76
fsapp.py
76
fsapp.py
@@ -245,15 +245,15 @@ def _card(text):
|
|||||||
return json.dumps({"config": {"wide_screen_mode": True}, "elements": [{"tag": "markdown", "content": text}]}, ensure_ascii=False)
|
return json.dumps({"config": {"wide_screen_mode": True}, "elements": [{"tag": "markdown", "content": text}]}, ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
def send_message(open_id, content, msg_type="text", use_card=False):
|
def send_message(receive_id, content, msg_type="text", use_card=False, receive_id_type="open_id"):
|
||||||
if use_card:
|
if use_card:
|
||||||
payload, real_type = _card(content), "interactive"
|
payload, real_type = _card(content), "interactive"
|
||||||
elif msg_type == "text":
|
elif msg_type == "text":
|
||||||
payload, real_type = json.dumps({"text": content}, ensure_ascii=False), "text"
|
payload, real_type = json.dumps({"text": content}, ensure_ascii=False), "text"
|
||||||
else:
|
else:
|
||||||
payload, real_type = content, msg_type
|
payload, real_type = content, msg_type
|
||||||
body = CreateMessageRequest.builder().receive_id_type("open_id").request_body(
|
body = CreateMessageRequest.builder().receive_id_type(receive_id_type).request_body(
|
||||||
CreateMessageRequestBody.builder().receive_id(open_id).msg_type(real_type).content(payload).build()
|
CreateMessageRequestBody.builder().receive_id(receive_id).msg_type(real_type).content(payload).build()
|
||||||
).build()
|
).build()
|
||||||
response = client.im.v1.message.create(body)
|
response = client.im.v1.message.create(body)
|
||||||
if response.success():
|
if response.success():
|
||||||
@@ -367,29 +367,29 @@ def _describe_media(msg_type, file_path, filename):
|
|||||||
return f"[{msg_type}]\n[File: source: {file_path}]"
|
return f"[{msg_type}]\n[File: source: {file_path}]"
|
||||||
|
|
||||||
|
|
||||||
def _send_local_file(open_id, file_path):
|
def _send_local_file(receive_id, file_path, receive_id_type="open_id"):
|
||||||
if not os.path.isfile(file_path):
|
if not os.path.isfile(file_path):
|
||||||
send_message(open_id, f"⚠️ 文件不存在: {file_path}")
|
send_message(receive_id, f"⚠️ 文件不存在: {file_path}", receive_id_type=receive_id_type)
|
||||||
return False
|
return False
|
||||||
ext = os.path.splitext(file_path)[1].lower()
|
ext = os.path.splitext(file_path)[1].lower()
|
||||||
if ext in _IMAGE_EXTS:
|
if ext in _IMAGE_EXTS:
|
||||||
image_key = _upload_image_sync(file_path)
|
image_key = _upload_image_sync(file_path)
|
||||||
if image_key:
|
if image_key:
|
||||||
send_message(open_id, json.dumps({"image_key": image_key}, ensure_ascii=False), msg_type="image")
|
send_message(receive_id, json.dumps({"image_key": image_key}, ensure_ascii=False), msg_type="image", receive_id_type=receive_id_type)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
file_key = _upload_file_sync(file_path)
|
file_key = _upload_file_sync(file_path)
|
||||||
if file_key:
|
if file_key:
|
||||||
msg_type = "media" if ext in _AUDIO_EXTS or ext in _VIDEO_EXTS else "file"
|
msg_type = "media" if ext in _AUDIO_EXTS or ext in _VIDEO_EXTS else "file"
|
||||||
send_message(open_id, json.dumps({"file_key": file_key}, ensure_ascii=False), msg_type=msg_type)
|
send_message(receive_id, json.dumps({"file_key": file_key}, ensure_ascii=False), msg_type=msg_type, receive_id_type=receive_id_type)
|
||||||
return True
|
return True
|
||||||
send_message(open_id, f"⚠️ 文件发送失败: {os.path.basename(file_path)}")
|
send_message(receive_id, f"⚠️ 文件发送失败: {os.path.basename(file_path)}", receive_id_type=receive_id_type)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _send_generated_files(open_id, raw_text):
|
def _send_generated_files(receive_id, raw_text, receive_id_type="open_id"):
|
||||||
for file_path in _extract_files(raw_text):
|
for file_path in _extract_files(raw_text):
|
||||||
_send_local_file(open_id, file_path)
|
_send_local_file(receive_id, file_path, receive_id_type)
|
||||||
|
|
||||||
|
|
||||||
def _build_user_message(message):
|
def _build_user_message(message):
|
||||||
@@ -430,21 +430,28 @@ def _build_user_message(message):
|
|||||||
def handle_message(data):
|
def handle_message(data):
|
||||||
event, message, sender = data.event, data.event.message, data.event.sender
|
event, message, sender = data.event, data.event.message, data.event.sender
|
||||||
open_id = sender.sender_id.open_id
|
open_id = sender.sender_id.open_id
|
||||||
|
chat_id = message.chat_id
|
||||||
if not PUBLIC_ACCESS and open_id not in ALLOWED_USERS:
|
if not PUBLIC_ACCESS and open_id not in ALLOWED_USERS:
|
||||||
print(f"未授权用户: {open_id}")
|
print(f"未授权用户: {open_id}")
|
||||||
return
|
return
|
||||||
user_input, image_paths = _build_user_message(message)
|
user_input, image_paths = _build_user_message(message)
|
||||||
if not user_input:
|
if not user_input:
|
||||||
send_message(open_id, f"⚠️ 暂不支持处理此类飞书消息:{message.message_type}")
|
if chat_id:
|
||||||
|
send_message(chat_id, f"⚠️ 暂不支持处理此类飞书消息:{message.message_type}", receive_id_type="chat_id")
|
||||||
|
else:
|
||||||
|
send_message(open_id, f"⚠️ 暂不支持处理此类飞书消息:{message.message_type}")
|
||||||
return
|
return
|
||||||
print(f"收到消息 [{open_id}] ({message.message_type}, {len(image_paths)} images): {user_input[:200]}")
|
print(f"收到消息 [{open_id}] ({message.message_type}, {len(image_paths)} images): {user_input[:200]}")
|
||||||
if message.message_type == "text" and user_input.startswith("/"):
|
if message.message_type == "text" and user_input.startswith("/"):
|
||||||
return handle_command(open_id, user_input)
|
return handle_command(open_id, user_input, chat_id)
|
||||||
|
|
||||||
def run_agent():
|
def run_agent():
|
||||||
user_tasks[open_id] = {"running": True}
|
user_tasks[open_id] = {"running": True}
|
||||||
try:
|
try:
|
||||||
msg_id, dq, last_text = send_message(open_id, "思考中...", use_card=True), agent.put_task(user_input, source="feishu", images=image_paths), ""
|
if chat_id:
|
||||||
|
msg_id, dq, last_text = send_message(chat_id, "思考中...", use_card=True, receive_id_type="chat_id"), agent.put_task(user_input, source="feishu", images=image_paths), ""
|
||||||
|
else:
|
||||||
|
msg_id, dq, last_text = send_message(open_id, "思考中...", use_card=True), agent.put_task(user_input, source="feishu", images=image_paths), ""
|
||||||
while user_tasks.get(open_id, {}).get("running", False):
|
while user_tasks.get(open_id, {}).get("running", False):
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
item = None
|
item = None
|
||||||
@@ -462,47 +469,64 @@ def handle_message(data):
|
|||||||
cut = show[-3000:]
|
cut = show[-3000:]
|
||||||
if cut.count("```") % 2 == 1:
|
if cut.count("```") % 2 == 1:
|
||||||
cut = "```\n" + cut
|
cut = "```\n" + cut
|
||||||
msg_id, last_text, show = send_message(open_id, "(继续...)", use_card=True), "", cut
|
if chat_id:
|
||||||
|
msg_id, last_text, show = send_message(chat_id, "(继续...)", use_card=True, receive_id_type="chat_id"), "", cut
|
||||||
|
else:
|
||||||
|
msg_id, last_text, show = send_message(open_id, "(继续...)", use_card=True), "", cut
|
||||||
display = show if done else show + " ⏳"
|
display = show if done else show + " ⏳"
|
||||||
if display != last_text and msg_id:
|
if display != last_text and msg_id:
|
||||||
update_message(msg_id, display)
|
update_message(msg_id, display)
|
||||||
last_text = display
|
last_text = display
|
||||||
if done:
|
if done:
|
||||||
_send_generated_files(open_id, raw)
|
if chat_id:
|
||||||
|
_send_generated_files(chat_id, raw, receive_id_type="chat_id")
|
||||||
|
else:
|
||||||
|
_send_generated_files(open_id, raw)
|
||||||
break
|
break
|
||||||
if not user_tasks.get(open_id, {}).get("running", True):
|
if not user_tasks.get(open_id, {}).get("running", True):
|
||||||
send_message(open_id, "已停止")
|
if chat_id:
|
||||||
|
send_message(chat_id, "已停止", receive_id_type="chat_id")
|
||||||
|
else:
|
||||||
|
send_message(open_id, "已停止")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
print(f"[ERROR] run_agent 异常: {e}")
|
print(f"[ERROR] run_agent 异常: {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
send_message(open_id, f"错误: {str(e)}")
|
if chat_id:
|
||||||
|
send_message(chat_id, f"错误: {str(e)}", receive_id_type="chat_id")
|
||||||
|
else:
|
||||||
|
send_message(open_id, f"错误: {str(e)}")
|
||||||
finally:
|
finally:
|
||||||
user_tasks.pop(open_id, None)
|
user_tasks.pop(open_id, None)
|
||||||
|
|
||||||
threading.Thread(target=run_agent, daemon=True).start()
|
threading.Thread(target=run_agent, daemon=True).start()
|
||||||
|
|
||||||
|
|
||||||
def handle_command(open_id, cmd):
|
def handle_command(open_id, cmd, chat_id=None):
|
||||||
|
def _send_cmd_response(content):
|
||||||
|
if chat_id:
|
||||||
|
send_message(chat_id, content, receive_id_type="chat_id")
|
||||||
|
else:
|
||||||
|
send_message(open_id, content)
|
||||||
if cmd == "/stop":
|
if cmd == "/stop":
|
||||||
if open_id in user_tasks:
|
if open_id in user_tasks:
|
||||||
user_tasks[open_id]["running"] = False
|
user_tasks[open_id]["running"] = False
|
||||||
agent.abort()
|
agent.abort()
|
||||||
send_message(open_id, "正在停止...")
|
_send_cmd_response("正在停止...")
|
||||||
elif cmd == "/new":
|
elif cmd == "/new":
|
||||||
agent.abort()
|
agent.abort()
|
||||||
agent.history = []
|
agent.history = []
|
||||||
send_message(open_id, "已清空当前共享上下文")
|
_send_cmd_response("已清空当前共享上下文")
|
||||||
elif cmd == "/help":
|
elif cmd == "/help":
|
||||||
send_message(open_id, "命令列表:\n/stop - 停止当前任务\n/status - 查看状态\n/restore - 恢复上次对话历史\n/new - 开启新对话\n/help - 显示帮助")
|
_send_cmd_response("命令列表:\n/stop - 停止当前任务\n/status - 查看状态\n/restore - 恢复上次对话历史\n/new - 开启新对话\n/help - 显示帮助")
|
||||||
elif cmd == "/status":
|
elif cmd == "/status":
|
||||||
send_message(open_id, f"状态: {'空闲' if not agent.is_running else '运行中'}")
|
_send_cmd_response(f"状态: {'空闲' if not agent.is_running else '运行中'}")
|
||||||
elif cmd == "/restore":
|
elif cmd == "/restore":
|
||||||
try:
|
try:
|
||||||
files = glob.glob("./temp/model_responses_*.txt")
|
files = glob.glob("./temp/model_responses_*.txt")
|
||||||
if not files:
|
if not files:
|
||||||
return send_message(open_id, "没有找到历史记录")
|
return _send_cmd_response("没有找到历史记录")
|
||||||
latest = max(files, key=os.path.getmtime)
|
latest = max(files, key=os.path.getmtime)
|
||||||
with open(latest, "r", encoding="utf-8") as f:
|
with open(latest, "r", encoding="utf-8") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
@@ -515,11 +539,11 @@ def handle_command(open_id, cmd):
|
|||||||
agent.history.extend([f"[USER]: {u}", f"[Agent] {r}"])
|
agent.history.extend([f"[USER]: {u}", f"[Agent] {r}"])
|
||||||
count += 1
|
count += 1
|
||||||
agent.abort()
|
agent.abort()
|
||||||
send_message(open_id, f"已恢复 {count} 轮对话\n来源: {os.path.basename(latest)}\n(仅恢复上下文,请输入新问题继续)")
|
_send_cmd_response(f"已恢复 {count} 轮对话\n来源: {os.path.basename(latest)}\n(仅恢复上下文,请输入新问题继续)")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
send_message(open_id, f"恢复失败: {e}")
|
_send_cmd_response(f"恢复失败: {e}")
|
||||||
else:
|
else:
|
||||||
send_message(open_id, f"未知命令: {cmd}")
|
_send_cmd_response(f"未知命令: {cmd}")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
Reference in New Issue
Block a user