Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

tool.py 14KB

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