From c783c2c8bb59495995ba5cf7698961ed8554067d Mon Sep 17 00:00:00 2001 From: Wendong Date: Sat, 15 Mar 2025 10:02:42 +0800 Subject: [PATCH] update wendong --- owl/.env_template | 18 +- owl/examples/run.py | 9 +- owl/examples/run_deepseek_zh.py | 14 +- owl/examples/run_mini.py | 10 +- owl/examples/run_ollama.py | 10 +- owl/examples/run_openai_compatiable_model.py | 10 +- owl/examples/run_qwen_mini_zh.py | 8 +- owl/examples/run_qwen_zh.py | 9 +- owl/examples/run_terminal.py | 16 +- owl/examples/run_terminal_zh.py | 20 +- owl/nextwebapp.py | 395 +++--- owl/utils/enhanced_role_playing.py | 8 + owl/webapp_zh.py | 1328 +++++++++++------- pyproject.toml | 2 +- requirements.txt | 2 +- uv.lock | 8 +- 16 files changed, 1110 insertions(+), 757 deletions(-) diff --git a/owl/.env_template b/owl/.env_template index cbf77f4..2858377 100644 --- a/owl/.env_template +++ b/owl/.env_template @@ -1,6 +1,9 @@ -# MODEL & API (See https://docs.camel-ai.org/key_modules/models.html#) +#=========================================== +# MODEL & API +# (See https://docs.camel-ai.org/key_modules/models.html#) +#=========================================== -# OPENAI API +# OPENAI API (https://platform.openai.com/api-keys) # OPENAI_API_KEY= "" # OPENAI_API_BASE_URL="" @@ -15,15 +18,12 @@ #=========================================== # Google Search API (https://developers.google.com/custom-search/v1/overview) -GOOGLE_API_KEY="" -SEARCH_ENGINE_ID="" - -# Hugging Face API (https://huggingface.co/join) -HF_TOKEN="" +# GOOGLE_API_KEY="" +# SEARCH_ENGINE_ID="" # Chunkr API (https://chunkr.ai/) -CHUNKR_API_KEY="" +# CHUNKR_API_KEY="" # Firecrawl API (https://www.firecrawl.dev/) -FIRECRAWL_API_KEY="" +#FIRECRAWL_API_KEY="" #FIRECRAWL_API_URL="https://api.firecrawl.dev" \ No newline at end of file diff --git a/owl/examples/run.py b/owl/examples/run.py index 9f1cb32..e3f8960 100644 --- a/owl/examples/run.py +++ b/owl/examples/run.py @@ -25,22 +25,23 @@ from camel.toolkits import ( ) from camel.types import ModelPlatformType, ModelType from camel.logger import set_log_level +from camel.societies import RolePlaying -from owl.utils import OwlRolePlaying, run_society, DocumentProcessingToolkit +from owl.utils import run_society, DocumentProcessingToolkit load_dotenv() set_log_level(level="DEBUG") -def construct_society(question: str) -> OwlRolePlaying: +def construct_society(question: str) -> RolePlaying: r"""Construct a society of agents based on the given question. Args: question (str): The task or question to be addressed by the society. Returns: - OwlRolePlaying: A configured society of agents ready to address the question. + RolePlaying: A configured society of agents ready to address the question. """ # Create models for different components @@ -112,7 +113,7 @@ def construct_society(question: str) -> OwlRolePlaying: } # Create and return the society - society = OwlRolePlaying( + society = RolePlaying( **task_kwargs, user_role_name="user", user_agent_kwargs=user_agent_kwargs, diff --git a/owl/examples/run_deepseek_zh.py b/owl/examples/run_deepseek_zh.py index f109722..3228b6d 100644 --- a/owl/examples/run_deepseek_zh.py +++ b/owl/examples/run_deepseek_zh.py @@ -31,7 +31,9 @@ from camel.toolkits import ( from camel.types import ModelPlatformType, ModelType -from owl.utils import OwlRolePlaying, run_society, DocumentProcessingToolkit +from owl.utils import run_society + +from camel.societies import RolePlaying from camel.logger import set_log_level @@ -40,14 +42,14 @@ set_log_level(level="DEBUG") load_dotenv() -def construct_society(question: str) -> OwlRolePlaying: +def construct_society(question: str) -> RolePlaying: r"""Construct a society of agents based on the given question. Args: question (str): The task or question to be addressed by the society. Returns: - OwlRolePlaying: A configured society of agents ready to address the question. + RolePlaying: A configured society of agents ready to address the question. """ # Create models for different components @@ -84,7 +86,7 @@ def construct_society(question: str) -> OwlRolePlaying: } # Create and return the society - society = OwlRolePlaying( + society = RolePlaying( **task_kwargs, user_role_name="user", user_agent_kwargs=user_agent_kwargs, @@ -99,9 +101,7 @@ def construct_society(question: str) -> OwlRolePlaying: def main(): r"""Main function to run the OWL system with an example question.""" # Example research question - question = ( - "搜索OWL项目最近的新闻并生成一篇报告,最后保存到本地。" - ) + question = "搜索OWL项目最近的新闻并生成一篇报告,最后保存到本地。" # Construct and run the society society = construct_society(question) diff --git a/owl/examples/run_mini.py b/owl/examples/run_mini.py index 59fac6e..400c851 100644 --- a/owl/examples/run_mini.py +++ b/owl/examples/run_mini.py @@ -22,20 +22,22 @@ from camel.toolkits import ( from camel.types import ModelPlatformType, ModelType from camel.logger import set_log_level -from owl.utils import OwlRolePlaying, run_society +from owl.utils import run_society + +from camel.societies import RolePlaying load_dotenv() set_log_level(level="DEBUG") -def construct_society(question: str) -> OwlRolePlaying: +def construct_society(question: str) -> RolePlaying: r"""Construct a society of agents based on the given question. Args: question (str): The task or question to be addressed by the society. Returns: - OwlRolePlaying: A configured society of agents ready to address the + RolePlaying: A configured society of agents ready to address the question. """ @@ -86,7 +88,7 @@ def construct_society(question: str) -> OwlRolePlaying: } # Create and return the society - society = OwlRolePlaying( + society = RolePlaying( **task_kwargs, user_role_name="user", user_agent_kwargs=user_agent_kwargs, diff --git a/owl/examples/run_ollama.py b/owl/examples/run_ollama.py index 8da8a64..cc525cf 100644 --- a/owl/examples/run_ollama.py +++ b/owl/examples/run_ollama.py @@ -25,7 +25,9 @@ from camel.toolkits import ( ) from camel.types import ModelPlatformType -from owl.utils import OwlRolePlaying, run_society +from owl.utils import run_society + +from camel.societies import RolePlaying from camel.logger import set_log_level @@ -34,14 +36,14 @@ set_log_level(level="DEBUG") load_dotenv() -def construct_society(question: str) -> OwlRolePlaying: +def construct_society(question: str) -> RolePlaying: r"""Construct a society of agents based on the given question. Args: question (str): The task or question to be addressed by the society. Returns: - OwlRolePlaying: A configured society of agents ready to address the question. + RolePlaying: A configured society of agents ready to address the question. """ # Create models for different components @@ -105,7 +107,7 @@ def construct_society(question: str) -> OwlRolePlaying: } # Create and return the society - society = OwlRolePlaying( + society = RolePlaying( **task_kwargs, user_role_name="user", user_agent_kwargs=user_agent_kwargs, diff --git a/owl/examples/run_openai_compatiable_model.py b/owl/examples/run_openai_compatiable_model.py index 029059b..6c5d09a 100644 --- a/owl/examples/run_openai_compatiable_model.py +++ b/owl/examples/run_openai_compatiable_model.py @@ -25,8 +25,8 @@ from camel.toolkits import ( ) from camel.types import ModelPlatformType -from owl.utils import OwlRolePlaying, run_society - +from owl.utils import run_society +from camel.societies import RolePlaying from camel.logger import set_log_level set_log_level(level="DEBUG") @@ -34,14 +34,14 @@ set_log_level(level="DEBUG") load_dotenv() -def construct_society(question: str) -> OwlRolePlaying: +def construct_society(question: str) -> RolePlaying: r"""Construct a society of agents based on the given question. Args: question (str): The task or question to be addressed by the society. Returns: - OwlRolePlaying: A configured society of agents ready to address the question. + RolePlaying: A configured society of agents ready to address the question. """ # Create models for different components @@ -110,7 +110,7 @@ def construct_society(question: str) -> OwlRolePlaying: } # Create and return the society - society = OwlRolePlaying( + society = RolePlaying( **task_kwargs, user_role_name="user", user_agent_kwargs=user_agent_kwargs, diff --git a/owl/examples/run_qwen_mini_zh.py b/owl/examples/run_qwen_mini_zh.py index f6782ee..0ba81f1 100644 --- a/owl/examples/run_qwen_mini_zh.py +++ b/owl/examples/run_qwen_mini_zh.py @@ -22,7 +22,9 @@ from camel.models import ModelFactory from camel.toolkits import BrowserToolkit, SearchToolkit, FileWriteToolkit from camel.types import ModelPlatformType, ModelType -from owl.utils import OwlRolePlaying, run_society +from owl.utils import run_society + +from camel.societies import RolePlaying from camel.logger import set_log_level @@ -31,7 +33,7 @@ set_log_level(level="DEBUG") load_dotenv() -def construct_society(question: str) -> OwlRolePlaying: +def construct_society(question: str) -> RolePlaying: r"""Construct the society based on the question.""" user_role_name = "user" @@ -82,7 +84,7 @@ def construct_society(question: str) -> OwlRolePlaying: "with_task_specify": False, } - society = OwlRolePlaying( + society = RolePlaying( **task_kwargs, user_role_name=user_role_name, user_agent_kwargs=user_agent_kwargs, diff --git a/owl/examples/run_qwen_zh.py b/owl/examples/run_qwen_zh.py index e7c36df..1e22b14 100644 --- a/owl/examples/run_qwen_zh.py +++ b/owl/examples/run_qwen_zh.py @@ -28,8 +28,9 @@ from camel.toolkits import ( FileWriteToolkit, ) from camel.types import ModelPlatformType, ModelType +from camel.societies import RolePlaying -from owl.utils import OwlRolePlaying, run_society, DocumentProcessingToolkit +from owl.utils import run_society, DocumentProcessingToolkit from camel.logger import set_log_level @@ -38,7 +39,7 @@ set_log_level(level="DEBUG") load_dotenv() -def construct_society(question: str) -> OwlRolePlaying: +def construct_society(question: str) -> RolePlaying: """ Construct a society of agents based on the given question. @@ -46,7 +47,7 @@ def construct_society(question: str) -> OwlRolePlaying: question (str): The task or question to be addressed by the society. Returns: - OwlRolePlaying: A configured society of agents ready to address the question. + RolePlaying: A configured society of agents ready to address the question. """ # Create models for different components @@ -118,7 +119,7 @@ def construct_society(question: str) -> OwlRolePlaying: } # Create and return the society - society = OwlRolePlaying( + society = RolePlaying( **task_kwargs, user_role_name="user", user_agent_kwargs=user_agent_kwargs, diff --git a/owl/examples/run_terminal.py b/owl/examples/run_terminal.py index 576f29c..97b92d5 100644 --- a/owl/examples/run_terminal.py +++ b/owl/examples/run_terminal.py @@ -18,26 +18,28 @@ from camel.toolkits import ( SearchToolkit, BrowserToolkit, FileWriteToolkit, - TerminalToolkit + TerminalToolkit, ) from camel.types import ModelPlatformType, ModelType from camel.logger import set_log_level -from owl.utils import OwlRolePlaying, run_society +from owl.utils import run_society +from camel.societies import RolePlaying load_dotenv() set_log_level(level="DEBUG") # Get current script directory base_dir = os.path.dirname(os.path.abspath(__file__)) -def construct_society(question: str) -> OwlRolePlaying: + +def construct_society(question: str) -> RolePlaying: r"""Construct a society of agents based on the given question. Args: question (str): The task or question to be addressed by the society. Returns: - OwlRolePlaying: A configured society of agents ready to address the + RolePlaying: A configured society of agents ready to address the question. """ @@ -89,7 +91,7 @@ def construct_society(question: str) -> OwlRolePlaying: } # Create and return the society - society = OwlRolePlaying( + society = RolePlaying( **task_kwargs, user_role_name="user", user_agent_kwargs=user_agent_kwargs, @@ -113,7 +115,9 @@ def main(): answer, chat_history, token_count = run_society(society) # Output the result - print(f"\033[94mAnswer: {answer}\nChat History: {chat_history}\ntoken_count:{token_count}\033[0m") + print( + f"\033[94mAnswer: {answer}\nChat History: {chat_history}\ntoken_count:{token_count}\033[0m" + ) if __name__ == "__main__": diff --git a/owl/examples/run_terminal_zh.py b/owl/examples/run_terminal_zh.py index 946a0ed..80c9c60 100644 --- a/owl/examples/run_terminal_zh.py +++ b/owl/examples/run_terminal_zh.py @@ -18,27 +18,31 @@ from camel.toolkits import ( SearchToolkit, BrowserToolkit, FileWriteToolkit, - TerminalToolkit + TerminalToolkit, ) from camel.types import ModelPlatformType, ModelType from camel.logger import set_log_level -from owl.utils import OwlRolePlaying, run_society +from owl.utils import run_society +from camel.societies import RolePlaying +import os load_dotenv() set_log_level(level="DEBUG") -import os + + # Get current script directory base_dir = os.path.dirname(os.path.abspath(__file__)) -def construct_society(question: str) -> OwlRolePlaying: + +def construct_society(question: str) -> RolePlaying: r"""Construct a society of agents based on the given question. Args: question (str): The task or question to be addressed by the society. Returns: - OwlRolePlaying: A configured society of agents ready to address the + RolePlaying: A configured society of agents ready to address the question. """ @@ -90,7 +94,7 @@ def construct_society(question: str) -> OwlRolePlaying: } # Create and return the society - society = OwlRolePlaying( + society = RolePlaying( **task_kwargs, user_role_name="user", user_agent_kwargs=user_agent_kwargs, @@ -112,7 +116,9 @@ def main(): answer, chat_history, token_count = run_society(society) # Output the result - print(f"\033[94mAnswer: {answer}\nChat History: {chat_history}\ntoken_count:{token_count}\033[0m") + print( + f"\033[94mAnswer: {answer}\nChat History: {chat_history}\ntoken_count:{token_count}\033[0m" + ) if __name__ == "__main__": diff --git a/owl/nextwebapp.py b/owl/nextwebapp.py index 9da3e86..de39bdc 100644 --- a/owl/nextwebapp.py +++ b/owl/nextwebapp.py @@ -1,14 +1,25 @@ +# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. ========= +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. ========= # Import from the correct module path from owl.utils import run_society import os import gradio as gr -import time -import json -from typing import Tuple, List, Dict, Any +from typing import Tuple, List, Dict import importlib from dotenv import load_dotenv, set_key, find_dotenv, unset_key -os.environ['PYTHONIOENCODING'] = 'utf-8' +os.environ["PYTHONIOENCODING"] = "utf-8" # Enhanced CSS with navigation bar and additional styling custom_css = """ @@ -250,14 +261,14 @@ button.primary:hover { # Dictionary containing module descriptions MODULE_DESCRIPTIONS = { "run": "默认模式:使用OpenAI模型的默认的智能体协作模式,适合大多数任务。", - "run_mini":"使用使用OpenAI模型最小化配置处理任务", - "run_deepseek_zh":"使用deepseek模型处理中文任务", + "run_mini": "使用使用OpenAI模型最小化配置处理任务", + "run_deepseek_zh": "使用deepseek模型处理中文任务", "run_terminal_zh": "终端模式:可执行命令行操作,支持网络搜索、文件处理等功能。适合需要系统交互的任务,使用OpenAI模型", - "run_gaia_roleplaying":"GAIA基准测试实现,用于评估Agent能力", - "run_openai_compatiable_model":"使用openai兼容模型处理任务", - "run_ollama":"使用本地ollama模型处理任务", - "run_qwen_mini_zh":"使用qwen模型最小化配置处理任务", - "run_qwen_zh":"使用qwen模型处理任务", + "run_gaia_roleplaying": "GAIA基准测试实现,用于评估Agent能力", + "run_openai_compatiable_model": "使用openai兼容模型处理任务", + "run_ollama": "使用本地ollama模型处理任务", + "run_qwen_mini_zh": "使用qwen模型最小化配置处理任务", + "run_qwen_zh": "使用qwen模型处理任务", } # 默认环境变量模板 @@ -292,12 +303,13 @@ FIRECRAWL_API_KEY="" #FIRECRAWL_API_URL="https://api.firecrawl.dev" """ + def format_chat_history(chat_history: List[Dict[str, str]]) -> List[List[str]]: """将聊天历史格式化为Gradio聊天组件可接受的格式 - + Args: chat_history: 原始聊天历史 - + Returns: List[List[str]]: 格式化后的聊天历史 """ @@ -305,22 +317,23 @@ def format_chat_history(chat_history: List[Dict[str, str]]) -> List[List[str]]: for message in chat_history: user_msg = message.get("user", "") assistant_msg = message.get("assistant", "") - + if user_msg: formatted_history.append([user_msg, None]) if assistant_msg and formatted_history: formatted_history[-1][1] = assistant_msg elif assistant_msg: formatted_history.append([None, assistant_msg]) - + return formatted_history + def validate_input(question: str) -> bool: """验证用户输入是否有效 - + Args: question: 用户问题 - + Returns: bool: 输入是否有效 """ @@ -329,121 +342,111 @@ def validate_input(question: str) -> bool: return False return True -def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], str, str]: + +def run_owl( + question: str, example_module: str +) -> Tuple[str, List[List[str]], str, str]: """运行OWL系统并返回结果 - + Args: question: 用户问题 example_module: 要导入的示例模块名(如 "run_terminal_zh" 或 "run_deep") - + Returns: Tuple[...]: 回答、聊天历史、令牌计数、状态 """ # 验证输入 if not validate_input(question): - return ( - "请输入有效的问题", - [], - "0", - "❌ 错误: 输入无效" - ) - + return ("请输入有效的问题", [], "0", "❌ 错误: 输入无效") + try: # 确保环境变量已加载 load_dotenv(find_dotenv(), override=True) # 检查模块是否在MODULE_DESCRIPTIONS中 if example_module not in MODULE_DESCRIPTIONS: return ( - f"所选模块 '{example_module}' 不受支持", - [], - "0", - f"❌ 错误: 不支持的模块" + f"所选模块 '{example_module}' 不受支持", + [], + "0", + "❌ 错误: 不支持的模块", ) - + # 动态导入目标模块 module_path = f"owl.examples.{example_module}" try: module = importlib.import_module(module_path) except ImportError as ie: return ( - f"无法导入模块: {module_path}", - [], - "0", - f"❌ 错误: 模块 {example_module} 不存在或无法加载 - {str(ie)}" + f"无法导入模块: {module_path}", + [], + "0", + f"❌ 错误: 模块 {example_module} 不存在或无法加载 - {str(ie)}", ) except Exception as e: - return ( - f"导入模块时发生错误: {module_path}", - [], - "0", - f"❌ 错误: {str(e)}" - ) - + return (f"导入模块时发生错误: {module_path}", [], "0", f"❌ 错误: {str(e)}") + # 检查是否包含construct_society函数 if not hasattr(module, "construct_society"): return ( - f"模块 {module_path} 中未找到 construct_society 函数", - [], - "0", - f"❌ 错误: 模块接口不兼容" + f"模块 {module_path} 中未找到 construct_society 函数", + [], + "0", + "❌ 错误: 模块接口不兼容", ) - + # 构建社会模拟 try: society = module.construct_society(question) except Exception as e: return ( - f"构建社会模拟时发生错误: {str(e)}", - [], - "0", - f"❌ 错误: 构建失败 - {str(e)}" + f"构建社会模拟时发生错误: {str(e)}", + [], + "0", + f"❌ 错误: 构建失败 - {str(e)}", ) - + # 运行社会模拟 try: answer, chat_history, token_info = run_society(society) except Exception as e: return ( - f"运行社会模拟时发生错误: {str(e)}", - [], - "0", - f"❌ 错误: 运行失败 - {str(e)}" + f"运行社会模拟时发生错误: {str(e)}", + [], + "0", + f"❌ 错误: 运行失败 - {str(e)}", ) - + # 格式化聊天历史 try: formatted_chat_history = format_chat_history(chat_history) - except Exception as e: + except Exception: # 如果格式化失败,返回空历史记录但继续处理 formatted_chat_history = [] - + # 安全地获取令牌计数 if not isinstance(token_info, dict): token_info = {} - + completion_tokens = token_info.get("completion_token_count", 0) prompt_tokens = token_info.get("prompt_token_count", 0) total_tokens = completion_tokens + prompt_tokens - + return ( - answer, - formatted_chat_history, - f"完成令牌: {completion_tokens:,} | 提示令牌: {prompt_tokens:,} | 总计: {total_tokens:,}", - "✅ 成功完成" + answer, + formatted_chat_history, + f"完成令牌: {completion_tokens:,} | 提示令牌: {prompt_tokens:,} | 总计: {total_tokens:,}", + "✅ 成功完成", ) - + except Exception as e: - return ( - f"发生错误: {str(e)}", - [], - "0", - f"❌ 错误: {str(e)}" - ) + return (f"发生错误: {str(e)}", [], "0", f"❌ 错误: {str(e)}") + def update_module_description(module_name: str) -> str: """返回所选模块的描述""" return MODULE_DESCRIPTIONS.get(module_name, "无可用描述") + # 环境变量管理功能 def init_env_file(): """初始化.env文件如果不存在""" @@ -454,11 +457,12 @@ def init_env_file(): dotenv_path = find_dotenv() return dotenv_path + def load_env_vars(): """加载环境变量并返回字典格式""" dotenv_path = init_env_file() load_dotenv(dotenv_path, override=True) - + env_vars = {} with open(dotenv_path, "r") as f: for line in f: @@ -466,79 +470,84 @@ def load_env_vars(): if line and not line.startswith("#"): if "=" in line: key, value = line.split("=", 1) - env_vars[key.strip()] = value.strip().strip('"\'') - + env_vars[key.strip()] = value.strip().strip("\"'") + return env_vars + def save_env_vars(env_vars): """保存环境变量到.env文件""" try: dotenv_path = init_env_file() - + # 保存每个环境变量 for key, value in env_vars.items(): if key and key.strip(): # 确保键不为空 set_key(dotenv_path, key.strip(), value.strip()) - + # 重新加载环境变量以确保生效 load_dotenv(dotenv_path, override=True) - + return True, "环境变量已成功保存!" except Exception as e: return False, f"保存环境变量时出错: {str(e)}" + def add_env_var(key, value): """添加或更新单个环境变量""" try: if not key or not key.strip(): return False, "变量名不能为空" - + dotenv_path = init_env_file() set_key(dotenv_path, key.strip(), value.strip()) load_dotenv(dotenv_path, override=True) - + return True, f"环境变量 {key} 已成功添加/更新!" except Exception as e: return False, f"添加环境变量时出错: {str(e)}" + def delete_env_var(key): """删除环境变量""" try: if not key or not key.strip(): return False, "变量名不能为空" - + dotenv_path = init_env_file() unset_key(dotenv_path, key.strip()) - + # 从当前进程环境中也删除 if key in os.environ: del os.environ[key] - + return True, f"环境变量 {key} 已成功删除!" except Exception as e: return False, f"删除环境变量时出错: {str(e)}" + def mask_sensitive_value(key: str, value: str) -> str: """对敏感信息进行掩码处理 - + Args: key: 环境变量名 value: 环境变量值 - + Returns: str: 处理后的值 """ # 定义需要掩码的敏感关键词 - sensitive_keywords = ['key', 'token', 'secret', 'password', 'api'] - + sensitive_keywords = ["key", "token", "secret", "password", "api"] + # 检查是否包含敏感关键词(不区分大小写) is_sensitive = any(keyword in key.lower() for keyword in sensitive_keywords) - + if is_sensitive and value: # 如果是敏感信息且有值,则显示掩码 - return '*' * 8 + return "*" * 8 return value + def update_env_table(): """更新环境变量表格显示,对敏感信息进行掩码处理""" env_vars = load_env_vars() @@ -546,6 +555,7 @@ def update_env_table(): masked_env_vars = [[k, mask_sensitive_value(k, v)] for k, v in env_vars.items()] return masked_env_vars + def create_ui(): """创建增强版Gradio界面""" with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue="blue")) as app: @@ -569,7 +579,7 @@ def create_ui():

