From 08e3e8ee4ddde403f8e6c1d6e99ff8d7a1db9716 Mon Sep 17 00:00:00 2001 From: "yifeng.wang" <3038880699@qq.com> Date: Sun, 9 Mar 2025 22:14:00 +0800 Subject: [PATCH] add env --- owl/app.py | 272 ++++++++++++++++++++++++++++++++---------- owl/script_adapter.py | 109 ++++++++++++----- 2 files changed, 291 insertions(+), 90 deletions(-) diff --git a/owl/app.py b/owl/app.py index 3ca4cc1..a8b89d1 100644 --- a/owl/app.py +++ b/owl/app.py @@ -9,10 +9,16 @@ import queue import re from pathlib import Path import json +import signal +import dotenv # 设置日志队列 log_queue = queue.Queue() +# 当前运行的进程 +current_process = None +process_lock = threading.Lock() + # 脚本选项 SCRIPTS = { "Qwen Mini (中文)": "run_qwen_mini_zh.py", @@ -33,12 +39,98 @@ SCRIPT_DESCRIPTIONS = { "GAIA Roleplaying": "GAIA基准测试实现,用于评估模型能力" } +# 环境变量分组 +ENV_GROUPS = { + "模型API": [ + {"name": "OPENAI_API_KEY", "label": "OpenAI API密钥", "type": "password", "required": True}, + {"name": "OPENAI_API_BASE_URL", "label": "OpenAI API基础URL", "type": "text", "required": False}, + {"name": "QWEN_API_KEY", "label": "阿里云Qwen API密钥", "type": "password", "required": False}, + {"name": "DEEPSEEK_API_KEY", "label": "DeepSeek API密钥", "type": "password", "required": False}, + ], + "搜索工具": [ + {"name": "GOOGLE_API_KEY", "label": "Google API密钥", "type": "password", "required": False}, + {"name": "SEARCH_ENGINE_ID", "label": "搜索引擎ID", "type": "text", "required": False}, + ], + "其他工具": [ + {"name": "HF_TOKEN", "label": "Hugging Face令牌", "type": "password", "required": False}, + {"name": "CHUNKR_API_KEY", "label": "Chunkr API密钥", "type": "password", "required": False}, + {"name": "FIRECRAWL_API_KEY", "label": "Firecrawl API密钥", "type": "password", "required": False}, + ] +} + def get_script_info(script_name): """获取脚本的详细信息""" return SCRIPT_DESCRIPTIONS.get(script_name, "无描述信息") +def load_env_vars(): + """加载环境变量""" + env_vars = {} + # 尝试从.env文件加载 + dotenv.load_dotenv() + + # 获取所有环境变量 + for group in ENV_GROUPS.values(): + for var in group: + env_vars[var["name"]] = os.environ.get(var["name"], "") + + return env_vars + +def save_env_vars(env_vars): + """保存环境变量到.env文件""" + # 读取现有的.env文件内容 + env_path = Path(".env") + existing_content = {} + + if env_path.exists(): + with open(env_path, "r", encoding="utf-8") as f: + for line in f: + line = line.strip() + if line and not line.startswith("#") and "=" in line: + key, value = line.split("=", 1) + existing_content[key.strip()] = value.strip() + + # 更新环境变量 + for key, value in env_vars.items(): + if value: # 只保存非空值 + existing_content[key] = value + # 同时更新当前进程的环境变量 + os.environ[key] = value + + # 写入.env文件 + with open(env_path, "w", encoding="utf-8") as f: + for key, value in existing_content.items(): + f.write(f"{key}={value}\n") + + return "环境变量已保存" + +def terminate_process(): + """终止当前运行的进程""" + global current_process + + with process_lock: + if current_process is not None and current_process.poll() is None: + # 在Windows上使用CTRL_BREAK_EVENT,在Unix上使用SIGTERM + if os.name == 'nt': + current_process.send_signal(signal.CTRL_BREAK_EVENT) + else: + current_process.terminate() + + # 等待进程终止 + try: + current_process.wait(timeout=5) + except subprocess.TimeoutExpired: + # 如果进程没有在5秒内终止,强制终止 + current_process.kill() + + log_queue.put("进程已终止\n") + return "✅ 进程已终止" + else: + return "❌ 没有正在运行的进程" + def run_script(script_dropdown, question, progress=gr.Progress()): """运行选定的脚本并返回输出""" + global current_process + script_name = SCRIPTS[script_dropdown] if not question.strip(): @@ -64,19 +156,20 @@ def run_script(script_dropdown, question, progress=gr.Progress()): env["OWL_QUESTION"] = question # 启动进程 - process = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - bufsize=1, - env=env - ) + with process_lock: + current_process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1, + env=env + ) # 创建线程来读取输出 def read_output(): with open(log_file, "w", encoding="utf-8") as f: - for line in iter(process.stdout.readline, ""): + for line in iter(current_process.stdout.readline, ""): if line: # 写入日志文件 f.write(line) @@ -95,11 +188,13 @@ def run_script(script_dropdown, question, progress=gr.Progress()): start_time = time.time() timeout = 1800 # 30分钟超时 - while process.poll() is None: + while current_process.poll() is None: # 检查是否超时 if time.time() - start_time > timeout: - process.terminate() - log_queue.put("执行超时,已终止进程\n") + with process_lock: + if current_process.poll() is None: + current_process.terminate() + log_queue.put("执行超时,已终止进程\n") break # 从队列获取日志 @@ -115,7 +210,7 @@ def run_script(script_dropdown, question, progress=gr.Progress()): time.sleep(0.1) # 每秒更新一次日志显示 - yield status_message(process), extract_answer(logs), "".join(logs), str(log_file), None + yield status_message(current_process), extract_answer(logs), "".join(logs), str(log_file), None # 获取剩余日志 while not log_queue.empty(): @@ -125,7 +220,7 @@ def run_script(script_dropdown, question, progress=gr.Progress()): chat_history = extract_chat_history(logs) # 返回最终状态和日志 - return status_message(process), extract_answer(logs), "".join(logs), str(log_file), chat_history + return status_message(current_process), extract_answer(logs), "".join(logs), str(log_file), chat_history def status_message(process): """根据进程状态返回状态消息""" @@ -203,6 +298,9 @@ def modify_script(script_name, question): def create_ui(): """创建Gradio界面""" + # 加载环境变量 + env_vars = load_env_vars() + with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as app: gr.Markdown( """ @@ -212,47 +310,101 @@ def create_ui(): """ ) - with gr.Row(): - with gr.Column(scale=1): - script_dropdown = gr.Dropdown( - choices=list(SCRIPTS.keys()), - value=list(SCRIPTS.keys())[0], - label="选择模型" - ) + with gr.Tabs() as tabs: + with gr.TabItem("运行模型"): + with gr.Row(): + with gr.Column(scale=1): + script_dropdown = gr.Dropdown( + choices=list(SCRIPTS.keys()), + value=list(SCRIPTS.keys())[0], + label="选择模型" + ) + + script_info = gr.Textbox( + value=get_script_info(list(SCRIPTS.keys())[0]), + label="模型描述", + interactive=False + ) + + script_dropdown.change( + fn=lambda x: get_script_info(x), + inputs=script_dropdown, + outputs=script_info + ) + + question_input = gr.Textbox( + lines=5, + placeholder="请输入您的问题...", + label="问题" + ) + + gr.Markdown( + """ + > **注意**: 您输入的问题将替换脚本中的默认问题。系统会自动处理问题的替换,确保您的问题被正确使用。 + """ + ) + + with gr.Row(): + run_button = gr.Button("运行", variant="primary") + stop_button = gr.Button("终止", variant="stop") + + with gr.Column(scale=2): + with gr.Tabs(): + with gr.TabItem("结果"): + status_output = gr.Textbox(label="状态") + answer_output = gr.Textbox(label="回答", lines=10) + log_file_output = gr.Textbox(label="日志文件路径") + + with gr.TabItem("运行日志"): + log_output = gr.Textbox(label="完整日志", lines=25) + + with gr.TabItem("聊天历史"): + chat_output = gr.Chatbot(label="对话历史") - script_info = gr.Textbox( - value=get_script_info(list(SCRIPTS.keys())[0]), - label="模型描述", - interactive=False - ) + # 示例问题 + examples = [ + ["Qwen Mini (中文)", "打开小红书上浏览推荐栏目下的前三个笔记内容,不要登陆,之后给我一个总结报告"], + ["Mini", "What was the volume in m^3 of the fish bag that was calculated in the University of Leicester paper `Can Hiccup Supply Enough Fish to Maintain a Dragon's Diet?`"], + ["默认", "What is the current weather in New York?"] + ] - script_dropdown.change( - fn=lambda x: get_script_info(x), - inputs=script_dropdown, - outputs=script_info + gr.Examples( + examples=examples, + inputs=[script_dropdown, question_input] ) - - question_input = gr.Textbox( - lines=5, - placeholder="请输入您的问题...", - label="问题" - ) - - run_button = gr.Button("运行", variant="primary") - with gr.Column(scale=2): - with gr.Tabs(): - with gr.TabItem("结果"): - status_output = gr.Textbox(label="状态") - answer_output = gr.Textbox(label="回答", lines=10) - log_file_output = gr.Textbox(label="日志文件路径") - - with gr.TabItem("运行日志"): - log_output = gr.Textbox(label="完整日志", lines=25) - - with gr.TabItem("聊天历史"): - chat_output = gr.Chatbot(label="对话历史") + with gr.TabItem("环境变量配置"): + env_inputs = {} + save_status = gr.Textbox(label="保存状态", interactive=False) + + for group_name, vars in ENV_GROUPS.items(): + with gr.Accordion(group_name, open=True): + for var in vars: + if var["type"] == "password": + env_inputs[var["name"]] = gr.Textbox( + value=env_vars.get(var["name"], ""), + label=var["label"] + (" (必填)" if var.get("required", False) else ""), + placeholder=f"请输入{var['label']}", + type="password" + ) + else: + env_inputs[var["name"]] = gr.Textbox( + value=env_vars.get(var["name"], ""), + label=var["label"] + (" (必填)" if var.get("required", False) else ""), + placeholder=f"请输入{var['label']}" + ) + + save_button = gr.Button("保存环境变量", variant="primary") + + # 保存环境变量 + save_inputs = [env_inputs[var_name] for group in ENV_GROUPS.values() for var in group for var_name in [var["name"]]] + save_button.click( + fn=lambda *values: save_env_vars(dict(zip([var["name"] for group in ENV_GROUPS.values() for var in group], values))), + inputs=save_inputs, + outputs=save_status + ) + # 运行脚本 run_button.click( fn=run_script, inputs=[ @@ -263,16 +415,11 @@ def create_ui(): show_progress=True ) - # 示例问题 - examples = [ - ["Qwen Mini (中文)", "打开小红书上浏览推荐栏目下的前三个笔记内容,不要登陆,之后给我一个总结报告"], - ["Mini", "What was the volume in m^3 of the fish bag that was calculated in the University of Leicester paper `Can Hiccup Supply Enough Fish to Maintain a Dragon's Diet?`"], - ["默认", "What is the current weather in New York?"] - ] - - gr.Examples( - examples=examples, - inputs=[script_dropdown, question_input] + # 终止运行 + stop_button.click( + fn=terminate_process, + inputs=[], + outputs=[status_output] ) # 添加页脚 @@ -282,15 +429,18 @@ def create_ui(): - 选择一个模型并输入您的问题 - 点击"运行"按钮开始执行 + - 如需终止运行,点击"终止"按钮 - 在"结果"标签页查看执行状态和回答 - 在"运行日志"标签页查看完整日志 - 在"聊天历史"标签页查看对话历史(如果有) + - 在"环境变量配置"标签页配置API密钥和其他环境变量 ### ⚠️ 注意事项 - - 运行某些模型可能需要API密钥,请确保在`.env`文件中设置了相应的环境变量 + - 运行某些模型可能需要API密钥,请确保在"环境变量配置"标签页中设置了相应的环境变量 - 某些脚本可能需要较长时间运行,请耐心等待 - 如果运行超过30分钟,进程将自动终止 + - 您输入的问题将替换脚本中的默认问题,确保问题与所选模型兼容 """ ) diff --git a/owl/script_adapter.py b/owl/script_adapter.py index 3356fb0..1395155 100644 --- a/owl/script_adapter.py +++ b/owl/script_adapter.py @@ -26,45 +26,96 @@ def run_script_with_env_question(script_name): print(f"错误: 脚本 {script_path} 不存在") sys.exit(1) - # 加载脚本模块 - module_name = script_path.stem + # 读取脚本内容 + with open(script_path, "r", encoding="utf-8") as f: + content = f.read() + + # 检查脚本是否有main函数 + has_main = re.search(r'def\s+main\s*\(\s*\)\s*:', content) is not None + + # 尝试查找并替换question变量 + # 匹配多种可能的question定义模式 + patterns = [ + r'question\s*=\s*["\'].*?["\']', # 简单字符串赋值 + r'question\s*=\s*\(\s*["\'].*?["\']\s*\)', # 带括号的字符串赋值 + r'question\s*=\s*f["\'].*?["\']', # f-string赋值 + r'question\s*=\s*r["\'].*?["\']', # 原始字符串赋值 + ] + + question_replaced = False + for pattern in patterns: + if re.search(pattern, content): + # 转义问题中的特殊字符 + escaped_question = question.replace("\\", "\\\\").replace("\"", "\\\"").replace("'", "\\'") + # 替换问题 + modified_content = re.sub( + pattern, + f'question = "{escaped_question}"', + content + ) + question_replaced = True + break + + if not question_replaced: + # 如果没有找到question变量,尝试在main函数前插入 + if has_main: + # 在main函数前插入question变量 + main_match = re.search(r'def\s+main\s*\(\s*\)\s*:', content) + if main_match: + insert_pos = main_match.start() + # 转义问题中的特殊字符 + escaped_question = question.replace("\\", "\\\\").replace("\"", "\\\"").replace("'", "\\'") + modified_content = content[:insert_pos] + f'\n# 用户输入的问题\nquestion = "{escaped_question}"\n\n' + content[insert_pos:] + question_replaced = True + + if not question_replaced: + # 如果仍然无法替换,尝试在文件末尾添加代码来使用用户的问题 + modified_content = content + f'\n\n# 用户输入的问题\nquestion = "{question}"\n\n' + modified_content += ''' +# 如果脚本中有construct_society函数,使用用户问题运行 +if "construct_society" in globals(): + try: + society = construct_society(question) + from utils import run_society + answer, chat_history, token_count = run_society(society) + print(f"Answer: {answer}") + except Exception as e: + print(f"运行时出错: {e}") + import traceback + traceback.print_exc() +''' + + # 执行修改后的脚本 try: # 将脚本目录添加到sys.path script_dir = script_path.parent if str(script_dir) not in sys.path: sys.path.insert(0, str(script_dir)) - # 读取脚本内容 - with open(script_path, "r", encoding="utf-8") as f: - content = f.read() - - # 检查脚本是否有main函数 - has_main = re.search(r'def\s+main\s*\(\s*\)\s*:', content) is not None - if has_main: # 如果有main函数,加载模块并调用main - module = load_module_from_path(module_name, script_path) + # 创建临时文件 + temp_script_path = script_path.with_name(f"temp_{script_path.name}") + with open(temp_script_path, "w", encoding="utf-8") as f: + f.write(modified_content) - # 修改模块中的question变量 - if hasattr(module, "question"): - setattr(module, "question", question) - - # 调用main函数 - if hasattr(module, "main"): - module.main() - else: - print(f"错误: 脚本 {script_path} 中没有main函数") - sys.exit(1) + try: + # 加载临时模块 + module_name = f"temp_{script_path.stem}" + module = load_module_from_path(module_name, temp_script_path) + + # 调用main函数 + if hasattr(module, "main"): + module.main() + else: + print(f"错误: 脚本 {script_path} 中没有main函数") + sys.exit(1) + finally: + # 删除临时文件 + if temp_script_path.exists(): + temp_script_path.unlink() else: - # 如果没有main函数,直接执行脚本内容 - # 替换question变量 - modified_content = re.sub( - r'question\s*=\s*["\'].*?["\']', - f'question = "{question}"', - content - ) - - # 执行修改后的脚本 + # 如果没有main函数,直接执行修改后的脚本 exec(modified_content, {"__file__": str(script_path)}) except Exception as e: