mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-04-30 19:36:24 +02:00
Revert "Merge branch 'dynamic_think' into 'mgx_ops'"
This reverts merge request !98
This commit is contained in:
parent
476e6256a7
commit
de27855c56
8 changed files with 60 additions and 154 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
},
|
||||
]
|
||||
```
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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,
|
||||
Loading…
Add table
Add a link
Reference in a new issue