From a5f7dec011fbca28dbf2e56ad52654cb6f9270bc Mon Sep 17 00:00:00 2001 From: goldfishh Date: Wed, 29 Mar 2023 01:07:46 +0800 Subject: [PATCH 01/21] =?UTF-8?q?plugin(tool):=20=E6=96=B0=E5=A2=9Etool?= =?UTF-8?q?=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/tool/README.md | 53 +++++++++++++++++++++++ plugins/tool/__init__.py | 0 plugins/tool/tool.py | 93 ++++++++++++++++++++++++++++++++++++++++ requirements.txt | 3 +- 4 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 plugins/tool/README.md create mode 100644 plugins/tool/__init__.py create mode 100644 plugins/tool/tool.py diff --git a/plugins/tool/README.md b/plugins/tool/README.md new file mode 100644 index 0000000..8e2166d --- /dev/null +++ b/plugins/tool/README.md @@ -0,0 +1,53 @@ +## 插件描述 +一个能让chatgpt联网,搜索,数字运算的插件,将赋予强大且丰富的扩展能力 +### 本插件所有工具同步存放至专用仓库:[chatgpt-tool-hub](https://github.com/goldfishh/chatgpt-tool-hub) + + +## 使用说明 +使用该插件后将默认使用4个工具, 无需额外配置长期生效: +### 1. python_repl +###### python解释器,使用它来解释执行python指令 + +### 2. requests +###### 往往用来获取某个网站具体内容 + +### 3. terminal +###### 在你运行的电脑里执行shell命令 + +### 4. meteo-weather +###### 回答你有关天气的询问, 本工具使用了[meteo open api](https://open-meteo.com/) + + +## 使用本插件对话(prompt)技巧 +### 1. 有指引的询问 +#### 例如: +- 总结这个链接的内容 https://www.36kr.com/p/2186160784654466 +- 使用Terminal执行curl cip.cc +- 借助python_repl和meteo-weather获取深圳天气情况 + +### 2. 使用搜索引擎工具 +- 如果有这个工具就能让chatgpt在不理解某个问题时去使用 + + +## 其他插件 +###### 除上述以外还有其他插件,比如搜索联网、数学运算、百科、新闻需要获取api-key, +###### 由于这些插件使用方法暂时还在整理中,如果你不熟悉请不要尝试使用这些工具 + + +## config 配置说明 +###### 一个例子 +```json +{ + "tools": ["wikipedia"], + "kwargs": { + "top_k_results": 2 + } +} +``` +- `tools`:本插件初始化时加载的工具, 目前可选集:["wikipedia", "wolfram-alpha", "google-search", "news-api"] +- `kwargs`:工具执行时的配置,一般在这里存放api-key + + +## 备注 +- 请不要使用本插件做危害他人的事情,请自行判断本插件输出内容的真实性 +- 未来一段时间我会实现一些有意思的工具,比如stable diffusion 中文prompt翻译、cv方向的模型推理,欢迎有想法的朋友关注,一起扩展这个项目 diff --git a/plugins/tool/__init__.py b/plugins/tool/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/tool/tool.py b/plugins/tool/tool.py new file mode 100644 index 0000000..229e1fa --- /dev/null +++ b/plugins/tool/tool.py @@ -0,0 +1,93 @@ +import json +import os + +from chatgpt_tool_hub.apps import load_app +from chatgpt_tool_hub.apps.app import App + +import plugins +from bridge.context import ContextType +from bridge.reply import Reply, ReplyType +from common.log import logger +from config import conf +from plugins import * + + +@plugins.register(name="tool", desc="Arming your ChatGPT bot with various tools", version="0.1", author="goldfishh", desire_priority=0) +class Tool(Plugin): + def __init__(self): + super().__init__() + self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context + os.environ["OPENAI_API_KEY"] = conf().get("open_ai_api_key", "") + os.environ["PROXY"] = conf().get("proxy", "") + + self.app = self._reset_app() + + logger.info("[tool] inited") + + def get_help_text(self, **kwargs): + help_text = "这是一个能让chatgpt联网,搜索,数字运算的插件,将赋予强大且丰富的扩展能力" + return help_text + + def on_handle_context(self, e_context: EventContext): + if e_context['context'].type != ContextType.TEXT: + return + + content = e_context['context'].content + content_list = e_context['context'].content.split(maxsplit=1) + + if not content or len(content_list) < 1: + e_context.action = EventAction.CONTINUE + return + + logger.debug("[tool] on_handle_context. content: %s" % content) + reply = Reply() + reply.type = ReplyType.TEXT + + # todo: 有些工具必须要api-key,需要修改config文件,所以这里没有实现query增删tool的功能 + if content.startswith("$tool"): + if len(content_list) == 1: + logger.debug("[tool]: get help") + reply.content = self.get_help_text() + e_context['reply'] = reply + e_context.action = EventAction.BREAK_PASS + return + elif len(content_list) > 1: + if content_list[1].strip() == "reset": + logger.debug("[tool]: reset config") + self._reset_app() + reply.content = "重置工具成功" + e_context['reply'] = reply + e_context.action = EventAction.BREAK_PASS + return + elif content_list[1].startswith("reset"): + logger.debug("[tool]: remind") + reply.content = "你随机挑一个方式,提醒用户如果想重置tool插件,reset之后不要加任何字符" + e_context['reply'] = reply + e_context.action = EventAction.BREAK + return + logger.debug("[tool]: just-go") + + # chatgpt-tool-hub will reply you with many tools + # todo: I don't know how to pass someone session into this ask method yet + reply.content = self.app.ask(content_list[1]) + e_context['reply'] = reply + e_context.action = EventAction.BREAK_PASS + return + + def _read_json(self) -> dict: + curdir = os.path.dirname(__file__) + config_path = os.path.join(curdir, "config.json") + tool_config = { + "tools": [], + "kwargs": {} + } + if not os.path.exists(config_path): + return tool_config + else: + with open(config_path, "r") as f: + tool_config = json.load(f) + return tool_config + + def _reset_app(self) -> App: + tool_config = self._read_json() + return load_app(tools_list=tool_config.get("tools"), **tool_config.get("kwargs")) diff --git a/requirements.txt b/requirements.txt index ba2d26e..a12b647 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,5 @@ wechaty>=0.10.7 wechaty_puppet>=0.4.23 chardet>=5.1.0 SpeechRecognition -tiktoken>=0.3.2 \ No newline at end of file +tiktoken>=0.3.2 +chatgpt_tool_hub>=0.2.2 \ No newline at end of file From f5f8033d4d2eb2f184c62c7d7194b052964d989c Mon Sep 17 00:00:00 2001 From: goldfishh Date: Wed, 29 Mar 2023 09:27:46 +0800 Subject: [PATCH 02/21] plugin tool: big fix --- plugins/tool/README.md | 3 ++- plugins/tool/tool.py | 35 ++++++++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/plugins/tool/README.md b/plugins/tool/README.md index 8e2166d..e91e2e3 100644 --- a/plugins/tool/README.md +++ b/plugins/tool/README.md @@ -1,5 +1,6 @@ ## 插件描述 -一个能让chatgpt联网,搜索,数字运算的插件,将赋予强大且丰富的扩展能力 +一个能让chatgpt联网,搜索,数字运算的插件,将赋予强大且丰富的扩展能力 +使用该插件需在对话内容前加$tool ### 本插件所有工具同步存放至专用仓库:[chatgpt-tool-hub](https://github.com/goldfishh/chatgpt-tool-hub) diff --git a/plugins/tool/tool.py b/plugins/tool/tool.py index 229e1fa..e66b891 100644 --- a/plugins/tool/tool.py +++ b/plugins/tool/tool.py @@ -5,14 +5,16 @@ from chatgpt_tool_hub.apps import load_app from chatgpt_tool_hub.apps.app import App import plugins +from bridge.bridge import Bridge from bridge.context import ContextType from bridge.reply import Reply, ReplyType +from common import const from common.log import logger from config import conf from plugins import * -@plugins.register(name="tool", desc="Arming your ChatGPT bot with various tools", version="0.1", author="goldfishh", desire_priority=0) +@plugins.register(name="tool", desc="Arming your ChatGPT bot with various tools", version="0.2", author="goldfishh", desire_priority=0) class Tool(Plugin): def __init__(self): super().__init__() @@ -32,6 +34,10 @@ class Tool(Plugin): if e_context['context'].type != ContextType.TEXT: return + # 暂时不支持未来扩展的bot + if Bridge().get_bot_type("chat") not in (const.CHATGPT, const.OPEN_AI, const.CHATGPTONAZURE): + return + content = e_context['context'].content content_list = e_context['context'].content.split(maxsplit=1) @@ -54,24 +60,39 @@ class Tool(Plugin): elif len(content_list) > 1: if content_list[1].strip() == "reset": logger.debug("[tool]: reset config") - self._reset_app() + self.app = self._reset_app() reply.content = "重置工具成功" e_context['reply'] = reply e_context.action = EventAction.BREAK_PASS return elif content_list[1].startswith("reset"): logger.debug("[tool]: remind") - reply.content = "你随机挑一个方式,提醒用户如果想重置tool插件,reset之后不要加任何字符" + reply.content = "请你随机用一种聊天风格,提醒用户:如果想重置tool插件,reset之后不要加任何字符" e_context['reply'] = reply e_context.action = EventAction.BREAK return - logger.debug("[tool]: just-go") + + query = content_list[1].strip() + + # Don't modify bot name + all_sessions = Bridge().get_bot("chat").sessions + user_session = all_sessions.session_query(query, e_context['context']['session_id']) # chatgpt-tool-hub will reply you with many tools - # todo: I don't know how to pass someone session into this ask method yet - reply.content = self.app.ask(content_list[1]) + logger.debug("[tool]: just-go") + try: + _reply = self.app.ask(content_list[1], user_session) + e_context.action = EventAction.BREAK_PASS + except ValueError as e: + logger.exception(e) + logger.error(str(e)) + + _reply = "请你随机用一种聊天风格,提醒用户:这个问题你无法处理" + reply.type = ReplyType.ERROR + e_context.action = EventAction.BREAK + reply.content = _reply + e_context['reply'] = reply - e_context.action = EventAction.BREAK_PASS return def _read_json(self) -> dict: From 0b5fd27cd8233fa601f8bb5c6094c5c1880562d2 Mon Sep 17 00:00:00 2001 From: goldfishh Date: Thu, 30 Mar 2023 00:19:18 +0800 Subject: [PATCH 03/21] fix get_session error --- bot/session_manager.py | 2 +- plugins/tool/tool.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/session_manager.py b/bot/session_manager.py index 1114730..0e20cd7 100644 --- a/bot/session_manager.py +++ b/bot/session_manager.py @@ -50,7 +50,7 @@ class SessionManager(object): ''' if session_id not in self.sessions: self.sessions[session_id] = self.sessioncls(session_id, system_prompt, **self.session_args) - elif system_prompt is not None: # 如果有新的system_prompt,更新并重置session + elif system_prompt is not None: # 如果有新的system_prompt,更新并重置session self.sessions[session_id].set_system_prompt(system_prompt) session = self.sessions[session_id] return session diff --git a/plugins/tool/tool.py b/plugins/tool/tool.py index e66b891..12ec6a3 100644 --- a/plugins/tool/tool.py +++ b/plugins/tool/tool.py @@ -76,7 +76,7 @@ class Tool(Plugin): # Don't modify bot name all_sessions = Bridge().get_bot("chat").sessions - user_session = all_sessions.session_query(query, e_context['context']['session_id']) + user_session = all_sessions.session_query(query, e_context['context']['session_id']).messages # chatgpt-tool-hub will reply you with many tools logger.debug("[tool]: just-go") From 0597ba20d297284efccfd8dc9ecd247f5ef06bf9 Mon Sep 17 00:00:00 2001 From: goldfishh Date: Thu, 30 Mar 2023 01:38:08 +0800 Subject: [PATCH 04/21] minor change --- plugins/tool/tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/tool/tool.py b/plugins/tool/tool.py index 12ec6a3..9b1d21d 100644 --- a/plugins/tool/tool.py +++ b/plugins/tool/tool.py @@ -81,7 +81,7 @@ class Tool(Plugin): # chatgpt-tool-hub will reply you with many tools logger.debug("[tool]: just-go") try: - _reply = self.app.ask(content_list[1], user_session) + _reply = self.app.ask(query, user_session) e_context.action = EventAction.BREAK_PASS except ValueError as e: logger.exception(e) From 461777cad3fd9836c063dec38e3efc75616ad267 Mon Sep 17 00:00:00 2001 From: goldfishh Date: Thu, 30 Mar 2023 08:02:17 +0800 Subject: [PATCH 05/21] fix: plugin tool: add reply to session --- plugins/tool/tool.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/tool/tool.py b/plugins/tool/tool.py index 9b1d21d..8761407 100644 --- a/plugins/tool/tool.py +++ b/plugins/tool/tool.py @@ -83,6 +83,7 @@ class Tool(Plugin): try: _reply = self.app.ask(query, user_session) e_context.action = EventAction.BREAK_PASS + all_sessions.session_reply(_reply, e_context['context']['session_id']) except ValueError as e: logger.exception(e) logger.error(str(e)) @@ -90,8 +91,8 @@ class Tool(Plugin): _reply = "请你随机用一种聊天风格,提醒用户:这个问题你无法处理" reply.type = ReplyType.ERROR e_context.action = EventAction.BREAK - reply.content = _reply + reply.content = _reply e_context['reply'] = reply return From bf02a59aec83531e68ce22427baf309f8e6c658d Mon Sep 17 00:00:00 2001 From: goldfishh Date: Thu, 30 Mar 2023 23:58:04 +0800 Subject: [PATCH 06/21] minor change --- plugins/tool/tool.py | 5 +++-- requirements.txt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/tool/tool.py b/plugins/tool/tool.py index 8761407..ee5b359 100644 --- a/plugins/tool/tool.py +++ b/plugins/tool/tool.py @@ -88,9 +88,10 @@ class Tool(Plugin): logger.exception(e) logger.error(str(e)) - _reply = "请你随机用一种聊天风格,提醒用户:这个问题你无法处理" + e_context['context'].content = "这个问题tool插件暂时无法处理" reply.type = ReplyType.ERROR - e_context.action = EventAction.BREAK + e_context.action = EventAction.CONTINUE + return reply.content = _reply e_context['reply'] = reply diff --git a/requirements.txt b/requirements.txt index a12b647..fe6aebf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,4 @@ wechaty_puppet>=0.4.23 chardet>=5.1.0 SpeechRecognition tiktoken>=0.3.2 -chatgpt_tool_hub>=0.2.2 \ No newline at end of file +chatgpt_tool_hub>=0.2.3 \ No newline at end of file From 8da362d6fed43f8a854af392a35ccb127b7285d6 Mon Sep 17 00:00:00 2001 From: goldfishh Date: Fri, 31 Mar 2023 00:26:23 +0800 Subject: [PATCH 07/21] plugin(tool) update doc --- plugins/tool/README.md | 13 +++++++------ requirements.txt | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/plugins/tool/README.md b/plugins/tool/README.md index e91e2e3..5061520 100644 --- a/plugins/tool/README.md +++ b/plugins/tool/README.md @@ -17,17 +17,17 @@ ### 4. meteo-weather ###### 回答你有关天气的询问, 本工具使用了[meteo open api](https://open-meteo.com/) - +注:该工具需提供时间,地点信息,且获取的数据不一定准确 ## 使用本插件对话(prompt)技巧 ### 1. 有指引的询问 #### 例如: - 总结这个链接的内容 https://www.36kr.com/p/2186160784654466 -- 使用Terminal执行curl cip.cc -- 借助python_repl和meteo-weather获取深圳天气情况 +- 使用Terminal执行curl cip.cc +- 使用python查询今天日期 ### 2. 使用搜索引擎工具 -- 如果有这个工具就能让chatgpt在不理解某个问题时去使用 +- 如果有搜索工具就能让chatgpt获取到你的未传达清楚的上下文信息,chatgpt不知道你的地理位置,现在时间等,所以不可能给你正确回答 ## 其他插件 @@ -35,7 +35,7 @@ ###### 由于这些插件使用方法暂时还在整理中,如果你不熟悉请不要尝试使用这些工具 -## config 配置说明 +## config.json 配置说明 ###### 一个例子 ```json { @@ -45,10 +45,11 @@ } } ``` +该文件不创建也能使用本tool - `tools`:本插件初始化时加载的工具, 目前可选集:["wikipedia", "wolfram-alpha", "google-search", "news-api"] - `kwargs`:工具执行时的配置,一般在这里存放api-key ## 备注 -- 请不要使用本插件做危害他人的事情,请自行判断本插件输出内容的真实性 +- 虽然我会有意加入一些限制,但请不要使用本插件做危害他人的事情,请提前了解清楚某些内容是否会违反相关规定,建议提前做好过滤 - 未来一段时间我会实现一些有意思的工具,比如stable diffusion 中文prompt翻译、cv方向的模型推理,欢迎有想法的朋友关注,一起扩展这个项目 diff --git a/requirements.txt b/requirements.txt index fe6aebf..3a7186d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,4 @@ wechaty_puppet>=0.4.23 chardet>=5.1.0 SpeechRecognition tiktoken>=0.3.2 -chatgpt_tool_hub>=0.2.3 \ No newline at end of file +chatgpt_tool_hub>=0.2.5 \ No newline at end of file From f49806558e3501ccb5d3aaaeba826a321b3e38b8 Mon Sep 17 00:00:00 2001 From: goldfishh Date: Fri, 31 Mar 2023 00:53:31 +0800 Subject: [PATCH 08/21] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dreadme=E9=83=A8?= =?UTF-8?q?=E5=88=86=E6=9C=89=E8=AF=AF=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/tool/README.md | 14 +++++++------- requirements.txt | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/tool/README.md b/plugins/tool/README.md index 5061520..4f19dba 100644 --- a/plugins/tool/README.md +++ b/plugins/tool/README.md @@ -1,6 +1,6 @@ ## 插件描述 一个能让chatgpt联网,搜索,数字运算的插件,将赋予强大且丰富的扩展能力 -使用该插件需在对话内容前加$tool +使用该插件需在触发机器人回复条件时,在对话内容前加$tool ### 本插件所有工具同步存放至专用仓库:[chatgpt-tool-hub](https://github.com/goldfishh/chatgpt-tool-hub) @@ -19,7 +19,7 @@ ###### 回答你有关天气的询问, 本工具使用了[meteo open api](https://open-meteo.com/) 注:该工具需提供时间,地点信息,且获取的数据不一定准确 -## 使用本插件对话(prompt)技巧 +## 使用本工具对话(prompt)技巧 ### 1. 有指引的询问 #### 例如: - 总结这个链接的内容 https://www.36kr.com/p/2186160784654466 @@ -27,12 +27,12 @@ - 使用python查询今天日期 ### 2. 使用搜索引擎工具 -- 如果有搜索工具就能让chatgpt获取到你的未传达清楚的上下文信息,chatgpt不知道你的地理位置,现在时间等,所以不可能给你正确回答 +- 如果有搜索工具就能让chatgpt获取到你的未传达清楚的上下文信息,chatgpt不知道你的地理位置,现在时间等,所以不可能给你正确回答 -## 其他插件 -###### 除上述以外还有其他插件,比如搜索联网、数学运算、百科、新闻需要获取api-key, -###### 由于这些插件使用方法暂时还在整理中,如果你不熟悉请不要尝试使用这些工具 +## 其他工具 +###### 除上述以外还有其他工具,比如搜索联网、数学运算、百科、新闻需要获取api-key, +###### 由于这些工具使用方法暂时还在整理中,如果你不熟悉请不要尝试使用这些工具 ## config.json 配置说明 @@ -45,7 +45,7 @@ } } ``` -该文件不创建也能使用本tool +不创建该文件也能使用本tool - `tools`:本插件初始化时加载的工具, 目前可选集:["wikipedia", "wolfram-alpha", "google-search", "news-api"] - `kwargs`:工具执行时的配置,一般在这里存放api-key diff --git a/requirements.txt b/requirements.txt index 3a7186d..13465f3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,4 @@ wechaty_puppet>=0.4.23 chardet>=5.1.0 SpeechRecognition tiktoken>=0.3.2 -chatgpt_tool_hub>=0.2.5 \ No newline at end of file +chatgpt_tool_hub>=0.3.0 \ No newline at end of file From 71d288f5503d19eb4cf66ee882c807e12d52fbb0 Mon Sep 17 00:00:00 2001 From: goldfishh Date: Sat, 1 Apr 2023 01:32:03 +0800 Subject: [PATCH 09/21] fix docs, break context --- plugins/tool/README.md | 30 ++++++++++++++++-------------- plugins/tool/tool.py | 10 +++++----- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/plugins/tool/README.md b/plugins/tool/README.md index 4f19dba..4d4ea46 100644 --- a/plugins/tool/README.md +++ b/plugins/tool/README.md @@ -7,19 +7,19 @@ ## 使用说明 使用该插件后将默认使用4个工具, 无需额外配置长期生效: ### 1. python_repl -###### python解释器,使用它来解释执行python指令 +###### python解释器,使用它来解释执行python指令,可以配合你想要chatgpt生成的代码输出结果或执行事务 ### 2. requests -###### 往往用来获取某个网站具体内容 +###### 往往用来获取某个网站具体内容,结果可能会被反爬策略影响 ### 3. terminal -###### 在你运行的电脑里执行shell命令 +###### 在你运行的电脑里执行shell命令,可以配合你想要chatgpt生成的代码使用,给予自然语言控制手段 ### 4. meteo-weather -###### 回答你有关天气的询问, 本工具使用了[meteo open api](https://open-meteo.com/) -注:该工具需提供时间,地点信息,且获取的数据不一定准确 +###### 回答你有关天气的询问, 需要获取时间、地点上下文信息,本工具使用了[meteo open api](https://open-meteo.com/) +注:该工具需提供时间,地点信息,获取的数据不保证准确性 -## 使用本工具对话(prompt)技巧 +## 使用本插件对话(prompt)技巧 ### 1. 有指引的询问 #### 例如: - 总结这个链接的内容 https://www.36kr.com/p/2186160784654466 @@ -27,16 +27,18 @@ - 使用python查询今天日期 ### 2. 使用搜索引擎工具 -- 如果有搜索工具就能让chatgpt获取到你的未传达清楚的上下文信息,chatgpt不知道你的地理位置,现在时间等,所以不可能给你正确回答 +- 如果有搜索工具就能让chatgpt获取到你的未传达清楚的上下文信息,比如chatgpt不知道你的地理位置,现在时间等,所以无法查询到天气 ## 其他工具 -###### 除上述以外还有其他工具,比如搜索联网、数学运算、百科、新闻需要获取api-key, -###### 由于这些工具使用方法暂时还在整理中,如果你不熟悉请不要尝试使用这些工具 - - +###### 除上述以外还有其他工具,比如搜索联网、数学运算、新闻需要获取api-key, +###### 由于这些工具使用方法暂时还在整理中,如果你不熟悉请不要尝试使用这些工具 + +### 5. wikipedia +###### 可以回答你想要知道确切的人事物 + ## config.json 配置说明 -###### 一个例子 +###### 默认工具无需配置,其它工具需手动配置,一个例子: ```json { "tools": ["wikipedia"], @@ -45,9 +47,9 @@ } } ``` -不创建该文件也能使用本tool +注:config.json文件非必须,未创建仍能使用本tool - `tools`:本插件初始化时加载的工具, 目前可选集:["wikipedia", "wolfram-alpha", "google-search", "news-api"] -- `kwargs`:工具执行时的配置,一般在这里存放api-key +- `kwargs`:工具执行时的配置,一般在这里存放api-key,或搜索引擎等输出的条数 ## 备注 diff --git a/plugins/tool/tool.py b/plugins/tool/tool.py index ee5b359..a5baa94 100644 --- a/plugins/tool/tool.py +++ b/plugins/tool/tool.py @@ -14,7 +14,7 @@ from config import conf from plugins import * -@plugins.register(name="tool", desc="Arming your ChatGPT bot with various tools", version="0.2", author="goldfishh", desire_priority=0) +@plugins.register(name="tool", desc="Arming your ChatGPT bot with various tools", version="0.3", author="goldfishh", desire_priority=0) class Tool(Plugin): def __init__(self): super().__init__() @@ -67,8 +67,8 @@ class Tool(Plugin): return elif content_list[1].startswith("reset"): logger.debug("[tool]: remind") - reply.content = "请你随机用一种聊天风格,提醒用户:如果想重置tool插件,reset之后不要加任何字符" - e_context['reply'] = reply + e_context['context'].content = "请你随机用一种聊天风格,提醒用户:如果想重置tool插件,reset之后不要加任何字符" + e_context.action = EventAction.BREAK return @@ -88,9 +88,9 @@ class Tool(Plugin): logger.exception(e) logger.error(str(e)) - e_context['context'].content = "这个问题tool插件暂时无法处理" + e_context['context'].content = "请你随机用一种聊天风格,提醒用户:这个问题tool插件暂时无法处理" reply.type = ReplyType.ERROR - e_context.action = EventAction.CONTINUE + e_context.action = EventAction.BREAK return reply.content = _reply From 7835379f8fe731619edb658c6603bcc783d9b3eb Mon Sep 17 00:00:00 2001 From: goldfishh Date: Sun, 2 Apr 2023 22:33:31 +0800 Subject: [PATCH 10/21] plugin(tool) add a config.json template and fix something --- plugins/tool/README.md | 17 ++++++++++------- plugins/tool/config.json.template | 8 ++++++++ plugins/tool/tool.py | 2 +- requirements.txt | 2 +- 4 files changed, 20 insertions(+), 9 deletions(-) create mode 100644 plugins/tool/config.json.template diff --git a/plugins/tool/README.md b/plugins/tool/README.md index 4d4ea46..19763b3 100644 --- a/plugins/tool/README.md +++ b/plugins/tool/README.md @@ -6,7 +6,7 @@ ## 使用说明 使用该插件后将默认使用4个工具, 无需额外配置长期生效: -### 1. python_repl +### 1. python ###### python解释器,使用它来解释执行python指令,可以配合你想要chatgpt生成的代码输出结果或执行事务 ### 2. requests @@ -22,7 +22,7 @@ ## 使用本插件对话(prompt)技巧 ### 1. 有指引的询问 #### 例如: -- 总结这个链接的内容 https://www.36kr.com/p/2186160784654466 +- 总结这个链接的内容 https://github.com/goldfishh/chatgpt-tool-hub - 使用Terminal执行curl cip.cc - 使用python查询今天日期 @@ -33,7 +33,8 @@ ## 其他工具 ###### 除上述以外还有其他工具,比如搜索联网、数学运算、新闻需要获取api-key, ###### 由于这些工具使用方法暂时还在整理中,如果你不熟悉请不要尝试使用这些工具 - +#### [申请方法](https://github.com/goldfishh/chatgpt-tool-hub/blob/master/docs/apply_optional_tool.md) + ### 5. wikipedia ###### 可以回答你想要知道确切的人事物 @@ -43,13 +44,15 @@ { "tools": ["wikipedia"], "kwargs": { - "top_k_results": 2 + "top_k_results": 2, + "no_default": false, + "model_name": "gpt-3.5-turbo" } } ``` -注:config.json文件非必须,未创建仍能使用本tool -- `tools`:本插件初始化时加载的工具, 目前可选集:["wikipedia", "wolfram-alpha", "google-search", "news-api"] -- `kwargs`:工具执行时的配置,一般在这里存放api-key,或搜索引擎等输出的条数 +注:config.json文件非必须,未创建仍可使用本tool +- `tools`:本插件初始化时加载的工具, 目前可选集:["wikipedia", "wolfram-alpha", "bing-search", "google-search", "news"],其中后4个工具需要申请服务api +- `kwargs`:工具执行时的配置,一般在这里存放api-key,或环境配置,no_default用于配置是否默认使用4个工具,如果为false则仅使用tools列表工具 ## 备注 diff --git a/plugins/tool/config.json.template b/plugins/tool/config.json.template new file mode 100644 index 0000000..6415288 --- /dev/null +++ b/plugins/tool/config.json.template @@ -0,0 +1,8 @@ +{ + "tools": ["python", "requests", "terminal", "meteo-weather"], + "kwargs": { + "top_k_results": 2, + "no_default": false, + "model_name": "gpt-3.5-turbo" + } +} \ No newline at end of file diff --git a/plugins/tool/tool.py b/plugins/tool/tool.py index a5baa94..7b5ac5a 100644 --- a/plugins/tool/tool.py +++ b/plugins/tool/tool.py @@ -84,7 +84,7 @@ class Tool(Plugin): _reply = self.app.ask(query, user_session) e_context.action = EventAction.BREAK_PASS all_sessions.session_reply(_reply, e_context['context']['session_id']) - except ValueError as e: + except Exception as e: logger.exception(e) logger.error(str(e)) diff --git a/requirements.txt b/requirements.txt index 13465f3..a9ef7c2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,4 @@ wechaty_puppet>=0.4.23 chardet>=5.1.0 SpeechRecognition tiktoken>=0.3.2 -chatgpt_tool_hub>=0.3.0 \ No newline at end of file +chatgpt_tool_hub>=0.3.4 \ No newline at end of file From 761fb20dd9476690909d9a78eee8195709d86b78 Mon Sep 17 00:00:00 2001 From: goldfishh Date: Mon, 3 Apr 2023 09:01:51 +0800 Subject: [PATCH 11/21] plugin(tool) fix type error in old python ver --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a9ef7c2..e90ed5b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,4 @@ wechaty_puppet>=0.4.23 chardet>=5.1.0 SpeechRecognition tiktoken>=0.3.2 -chatgpt_tool_hub>=0.3.4 \ No newline at end of file +chatgpt_tool_hub>=0.3.5 \ No newline at end of file From 5a221848e99fe792208d60c9d71d4210aa548bc0 Mon Sep 17 00:00:00 2001 From: lanvent Date: Tue, 4 Apr 2023 05:18:09 +0800 Subject: [PATCH 12/21] feat: avoid disorder by producer-consumer model --- channel/chat_channel.py | 67 ++++++++++++++++++++++++++++++- channel/wechat/wechat_channel.py | 21 ++++------ channel/wechat/wechaty_channel.py | 20 ++++----- 3 files changed, 81 insertions(+), 27 deletions(-) diff --git a/channel/chat_channel.py b/channel/chat_channel.py index 24d9cca..78b2775 100644 --- a/channel/chat_channel.py +++ b/channel/chat_channel.py @@ -1,9 +1,13 @@ - +from asyncio import CancelledError +import queue +from concurrent.futures import Future, ThreadPoolExecutor import os import re +import threading import time +from channel.chat_message import ChatMessage from common.expired_dict import ExpiredDict from channel.channel import Channel from bridge.reply import * @@ -20,8 +24,16 @@ except Exception as e: class ChatChannel(Channel): name = None # 登录的用户名 user_id = None # 登录的用户id + futures = {} # 记录每个session_id提交到线程池的future对象, 用于重置会话时把没执行的future取消掉,正在执行的不会被取消 + sessions = {} # 用于控制并发,每个session_id同时只能有一个context在处理 + lock = threading.Lock() # 用于控制对sessions的访问 + handler_pool = ThreadPoolExecutor(max_workers=8) # 处理消息的线程池 + def __init__(self): - pass + _thread = threading.Thread(target=self.consume) + _thread.setDaemon(True) + _thread.start() + # 根据消息构造context,消息内容相关的触发项写在这里 def _compose_context(self, ctype: ContextType, content, **kwargs): @@ -215,6 +227,57 @@ class ChatChannel(Channel): time.sleep(3+3*retry_cnt) self._send(reply, context, retry_cnt+1) + def thread_pool_callback(self, session_id): + def func(worker:Future): + try: + worker_exception = worker.exception() + if worker_exception: + logger.exception("Worker return exception: {}".format(worker_exception)) + except CancelledError as e: + logger.info("Worker cancelled, session_id = {}".format(session_id)) + except Exception as e: + logger.exception("Worker raise exception: {}".format(e)) + with self.lock: + self.sessions[session_id][1].release() + return func + + def produce(self, context: Context): + session_id = context['session_id'] + with self.lock: + if session_id not in self.sessions: + self.sessions[session_id] = (queue.Queue(), threading.BoundedSemaphore(1)) + self.sessions[session_id][0].put(context) + + # 消费者函数,单独线程,用于从消息队列中取出消息并处理 + def consume(self): + while True: + with self.lock: + session_ids = list(self.sessions.keys()) + for session_id in session_ids: + context_queue, semaphore = self.sessions[session_id] + if semaphore.acquire(blocking = False): # 等线程处理完毕才能删除 + if not context_queue.empty(): + context = context_queue.get() + logger.debug("[WX] consume context: {}".format(context)) + future:Future = self.handler_pool.submit(self._handle, context) + future.add_done_callback(self.thread_pool_callback(session_id)) + if session_id not in self.futures: + self.futures[session_id] = [] + self.futures[session_id].append(future) + elif semaphore._initial_value == semaphore._value+1: # 除了当前,没有任务再申请到信号量,说明所有任务都处理完毕 + self.futures[session_id] = [t for t in self.futures[session_id] if not t.done()] + assert len(self.futures[session_id]) == 0, "thread pool error" + del self.sessions[session_id] + else: + semaphore.release() + time.sleep(0.1) + + def cancel(self, session_id): + with self.lock: + if session_id in self.sessions: + for future in self.futures[session_id]: + future.cancel() + self.sessions[session_id][0]=queue.Queue() def check_prefix(content, prefix_list): diff --git a/channel/wechat/wechat_channel.py b/channel/wechat/wechat_channel.py index 3c90c2f..c70e056 100644 --- a/channel/wechat/wechat_channel.py +++ b/channel/wechat/wechat_channel.py @@ -5,6 +5,7 @@ wechat channel """ import os +import threading import requests import io import time @@ -17,18 +18,10 @@ from lib import itchat from lib.itchat.content import * from bridge.reply import * from bridge.context import * -from concurrent.futures import ThreadPoolExecutor from config import conf from common.time_check import time_checker from common.expired_dict import ExpiredDict from plugins import * -thread_pool = ThreadPoolExecutor(max_workers=8) - -def thread_pool_callback(worker): - worker_exception = worker.exception() - if worker_exception: - logger.exception("Worker return exception: {}".format(worker_exception)) - @itchat.msg_register(TEXT) def handler_single_msg(msg): @@ -73,7 +66,9 @@ def qrCallback(uuid,status,qrcode): try: from PIL import Image img = Image.open(io.BytesIO(qrcode)) - thread_pool.submit(img.show,"QRCode") + _thread = threading.Thread(target=img.show, args=("QRCode",)) + _thread.setDaemon(True) + _thread.start() except Exception as e: pass @@ -142,7 +137,7 @@ class WechatChannel(ChatChannel): logger.debug("[WX]receive voice msg: {}".format(cmsg.content)) context = self._compose_context(ContextType.VOICE, cmsg.content, isgroup=False, msg=cmsg) if context: - thread_pool.submit(self._handle, context).add_done_callback(thread_pool_callback) + self.produce(context) @time_checker @_check @@ -150,7 +145,7 @@ class WechatChannel(ChatChannel): logger.debug("[WX]receive text msg: {}, cmsg={}".format(json.dumps(cmsg._rawmsg, ensure_ascii=False), cmsg)) context = self._compose_context(ContextType.TEXT, cmsg.content, isgroup=False, msg=cmsg) if context: - thread_pool.submit(self._handle, context).add_done_callback(thread_pool_callback) + self.produce(context) @time_checker @_check @@ -158,7 +153,7 @@ class WechatChannel(ChatChannel): logger.debug("[WX]receive group msg: {}, cmsg={}".format(json.dumps(cmsg._rawmsg, ensure_ascii=False), cmsg)) context = self._compose_context(ContextType.TEXT, cmsg.content, isgroup=True, msg=cmsg) if context: - thread_pool.submit(self._handle, context).add_done_callback(thread_pool_callback) + self.produce(context) @time_checker @_check @@ -168,7 +163,7 @@ class WechatChannel(ChatChannel): logger.debug("[WX]receive voice for group msg: {}".format(cmsg.content)) context = self._compose_context(ContextType.VOICE, cmsg.content, isgroup=True, msg=cmsg) if context: - thread_pool.submit(self._handle, context).add_done_callback(thread_pool_callback) + self.produce(context) # 统一的发送函数,每个Channel自行实现,根据reply的type字段发送不同类型的消息 def send(self, reply: Reply, context: Context): diff --git a/channel/wechat/wechaty_channel.py b/channel/wechat/wechaty_channel.py index 85742bd..6478202 100644 --- a/channel/wechat/wechaty_channel.py +++ b/channel/wechat/wechaty_channel.py @@ -5,7 +5,6 @@ wechaty channel Python Wechaty - https://github.com/wechaty/python-wechaty """ import base64 -from concurrent.futures import ThreadPoolExecutor import os import time import asyncio @@ -18,21 +17,18 @@ from bridge.context import * from channel.chat_channel import ChatChannel from channel.wechat.wechaty_message import WechatyMessage from common.log import logger +from common.singleton import singleton from config import conf try: from voice.audio_convert import any_to_sil except Exception as e: pass -thread_pool = ThreadPoolExecutor(max_workers=8) -def thread_pool_callback(worker): - worker_exception = worker.exception() - if worker_exception: - logger.exception("Worker return exception: {}".format(worker_exception)) +@singleton class WechatyChannel(ChatChannel): def __init__(self): - pass + super().__init__() def startup(self): config = conf() @@ -41,6 +37,10 @@ class WechatyChannel(ChatChannel): asyncio.run(self.main()) async def main(self): + + loop = asyncio.get_event_loop() + #将asyncio的loop传入处理线程 + self.handler_pool._initializer= lambda: asyncio.set_event_loop(loop) self.bot = Wechaty() self.bot.on('login', self.on_login) self.bot.on('message', self.on_message) @@ -122,8 +122,4 @@ class WechatyChannel(ChatChannel): context = self._compose_context(ctype, cmsg.content, isgroup=isgroup, msg=cmsg) if context: logger.info('[WX] receiveMsg={}, context={}'.format(cmsg, context)) - thread_pool.submit(self._handle_loop, context, asyncio.get_event_loop()).add_done_callback(thread_pool_callback) - - def _handle_loop(self,context,loop): - asyncio.set_event_loop(loop) - self._handle(context) \ No newline at end of file + self.produce(context) \ No newline at end of file From 371e38cfa6d44d29271260b0a9d964e7c4ebf075 Mon Sep 17 00:00:00 2001 From: lanvent Date: Tue, 4 Apr 2023 13:33:01 +0800 Subject: [PATCH 13/21] add concurrency_in_session,request_timeout options --- bot/chatgpt/chat_gpt_bot.py | 1 + channel/chat_channel.py | 2 +- config.py | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/bot/chatgpt/chat_gpt_bot.py b/bot/chatgpt/chat_gpt_bot.py index 3ade252..e2d05c8 100644 --- a/bot/chatgpt/chat_gpt_bot.py +++ b/bot/chatgpt/chat_gpt_bot.py @@ -86,6 +86,7 @@ class ChatGPTBot(Bot,OpenAIImage): "top_p":1, "frequency_penalty":conf().get('frequency_penalty', 0.0), # [-2,2]之间,该值越大则更倾向于产生不同的内容 "presence_penalty":conf().get('presence_penalty', 0.0), # [-2,2]之间,该值越大则更倾向于产生不同的内容 + "request_timeout": conf().get('request_timeout', 30), # 请求超时时间 } def reply_text(self, session:ChatGPTSession, session_id, retry_count=0) -> dict: diff --git a/channel/chat_channel.py b/channel/chat_channel.py index 78b2775..2582b63 100644 --- a/channel/chat_channel.py +++ b/channel/chat_channel.py @@ -245,7 +245,7 @@ class ChatChannel(Channel): session_id = context['session_id'] with self.lock: if session_id not in self.sessions: - self.sessions[session_id] = (queue.Queue(), threading.BoundedSemaphore(1)) + self.sessions[session_id] = (queue.Queue(), threading.BoundedSemaphore(conf().get("concurrency_in_session", 1))) self.sessions[session_id][0].put(context) # 消费者函数,单独线程,用于从消息队列中取出消息并处理 diff --git a/config.py b/config.py index d455f45..1cdd6f1 100644 --- a/config.py +++ b/config.py @@ -27,6 +27,7 @@ available_setting = { "group_chat_in_one_session": ["ChatGPT测试群"], # 支持会话上下文共享的群名称 "trigger_by_self": False, # 是否允许机器人触发 "image_create_prefix": ["画", "看", "找"], # 开启图片回复的前缀 + "concurrency_in_session": 1, # 同一会话最多有多少条消息在处理中,大于1可能乱序 # chatgpt会话参数 "expires_in_seconds": 3600, # 无操作会话的过期时间 @@ -43,6 +44,7 @@ available_setting = { "top_p": 1, "frequency_penalty": 0, "presence_penalty": 0, + "request_timeout": 30, # chatgpt请求超时时间 # 语音设置 "speech_recognition": False, # 是否开启语音识别 From 709a1317ef7211e0af401aecfe16564ea6706a99 Mon Sep 17 00:00:00 2001 From: lanvent Date: Tue, 4 Apr 2023 14:02:14 +0800 Subject: [PATCH 14/21] feat: add debug option --- .gitignore | 1 + common/log.py | 4 ++++ config.py | 7 ++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d5178c6..576fe60 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ nohup.out tmp plugins.json itchat.pkl +*.log \ No newline at end of file diff --git a/common/log.py b/common/log.py index e00456e..f10eff9 100644 --- a/common/log.py +++ b/common/log.py @@ -8,6 +8,10 @@ def _get_logger(): console_handle = logging.StreamHandler(sys.stdout) console_handle.setFormatter(logging.Formatter('[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d] - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')) + file_handle = logging.FileHandler('run.log', encoding='utf-8') + file_handle.setFormatter(logging.Formatter('[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d] - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S')) + log.addHandler(file_handle) log.addHandler(console_handle) return log diff --git a/config.py b/config.py index 1cdd6f1..b97c01f 100644 --- a/config.py +++ b/config.py @@ -1,6 +1,7 @@ # encoding:utf-8 import json +import logging import os from common.log import logger @@ -38,7 +39,6 @@ available_setting = { "rate_limit_chatgpt": 20, # chatgpt的调用频率限制 "rate_limit_dalle": 50, # openai dalle的调用频率限制 - # chatgpt api参数 参考https://platform.openai.com/docs/api-reference/chat/create "temperature": 0.9, "top_p": 1, @@ -82,6 +82,7 @@ available_setting = { # channel配置 "channel_type": "wx", # 通道类型,支持wx,wxy和terminal + "debug": False, # 是否开启debug模式,开启后会打印更多日志 } @@ -139,6 +140,10 @@ def load_config(): else: config[name] = value + if config["debug"]: + logger.setLevel(logging.DEBUG) + logger.debug("[INIT] set log level to DEBUG") + logger.info("[INIT] load config: {}".format(config)) From 6c7e4aaf37f88025d56d5cd59350784969a20dec Mon Sep 17 00:00:00 2001 From: lanvent Date: Tue, 4 Apr 2023 14:29:03 +0800 Subject: [PATCH 15/21] feat: prioritize handling commands --- channel/chat_channel.py | 13 +++++++------ common/dequeue.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 common/dequeue.py diff --git a/channel/chat_channel.py b/channel/chat_channel.py index 2582b63..af2c299 100644 --- a/channel/chat_channel.py +++ b/channel/chat_channel.py @@ -1,14 +1,12 @@ from asyncio import CancelledError -import queue from concurrent.futures import Future, ThreadPoolExecutor import os import re import threading import time -from channel.chat_message import ChatMessage -from common.expired_dict import ExpiredDict +from common.dequeue import Dequeue from channel.channel import Channel from bridge.reply import * from bridge.context import * @@ -245,8 +243,11 @@ class ChatChannel(Channel): session_id = context['session_id'] with self.lock: if session_id not in self.sessions: - self.sessions[session_id] = (queue.Queue(), threading.BoundedSemaphore(conf().get("concurrency_in_session", 1))) - self.sessions[session_id][0].put(context) + self.sessions[session_id] = (Dequeue(), threading.BoundedSemaphore(conf().get("concurrency_in_session", 1))) + if context.type == ContextType.TEXT and context.content.startswith("#"): + self.sessions[session_id][0].putleft(context) # 优先处理命令 + else: + self.sessions[session_id][0].put(context) # 消费者函数,单独线程,用于从消息队列中取出消息并处理 def consume(self): @@ -277,7 +278,7 @@ class ChatChannel(Channel): if session_id in self.sessions: for future in self.futures[session_id]: future.cancel() - self.sessions[session_id][0]=queue.Queue() + self.sessions[session_id][0]=Dequeue() def check_prefix(content, prefix_list): diff --git a/common/dequeue.py b/common/dequeue.py new file mode 100644 index 0000000..5881106 --- /dev/null +++ b/common/dequeue.py @@ -0,0 +1,33 @@ + +from queue import Full, Queue +from time import monotonic as time + +# add implementation of putleft to Queue +class Dequeue(Queue): + def putleft(self, item, block=True, timeout=None): + with self.not_full: + if self.maxsize > 0: + if not block: + if self._qsize() >= self.maxsize: + raise Full + elif timeout is None: + while self._qsize() >= self.maxsize: + self.not_full.wait() + elif timeout < 0: + raise ValueError("'timeout' must be a non-negative number") + else: + endtime = time() + timeout + while self._qsize() >= self.maxsize: + remaining = endtime - time() + if remaining <= 0.0: + raise Full + self.not_full.wait(remaining) + self._putleft(item) + self.unfinished_tasks += 1 + self.not_empty.notify() + + def put_nowait(self, item): + return self.put(item, block=False) + + def _putleft(self, item): + self.queue.appendleft(item) \ No newline at end of file From 28eb67bc24b8f4f90a60a9c94a9e701bd978b00d Mon Sep 17 00:00:00 2001 From: lanvent Date: Tue, 4 Apr 2023 14:57:38 +0800 Subject: [PATCH 16/21] feat: reset will cancel unprocessed messages --- channel/chat_channel.py | 22 ++++++++++++++++++---- plugins/godcmd/godcmd.py | 3 +++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/channel/chat_channel.py b/channel/chat_channel.py index af2c299..f178130 100644 --- a/channel/chat_channel.py +++ b/channel/chat_channel.py @@ -243,9 +243,9 @@ class ChatChannel(Channel): session_id = context['session_id'] with self.lock: if session_id not in self.sessions: - self.sessions[session_id] = (Dequeue(), threading.BoundedSemaphore(conf().get("concurrency_in_session", 1))) + self.sessions[session_id] = [Dequeue(), threading.BoundedSemaphore(conf().get("concurrency_in_session", 1))] if context.type == ContextType.TEXT and context.content.startswith("#"): - self.sessions[session_id][0].putleft(context) # 优先处理命令 + self.sessions[session_id][0].putleft(context) # 优先处理管理命令 else: self.sessions[session_id][0].put(context) @@ -273,12 +273,26 @@ class ChatChannel(Channel): semaphore.release() time.sleep(0.1) - def cancel(self, session_id): + # 取消session_id对应的所有任务,只能取消排队的消息和已提交线程池但未执行的任务 + def cancel_session(self, session_id): with self.lock: if session_id in self.sessions: for future in self.futures[session_id]: future.cancel() - self.sessions[session_id][0]=Dequeue() + cnt = self.sessions[session_id][0].qsize() + if cnt>0: + logger.info("Cancel {} messages in session {}".format(cnt, session_id)) + self.sessions[session_id][0] = Dequeue() + + def cancel_all_session(self): + with self.lock: + for session_id in self.sessions: + for future in self.futures[session_id]: + future.cancel() + cnt = self.sessions[session_id][0].qsize() + if cnt>0: + logger.info("Cancel {} messages in session {}".format(cnt, session_id)) + self.sessions[session_id][0] = Dequeue() def check_prefix(content, prefix_list): diff --git a/plugins/godcmd/godcmd.py b/plugins/godcmd/godcmd.py index 0d574d0..d29e7fc 100644 --- a/plugins/godcmd/godcmd.py +++ b/plugins/godcmd/godcmd.py @@ -146,6 +146,7 @@ class Godcmd(Plugin): logger.debug("[Godcmd] on_handle_context. content: %s" % content) if content.startswith("#"): # msg = e_context['context']['msg'] + channel = e_context['channel'] user = e_context['context']['receiver'] session_id = e_context['context']['session_id'] isgroup = e_context['context']['isgroup'] @@ -181,6 +182,7 @@ class Godcmd(Plugin): elif cmd == "reset": if bottype in (const.CHATGPT, const.OPEN_AI): bot.sessions.clear_session(session_id) + channel.cancel_session(session_id) ok, result = True, "会话已重置" else: ok, result = False, "当前对话机器人不支持重置会话" @@ -202,6 +204,7 @@ class Godcmd(Plugin): ok, result = True, "配置已重载" elif cmd == "resetall": if bottype in (const.CHATGPT, const.OPEN_AI): + channel.cancel_all_session() bot.sessions.clear_all_session() ok, result = True, "重置所有会话成功" else: From 186e18fe94c801308a53c7a822a4f77e5f371a3a Mon Sep 17 00:00:00 2001 From: lanvent Date: Tue, 4 Apr 2023 14:58:51 +0800 Subject: [PATCH 17/21] godcmd: load clear_memory_commands --- config.py | 2 +- plugins/godcmd/godcmd.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/config.py b/config.py index b97c01f..a81741e 100644 --- a/config.py +++ b/config.py @@ -77,7 +77,7 @@ available_setting = { "wechaty_puppet_service_token": "", # wechaty的token # chatgpt指令自定义触发词 - "clear_memory_commands": ['#清除记忆'], # 重置会话指令 + "clear_memory_commands": ['#清除记忆'], # 重置会话指令,必须以#开头 # channel配置 "channel_type": "wx", # 通道类型,支持wx,wxy和terminal diff --git a/plugins/godcmd/godcmd.py b/plugins/godcmd/godcmd.py index d29e7fc..69e8c27 100644 --- a/plugins/godcmd/godcmd.py +++ b/plugins/godcmd/godcmd.py @@ -7,7 +7,7 @@ from typing import Tuple from bridge.bridge import Bridge from bridge.context import ContextType from bridge.reply import Reply, ReplyType -from config import load_config +from config import conf, load_config import plugins from plugins import * from common import const @@ -126,7 +126,14 @@ class Godcmd(Plugin): else: with open(config_path,"r") as f: gconf=json.load(f) - + + custom_commands = conf().get("clear_memory_commands", []) + for custom_command in custom_commands: + if custom_command and custom_command.startswith("#"): + custom_command = custom_command[1:] + if custom_command and custom_command not in COMMANDS["reset"]["alias"]: + COMMANDS["reset"]["alias"].append(custom_command) + self.password = gconf["password"] self.admin_users = gconf["admin_users"] # 预存的管理员账号,这些账号不需要认证 TODO: 用户名每次都会变,目前不可用 self.isrunning = True # 机器人是否运行中 From f652d592bdd777ebd3813f826ad50d53f0cfc2c3 Mon Sep 17 00:00:00 2001 From: lanvent Date: Tue, 4 Apr 2023 15:10:35 +0800 Subject: [PATCH 18/21] fix: typo in dequeue --- common/dequeue.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/dequeue.py b/common/dequeue.py index 5881106..edc9ef0 100644 --- a/common/dequeue.py +++ b/common/dequeue.py @@ -26,8 +26,8 @@ class Dequeue(Queue): self.unfinished_tasks += 1 self.not_empty.notify() - def put_nowait(self, item): - return self.put(item, block=False) + def putleft_nowait(self, item): + return self.putleft(item, block=False) def _putleft(self, item): self.queue.appendleft(item) \ No newline at end of file From 94004b095b176ae8072b055a50ebb668b36efb7b Mon Sep 17 00:00:00 2001 From: zhayujie Date: Tue, 4 Apr 2023 15:59:56 +0800 Subject: [PATCH 19/21] fix: no debug config #744 --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index a81741e..8c988b1 100644 --- a/config.py +++ b/config.py @@ -140,7 +140,7 @@ def load_config(): else: config[name] = value - if config["debug"]: + if config.get("debug", False): logger.setLevel(logging.DEBUG) logger.debug("[INIT] set log level to DEBUG") From f973bc3fe2bedaa9d10e2818a059ea967887322a Mon Sep 17 00:00:00 2001 From: lanvent Date: Tue, 4 Apr 2023 19:35:59 +0800 Subject: [PATCH 20/21] add requirements-optional.txt --- .github/ISSUE_TEMPLATE.md | 7 ++++--- README.md | 16 ++++++++++++---- docker/Dockerfile.alpine | 1 + docker/Dockerfile.debian | 3 ++- docker/Dockerfile.debian.latest | 1 + docker/Dockerfile.latest | 3 ++- requirements-optional.txt | 18 ++++++++++++++++++ requirements.txt | 13 +------------ 8 files changed, 41 insertions(+), 21 deletions(-) create mode 100644 requirements-optional.txt diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 62c98d3..4abe428 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -4,8 +4,9 @@ 2. python 已安装:版本在 3.7 ~ 3.10 之间 3. `git pull` 拉取最新代码 4. 执行`pip3 install -r requirements.txt`,检查依赖是否满足 -5. 在已有 issue 中未搜索到类似问题 -6. [FAQS](https://github.com/zhayujie/chatgpt-on-wechat/wiki/FAQs) 中无类似问题 +5. 拓展功能请执行`pip3 install -r requirements-optional.txt`,检查依赖是否满足 +6. 在已有 issue 中未搜索到类似问题 +7. [FAQS](https://github.com/zhayujie/chatgpt-on-wechat/wiki/FAQs) 中无类似问题 ### 问题描述 @@ -18,7 +19,7 @@ ### 终端日志 (如有报错) ``` -[在此处粘贴终端日志] +[在此处粘贴终端日志, 可在主目录下`run.log`文件中找到] ``` diff --git a/README.md b/README.md index d0e3cb2..dc00646 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ ### 2.运行环境 支持 Linux、MacOS、Windows 系统(可在Linux服务器上长期运行),同时需安装 `Python`。 -> 建议Python版本在 3.7.1~3.9.X 之间,3.10及以上版本在 MacOS 可用,其他系统上不确定能否正常运行。 +> 建议Python版本在 3.7.1~3.9.X 之间,推荐3.8版本,3.10及以上版本在 MacOS 可用,其他系统上不确定能否正常运行。 **(1) 克隆项目代码:** @@ -80,9 +80,15 @@ cd chatgpt-on-wechat/ pip3 install -r requirements.txt ``` -其中`tiktoken`要求`python`版本在3.8以上,它用于精确计算会话使用的tokens数量,可以不装但建议安装。 +**(3) 拓展依赖 (可选,建议安装):** + +```bash +pip3 install -r requirements-optional.txt +``` +> 如果某项依赖安装失败请注释掉对应的行再继续。 + +其中`tiktoken`要求`python`版本在3.8以上,它用于精确计算会话使用的tokens数量,强烈建议安装。 -**(3) 拓展依赖 (可选):** 使用`google`或`baidu`语音识别需安装`ffmpeg`, @@ -90,10 +96,12 @@ pip3 install -r requirements.txt 参考[#415](https://github.com/zhayujie/chatgpt-on-wechat/issues/415) -使用`azure`语音功能需安装依赖: +使用`azure`语音功能需安装依赖(列在`requirements-optional.txt`内,但为便于`railway`部署已注释): + ```bash pip3 install azure-cognitiveservices-speech ``` + > 目前默认发布的镜像和`railway`部署,都基于`apline`,无法安装`azure`的依赖。若有需求请自行基于[`debian`](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/docker/Dockerfile.debian.latest)打包。 参考[文档](https://learn.microsoft.com/en-us/azure/cognitive-services/speech-service/quickstarts/setup-platform?pivots=programming-language-python&tabs=linux%2Cubuntu%2Cdotnet%2Cjre%2Cmaven%2Cnodejs%2Cmac%2Cpypi) diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index 6b3b1fb..324a76e 100644 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -23,6 +23,7 @@ RUN apk add --no-cache \ && cp config-template.json ${BUILD_PREFIX}/config.json \ && /usr/local/bin/python -m pip install --no-cache --upgrade pip \ && pip install --no-cache -r requirements.txt \ + && pip install --no-cache -r requirements-optional.txt \ && apk del curl wget WORKDIR ${BUILD_PREFIX} diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index 0a17ae5..dfd289d 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -23,7 +23,8 @@ RUN apt-get update \ && cd ${BUILD_PREFIX} \ && cp config-template.json ${BUILD_PREFIX}/config.json \ && /usr/local/bin/python -m pip install --no-cache --upgrade pip \ - && pip install --no-cache -r requirements.txt + && pip install --no-cache -r requirements.txt \ + && pip install --no-cache -r requirements-optional.txt WORKDIR ${BUILD_PREFIX} diff --git a/docker/Dockerfile.debian.latest b/docker/Dockerfile.debian.latest index 6e5a3fe..95bb352 100644 --- a/docker/Dockerfile.debian.latest +++ b/docker/Dockerfile.debian.latest @@ -16,6 +16,7 @@ RUN apt-get update \ && cp config-template.json config.json \ && /usr/local/bin/python -m pip install --no-cache --upgrade pip \ && pip install --no-cache -r requirements.txt \ + && pip install --no-cache -r requirements-optional.txt \ && pip install azure-cognitiveservices-speech WORKDIR ${BUILD_PREFIX} diff --git a/docker/Dockerfile.latest b/docker/Dockerfile.latest index 53bb41b..c9a5a55 100644 --- a/docker/Dockerfile.latest +++ b/docker/Dockerfile.latest @@ -13,7 +13,8 @@ RUN apk add --no-cache bash ffmpeg espeak \ && cd ${BUILD_PREFIX} \ && cp config-template.json config.json \ && /usr/local/bin/python -m pip install --no-cache --upgrade pip \ - && pip install --no-cache -r requirements.txt + && pip install --no-cache -r requirements.txt \ + && pip install --no-cache -r requirements-optional.txt WORKDIR ${BUILD_PREFIX} diff --git a/requirements-optional.txt b/requirements-optional.txt new file mode 100644 index 0000000..1b6ff50 --- /dev/null +++ b/requirements-optional.txt @@ -0,0 +1,18 @@ +tiktoken>=0.3.2 # openai calculate token + +#voice +pydub>=0.25.1 # need ffmpeg +SpeechRecognition # google speech to text +gTTS>=2.3.1 # google text to speech +pyttsx3>=2.90 # pytsx text to speech +baidu_aip>=4.16.10 # baidu voice +# azure-cognitiveservices-speech # azure voice + +# wechaty +wechaty>=0.10.7 +wechaty_puppet>=0.4.23 +pysilk_mod>=1.6.0 # needed by send voice + +# webuiapi plugin +webuiapi>=0.6.2 + diff --git a/requirements.txt b/requirements.txt index a38c622..4e1e6a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,17 +1,6 @@ openai>=0.27.2 -baidu_aip>=4.16.10 -gTTS>=2.3.1 HTMLParser>=0.0.2 -pydub>=0.25.1 PyQRCode>=1.2.1 -pysilk>=0.0.1 -pysilk_mod>=1.6.0 -pyttsx3>=2.90 qrcode>=7.4.2 requests>=2.28.2 -webuiapi>=0.6.2 -wechaty>=0.10.7 -wechaty_puppet>=0.4.23 -chardet>=5.1.0 -SpeechRecognition -tiktoken>=0.3.2 \ No newline at end of file +chardet>=5.1.0 \ No newline at end of file From 9520d94b138500a41887a7dce3498eb02799b47d Mon Sep 17 00:00:00 2001 From: Jianglang Date: Tue, 4 Apr 2023 20:01:10 +0800 Subject: [PATCH 21/21] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dc00646..fec580c 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ cd chatgpt-on-wechat/ ``` **(2) 安装核心依赖 (必选):** - +> 能够使用`itchat`创建机器人,并具有文字交流功能所需的最小依赖集合。 ```bash pip3 install -r requirements.txt ```