Repository: RockChinQ/revLibs Branch: master Commit: 4bb7da653228 Files: 25 Total size: 49.2 KB Directory structure: gitextract_2z_xvd8v/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ └── issue模板.md │ └── dependabot.yml ├── .gitignore ├── README.md ├── __init__.py ├── main.py ├── pkg/ │ ├── __init__.py │ ├── accounts/ │ │ └── accmgr.py │ ├── models/ │ │ ├── forward.py │ │ └── interface.py │ ├── process/ │ │ ├── __init__.py │ │ ├── impls/ │ │ │ ├── __init__.py │ │ │ ├── bard.py │ │ │ ├── claude.py │ │ │ ├── edgegpt.py │ │ │ ├── fproxy.py │ │ │ ├── gpt4free.py │ │ │ ├── hugchat.py │ │ │ └── v1impl.py │ │ ├── proccmd.py │ │ ├── procmsg.py │ │ └── revss.py │ └── utils.py ├── requirements.txt └── revcfg-template.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/issue模板.md ================================================ --- name: issue模板 about: 任何问题请使用这个模板创建 title: "" labels: '' assignees: '' --- 请您认真填写以下信息。 ### 任务 - [ ] 我已严格按照文档中的步骤操作 - [ ] 我已经搜索了相关的issue ### 预期/正在使用的逆向库 我要使用的逆向库是:(例如:New Bing) ### 期望情况 我希望的情况是: ### 实际情况 实际情况是: ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "pip" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "daily" allow: - dependency-name: "revChatGPT" - dependency-name: "EdgeGPT" ================================================ FILE: .gitignore ================================================ __pycache__/ ================================================ FILE: README.md ================================================ # RevLib Support for QChatGPT > 2023/8/29 现已支持`gpt4free`,请查看下方的使用方法 > 2023/8/14 现已支持`Claude`和`Bard`,请查看下方的使用方法 > 2023/8/03 逆向库现已支持`函数调用`, 配置方法同[主程序配置方法](https://github.com/RockChinQ/QChatGPT/wiki/%E6%8F%92%E4%BB%B6%E4%BD%BF%E7%94%A8-%E5%86%85%E5%AE%B9%E5%87%BD%E6%95%B0) 得益于[QChatGPT项目](https://github.com/RockChinQ/QChatGPT)的插件功能,此插件将允许接入`ChatGPT网页版`用以替换原项目主线的GPT-3模型接口,提升回复质量。 [官方接口与ChatGPT网页版的区别?](https://github.com/RockChinQ/QChatGPT/wiki/%E5%AE%98%E6%96%B9%E6%8E%A5%E5%8F%A3%E4%B8%8EChatGPT%E7%BD%91%E9%A1%B5%E7%89%88) ## 安装方式 > 若您未安装QChatGPT程序,请先查看原仓库[文档](https://github.com/RockChinQ/QChatGPT) 使用管理员账号私聊机器人发送指令: ``` !plugin https://github.com/RockChinQ/revLibs ``` 若无法访问GitHub,可以使用Gitee镜像 ``` !plugin https://gitee.com/RockChin/revLibs ``` 等待程序获取源码,并解决依赖,这可能需要数分钟的时间。 安装完毕后,请发送: ``` !reload ``` 重载插件,生成配置文件,**关闭主程序**。 到`QChatGPT`程序目录编辑`revcfg.py`文件,根据注释修改必填配置项。 配置完成后重新启动主程序以使用。 ## Cookies获取方法 大部分逆向库基于Cookies登录,因此需要获取Cookies。这里讲解了获取一个网站的Cookies的详细步骤,您可以先查看下方选择逆向库的步骤,到需要的网站使用以下方式获取Cookies。 1. 安装适用于[Chrome/Edge](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm) 或 [Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/) 的Cookies编辑器插件 2. 访问 目标网站 3. 打开这个插件 4. 点击 `Export` 按钮, 复制JSON格式的Cookies 5. 将`Cookies`保存到指定的文件中 ## 选择逆向库 目前支持的逆向库及使用方式如下, 下方所述文件保存位置均为主程序`config.py`同目录,若无此文件,请自行创建:
ChatGPT网页版 本插件默认使用的逆向库,使用方法请参考配置文件注释。 使用的是 [acheong08/ChatGPT](https://github.com/acheong08/ChatGPT) > **WARNING** > 必须配置反向代理才能使用,默认的是作者自建的,不一定能用,建议自己搭建。 > 可以参考:[另外一个项目的文档,仅参考 ChatGPT 反向代理搭建方式](https://free-one-api.rockchin.top/#/zh-CN/Adapters?id=%e5%8f%8d%e5%90%91%e4%bb%a3%e7%90%86)
New Bing(暂不可用) 使用的是 [acheong08/EdgeGPT](https://github.com/acheong08/EdgeGPT) - 修改`revcfg.py`中的`reverse_lib`的值为`acheong08/EdgeGPT` - 获取[NewBing](https://bing.com/chat)的Cookies,保存到`cookies.json`中 #### 配置 - new bing逆向库默认输出参考资料, 若不需要, 请在`revcfg.py`中设置: ```python output_references = False ``` - 设置New Bing的风格 查看revcfg.py中的`new_bing_style`字段,按照说明更改。在运行期间可以通过指令`!style <风格(创意、平衡、精确)>`来更改风格。
HuggingChat 1. 在`revcfg.py`中修改`reverse_lib`的值为`Soulter/hugging-chat-api` 2. 获取[HuggingChat](https://huggingface.co/chat/)的Cookies,保存到`hugchat.json`中
Claude 1. 在`revcfg.py`中修改`reverse_lib`的值为`KoushikNavuluri/Claude-API` 2. 获取[Claude](https://claude.ai/chats)的Cookies,保存到`claude.json`中
Google Bard 1. 在`revcfg.py`中修改`reverse_lib`的值为`dsdanielpark/Bard-API` 2. 获取[Bard](https://bard.google.com/)的Cookies,保存到`bard.json`中
gpt4free 接入[xtekky/gpt4free](https://github.com/xtekky/gpt4free)自动从数个平台选择可用的 GPT-4,**无需鉴权**,但很不稳定,仅需要在`revcfg.py`中修改`reverse_lib`的值为`xtekky/gpt4free`即可。 #### 配置 - gpt4free 提供了多个渠道,默认情况下程序会自动测试并选择可用的渠道,可以在 `revcfg.py` 中指定要使用的渠道和要排除的渠道 - 向机器人发送 `!provider ls` 来查看所有支持设置的渠道, 发送 `!provider` 查看当前使用的渠道

### 🚫请勿修改`revcfg-template.py`的内容,配置项请在主程序`config.py`同目录的`revcfg.py`中修改🚫 ## 特别感谢 > 向所有致力于人工智能民主化的开发者致敬。 > Salute to all developers committed to the democratization of artificial intelligence. > 인공지능 민주화에 힘쓴 모든 개발자들에게 경의를 표합니다. > 人工知能の民主化に取り組むすべての開発者に敬意を表します。 ================================================ FILE: __init__.py ================================================ ================================================ FILE: main.py ================================================ import traceback from pkg.plugin.models import * from pkg.plugin.host import EventContext, PluginHost, emit import time import os from revChatGPT.V1 import Chatbot import plugins.revLibs.pkg.process.revss as revss import plugins.revLibs.pkg.process.procmsg as procmsg import plugins.revLibs.pkg.process.proccmd as proccmd from EdgeGPT.EdgeGPT import ConversationStyle def check_config(): this_file = __file__ template_file = os.path.join(os.path.dirname(this_file), "revcfg-template.py") # 检查revlib.py是否存在 if not os.path.exists("revcfg.py"): # 不存在则使用本模块同目录的revcfg-template.py复制创建 with open(template_file, "r", encoding="utf-8") as f: template = f.read() with open("revcfg.py", "w", encoding="utf-8") as f: f.write(template) return False return True # 注册插件 @register(name="revLibs", description="接入acheong08/ChatGPT、Claude、Bard等逆向库", version="0.9.1", author="RockChinQ") class RevLibsPlugin(Plugin): chatbot: Chatbot = None # 插件加载时触发 def __init__(self, plugin_host: PluginHost): # 执行依赖库更新 # try: # import plugins.revLibs.pkg.utils as utils # # utils.upgrade_revlibs() # except: # traceback.print_exc() # logging.warn("[rev] 依赖库更新失败,不能确保逆向库正常运行") if not check_config(): logging.error("[rev] 已生成配置文件(revcfg.py),请按照其中注释填写配置文件后重启程序") # plugin_host.notify_admin("[rev] 已生成配置文件(revcfg.py),请按照其中注释填写配置文件后重启程序") return import revcfg if not hasattr(revcfg, "new_bing_style"): setattr(revcfg, "new_bing_style", ConversationStyle.balanced) try: from plugins.revLibs.pkg.process.impls import v1impl, edgegpt, hugchat, claude, bard, gpt4free reverse_lib_mapping = { "acheong08/ChatGPT.V1": v1impl.RevChatGPTV1, "acheong08/EdgeGPT": edgegpt.EdgeGPTImpl, "Soulter/hugging-chat-api": hugchat.HugChatImpl, "KoushikNavuluri/Claude-API": claude.ClaudeImpl, "dsdanielpark/Bard-API": bard.BardImpl, "xtekky/gpt4free": gpt4free.GPT4FreeImpl, } import plugins.revLibs.pkg.process.revss as revss if hasattr(revcfg, "reverse_lib") and revcfg.reverse_lib in reverse_lib_mapping: revss.__rev_interface_impl_class__ = reverse_lib_mapping[revcfg.reverse_lib] logging.info("[rev] 已加载逆向库{}, 使用接口实现类: {}".format(revcfg.reverse_lib, str(reverse_lib_mapping[revcfg.reverse_lib]))) else: logging.error("[rev] 未知的逆向库: " + revcfg.reverse_lib + ", 请检查配置文件是否填写正确或尝试更新逆向库插件") time.sleep(5) return except: # 输出完整的错误信息 # plugin_host.notify_admin("[rev] 逆向库初始化失败,请检查配置文件(revcfg.py)是否正确") logging.error("[rev] 逆向库初始化失败,请检查配置文件(revcfg.py)是否正确") logging.error("[rev] " + traceback.format_exc()) return import config revcfg.process_message_timeout = config.process_message_timeout config.process_message_timeout = 10*60 logging.info("[rev] 已将主程序消息处理超时时间设置为10分钟") @on(PersonNormalMessageReceived) @on(GroupNormalMessageReceived) def normal_message_received(inst, event: EventContext, **kwargs): event.prevent_default() event.prevent_postorder() reply = [] try: prefix = revcfg.reply_prefix reply_message = "" reply_message = procmsg.process_message(session_name=kwargs['launcher_type']+"_"+str(kwargs['launcher_id']), prompt=kwargs['text_message'], **kwargs) logging.debug("[rev] " + reply_message) reply_message = reply_message # 触发NormalMessageResponded事件 args = { "launcher_type": kwargs['launcher_type'], "launcher_id": kwargs['launcher_id'], "sender_id": kwargs['sender_id'], "session": None, "prefix": prefix, "response_text": reply_message, "finish_reason": "revLibs."+revcfg.reverse_lib+".finish", } inter_event: EventContext = emit(NormalMessageResponded, **args) reply = [] if inter_event.get_return_value("prefix") is not None: prefix = inter_event.get_return_value("prefix") if inter_event.get_return_value("reply") is not None: reply = inter_event.get_return_value("reply") if not inter_event.is_prevented_default(): reply = [prefix + reply_message] except Exception as e: logging.error("[rev] " + traceback.format_exc()) import config if config.hide_exce_info_to_user: reply_message = '' try: import tips reply_message = tips.alter_tip_message except: if hasattr(config, "alter_tip_message"): reply_message = config.alter_tip_message else: reply_message = "处理消息时出现错误,请联系管理员" kwargs['host'].notify_admin("[rev] 处理消息时出现错误:\n"+traceback.format_exc()) else: reply_message = "处理消息时出现错误,请联系管理员"+"\n"+traceback.format_exc() reply = [revcfg.reply_prefix+reply_message] if reply_message != "": event.add_return( "reply", reply, ) @on(PersonCommandSent) @on(GroupCommandSent) def command_send(inst, event: EventContext, **kwargs): reply_message = "" try: reply_message = proccmd.process_command(session_name=kwargs['launcher_type']+"_"+str(kwargs['launcher_id']), **kwargs) logging.debug("[rev] " + reply_message) except Exception as e: logging.error("[rev] " + traceback.format_exc()) reply_message = "处理命令时出现错误,请联系管理员"+"\n"+traceback.format_exc() if reply_message.strip() != "": event.add_return( "reply", ["{}(cmd)".format(revcfg.reply_prefix)+reply_message], ) event.prevent_default() event.prevent_postorder() def make_reply(self, prompt, **kwargs) -> dict: reply_gen = self.chatbot.ask(prompt, **kwargs) reply = {} for r in reply_gen: reply = r return reply # 插件卸载时触发 def __del__(self): pass ================================================ FILE: pkg/__init__.py ================================================ ================================================ FILE: pkg/accounts/accmgr.py ================================================ import time def get_account_list(): import revcfg if type(revcfg.openai_account) is dict: revcfg.openai_account = [revcfg.openai_account] return revcfg.openai_account def get_account_brief_name(account: dict): if 'email' in account: return account['email'] elif 'session_token' in account: return "sessionToken: "+account['session_token'][:8] elif 'access_token' in account: return "accessToken: "+account['access_token'][:8] else: return "未知账户" def move_account_to_end(account: dict): # 将账户移动到末尾 import revcfg account_list = get_account_list() account_list.remove(account) account_list.append(account) revcfg.openai_account = account_list def delete_invalid_attr(account: dict): temp_dict = dict(account) if 'invalid_at' in temp_dict: del temp_dict['invalid_at'] return temp_dict def use_account() -> tuple[bool, dict]: # 计算并返回下一个账户 import revcfg account_resume_interval = revcfg.openai_account_resume_interval if hasattr(revcfg, 'openai_account_resume_interval') else 60 now = int(time.time()) for account in get_account_list(): if 'invalid_at' in account: if account['invalid_at'] < now - account_resume_interval * 60: move_account_to_end(account) return True, delete_invalid_attr(account) if 'invalid_at' not in account: move_account_to_end(account) return True, delete_invalid_attr(account) move_account_to_end(get_account_list()[0]) return False, delete_invalid_attr(get_account_list()[0]) def report_invalidation(account: dict): # 报告账户失效 for acc in get_account_list(): if 'email' in acc and 'email' in account and acc['email'] == account['email']: acc['invalid_at'] = int(time.time()) return elif 'session_token' in acc and 'session_token' in account and acc['session_token'] == account['session_token']: acc['invalid_at'] = int(time.time()) return elif 'access_token' in acc and 'access_token' in account and acc['access_token'] == account['access_token']: acc['invalid_at'] = int(time.time()) return ================================================ FILE: pkg/models/forward.py ================================================ from mirai.models.message import MessageComponent from mirai.models.message import ForwardMessageNode from mirai.models.base import MiraiBaseModel from typing import List, Optional class ForwardMessageDiaplay(MiraiBaseModel): title: str = "群聊的聊天记录" brief: str = "[聊天记录]" source: str = "聊天记录" preview: List[str] = [] summary: str = "查看x条转发消息" class Forward(MessageComponent): """合并转发。""" type: str = "Forward" """消息组件类型。""" display: ForwardMessageDiaplay """显示信息""" node_list: List[ForwardMessageNode] """转发消息节点列表。""" def __init__(self, *args, **kwargs): if len(args) == 1: self.node_list = args[0] super().__init__(**kwargs) super().__init__(*args, **kwargs) def __str__(self): return '[聊天记录]' ================================================ FILE: pkg/models/interface.py ================================================ from typing import Tuple class RevLibInterface: """逆向库接口""" @staticmethod def create_instance() -> tuple["RevLibInterface", bool, dict]: raise NotImplementedError def get_rev_lib_inst(self): raise NotImplementedError def get_reply(self, prompt: str, **kwargs) -> Tuple[str, dict]: raise NotImplementedError def reset_chat(self, **kwargs): raise NotImplementedError def rollback(self): raise NotImplementedError ================================================ FILE: pkg/process/__init__.py ================================================ ================================================ FILE: pkg/process/impls/__init__.py ================================================ ================================================ FILE: pkg/process/impls/bard.py ================================================ from typing import Tuple import logging from plugins.revLibs.pkg.models.interface import RevLibInterface from bardapi import Bard import os import json class BardImpl(RevLibInterface): chatbot: Bard = None token: str = None @staticmethod def create_instance() -> tuple[RevLibInterface, bool, dict]: import revcfg # 检查bard的cookies是否存在 if not os.path.exists("bard.json"): logging.error("Bard cookies不存在") raise Exception("Bard cookies不存在, 请根据文档进行配置: https://github.com/RockChinQ/revLibs") cookies_list = [] with open("bard.json", "r", encoding="utf-8") as f: cookies_list = json.load(f) return BardImpl(cookies_list), True, cookies_list def __init__(self, cookies: str = None): __Secure_1PSID = "" for cookie in cookies: if cookie["name"] == "__Secure-1PSID": __Secure_1PSID = cookie["value"] break if __Secure_1PSID == "": raise Exception("Bard cookies中不含有所需的__Secure-1PSID字段, 请尝试重新获取.") self.token = __Secure_1PSID self.chatbot = Bard(token=__Secure_1PSID) def get_rev_lib_inst(self): return self.chatbot def get_reply(self, prompt: str, **kwargs) -> Tuple[str, dict]: logging.debug("[rev] 请求bard回复: {}".format(prompt)) resp = self.chatbot.get_answer(prompt) yield resp['content'], resp def reset_chat(self): self.chatbot = Bard(token=self.token) def rollback(self): pass ================================================ FILE: pkg/process/impls/claude.py ================================================ from typing import Tuple import logging from plugins.revLibs.pkg.models.interface import RevLibInterface from claude_api import Client import os import json class ClaudeImpl(RevLibInterface): chatbot: Client = None conversation_id: str = None @staticmethod def create_instance() -> tuple[RevLibInterface, bool, dict]: import revcfg # 检查claude的cookies是否存在 if not os.path.exists("claude.json"): logging.error("Claude cookies不存在") raise Exception("Claude cookies不存在, 请根据文档进行配置: https://github.com/RockChinQ/revLibs") cookies_list = [] with open("claude.json", "r", encoding="utf-8") as f: cookies_list = json.load(f) # 把cookies转换为字符串 cookies = "" for cookie in cookies_list: cookies += cookie["name"] + "=" + cookie["value"] + ";" return ClaudeImpl(cookies), True, cookies_list def __init__(self, cookies: str = None): self.chatbot = Client(cookies) def get_rev_lib_inst(self): return self.chatbot def get_reply(self, prompt: str, **kwargs) -> Tuple[str, dict]: logging.debug("[rev] 请求claude回复: {}".format(prompt)) if self.conversation_id is None: self.conversation_id = self.chatbot.create_new_chat()['uuid'] resp = self.chatbot.send_message(prompt, self.conversation_id) yield resp, {} def reset_chat(self): self.conversation_id = None def rollback(self): pass ================================================ FILE: pkg/process/impls/edgegpt.py ================================================ """接入acheone08/EdgeGPT """ import os import logging import json import asyncio from plugins.revLibs.pkg.models.interface import RevLibInterface from EdgeGPT.EdgeGPT import Chatbot, ConversationStyle ref_num_loop = ['¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹', '¹⁰', '¹¹', '¹²', '¹³', '¹⁴', '¹⁵', '¹⁶', '¹⁷', '¹⁸', '¹⁹', '²⁰'] class EdgeGPTImpl(RevLibInterface): """使用acheong08/EdgeGPT接入new bing """ chatbot: Chatbot = None style = ConversationStyle.balanced inst_name: str wss_link: str = "wss://sydney.bing.com/sydney/ChatHub" @staticmethod def create_instance() -> tuple[RevLibInterface, bool]: import revcfg return EdgeGPTImpl(revcfg.new_bing_style if hasattr(revcfg, "new_bing_style") else ConversationStyle.balanced), True def __init__(self, style): # logging.debug("[rev] 初始化接口实现,使用账户cookies: {}".format(str(cookies)[:30])) logging.debug("[rev] 初始化New Bing接口实现") import revcfg params = {} if hasattr(revcfg, "new_bing_proxy"): params["proxy"] = revcfg.new_bing_proxy logging.info("[rev] 初始化NewBing使用代理: {}".format(revcfg.new_bing_proxy)) if hasattr(revcfg, "new_bing_reverse_proxy") and revcfg.new_bing_reverse_proxy != "": self.wss_link = revcfg.new_bing_reverse_proxy logging.info("[rev] 初始化NewBing使用反向代理: {}".format(revcfg.new_bing_reverse_proxy)) if os.path.exists("cookies.json"): with open("cookies.json", "r") as f: cookies_list = json.load(f) params["cookies"] = cookies_list logging.info("[rev] 初始化NewBing具有Cookies") self.chatbot = Chatbot(**params) self.style = style # 随机一个uuid作为实例名 import uuid self.inst_name = str(uuid.uuid4()) def get_rev_lib_inst(self): return self.chatbot def get_reply(self, prompt: str, **kwargs) -> tuple[str, dict]: """获取回复""" logging.debug("[rev] 请求bing回复: {}".format(prompt)) task = self.chatbot.ask(prompt, conversation_style=self.style, wss_link=self.wss_link) resp = asyncio.run(task) logging.debug(json.dumps(resp, indent=4, ensure_ascii=False)) reply_obj = resp["item"]["messages"][-1] if 'messageType' not in resp["item"]["messages"][-1] else resp["item"]["messages"][-2] body = reply_obj["text"] if "text" in reply_obj else ( reply_obj['hiddenText'] if "hiddenText" in reply_obj else ( reply_obj['spokenText'] if "spokenText" in reply_obj else "" ) ) if "sourceAttributions" in reply_obj: refs_str = "参考资料: \n" index = 1 for ref in reply_obj["sourceAttributions"]: refs_str += "{}".format(ref_num_loop[index-1]) + " " + ref['providerDisplayName'] + " | " + ref['seeMoreUrl'] + "\n" index += 1 throttling = resp["item"]["throttling"] throttling_str = "本次对话: {}/{}".format(throttling["numUserMessagesInConversation"], throttling["maxNumUserMessagesInConversation"]) import revcfg if hasattr(revcfg, "output_references") and not revcfg.output_references: # 把正文的[^n^]替换成空 import re body = re.sub(r"\[\^[0-9]+\^\]", "", body) else: # 把正文的[^n^]替换成对应的序号 for i in range(index): body = body.replace("[^"+str(i+1)+"^]", ref_num_loop[i]) # if throttling["numUserMessagesInConversation"] == 3: if throttling["numUserMessagesInConversation"] == throttling["maxNumUserMessagesInConversation"]: self.reset_chat() throttling_str += "(已达最大次数,下一回合将开启新对话)" reply_str = body + "\n\n" + ((refs_str + "\n\n") if index != 1 and (not hasattr(revcfg, "output_references") or revcfg.output_references) else "") + throttling_str yield reply_str, resp else: self.reset_chat() yield "err: 可能由于内容不当,对话已被接口拒绝,下一回合将开启新的会话。", resp def reset_chat(self): asyncio.run(self.chatbot.reset()) def rollback(self): pass ================================================ FILE: pkg/process/impls/fproxy.py ================================================ class Proxy: funcs: list def __init__(self, funcs: list): self.funcs = funcs def prompt(self, user_prompt: str): if len(self.funcs) == 0: return user_prompt raw = \ """ # You now have access to the following functions: ```yaml {functions}``` # P.S. - `required` means the arguments are required. # If user's input matches the description of a certain function, please output with following format to call the function: ```json {{"name": "function_name", "args": {{"arg_name": "arg_value", ...}}}} ``` # If not, you can reply in your own way. # User's input: {user_prompt} # Rules - You can only call one function at a time. # Your output: """.strip() funcs = "" for i in range(len(self.funcs)): # - name: func_name # description: func_description # args: # - name: arg_name # description: arg_description # type: int # - name: arg_name # description: arg_description # type: str # required: [arg_name, ...] # return: return_type func = self.funcs[i] func_desc = func['description'].replace("\n", "") args = "" for arg in func['parameters']['properties']: args += \ f""" - name: {arg} description: {func['parameters']['properties'][arg]['description']} type: {func['parameters']['properties'][arg]['type']} """ if args.endswith("\n"): args = args[:-1] func_str = \ f"""- name: {func['name']} description: {func_desc} args: {args} required: {str(func['parameters']['required'])} """ funcs += func_str return raw.format(functions=funcs, user_prompt=user_prompt) def call(self, func_name: str, args: dict) -> tuple[bool, str, any]: for func in self.funcs: if func['name'] == func_name: return func['function'](**args) return "error: no such function" def extra_function_call(self, text: str): """Extra function call request from text ```json { "name": "access_web", "args": { "link": "https://www.example.com/search?q=RockChinQ" } } ``` """ import re import json # 用正则提取json数据 # 直接找大括号包围的, 匹配全文,不限于一行内 json_strs = re.findall(r"\{.*\}", text, re.S) func_name = None args = None found = False # 逐个检查json数据是否符合格式 for json_str in json_strs: try: # 删除所有// xxx json_str = re.sub(r",\s*//.*", "", json_str) json_data = json.loads(json_str) func_name = json_data['name'] args = json_data['args'] found = True break except: continue if not found: return False, None, None return True, func_name, args ================================================ FILE: pkg/process/impls/gpt4free.py ================================================ from typing import Tuple import logging from plugins.revLibs.pkg.models.interface import RevLibInterface import g4f from g4f import models class GPT4FreeImpl(RevLibInterface): messages: list[dict] use_model: models.Model = models.gpt_4 use_provider: g4f.Provider.BaseProvider = None @staticmethod def create_instance() -> tuple[RevLibInterface, bool, dict]: return GPT4FreeImpl(), True, {} @classmethod def select_provider(cls): logging.info("[rev] 测试并选择provider,如果某个provider测试过久,可以在revcfg.py中将其排除") from g4f.Provider import __all__ as providers providers = providers.copy() exclude = [ 'Acytoo' ] import revcfg if hasattr(revcfg, 'g4f_use_adapters') and len(revcfg.g4f_use_adapters) > 0: providers = revcfg.g4f_use_adapters logging.debug("[rev] 已指定provider列表: {}".format(providers)) if hasattr(revcfg, 'g4f_exclude_adapters') and len(revcfg.g4f_exclude_adapters) > 0: exclude = revcfg.g4f_exclude_adapters logging.debug("[rev] 已指定排除provider列表: {}".format(exclude)) for provider in providers: if provider in exclude: continue logging.info("[rev] 测试provider: {}".format(provider)) provider = getattr(g4f.Provider, provider) try: response = g4f.ChatCompletion.create( model=cls.use_model, messages=[ { "role": "user", "content": "hi" } ], provider=provider ) logging.debug("[rev] 测试provider: {} 成功: {}".format(provider, response)) cls.use_provider = provider break except Exception as e: continue def __init__(self): self.messages = [] if GPT4FreeImpl.use_provider is None: # 测试可用模型 self.select_provider() def get_rev_lib_inst(self): return None def get_reply(self, prompt: str, **kwargs) -> Tuple[str, dict]: logging.debug("[rev] 请求gpt4free: {} 回复: {}".format(GPT4FreeImpl.use_provider, prompt)) retry = 0 while True: try: resp = g4f.ChatCompletion.create( model=GPT4FreeImpl.use_model, messages=self.messages+[ { "role": "user", "content": prompt } ], provider=GPT4FreeImpl.use_provider, ) self.messages.append( { "role": "user", "content": prompt } ) self.messages.append( { "role": "assistant", "content": resp } ) yield resp, {} break except Exception as e: retry += 1 if retry >= 3: raise e GPT4FreeImpl.select_provider() def reset_chat(self): self.messages = [] def rollback(self): if len(self.messages) < 2: return self.messages.pop() self.messages.pop() ================================================ FILE: pkg/process/impls/hugchat.py ================================================ from typing import Tuple import logging from plugins.revLibs.pkg.models.interface import RevLibInterface from hugchat import hugchat import os import json class HugChatImpl(RevLibInterface): chatbot: hugchat.ChatBot = None @staticmethod def create_instance() -> tuple[RevLibInterface, bool, dict]: import revcfg # 检查hugging chat的cookies是否存在 if not os.path.exists("hugchat.json"): logging.error("HuggingChat cookies不存在") raise Exception("HuggingChat cookies不存在, 请根据文档进行配置: https://github.com/RockChinQ/revLibs") cookies_dict = {} with open("hugchat.json", "r", encoding="utf-8") as f: cookies_dict = json.load(f) return HugChatImpl(cookies_dict), True, cookies_dict def __init__(self, cookies_dict: dict = None): self.chatbot = hugchat.ChatBot(cookies=cookies_dict) def get_rev_lib_inst(self): return self.chatbot def get_reply(self, prompt: str, **kwargs) -> Tuple[str, dict]: logging.debug("[rev] 请求hugchat回复: {}".format(prompt)) resp = self.chatbot.chat(prompt) yield resp, {} def reset_chat(self): self.chatbot.new_conversation() def rollback(self): pass ================================================ FILE: pkg/process/impls/v1impl.py ================================================ from typing import Tuple from plugins.revLibs.pkg.models.interface import RevLibInterface from revChatGPT.V1 import Chatbot import threading __thr_locks__ = {} def get_lock(key: str): if key not in __thr_locks__: __thr_locks__[key] = threading.Lock() return __thr_locks__[key] import logging class RevChatGPTV1(RevLibInterface): """acheong08/ChatGPT的逆向库接口 V1""" chatbot: Chatbot = None inst_name: str @staticmethod def create_instance() -> tuple[RevLibInterface, bool, dict]: import plugins.revLibs.pkg.accounts.accmgr as accmgr valid_acc, acc = accmgr.use_account() return RevChatGPTV1(acc), valid_acc, acc def __init__(self, cfg): logging.debug("[rev] 初始化接口实现,使用账户配置: {}".format(cfg)) import revcfg if hasattr(revcfg, "revchatgpt_reverse_proxy") and revcfg.revchatgpt_reverse_proxy != "": import revChatGPT.V1 as revcgpt revcgpt.BASE_URL = revcfg.revchatgpt_reverse_proxy logging.info("[rev] 已指定revchatgpt的反向代理: {}".format(revcfg.revchatgpt_reverse_proxy)) self.chatbot = Chatbot( config=cfg, ) self.inst_name = str(cfg) def get_rev_lib_inst(self): return self.chatbot def get_reply(self, prompt: str, **kwargs) -> Tuple[str, dict]: import revcfg logging.debug("[rev] 请求ChatGPT回复: {}".format(prompt)) # 构建函数代理 funcs = [] import pkg.plugin.models as models import pkg.plugin.host as plugin_host import pkg.openai.funcmgr as funcmgr if hasattr(models, "require_ver"): funcs = funcmgr.get_func_schema_list() for func in funcs: func['function'] = plugin_host.__function_inst_map__[func['name']] from .fproxy import Proxy fp = Proxy(funcs) try: get_lock(self.inst_name).acquire() if self.chatbot is None: raise Exception("acheong08/ChatGPT.V1 逆向接口未初始化") prompt = fp.prompt(prompt) reply_gen = self.chatbot.ask(prompt, **kwargs) already_reply_msg = "" reply = {} first_received = False for r in reply_gen: if not first_received: first_received = True logging.debug("已响应,正在接收...") reply = r if "message" in reply: assert isinstance(reply['message'], str) reply['message'] = reply['message'].replace(already_reply_msg, "") logging.debug("接收完毕: {}".format(reply)) # 检查是否有JSON函数调用 is_func_call, func_name, args = fp.extra_function_call(reply['message']) while is_func_call: logging.info("[REV] 执行函数调用: {} {}".format(func_name, args)) result = "" try: result = fp.call(func_name, args) logging.debug("func result: {}".format(result)) logging.info("[REV] 函数调用完成。") except Exception as e: logging.info("[REV] 函数调用失败。") result = "error: failed to call function: "+str(e) reply = {} prompt = f"""## Function call result: ``` {result} ``` ## Rules - If you haven't done completely, you can call function again. - Please reply in the user's language.""" for data in self.chatbot.ask( prompt=prompt, ): try: assert 'message' in data reply = data except: continue logging.debug("ChatGPT's output: {}".format(reply['message'])) is_func_call, func_name, args = fp.extra_function_call(reply['message']) yield reply['message'], reply except Exception as e: raise e finally: get_lock(self.inst_name).release() def reset_chat(self): self.chatbot.reset_chat() def rollback(self): self.chatbot.rollback_conversation() ================================================ FILE: pkg/process/proccmd.py ================================================ import plugins.revLibs.pkg.process.revss as revss import plugins.revLibs.pkg.process.impls.v1impl as v1impl import plugins.revLibs.pkg.process.impls.edgegpt as edgegpt import plugins.revLibs.pkg.process.impls.gpt4free as gpt4free def process_command(session_name: str, **kwargs) -> str: """处理命令""" cmd = kwargs['command'] params = kwargs['params'] reply_message = "" if cmd == 'reset': session: revss.RevSession = revss.get_session(session_name) if len(params) >= 1: prompt_whole_name = session.reset(params[0]) reply_message = "已重置会话,使用情景预设: {}".format(prompt_whole_name) else: session.reset() reply_message = "已重置会话" elif cmd == 'list': session: revss.RevSession = revss.get_session(session_name) page = 1 if len(params)>=1: page = int(params[0]) cbinst = session.get_rev_lib_inst() from revChatGPT.V1 import Chatbot assert isinstance(cbinst, Chatbot) conversations = cbinst.get_conversations((page-1)*10, 10) reply_message = "会话列表 (第{}页) 本页{}个会话:\n".format(page, len(conversations)) for conversation in conversations: reply_message += "#{}: {}\n".format(conversation['id'], conversation['create_time']) elif cmd == 'prompt': # cbinst = session.get_rev_lib_inst() # from revChatGPT.V1 import Chatbot # assert isinstance(cbinst, Chatbot) # reply_message = str(cbinst.get_msg_history(session.conversation_id)) reply_message = "正在使用逆向库,暂不支持查看历史消息" elif cmd == "last": reply_message = "正在使用逆向库,暂不支持切换到前一次会话" elif cmd == "next": reply_message = "正在使用逆向库,暂不支持切换到后一次会话" elif cmd == "resend": session: revss.RevSession = revss.get_session(session_name) if session.__ls_prompt__ == "": reply_message = "没有上一条成功回复的消息" else: reply_message = session.resend() elif cmd == "accs": """查看每个账户的使用情况""" import plugins.revLibs.pkg.accounts.accmgr as accmgr if revss.__rev_interface_impl_class__ == v1impl.RevInterfaceImplV1: reply_message = "账户列表:\n" for account in accmgr.get_account_list(): """ - 账户名称 """ reply_message += "账户: {}\n - ".format(accmgr.get_account_brief_name(account)) using = False for k in revss.__sessions__: v: revss.RevSession = revss.__sessions__[k] if accmgr.get_account_brief_name(v.using_account) == accmgr.get_account_brief_name(account): reply_message += v.name + ", " using = True if not using: reply_message += "未使用" else: reply_message = reply_message[:-2] reply_message += "\n\n" reply_message = reply_message[:-1] else: reply_message = "仅当使用ChatGPT逆向库时可查看账户负载情况" elif cmd == "style": if revss.__rev_interface_impl_class__ == edgegpt.EdgeGPTImpl: import revcfg from EdgeGPT import ConversationStyle if len(params) >= 1: mapping = { "创意": ConversationStyle.creative, "平衡": ConversationStyle.balanced, "精确": ConversationStyle.precise, } if params[0] not in mapping: reply_message = "风格参数错误,可选参数: 创意, 平衡, 精确" return reply_message setattr(revcfg, "new_bing_style", mapping[params[0]]) reply_message = "已切换到{}风格,重置会话后生效".format(params[0]) else: current = "创意" if getattr(revcfg, "new_bing_style") == ConversationStyle.balanced: current = "平衡" elif getattr(revcfg, "new_bing_style") == ConversationStyle.precise: current = "精确" reply_message = "当前风格为: {},可选参数: 创意, 平衡, 精确\n例如: !style 创意".format(current) else: reply_message = "仅当使用New Bing逆向库时可切换风格" elif cmd == "provider": if revss.__rev_interface_impl_class__ == gpt4free.GPT4FreeImpl: if len(params) >= 1: if params[0] == 'ls': from g4f.Provider import __all__ as providers providers = providers.copy() reply_message = "适配器列表:\n" for adapter in providers: reply_message += " - {}\n".format(adapter) reply_message = reply_message[:-1] else: if gpt4free.GPT4FreeImpl.use_provider is None: reply_message = "当前未选择适配器,第一次对话时将测试并选择适配器" else: reply_message = "当前使用的适配器: {}".format(gpt4free.GPT4FreeImpl.use_provider) else: reply_message = "仅当使用gpt4free逆向库时可查看适配器" return reply_message ================================================ FILE: pkg/process/procmsg.py ================================================ import time import plugins.revLibs.pkg.process.revss as revss from pkg.plugin.host import PluginHost import logging from mirai import MessageChain from mirai.models.message import ForwardMessageNode from plugins.revLibs.pkg.models.forward import Forward, ForwardMessageDiaplay import traceback import pkg.utils.context as context import pkg.qqbot.manager as qqmgr __host__: PluginHost = None def process_message(session_name: str, prompt: str, host: PluginHost, **kwargs) -> str: """处理消息""" global __host__ logging.info("[rev] 收到{}消息: {}".format(session_name, prompt)) if __host__ is None: __host__ = host if host is None: host = __host__ import revcfg # 重试循环 fail_times = 0 reply_message = "" while True: session: revss.RevSession = revss.get_session(session_name) try: if hasattr(revcfg, "blog_msg_strategy"): logging.warning("[rev] 逆向库不再进行长消息处理,请使用主程序的长消息处理功能,详情请查看主程序的config-template.py") all_reply = "" for section in session.get_reply(prompt): all_reply += section reply_message = all_reply # 发送使用量信息 from pkg.utils.context import get_openai_manager openai_mgr = get_openai_manager() openai_mgr.audit_mgr.report_to_server( revcfg.reverse_lib, len(reply_message) ) break except Exception as e: traceback.print_exc() if str(e).__contains__("Too many requests in 1 hour"): session.__init__(session_name) logging.warn("超过一小时限次,切换会话账户") continue if fail_times < revcfg.retry_when_fail: fail_times += 1 logging.error(traceback.format_exc()) logging.warn("失败,重试({}/{})...".format(fail_times, revcfg.retry_when_fail)) time.sleep(2) continue else: raise e return reply_message ================================================ FILE: pkg/process/revss.py ================================================ # 逆向库的session from plugins.revLibs.pkg.models.interface import RevLibInterface from plugins.revLibs.pkg.process.impls.v1impl import RevChatGPTV1 from plugins.revLibs.pkg.process.impls.edgegpt import EdgeGPTImpl from plugins.revLibs.pkg.process.impls.hugchat import HugChatImpl from plugins.revLibs.pkg.process.impls.claude import ClaudeImpl from plugins.revLibs.pkg.process.impls.bard import BardImpl import pkg.openai.dprompt as dprompt import uuid import time import logging import threading import config __sessions__ = {} """所有session""" __rev_interface_impl_class__: RevLibInterface = None class RevSession: name: str conversation_id: str = None """会话id,第一次获取到回复时生成""" parent_id: str = None """父会话id""" __rev_interface_impl__: RevLibInterface = None __ls_prompt__: str = "" __set_prompt__: str = "" using_account: dict = None last_interaction_time: int = 0 """最后一次交互时间""" getting_reply: bool = False def __init__(self, name: str): self.name = name logging.debug("[rev] 逆向库接口实现为 {}".format(__rev_interface_impl_class__)) self.__rev_interface_impl__, valid, acc = __rev_interface_impl_class__.create_instance() self.using_account = acc self.reset() # if __rev_interface_impl_class__ is RevChatGPTV1: # logging.debug("[rev] 逆向接口实现为RevChatGPTV1") # self.__rev_interface_impl__, valid, acc = __rev_interface_impl_class__.create_instance() # self.using_account = acc # self.reset() # elif __rev_interface_impl_class__ is EdgeGPTImpl: # logging.debug("[rev] 逆向接口实现为EdgeGPTImpl") # self.__rev_interface_impl__,_ = __rev_interface_impl_class__.create_instance() # self.reset() # elif __rev_interface_impl_class__ is HugChatImpl: # logging.debug("[rev] 逆向接口实现为HugChatImpl") # self.__rev_interface_impl__,_,_ = __rev_interface_impl_class__.create_instance() # self.reset() # elif __rev_interface_impl_class__ is ClaudeImpl: # logging.debug("[rev] 逆向接口实现为ClaudeImpl") # self.__rev_interface_impl__,_,_ = __rev_interface_impl_class__.create_instance() # self.reset() # elif __rev_interface_impl_class__ is BardImpl: # logging.debug("[rev] 逆向接口实现为BardImpl") # self.__rev_interface_impl__,_,_ = __rev_interface_impl_class__.create_instance() # self.reset() threading.Thread(target=self.check_expire_loop, daemon=True).start() def check_expire_loop(self): while True: time.sleep(60) if self not in __sessions__.values(): break if self.last_interaction_time < int(time.time()) - config.session_expire_time and not self.getting_reply: # 删除此session logging.info("[rev] 会话 {} 已过期,自动重置".format(self.name)) del __sessions__[self.name] break def get_rev_lib_inst(self): return self.__rev_interface_impl__.get_rev_lib_inst() def get_reply(self, prompt: str, **kwargs) -> str: """获取回复""" self.getting_reply = True if self.__rev_interface_impl__ is None: raise Exception("逆向接口未初始化") self.last_interaction_time = int(time.time()) self.__ls_prompt__ = prompt if self.conversation_id is not None: kwargs['conversation_id'] = self.conversation_id using_name = dprompt.mode_inst().get_using_name() dprompt_, _ = dprompt.mode_inst().get_prompt(using_name) if self.__set_prompt__ != "": dprompt_ = self.__set_prompt__ self.__set_prompt__ = "" if dprompt_ != "" and self.conversation_id is None and __rev_interface_impl_class__ is not EdgeGPTImpl: # new bing不使用情景预设 if type(dprompt_) == list: dprompt_ = dprompt_[0]['content'] # prompt = dprompt_ +" \n"+ prompt prompt = f"{dprompt_} \n{prompt}" logging.info("[rev] 使用情景预设: {}".format(dprompt_)) # 改成迭代器以支持回复分节 for reply_period_msg, reply_period_dict in self.__rev_interface_impl__.get_reply(prompt, **kwargs): if __rev_interface_impl_class__ is RevChatGPTV1: self.conversation_id = reply_period_dict['conversation_id'] else: self.conversation_id = uuid.uuid4().hex yield reply_period_msg self.getting_reply = False def reset(self, using_prompt_name: str = None) -> str: """重置会话""" self.conversation_id = None self.parent_id = None self.__ls_prompt__ = "" self.__rev_interface_impl__.reset_chat() if using_prompt_name is not None: for key in dprompt.mode_inst().list(): if key.startswith(using_prompt_name): using_prompt_name = key break self.__set_prompt__ = dprompt.mode_inst().get_prompt(using_prompt_name) return using_prompt_name def resend(self): """重新发送上一条消息""" self.__rev_interface_impl__.rollback() import plugins.revLibs.pkg.process.procmsg as procmsg # 不优雅的解决办法 return procmsg.process_message(self.name, self.__ls_prompt__, None, launcher_type=self.name.split("_")[0], launcher_id=int(self.name.split("_")[1])) def get_session(name: str) -> RevSession: """获取session""" if name not in __sessions__: # 创建session __sessions__[name] = RevSession(name) return __sessions__[name] ================================================ FILE: pkg/utils.py ================================================ from pip._internal import main as pipmain def upgrade_revlibs(): """更新逆向库""" pipmain(['install', '--upgrade', 'revChatGPT', '--quiet']) import main main.reset_logging() ================================================ FILE: requirements.txt ================================================ revChatGPT~=6.8.6 EdgeGPT~=0.13.2 hugchat requests claude-api bardapi g4f==0.1.9.3 PyExecJS ================================================ FILE: revcfg-template.py ================================================ from EdgeGPT.EdgeGPT import ConversationStyle # 选择使用的逆向库 # 目前支持以下库: # - "acheong08/ChatGPT.V1": acheong08/ChatGPT库的V1版本 # - "acheong08/EdgeGPT": acheong08/EdgeGPT库,接入new bing # - "Soulter/hugging-chat-api": Soulter/hugging-chat-api库,接入hugging chat # - "KoushikNavuluri/Claude-API": KoushikNavuluri/Claude-API库,接入Claude # - "dsdanielpark/Bard-API": dsdanielpark/Bard-API库,接入Bard # - "xtekky/gpt4free": xtekky/gpt4free库,接入多个平台的免费的 GPT-4,无需鉴权 reverse_lib = "acheong08/ChatGPT.V1" # [必填][❗此说明很重要,请您认真阅读❗] OpenAI账户信息 # *仅使用acheong08/ChatGPT.V1时填写 # 目前支持三种登录方式: # - 账号密码登录(仅支持ChatGPT Plus账号) # - SessionToken登录(仅Microsoft、Google账号注册的账号) # - accessToken登录(普通账号请使用此方法登录) # # *账号密码登录方式,例如: # openai_account = { # "email": "your email", # "password": "your password" # } # # *若要使用SessionToken登录方式,请删掉email和password参数,添加session_token参数: # 例如: # openai_account = { # "session_token": "your session token" # } # # *若要使用accessToken登录方式,请删掉email和password参数,添加access_token参数: # 你可以在 https://chat.openai.com/api/auth/session 返回的数据中找到你的accessToken,记得先登录再获取 # 例如: # openai_account = { # "access_token": "your access token" # } # # 除了登录信息,还支持以下可选参数: # - proxy: 代理服务器地址,格式为"protocol:ip:port",例如"https://localhost:1080" # - paid: 是否订阅了ChatGPT Plus服务,若为True则使用ChatGPT Plus服务 # - model: 使用的模型, 若要使用GPT-4, 可以添加此参数并设置为"gpt-4" # 可选参数填写格式示例: # openai_account = [ # { # "access_token": "your access_token", # "proxy": "https://localhost:1080", # "paid": True, # "model": "gpt-4" # } # ] # # **若要使用多个账户均衡负载,可以以列表的形式添加多个账户信息,例如: # openai_account = [ # { # "access_token": "your access_token", # }, # { # "access_token": "your access_token", # } # ] # 其中每个账户的格式符合前文所述的格式 openai_account = [ { "access_token": "your access_token", } ] # 账号重新恢复使用的时间间隔 # 以分钟为单位 openai_account_resume_interval = 60 # New Bing的代理地址 # 参考config.py中openai的代理地址 # 若为None则不使用代理 new_bing_proxy = None # New Bing的Style # 请将此值设置为以下之一: # ConversationStyle.creative 有创意 # ConversationStyle.balanced 平衡 # ConversationStyle.precise 精确 new_bing_style = ConversationStyle.balanced # 使用New Bing时是否显示参考资料 output_references = True # 使用 revChatGPT 时使用的反向代理 # 现在不用反向代理基本没法用了吧,可以自己上github找一下 # 用来搭建 ChatGPT 反向代理的项目 # 或者到 QChatGPT 主页找到社区群加进去有公用的反向代理 # 这里默认的是作者的反向代理,但是不保证一直可用 revchatgpt_reverse_proxy = "https://chatproxy.rockchin.top/api/" # 使用 New Bing (EdgeGPT) 时使用的反向代理 # 参考 revchatgpt_reverse_proxy,但是你需要的是 New Bing 的反向代理 # 需要不同的项目,社区群里也有公用的 new_bing_reverse_proxy = "" # 消息回复前缀 # 建议保留此前缀,以便区分GPT-3和此插件的回复 reply_prefix = "[REV]" # 获取回复失败时的重试次数 # 若为0则不重试 retry_when_fail = 3 # 使用 gpt4free 时,仅使用的适配器名称列表 # 程序将在这些适配器中选择一个可用的适配器 # 例如: # g4f_use_adapters = ['Acytoo', 'FakeGpt'] # # 若为 空列表 则会测试所有适配器并自动选择 # 所有适配器列表可以在 # https://github.com/xtekky/gpt4free/blob/main/g4f/Provider/__init__.py # 中找到 # 也可以再启动机器人之后,发送命令 `!provider ls` 列出所有适配器 # # 注意:设置此字段可能会影响可用性,请确认指定的适配器可用 g4f_use_adapters = [] # 使用 gpt4free 时,排除的适配器名称 # 此字段优先级高于 g4f_use_adapters g4f_exclude_adapters = []