ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

GPT-Academic 二级菜单插件开发指南:从 GptAcademicPluginTemplate 模板到前后端交互原理

GPT-Academic 二级菜单插件开发指南:从 GptAcademicPluginTemplate 模板到前后端交互原理 GPT-Academic 二级菜单插件开发指南从 GptAcademicPluginTemplate 模板到前后端交互原理【免费下载链接】gpt_academic为GPT/GLM等LLM大语言模型提供实用化交互接口特别优化论文阅读/润色/写作体验模块化设计支持自定义快捷按钮函数插件支持Python和C等项目剖析自译解功能PDF/LaTex论文翻译总结功能支持并行问询多种LLM模型支持chatglm3等本地模型。接入通义千问, deepseekcoder, 讯飞星火, 文心一言, llama2, rwkv, claude2, moss等。项目地址: https://gitcode.com/GitHub_Trending/gp/gpt_academic本文以 GPT-Academicgpt_academic官方文档 实现带二级菜单的插件 为骨架完整讲解新一代二级菜单插件的开发全流程如何继承插件模板类、如何声明参数菜单、如何实现执行逻辑、如何注册到功能插件区并结合仓库源码深入剖析点击插件按钮后参数菜单如何在前端 JavaScript 中生成、采集并最终汇入plugin_kwargs触发后端execute的完整调用链。读完本文你可以独立开发一个带自定义参数面板文本框 下拉菜单的 GPT-Academic 插件并理解其底层的前后端交互机制。一、什么是二级菜单插件GPT-Academic 的插件系统分两代旧式插件在 crazy_functional.py 中通过Function键注册一个可直接调用的函数点击按钮后直接执行用户无法在执行前调整参数新一代插件本文主角通过Class键注册一个继承GptAcademicPluginTemplate的类。用户点击插件按钮时不会立即执行而是弹出一个参数选择菜单即二级菜单用户确认参数后再触发execute方法。在 crazy_functional.py 中可以看到两种注册方式的真实对照历史上的今天: { Group: 对话, Color: stop, AsButton: False, Info: 查看历史上的今天事件 (这是一个面向开发者的插件Demo) | 不需要输入参数, Function: None, Class: Demo_Wrap, # 新一代插件需要注册Class }, PDF论文翻译: { Group: 学术, Color: stop, AsButton: True, Info: 精准翻译PDF论文为中文 | 输入参数为路径, Function: HotReload(批量翻译PDF文档), # 当注册Class后Function旧接口仅会在Void_Terminal中起作用 Class: PDF_Tran, # 新一代插件需要注册Class },从源码注释可以确认一个关键细节当插件注册了Class后Function旧接口仅在 Void_Terminal命令行界面中起作用Web 界面的执行全部走类插件的二级菜单通道。二、开发一个二级菜单插件的五个步骤步骤 1继承 GptAcademicPluginTemplate父类模板定义于 plugin_class_template.py声明插件只需三步走继承类、覆盖define_arg_selection_menu、覆盖execute。from crazy_functions.plugin_template.plugin_class_template import GptAcademicPluginTemplate from crazy_functions.plugin_template.plugin_class_template import ArgProperty class Demo_Wrap(GptAcademicPluginTemplate): def __init__(self): ...阅读模板源码 plugin_class_template.py 有几点官方文档未展开、但直接影响实现的约束值得注意execute可能运行在不同线程中模板构造函数中的注释明确提醒不要在插件实例里保存会被多线程并发访问的状态参数个数上限为 8get_js_code_for_generating_menu会在超过 8 个参数时抛出ValueError见 plugin_class_template.py这与前端预置 8 个文本框 8 个下拉菜单的组件池一一对应见 gui_advanced_plugin_class.py菜单定义在编码为 Base64 之前会经过json.dumps即每个参数的值必须能 JSON 序列化。步骤 2定义二级菜单 ——define_arg_selection_menu覆盖父类的define_arg_selection_menu函数返回一个字典键是参数名值是用 Pydantic 模型ArgProperty描述并经model_dump_json()序列化后的 JSON 字符串。class Demo_Wrap(GptAcademicPluginTemplate): ... def define_arg_selection_menu(self): 定义插件的二级选项菜单 第一个参数名称main_input参数type声明这是一个文本框文本框上方显示title文本框内部显示descriptiondefault_value为默认值 第二个参数名称advanced_arg参数type声明这是一个文本框文本框上方显示title文本框内部显示descriptiondefault_value为默认值 第三个参数名称allow_cache参数type声明这是一个下拉菜单下拉菜单上方显示titledescription下拉菜单的选项为optionsdefault_value为下拉菜单默认值 gui_definition { main_input: ArgProperty(titleArxivID, description输入Arxiv的ID或者网址, default_value, typestring).model_dump_json(), advanced_arg: ArgProperty(title额外的翻译提示词, descriptionr如果有必要, 请在此处给出自定义翻译命令, default_value, typestring).model_dump_json(), allow_cache: ArgProperty(title是否允许从缓存中调取结果, options[允许缓存, 从头执行], default_value允许缓存, description无, typedropdown).model_dump_json(), } return gui_definition对照 ArgProperty 的定义各字段含义如下字段说明适用类型title参数标题显示在控件上方label全部description参数描述显示为 placeholder 并拼在 label 后全部default_value默认值菜单生成时自动填入全部typestring渲染为文本框dropdown渲染为下拉菜单全部options下拉菜单的候选项列表仅dropdown官方文档特别强调了两个特殊参数名的语义从前端源码 common.js 可以得到印证main_input自动与界面右上角的输入区user_input_main/user_input_float同步——菜单弹出时自动把当前输入区的文本载入该文本框advanced_arg自动与界面右下角的高级参数输入区advance_arg_input_legacy同步除此之外参数名可以任意选取其余参数则使用default_value作为初始值。步骤 3编写插件逻辑 ——execute覆盖父类的execute函数它是一个generator 函数用yield from逐条产出对话内容class Demo_Wrap(GptAcademicPluginTemplate): ... def execute(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, user_request): 执行插件 plugin_kwargs字典中会包含用户的选择与上述 define_arg_selection_menu 一一对应 allow_cache plugin_kwargs[allow_cache] advanced_arg plugin_kwargs[advanced_arg] if allow_cache 从头执行: plugin_kwargs[advanced_arg] --no-cache plugin_kwargs[advanced_arg] yield from 翻译Arxiv论文并重新编译PDF(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, user_request)execute的固定签名为execute(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, user_request)。其中plugin_kwargs字典的键与define_arg_selection_menu中声明的参数名一一对应值为用户在二级菜单中确认的值字符串txt即主输入区的文本。如果菜单中声明了main_input后端会用它覆盖txt见后文route_switchy_bt_with_arg的源码llm_kwargs、chatbot、history、system_prompt等与基础功能区的调用约定一致。上面的示例也展示了常见的参数联动写法根据下拉菜单的选项从头执行/允许缓存动态改写advanced_arg把界面选项翻译成底层脚本的命令行参数。步骤 4注册插件将条目插入 crazy_functional.py 的插件注册字典中。与旧插件不同的是Function键值应为NoneClass键值为你的插件类新插件: { Group: 学术, Color: stop, AsButton: True, Info: 插件说明, Function: None, Class: Demo_Wrap, },各注册字段的含义以仓库中现有条目为参照Group插件分组功能区按钮按分组筛选显示Color按钮样式如stop红色AsButtonTrue表示在功能区显示为固定按钮False表示仅出现在下拉菜单中Info按钮悬浮提示与下拉菜单说明Function旧式执行函数入口类插件中置NoneClass新一代插件的类本文的主角。步骤 5启动测试完成以上四步即可启动main.py测试点击新插件按钮应弹出参数选择浮层修改参数后点击确认参数并执行execute开始流式输出。至此开发流程结束。三、背后的原理从点击按钮到执行插件的完整调用链官方文档第二部分用 JavaScript 视角解释了这套机制。其总体思路是程序启动时把每个插件的二级菜单编码为 Base64 下发到浏览器用户点击插件时前端按该 Base64 编码把平时隐藏的菜单组件有选择地显示出来用户确认后前端模拟点击一个隐藏按钮把参数带回后端。下面按执行时序结合源码逐环节剖析。3.1 三个 Gradio 官方没有提供的底层前端 API主 JavaScript 程序 common.js 提供了三个二级菜单机制依赖的核心函数1get_data_from_gradio_component—— 获取任意 Gradio 组件的当前值文本框内容、下拉框当前选项、chatbot 对话等。其实现非常薄见 common.jsasync function get_data_from_gradio_component(ELEM_ID) { let comp await get_gradio_component(ELEM_ID); return comp.props.value; }2get_gradio_component—— 获取组件句柄本身。当你不仅需要当前值还需要 label、是否隐藏、下拉菜单的全部候选项等属性时使用。其实现common.js通过CustomEvent向 Gradio 前端请求组件对象并以 Promise 方式取回// 获取下拉菜单组件的句柄 var model_sel await get_gradio_component(elem_model_sel); // 获取它的所有属性包括其所有可选选项 console.log(model_sel.props)3push_data_to_gradio_component—— 将数据推回组件是生成/销毁菜单的核心操作。第一个参数是 value可以是字符串调整文本框、按钮的文本也可以是{ visible: false, __type__: update }这样的字典调整 visible、label、choices第二个参数是elem_id第三个参数为string或obj。典型用法// 修改一个按钮上面的文本 push_data_to_gradio_component(btnName, gradio_element_id, string); // 隐藏一个组件 push_data_to_gradio_component({ visible: false, __type__: update }, plugin_arg_menu, obj); // 修改组件label push_data_to_gradio_component({ label: 新label的值, __type__: update }, gpt-chatbot, obj)3.2 启动阶段菜单编码为 Base64 并下发main.py 在构建 Gradio 界面时遍历所有插件。对注册了Class的插件实例化后调用模板方法把菜单定义编码为 Base64# main.py 中的插件注册循环节选 for k in plugins: register_advanced_plugin_init_arr fregister_plugin_init({k},{encode_plugin_info(k, plugins[k])}); if plugins[k].get(Class, None): plugins[k][JsMenu] plugins[k][Class]().get_js_code_for_generating_menu(k) register_advanced_plugin_init_arr register_advanced_plugin_init_code({k},{gui_js});.format(kk, gui_jsplugins[k][JsMenu]) if not plugins[k].get(AsButton, True): continue if plugins[k].get(Class, None) is None: assert plugins[k].get(Function, None) is not None click_handle plugins[k][Button].click(None, inputs[], outputsNone, _jsf()run_classic_plugin_via_id({plugins[k][ButtonElemId]})) else: click_handle plugins[k][Button].click(None, inputs[], outputsNone, _jsf()run_advanced_plugin_launch_code({k}))其中get_js_code_for_generating_menu就是 模板类 中的方法先调用插件的define_arg_selection_menu()json.dumps后做 Base64 编码。这些register_*调用被拼接进register_advanced_plugin_init_arr最终在 main.py 通过app_block.load(..., _js...)注入页面加载脚本app_block.load(None, inputs[], outputsNone, _jsf(){REP}.replace(REP, register_advanced_plugin_init_arr))3.3 浏览器加载阶段缓存菜单编码前端 common.js 中register_advanced_plugin_init_code把每个插件的 Base64 菜单码存入全局字典以secondary_menu_code属性挂到plugin_init_info_lib中function register_advanced_plugin_init_code(key, code) { if (key in plugin_init_info_lib) { } else { plugin_init_info_lib[key] {}; } plugin_init_info_lib[key].secondary_menu_code code; }说明官方文档中的示例把它简化为独立的advanced_plugin_init_code_lib字典实际源码是复用了register_plugin_init建立的plugin_init_info_lib二者语义一致。3.4 点击插件按钮唤醒隐藏的二级菜单由于启动阶段已把按钮点击绑定为纯前端 JS 调用_jsf()run_advanced_plugin_launch_code({k})点击时不经过后端只执行 common.js 中的两段代码function run_advanced_plugin_launch_code(key) { generate_menu(plugin_init_info_lib[key].secondary_menu_code, key); } function on_flex_button_click(key) { if (plugin_init_info_lib.hasOwnProperty(key) plugin_init_info_lib[key].hasOwnProperty(secondary_menu_code)) { run_advanced_plugin_launch_code(key); } else { document.getElementById(old_callback_btn_for_plugin_exe).click(); } }核心渲染逻辑在generate_menucommon.js其工作方式为把 Base64 串通过atob解码为 JSON并把其中每个参数条目本身也是 JSON 字符串二次解析成gui_args字典把菜单编码与插件名分别推入两个隐藏组件invisible_current_pop_up_plugin_arg保存菜单定义和隐藏执行按钮的 label显示plugin_arg_menu浮动面板visible: true先调用hide_all_elem()把全部 8 个预留文本框、8 个预留下拉菜单复位为隐藏按参数声明顺序依次点亮控件type string占用下一个plugin_arg_txt_{n}文本框type dropdown占用下一个plugin_arg_drop_{n}下拉菜单并设置 label、placeholder、choices、初始值对特殊参数做兼容加载main_input自动读入user_input_mainuser_input_float的当前文本advanced_arg自动读入advance_arg_input_legacy的值——这正是自动同步输入区的前端实现。这套88 预留组件池 按需点亮的设计解释了为什么插件参数个数被硬性限制为 8 个gui_advanced_plugin_class.py 中一次性创建了plugin_arg_txt_0..7与plugin_arg_drop_0..7共 16 个控件。3.5 确认执行参数回传与隐藏按钮模拟点击用户点击二级菜单中的确认参数并执行按钮时该按钮在 gui_advanced_plugin_class.py 中定义为arg_confirm_btn其click绑定的是纯 JS 回调()execute_current_pop_up_plugin()前端执行 execute_current_pop_up_plugin重新解码菜单 Base64按声明顺序逐个读取用户在每个文本框、下拉菜单中最终确认的值user_confirmed_value关闭菜单面板并复位所有控件把带确认值的gui_args序列化为 JSON推入隐藏文本框invisible_current_pop_up_plugin_arg_final调用document.getElementById(invisible_callback_btn_for_plugin_exe).click()模拟点击隐藏按钮从而把控制权交还给后端的 Gradio 事件回调。3.6 后端汇聚route_switchy_bt_with_arg触发execute隐藏按钮invisible_callback_btn_for_plugin_exe在 gui_advanced_plugin_class.py 中通过define_gui_advanced_plugin_class定义并在 main.py 中绑定路由函数click_handle_ng new_plugin_callback.click(route_switchy_bt_with_arg, [ gr.State([new_plugin_callback, usr_confirmed_arg] input_combo_order), # 第一个参数: 指定了后续参数的名称 new_plugin_callback, usr_confirmed_arg, *input_combo # 后续参数: 真正的参数 ], output_combo)第一个输入是一个gr.State按名字列出后续所有参数的顺序route_switchy_bt_with_arggui_advanced_plugin_class.py据此把所有位置参数重新整理为 kwargs 字典def route_switchy_bt_with_arg(request: gr.Request, input_order, *arg): arguments {k:v for k,v in zip(input_order, arg)} # 重新梳理输入参数转化为kwargs字典 which_plugin arguments.pop(new_plugin_callback) # 获取需要执行的插件名称 if which_plugin in [r未选定任何插件]: return usr_confirmed_arg arguments.pop(usr_confirmed_arg) # 获取插件参数 arg_confirm: dict {} usr_confirmed_arg_dict json.loads(usr_confirmed_arg) # 读取插件参数 for arg_name in usr_confirmed_arg_dict: arg_confirm.update({arg_name: str(usr_confirmed_arg_dict[arg_name][user_confirmed_value])}) if plugins[which_plugin].get(Class, None) is not None: # 获取插件执行函数 plugin_obj plugins[which_plugin][Class] plugin_exe plugin_obj.execute else: plugin_exe plugins[which_plugin][Function] arguments[plugin_advanced_arg] arg_confirm # 更新高级参数输入区的参数 if arg_confirm.get(main_input, None) is not None: # 更新主输入区的参数 arguments[txt] arg_confirm[main_input] # 万事俱备开始执行 yield from ArgsGeneralWrapper(plugin_exe)(request, *arguments.values())这段代码把前文所有环节串了起来new_plugin_callback组件的文本即插件名generate_menu时写入决定了执行哪个插件usr_confirmed_arg组件中的 JSON 被解析后取出每项的user_confirmed_value汇入arg_confirm——这就是传给execute的plugin_kwargsmain_input会覆盖txt主输入区实现菜单与输入区的双向一致最终经ArgsGeneralWrapper统一包装后以 generator 方式执行插件的execute流式输出到 chatbot。四、关键源码位置速查环节源码位置说明插件模板与 ArgPropertycrazy_functions/plugin_template/plugin_class_template.py类定义、8 参数上限、Base64 编码插件注册表crazy_functional.py新增插件条目写入此处插件按钮与菜单下发main.py启动阶段生成 Base64 菜单码并绑定按钮 JS隐藏参数区 GUI 定义themes/gui_advanced_plugin_class.py88 预留控件、隐藏确认/执行按钮、路由函数前端菜单渲染与参数采集themes/common.jsgenerate_menu/execute_current_pop_up_plugin插件初始化注册JS 侧themes/common.jsregister_plugin_init/register_advanced_plugin_init_code/run_advanced_plugin_launch_code五、小结GPT-Academic 的二级菜单插件机制本质上是一个轻量级的前端表单协议后端用 PydanticArgProperty声明式描述参数JSON Base64 跨语言传输前端用固定的 16 个预留控件池按需渲染最终通过隐藏按钮模拟点击把用户确认值回传由route_switchy_bt_with_arg汇聚成plugin_kwargs调用execute。对插件开发者而言你只需要关心 docs/plugin_with_secondary_menu.md 中描述的四件事——继承GptAcademicPluginTemplate、实现define_arg_selection_menu、实现execute、在 crazy_functional.py 中注册而参数同步、菜单渲染、线程包装等复杂交互均由框架的模板类与common.js自动完成。【免费下载链接】gpt_academic为GPT/GLM等LLM大语言模型提供实用化交互接口特别优化论文阅读/润色/写作体验模块化设计支持自定义快捷按钮函数插件支持Python和C等项目剖析自译解功能PDF/LaTex论文翻译总结功能支持并行问询多种LLM模型支持chatglm3等本地模型。接入通义千问, deepseekcoder, 讯飞星火, 文心一言, llama2, rwkv, claude2, moss等。项目地址: https://gitcode.com/GitHub_Trending/gp/gpt_academic创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表