diff --git a/examples/run.py b/examples/run.py index d98d721d9..52c55423b 100644 --- a/examples/run.py +++ b/examples/run.py @@ -145,4 +145,4 @@ def main(): if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/examples/run_human_control.py b/examples/run_human_control.py new file mode 100644 index 000000000..b84a35ad1 --- /dev/null +++ b/examples/run_human_control.py @@ -0,0 +1,152 @@ +# ========= 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 sys +import pathlib +from dotenv import load_dotenv +from camel.models import ModelFactory +from camel.toolkits import ( + AudioAnalysisToolkit, + CodeExecutionToolkit, + ExcelToolkit, + ImageAnalysisToolkit, + SearchToolkit, + VideoAnalysisToolkit, + BrowserToolkit, + FileWriteToolkit, + HumanToolkit, + TerminalToolkit, +) +from camel.types import ModelPlatformType, ModelType +from camel.logger import set_log_level +from camel.societies import RolePlaying + +from owl.utils import run_society, DocumentProcessingToolkit + +base_dir = pathlib.Path(__file__).parent.parent +env_path = base_dir / "owl" / ".env" +load_dotenv(dotenv_path=str(env_path)) + +set_log_level(level="DEBUG") + + +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: + RolePlaying: A configured society of agents ready to address the question. + """ + + # Create models for different components + models = { + "user": ModelFactory.create( + model_platform=ModelPlatformType.OPENAI, + model_type=ModelType.GPT_4O, + model_config_dict={"temperature": 0}, + ), + "assistant": ModelFactory.create( + model_platform=ModelPlatformType.OPENAI, + model_type=ModelType.GPT_4O, + model_config_dict={"temperature": 0}, + ), + "browsing": ModelFactory.create( + model_platform=ModelPlatformType.OPENAI, + model_type=ModelType.GPT_4O, + model_config_dict={"temperature": 0}, + ), + "planning": ModelFactory.create( + model_platform=ModelPlatformType.OPENAI, + model_type=ModelType.GPT_4O, + model_config_dict={"temperature": 0}, + ), + "video": ModelFactory.create( + model_platform=ModelPlatformType.OPENAI, + model_type=ModelType.GPT_4O, + model_config_dict={"temperature": 0}, + ), + "image": ModelFactory.create( + model_platform=ModelPlatformType.OPENAI, + model_type=ModelType.GPT_4O, + model_config_dict={"temperature": 0}, + ), + "document": ModelFactory.create( + model_platform=ModelPlatformType.OPENAI, + model_type=ModelType.GPT_4O, + model_config_dict={"temperature": 0}, + ), + } + + # Configure toolkits + tools = [ + *HumanToolkit().get_tools(), + *TerminalToolkit().get_tools(), + *BrowserToolkit( + headless=False, # Set to True for headless mode (e.g., on remote servers) + web_agent_model=models["browsing"], + planning_agent_model=models["planning"], + ).get_tools(), + *VideoAnalysisToolkit(model=models["video"]).get_tools(), + *AudioAnalysisToolkit().get_tools(), # This requires OpenAI Key + *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(), + *ImageAnalysisToolkit(model=models["image"]).get_tools(), + SearchToolkit().search_duckduckgo, + SearchToolkit().search_google, # Comment this out if you don't have google search + SearchToolkit().search_wiki, + *ExcelToolkit().get_tools(), + *DocumentProcessingToolkit(model=models["document"]).get_tools(), + *FileWriteToolkit(output_dir="./").get_tools(), + ] + + # Configure agent roles and parameters + user_agent_kwargs = {"model": models["user"]} + assistant_agent_kwargs = {"model": models["assistant"], "tools": tools} + + # Configure task parameters + task_kwargs = { + "task_prompt": question, + "with_task_specify": False, + } + + # Create and return the society + society = RolePlaying( + **task_kwargs, + user_role_name="user", + user_agent_kwargs=user_agent_kwargs, + assistant_role_name="assistant", + assistant_agent_kwargs=assistant_agent_kwargs, + ) + + return society + + +def main(): + r"""Main function to run the OWL system with an example question.""" + # Default research question + default_task = "Use plot to draw a heart-shaped graph, save it locally, and ask for my opinion using HumanToolkit before taking each step." + + # Override default task if command line argument is provided + task = sys.argv[1] if len(sys.argv) > 1 else default_task + + # Construct and run the society + society = construct_society(task) + answer, chat_history, token_count = run_society(society) + + # Output the result + print(f"\033[94mAnswer: {answer}\033[0m") + + +if __name__ == "__main__": + main() diff --git a/owl/webapp_zh.py b/owl/webapp_zh.py index e55d9cb83..b0054c706 100644 --- a/owl/webapp_zh.py +++ b/owl/webapp_zh.py @@ -25,6 +25,7 @@ import threading import queue import re # For regular expression operations +import tempfile # 添加此行,用于处理临时文件 os.environ["PYTHONIOENCODING"] = "utf-8" @@ -309,12 +310,13 @@ def validate_input(question: str) -> bool: return True -def run_owl(question: str, example_module: str) -> Tuple[str, str, str]: +def run_owl(question: str, example_module: str, uploaded_file=None) -> Tuple[str, str, str]: """运行OWL系统并返回结果 Args: question: 用户问题 example_module: 要导入的示例模块名(如 "run_terminal_zh" 或 "run_deep") + uploaded_file: 上传的文件(可选)- 当使用type="binary"时,这是字节数据 Returns: Tuple[...]: 回答、令牌计数、状态 @@ -330,6 +332,73 @@ def run_owl(question: str, example_module: str) -> Tuple[str, str, str]: # 确保环境变量已加载 load_dotenv(find_dotenv(), override=True) logging.info(f"处理问题: '{question}', 使用模块: {example_module}") + + # 处理上传的文件 + if uploaded_file is not None: + try: + # 创建临时目录保存上传的文件 + tmp_dir = tempfile.mkdtemp(prefix="owl_upload_") + logging.info(f"创建临时目录: {tmp_dir}") + + # 生成一个唯一的文件名 + timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + + # 简单地根据文件内容的前几个字节来猜测文件类型 + file_ext = ".bin" # 默认扩展名 + + # 简单的文件类型检测,不依赖magic库 + if len(uploaded_file) > 4: + # PDF 文件通常以 %PDF 开头 + if uploaded_file[:4] == b'%PDF': + file_ext = ".pdf" + # ZIP 文件 (可能是DOCX, XLSX等) + elif uploaded_file[:4] == b'PK\x03\x04': + # 尝试根据文件名确定具体类型 + file_ext = ".zip" # 默认为zip + # 纯文本文件检测 (如果前100个字节都是可打印ASCII或常见Unicode) + elif all(c < 128 and c >= 32 or c in (9, 10, 13) for c in uploaded_file[:100]): + file_ext = ".txt" + + file_name = f"uploaded_file_{timestamp}{file_ext}" + + # 生成安全的文件路径 + file_path = os.path.normpath(os.path.join(tmp_dir, file_name)) + + logging.info(f"准备保存文件: {file_path}") + + # 保存文件内容 + try: + with open(file_path, "wb") as f: + f.write(uploaded_file) + + # 检查文件是否成功写入 + if not os.path.exists(file_path) or os.path.getsize(file_path) == 0: + raise ValueError("文件保存失败或文件为空") + + logging.info(f"文件已成功保存: {file_path}, 大小: {os.path.getsize(file_path)} 字节") + + # 将文件路径添加到问题中(使用规范化的路径,避免系统差异) + normalized_path = file_path.replace('\\', '/') + if "文件路径:" not in question and "file path:" not in question.lower(): + question = f"{question}\n文件路径: {normalized_path}" + + logging.info(f"更新后的问题: '{question}'") + + except Exception as e: + logging.error(f"保存文件时出错: {str(e)}") + return ( + f"保存文件时出错: {str(e)}", + "0", + f"❌ 错误: 文件保存失败 - {str(e)}", + ) + + except Exception as e: + logging.error(f"处理上传文件时出错: {str(e)}") + return ( + f"处理上传文件时出错: {str(e)}", + "0", + f"❌ 错误: 文件处理失败 - {str(e)}", + ) # 检查模块是否在MODULE_DESCRIPTIONS中 if example_module not in MODULE_DESCRIPTIONS: @@ -769,8 +838,7 @@ def clear_log_file(): logging.error(f"清空日志文件时出错: {str(e)}") return "" - # 创建一个实时日志更新函数 - def process_with_live_logs(question, module_name): + def process_with_live_logs(question, module_name, uploaded_file=None): """处理问题并实时更新日志""" global CURRENT_PROCESS @@ -782,7 +850,7 @@ def process_with_live_logs(question, module_name): def process_in_background(): try: - result = run_owl(question, module_name) + result = run_owl(question, module_name, uploaded_file) result_queue.put(result) except Exception as e: result_queue.put((f"发生错误: {str(e)}", "0", f"❌ 错误: {str(e)}")) @@ -1062,6 +1130,23 @@ def process_in_background(): value="打开百度搜索,总结一下camel-ai的camel框架的github star、fork数目等,并把数字用plot包写成python文件保存到本地,并运行生成的python文件。", ) + + file_upload = gr.File( + label="上传文件(可选)", + file_types=["pdf", "docx", "txt", "csv", "xlsx", "json", "py", "ipynb"], + file_count="single", + type="binary", # 使用binary类型 + ) + + # 文件上传说明 - 更新说明以包含Windows兼容性 + gr.Markdown(""" +
+ 文件上传说明: 上传文件后,系统会自动将文件保存到临时目录并添加文件路径到您的问题中。 + 支持多种文件格式,包括PDF、Word文档、文本文件、CSV、Excel、Python代码等。 +
注意: 文件会被保存在临时目录,系统重启后可能会被清除。 +
+ """) + # 增强版模块选择下拉菜单 # 只包含MODULE_DESCRIPTIONS中定义的模块 module_dropdown = gr.Dropdown( @@ -1205,10 +1290,10 @@ def process_in_background(): refresh_button.click(fn=update_env_table, outputs=[env_table]) - # 设置事件处理 + run_button.click( fn=process_with_live_logs, - inputs=[question_input, module_dropdown], + inputs=[question_input, module_dropdown, file_upload], outputs=[token_count_output, status_output, log_display2], )