mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-04-30 19:36:24 +02:00
Merge branch 'editor_fix_issue' into 'mgx_ops'
Add issue fixing ability See merge request pub/MetaGPT!101
This commit is contained in:
commit
97034e00b5
14 changed files with 228 additions and 133 deletions
|
|
@ -9,8 +9,8 @@ from metagpt.roles.di.data_interpreter import DataInterpreter
|
|||
|
||||
REQ = """
|
||||
# Requirement
|
||||
Below is a github issue, solve it. Use FileManager to search for the function, understand it, and modify the relevant code.
|
||||
Write a new test file test.py with FileManager and use Terminal to python the test file to ensure you have fixed the issue.
|
||||
Below is a github issue, solve it. Use Editor to search for the function, understand it, and modify the relevant code.
|
||||
Write a new test file test.py with Editor and use Terminal to python the test file to ensure you have fixed the issue.
|
||||
When writing test.py, you should import the function from the file you modified and test it with the given input.
|
||||
Notice: Don't write all codes in one response, each time, just write code for one step.
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ resulted in an infinite loop for the react mode.
|
|||
|
||||
|
||||
async def main():
|
||||
di = DataInterpreter(tools=["Terminal", "FileManager"], react_mode="react")
|
||||
di = DataInterpreter(tools=["Terminal", "Editor"], react_mode="react")
|
||||
await di.run(REQ)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ Notice: Don't write all codes in one response, each time, just write code for on
|
|||
|
||||
|
||||
async def main():
|
||||
di = DataInterpreter(tools=["Terminal", "FileManager"])
|
||||
di = DataInterpreter(tools=["Terminal", "Editor"])
|
||||
await di.run(USE_GOT_REPO_REQ)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -246,6 +246,8 @@ class ExecuteNbCode(Action):
|
|||
if "!pip" in code:
|
||||
success = False
|
||||
outputs = outputs[-INSTALL_KEEPLEN:]
|
||||
elif "git clone" in code:
|
||||
outputs = outputs[:INSTALL_KEEPLEN] + "..." + outputs[-INSTALL_KEEPLEN:]
|
||||
|
||||
elif language == "markdown":
|
||||
# add markdown content to markdown cell in a notebook.
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ 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
|
||||
|
|
@ -55,7 +56,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=self.goal, working_memory=self.rc.working_memory, auto_run=True)
|
||||
self.planner = Planner(goal="", working_memory=self.rc.working_memory, auto_run=True)
|
||||
|
||||
return self
|
||||
|
||||
|
|
@ -69,6 +70,7 @@ class DataAnalyst(DataInterpreter):
|
|||
example = KeywordExpRetriever().retrieve(self.user_requirement)
|
||||
else:
|
||||
self.working_memory.add_batch(self.rc.news)
|
||||
# TODO: implement experience retrieval in multi-round setting
|
||||
|
||||
plan_status = self.planner.plan.model_dump(include=["goal", "tasks"])
|
||||
for task in plan_status["tasks"]:
|
||||
|
|
@ -92,10 +94,19 @@ class DataAnalyst(DataInterpreter):
|
|||
async def _act(self) -> Message:
|
||||
"""Useful in 'react' mode. Return a Message conforming to Role._act interface."""
|
||||
logger.info(f"ready to take on task {self.planner.plan.current_task}")
|
||||
|
||||
# TODO: Consider an appropriate location to insert task experience formally
|
||||
experience = KeywordExpRetriever().retrieve(self.planner.plan.current_task.instruction, exp_type="task")
|
||||
if experience and experience not in [msg.content for msg in self.rc.working_memory.get()]:
|
||||
exp_msg = Message(content=experience, role="assistant")
|
||||
self.rc.working_memory.add(exp_msg)
|
||||
|
||||
code, result, is_success = await self._write_and_exec_code()
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -514,6 +514,13 @@ class Plan(BaseModel):
|
|||
if task_id in self.task_map:
|
||||
task = self.task_map[task_id]
|
||||
task.reset()
|
||||
# reset all downstream tasks that are dependent on the reset task
|
||||
for dep_task in self.tasks:
|
||||
if task_id in dep_task.dependent_task_ids:
|
||||
# FIXME: if LLM generates cyclic tasks, this will result in infinite recursion
|
||||
self.reset_task(dep_task.task_id)
|
||||
|
||||
self._update_current_task()
|
||||
|
||||
def replace_task(self, new_task: Task):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
|
|
@ -159,44 +161,126 @@ 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 self.EXAMPLE["deploy"]
|
||||
def retrieve(self, context: str, exp_type: Literal["plan", "task"] = "plan") -> str:
|
||||
if exp_type == "plan":
|
||||
if "deploy" in context.lower():
|
||||
return DEPLOY_EXAMPLE
|
||||
elif "issue" in context.lower():
|
||||
return FIX_ISSUE_EXAMPLE
|
||||
elif exp_type == "task":
|
||||
if "diagnose" in context.lower():
|
||||
return SEARCH_SYMBOL_EXAMPLE
|
||||
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"
|
||||
}
|
||||
},
|
||||
]
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
SEARCH_SYMBOL_EXAMPLE = """
|
||||
## Past Experience
|
||||
Issue: PaiEasChatEndpoint._call_eas should return bytes type instead of str type
|
||||
Explanation: To understand the issue, we first need to know the content of the method in the codebase. Therefore, we search the symbol `def _call_eas` in the cloned repo langchain.
|
||||
|
||||
```python
|
||||
from metagpt.tools.libs.editor import Editor
|
||||
|
||||
# Initialize the Editor tool
|
||||
editor = Editor()
|
||||
|
||||
# Search for the PaiEasChatEndpoint._call_eas method in the codebase to understand the issue
|
||||
symbol_to_search = "def _call_eas"
|
||||
file_block = editor.search_content(symbol=symbol_to_search, root_path="langchain")
|
||||
|
||||
# Output the file block containing the method to diagnose the problem
|
||||
file_block
|
||||
```
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -96,7 +96,13 @@ 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:
|
||||
role.planner.plan.replace_task(Task(**cmd["args"]))
|
||||
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)
|
||||
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,
|
||||
file_manager,
|
||||
editor,
|
||||
browser,
|
||||
deployer,
|
||||
git,
|
||||
|
|
@ -27,7 +27,7 @@ _ = (
|
|||
# web_scraping,
|
||||
# email_login,
|
||||
terminal,
|
||||
file_manager,
|
||||
editor,
|
||||
browser,
|
||||
deployer,
|
||||
git,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from playwright.async_api import async_playwright
|
||||
|
||||
from metagpt.const import DEFAULT_WORKSPACE_ROOT
|
||||
|
|
@ -5,10 +7,10 @@ from metagpt.tools.tool_registry import register_tool
|
|||
from metagpt.utils.report import BrowserReporter
|
||||
|
||||
|
||||
@register_tool()
|
||||
@register_tool(tags=["web", "browse", "scrape"])
|
||||
class Browser:
|
||||
"""
|
||||
A tool for browsing the web. Don't initialize a new instance of this class if one already exists.
|
||||
A tool for browsing the web and scraping. 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.
|
||||
"""
|
||||
|
||||
|
|
@ -31,7 +33,7 @@ class Browser:
|
|||
self.current_page = page
|
||||
self.current_page_url = url
|
||||
print("Now on page ", url)
|
||||
print(await self._view())
|
||||
await self._view()
|
||||
|
||||
async def open_new_page(self, url: str):
|
||||
"""open a new page in the browser and view the page"""
|
||||
|
|
@ -51,6 +53,12 @@ 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.
|
||||
|
|
@ -142,10 +150,12 @@ class Browser:
|
|||
await self.current_page.screenshot(path=path)
|
||||
print(f"Screenshot saved to: {path}")
|
||||
|
||||
async def _view(self) -> str:
|
||||
async def _view(self, keep_len: int = 5000) -> 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)
|
||||
return visible_text_with_links
|
||||
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)
|
||||
|
||||
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"""
|
||||
|
|
|
|||
|
|
@ -16,11 +16,13 @@ class FileBlock(BaseModel):
|
|||
block_start_line: int
|
||||
block_end_line: int
|
||||
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")
|
||||
symbol_start_line: int = Field(
|
||||
default=-1, description="The line number of the symbol in the file, -1 if not applicable"
|
||||
)
|
||||
|
||||
|
||||
@register_tool()
|
||||
class FileManager:
|
||||
class Editor:
|
||||
"""A tool for reading, understanding, writing, and editing files"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
|
@ -38,14 +40,15 @@ class FileManager:
|
|||
self.resource.report(path, "path")
|
||||
return f.read()
|
||||
|
||||
def search_content(self, symbol: str, root_path: str = "", window: int = 20) -> FileBlock:
|
||||
def search_content(self, symbol: str, root_path: str = ".", window: int = 20) -> FileBlock:
|
||||
"""
|
||||
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.
|
||||
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.
|
||||
root_path (str, optional): The root path to search in. If not provided, search in the current directory. Defaults to "".
|
||||
root_path (str, optional): The root path to search in. If not provided, search in the current directory. Defaults to ".".
|
||||
window (int, optional): The window size to return. Defaults to 20.
|
||||
|
||||
Returns:
|
||||
|
|
@ -56,8 +59,11 @@ class FileManager:
|
|||
block_start_line: int
|
||||
block_end_line: int
|
||||
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")
|
||||
symbol_start_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)
|
||||
|
|
@ -79,10 +85,13 @@ class FileManager:
|
|||
block_start_line=start + 1,
|
||||
block_end_line=end + 1,
|
||||
symbol=symbol,
|
||||
symbol_line=i + 1,
|
||||
symbol_start_line=i + 1,
|
||||
)
|
||||
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:
|
||||
|
|
@ -91,7 +100,7 @@ class FileManager:
|
|||
1. If the new block content is empty, the original block will be deleted.
|
||||
2. If the new block content is not empty and end_line < start_line (e.g. set end_line = -1) the new block content will be inserted at start_line.
|
||||
3. If the new block content is not empty and end_line >= start_line, the original block from start_line to end_line (both inclusively) will be replaced by the new block content.
|
||||
This function can sometimes be used given a FileBlock upstream. Think carefully if you want to use block_start_line or symbol_line in the FileBlock as your start_line input.
|
||||
This function can sometimes be used given a FileBlock upstream. Think carefully if you want to use block_start_line or symbol_start_line in the FileBlock as your start_line input. Your new_block_content will be placed at the start_line.
|
||||
|
||||
Args:
|
||||
file_path (str): The file path to write the new block content.
|
||||
|
|
@ -112,8 +121,8 @@ class FileManager:
|
|||
|
||||
# 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)
|
||||
|
|
@ -12,55 +12,6 @@ from metagpt.tools.tool_registry import register_tool
|
|||
from metagpt.utils.git_repository import GitBranch, GitRepository
|
||||
|
||||
|
||||
# @register_tool(tags=["git"])
|
||||
async def git_clone(url: str, output_dir: str | Path = None) -> Path:
|
||||
"""
|
||||
Clones a Git repository from the given URL.
|
||||
|
||||
Args:
|
||||
url (Union[str, Path]): The URL or local path of the Git repository to clone.
|
||||
output_dir (Union[str, Path], optional): The directory where the repository should be cloned.
|
||||
If None, the repository will be cloned into the current working directory. Defaults to None.
|
||||
|
||||
Returns:
|
||||
Path: The path to the cloned repository.
|
||||
|
||||
Example:
|
||||
>>> url = "https://github.com/iorisa/snake-game.git"
|
||||
>>> local_path = await git_clone(url=url)
|
||||
>>> print(local_path)
|
||||
/local/path/to/snake-game
|
||||
"""
|
||||
repo = await GitRepository.clone_from(url=url, output_dir=output_dir)
|
||||
return repo.workdir
|
||||
|
||||
|
||||
@register_tool(
|
||||
tags=["software development", "git", "Checks out the specific commit/branch/tag of the local Git repository."]
|
||||
)
|
||||
async def git_checkout(repo_dir: str | Path, commit_id: str):
|
||||
"""
|
||||
Checks out a specific commit in a Git repository.
|
||||
|
||||
Args:
|
||||
repo_dir (str or Path): The directory containing the Git repository.
|
||||
commit_id (str): The ID of the commit or the name of branch/tag to check out.
|
||||
|
||||
Raises:
|
||||
ValueError: If the specified Git root is invalid.
|
||||
|
||||
Example:
|
||||
>>> repo_dir = '/TO/GIT/REPO'
|
||||
>>> commit_id = 'main'
|
||||
>>> await git_checkout(repo_dir=repo_dir, commit_id=commit_id)
|
||||
git checkout main
|
||||
"""
|
||||
repo = GitRepository(local_path=repo_dir, auto_init=False)
|
||||
if not repo.is_valid:
|
||||
ValueError(f"Invalid git root: {repo_dir}")
|
||||
await repo.checkout(commit_id)
|
||||
|
||||
|
||||
@register_tool(tags=["software development", "git", "Commit the changes and push to remote git repository."])
|
||||
async def git_push(
|
||||
local_path: Union[str, Path],
|
||||
|
|
@ -106,7 +57,7 @@ async def git_push(
|
|||
return branch
|
||||
|
||||
|
||||
@register_tool(tags=["software development", "git", "create a git pull/merge request"])
|
||||
@register_tool(tags=["software development", "git", "create a git pull request or merge request"])
|
||||
async def git_create_pull(
|
||||
base: str,
|
||||
head: str,
|
||||
|
|
|
|||
|
|
@ -238,6 +238,8 @@ class GitRepository:
|
|||
:param comments: Comments for the archive commit.
|
||||
"""
|
||||
logger.info(f"Archive: {list(self.changed_files.keys())}")
|
||||
if not self.changed_files:
|
||||
return
|
||||
self.add_change(self.changed_files)
|
||||
self.commit(comments)
|
||||
|
||||
|
|
@ -271,7 +273,7 @@ class GitRepository:
|
|||
|
||||
base = self.current_branch
|
||||
head = base if not new_branch else self.new_branch(new_branch)
|
||||
self.archive(comments)
|
||||
self.archive(comments) # will skip committing if no changes
|
||||
ctx = Context()
|
||||
env = ctx.new_environ()
|
||||
proxy = ["-c", f"http.proxy={ctx.config.proxy}"] if ctx.config.proxy else []
|
||||
|
|
@ -288,7 +290,7 @@ class GitRepository:
|
|||
raise BadCredentialsException(status=401, message=info)
|
||||
info = f"{stdout}\n{stderr}\nexit: {return_code}\n"
|
||||
info = info.replace(token, "<TOKEN>")
|
||||
logger.info(info)
|
||||
print(info)
|
||||
|
||||
return GitBranch(base=base, head=head, repo_name=self.repo_name)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,15 @@
|
|||
import asyncio
|
||||
import os
|
||||
import threading
|
||||
|
||||
from metagpt.environment.mgx.mgx_env import MGXEnv
|
||||
from metagpt.roles import (
|
||||
Architect,
|
||||
Engineer,
|
||||
ProductManager,
|
||||
ProjectManager,
|
||||
QaEngineer,
|
||||
)
|
||||
from metagpt.roles import Architect, Engineer, ProductManager, ProjectManager
|
||||
from metagpt.roles.di.data_analyst import DataAnalyst
|
||||
from metagpt.roles.di.team_leader import TeamLeader
|
||||
from metagpt.schema import Message
|
||||
|
||||
|
||||
async def main(requirement, enable_human_input=False):
|
||||
async def main(requirement="", enable_human_input=False):
|
||||
env = MGXEnv()
|
||||
env.add_roles(
|
||||
[
|
||||
|
|
@ -23,7 +18,7 @@ async def main(requirement, enable_human_input=False):
|
|||
Architect(),
|
||||
ProjectManager(),
|
||||
Engineer(n_borg=5, use_code_review=False),
|
||||
QaEngineer(),
|
||||
# QaEngineer(),
|
||||
DataAnalyst(tools=["<all>"]),
|
||||
]
|
||||
)
|
||||
|
|
@ -32,7 +27,9 @@ async def main(requirement, enable_human_input=False):
|
|||
# simulate human sending messages in chatbox
|
||||
send_human_input(env)
|
||||
|
||||
env.publish_message(Message(content=requirement))
|
||||
if requirement:
|
||||
env.publish_message(Message(content=requirement))
|
||||
# env.publish_message(Message(content=requirement, send_to={"David"}), user_defined_recipient="David")
|
||||
|
||||
while not env.is_idle:
|
||||
await env.run()
|
||||
|
|
@ -70,9 +67,25 @@ data_path = "data/titanic"
|
|||
train_path = f"{data_path}/split_train.csv"
|
||||
eval_path = f"{data_path}/split_eval.csv"
|
||||
TITANIC_REQ = f"This is a titanic passenger survival dataset, your goal is to predict passenger survival outcome. The target column is Survived. Perform data analysis, data preprocessing, feature engineering, and modeling to predict the target. Report accuracy on the eval data. Train data path: '{train_path}', eval data path: '{eval_path}'."
|
||||
FIX_ISSUE = """
|
||||
Write a fix for this issue: https://github.com/langchain-ai/langchain/issues/20453,
|
||||
you can fix it on this repo https://github.com/garylin2099/langchain,
|
||||
checkout a branch named test-fix, commit your changes, push, and create a PR to the master branch of https://github.com/iorisa/langchain
|
||||
"""
|
||||
FIX_ISSUE_SIMPLE = """
|
||||
Write a fix for this issue: https://github.com/mannaandpoem/simple_calculator/issues/1,
|
||||
you can fix it on this repo https://github.com/garylin2099/simple_calculator,
|
||||
checkout a branch named test, commit your changes, push, and create a PR to the original repo.
|
||||
"""
|
||||
PUSH_PR_REQ = """
|
||||
clone https://github.com/garylin2099/simple_calculator, checkout a new branch named test-branch, add an empty file test_file.py to the repo.
|
||||
Commit your changes and push, finally, create a PR to the master branch of https://github.com/mannaandpoem/simple_calculator.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# NOTE: Add access_token to test github issue fixing
|
||||
os.environ["access_token"] = "ghp_xxx"
|
||||
# NOTE: Change the requirement to the one you want to test
|
||||
# Set enable_human_input to True if you want to simulate sending messages in chatbox
|
||||
asyncio.run(main(requirement=SIMPLE_REQ, enable_human_input=False))
|
||||
asyncio.run(main(requirement=FIX_ISSUE, enable_human_input=False))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.const import TEST_DATA_PATH
|
||||
from metagpt.tools.libs.file_manager import FileBlock, FileManager
|
||||
from metagpt.tools.libs.editor import Editor, FileBlock
|
||||
|
||||
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_file_manager.py"
|
||||
TEST_FILE_PATH = TEST_DATA_PATH / "tools/test_script_for_editor.py"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -36,7 +36,7 @@ EXPECTED_SEARCHED_BLOCK = FileBlock(
|
|||
|
||||
|
||||
def test_search_content(test_file):
|
||||
block = FileManager().search_content("def test_function_for_fm", root_path=TEST_DATA_PATH, window=3)
|
||||
block = Editor().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):
|
||||
FileManager().write_content(
|
||||
Editor().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):
|
||||
FileManager().write_content(file_path=str(TEST_FILE_PATH), start_line=3, end_line=5)
|
||||
Editor().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):
|
||||
FileManager().write_content(
|
||||
Editor().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 = FileManager().write_content(
|
||||
msg = Editor().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 = FileManager().write_content(
|
||||
msg = Editor().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