diff --git a/.gitignore b/.gitignore index e03eab3d3..d01469a36 100644 --- a/.gitignore +++ b/.gitignore @@ -148,6 +148,9 @@ allure-results .DS_Store .vscode +# Config +config/config.yaml + log.txt docs/scripts/set_env.sh key.yaml diff --git a/config/config.yaml b/config/config.yaml index bed67083c..694251f17 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -5,7 +5,7 @@ ## The official OPENAI_API_BASE is https://api.openai.com/v1 ## If the official OPENAI_API_BASE is not available, we recommend using the [openai-forward](https://github.com/beidongjiedeguang/openai-forward). ## Or, you can configure OPENAI_PROXY to access official OPENAI_API_BASE. -OPENAI_API_BASE: "https://api.openai.com/v1" +#OPENAI_API_BASE: "https://api.openai.com/v1" #OPENAI_PROXY: "http://127.0.0.1:8118" #OPENAI_API_KEY: "YOUR_API_KEY" # set the value to sk-xxx if you host the openai interface for open llm model OPENAI_API_MODEL: "gpt-4" @@ -24,12 +24,13 @@ RPM: 10 #### if AZURE, check https://github.com/openai/openai-cookbook/blob/main/examples/azure/chat.ipynb #### You can use ENGINE or DEPLOYMENT mode -#OPENAI_API_TYPE: "azure" -#OPENAI_API_BASE: "YOUR_AZURE_ENDPOINT" -#OPENAI_API_KEY: "YOUR_AZURE_API_KEY" -#OPENAI_API_VERSION: "YOUR_AZURE_API_VERSION" -#DEPLOYMENT_NAME: "YOUR_DEPLOYMENT_NAME" -#DEPLOYMENT_ID: "YOUR_DEPLOYMENT_ID" +OPENAI_API_TYPE: "azure" +OPENAI_API_BASE: "https://deepwisdom.openai.azure.com/" +OPENAI_API_KEY: "02ae6058d09849c691176befeae2107c" +#OPENAI_API_VERSION: "2023-05-15" +OPENAI_API_VERSION: "2023-07-01-preview" +DEPLOYMENT_ID: "GPT-4" +OPENAI_API_ENGINE: "gpt-4" #### if zhipuai from `https://open.bigmodel.cn`. You can set here or export API_KEY="YOUR_API_KEY" # ZHIPUAI_API_KEY: "YOUR_API_KEY" diff --git a/metagpt/actions/write_analysis_code.py b/metagpt/actions/write_analysis_code.py index 71467edd0..957d35f7e 100644 --- a/metagpt/actions/write_analysis_code.py +++ b/metagpt/actions/write_analysis_code.py @@ -16,10 +16,14 @@ from metagpt.prompts.ml_engineer import ( ML_SPECIFIC_PROMPT, ML_MODULE_MAP, TOOL_OUTPUT_DESC, + TOOL_USAGE_PROMPT, ) from metagpt.schema import Message, Plan from metagpt.tools.functions import registry from metagpt.utils.common import create_func_config +from metagpt.prompts.ml_engineer import GEN_DATA_DESC_PROMPT, GENERATE_CODE_PROMPT +from metagpt.utils.common import CodeParser +from metagpt.actions.execute_code import ExecutePyCode class BaseWriteAnalysisCode(Action): @@ -47,13 +51,13 @@ class BaseWriteAnalysisCode(Action): # 添加默认的提示词 if ( - default_system_msg not in messages[0]["content"] - and messages[0]["role"] != "system" + default_system_msg not in messages[0]["content"] + and messages[0]["role"] != "system" ): messages.insert(0, {"role": "system", "content": default_system_msg}) elif ( - default_system_msg not in messages[0]["content"] - and messages[0]["role"] == "system" + default_system_msg not in messages[0]["content"] + and messages[0]["role"] == "system" ): messages[0] = { "role": "system", @@ -62,7 +66,7 @@ class BaseWriteAnalysisCode(Action): return messages async def run( - self, context: List[Message], plan: Plan = None, code_steps: str = "" + self, context: List[Message], plan: Plan = None, code_steps: str = "" ) -> str: """Run of a code writing action, used in data analysis or modeling @@ -83,12 +87,12 @@ class WriteCodeByGenerate(BaseWriteAnalysisCode): super().__init__(name, context, llm) async def run( - self, - context: [List[Message]], - plan: Plan = None, - code_steps: str = "", - system_msg: str = None, - **kwargs, + self, + context: [List[Message]], + plan: Plan = None, + code_steps: str = "", + system_msg: str = None, + **kwargs, ) -> str: context.append(Message(content=self.REUSE_CODE_INSTRUCTION, role="user")) prompt = self.process_msg(context, system_msg) @@ -98,6 +102,7 @@ class WriteCodeByGenerate(BaseWriteAnalysisCode): class WriteCodeWithTools(BaseWriteAnalysisCode): """Write code with help of local available tools. Choose tools first, then generate code to use the tools""" + execute_code = ExecutePyCode() @staticmethod def _parse_recommend_tools(module: str, recommend_tools: list) -> List[Dict]: @@ -121,10 +126,10 @@ class WriteCodeWithTools(BaseWriteAnalysisCode): return tool_catalog async def _tool_recommendation( - self, - context: [List[Message]], - code_steps: str, - available_tools: list + self, + context: [List[Message]], + code_steps: str, + available_tools: list ) -> list: """ Recommend tools for the specified task. @@ -148,15 +153,28 @@ class WriteCodeWithTools(BaseWriteAnalysisCode): recommend_tools = rsp["recommend_tools"] return recommend_tools + async def run( - self, - context: List[Message], - plan: Plan = None, - code_steps: str = "", + self, + context: List[Message], + plan: Plan = None, + code_steps: str = "", + **kwargs, ) -> str: task_type = plan.current_task.task_type + logger.info(f"task_type is: {task_type}") available_tools = registry.get_all_schema_by_module(task_type) - special_prompt = ML_SPECIFIC_PROMPT.get(task_type, "") + + # special_prompt = ML_SPECIFIC_PROMPT.get(task_type, "") + + finished_tasks = plan.get_finished_tasks() + code_context = [task.code for task in finished_tasks] + + code_context = "\n\n".join(code_context) + + ### add runtime info + result, success = await self.execute_code.run(code_context) + logger.info(result) if len(available_tools) > 0: available_tools = [ @@ -164,25 +182,46 @@ class WriteCodeWithTools(BaseWriteAnalysisCode): for tool in available_tools ] + final_code = code_context + recommend_tools = await self._tool_recommendation(context, code_steps, available_tools) tool_catalog = self._parse_recommend_tools(task_type, recommend_tools) logger.info(f"Recommended tools: \n{recommend_tools}") module_name = ML_MODULE_MAP[task_type] output_desc = TOOL_OUTPUT_DESC.get(task_type, "") - prompt = TOO_ORGANIZATION_PROMPT.format( - special_prompt=special_prompt, + + hist_info = f"Previous finished code is \n\n ```Python {final_code} ``` \n\n " \ + f"Runtime result is {result} \n\n" + + prompt = TOOL_USAGE_PROMPT.format( + goal=plan.current_task.instruction, + context=hist_info, code_steps=code_steps, module_name=module_name, output_desc=output_desc, function_catalog=tool_catalog, ) - context.append(Message(content=prompt, role="user")) - else: - context.append(Message(content=self.REUSE_CODE_INSTRUCTION, role="user")) - context.append(Message(content=special_prompt, role="user")) - prompt = self.process_msg(context) - tool_config = create_func_config(CODE_GENERATOR_WITH_TOOLS) - rsp = await self.llm.aask_code(prompt, **tool_config) - return rsp["code"] + tool_config = create_func_config(CODE_GENERATOR_WITH_TOOLS) + + rsp = await self.llm.aask_code(prompt, **tool_config) + logger.info(f"rsp is: {rsp}") + final_code = final_code + "\n\n" + rsp["code"] + + return final_code + + else: + hist_info = f"Previous finished code is \n\n ```Python {code_context} ``` \n\n " \ + f"runtime result is {result} \n\n" + + prompt = GENERATE_CODE_PROMPT.format( + goal=plan.current_task.instruction, + context=hist_info, + ) + + tool_config = create_func_config(CODE_GENERATOR_WITH_TOOLS) + logger.info(f"prompt is: {prompt}") + rsp = await self.llm.aask_code(prompt, **tool_config) + logger.info(f"rsp is: {rsp}") + return rsp["code"] diff --git a/metagpt/prompts/ml_engineer.py b/metagpt/prompts/ml_engineer.py index d568bdd1f..b68dadc9a 100644 --- a/metagpt/prompts/ml_engineer.py +++ b/metagpt/prompts/ml_engineer.py @@ -23,7 +23,6 @@ Output the information in a JSON format, as shown in this example: ``` """ - ASSIGN_TASK_TYPE_PROMPT = """ Please assign a task type to each task in the list below from the given categories: {task_list} @@ -53,7 +52,6 @@ ASSIGN_TASK_TYPE = { }, } - TOOL_RECOMMENDATION_PROMPT = """ Your are a tool recommender, the main goal is to recommend suitable tools for current task before coding. A tool means a function that can be used to help you solve the task. @@ -88,7 +86,6 @@ SELECT_FUNCTION_TOOLS = { }, } - CODE_GENERATOR_WITH_TOOLS = { "name": "add_subtask_code", "description": "Add new code cell of current task to the end of an active Jupyter notebook.", @@ -104,6 +101,54 @@ CODE_GENERATOR_WITH_TOOLS = { }, } +TOOL_USAGE_PROMPT = """ +## Target +{goal} + +## History Info +{context} + +## Available Tools: +Each function is described in JSON format, including the function name and parameters. {output_desc} +{function_catalog} + +When you call a function above, you should import the function from `{module_name}` first, e.g.: +```python +from metagpt.tools.functions.libs.data_preprocess import fill_missing_value +```end + +## Your Output Format: +Generate the complete code for this task: +```python +# Tools used: [function names or 'none'] + +```end + +## Attention: +Make sure use the columns from the dataset columns +Finish your coding tasks as a helpful programmer based on the tools. + +""" +GENERATE_CODE_PROMPT = """ +## Target +{goal} + +## History Info +{context} + +## Your Output Format: +Generate the complete code for this task: +```python +# Tools used: [function names or 'none'] + +```end + +## Attention: +Make sure use the columns from the dataset columns +Finish your coding tasks as a helpful programmer based on the tools. + +""" + TOO_ORGANIZATION_PROMPT = """ The previous conversation has provided all tasks step-by-step for the use goal and their statuses. Now, begin writing code for the current task. This code should writen strictly on the basis of all previous completed tasks code, not a standalone code. And avoid writing duplicate code that has already been written in previous tasks, such as repeated import of packages, reading data, etc. @@ -167,7 +212,6 @@ CLASSIFICATION_MODEL_OUTPUT_DESC = "" REGRESSION_MODEL_OUTPUT_DESC = "" - ML_SPECIFIC_PROMPT = { "data_preprocess": DATA_PREPROCESS_PROMPT, "feature_engineering": FEATURE_ENGINEERING_PROMPT, diff --git a/metagpt/roles/ml_engineer.py b/metagpt/roles/ml_engineer.py index c2841be4c..deb76f0a9 100644 --- a/metagpt/roles/ml_engineer.py +++ b/metagpt/roles/ml_engineer.py @@ -159,6 +159,7 @@ class MLEngineer(Role): if self.data_path: self.data_desc = await self._generate_data_desc() + # create initial plan and update until confirmation await self._update_plan() @@ -200,12 +201,10 @@ class MLEngineer(Role): success = False while not success and counter < max_retry: context = self.get_useful_memories() - - # print("*" * 10) - # print(context) - # print("*" * 10) # breakpoint() + column_names_dict = {key: value["column_info"] for key,value in self.data_desc.items()} + if not self.use_tools or self.plan.current_task.task_type == "other": logger.info("Write code with pure generation") # code = "print('abc')" @@ -215,8 +214,9 @@ class MLEngineer(Role): cause_by = WriteCodeByGenerate else: logger.info("Write code with tools") + code = await WriteCodeWithTools().run( - context=context, plan=self.plan, code_steps=code_steps, + context=context, plan=self.plan, code_steps=code_steps, **{"column_names": column_names_dict} ) cause_by = WriteCodeWithTools @@ -296,8 +296,10 @@ if __name__ == "__main__": # requirement = "Run data analysis on sklearn Wisconsin Breast Cancer dataset, include a plot, train a model to predict targets (20% as validation), and show validation accuracy" # requirement = "Run EDA and visualization on this dataset, train a model to predict survival, report metrics on validation set (20%), dataset: workspace/titanic/train.csv" + from metagpt.const import DATA_PATH + requirement = "Perform data analysis on the provided data. Train a model to predict the target variable Survived. Include data preprocessing, feature engineering, and modeling in your pipeline. The metric is accuracy." - data_path = "/data/lidanyang/tabular_data/titanic" + data_path = f"{DATA_PATH}/titanic" async def main(requirement: str = requirement, auto_run: bool = True, data_path: str = data_path): role = MLEngineer(goal=requirement, auto_run=auto_run, data_path=data_path) diff --git a/metagpt/tools/web_browser_engine.py b/metagpt/tools/web_browser_engine.py index 453d87f31..7228ae9cf 100644 --- a/metagpt/tools/web_browser_engine.py +++ b/metagpt/tools/web_browser_engine.py @@ -7,7 +7,7 @@ from typing import Any, Callable, Coroutine, Literal, overload from metagpt.config import CONFIG from metagpt.tools import WebBrowserEngineType -from metagpt.utils.parse_html import WebPage +# from metagpt.utils.parse_html import WebPage class WebBrowserEngine: diff --git a/metagpt/utils/__init__.py b/metagpt/utils/__init__.py index f13175cf8..86cac50db 100644 --- a/metagpt/utils/__init__.py +++ b/metagpt/utils/__init__.py @@ -6,7 +6,7 @@ @File : __init__.py """ -from metagpt.utils.read_document import read_docx +# from metagpt.utils.read_document import read_docx from metagpt.utils.singleton import Singleton from metagpt.utils.token_counter import ( TOKEN_COSTS, @@ -16,7 +16,7 @@ from metagpt.utils.token_counter import ( __all__ = [ - "read_docx", + # "read_docx", "Singleton", "TOKEN_COSTS", "count_message_tokens", diff --git a/requirements.txt b/requirements.txt index 1d1bc95a1..9b75fd200 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,7 +35,6 @@ tqdm==4.64.0 # webdriver_manager<3.9 anthropic==0.3.6 typing-inspect==0.8.0 -typing_extensions==4.5.0 libcst==1.0.1 qdrant-client==1.4.0 pytest-mock==3.11.1 @@ -46,7 +45,6 @@ wrapt==1.15.0 websocket-client==0.58.0 zhipuai==1.0.7 rich==13.6.0 -nbclient==0.9.0 nbformat==5.9.2 ipython==8.17.2 ipykernel==6.27.0