diff --git a/crazy_functional.py b/crazy_functional.py index d3b2953..354c290 100644 --- a/crazy_functional.py +++ b/crazy_functional.py @@ -432,18 +432,18 @@ def get_crazy_functions(): except: print('Load function plugin failed') - # try: - # from crazy_functions.虚空终端 import 终端 - # function_plugins.update({ - # "超级终端": { - # "Color": "stop", - # "AsButton": False, - # # "AdvancedArgs": True, - # # "ArgsReminder": "", - # "Function": HotReload(终端) - # } - # }) - # except: - # print('Load function plugin failed') + try: + from crazy_functions.虚空终端CodeInterpreter import 虚空终端CodeInterpreter + function_plugins.update({ + "虚空终端CodeInterpreter": { + "Color": "stop", + "AsButton": True, + # "AdvancedArgs": True, + # "ArgsReminder": "", + "Function": HotReload(虚空终端CodeInterpreter) + } + }) + except: + print('Load function plugin failed') return function_plugins diff --git a/crazy_functions/crazy_functions_test.py b/crazy_functions/crazy_functions_test.py index dd21daf..a141149 100644 --- a/crazy_functions/crazy_functions_test.py +++ b/crazy_functions/crazy_functions_test.py @@ -228,6 +228,14 @@ def test_chatglm_finetune(): for cookies, cb, hist, msg in (启动微调)(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, web_port): cli_printer.print(cb) +def test_虚空终端CodeInterpreter(): + from crazy_functions.虚空终端CodeInterpreter import 虚空终端CodeInterpreter + txt = 'Convert this dataset to excel.' + plugin_kwargs = {"recently_uploaded_files":"build/assets/iris.csv"} + + for cookies, cb, hist, msg in (虚空终端CodeInterpreter)(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, web_port): + cli_printer.print(cb) + if __name__ == "__main__": # test_解析一个Python项目() @@ -243,7 +251,8 @@ if __name__ == "__main__": # test_数学动画生成manim() # test_Langchain知识库() # test_Langchain知识库读取() - test_Latex() + # test_Latex() # test_chatglm_finetune() + test_虚空终端CodeInterpreter() input("程序完成,回车退出。") print("退出。") \ No newline at end of file diff --git a/crazy_functions/虚空终端.py b/crazy_functions/虚空终端.py index fe71a46..36667e9 100644 --- a/crazy_functions/虚空终端.py +++ b/crazy_functions/虚空终端.py @@ -1,87 +1,70 @@ from toolbox import CatchException, update_ui, gen_time_str from .crazy_utils import request_gpt_model_in_new_thread_with_ui_alive from .crazy_utils import input_clipping +import copy, json - -prompt = """ -I have to achieve some functionalities by calling one of the functions below. -Your job is to find the correct funtion to use to satisfy my requirement, -and then write python code to call this function with correct parameters. - -These are functions you are allowed to choose from: -1. - 功能描述: 总结音视频内容 - 调用函数: ConcludeAudioContent(txt, llm_kwargs) - 参数说明: - txt: 音频文件的路径 - llm_kwargs: 模型参数, 永远给定None -2. - 功能描述: 将每次对话记录写入Markdown格式的文件中 - 调用函数: WriteMarkdown() -3. - 功能描述: 将指定目录下的PDF文件从英文翻译成中文 - 调用函数: BatchTranslatePDFDocuments_MultiThreaded(txt, llm_kwargs) - 参数说明: - txt: PDF文件所在的路径 - llm_kwargs: 模型参数, 永远给定None -4. - 功能描述: 根据文本使用GPT模型生成相应的图像 - 调用函数: ImageGeneration(txt, llm_kwargs) - 参数说明: - txt: 图像生成所用到的提示文本 - llm_kwargs: 模型参数, 永远给定None -5. - 功能描述: 对输入的word文档进行摘要生成 - 调用函数: SummarizingWordDocuments(input_path, output_path) - 参数说明: - input_path: 待处理的word文档路径 - output_path: 摘要生成后的文档路径 - - -You should always anwser with following format: ----------------- -Code: -``` -class AutoAcademic(object): - def __init__(self): - self.selected_function = "FILL_CORRECT_FUNCTION_HERE" # e.g., "GenerateImage" - self.txt = "FILL_MAIN_PARAMETER_HERE" # e.g., "荷叶上的蜻蜓" - self.llm_kwargs = None -``` -Explanation: -只有GenerateImage和生成图像相关, 因此选择GenerateImage函数。 ----------------- - -Now, this is my requirement: - -""" def get_fn_lib(): return { - "BatchTranslatePDFDocuments_MultiThreaded": ("crazy_functions.批量翻译PDF文档_多线程", "批量翻译PDF文档"), - "SummarizingWordDocuments": ("crazy_functions.总结word文档", "总结word文档"), - "ImageGeneration": ("crazy_functions.图片生成", "图片生成"), - "TranslateMarkdownFromEnglishToChinese": ("crazy_functions.批量Markdown翻译", "Markdown中译英"), - "SummaryAudioVideo": ("crazy_functions.总结音视频", "总结音视频"), + "BatchTranslatePDFDocuments_MultiThreaded": { + "module": "crazy_functions.批量翻译PDF文档_多线程", + "function": "批量翻译PDF文档", + "description": "Translate PDF Documents", + "arg_1_description": "A path containing pdf files.", + }, + "SummarizingWordDocuments": { + "module": "crazy_functions.总结word文档", + "function": "总结word文档", + "description": "Summarize Word Documents", + "arg_1_description": "A path containing Word files.", + }, + "ImageGeneration": { + "module": "crazy_functions.图片生成", + "function": "图片生成", + "description": "Generate a image that satisfies some description.", + "arg_1_description": "Descriptions about the image to be generated.", + }, + "TranslateMarkdownFromEnglishToChinese": { + "module": "crazy_functions.批量Markdown翻译", + "function": "Markdown中译英", + "description": "Translate Markdown Documents from English to Chinese.", + "arg_1_description": "A path containing Markdown files.", + }, + "SummaryAudioVideo": { + "module": "crazy_functions.总结音视频", + "function": "总结音视频", + "description": "Get text from a piece of audio and summarize this audio.", + "arg_1_description": "A path containing audio files.", + }, } +functions = [ + { + "name": k, + "description": v['description'], + "parameters": { + "type": "object", + "properties": { + "plugin_arg_1": { + "type": "string", + "description": v['arg_1_description'], + }, + }, + "required": ["plugin_arg_1"], + }, + } for k, v in get_fn_lib().items() +] + def inspect_dependency(chatbot, history): return True def eval_code(code, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, web_port): - import subprocess, sys, os, shutil, importlib - - with open('gpt_log/void_terminal_runtime.py', 'w', encoding='utf8') as f: - f.write(code) - + import importlib try: - AutoAcademic = getattr(importlib.import_module('gpt_log.void_terminal_runtime', 'AutoAcademic'), 'AutoAcademic') - # importlib.reload(AutoAcademic) - auto_dict = AutoAcademic() - selected_function = auto_dict.selected_function - txt = auto_dict.txt - fp, fn = get_fn_lib()[selected_function] + tmp = get_fn_lib()[code['name']] + fp, fn = tmp['module'], tmp['function'] fn_plugin = getattr(importlib.import_module(fp, fn), fn) - yield from fn_plugin(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, web_port) + arg = json.loads(code['arguments'])['plugin_arg_1'] + yield from fn_plugin(arg, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, web_port) except: from toolbox import trimmed_format_exc chatbot.append(["执行错误", f"\n```\n{trimmed_format_exc()}\n```\n"]) @@ -110,22 +93,27 @@ def 终端(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, web_ history = [] # 基本信息:功能、贡献者 - chatbot.append(["函数插件功能?", "根据自然语言执行插件命令, 作者: binary-husky, 插件初始化中 ..."]) + chatbot.append(["虚空终端插件的功能?", "根据自然语言的描述, 执行任意插件的命令."]) yield from update_ui(chatbot=chatbot, history=history) # 刷新界面 - - # # 尝试导入依赖, 如果缺少依赖, 则给出安装建议 - # dep_ok = yield from inspect_dependency(chatbot=chatbot, history=history) # 刷新界面 - # if not dep_ok: return # 输入 - i_say = prompt + txt + i_say = txt # 开始 + llm_kwargs_function_call = copy.deepcopy(llm_kwargs) + llm_kwargs_function_call['llm_model'] = 'gpt-call-fn' # 修改调用函数 gpt_say = yield from request_gpt_model_in_new_thread_with_ui_alive( inputs=i_say, inputs_show_user=txt, - llm_kwargs=llm_kwargs, chatbot=chatbot, history=[], - sys_prompt="" + llm_kwargs=llm_kwargs_function_call, chatbot=chatbot, history=[], + sys_prompt=functions ) # 将代码转为动画 - code = get_code_block(gpt_say) - yield from eval_code(code, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, web_port) + res = json.loads(gpt_say)['choices'][0] + if res['finish_reason'] == 'function_call': + code = json.loads(gpt_say)['choices'][0] + yield from eval_code(code['message']['function_call'], llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, web_port) + else: + chatbot.append(["无法调用相关功能", res]) + yield from update_ui(chatbot=chatbot, history=history) # 刷新界面 + + diff --git a/crazy_functions/虚空终端CodeInterpreter.py b/crazy_functions/虚空终端CodeInterpreter.py new file mode 100644 index 0000000..aefe430 --- /dev/null +++ b/crazy_functions/虚空终端CodeInterpreter.py @@ -0,0 +1,185 @@ +from toolbox import CatchException, update_ui, gen_time_str, trimmed_format_exc, promote_file_to_downloadzone +from .crazy_utils import request_gpt_model_in_new_thread_with_ui_alive +from .crazy_utils import input_clipping, try_install_deps +import os + +def inspect_dependency(chatbot, history): + yield from update_ui(chatbot=chatbot, history=history) # 刷新界面 + return True + +def get_code_block(reply): + import re + pattern = r"```([\s\S]*?)```" # regex pattern to match code blocks + matches = re.findall(pattern, reply) # find all code blocks in text + if len(matches) == 1: + return matches[0].strip('python') # code block + for match in matches: + if 'class TerminalFunction' in match: + return match.strip('python') # code block + raise RuntimeError("GPT is not generating proper code.") + +def gpt_interact_multi_step(txt, file_type, llm_kwargs, chatbot, history): + # 输入 + prompt_compose = [ + f'Your job:\n' + f'1. write a single Python function, which takes a path of a `{file_type}` file as the only argument and returns a `string` containing the result of analysis or the path of generated files. \n', + f"2. You should write this function to perform following task: " + txt + "\n", + f"3. Wrap the output python function with markdown codeblock." + ] + i_say = "".join(prompt_compose) + demo = [] + + # 第一步 + gpt_say = yield from request_gpt_model_in_new_thread_with_ui_alive( + inputs=i_say, inputs_show_user=i_say, + llm_kwargs=llm_kwargs, chatbot=chatbot, history=demo, + sys_prompt= r"You are a programmer." + ) + history.extend([i_say, gpt_say]) + yield from update_ui(chatbot=chatbot, history=history) # 刷新界面 # 界面更新 + + # 第二步 + prompt_compose = [ + "If previous stage is successful, rewrite the function you have just written to satisfy following templete: ", +""" +```python +import ... # Put dependencies here, e.g. import numpy as np + +class TerminalFunction(object): # Do not change the name of the class, The name of the class must be `TerminalFunction` + + def run(self, path): # The name of the function must be `run`, it takes only a positional argument. + # rewrite the function you have just written here + ... + return generated_string_or_path +``` +""" + ] + i_say = "".join(prompt_compose); inputs_show_user = "If previous stage is successful, rewrite the function you have just written to satisfy executable templete. " + gpt_say = yield from request_gpt_model_in_new_thread_with_ui_alive( + inputs=i_say, inputs_show_user=inputs_show_user, + llm_kwargs=llm_kwargs, chatbot=chatbot, history=history, + sys_prompt= r"You are a programmer." + ) + code_to_return = gpt_say + history.extend([i_say, gpt_say]) + yield from update_ui(chatbot=chatbot, history=history) # 刷新界面 # 界面更新 + + + + # # 第三步 + # i_say = "Please list to packages to install to run the code above. Then show me how to use `try_install_deps` function to install them." + # i_say += 'For instance. `try_install_deps(["opencv-python", "scipy", "numpy"])`' + # installation_advance = yield from request_gpt_model_in_new_thread_with_ui_alive( + # inputs=i_say, inputs_show_user=inputs_show_user, + # llm_kwargs=llm_kwargs, chatbot=chatbot, history=history, + # sys_prompt= r"You are a programmer." + # ) + # # 第三步 + i_say = "Show me how to use `pip` to install packages to run the code above. " + i_say += 'For instance. `pip install -r opencv-python scipy numpy`' + installation_advance = yield from request_gpt_model_in_new_thread_with_ui_alive( + inputs=i_say, inputs_show_user=inputs_show_user, + llm_kwargs=llm_kwargs, chatbot=chatbot, history=history, + sys_prompt= r"You are a programmer." + ) + + return code_to_return, installation_advance, txt, file_type, llm_kwargs, chatbot, history + +def make_module(code): + import subprocess, sys, os, shutil + + module_file = 'gpt_fn_' + gen_time_str().replace('-','_') + with open(f'gpt_log/{module_file}.py', 'w', encoding='utf8') as f: + f.write(code) + + def get_class_name(class_string): + import re + # Use regex to extract the class name + class_name = re.search(r'class (\w+)\(', class_string).group(1) + return class_name + + class_name = get_class_name(code) + return f"gpt_log.{module_file}->{class_name}" + +def init_module_instance(module): + import importlib + module_, class_ = module.split('->') + init_f = getattr(importlib.import_module(module_), class_) + return init_f() + + + + + +@CatchException +def 虚空终端CodeInterpreter(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, web_port): + """ + txt 输入栏用户输入的文本,例如需要翻译的一段话,再例如一个包含了待处理文件的路径 + llm_kwargs gpt模型参数,如温度和top_p等,一般原样传递下去就行 + plugin_kwargs 插件模型的参数,暂时没有用武之地 + chatbot 聊天显示框的句柄,用于显示给用户 + history 聊天历史,前情提要 + system_prompt 给gpt的静默提醒 + web_port 当前软件运行的端口号 + """ + # 清空历史,以免输入溢出 + history = [] + + # 基本信息:功能、贡献者 + chatbot.append([ + "函数插件功能?", + "CodeInterpreter开源版, 此插件处于开发阶段, 建议暂时不要使用, 作者: binary-husky, 插件初始化中 ..." + ]) + yield from update_ui(chatbot=chatbot, history=history) # 刷新界面 + + # 尝试导入依赖, 如果缺少依赖, 则给出安装建议 + dep_ok = yield from inspect_dependency(chatbot=chatbot, history=history) # 刷新界面 + if not dep_ok: return + + # 读取文件 + if ("recently_uploaded_files" in plugin_kwargs) and (plugin_kwargs["recently_uploaded_files"] == ""): plugin_kwargs.pop("recently_uploaded_files") + recently_uploaded_files = plugin_kwargs.get("recently_uploaded_files", None) + file_path = recently_uploaded_files[-1] + file_type = file_path.split('.')[-1] + + # 粗心检查 + if 'private_upload' in txt: + chatbot.append([ + "...", + f"请在输入框内填写需求,然后再次点击该插件(文件路径 {file_path} 已经被记忆)" + ]) + yield from update_ui(chatbot=chatbot, history=history) # 刷新界面 + return + + # 开始干正事 + for j in range(5): # 最多重试5次 + try: + code, installation_advance, txt, file_type, llm_kwargs, chatbot, history = \ + yield from gpt_interact_multi_step(txt, file_type, llm_kwargs, chatbot, history) + code = get_code_block(code) + res = make_module(code) + instance = init_module_instance(res) + break + except Exception as e: + chatbot.append([f"第{j}次代码生成尝试,失败了", f"\n```\n{trimmed_format_exc()}\n```\n"]) + yield from update_ui(chatbot=chatbot, history=history) # 刷新界面 + + # 代码生成结束, 开始执行 + try: + res = instance.run(file_path) + except Exception as e: + chatbot.append(["执行失败了", f"\n```\n{trimmed_format_exc()}\n```\n"]) + chatbot.append(["如果是缺乏依赖,请参考以下建议", installation_advance]) + yield from update_ui(chatbot=chatbot, history=history) # 刷新界面 + return + + # 顺利完成,收尾 + res = str(res) + if os.path.exists(res): + chatbot.append(["执行成功了,结果是一个有效文件", "结果:" + res]) + promote_file_to_downloadzone(res, chatbot=chatbot) + yield from update_ui(chatbot=chatbot, history=history) # 刷新界面 # 界面更新 + else: + chatbot.append(["执行成功了", "结果:" + res]) + yield from update_ui(chatbot=chatbot, history=history) # 刷新界面 # 界面更新 + diff --git a/main.py b/main.py index 0f8ea07..f0f366e 100644 --- a/main.py +++ b/main.py @@ -160,7 +160,7 @@ def main(): click_handle = functional[k]["Button"].click(fn=ArgsGeneralWrapper(predict), inputs=[*input_combo, gr.State(True), gr.State(k)], outputs=output_combo) cancel_handles.append(click_handle) # 文件上传区,接收文件后与chatbot的互动 - file_upload.upload(on_file_uploaded, [file_upload, chatbot, txt, txt2, checkboxes], [chatbot, txt, txt2]) + file_upload.upload(on_file_uploaded, [cookies, file_upload, chatbot, txt, txt2, checkboxes], [cookies, chatbot, txt, txt2]) # 函数插件-固定按钮区 for k in crazy_fns: if not crazy_fns[k].get("AsButton", True): continue diff --git a/toolbox.py b/toolbox.py index 89a46fb..d17faee 100644 --- a/toolbox.py +++ b/toolbox.py @@ -60,6 +60,9 @@ def ArgsGeneralWrapper(f): plugin_kwargs = { "advanced_arg": plugin_advanced_arg, } + if "recently_uploaded_files" in cookies: + plugin_kwargs.update({"recently_uploaded_files": cookies["recently_uploaded_files"]}) + chatbot_with_cookie = ChatBotWithCookies(cookies) chatbot_with_cookie.write_list(chatbot) if cookies.get('lock_plugin', None) is None: @@ -477,12 +480,12 @@ def promote_file_to_downloadzone(file, rename_file=None, chatbot=None): else: current = [] chatbot._cookies.update({'file_to_promote': [new_path] + current}) -def on_file_uploaded(files, chatbot, txt, txt2, checkboxes): +def on_file_uploaded(cookies, files, chatbot, txt, txt2, checkboxes): """ 当文件被上传时的回调函数 """ if len(files) == 0: - return chatbot, txt + return cookies, chatbot, txt import shutil import os import time @@ -512,7 +515,8 @@ def on_file_uploaded(files, chatbot, txt, txt2, checkboxes): f'[Local Message] 收到以下文件: \n\n{moved_files_str}' + f'\n\n调用路径参数已自动修正到: \n\n{txt}' + f'\n\n现在您点击任意“红颜色”标识的函数插件时,以上文件将被作为输入参数'+err_msg]) - return chatbot, txt, txt2 + cookies.update({"recently_uploaded_files": moved_files}) + return cookies, chatbot, txt, txt2 def on_report_generated(cookies, files, chatbot):