選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

249 行
14KB

  1. from chatgpt_tool_hub.apps import AppFactory
  2. from chatgpt_tool_hub.apps.app import App
  3. from chatgpt_tool_hub.tools.tool_register import main_tool_register
  4. import plugins
  5. from bridge.bridge import Bridge
  6. from bridge.context import ContextType
  7. from bridge.reply import Reply, ReplyType
  8. from common import const
  9. from config import conf, get_appdata_dir
  10. from plugins import *
  11. @plugins.register(
  12. name="tool",
  13. desc="Arming your ChatGPT bot with various tools",
  14. version="0.5",
  15. author="goldfishh",
  16. desire_priority=0,
  17. )
  18. class Tool(Plugin):
  19. def __init__(self):
  20. super().__init__()
  21. self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context
  22. self.app = self._reset_app()
  23. if not self.tool_config.get("tools"):
  24. logger.warn("[tool] init failed, ignore ")
  25. raise Exception("config.json not found")
  26. logger.info("[tool] inited")
  27. def get_help_text(self, verbose=False, **kwargs):
  28. help_text = "这是一个能让chatgpt联网,搜索,数字运算的插件,将赋予强大且丰富的扩展能力。"
  29. trigger_prefix = conf().get("plugin_trigger_prefix", "$")
  30. if not verbose:
  31. return help_text
  32. help_text += "\n使用说明:\n"
  33. help_text += f"{trigger_prefix}tool " + "命令: 根据给出的{命令}模型来选择使用哪些工具尽力为你得到结果。\n"
  34. help_text += f"{trigger_prefix}tool 工具名 " + "命令: 根据给出的{命令}使用指定工具尽力为你得到结果。\n"
  35. help_text += f"{trigger_prefix}tool reset: 重置工具。\n\n"
  36. help_text += f"已加载工具列表: \n"
  37. for idx, tool in enumerate(main_tool_register.get_registered_tool_names()):
  38. if idx != 0:
  39. help_text += ", "
  40. help_text += f"{tool}"
  41. return help_text
  42. def on_handle_context(self, e_context: EventContext):
  43. if e_context["context"].type != ContextType.TEXT:
  44. return
  45. # 暂时不支持未来扩展的bot
  46. if Bridge().get_bot_type("chat") not in (
  47. const.CHATGPT,
  48. const.OPEN_AI,
  49. const.CHATGPTONAZURE,
  50. const.LINKAI,
  51. ):
  52. return
  53. content = e_context["context"].content
  54. content_list = e_context["context"].content.split(maxsplit=1)
  55. if not content or len(content_list) < 1:
  56. e_context.action = EventAction.CONTINUE
  57. return
  58. logger.debug("[tool] on_handle_context. content: %s" % content)
  59. reply = Reply()
  60. reply.type = ReplyType.TEXT
  61. trigger_prefix = conf().get("plugin_trigger_prefix", "$")
  62. # todo: 有些工具必须要api-key,需要修改config文件,所以这里没有实现query增删tool的功能
  63. if content.startswith(f"{trigger_prefix}tool"):
  64. if len(content_list) == 1:
  65. logger.debug("[tool]: get help")
  66. reply.content = self.get_help_text()
  67. e_context["reply"] = reply
  68. e_context.action = EventAction.BREAK_PASS
  69. return
  70. elif len(content_list) > 1:
  71. if content_list[1].strip() == "reset":
  72. logger.debug("[tool]: reset config")
  73. self.app = self._reset_app()
  74. reply.content = "重置工具成功"
  75. e_context["reply"] = reply
  76. e_context.action = EventAction.BREAK_PASS
  77. return
  78. elif content_list[1].startswith("reset"):
  79. logger.debug("[tool]: remind")
  80. e_context["context"].content = "请你随机用一种聊天风格,提醒用户:如果想重置tool插件,reset之后不要加任何字符"
  81. e_context.action = EventAction.BREAK
  82. return
  83. query = content_list[1].strip()
  84. use_one_tool = False
  85. for tool_name in main_tool_register.get_registered_tool_names():
  86. if query.startswith(tool_name):
  87. use_one_tool = True
  88. query = query[len(tool_name):]
  89. break
  90. # Don't modify bot name
  91. all_sessions = Bridge().get_bot("chat").sessions
  92. user_session = all_sessions.session_query(query, e_context["context"]["session_id"]).messages
  93. logger.debug("[tool]: just-go")
  94. try:
  95. if use_one_tool:
  96. _func, _ = main_tool_register.get_registered_tool()[tool_name]
  97. tool = _func(**self.app_kwargs)
  98. _reply = tool.run(query)
  99. else:
  100. # chatgpt-tool-hub will reply you with many tools
  101. _reply = self.app.ask(query, user_session)
  102. e_context.action = EventAction.BREAK_PASS
  103. all_sessions.session_reply(_reply, e_context["context"]["session_id"])
  104. except Exception as e:
  105. logger.exception(e)
  106. logger.error(str(e))
  107. e_context["context"].content = "请你随机用一种聊天风格,提醒用户:这个问题tool插件暂时无法处理"
  108. reply.type = ReplyType.ERROR
  109. e_context.action = EventAction.BREAK
  110. return
  111. reply.content = _reply
  112. e_context["reply"] = reply
  113. return
  114. def _read_json(self) -> dict:
  115. default_config = {"tools": [], "kwargs": {}}
  116. return super().load_config() or default_config
  117. def _build_tool_kwargs(self, kwargs: dict):
  118. tool_model_name = kwargs.get("model_name")
  119. request_timeout = kwargs.get("request_timeout")
  120. return {
  121. # 全局配置相关
  122. "log": False, # tool 日志开关
  123. "debug": kwargs.get("debug", False), # 输出更多日志
  124. "no_default": kwargs.get("no_default", False), # 不要默认的工具,只加载自己导入的工具
  125. "think_depth": kwargs.get("think_depth", 2), # 一个问题最多使用多少次工具
  126. "proxy": conf().get("proxy", ""), # 科学上网
  127. "request_timeout": request_timeout if request_timeout else conf().get("request_timeout", 120),
  128. "temperature": kwargs.get("temperature", 0), # llm 温度,建议设置0
  129. # LLM配置相关
  130. "llm_api_key": conf().get("open_ai_api_key", ""), # 如果llm api用key鉴权,传入这里
  131. "llm_api_base_url": conf().get("open_ai_api_base", "https://api.openai.com/v1"), # 支持openai接口的llm服务地址前缀
  132. "deployment_id": conf().get("azure_deployment_id", ""), # azure openai会用到
  133. # note: 目前tool暂未对其他模型测试,但这里仍对配置来源做了优先级区分,一般插件配置可覆盖全局配置
  134. "model_name": tool_model_name if tool_model_name else conf().get("model", const.GPT35),
  135. # 工具配置相关
  136. # for arxiv tool
  137. "arxiv_simple": kwargs.get("arxiv_simple", True), # 返回内容更精简
  138. "arxiv_top_k_results": kwargs.get("arxiv_top_k_results", 2), # 只返回前k个搜索结果
  139. "arxiv_sort_by": kwargs.get("arxiv_sort_by", "relevance"), # 搜索排序方式 ["relevance","lastUpdatedDate","submittedDate"]
  140. "arxiv_sort_order": kwargs.get("arxiv_sort_order", "descending"), # 搜索排序方式 ["ascending", "descending"]
  141. "arxiv_output_type": kwargs.get("arxiv_output_type", "text"), # 搜索结果类型 ["text", "pdf", "all"]
  142. # for bing-search tool
  143. "bing_subscription_key": kwargs.get("bing_subscription_key", ""),
  144. "bing_search_url": kwargs.get("bing_search_url", "https://api.bing.microsoft.com/v7.0/search"), # 必应搜索的endpoint地址,无需修改
  145. "bing_search_top_k_results": kwargs.get("bing_search_top_k_results", 2), # 只返回前k个搜索结果
  146. "bing_search_simple": kwargs.get("bing_search_simple", True), # 返回内容更精简
  147. "bing_search_output_type": kwargs.get("bing_search_output_type", "text"), # 搜索结果类型 ["text", "json"]
  148. # for email tool
  149. "email_nickname_mapping": kwargs.get("email_nickname_mapping", "{}"), # 关于人的代号对应的邮箱地址,可以不输入邮箱地址发送邮件。键为代号值为邮箱地址
  150. "email_smtp_host": kwargs.get("email_smtp_host", ""), # 例如 'smtp.qq.com'
  151. "email_smtp_port": kwargs.get("email_smtp_port", ""), # 例如 587
  152. "email_sender": kwargs.get("email_sender", ""), # 发送者的邮件地址
  153. "email_authorization_code": kwargs.get("email_authorization_code", ""), # 发送者验证秘钥(可能不是登录密码)
  154. # for google-search tool
  155. "google_api_key": kwargs.get("google_api_key", ""),
  156. "google_cse_id": kwargs.get("google_cse_id", ""),
  157. "google_simple": kwargs.get("google_simple", True), # 返回内容更精简
  158. "google_output_type": kwargs.get("google_output_type", "text"), # 搜索结果类型 ["text", "json"]
  159. # for finance-news tool
  160. "finance_news_filter": kwargs.get("finance_news_filter", False), # 是否开启过滤
  161. "finance_news_filter_list": kwargs.get("finance_news_filter_list", []), # 过滤词列表
  162. "finance_news_simple": kwargs.get("finance_news_simple", True), # 返回内容更精简
  163. "finance_news_repeat_news": kwargs.get("finance_news_repeat_news", False), # 是否过滤不返回。该tool每次返回约50条新闻,可能有重复新闻
  164. # for morning-news tool
  165. "morning_news_api_key": kwargs.get("morning_news_api_key", ""), # api-key
  166. "morning_news_simple": kwargs.get("morning_news_simple", True), # 返回内容更精简
  167. "morning_news_output_type": kwargs.get("morning_news_output_type", "text"), # 搜索结果类型 ["text", "image"]
  168. # for news-api tool
  169. "news_api_key": kwargs.get("news_api_key", ""),
  170. # for searxng-search tool
  171. "searxng_search_host": kwargs.get("searxng_search_host", ""),
  172. "searxng_search_top_k_results": kwargs.get("searxng_search_top_k_results", 2), # 只返回前k个搜索结果
  173. "searxng_search_output_type": kwargs.get("searxng_search_output_type", "text"), # 搜索结果类型 ["text", "json"]
  174. # for sms tool
  175. "sms_nickname_mapping": kwargs.get("sms_nickname_mapping", "{}"), # 关于人的代号对应的手机号,可以不输入手机号发送sms。键为代号值为手机号
  176. "sms_username": kwargs.get("sms_username", ""), # smsbao用户名
  177. "sms_apikey": kwargs.get("sms_apikey", ""), # smsbao
  178. # for stt tool
  179. "stt_api_key": kwargs.get("stt_api_key", ""), # azure
  180. "stt_api_region": kwargs.get("stt_api_region", ""), # azure
  181. "stt_recognition_language": kwargs.get("stt_recognition_language", "zh-CN"), # 识别的语言类型 部分:en-US ja-JP ko-KR yue-CN zh-CN
  182. # for tts tool
  183. "tts_api_key": kwargs.get("tts_api_key", ""), # azure
  184. "tts_api_region": kwargs.get("tts_api_region", ""), # azure
  185. "tts_auto_detect": kwargs.get("tts_auto_detect", True), # 是否自动检测语音的语言
  186. "tts_speech_id": kwargs.get("tts_speech_id", "zh-CN-XiaozhenNeural"), # 输出语音ID
  187. # for summary tool
  188. "summary_max_segment_length": kwargs.get("summary_max_segment_length", 2500), # 每2500tokens分段,多段触发总结tool
  189. # for terminal tool
  190. "terminal_nsfc_filter": kwargs.get("terminal_nsfc_filter", True), # 是否过滤llm输出的危险命令
  191. "terminal_return_err_output": kwargs.get("terminal_return_err_output", True), # 是否输出错误信息
  192. "terminal_timeout": kwargs.get("terminal_timeout", 20), # 允许命令最长执行时间
  193. # for visual tool
  194. "caption_api_key": kwargs.get("caption_api_key", ""), # ali dashscope apikey
  195. # for browser tool
  196. "browser_use_summary": kwargs.get("browser_use_summary", True), # 是否对返回结果使用tool功能
  197. # for url-get tool
  198. "url_get_use_summary": kwargs.get("url_get_use_summary", True), # 是否对返回结果使用tool功能
  199. # for wechat tool
  200. "wechat_hot_reload": kwargs.get("wechat_hot_reload", True), # 是否使用热重载的方式发送wechat
  201. "wechat_cpt_path": kwargs.get("wechat_cpt_path", os.path.join(get_appdata_dir(), "itchat.pkl")), # wechat 配置文件(`itchat.pkl`)
  202. "wechat_send_group": kwargs.get("wechat_send_group", False), # 是否向群组发送消息
  203. "wechat_nickname_mapping": kwargs.get("wechat_nickname_mapping", "{}"), # 关于人的代号映射关系。键为代号值为微信名(昵称、备注名均可)
  204. # for wikipedia tool
  205. "wikipedia_top_k_results": kwargs.get("wikipedia_top_k_results", 2), # 只返回前k个搜索结果
  206. # for wolfram-alpha tool
  207. "wolfram_alpha_appid": kwargs.get("wolfram_alpha_appid", ""),
  208. }
  209. def _filter_tool_list(self, tool_list: list):
  210. valid_list = []
  211. for tool in tool_list:
  212. if tool in main_tool_register.get_registered_tool_names():
  213. valid_list.append(tool)
  214. else:
  215. logger.warning("[tool] filter invalid tool: " + repr(tool))
  216. return valid_list
  217. def _reset_app(self) -> App:
  218. self.tool_config = self._read_json()
  219. self.app_kwargs = self._build_tool_kwargs(self.tool_config.get("kwargs", {}))
  220. app = AppFactory()
  221. app.init_env(**self.app_kwargs)
  222. # filter not support tool
  223. tool_list = self._filter_tool_list(self.tool_config.get("tools", []))
  224. return app.create_app(tools_list=tool_list, **self.app_kwargs)