我们的愿景是彻底改变AI代理协作解决现实世界任务的方式。通过利用动态代理交互,OWL能够在多个领域实现更自然、高效和稳健的任务自动化。

""") - + with gr.Row(elem_id="features"): gr.HTML("""
@@ -610,7 +620,7 @@ def create_ui():
""") - + with gr.Row(): with gr.Column(scale=2): question_input = gr.Textbox( @@ -620,26 +630,28 @@ def create_ui(): elem_id="question_input", show_copy_button=True, ) - + # 增强版模块选择下拉菜单 # 只包含MODULE_DESCRIPTIONS中定义的模块 module_dropdown = gr.Dropdown( choices=list(MODULE_DESCRIPTIONS.keys()), value="run_terminal_zh", label="选择功能模块", - interactive=True + interactive=True, ) - + # 模块描述文本框 module_description = gr.Textbox( value=MODULE_DESCRIPTIONS["run_terminal_zh"], label="模块描述", interactive=False, - elem_classes="module-info" + elem_classes="module-info", ) - - run_button = gr.Button("运行", variant="primary", elem_classes="primary") - + + run_button = gr.Button( + "运行", variant="primary", elem_classes="primary" + ) + with gr.Column(scale=1): gr.Markdown(""" ### 使用指南 @@ -651,127 +663,103 @@ def create_ui(): > **高级提示**: 对于复杂任务,可以尝试指定具体步骤和预期结果 """) - + status_output = gr.Textbox(label="状态", interactive=False) - + with gr.Tabs(): with gr.TabItem("回答"): answer_output = gr.Textbox( - label="回答", - lines=10, - elem_classes="answer-box" + label="回答", lines=10, elem_classes="answer-box" ) - + with gr.TabItem("对话历史"): chat_output = gr.Chatbot( - label="完整对话记录", - elem_classes="chat-container", - height=500 + label="完整对话记录", elem_classes="chat-container", height=500 ) - - - + token_count_output = gr.Textbox( - label="令牌计数", - interactive=False, - elem_classes="token-count" + label="令牌计数", interactive=False, elem_classes="token-count" ) - + # 示例问题 examples = [ "打开百度搜索,总结一下camel-ai的camel框架的github star、fork数目等,并把数字用plot包写成python文件保存到本地,用本地终端执行python文件显示图出来给我", "请分析GitHub上CAMEL-AI项目的最新统计数据。找出该项目的星标数量、贡献者数量和最近的活跃度。", "浏览亚马逊并找出一款对程序员有吸引力的产品。请提供产品名称和价格", "写一个hello world的python文件,保存到本地", - ] - - gr.Examples( - examples=examples, - inputs=question_input - ) + + gr.Examples(examples=examples, inputs=question_input) # 新增: 环境变量管理选项卡 with gr.TabItem("环境变量管理", id="env-settings"): - gr.Markdown(""" + gr.Markdown(""" ## 环境变量管理 在此处设置模型API密钥和其他服务凭证。这些信息将保存在本地的`.env`文件中,确保您的API密钥安全存储且不会上传到网络。 """) - - # 环境变量表格 - env_table = gr.Dataframe( - headers=["变量名", "值"], - datatype=["str", "str"], - row_count=10, - col_count=(2, "fixed"), - value=update_env_table, - label="当前环境变量", - interactive=False - ) - - with gr.Row(): - with gr.Column(scale=1): - new_env_key = gr.Textbox(label="变量名", placeholder="例如: OPENAI_API_KEY") - with gr.Column(scale=2): - new_env_value = gr.Textbox(label="值", placeholder="输入API密钥或其他配置值") - - with gr.Row(): - add_env_button = gr.Button("添加/更新变量", variant="primary") - refresh_button = gr.Button("刷新变量列表") - delete_env_button = gr.Button("删除选定变量", variant="stop") - - env_status = gr.Textbox(label="状态", interactive=False) - - # 变量选择器(用于删除) - env_var_to_delete = gr.Dropdown( - choices=[], - label="选择要删除的变量", - interactive=True - ) - - # 更新变量选择器的选项 - def update_delete_dropdown(): - env_vars = load_env_vars() - return gr.Dropdown.update(choices=list(env_vars.keys())) - - # 连接事件处理函数 - add_env_button.click( - fn=lambda k, v: add_env_var(k, v), - inputs=[new_env_key, new_env_value], - outputs=[env_status] - ).then( - fn=update_env_table, - outputs=[env_table] - ).then( - fn=update_delete_dropdown, - outputs=[env_var_to_delete] - ).then( - fn=lambda: ("", ""), # 修改为返回两个空字符串的元组 - outputs=[new_env_key, new_env_value] - ) - - refresh_button.click( - fn=update_env_table, - outputs=[env_table] - ).then( - fn=update_delete_dropdown, - outputs=[env_var_to_delete] - ) - - delete_env_button.click( - fn=lambda k: delete_env_var(k), - inputs=[env_var_to_delete], - outputs=[env_status] - ).then( - fn=update_env_table, - outputs=[env_table] - ).then( - fn=update_delete_dropdown, - outputs=[env_var_to_delete] - ) + # 环境变量表格 + env_table = gr.Dataframe( + headers=["变量名", "值"], + datatype=["str", "str"], + row_count=10, + col_count=(2, "fixed"), + value=update_env_table, + label="当前环境变量", + interactive=False, + ) + + with gr.Row(): + with gr.Column(scale=1): + new_env_key = gr.Textbox( + label="变量名", placeholder="例如: OPENAI_API_KEY" + ) + with gr.Column(scale=2): + new_env_value = gr.Textbox( + label="值", placeholder="输入API密钥或其他配置值" + ) + + with gr.Row(): + add_env_button = gr.Button("添加/更新变量", variant="primary") + refresh_button = gr.Button("刷新变量列表") + delete_env_button = gr.Button("删除选定变量", variant="stop") + + env_status = gr.Textbox(label="状态", interactive=False) + + # 变量选择器(用于删除) + env_var_to_delete = gr.Dropdown( + choices=[], label="选择要删除的变量", interactive=True + ) + + # 更新变量选择器的选项 + def update_delete_dropdown(): + env_vars = load_env_vars() + return gr.Dropdown.update(choices=list(env_vars.keys())) + + # 连接事件处理函数 + add_env_button.click( + fn=lambda k, v: add_env_var(k, v), + inputs=[new_env_key, new_env_value], + outputs=[env_status], + ).then(fn=update_env_table, outputs=[env_table]).then( + fn=update_delete_dropdown, outputs=[env_var_to_delete] + ).then( + fn=lambda: ("", ""), # 修改为返回两个空字符串的元组 + outputs=[new_env_key, new_env_value], + ) + + refresh_button.click(fn=update_env_table, outputs=[env_table]).then( + fn=update_delete_dropdown, outputs=[env_var_to_delete] + ) + + delete_env_button.click( + fn=lambda k: delete_env_var(k), + inputs=[env_var_to_delete], + outputs=[env_status], + ).then(fn=update_env_table, outputs=[env_table]).then( + fn=update_delete_dropdown, outputs=[env_var_to_delete] + ) - gr.HTML(""" """) - + # 设置事件处理 run_button.click( fn=run_owl, - inputs=[question_input, module_dropdown], - outputs=[answer_output, chat_output, token_count_output, status_output] + inputs=[question_input, module_dropdown], + outputs=[answer_output, chat_output, token_count_output, status_output], ) - + # 模块选择更新描述 module_dropdown.change( fn=update_module_description, inputs=module_dropdown, - outputs=module_description + outputs=module_description, ) - + return app + # 主函数 def main(): try: @@ -807,7 +796,9 @@ def main(): except Exception as e: print(f"启动应用程序时发生错误: {str(e)}") import traceback + traceback.print_exc() + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/owl/utils/enhanced_role_playing.py b/owl/utils/enhanced_role_playing.py index 0cbc2c9..bf1e5bb 100644 --- a/owl/utils/enhanced_role_playing.py +++ b/owl/utils/enhanced_role_playing.py @@ -381,6 +381,12 @@ Now please give me instructions to solve over overall task step by step. If the """ input_msg = society.init_chat(init_prompt) for _round in range(round_limit): + # Check if previous user response had TASK_DONE before getting next assistant response + if _round > 0 and ( + "TASK_DONE" in input_msg.content or "任务已完成" in input_msg.content + ): + break + assistant_response, user_response = society.step(input_msg) overall_completion_token_count += ( assistant_response.info["usage"]["completion_tokens"] @@ -408,10 +414,12 @@ Now please give me instructions to solve over overall task step by step. If the f"Round #{_round} assistant_response:\n {assistant_response.msgs[0].content}" ) + # Check other termination conditions if ( assistant_response.terminated or user_response.terminated or "TASK_DONE" in user_response.msg.content + or "任务已完成" in user_response.msg.content ): break diff --git a/owl/webapp_zh.py b/owl/webapp_zh.py index 9b58cbe..834508d 100644 --- a/owl/webapp_zh.py +++ b/owl/webapp_zh.py @@ -1,3 +1,16 @@ +# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. ========= +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. ========= # Import from the correct module path from owl.utils import run_society import os @@ -6,19 +19,15 @@ import time import json import logging import datetime -from typing import Tuple, List, Dict, Any +from typing import Tuple import importlib from dotenv import load_dotenv, set_key, find_dotenv, unset_key import threading import queue -import time -import signal -import sys -import subprocess -import platform -import re +import re # For regular expression operations + +os.environ["PYTHONIOENCODING"] = "utf-8" -os.environ['PYTHONIOENCODING'] = 'utf-8' # 配置日志系统 def setup_logging(): @@ -26,94 +35,112 @@ def setup_logging(): # 创建logs目录(如果不存在) logs_dir = os.path.join(os.path.dirname(__file__), "logs") os.makedirs(logs_dir, exist_ok=True) - + # 生成日志文件名(使用当前日期) current_date = datetime.datetime.now().strftime("%Y-%m-%d") log_file = os.path.join(logs_dir, f"gradio_log_{current_date}.txt") - + # 配置根日志记录器(捕获所有日志) root_logger = logging.getLogger() - + # 清除现有的处理器,避免重复日志 for handler in root_logger.handlers[:]: root_logger.removeHandler(handler) - + root_logger.setLevel(logging.INFO) - + # 创建文件处理器 - file_handler = logging.FileHandler(log_file, encoding='utf-8', mode='a') + file_handler = logging.FileHandler(log_file, encoding="utf-8", mode="a") file_handler.setLevel(logging.INFO) - + # 创建控制台处理器 console_handler = logging.StreamHandler() console_handler.setLevel(logging.INFO) - + # 创建格式化器 - formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + formatter = logging.Formatter( + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + ) file_handler.setFormatter(formatter) console_handler.setFormatter(formatter) - + # 添加处理器到根日志记录器 root_logger.addHandler(file_handler) root_logger.addHandler(console_handler) - + logging.info("日志系统已初始化,日志文件: %s", log_file) return log_file + # 全局变量 LOG_FILE = None -LOG_QUEUE = queue.Queue() -LOG_QUEUE2 = queue.Queue() # 对话记录的队列 +LOG_QUEUE: queue.Queue = queue.Queue() # 日志队列 STOP_LOG_THREAD = threading.Event() CURRENT_PROCESS = None # 用于跟踪当前运行的进程 STOP_REQUESTED = threading.Event() # 用于标记是否请求停止 + # 日志读取和更新函数 def log_reader_thread(log_file): """后台线程,持续读取日志文件并将新行添加到队列中""" try: - with open(log_file, 'r', encoding='utf-8') as f: + with open(log_file, "r", encoding="utf-8") as f: # 移动到文件末尾 f.seek(0, 2) - + while not STOP_LOG_THREAD.is_set(): line = f.readline() if line: - LOG_QUEUE.put(line) - LOG_QUEUE2.put(line) # 同时添加到第二个队列 + LOG_QUEUE.put(line) # 添加到对话记录队列 else: # 没有新行,等待一小段时间 time.sleep(0.1) except Exception as e: logging.error(f"日志读取线程出错: {str(e)}") + def get_latest_logs(max_lines=100, queue_source=None): """从队列中获取最新的日志行,如果队列为空则直接从文件读取 - + Args: max_lines: 最大返回行数 queue_source: 指定使用哪个队列,默认为LOG_QUEUE - + Returns: str: 日志内容 """ logs = [] log_queue = queue_source if queue_source else LOG_QUEUE + + # 创建一个临时队列来存储日志,以便我们可以处理它们而不会从原始队列中删除它们 + temp_queue = queue.Queue() + temp_logs = [] + try: # 尝试从队列中获取所有可用的日志行 - while not log_queue.empty() and len(logs) < max_lines: - logs.append(log_queue.get_nowait()) + while not log_queue.empty() and len(temp_logs) < max_lines: + log = log_queue.get_nowait() + temp_logs.append(log) + temp_queue.put(log) # 将日志放回临时队列 except queue.Empty: pass - + + # 处理对话记录 + logs = temp_logs + # 如果没有新日志或日志不足,尝试直接从文件读取最后几行 if len(logs) < max_lines and LOG_FILE and os.path.exists(LOG_FILE): try: - with open(LOG_FILE, 'r', encoding='utf-8') as f: + with open(LOG_FILE, "r", encoding="utf-8") as f: all_lines = f.readlines() # 如果队列中已有一些日志,只读取剩余需要的行数 remaining_lines = max_lines - len(logs) - file_logs = all_lines[-remaining_lines:] if len(all_lines) > remaining_lines else all_lines + file_logs = ( + all_lines[-remaining_lines:] + if len(all_lines) > remaining_lines + else all_lines + ) + # 将文件日志添加到队列日志之前 logs = file_logs + logs except Exception as e: @@ -121,51 +148,106 @@ def get_latest_logs(max_lines=100, queue_source=None): logging.error(error_msg) if not logs: # 只有在没有任何日志的情况下才添加错误消息 logs = [error_msg] - + # 如果仍然没有日志,返回提示信息 if not logs: - return "暂无日志记录或日志系统未正确初始化。" - - # 格式化日志输出,确保每个日志条目有适当的换行和分隔 - formatted_logs = [] + return "暂无对话记录。" + + # 过滤日志,只保留 camel.agents.chat_agent - INFO 的日志 + filtered_logs = [] for log in logs: + if "camel.agents.chat_agent - INFO" in log: + filtered_logs.append(log) + + # 如果过滤后没有日志,返回提示信息 + if not filtered_logs: + return "暂无对话记录。" + + # 处理日志内容,提取最新的用户和助手消息 + simplified_logs = [] + + # 使用集合来跟踪已经处理过的消息,避免重复 + processed_messages = set() + + def process_message(role, content): + # 创建一个唯一标识符来跟踪消息 + msg_id = f"{role}:{content}" + if msg_id in processed_messages: + return None + + processed_messages.add(msg_id) + content = content.replace("\\n", "\n") + lines = [line.strip() for line in content.split("\n")] + content = "\n".join(lines) + + return f"[{role.title()} Agent]: {content}" + + for log in filtered_logs: + formatted_messages = [] + # 尝试提取消息数组 + messages_match = re.search( + r"Model (.*?), index (\d+), processed these messages: (\[.*\])", log + ) + + if messages_match: + try: + messages = json.loads(messages_match.group(3)) + for msg in messages: + if msg.get("role") in ["user", "assistant"]: + formatted_msg = process_message( + msg.get("role"), msg.get("content", "") + ) + if formatted_msg: + formatted_messages.append(formatted_msg) + except json.JSONDecodeError: + pass + + # 如果JSON解析失败或没有找到消息数组,尝试直接提取对话内容 + if not formatted_messages: + user_pattern = re.compile(r"\{'role': 'user', 'content': '(.*?)'\}") + assistant_pattern = re.compile( + r"\{'role': 'assistant', 'content': '(.*?)'\}" + ) + + for content in user_pattern.findall(log): + formatted_msg = process_message("user", content) + if formatted_msg: + formatted_messages.append(formatted_msg) + + for content in assistant_pattern.findall(log): + formatted_msg = process_message("assistant", content) + if formatted_msg: + formatted_messages.append(formatted_msg) + + if formatted_messages: + simplified_logs.append("\n\n".join(formatted_messages)) + + # 格式化日志输出,确保每个对话记录之间有适当的分隔 + formatted_logs = [] + for i, log in enumerate(simplified_logs): # 移除开头和结尾的多余空白字符 log = log.strip() - - # 处理包含JSON或代码片段的日志,确保它们有正确的换行和缩进 - if '"]"\n}' in log or '\n}\n\n' in log: - # 替换不合理的换行为更清晰的格式 - log = log.replace('"]"\n}', '"]" }').replace('\n}\n\n', ' }\n') - - # 检测日期时间格式的开头,这通常表示一个新的日志条目 - # 例如:2025-03-14 18:49:31,008 - httpx - INFO - if re.match(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}', log): - # 在新的日志条目前添加一个空行,使日志更易读 - formatted_logs.append('\n') - - # 确保每个日志条目以换行符结束 - if not log.endswith('\n'): - log += '\n' - + formatted_logs.append(log) - - # 移除第一个可能的额外空行 - if formatted_logs and formatted_logs[0] == '\n': - formatted_logs.pop(0) - + + # 确保每个对话记录以换行符结束 + if not log.endswith("\n"): + formatted_logs.append("\n") + return "".join(formatted_logs) + # Dictionary containing module descriptions MODULE_DESCRIPTIONS = { "run": "默认模式:使用OpenAI模型的默认的智能体协作模式,适合大多数任务。", - "run_mini":"使用使用OpenAI模型最小化配置处理任务", - "run_deepseek_zh":"使用deepseek模型处理中文任务", + "run_mini": "使用使用OpenAI模型最小化配置处理任务", + "run_deepseek_zh": "使用deepseek模型处理中文任务", "run_terminal_zh": "终端模式:可执行命令行操作,支持网络搜索、文件处理等功能。适合需要系统交互的任务,使用OpenAI模型", - "run_gaia_roleplaying":"GAIA基准测试实现,用于评估Agent能力", - "run_openai_compatiable_model":"使用openai兼容模型处理任务", - "run_ollama":"使用本地ollama模型处理任务", - "run_qwen_mini_zh":"使用qwen模型最小化配置处理任务", - "run_qwen_zh":"使用qwen模型处理任务", + "run_gaia_roleplaying": "GAIA基准测试实现,用于评估Agent能力", + "run_openai_compatiable_model": "使用openai兼容模型处理任务", + "run_ollama": "使用本地ollama模型处理任务", + "run_qwen_mini_zh": "使用qwen模型最小化配置处理任务", + "run_qwen_zh": "使用qwen模型处理任务", } # API帮助信息 @@ -173,43 +255,43 @@ API_HELP_INFO = { "OPENAI_API_KEY": { "name": "OpenAI API", "desc": "OpenAI API密钥,用于访问GPT系列模型", - "url": "https://platform.openai.com/api-keys" + "url": "https://platform.openai.com/api-keys", }, "QWEN_API_KEY": { "name": "通义千问 API", "desc": "阿里云通义千问API密钥", - "url": "https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key" + "url": "https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key", }, "DEEPSEEK_API_KEY": { "name": "DeepSeek API", "desc": "DeepSeek API密钥", - "url": "https://platform.deepseek.com/api_keys" + "url": "https://platform.deepseek.com/api_keys", }, "GOOGLE_API_KEY": { "name": "Google Search API", "desc": "Google自定义搜索API密钥", - "url": "https://developers.google.com/custom-search/v1/overview" + "url": "https://developers.google.com/custom-search/v1/overview", }, "SEARCH_ENGINE_ID": { "name": "Google Search Engine ID", "desc": "Google自定义搜索引擎ID", - "url": "https://developers.google.com/custom-search/v1/overview" + "url": "https://developers.google.com/custom-search/v1/overview", }, "HF_TOKEN": { "name": "Hugging Face API", "desc": "Hugging Face API令牌", - "url": "https://huggingface.co/join" + "url": "https://huggingface.co/join", }, "CHUNKR_API_KEY": { "name": "Chunkr API", "desc": "Chunkr API密钥", - "url": "https://chunkr.ai/" + "url": "https://chunkr.ai/", }, "FIRECRAWL_API_KEY": { "name": "Firecrawl API", "desc": "Firecrawl API密钥", - "url": "https://www.firecrawl.dev/" - } + "url": "https://www.firecrawl.dev/", + }, } # 默认环境变量模板 @@ -245,13 +327,12 @@ FIRECRAWL_API_KEY="" """ - def validate_input(question: str) -> bool: """验证用户输入是否有效 - + Args: question: 用户问题 - + Returns: bool: 输入是否有效 """ @@ -260,41 +341,38 @@ def validate_input(question: str) -> bool: return False return True + def run_owl(question: str, example_module: str) -> Tuple[str, str, str]: """运行OWL系统并返回结果 - + Args: question: 用户问题 example_module: 要导入的示例模块名(如 "run_terminal_zh" 或 "run_deep") - + Returns: Tuple[...]: 回答、令牌计数、状态 """ global CURRENT_PROCESS - + # 验证输入 if not validate_input(question): logging.warning("用户提交了无效的输入") - return ( - "请输入有效的问题", - "0", - "❌ 错误: 输入无效" - ) - + return ("请输入有效的问题", "0", "❌ 错误: 输入无效") + try: # 确保环境变量已加载 load_dotenv(find_dotenv(), override=True) logging.info(f"处理问题: '{question}', 使用模块: {example_module}") - + # 检查模块是否在MODULE_DESCRIPTIONS中 if example_module not in MODULE_DESCRIPTIONS: logging.error(f"用户选择了不支持的模块: {example_module}") return ( - f"所选模块 '{example_module}' 不受支持", - "0", - f"❌ 错误: 不支持的模块" + f"所选模块 '{example_module}' 不受支持", + "0", + "❌ 错误: 不支持的模块", ) - + # 动态导入目标模块 module_path = f"owl.examples.{example_module}" try: @@ -303,41 +381,36 @@ def run_owl(question: str, example_module: str) -> Tuple[str, str, str]: except ImportError as ie: logging.error(f"无法导入模块 {module_path}: {str(ie)}") return ( - f"无法导入模块: {module_path}", - "0", - f"❌ 错误: 模块 {example_module} 不存在或无法加载 - {str(ie)}" + f"无法导入模块: {module_path}", + "0", + f"❌ 错误: 模块 {example_module} 不存在或无法加载 - {str(ie)}", ) except Exception as e: logging.error(f"导入模块 {module_path} 时发生错误: {str(e)}") - return ( - f"导入模块时发生错误: {module_path}", - "0", - f"❌ 错误: {str(e)}" - ) - + return (f"导入模块时发生错误: {module_path}", "0", f"❌ 错误: {str(e)}") + # 检查是否包含construct_society函数 if not hasattr(module, "construct_society"): logging.error(f"模块 {module_path} 中未找到 construct_society 函数") return ( - f"模块 {module_path} 中未找到 construct_society 函数", - "0", - f"❌ 错误: 模块接口不兼容" + f"模块 {module_path} 中未找到 construct_society 函数", + "0", + "❌ 错误: 模块接口不兼容", ) - + # 构建社会模拟 try: logging.info("正在构建社会模拟...") society = module.construct_society(question) - except Exception as e: logging.error(f"构建社会模拟时发生错误: {str(e)}") return ( - f"构建社会模拟时发生错误: {str(e)}", - "0", - f"❌ 错误: 构建失败 - {str(e)}" + f"构建社会模拟时发生错误: {str(e)}", + "0", + f"❌ 错误: 构建失败 - {str(e)}", ) - + # 运行社会模拟 try: logging.info("正在运行社会模拟...") @@ -346,42 +419,45 @@ def run_owl(question: str, example_module: str) -> Tuple[str, str, str]: except Exception as e: logging.error(f"运行社会模拟时发生错误: {str(e)}") return ( - f"运行社会模拟时发生错误: {str(e)}", - "0", - f"❌ 错误: 运行失败 - {str(e)}" + f"运行社会模拟时发生错误: {str(e)}", + "0", + f"❌ 错误: 运行失败 - {str(e)}", ) - - # 安全地获取令牌计数 if not isinstance(token_info, dict): token_info = {} - + completion_tokens = token_info.get("completion_token_count", 0) prompt_tokens = token_info.get("prompt_token_count", 0) total_tokens = completion_tokens + prompt_tokens - - logging.info(f"处理完成,令牌使用: 完成={completion_tokens}, 提示={prompt_tokens}, 总计={total_tokens}") - - return ( - answer, - f"完成令牌: {completion_tokens:,} | 提示令牌: {prompt_tokens:,} | 总计: {total_tokens:,}", - "✅ 成功完成" + + logging.info( + f"处理完成,令牌使用: 完成={completion_tokens}, 提示={prompt_tokens}, 总计={total_tokens}" ) - + + return ( + answer, + f"完成令牌: {completion_tokens:,} | 提示令牌: {prompt_tokens:,} | 总计: {total_tokens:,}", + "✅ 成功完成", + ) + except Exception as e: logging.error(f"处理问题时发生未捕获的错误: {str(e)}") - return ( - f"发生错误: {str(e)}", - "0", - f"❌ 错误: {str(e)}" - ) + return (f"发生错误: {str(e)}", "0", f"❌ 错误: {str(e)}") + def update_module_description(module_name: str) -> str: """返回所选模块的描述""" return MODULE_DESCRIPTIONS.get(module_name, "无可用描述") + # 环境变量管理功能 + +# 存储前端配置的环境变量 +WEB_FRONTEND_ENV_VARS: dict[str, str] = {} + + def init_env_file(): """初始化.env文件如果不存在""" dotenv_path = find_dotenv() @@ -391,116 +467,332 @@ def init_env_file(): dotenv_path = find_dotenv() return dotenv_path + def load_env_vars(): - """加载环境变量并返回字典格式""" + """加载环境变量并返回字典格式 + + Returns: + dict: 环境变量字典,每个值为一个包含值和来源的元组 (value, source) + """ dotenv_path = init_env_file() load_dotenv(dotenv_path, override=True) - - env_vars = {} + + # 从.env文件读取环境变量 + env_file_vars = {} with open(dotenv_path, "r") as f: for line in f: line = line.strip() if line and not line.startswith("#"): if "=" in line: key, value = line.split("=", 1) - env_vars[key.strip()] = value.strip().strip('"\'') - + env_file_vars[key.strip()] = value.strip().strip("\"'") + + # 从系统环境变量中获取 + system_env_vars = { + k: v + for k, v in os.environ.items() + if k not in env_file_vars and k not in WEB_FRONTEND_ENV_VARS + } + + # 合并环境变量,并标记来源 + env_vars = {} + + # 添加系统环境变量(最低优先级) + for key, value in system_env_vars.items(): + env_vars[key] = (value, "系统") + + # 添加.env文件环境变量(中等优先级) + for key, value in env_file_vars.items(): + env_vars[key] = (value, ".env文件") + + # 添加前端配置的环境变量(最高优先级) + for key, value in WEB_FRONTEND_ENV_VARS.items(): + env_vars[key] = (value, "前端配置") + # 确保操作系统环境变量也被更新 + os.environ[key] = value + return env_vars + def save_env_vars(env_vars): - """保存环境变量到.env文件""" + """保存环境变量到.env文件 + + Args: + env_vars: 字典,键为环境变量名,值可以是字符串或(值,来源)元组 + """ try: dotenv_path = init_env_file() - + # 保存每个环境变量 - for key, value in env_vars.items(): + for key, value_data in env_vars.items(): if key and key.strip(): # 确保键不为空 + # 处理值可能是元组的情况 + if isinstance(value_data, tuple): + value = value_data[0] + else: + value = value_data + set_key(dotenv_path, key.strip(), value.strip()) - + # 重新加载环境变量以确保生效 load_dotenv(dotenv_path, override=True) - + return True, "环境变量已成功保存!" except Exception as e: return False, f"保存环境变量时出错: {str(e)}" -def add_env_var(key, value): - """添加或更新单个环境变量""" + +def add_env_var(key, value, from_frontend=True): + """添加或更新单个环境变量 + + Args: + key: 环境变量名 + value: 环境变量值 + from_frontend: 是否来自前端配置,默认为True + """ try: if not key or not key.strip(): return False, "变量名不能为空" - + + key = key.strip() + value = value.strip() + + # 如果来自前端,则添加到前端环境变量字典 + if from_frontend: + WEB_FRONTEND_ENV_VARS[key] = value + # 直接更新系统环境变量 + os.environ[key] = value + + # 同时更新.env文件 dotenv_path = init_env_file() - set_key(dotenv_path, key.strip(), value.strip()) + set_key(dotenv_path, key, value) load_dotenv(dotenv_path, override=True) - + return True, f"环境变量 {key} 已成功添加/更新!" except Exception as e: return False, f"添加环境变量时出错: {str(e)}" + def delete_env_var(key): """删除环境变量""" try: if not key or not key.strip(): return False, "变量名不能为空" - + + key = key.strip() + + # 从.env文件中删除 dotenv_path = init_env_file() - unset_key(dotenv_path, key.strip()) - + unset_key(dotenv_path, key) + + # 从前端环境变量字典中删除 + if key in WEB_FRONTEND_ENV_VARS: + del WEB_FRONTEND_ENV_VARS[key] + # 从当前进程环境中也删除 if key in os.environ: del os.environ[key] - + return True, f"环境变量 {key} 已成功删除!" except Exception as e: return False, f"删除环境变量时出错: {str(e)}" -def mask_sensitive_value(key: str, value: str) -> str: - """对敏感信息进行掩码处理 - + +def is_api_related(key: str) -> bool: + """判断环境变量是否与API相关 + Args: key: 环境变量名 - value: 环境变量值 - + Returns: - str: 处理后的值 + bool: 是否与API相关 """ - # 定义需要掩码的敏感关键词 - sensitive_keywords = ['key', 'token', 'secret', 'password', 'api'] - - # 检查是否包含敏感关键词(不区分大小写) - is_sensitive = any(keyword in key.lower() for keyword in sensitive_keywords) - - if is_sensitive and value: - # 如果是敏感信息且有值,则显示掩码 - return '*' * 8 - return value + # API相关的关键词 + api_keywords = [ + "api", + "key", + "token", + "secret", + "password", + "openai", + "qwen", + "deepseek", + "google", + "search", + "hf", + "hugging", + "chunkr", + "firecrawl", + ] + + # 检查是否包含API相关关键词(不区分大小写) + return any(keyword in key.lower() for keyword in api_keywords) + + +def get_api_guide(key: str) -> str: + """根据环境变量名返回对应的API获取指南 + + Args: + key: 环境变量名 + + Returns: + str: API获取指南链接或说明 + """ + key_lower = key.lower() + if "openai" in key_lower: + return "https://platform.openai.com/api-keys" + elif "qwen" in key_lower or "dashscope" in key_lower: + return "https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key" + elif "deepseek" in key_lower: + return "https://platform.deepseek.com/api_keys" + elif "google" in key_lower: + return "https://developers.google.com/custom-search/v1/overview" + elif "chunkr" in key_lower: + return "https://chunkr.ai/" + elif "firecrawl" in key_lower: + return "https://www.firecrawl.dev/" + else: + return "" + def update_env_table(): - """更新环境变量表格显示,对敏感信息进行掩码处理""" + """更新环境变量表格显示,只显示API相关的环境变量""" env_vars = load_env_vars() - # 对敏感值进行掩码处理 - masked_env_vars = [[k, mask_sensitive_value(k, v)] for k, v in env_vars.items()] - return masked_env_vars + # 过滤出API相关的环境变量 + api_env_vars = {k: v for k, v in env_vars.items() if is_api_related(k)} + # 转换为列表格式,以符合Gradio Dataframe的要求 + # 格式: [变量名, 变量值, 获取指南链接] + result = [] + for k, v in api_env_vars.items(): + guide = get_api_guide(k) + # 如果有指南链接,创建一个可点击的链接 + guide_link = ( + f"🔗 获取" + if guide + else "" + ) + result.append([k, v[0], guide_link]) + return result + + +def save_env_table_changes(data): + """保存环境变量表格的更改 + + Args: + data: Dataframe数据,可能是pandas DataFrame对象 + + Returns: + str: 操作状态信息,包含HTML格式的状态消息 + """ + try: + logging.info(f"开始处理环境变量表格数据,类型: {type(data)}") + + # 获取当前所有环境变量 + current_env_vars = load_env_vars() + processed_keys = set() # 记录已处理的键,用于检测删除的变量 + + # 处理pandas DataFrame对象 + import pandas as pd + + if isinstance(data, pd.DataFrame): + # 获取列名信息 + columns = data.columns.tolist() + logging.info(f"DataFrame列名: {columns}") + + # 遍历DataFrame的每一行 + for index, row in data.iterrows(): + # 使用列名或索引访问数据 + if len(columns) >= 3: + # 如果有列名,使用列名访问 + key = row.iloc[1] if hasattr(row, "iloc") else row[1] + value = row.iloc[2] if hasattr(row, "iloc") else row[2] + + # 检查是否为空行或已删除的变量 + if key and str(key).strip(): # 如果键名不为空,则添加或更新 + logging.info(f"处理环境变量: {key} = {value}") + add_env_var(key, str(value)) + processed_keys.add(key) + # 处理其他格式 + elif isinstance(data, dict): + logging.info(f"字典格式数据的键: {list(data.keys())}") + # 如果是字典格式,尝试不同的键 + if "data" in data: + rows = data["data"] + elif "values" in data: + rows = data["values"] + elif "value" in data: + rows = data["value"] + else: + # 尝试直接使用字典作为行数据 + rows = [] + for key, value in data.items(): + if key not in ["headers", "types", "columns"]: + rows.append([key, value]) + + if isinstance(rows, list): + for row in rows: + if isinstance(row, list) and len(row) >= 2: + key, value = row[0], row[1] + if key and str(key).strip(): + add_env_var(key, str(value)) + processed_keys.add(key) + elif isinstance(data, list): + # 列表格式 + for row in data: + if isinstance(row, list) and len(row) >= 2: + key, value = row[0], row[1] + if key and str(key).strip(): + add_env_var(key, str(value)) + processed_keys.add(key) + else: + logging.error(f"未知的数据格式: {type(data)}") + return f"❌ 保存失败: 未知的数据格式 {type(data)}" + + # 处理删除的变量 - 检查当前环境变量中是否有未在表格中出现的变量 + api_related_keys = {k for k in current_env_vars.keys() if is_api_related(k)} + keys_to_delete = api_related_keys - processed_keys + + # 删除不再表格中的变量 + for key in keys_to_delete: + logging.info(f"删除环境变量: {key}") + delete_env_var(key) + + return "✅ 环境变量已成功保存" + except Exception as e: + import traceback + + error_details = traceback.format_exc() + logging.error(f"保存环境变量时出错: {str(e)}\n{error_details}") + return f"❌ 保存失败: {str(e)}" + + +def get_env_var_value(key): + """获取环境变量的实际值 + + 优先级:前端配置 > .env文件 > 系统环境变量 + """ + # 检查前端配置的环境变量 + if key in WEB_FRONTEND_ENV_VARS: + return WEB_FRONTEND_ENV_VARS[key] + + # 检查系统环境变量(包括从.env加载的) + return os.environ.get(key, "") + def create_ui(): """创建增强版Gradio界面""" - - # 定义日志更新函数 - def update_logs(): - """获取最新日志并返回给前端显示""" - return get_latest_logs(100) - + + # 定义对话记录更新函数 def update_logs2(): """获取最新对话记录并返回给前端显示""" - return get_latest_logs(100, LOG_QUEUE2) - + return get_latest_logs(100, LOG_QUEUE) + def clear_log_file(): """清空日志文件内容""" try: if LOG_FILE and os.path.exists(LOG_FILE): # 清空日志文件内容而不是删除文件 - open(LOG_FILE, 'w').close() + open(LOG_FILE, "w").close() logging.info("日志文件已清空") # 清空日志队列 while not LOG_QUEUE.empty(): @@ -508,82 +800,85 @@ def create_ui(): LOG_QUEUE.get_nowait() except queue.Empty: break - # 清空第二个日志队列 - while not LOG_QUEUE2.empty(): - try: - LOG_QUEUE2.get_nowait() - except queue.Empty: - break - return "日志文件已清空" + return "" else: - return "日志文件不存在或未设置" + return "" except Exception as e: logging.error(f"清空日志文件时出错: {str(e)}") - return f"清空日志文件时出错: {str(e)}" - + return "" + # 创建一个实时日志更新函数 def process_with_live_logs(question, module_name): """处理问题并实时更新日志""" global CURRENT_PROCESS - + # 创建一个后台线程来处理问题 result_queue = queue.Queue() - + def process_in_background(): try: result = run_owl(question, module_name) result_queue.put(result) except Exception as e: result_queue.put((f"发生错误: {str(e)}", "0", f"❌ 错误: {str(e)}")) - + # 启动后台处理线程 bg_thread = threading.Thread(target=process_in_background) CURRENT_PROCESS = bg_thread # 记录当前进程 bg_thread.start() - + # 在等待处理完成的同时,每秒更新一次日志 while bg_thread.is_alive(): - # 更新日志显示 - logs = get_latest_logs(100) - logs2 = get_latest_logs(100, LOG_QUEUE2) - + # 更新对话记录显示 + logs2 = get_latest_logs(100, LOG_QUEUE) + # 始终更新状态 - yield None, "0", " 处理中...", logs, logs2 - + yield ( + "0", + " 处理中...", + logs2, + ) + time.sleep(1) - + # 处理完成,获取结果 if not result_queue.empty(): result = result_queue.get() answer, token_count, status = result - - # 最后一次更新日志 - logs = get_latest_logs(100) - logs2 = get_latest_logs(100, LOG_QUEUE2) - + + # 最后一次更新对话记录 + logs2 = get_latest_logs(100, LOG_QUEUE) + # 根据状态设置不同的指示器 if "错误" in status: - status_with_indicator = f" {status}" + status_with_indicator = ( + f" {status}" + ) else: - status_with_indicator = f" {status}" - - yield answer, token_count, status_with_indicator, logs, logs2 + status_with_indicator = ( + f" {status}" + ) + + yield token_count, status_with_indicator, logs2 else: - logs = get_latest_logs(100) - logs2 = get_latest_logs(100, LOG_QUEUE2) - yield "操作未完成", "0", " 已终止", logs, logs2 - + logs2 = get_latest_logs(100, LOG_QUEUE) + yield ( + "0", + " 已终止", + logs2, + ) + with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as app: - gr.Markdown( - """ + gr.Markdown( + """ # 🦉 OWL 多智能体协作系统 基于CAMEL框架开发的先进多智能体协作系统,旨在通过智能体协作解决复杂问题。 """ - ) - - # 添加自定义CSS - gr.HTML(""" + ) + + # 添加自定义CSS + gr.HTML(""" """) - - with gr.Row(): - with gr.Column(scale=1): - question_input = gr.Textbox( - lines=5, - placeholder="请输入您的问题...", - label="问题", - elem_id="question_input", + + with gr.Row(): + with gr.Column(scale=1): + question_input = gr.Textbox( + lines=5, + placeholder="请输入您的问题...", + label="问题", + elem_id="question_input", + show_copy_button=True, + ) + + # 增强版模块选择下拉菜单 + # 只包含MODULE_DESCRIPTIONS中定义的模块 + module_dropdown = gr.Dropdown( + choices=list(MODULE_DESCRIPTIONS.keys()), + value="run_qwen_zh", + label="选择功能模块", + interactive=True, + ) + + # 模块描述文本框 + module_description = gr.Textbox( + value=MODULE_DESCRIPTIONS["run_qwen_zh"], + label="模块描述", + interactive=False, + elem_classes="module-info", + ) + + with gr.Row(): + run_button = gr.Button( + "运行", variant="primary", elem_classes="primary" + ) + + status_output = gr.HTML( + value=" 已就绪", + label="状态", + ) + token_count_output = gr.Textbox( + label="令牌计数", interactive=False, elem_classes="token-count" + ) + + with gr.Tabs(): # 设置对话记录为默认选中的标签页 + with gr.TabItem("对话记录"): + # 添加对话记录显示区域 + log_display2 = gr.Textbox( + label="对话记录", + lines=25, + max_lines=100, + interactive=False, + autoscroll=True, show_copy_button=True, + elem_classes="log-display", + container=True, + value="", ) - - # 增强版模块选择下拉菜单 - # 只包含MODULE_DESCRIPTIONS中定义的模块 - module_dropdown = gr.Dropdown( - choices=list(MODULE_DESCRIPTIONS.keys()), - value="run", - label="选择功能模块", - interactive=True - ) - - # 模块描述文本框 - module_description = gr.Textbox( - value=MODULE_DESCRIPTIONS["run"], - label="模块描述", - interactive=False, - elem_classes="module-info" - ) - + with gr.Row(): - run_button = gr.Button("运行", variant="primary", elem_classes="primary") - - status_output = gr.HTML( - value=" 已就绪", - label="状态" - ) - token_count_output = gr.Textbox( - label="令牌计数", - interactive=False, - elem_classes="token-count" - ) - - - - with gr.Tabs(): # 设置对话记录为默认选中的标签页 - with gr.TabItem("对话记录"): - # 添加对话记录显示区域 - log_display2 = gr.Textbox( - label="对话记录", - lines=25, - max_lines=100, - interactive=False, - autoscroll=True, - show_copy_button=True, - elem_classes="log-display", - container=True + refresh_logs_button2 = gr.Button("刷新记录") + auto_refresh_checkbox2 = gr.Checkbox( + label="自动刷新", value=True, interactive=True ) - - with gr.Row(): - refresh_logs_button2 = gr.Button("刷新记录") - auto_refresh_checkbox2 = gr.Checkbox( - label="自动刷新", - value=True, - interactive=True - ) - clear_logs_button2 = gr.Button("清空记录", variant="secondary") - - with gr.TabItem("系统日志"): - # 添加日志显示区域 - log_display = gr.Textbox( - label="系统日志", - lines=25, - max_lines=100, - interactive=False, - autoscroll=True, - show_copy_button=True, - elem_classes="log-display", - container=True - ) - - with gr.Row(): - refresh_logs_button = gr.Button("刷新日志") - auto_refresh_checkbox = gr.Checkbox( - label="自动刷新", - value=True, - interactive=True - ) - clear_logs_button = gr.Button("清空日志", variant="secondary") - with gr.TabItem("回答"): - answer_output = gr.Textbox( - label="回答", - lines=10, - elem_classes="answer-box" - ) - + clear_logs_button2 = gr.Button("清空记录", variant="secondary") - - - - with gr.TabItem("环境变量管理", id="env-settings"): + with gr.TabItem("环境变量管理", id="env-settings"): + with gr.Box(elem_classes="env-manager-container"): gr.Markdown(""" - ## 环境变量管理 - - 在此处设置模型API密钥和其他服务凭证。这些信息将保存在本地的`.env`文件中,确保您的API密钥安全存储且不会上传到网络。 - """) - - # 添加API密钥获取指南 - gr.Markdown("### API密钥获取指南") - - for key, info in API_HELP_INFO.items(): - with gr.Accordion(f"{info['name']} ({key})", open=False): - gr.Markdown(f""" - - **说明**: {info['desc']} - - **获取地址**: [{info['url']}]({info['url']}) - """) - - gr.Markdown("---") - - # 环境变量表格 - env_table = gr.Dataframe( - headers=["变量名", "值"], - datatype=["str", "str"], - row_count=10, - col_count=(2, "fixed"), - value=update_env_table, - label="当前环境变量", - interactive=False - ) - - with gr.Row(): - with gr.Column(scale=1): - new_env_key = gr.Textbox(label="变量名", placeholder="例如: OPENAI_API_KEY") - with gr.Column(scale=2): - new_env_value = gr.Textbox(label="值", placeholder="输入API密钥或其他配置值") - - with gr.Row(): - add_env_button = gr.Button("添加/更新变量", variant="primary") - refresh_button = gr.Button("刷新变量列表") - delete_env_button = gr.Button("删除选定变量", variant="stop") - - env_status = gr.Textbox(label="状态", interactive=False) - - # 变量选择器(用于删除) - env_var_to_delete = gr.Dropdown( - choices=[], - label="选择要删除的变量", - interactive=True - ) - - # 更新变量选择器的选项 - def update_delete_dropdown(): - env_vars = load_env_vars() - return gr.Dropdown.update(choices=list(env_vars.keys())) - - # 连接事件处理函数 - add_env_button.click( - fn=lambda k, v: add_env_var(k, v), - inputs=[new_env_key, new_env_value], - outputs=[env_status] - ).then( - fn=update_env_table, - outputs=[env_table] - ).then( - fn=update_delete_dropdown, - outputs=[env_var_to_delete] - ).then( - fn=lambda: ("", ""), # 修改为返回两个空字符串的元组 - outputs=[new_env_key, new_env_value] - ) - - refresh_button.click( - fn=update_env_table, - outputs=[env_table] - ).then( - fn=update_delete_dropdown, - outputs=[env_var_to_delete] - ) - - delete_env_button.click( - fn=lambda k: delete_env_var(k), - inputs=[env_var_to_delete], - outputs=[env_status] - ).then( - fn=update_env_table, - outputs=[env_table] - ).then( - fn=update_delete_dropdown, - outputs=[env_var_to_delete] - ) + ## 环境变量管理 - - - - - # 示例问题 - examples = [ - "打开百度搜索,总结一下camel-ai的camel框架的github star、fork数目等,并把数字用plot包写成python文件保存到本地,用本地终端执行python文件显示图出来给我", - "请分析GitHub上CAMEL-AI项目的最新统计数据。找出该项目的星标数量、贡献者名称,把内容整理成一个markdown文件保存到本地", - "浏览亚马逊并找出一款对程序员有吸引力的产品。请提供产品名称和价格", - "写一个hello world的python文件,保存到本地", - - ] - - gr.Examples( - examples=examples, - inputs=question_input - ) - + 在此处设置模型API密钥和其他服务凭证。这些信息将保存在本地的`.env`文件中,确保您的API密钥安全存储且不会上传到网络。 + """) + # 主要内容分为两列布局 + with gr.Row(): + # 左侧列:环境变量管理控件 + with gr.Column(scale=3): + with gr.Box(elem_classes="env-controls"): + # 环境变量表格 - 设置为可交互以直接编辑 + gr.Markdown("### 环境变量管理") + gr.Markdown(""" + 管理您的API密钥和其他环境变量。正确设置API密钥对于OWL系统的功能至关重要。 + +
+ 提示: 请确保正确设置API密钥以确保系统功能正常 +
+ """) + # 增强版环境变量表格,支持添加和删除行 + env_table = gr.Dataframe( + headers=["变量名", "值", "获取指南"], + datatype=[ + "str", + "str", + "html", + ], # 将最后一列设置为html类型以支持链接 + row_count=10, # 增加行数,以便添加新变量 + col_count=(3, "fixed"), + value=update_env_table, + label="API密钥和环境变量", + interactive=True, # 设置为可交互,允许直接编辑 + elem_classes="env-table", + ) - - gr.HTML(""" + # 操作说明 + gr.Markdown( + """ +
+ 操作指南: + + 注意: 所有API密钥都安全地存储在本地,不会上传到网络 +
+ """, + elem_classes="env-instructions", + ) + + # 环境变量操作按钮 + with gr.Row(elem_classes="env-buttons"): + save_env_button = gr.Button( + "💾 保存更改", + variant="primary", + elem_classes="env-button", + ) + refresh_button = gr.Button( + "🔄 刷新列表", elem_classes="env-button" + ) + + # 状态显示 + env_status = gr.HTML( + label="操作状态", + value="", + elem_classes="env-status", + ) + + # 连接事件处理函数 + save_env_button.click( + fn=save_env_table_changes, + inputs=[env_table], + outputs=[env_status], + ).then(fn=update_env_table, outputs=[env_table]) + + refresh_button.click(fn=update_env_table, outputs=[env_table]) + + # 示例问题 + examples = [ + "打开百度搜索,总结一下camel-ai的camel框架的github star、fork数目等,并把数字用plot包写成python文件保存到本地,用本地终端执行python文件显示图出来给我", + "浏览亚马逊并找出一款对程序员有吸引力的产品。请提供产品名称和价格", + "写一个hello world的python文件,保存到本地", + ] + + gr.Examples(examples=examples, inputs=question_input) + + gr.HTML(""" """) - - # 设置事件处理 - run_button.click( - fn=process_with_live_logs, - inputs=[question_input, module_dropdown], - outputs=[answer_output, token_count_output, status_output, log_display, log_display2] - ) - - # 模块选择更新描述 - module_dropdown.change( - fn=update_module_description, - inputs=module_dropdown, - outputs=module_description - ) - - # 日志相关事件处理 - refresh_logs_button.click( - fn=update_logs, - outputs=[log_display] - ) - - refresh_logs_button2.click( - fn=update_logs2, - outputs=[log_display2] - ) - - clear_logs_button.click( - fn=clear_log_file, - outputs=[log_display] - ) - - clear_logs_button2.click( - fn=clear_log_file, - outputs=[log_display2] - ) - - # 自动刷新控制 - def toggle_auto_refresh(enabled): - if enabled: - return gr.update(every=3) - else: - return gr.update(every=0) - - auto_refresh_checkbox.change( - fn=toggle_auto_refresh, - inputs=[auto_refresh_checkbox], - outputs=[log_display] - ) - - auto_refresh_checkbox2.change( - fn=toggle_auto_refresh, - inputs=[auto_refresh_checkbox2], - outputs=[log_display2] - ) - - # 设置自动刷新(默认每3秒刷新一次) - if auto_refresh_checkbox.value: - app.load( - fn=update_logs, - outputs=[log_display], - every=2 - ) - - if auto_refresh_checkbox2.value: - app.load( - fn=update_logs2, - outputs=[log_display2], - every=2 - ) - + + # 设置事件处理 + run_button.click( + fn=process_with_live_logs, + inputs=[question_input, module_dropdown], + outputs=[token_count_output, status_output, log_display2], + ) + + # 模块选择更新描述 + module_dropdown.change( + fn=update_module_description, + inputs=module_dropdown, + outputs=module_description, + ) + + # 对话记录相关事件处理 + refresh_logs_button2.click( + fn=lambda: get_latest_logs(100, LOG_QUEUE), outputs=[log_display2] + ) + + clear_logs_button2.click(fn=clear_log_file, outputs=[log_display2]) + + # 自动刷新控制 + def toggle_auto_refresh(enabled): + if enabled: + return gr.update(every=3) + else: + return gr.update(every=0) + + auto_refresh_checkbox2.change( + fn=toggle_auto_refresh, + inputs=[auto_refresh_checkbox2], + outputs=[log_display2], + ) + + # 不再默认自动刷新日志 + return app + # 主函数 def main(): try: @@ -952,35 +1283,40 @@ def main(): global LOG_FILE LOG_FILE = setup_logging() logging.info("OWL Web应用程序启动") - + # 启动日志读取线程 - log_thread = threading.Thread(target=log_reader_thread, args=(LOG_FILE,), daemon=True) + log_thread = threading.Thread( + target=log_reader_thread, args=(LOG_FILE,), daemon=True + ) log_thread.start() logging.info("日志读取线程已启动") - + # 初始化.env文件(如果不存在) init_env_file() app = create_ui() - + # 注册应用关闭时的清理函数 def cleanup(): global STOP_LOG_THREAD, STOP_REQUESTED STOP_LOG_THREAD.set() STOP_REQUESTED.set() logging.info("应用程序关闭,停止日志线程") - app.queue() - app.launch(share=False,server_name="127.0.0.1",server_port=7860) + + app.queue() + app.launch(share=False, server_name="127.0.0.1", server_port=7860) except Exception as e: logging.error(f"启动应用程序时发生错误: {str(e)}") print(f"启动应用程序时发生错误: {str(e)}") import traceback + traceback.print_exc() - + finally: # 确保日志线程停止 STOP_LOG_THREAD.set() STOP_REQUESTED.set() logging.info("应用程序关闭") + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pyproject.toml b/pyproject.toml index 2fa6908..b69c1f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ keywords = [ "learning-systems" ] dependencies = [ - "camel-ai[all]==0.2.27", + "camel-ai[all]==0.2.30", "chunkr-ai>=0.0.41", "docx2markdown>=0.1.1", "gradio>=3.50.2", diff --git a/requirements.txt b/requirements.txt index d73c9c6..056bbac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -camel-ai[all]==0.2.27 +camel-ai[all]==0.2.30 chunkr-ai>=0.0.41 docx2markdown>=0.1.1 gradio>=3.50.2 diff --git a/uv.lock b/uv.lock index 5bb2dee..d0990b6 100644 --- a/uv.lock +++ b/uv.lock @@ -482,7 +482,7 @@ wheels = [ [[package]] name = "camel-ai" -version = "0.2.27" +version = "0.2.30" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, @@ -499,9 +499,9 @@ dependencies = [ { name = "pyyaml" }, { name = "tiktoken" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/27/2bce666ae7f7d0db276d037b3afe84a460e782438e5cacc08de20417233b/camel_ai-0.2.27.tar.gz", hash = "sha256:4689245ad48f51e5e602d2651cf463afe212bcf046633a19c2189574c1f3481a", size = 441363 } +sdist = { url = "https://files.pythonhosted.org/packages/ef/86/57cbcae86d2d60dab0aad31b5302525c75f45ff5edc3c3819a378fa9e12c/camel_ai-0.2.30.tar.gz", hash = "sha256:e1639376e70e9cf1477eca88d1bdc1813855cbd1db683528e1f93027b6aa0b0a", size = 442842 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/fa/94f5b41cb6babc81aac00494b170ec2bea058b6c00f477ceb3e886c49177/camel_ai-0.2.27-py3-none-any.whl", hash = "sha256:c4a6597791faf2f2161c56c2579e60850557b126135b29af77ebd08fa0774e0b", size = 746387 }, + { url = "https://files.pythonhosted.org/packages/85/fe/8f1d17896aedbc9e0dfa1bff40d560e5a6808d9b727e04c293be6be5954f/camel_ai-0.2.30-py3-none-any.whl", hash = "sha256:e09eec860331cdb4da4e49f46f5d45345a81820c5847556fdf9e7827dd9bbfa9", size = 752672 }, ] [package.optional-dependencies] @@ -3575,7 +3575,7 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "camel-ai", extras = ["all"], specifier = "==0.2.27" }, + { name = "camel-ai", extras = ["all"], specifier = "==0.2.30" }, { name = "chunkr-ai", specifier = ">=0.0.41" }, { name = "docx2markdown", specifier = ">=0.1.1" }, { name = "gradio", specifier = ">=3.50.2" },