Revert "Merge branch 'dynamic_think' into 'mgx_ops'"

This reverts merge request !98
This commit is contained in:
林义章 2024-05-11 19:29:12 +08:00
parent 476e6256a7
commit de27855c56
8 changed files with 60 additions and 154 deletions

View file

@ -22,8 +22,7 @@ If plan is created, you should track the progress and update the plan accordingl
Pay close attention to new user message, review the conversation history, use reply_to_human to respond to new user requirement.
Note:
1. If you keeping encountering errors, unexpected situation, or you are not sure of proceeding, use ask_human to ask for help.
2. Carefully review your progress at the current task, if your actions so far has not fulfilled the task instruction, you should continue with current task. Otherwise, finish current task.
3. Each time you finish a task, use reply_to_human to report your progress.
2. Each time you finish a task, use reply_to_human to report your progress.
Pay close attention to the Example provided, you can reuse the example for your current situation if it fits.
You may use any of the available commands to create a plan or update the plan. You may output mutiple commands, they will be executed sequentially.

View file

@ -25,7 +25,6 @@ from metagpt.utils.common import CodeParser
class DataAnalyst(DataInterpreter):
name: str = "David"
profile: str = "DataAnalyst"
goal: str = "Take on any data-related tasks, such as data analysis, machine learning, deep learning, web browsing, web scraping, web searching, web deployment, terminal operation, git and github operation, etc."
react_mode: Literal["react"] = "react"
max_react_loop: int = 20 # used for react mode
task_result: TaskResult = None
@ -34,7 +33,6 @@ class DataAnalyst(DataInterpreter):
Command.RESET_TASK,
Command.REPLACE_TASK,
Command.FINISH_CURRENT_TASK,
Command.CONTINUE_WITH_CURRENT_TASK,
# Command.PUBLISH_MESSAGE,
Command.ASK_HUMAN,
Command.REPLY_TO_HUMAN,
@ -57,7 +55,7 @@ class DataAnalyst(DataInterpreter):
self._set_state(0)
# HACK: Init Planner, control it through dynamic thinking; Consider formalizing as a react mode
self.planner = Planner(goal="", working_memory=self.rc.working_memory, auto_run=True)
self.planner = Planner(goal=self.goal, working_memory=self.rc.working_memory, auto_run=True)
return self
@ -71,14 +69,11 @@ class DataAnalyst(DataInterpreter):
example = KeywordExpRetriever().retrieve(self.user_requirement)
else:
self.working_memory.add_batch(self.rc.news)
context = "\n\n".join([str(mem) for mem in self.working_memory.get()])
example = KeywordExpRetriever().retrieve(context)
plan_status = self.planner.plan.model_dump(include=["goal", "tasks"])
for task in plan_status["tasks"]:
task.pop("code")
task.pop("result")
task.pop("is_success")
prompt = CMD_PROMPT.format(
plan_status=plan_status,
example=example,
@ -101,8 +96,6 @@ class DataAnalyst(DataInterpreter):
self.planner.plan.current_task.is_success = (
is_success # mark is_success, determine is_finished later in thinking
)
# FIXME: task result is always overwritten by the last act, whereas it can be made of of multiple acts
self.task_result = TaskResult(code=code, result=result, is_success=is_success)
return Message(content="Task completed", role="assistant", sent_from=self._setting, cause_by=WriteAnalysisCode)

View file

@ -159,101 +159,44 @@ class SimpleExpRetriever(ExpRetriever):
class KeywordExpRetriever(ExpRetriever):
"""An experience retriever that returns examples based on keywords in the context."""
EXAMPLE: dict = {
"deploy": """
## example 1
User Requirement: launch a service from workspace/web_snake_game/web_snake_game, and deploy it to public
Explanation: Launching a service requires Terminal tool with daemon mode, write this into task instruction.
```json
[
{
"command_name": "append_task",
"args": {
"task_id": "1",
"dependent_task_ids": [],
"instruction": "Use the Terminal tool to launch the service in daemon mode",
"assignee": "David"
}
},
{
"command_name": "append_task",
"args": {
"task_id": "2",
"dependent_task_ids": ["1"],
"instruction": "Test the service with a simple request",
"assignee": "David"
}
},
{
"command_name": "append_task",
"args": {
"task_id": "3",
"dependent_task_ids": ["2"],
"instruction": "Deploy the service to public",
"assignee": "David"
}
},
"""
}
def retrieve(self, context: str) -> str:
if "deploy" in context.lower():
return DEPLOY_EXAMPLE
elif "issue" in context.lower():
return FIX_ISSUE_EXAMPLE
return self.EXAMPLE["deploy"]
return ""
DEPLOY_EXAMPLE = """
## example 1
User Requirement: launch a service from workspace/web_snake_game/web_snake_game, and deploy it to public
Explanation: Launching a service requires Terminal tool with daemon mode, write this into task instruction.
```json
[
{
"command_name": "append_task",
"args": {
"task_id": "1",
"dependent_task_ids": [],
"instruction": "Use the Terminal tool to launch the service in daemon mode",
"assignee": "David"
}
},
{
"command_name": "append_task",
"args": {
"task_id": "2",
"dependent_task_ids": ["1"],
"instruction": "Test the service with a simple request",
"assignee": "David"
}
},
{
"command_name": "append_task",
"args": {
"task_id": "3",
"dependent_task_ids": ["2"],
"instruction": "Deploy the service to public",
"assignee": "David"
}
},
"""
FIX_ISSUE_EXAMPLE = """
## example 1
User Requirement: Write a fix for this issue: https://github.com/xxx/xxx/issues/xxx, and commit and push your changes.
Explanation: The requirement is for software development, focusing on fixing an issue in an existing repository. The process is broken down into several steps, each demanding specific actions and tools.
```json
[
{
"command_name": "append_task",
"args": {
"task_id": "1",
"dependent_task_ids": [],
"instruction": "Read the issue description to understand the problem using the Browser tool.",
"assignee": "David"
}
},
{
"command_name": "append_task",
"args": {
"task_id": "2",
"dependent_task_ids": ["1"],
"instruction": "Clone the repository using the Terminal tool.",
"assignee": "David"
}
},
{
"command_name": "append_task",
"args": {
"task_id": "3",
"dependent_task_ids": ["2"],
"instruction": "Use Editor to search the relevant function(s), then diagnose and identify the source of the problem.",
"assignee": "David"
}
},
{
"command_name": "append_task",
"args": {
"task_id": "4",
"dependent_task_ids": ["3"],
"instruction": "Use Editor tool to fix the problem in the corresponding file(s).",
"assignee": "David"
}
},
{
"command_name": "append_task",
"args": {
"task_id": "5",
"dependent_task_ids": ["4"],
"instruction": "Commit, push the changes to the repository.",
"assignee": "David"
}
},
]
```
"""

View file

@ -36,12 +36,7 @@ class Command(Enum):
FINISH_CURRENT_TASK = CommandDef(
name="finish_current_task",
signature="finish_current_task()",
desc="Finishes current task, set Task.is_finished=True, set current task to next task. You should not finish current task if task instruction has not been fulfilled.",
)
CONTINUE_WITH_CURRENT_TASK = CommandDef(
name="continue_with_current_task",
signature="continue_with_current_task()",
desc="Continue with the current task, use this if you think you need more actions to achieve what is prescribed by the task instruction.",
desc="Finishes current task, set Task.is_finished=True, set current task to next task",
)
# commands for env interaction
@ -101,13 +96,7 @@ def run_plan_command(role: Role, cmd: list[dict]):
elif cmd["command_name"] == Command.RESET_TASK.cmd_name:
role.planner.plan.reset_task(**cmd["args"])
elif cmd["command_name"] == Command.REPLACE_TASK.cmd_name:
new_task = Task(
task_id=cmd["args"]["task_id"],
dependent_task_ids=cmd["args"]["new_dependent_task_ids"],
instruction=cmd["args"]["new_instruction"],
assignee=cmd["args"]["new_assignee"],
)
role.planner.plan.replace_task(new_task)
role.planner.plan.replace_task(Task(**cmd["args"]))
elif cmd["command_name"] == Command.FINISH_CURRENT_TASK.cmd_name:
if role.planner.plan.is_plan_finished():
return

View file

@ -12,7 +12,7 @@ from metagpt.tools.libs import (
# web_scraping,
# email_login,
terminal,
editor,
file_manager,
browser,
deployer,
git,
@ -27,7 +27,7 @@ _ = (
# web_scraping,
# email_login,
terminal,
editor,
file_manager,
browser,
deployer,
git,

View file

@ -1,5 +1,3 @@
from __future__ import annotations
from playwright.async_api import async_playwright
from metagpt.const import DEFAULT_WORKSPACE_ROOT
@ -7,10 +5,10 @@ from metagpt.tools.tool_registry import register_tool
from metagpt.utils.report import BrowserReporter
@register_tool(tags=["web", "browse", "scrape"])
@register_tool()
class Browser:
"""
A tool for browsing the web and scraping. Don't initialize a new instance of this class if one already exists.
A tool for browsing the web. Don't initialize a new instance of this class if one already exists.
Note: Combine searching and scrolling together to achieve most effective browsing. DON'T stick to one method.
"""
@ -33,7 +31,7 @@ class Browser:
self.current_page = page
self.current_page_url = url
print("Now on page ", url)
await self._view()
print(await self._view())
async def open_new_page(self, url: str):
"""open a new page in the browser and view the page"""
@ -53,12 +51,6 @@ class Browser:
else:
print(f"Page not found: {url}")
async def _view_page_html(self, keep_len: int = 5000) -> str:
"""view the HTML content of current page, return the HTML content as a string. When executed, the content will be printed out"""
html = await self.current_page.content()
html_content = html.strip()[:keep_len]
return html_content
async def search_content_all(self, search_term: str) -> list[dict]:
"""search all occurences of search term in the current page and return the search results with their position.
Useful if you have a keyword or sentence in mind and want to quickly narrow down the content relevant to it.
@ -150,12 +142,10 @@ class Browser:
await self.current_page.screenshot(path=path)
print(f"Screenshot saved to: {path}")
async def _view(self, keep_len: int = 5000) -> str:
async def _view(self) -> str:
"""simulate human viewing the current page, return the visible text with links"""
visible_text_with_links = await self.current_page.evaluate(VIEW_CONTENT_JS)
print("The visible text and their links (if any): ", visible_text_with_links[:keep_len])
# html_content = await self._view_page_html(keep_len=keep_len)
# print("The html content: ", html_content)
return visible_text_with_links
async def scroll_current_page(self, offset: int = 500):
"""scroll the current page by offset pixels, negative value means scrolling up, will print out observed content after scrolling"""

View file

@ -20,7 +20,7 @@ class FileBlock(BaseModel):
@register_tool()
class Editor:
class FileManager:
"""A tool for reading, understanding, writing, and editing files"""
def __init__(self) -> None:
@ -42,8 +42,6 @@ class Editor:
"""
Search symbol in all files under root_path, return the context of symbol with window size
Useful for locating class or function in a large codebase. Example symbol can be "def some_function", "class SomeClass", etc.
When both search_content and find_file are feasible, search_content is used in priority.
In searching, attempt different symbols of different granualities, e.g. "def some_function", "class SomeClass", a certain line of code, etc.
Args:
symbol (str): The symbol to search.
@ -60,9 +58,6 @@ class Editor:
symbol: str = Field(default="", description="The symbol of interest in the block, empty if not applicable.")
symbol_line: int = Field(default=-1, description="The line number of the symbol in the file, -1 if not applicable")
"""
if not os.path.exists(root_path):
print(f"Currently at {os.getcwd()}. Path {root_path} does not exist.")
return None
for root, _, files in os.walk(root_path or "."):
for file in files:
file_path = os.path.join(root, file)
@ -88,9 +83,6 @@ class Editor:
)
self.resource.report(result.file_path, "path")
return result
print(
"symbol not found, you may try searching another one, or break down your search term to search a part of it"
)
return None
def write_content(self, file_path: str, start_line: int, end_line: int, new_block_content: str = "") -> str:
@ -120,8 +112,8 @@ class Editor:
# Lint the modified temporary file
lint_passed, lint_message = self._lint_file(temp_file_path)
# if not lint_passed:
# return f"Linting the content at a temp file, failed with:\n{lint_message}"
if not lint_passed:
return f"Linting the content at a temp file, failed with:\n{lint_message}"
# If linting passes, overwrite the original file with the temporary file
shutil.move(temp_file_path, file_path)

View file

@ -1,7 +1,7 @@
import pytest
from metagpt.const import TEST_DATA_PATH
from metagpt.tools.libs.editor import Editor, FileBlock
from metagpt.tools.libs.file_manager import FileBlock, FileManager
TEST_FILE_CONTENT = """
# this is line one
@ -13,7 +13,7 @@ def test_function_for_fm():
# this is the 7th line
""".strip()
TEST_FILE_PATH = TEST_DATA_PATH / "tools/test_script_for_editor.py"
TEST_FILE_PATH = TEST_DATA_PATH / "tools/test_script_for_file_manager.py"
@pytest.fixture
@ -36,7 +36,7 @@ EXPECTED_SEARCHED_BLOCK = FileBlock(
def test_search_content(test_file):
block = Editor().search_content("def test_function_for_fm", root_path=TEST_DATA_PATH, window=3)
block = FileManager().search_content("def test_function_for_fm", root_path=TEST_DATA_PATH, window=3)
assert block == EXPECTED_SEARCHED_BLOCK
@ -51,7 +51,7 @@ def test_function_for_fm():
def test_replace_content(test_file):
Editor().write_content(
FileManager().write_content(
file_path=str(TEST_FILE_PATH),
start_line=3,
end_line=5,
@ -71,7 +71,7 @@ def test_function_for_fm():
def test_delete_content(test_file):
Editor().write_content(file_path=str(TEST_FILE_PATH), start_line=3, end_line=5)
FileManager().write_content(file_path=str(TEST_FILE_PATH), start_line=3, end_line=5)
with open(TEST_FILE_PATH, "r") as f:
new_content = f.read()
assert new_content == EXPECTED_CONTENT_AFTER_DELETE
@ -90,7 +90,7 @@ def test_function_for_fm():
def test_insert_content(test_file):
Editor().write_content(
FileManager().write_content(
file_path=str(TEST_FILE_PATH),
start_line=3,
end_line=-1,
@ -102,7 +102,7 @@ def test_insert_content(test_file):
def test_new_content_wrong_indentation(test_file):
msg = Editor().write_content(
msg = FileManager().write_content(
file_path=str(TEST_FILE_PATH),
start_line=3,
end_line=-1,
@ -112,7 +112,7 @@ def test_new_content_wrong_indentation(test_file):
def test_new_content_format_issue(test_file):
msg = Editor().write_content(
msg = FileManager().write_content(
file_path=str(TEST_FILE_PATH),
start_line=3,
end_line=-1,