mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-06-11 15:15:18 +02:00
fix conflicts
This commit is contained in:
commit
482d78f6e0
20 changed files with 1018 additions and 875 deletions
|
|
@ -27,7 +27,7 @@ class MGXEnv(Environment):
|
|||
|
||||
def publish_message(self, message: Message, user_defined_recipient: str = "", publicer: str = "") -> bool:
|
||||
"""let the team leader take over message publishing"""
|
||||
tl = self.get_role("Tim") # TeamLeader's name is Tim
|
||||
tl = self.get_role("Mike") # TeamLeader's name is Mike
|
||||
|
||||
if user_defined_recipient:
|
||||
# human user's direct chat message to a certain role
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
import json
|
||||
import re
|
||||
|
||||
from unidiff import PatchedFile, PatchSet
|
||||
from unidiff import PatchSet
|
||||
|
||||
from metagpt.actions.action import Action
|
||||
from metagpt.ext.cr.utils.cleaner import (
|
||||
|
|
@ -19,6 +19,7 @@ from metagpt.utils.common import parse_json_code_block
|
|||
|
||||
CODE_REVIEW_PROMPT_TEMPLATE = """
|
||||
NOTICE
|
||||
Let's think and work step by step.
|
||||
With the given pull-request(PR) Patch, and referenced Points(Code Standards), you should compare each point with the code one-by-one.
|
||||
|
||||
The Patch code has added line number at the first character each line for reading, but the review should focus on new added code inside the `Patch` (lines starting with line number and '+').
|
||||
|
|
@ -52,12 +53,13 @@ CodeReview guidelines:
|
|||
- Don't suggest to add docstring unless it's necessary indeed.
|
||||
- If the same code error occurs multiple times, it cannot be omitted, and all places need to be identified.But Don't duplicate at the same place with the same comment!
|
||||
- Every line of code in the patch needs to be carefully checked, and laziness cannot be omitted. It is necessary to find out all the places.
|
||||
- The `comment` and `point_id` in the Output must correspond to and belong to the same one `Point`.
|
||||
|
||||
Just print the PR Patch comments in json format like **Output Format**.
|
||||
"""
|
||||
|
||||
CODE_REVIEW_COMFIRM_SYSTEM_PROMPT = """
|
||||
You are a professional engineer with Java stack, and good at code review comment result judgement.
|
||||
You are a professional engineer with {code_language} stack, and good at code review comment result judgement.Let's think and work step by step.
|
||||
"""
|
||||
|
||||
CODE_REVIEW_COMFIRM_TEMPLATE = """
|
||||
|
|
@ -76,7 +78,7 @@ CODE_REVIEW_COMFIRM_TEMPLATE = """
|
|||
|
||||
## Your Task:
|
||||
1. First, check if the code meets the requirements and does not violate any defects. If it meets the requirements and does not violate any defects, print `False` and do not proceed with further judgment.
|
||||
2. If the check in step 1 does not print `False`, proceed to further judgment. Based on the "Reference Example for Judgment" provided, determine if the "Code" and "Code Review Comments" match. If they match, print `True`; otherwise, print `False`.
|
||||
2. Based on the `Reference Example for Judgment` provided, determine if the `Code` and `Code Review Comments` match. If they match, print `True`; otherwise, print `False`.
|
||||
|
||||
Note: Your output should only be `True` or `False` without any explanations.
|
||||
"""
|
||||
|
|
@ -89,25 +91,35 @@ class CodeReview(Action):
|
|||
new_comments = []
|
||||
logger.debug(f"original comments: {comments}")
|
||||
for cmt in comments:
|
||||
for p in points:
|
||||
if int(cmt.get("point_id", -1)) == p.id:
|
||||
code_start_line = cmt.get("code_start_line")
|
||||
code_end_line = cmt.get("code_end_line")
|
||||
code = get_code_block_from_patch(patch, code_start_line, code_end_line)
|
||||
try:
|
||||
if cmt.get("commented_file").endswith(".py"):
|
||||
points = [p for p in points if p.language == "Python"]
|
||||
elif cmt.get("commented_file").endswith(".java"):
|
||||
points = [p for p in points if p.language == "Java"]
|
||||
else:
|
||||
continue
|
||||
for p in points:
|
||||
point_id = int(cmt.get("point_id", -1))
|
||||
if point_id == p.id:
|
||||
code_start_line = cmt.get("code_start_line")
|
||||
code_end_line = cmt.get("code_end_line")
|
||||
code = get_code_block_from_patch(patch, code_start_line, code_end_line)
|
||||
|
||||
new_comments.append(
|
||||
{
|
||||
"commented_file": cmt.get("commented_file"),
|
||||
"code": code,
|
||||
"code_start_line": code_start_line,
|
||||
"code_end_line": code_end_line,
|
||||
"comment": cmt.get("comment"),
|
||||
"point_id": p.id,
|
||||
"point": p.text,
|
||||
"point_detail": p.detail,
|
||||
}
|
||||
)
|
||||
break
|
||||
new_comments.append(
|
||||
{
|
||||
"commented_file": cmt.get("commented_file"),
|
||||
"code": code,
|
||||
"code_start_line": code_start_line,
|
||||
"code_end_line": code_end_line,
|
||||
"comment": cmt.get("comment"),
|
||||
"point_id": p.id,
|
||||
"point": p.text,
|
||||
"point_detail": p.detail,
|
||||
}
|
||||
)
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
logger.debug(f"new_comments: {new_comments}")
|
||||
return new_comments
|
||||
|
|
@ -132,51 +144,49 @@ class CodeReview(Action):
|
|||
code = get_code_block_from_patch(
|
||||
patch, str(max(1, int(code_start_line) - 5)), str(int(code_end_line) + 5)
|
||||
)
|
||||
code_language = "Java"
|
||||
code_file_ext = cmt.get("commented_file", ".java").split(".")[-1]
|
||||
if code_file_ext == ".java":
|
||||
code_language = "Java"
|
||||
elif code_file_ext == ".py":
|
||||
code_language = "Python"
|
||||
prompt = CODE_REVIEW_COMFIRM_TEMPLATE.format(
|
||||
code=code,
|
||||
comment=cmt.get("comment"),
|
||||
desc=point.text,
|
||||
example=point.yes_example + "\n" + point.no_example,
|
||||
)
|
||||
resp = await self.llm.aask(prompt, system_msgs=[CODE_REVIEW_COMFIRM_SYSTEM_PROMPT])
|
||||
system_prompt = [CODE_REVIEW_COMFIRM_SYSTEM_PROMPT.format(code_language=code_language)]
|
||||
resp = await self.llm.aask(prompt, system_msgs=system_prompt)
|
||||
if "True" in resp or "true" in resp:
|
||||
new_comments.append(cmt)
|
||||
logger.info(f"original comments num: {len(comments)}, confirmed comments num: {len(new_comments)}")
|
||||
return new_comments
|
||||
|
||||
async def cr_by_full_points(self, patch: PatchSet, points: list[Point]):
|
||||
async def cr_by_points(self, patch: PatchSet, points: list[Point]):
|
||||
comments = []
|
||||
points_str = "\n".join([f"{p.id} {p.text}" for p in points])
|
||||
for patched_file in patch:
|
||||
if not patched_file:
|
||||
continue
|
||||
if patched_file.path.endswith(".py"):
|
||||
points_str = "\n".join([f"{p.id} {p.text}" for p in points if p.language == "Python"])
|
||||
points = [p for p in points if p.language == "Python"]
|
||||
elif patched_file.path.endswith(".java"):
|
||||
points_str = "\n".join([f"{p.id} {p.text}" for p in points if p.language == "Java"])
|
||||
points = [p for p in points if p.language == "Java"]
|
||||
else:
|
||||
continue
|
||||
if len(str(patched_file).splitlines()) >= 50:
|
||||
cr_by_segment_points_comments = await self.cr_by_segment_points(
|
||||
patched_file=patched_file, points=points
|
||||
)
|
||||
comments += cr_by_segment_points_comments
|
||||
continue
|
||||
prompt = CODE_REVIEW_PROMPT_TEMPLATE.format(patch=str(patched_file), points=points_str)
|
||||
resp = await self.llm.aask(prompt)
|
||||
json_str = parse_json_code_block(resp)[0]
|
||||
comments += json.loads(json_str)
|
||||
|
||||
return comments
|
||||
|
||||
async def cr_by_segment_points(self, patched_file: PatchedFile, points: list[Point]):
|
||||
comments = []
|
||||
group_points = [points[i : i + 3] for i in range(0, len(points), 3)]
|
||||
for group_point in group_points:
|
||||
points_str = "\n".join([f"{p.id} {p.text}" for p in group_point])
|
||||
prompt = CODE_REVIEW_PROMPT_TEMPLATE.format(patch=str(patched_file), points=points_str)
|
||||
resp = await self.llm.aask(prompt)
|
||||
json_str = parse_json_code_block(resp)[0]
|
||||
comments_batch = json.loads(json_str)
|
||||
comments += comments_batch
|
||||
group_points = [points[i : i + 3] for i in range(0, len(points), 3)]
|
||||
for group_point in group_points:
|
||||
points_str = "id description\n"
|
||||
points_str += "\n".join([f"{p.id} {p.text}" for p in group_point])
|
||||
prompt = CODE_REVIEW_PROMPT_TEMPLATE.format(patch=str(patched_file), points=points_str)
|
||||
resp = await self.llm.aask(prompt)
|
||||
json_str = parse_json_code_block(resp)[0]
|
||||
comments_batch = json.loads(json_str)
|
||||
if comments_batch:
|
||||
patched_file_path = patched_file.path
|
||||
for c in comments_batch:
|
||||
c["commented_file"] = patched_file_path
|
||||
comments += comments_batch
|
||||
|
||||
return comments
|
||||
|
||||
|
|
@ -185,7 +195,7 @@ class CodeReview(Action):
|
|||
patch: PatchSet = add_line_num_on_patch(patch)
|
||||
|
||||
result = []
|
||||
comments = await self.cr_by_full_points(patch=patch, points=points)
|
||||
comments = await self.cr_by_points(patch=patch, points=points)
|
||||
if len(comments) != 0:
|
||||
comments = self.format_comments(comments, points, patch)
|
||||
comments = await self.confirm_comments(patch=patch, comments=comments, points=points)
|
||||
|
|
|
|||
|
|
@ -81,17 +81,18 @@ class ModifyCode(Action):
|
|||
}
|
||||
resp = None
|
||||
for patched_file in patch:
|
||||
patch_target_file_name = str(patched_file.target_file).split("/", maxsplit=1)[-1]
|
||||
if patch_target_file_name not in grouped_comments:
|
||||
patch_target_file_name = str(patched_file.path).split("/")[-1]
|
||||
if patched_file.path not in grouped_comments:
|
||||
continue
|
||||
comments_prompt = ""
|
||||
index = 1
|
||||
for grouped_comment in grouped_comments[patch_target_file_name]:
|
||||
for grouped_comment in grouped_comments[patched_file.path]:
|
||||
comments_prompt += f"""
|
||||
<comment{index}>
|
||||
{grouped_comment}
|
||||
</comment{index}>\n
|
||||
"""
|
||||
index += 1
|
||||
prompt = MODIFY_CODE_PROMPT.format(patch=patched_file, comments=comments_prompt)
|
||||
output_dir = (
|
||||
Path(output_dir)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -53,6 +53,7 @@ Some text indicating your thoughts, such as how you should update the plan statu
|
|||
```
|
||||
Notice: your output JSON data section must start with **```json [**
|
||||
"""
|
||||
|
||||
JSON_REPAIR_PROMPT = """
|
||||
## json data
|
||||
{json_data}
|
||||
|
|
@ -63,3 +64,10 @@ Formatted JSON data
|
|||
```
|
||||
Help check if there are any formatting issues with the JSON data? If so, please help format it
|
||||
"""
|
||||
|
||||
QUICK_THINK_PROMPT = """
|
||||
Decide if the latest user message is a quick question.
|
||||
Quick questions include common-sense, logical, math questions, greetings, or casual chat that you can answer directly, excluding software development tasks.
|
||||
Respond with "#YES#, (then start your actual response to the question...)" if so, otherwise, simply respond with "#NO#".
|
||||
Your response:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ https://github.com/princeton-nlp/SWE-agent/tree/main/config/configs
|
|||
SWE_AGENT_SYSTEM_TEMPLATE = """
|
||||
SETTING: You are an autonomous programmer, and you're working directly in the environment line with a special interface.
|
||||
|
||||
The special interface consists of a file editor that shows you {WINDOW} lines of a file at a time.
|
||||
The special interface consists of a file editor that shows you 100 lines of a file at a time.
|
||||
|
||||
Please note that THE EDIT COMMAND REQUIRES PROPER INDENTATION. Pay attention to the original indentation when replacing the function.
|
||||
If you'd like to add the line ' print(x)' you must fully write that out, with all those spaces before the code! Indentation is important and code that is not indented correctly will fail and require fixing before it can be run.
|
||||
|
|
@ -50,7 +50,8 @@ MINIMAL_EXAMPLE = """
|
|||
## Example of a actions trajectory
|
||||
User Requirement and Issue: Fix the bug in the repo. Because the environment is not available, you DO NOT need to run and modify any existing test case files or add new test case files to ensure that the bug is fixed.
|
||||
|
||||
### Read and understand issue(Require):
|
||||
### Read and understand issue:
|
||||
Thought: Firstly, I need to review the detailed information of this issue in order to understand the problem that needs fixing.
|
||||
{{
|
||||
"command_name": "Browser.goto",
|
||||
"args": {{
|
||||
|
|
@ -60,43 +61,99 @@ User Requirement and Issue: Fix the bug in the repo. Because the environment is
|
|||
->
|
||||
|
||||
### Locate issue(Require): Locate the issue in the code by searching for the relevant file, function, or class and open the file to view the code.
|
||||
Thought: I need to come under the repo path
|
||||
{{
|
||||
"command_name": "Bash.run",
|
||||
"args": {{
|
||||
"cmd": "cd /workspace/django__django_3.0"
|
||||
"cmd": "cd /workspace/MetaGPT"
|
||||
}}
|
||||
}}
|
||||
->
|
||||
|
||||
Bash.run(cmd='search_dir_and_preview ASCIIUsernameValidator')
|
||||
Thought: Let's start by locating the `openai_api.py` file.\nFirst, let's search for the `openai_api.py` file.
|
||||
{{
|
||||
"command_name": "Bash.run",
|
||||
"args": {{
|
||||
"cmd": "open /workspace/django__django_3.0/django/contrib/auth/validators.py"
|
||||
"cmd": "find_file 'openai_api.py'"
|
||||
}}
|
||||
}}
|
||||
->
|
||||
|
||||
Thought: We have located both the `openai_api.py` file. Let's start by opening the `openai_api.py` file to apply the necessary changes.",
|
||||
{{
|
||||
"command_name": "Bash.run",
|
||||
"args": {{
|
||||
"cmd": "open '/workspace/MetaGPT/provider/openai_api.py'"
|
||||
}}
|
||||
}}
|
||||
->
|
||||
|
||||
### Fix the Bug(Require): Fix the bug in the code by editing the relevant function, class or code snippet.
|
||||
Thought: Now that I've found the bug, let's fix it by edit.
|
||||
{{
|
||||
"command_name": "Bash.run",
|
||||
"args": {{
|
||||
"cmd": "edit 10:20 <<EOF\n regex = r'\A[\w.@+-]+\Z'\n message = _( \n 'Enter a valid username. This value may contain only English letters, ' \n 'numbers, and @/./+/-/_ characters.'\n )\n flags = re.ASCII\n\n@deconstructible\nclass UnicodeUsernameValidator(validators.RegexValidator):\n regex = r'\A[\w.@+-]+\Z'\nEOF"
|
||||
"cmd": "edit 93:95 <<EOF\n usage = None\n collected_messages = []\n async for chunk in response:\n if chunk.usage is not None:\n usage = CompletionUsage(**chunk.usage)\n chunk_message = chunk.choices[0].delta.content or '' if chunk.choices else '' # extract the message\n finish_reason = (\n chunk.choices[0].finish_reason if chunk.choices and hasattr(chunk.choices[0], 'finish_reason') else None\n )\n log_llm_stream(chunk_message)\nEOF"
|
||||
}}
|
||||
}}
|
||||
->
|
||||
Thought: Due to a syntax error related to an undefined name 'Image', we need to address this issue even though it is not directly related to our work. Let's try importing the package to fix it.
|
||||
{{
|
||||
"command_name": "Bash.run",
|
||||
"args": {{
|
||||
"cmd": "edit 14:14 <<EOF\nfrom PIL.Image import Image\nEOF"
|
||||
}}
|
||||
}}
|
||||
->
|
||||
|
||||
### Submit the Changes(Require): Submit the changes to the repository.
|
||||
### Save the Changes (Required): After all changes have been made, save them to the repository.
|
||||
> You must choose one of the following two methods.
|
||||
|
||||
#### Just save the changes locally, it only need one action.
|
||||
Thought: The bug has been fixed. Let's submit the changes.
|
||||
{{
|
||||
"command_name": "Bash.run",
|
||||
"args": {{
|
||||
"cmd": "submit"
|
||||
}}
|
||||
}}
|
||||
Bash.run(cmd='submit')
|
||||
->
|
||||
|
||||
#### Save the changes and commit them to the remote repository.
|
||||
|
||||
##### Push the changes from the local repository to the remote repository.
|
||||
Thought: All changes have been saved, let's push the code to the remote repository.
|
||||
{{
|
||||
"command_name": "end",
|
||||
"command_name": "git_push",
|
||||
"args": {{
|
||||
"local_path": "/workspace/MetaGPT",
|
||||
"app_name": "github",
|
||||
"comments": "Fix Issue #1275: produced TypeError: openai.types.completion_usage.CompletionUsage() argument after ** must be a mapping, not NoneType"",
|
||||
"new_branch": "test-fix"
|
||||
}}
|
||||
}}
|
||||
->
|
||||
|
||||
##### Create a pull request (Optional): Merge the changes from the new branch into the master branch.
|
||||
Thought: Now that the changes have been pushed to the remote repository, due to the user's requirement, let's create a pull request to merge the changes into the master branch.
|
||||
[{{
|
||||
"command_name": "git_create_pull",
|
||||
"args": {{
|
||||
"base": "master",
|
||||
"head": "test-fix",
|
||||
"base_repo_name": "garylin2099/MetaGPT",
|
||||
"head_repo_name": "seeker-jie/MetaGPT",
|
||||
"app_name": "github",
|
||||
"title": "Fix Issue #1275: produced TypeError: openai.types.completion_usage.CompletionUsage() argument after ** must be a mapping, not NoneType"",
|
||||
"body": "This pull request addresses issue #1275 by ensuring that chunk.usage is not None before passing it to CompletionUsage."
|
||||
}}
|
||||
}}]
|
||||
->
|
||||
|
||||
### Finally
|
||||
Thought: All task has been done, let's end the conversation.
|
||||
{{
|
||||
"command_name": "end"
|
||||
}}
|
||||
"""
|
||||
|
||||
|
|
@ -162,7 +219,10 @@ IMPORTANT_TIPS = """
|
|||
- If a search command fails, modify the search criteria and check for typos or incorrect paths, then try again.
|
||||
- Based on feedback of observation or bash command in trajectory to guide adjustments in your search strategy.
|
||||
|
||||
13. If the task results in succeed, fail, or NO PROGRESS, output `submit`.
|
||||
13. Save the code change:
|
||||
- If you need to submit changes to the remote repository, first use the regular git commit command to save the changes locally, then select a command from the `Available Commands: [git_push, git_create_pull]` to submit the changes to the remote repository.
|
||||
|
||||
- If you don't need to submit code changes to the remote repository. use the command Bash.run('submit') to commit the changes locally.
|
||||
|
||||
14. If provided an issue link, you MUST go to the issue page using Browser tool to understand the issue before starting your fix.
|
||||
|
||||
|
|
@ -185,5 +245,4 @@ The current bash state is:
|
|||
(Current directory: {{working_dir}})
|
||||
|
||||
Avoid repeating the same command. Instead, please think about the current situation and provide the next bash command to execute in JSON format:"
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from typing import Callable, Dict, List, Literal, Tuple
|
|||
|
||||
from pydantic import model_validator
|
||||
|
||||
from metagpt.actions import Action
|
||||
from metagpt.actions import Action, UserRequirement
|
||||
from metagpt.actions.di.run_command import RunCommand
|
||||
from metagpt.exp_pool import exp_cache
|
||||
from metagpt.exp_pool.context_builders import RoleZeroContextBuilder
|
||||
|
|
@ -18,6 +18,7 @@ from metagpt.prompts.di.role_zero import (
|
|||
CMD_PROMPT,
|
||||
CMD_PROMPT_EXP_PART,
|
||||
JSON_REPAIR_PROMPT,
|
||||
QUICK_THINK_PROMPT,
|
||||
ROLE_INSTRUCTION,
|
||||
)
|
||||
from metagpt.roles import Role
|
||||
|
|
@ -28,7 +29,7 @@ from metagpt.tools.libs.browser import Browser
|
|||
from metagpt.tools.libs.editor import Editor
|
||||
from metagpt.tools.tool_recommend import BM25ToolRecommender, ToolRecommender
|
||||
from metagpt.tools.tool_registry import register_tool
|
||||
from metagpt.utils.common import CodeParser
|
||||
from metagpt.utils.common import CodeParser, any_to_str
|
||||
from metagpt.utils.repair_llm_raw_output import RepairType, repair_llm_raw_output
|
||||
from metagpt.utils.report import ThoughtReporter
|
||||
|
||||
|
|
@ -161,7 +162,8 @@ class RoleZero(Role):
|
|||
memory.insert(index, UserMessage(cause_by="browser", content=await self.browser.view()))
|
||||
break
|
||||
req = self.llm.format_msg(memory + [UserMessage(content=prompt), UserMessage(content=cmd_prompt_exp_part)])
|
||||
async with ThoughtReporter(enable_llm_stream=True):
|
||||
async with ThoughtReporter(enable_llm_stream=True) as reporter:
|
||||
await reporter.async_report({"type": "react"})
|
||||
self.command_rsp = await self.llm_cached_aask(req=req, system_msgs=self.system_msg)
|
||||
self.rc.memory.add(AIMessage(content=self.command_rsp))
|
||||
|
||||
|
|
@ -200,9 +202,14 @@ class RoleZero(Role):
|
|||
)
|
||||
|
||||
async def _react(self) -> Message:
|
||||
# NOTE: Diff 1: Each time landing here means observing news, set todo to allow news processing in _think
|
||||
# NOTE: Diff 1: Each time landing here means news is observed, set todo to allow news processing in _think
|
||||
self._set_state(0)
|
||||
|
||||
# problems solvable by quick thinking doesn't need to a formal think-act cycle
|
||||
quick_rsp = await self._quick_think()
|
||||
if quick_rsp:
|
||||
return quick_rsp
|
||||
|
||||
actions_taken = 0
|
||||
rsp = AIMessage(content="No actions taken yet", cause_by=Action) # will be overwritten after Role _act
|
||||
while actions_taken < self.rc.max_react_loop:
|
||||
|
|
@ -219,6 +226,31 @@ class RoleZero(Role):
|
|||
actions_taken += 1
|
||||
return rsp # return output from the last action
|
||||
|
||||
async def _quick_think(self) -> Message:
|
||||
msg = self.rc.news[-1]
|
||||
rsp_msg = None
|
||||
if msg.cause_by != any_to_str(UserRequirement):
|
||||
# Agents themselves won't generate quick questions, use this rule to reduce extra llm calls
|
||||
return rsp_msg
|
||||
|
||||
context = self.llm.format_msg(self.get_memories(k=4) + [UserMessage(content=QUICK_THINK_PROMPT)])
|
||||
async with ThoughtReporter(enable_llm_stream=True) as reporter:
|
||||
await reporter.async_report({"type": "quick"})
|
||||
rsp = await self.llm.aask(context)
|
||||
|
||||
pattern = r"#YES#,? ?"
|
||||
if re.search(pattern, rsp):
|
||||
answer = re.sub(pattern, "", rsp).strip()
|
||||
self.rc.memory.add(AIMessage(content=answer, cause_by=RunCommand))
|
||||
await self.reply_to_human(content=answer)
|
||||
rsp_msg = AIMessage(
|
||||
content="Complete run",
|
||||
sent_from=self.name,
|
||||
cause_by=RunCommand,
|
||||
)
|
||||
|
||||
return rsp_msg
|
||||
|
||||
async def _parse_commands(self) -> Tuple[List[Dict], bool]:
|
||||
"""Retrieves commands from the Large Language Model (LLM).
|
||||
|
||||
|
|
@ -234,6 +266,7 @@ class RoleZero(Role):
|
|||
commands = CodeParser.parse_code(block=None, lang="json", text=self.command_rsp)
|
||||
commands = json.loads(repair_llm_raw_output(output=commands, req_keys=[None], repair_type=RepairType.JSON))
|
||||
except json.JSONDecodeError:
|
||||
logger.warning(f"Failed to parse JSON for: {self.command_rsp}. Trying to repair...")
|
||||
commands = await self.llm.aask(msg=JSON_REPAIR_PROMPT.format(json_data=self.command_rsp))
|
||||
commands = json.loads(CodeParser.parse_code(block=None, lang="json", text=commands))
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
|
|
@ -18,9 +17,7 @@ class SWEAgent(RoleZero):
|
|||
name: str = "Swen"
|
||||
profile: str = "Issue Solver"
|
||||
goal: str = "Resolve GitHub issue"
|
||||
_bash_window_size: int = 100
|
||||
_system_msg: str = SWE_AGENT_SYSTEM_TEMPLATE
|
||||
system_msg: list[str] = [_system_msg.format(WINDOW=_bash_window_size)]
|
||||
system_msg: str = [SWE_AGENT_SYSTEM_TEMPLATE]
|
||||
_instruction: str = NEXT_STEP_TEMPLATE
|
||||
tools: list[str] = [
|
||||
"Bash",
|
||||
|
|
@ -35,8 +32,7 @@ class SWEAgent(RoleZero):
|
|||
run_eval: bool = False
|
||||
|
||||
async def _think(self) -> bool:
|
||||
self._update_system_msg()
|
||||
self._format_instruction()
|
||||
await self._format_instruction()
|
||||
res = await super()._think()
|
||||
if self.run_eval:
|
||||
await self._parse_commands_for_eval()
|
||||
|
|
@ -51,32 +47,16 @@ class SWEAgent(RoleZero):
|
|||
}
|
||||
)
|
||||
|
||||
def _update_system_msg(self):
|
||||
"""
|
||||
Sets the system message for the SWE agent.
|
||||
|
||||
Sets the `_bash_window_size` from the environment variable `WINDOW` if it exists.
|
||||
Formats the `_system_msg` template with the current `_bash_window_size`.
|
||||
"""
|
||||
if os.getenv("WINDOW"):
|
||||
self._bash_window_size = int(os.getenv("WINDOW"))
|
||||
self.system_msg = [self._system_msg.format(WINDOW=self._bash_window_size)]
|
||||
|
||||
def _format_instruction(self):
|
||||
async def _format_instruction(self):
|
||||
"""
|
||||
Formats the instruction message for the SWE agent.
|
||||
|
||||
Runs the "state" command in the terminal, parses its output as JSON,
|
||||
and uses it to format the `_instruction` template.
|
||||
"""
|
||||
state_output = self.terminal.run("state")
|
||||
state_output = await self.terminal.run("state")
|
||||
bash_state = json.loads(state_output)
|
||||
|
||||
self.instruction = self._instruction.format(
|
||||
WINDOW=self._bash_window_size, examples=MINIMAL_EXAMPLE, **bash_state
|
||||
).strip()
|
||||
|
||||
return self.instruction
|
||||
self.instruction = self._instruction.format(**bash_state).strip()
|
||||
|
||||
async def _parse_commands_for_eval(self):
|
||||
"""
|
||||
|
|
@ -97,7 +77,7 @@ class SWEAgent(RoleZero):
|
|||
if "end" != cmd.get("command_name", ""):
|
||||
return
|
||||
try:
|
||||
diff_output = self.terminal.run("git diff --cached")
|
||||
diff_output = await self.terminal.run("git diff --cached")
|
||||
clear_diff = extract_patch(diff_output)
|
||||
logger.info(f"Diff output: \n{clear_diff}")
|
||||
if clear_diff:
|
||||
|
|
|
|||
|
|
@ -510,15 +510,6 @@ Explanation: The requirement is about software development. Assign each tasks to
|
|||
"assignee": "Alex"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command_name": "Plan.append_task",
|
||||
"args": {
|
||||
"task_id": "5",
|
||||
"dependent_task_ids": ["4"],
|
||||
"instruction": "Write comprehensive tests for the game logic and user interface to ensure functionality and reliability.",
|
||||
"assignee": "Edward"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command_name": "TeamLeader.publish_message",
|
||||
"args": {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ from metagpt.utils.a11y_tree import (
|
|||
scroll_page,
|
||||
type_text,
|
||||
)
|
||||
from metagpt.utils.proxy_env import get_proxy_from_env
|
||||
from metagpt.utils.report import BrowserReporter
|
||||
|
||||
|
||||
|
|
@ -72,7 +73,7 @@ class Browser:
|
|||
self.page: Optional[Page] = None
|
||||
self.accessibility_tree: list = []
|
||||
self.headless: bool = True
|
||||
self.proxy = None
|
||||
self.proxy = get_proxy_from_env()
|
||||
self.is_empty_page = True
|
||||
self.reporter = BrowserReporter()
|
||||
|
||||
|
|
@ -120,7 +121,7 @@ class Browser:
|
|||
await scroll_page(self.page, direction)
|
||||
return await self._wait_page()
|
||||
|
||||
async def goto(self, url: str, timeout: float = 30000):
|
||||
async def goto(self, url: str, timeout: float = 90000):
|
||||
"""Navigate to a specific URL."""
|
||||
if self.page is None:
|
||||
await self.start()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from pathlib import Path
|
|||
from typing import Optional
|
||||
|
||||
import aiofiles
|
||||
from bs4 import BeautifulSoup
|
||||
from unidiff import PatchSet
|
||||
|
||||
import metagpt.ext.cr
|
||||
|
|
@ -29,7 +30,7 @@ class CodeReview:
|
|||
Args:
|
||||
patch_path: The local path of the patch file or the url of the pull request. Example: "/data/xxx-pr-1.patch", "https://github.com/xx/XX/pull/1362"
|
||||
cr_output_file: Output file path where code review comments will be saved. Example: "cr/xxx-pr-1.json"
|
||||
cr_point_file: File path for specifying code review points. Defaults to a predefined file.
|
||||
cr_point_file: File path for specifying code review points. If not specified, this parameter is not passed..
|
||||
"""
|
||||
patch = await self._get_patch_content(patch_path)
|
||||
cr_point_file = cr_point_file if cr_point_file else Path(metagpt.ext.cr.__file__).parent / "points.json"
|
||||
|
|
@ -45,7 +46,7 @@ class CodeReview:
|
|||
)
|
||||
comments = await CodeReview_().run(patch, cr_points)
|
||||
cr_output_path.parent.mkdir(exist_ok=True, parents=True)
|
||||
async with aiofiles.open(cr_output_path, "w") as f:
|
||||
async with aiofiles.open(cr_output_path, "w", encoding="utf-8") as f:
|
||||
await f.write(json.dumps(comments, ensure_ascii=False))
|
||||
await reporter.async_report(cr_output_path)
|
||||
|
||||
|
|
@ -65,7 +66,7 @@ class CodeReview:
|
|||
output_dir: File path where code review comments are stored.
|
||||
"""
|
||||
patch = await self._get_patch_content(patch_path)
|
||||
async with aiofiles.open(cr_file, "r") as f:
|
||||
async with aiofiles.open(cr_file, "r", encoding="utf-8") as f:
|
||||
comments = json.loads(await f.read())
|
||||
await ModifyCode(pr="").run(patch, comments, output_dir)
|
||||
return f"The fixed patch files store in {output_dir}"
|
||||
|
|
@ -75,12 +76,14 @@ class CodeReview:
|
|||
# async with aiohttp.ClientSession(trust_env=True) as client:
|
||||
# async with client.get(f"{patch_path}.diff", ) as resp:
|
||||
# patch_file_content = await resp.text()
|
||||
browser = Browser()
|
||||
browser.proxy = {"server": "http://127.0.0.1:20172"}
|
||||
async with browser:
|
||||
async with Browser() as browser:
|
||||
await browser.goto(f"{patch_path}.diff")
|
||||
patch_file_content = await browser.page.content()
|
||||
|
||||
if patch_file_content.startswith("<html>"):
|
||||
soup = BeautifulSoup(patch_file_content, "html.parser")
|
||||
pre = soup.find("pre")
|
||||
if pre:
|
||||
patch_file_content = pre.text
|
||||
else:
|
||||
async with aiofiles.open(patch_path) as f:
|
||||
patch_file_content = await f.read()
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from github.PullRequest import PullRequest
|
|||
from metagpt.tools.tool_registry import register_tool
|
||||
|
||||
|
||||
@register_tool(tags=["software development", "git", "Commit the changes and push to remote git repository."])
|
||||
@register_tool(tags=["software development", "git", "Push to remote git repository."])
|
||||
async def git_push(
|
||||
local_path: Union[str, Path],
|
||||
app_name: str,
|
||||
|
|
@ -22,22 +22,23 @@ async def git_push(
|
|||
Pushes changes from a local Git repository to its remote counterpart.
|
||||
|
||||
Args:
|
||||
local_path (Union[str, Path]): The path to the local Git repository.
|
||||
app_name (str): The name of the application where the repository is hosted. For example, "github", "gitlab", "bitbucket", etc.
|
||||
comments (str, optional): The commit message to use. Defaults to "Commit".
|
||||
local_path (Union[str, Path]): The absolute path to the local Git repository.
|
||||
app_name (str): The name of the platform hosting the repository (e.g., "github", "gitlab", "bitbucket").
|
||||
comments (str, optional): Comments to be associated with the push. Defaults to "Commit".
|
||||
new_branch (str, optional): The name of the new branch to create and push changes to.
|
||||
If not provided, changes will be pushed to the current branch. Defaults to "".
|
||||
|
||||
Returns:
|
||||
GitBranch: The branch to which the changes were pushed.
|
||||
|
||||
Raises:
|
||||
ValueError: If the provided local_path does not point to a valid Git repository.
|
||||
|
||||
Example:
|
||||
>>> url = "https://github.com/iorisa/snake-game.git"
|
||||
>>> local_path = await git_clone(url=url)
|
||||
>>> app_name="github"
|
||||
>>> comments = "Archive"
|
||||
>>> app_name = "github"
|
||||
>>> comments = "Commit"
|
||||
>>> new_branch = "feature/new"
|
||||
>>> branch = await git_push(local_path=local_path, app_name=app_name, comments=comments, new_branch=new_branch)
|
||||
>>> base = branch.base
|
||||
|
|
@ -45,8 +46,8 @@ async def git_push(
|
|||
>>> repo_name = branch.repo_name
|
||||
>>> print(f"base branch:'{base}', head branch:'{head}', repo_name:'{repo_name}'")
|
||||
base branch:'master', head branch:'feature/new', repo_name:'iorisa/snake-game'
|
||||
|
||||
"""
|
||||
|
||||
from metagpt.tools.libs import get_env
|
||||
from metagpt.utils.git_repository import GitRepository
|
||||
|
||||
|
|
@ -64,9 +65,9 @@ async def git_push(
|
|||
async def git_create_pull(
|
||||
base: str,
|
||||
head: str,
|
||||
base_repo_name: str,
|
||||
app_name: str,
|
||||
head_repo_name: Optional[str] = None,
|
||||
base_repo_name: str,
|
||||
head_repo_name: str = None,
|
||||
title: Optional[str] = None,
|
||||
body: Optional[str] = None,
|
||||
issue: Optional[Issue] = None,
|
||||
|
|
@ -75,14 +76,14 @@ async def git_create_pull(
|
|||
Creates a pull request on a Git repository. Use this tool in priority over Browser to create a pull request.
|
||||
|
||||
Args:
|
||||
base (str): The base branch of the pull request.
|
||||
head (str): The head branch of the pull request.
|
||||
base_repo_name (str): The full repository name (user/repo) where the pull request will be created.
|
||||
app_name (str): The name of the application where the repository is hosted. For example, "github", "gitlab", "bitbucket", etc.
|
||||
head_repo_name (Optional[str], optional): The full repository name (user/repo) where the pull request will merge from. Defaults to None.
|
||||
title (Optional[str], optional): The title of the pull request. Defaults to None.
|
||||
body (Optional[str], optional): The body of the pull request. Defaults to None.
|
||||
issue (Optional[Issue], optional): The related issue of the pull request. Defaults to None.
|
||||
base (str): The name of the base branch where the pull request will be merged.
|
||||
head (str): The name of the branch that contains the changes for the pull request.
|
||||
app_name (str): The name of the platform hosting the repository (e.g., "github", "gitlab", "bitbucket").
|
||||
base_repo_name (str): The full name of the target repository (in the format "user/repo") where the pull request will be created.
|
||||
head_repo_name (Optional[str]): The full name of the source repository (in the format "user/repo") from which the changes will be pulled.
|
||||
title (Optional[str]): The title of the pull request. Defaults to None.
|
||||
body (Optional[str]): The description or body content of the pull request. Defaults to None.
|
||||
issue (Optional[Issue]): An optional issue related to the pull request. Defaults to None.
|
||||
|
||||
Example:
|
||||
>>> # create pull request
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import subprocess
|
||||
import threading
|
||||
from queue import Queue
|
||||
import asyncio
|
||||
from asyncio import Queue
|
||||
from asyncio.subprocess import PIPE, STDOUT
|
||||
from typing import Optional
|
||||
|
||||
from metagpt.const import DEFAULT_WORKSPACE_ROOT, SWE_SETUP_PATH
|
||||
from metagpt.logs import logger
|
||||
from metagpt.tools.tool_registry import register_tool
|
||||
from metagpt.utils.report import END_MARKER_VALUE, TerminalReporter
|
||||
|
||||
|
|
@ -19,62 +21,54 @@ class Terminal:
|
|||
def __init__(self):
|
||||
self.shell_command = ["bash"] # FIXME: should consider windows support later
|
||||
self.command_terminator = "\n"
|
||||
|
||||
# Start a persistent shell process
|
||||
self.process = subprocess.Popen(
|
||||
self.shell_command,
|
||||
shell=True,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
executable="/bin/bash",
|
||||
)
|
||||
self.stdout_queue = Queue()
|
||||
self.stdout_queue = Queue(maxsize=1000)
|
||||
self.observer = TerminalReporter()
|
||||
self.process: Optional[asyncio.subprocess.Process] = None
|
||||
|
||||
self._check_state()
|
||||
async def _start_process(self):
|
||||
# Start a persistent shell process
|
||||
self.process = await asyncio.create_subprocess_exec(
|
||||
*self.shell_command, stdin=PIPE, stdout=PIPE, stderr=STDOUT, executable="bash"
|
||||
)
|
||||
await self._check_state()
|
||||
|
||||
def _check_state(self):
|
||||
"""Check the state of the terminal, e.g. the current directory of the terminal process. Useful for agent to understand."""
|
||||
print("The terminal is at:", self.run_command("pwd"))
|
||||
async def _check_state(self):
|
||||
"""
|
||||
Check the state of the terminal, e.g. the current directory of the terminal process. Useful for agent to understand.
|
||||
"""
|
||||
output = await self.run_command("pwd")
|
||||
logger.info("The terminal is at:", output)
|
||||
|
||||
def run_command(self, cmd: str, daemon=False) -> str:
|
||||
async def run_command(self, cmd: str, daemon=False) -> str:
|
||||
"""
|
||||
Executes a specified command in the terminal and streams the output back in real time.
|
||||
This command maintains state across executions, such as the current directory,
|
||||
allowing for sequential commands to be contextually aware. The output from the
|
||||
command execution is placed into `stdout_queue`, which can be consumed as needed.
|
||||
allowing for sequential commands to be contextually aware.
|
||||
|
||||
Args:
|
||||
cmd (str): The command to execute in the terminal.
|
||||
daemon (bool): If True, executes the command in a background thread, allowing
|
||||
the main program to continue execution. The command's output is
|
||||
collected asynchronously in daemon mode and placed into `stdout_queue`.
|
||||
|
||||
daemon (bool): If True, executes the command in an asynchronous task, allowing
|
||||
the main program to continue execution.
|
||||
Returns:
|
||||
str: The command's output or an empty string if `daemon` is True. Remember that
|
||||
when `daemon` is True, the output is collected into `stdout_queue` and must
|
||||
be consumed from there.
|
||||
|
||||
Note:
|
||||
If `stdout_queue` is not periodically consumed, it could potentially grow indefinitely,
|
||||
consuming memory. Ensure that there's a mechanism in place to consume this queue,
|
||||
especially during long-running or output-heavy command executions.
|
||||
when `daemon` is True, use the `get_stdout_output` method to get the output.
|
||||
"""
|
||||
if self.process is None:
|
||||
await self._start_process()
|
||||
|
||||
# Send the command
|
||||
self.process.stdin.write((cmd + self.command_terminator).encode())
|
||||
self.process.stdin.write(
|
||||
(f'echo "{END_MARKER_VALUE}"{self.command_terminator}').encode() # write EOF
|
||||
f'echo "{END_MARKER_VALUE}"{self.command_terminator}'.encode() # write EOF
|
||||
) # Unique marker to signal command end
|
||||
self.process.stdin.flush()
|
||||
await self.process.stdin.drain()
|
||||
if daemon:
|
||||
threading.Thread(target=self._read_and_process_output, args=(cmd,), daemon=True).start()
|
||||
asyncio.create_task(self._read_and_process_output(cmd))
|
||||
return ""
|
||||
else:
|
||||
return self._read_and_process_output(cmd)
|
||||
return await self._read_and_process_output(cmd)
|
||||
|
||||
def execute_in_conda_env(self, cmd: str, env, daemon=False) -> str:
|
||||
async def execute_in_conda_env(self, cmd: str, env, daemon=False) -> str:
|
||||
"""
|
||||
Executes a given command within a specified Conda environment automatically without
|
||||
the need for manual activation. Users just need to provide the name of the Conda
|
||||
|
|
@ -84,7 +78,7 @@ class Terminal:
|
|||
cmd (str): The command to execute within the Conda environment.
|
||||
env (str, optional): The name of the Conda environment to activate before executing the command.
|
||||
If not specified, the command will run in the current active environment.
|
||||
daemon (bool): If True, the command is run in a background thread, similar to `run_command`,
|
||||
daemon (bool): If True, the command is run in an asynchronous task, similar to `run_command`,
|
||||
affecting error logging and handling in the same manner.
|
||||
|
||||
Returns:
|
||||
|
|
@ -96,19 +90,34 @@ class Terminal:
|
|||
to ensure the specified environment is active for the command's execution.
|
||||
"""
|
||||
cmd = f"conda run -n {env} {cmd}"
|
||||
return self.run_command(cmd, daemon=daemon)
|
||||
return await self.run_command(cmd, daemon=daemon)
|
||||
|
||||
def _read_and_process_output(self, cmd):
|
||||
with self.observer as observer:
|
||||
async def get_stdout_output(self) -> str:
|
||||
"""
|
||||
Retrieves all collected output from background running commands and returns it as a string.
|
||||
|
||||
Returns:
|
||||
str: The collected output from background running commands, returned as a string.
|
||||
"""
|
||||
output_lines = []
|
||||
while not self.stdout_queue.empty():
|
||||
line = await self.stdout_queue.get()
|
||||
output_lines.append(line)
|
||||
return "\n".join(output_lines)
|
||||
|
||||
async def _read_and_process_output(self, cmd, daemon=False) -> str:
|
||||
async with self.observer as observer:
|
||||
cmd_output = []
|
||||
observer.report(cmd + self.command_terminator, "cmd")
|
||||
# report the comman
|
||||
await observer.async_report(cmd + self.command_terminator, "cmd")
|
||||
# report the command
|
||||
# Read the output until the unique marker is found.
|
||||
# We read bytes directly from stdout instead of text because when reading text,
|
||||
# '\r' is changed to '\n', resulting in excessive output.
|
||||
tmp = b""
|
||||
while True:
|
||||
output = tmp + self.process.stdout.read(1)
|
||||
output = tmp + await self.process.stdout.read(1)
|
||||
if not output:
|
||||
continue
|
||||
*lines, tmp = output.splitlines(True)
|
||||
for line in lines:
|
||||
line = line.decode()
|
||||
|
|
@ -116,20 +125,20 @@ class Terminal:
|
|||
if ix >= 0:
|
||||
line = line[0:ix]
|
||||
if line:
|
||||
observer.report(line, "output")
|
||||
await observer.async_report(line, "output")
|
||||
# report stdout in real-time
|
||||
cmd_output.append(line)
|
||||
return "".join(cmd_output)
|
||||
# log stdout in real-time
|
||||
observer.report(line, "output")
|
||||
await observer.async_report(line, "output")
|
||||
cmd_output.append(line)
|
||||
self.stdout_queue.put(line)
|
||||
if daemon:
|
||||
await self.stdout_queue.put(line)
|
||||
|
||||
def close(self):
|
||||
async def close(self):
|
||||
"""Close the persistent shell process."""
|
||||
self.process.stdin.close()
|
||||
self.process.terminate()
|
||||
self.process.wait()
|
||||
await self.process.wait()
|
||||
|
||||
|
||||
@register_tool(include_functions=["run"])
|
||||
|
|
@ -142,10 +151,13 @@ class Bash(Terminal):
|
|||
def __init__(self):
|
||||
"""init"""
|
||||
super().__init__()
|
||||
self.run_command(f"cd {DEFAULT_WORKSPACE_ROOT}")
|
||||
self.run_command(f"source {SWE_SETUP_PATH}")
|
||||
self.start_flag = False
|
||||
|
||||
def run(self, cmd) -> str:
|
||||
async def start(self):
|
||||
await self.run_command(f"cd {DEFAULT_WORKSPACE_ROOT}")
|
||||
await self.run_command(f"source {SWE_SETUP_PATH}")
|
||||
|
||||
async def run(self, cmd) -> str:
|
||||
"""
|
||||
Executes a bash command.
|
||||
|
||||
|
|
@ -184,9 +196,6 @@ class Bash(Terminal):
|
|||
Arguments:
|
||||
filename (str): The name of the file to create.
|
||||
|
||||
- submit
|
||||
Submits your current code. it can only be executed once, the last action before the `end`.
|
||||
|
||||
- search_dir_and_preview <search_term> [<dir>]
|
||||
Searches for search_term in all files in dir and gives their code preview
|
||||
with line numbers. If dir is not provided, searches in the current directory.
|
||||
|
|
@ -220,6 +229,13 @@ class Bash(Terminal):
|
|||
end_line (int): The line number to end the edit at (inclusive), starting from 1.
|
||||
replacement_text (str): The text to replace the current selection with, must conform to PEP8 standards.
|
||||
|
||||
- submit
|
||||
Submits your current code locally. it can only be executed once, the last action before the `end`.
|
||||
|
||||
Note: Make sure to use these functions as per their defined arguments and behaviors.
|
||||
"""
|
||||
return self.run_command(cmd)
|
||||
if not self.start_flag:
|
||||
await self.start()
|
||||
self.start_flag = True
|
||||
|
||||
return await self.run_command(cmd)
|
||||
|
|
|
|||
|
|
@ -17,4 +17,3 @@ source $REPO_ROOT_DIR/metagpt/tools/swe_agent_commands/search.sh
|
|||
source $REPO_ROOT_DIR/metagpt/tools/swe_agent_commands/edit_linting.sh
|
||||
|
||||
export SWE_CMD_WORK_DIR="$REPO_ROOT_DIR/workspace/swe_agent_workdir"
|
||||
#sudo chmod 777 $REPO_ROOT_DIR/workspace/swe_agent_workdir
|
||||
|
|
|
|||
19
metagpt/utils/proxy_env.py
Normal file
19
metagpt/utils/proxy_env.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import os
|
||||
|
||||
|
||||
def get_proxy_from_env():
|
||||
proxy_config = {}
|
||||
server = None
|
||||
for i in ("ALL_PROXY", "all_proxy", "HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"):
|
||||
if os.environ.get(i):
|
||||
server = os.environ.get(i)
|
||||
if server:
|
||||
proxy_config["server"] = server
|
||||
no_proxy = os.environ.get("NO_PROXY") or os.environ.get("no_proxy")
|
||||
if no_proxy:
|
||||
proxy_config["bypass"] = no_proxy
|
||||
|
||||
if not proxy_config:
|
||||
proxy_config = None
|
||||
|
||||
return proxy_config
|
||||
|
|
@ -266,10 +266,6 @@ class ThoughtReporter(ObjectReporter):
|
|||
|
||||
block: Literal[BlockType.THOUGHT] = BlockType.THOUGHT
|
||||
|
||||
async def __aenter__(self):
|
||||
await self.async_report({})
|
||||
return await super().__aenter__()
|
||||
|
||||
|
||||
class FileReporter(ResourceReporter):
|
||||
"""File resource callback for reporting complete file paths.
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ TOKEN_COSTS = {
|
|||
"gpt-4-0125-preview": {"prompt": 0.01, "completion": 0.03},
|
||||
"gpt-4-1106-preview": {"prompt": 0.01, "completion": 0.03},
|
||||
"gpt-4-vision-preview": {"prompt": 0.01, "completion": 0.03}, # TODO add extra image price calculator
|
||||
"gpt-4-1106-vision-preview": {"prompt": 0.01, "completion": 0.03},
|
||||
"gpt-4o": {"prompt": 0.005, "completion": 0.015},
|
||||
"gpt-4o-2024-05-13": {"prompt": 0.005, "completion": 0.015},
|
||||
"gpt-4-1106-vision-preview": {"prompt": 0.01, "completion": 0.03},
|
||||
"text-embedding-ada-002": {"prompt": 0.0004, "completion": 0.0},
|
||||
"glm-3-turbo": {"prompt": 0.0007, "completion": 0.0007}, # 128k version, prompt + completion tokens=0.005¥/k-tokens
|
||||
"glm-4": {"prompt": 0.014, "completion": 0.014}, # 128k version, prompt + completion tokens=0.1¥/k-tokens
|
||||
|
|
@ -147,13 +147,14 @@ FIREWORKS_GRADE_TOKEN_COSTS = {
|
|||
|
||||
# https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo
|
||||
TOKEN_MAX = {
|
||||
"gpt-4o-2024-05-13": 128000,
|
||||
"gpt-4o": 128000,
|
||||
"gpt-4-0125-preview": 128000,
|
||||
"gpt-4-turbo-preview": 128000,
|
||||
"gpt-4-1106-preview": 128000,
|
||||
"gpt-4-vision-preview": 128000,
|
||||
"gpt-4-1106-vision-preview": 128000,
|
||||
"gpt-4-turbo": 128000,
|
||||
"gpt-4o": 128000,
|
||||
"gpt-4": 8192,
|
||||
"gpt-4-0613": 8192,
|
||||
"gpt-4-32k": 32768,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import asyncio
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
|
||||
from metagpt.environment.mgx.mgx_env import MGXEnv
|
||||
from metagpt.roles import Architect, Engineer, ProductManager, ProjectManager
|
||||
|
|
@ -11,7 +13,7 @@ from metagpt.roles.di.team_leader import TeamLeader
|
|||
from metagpt.schema import Message
|
||||
|
||||
|
||||
async def main(requirement="", enable_human_input=False, use_fixed_sop=False):
|
||||
async def main(requirement="", enable_human_input=False, use_fixed_sop=False, allow_idle_time=30):
|
||||
if use_fixed_sop:
|
||||
engineer = Engineer(n_borg=5, use_code_review=False)
|
||||
else:
|
||||
|
|
@ -33,30 +35,50 @@ async def main(requirement="", enable_human_input=False, use_fixed_sop=False):
|
|||
|
||||
if enable_human_input:
|
||||
# simulate human sending messages in chatbox
|
||||
send_human_input(env)
|
||||
stop_event = threading.Event()
|
||||
human_input_thread = send_human_input(env, stop_event)
|
||||
|
||||
if requirement:
|
||||
env.publish_message(Message(content=requirement))
|
||||
# env.publish_message(Message(content=requirement, send_to={"David"}), user_defined_recipient="David")
|
||||
# user_defined_recipient = "Alex"
|
||||
# env.publish_message(Message(content=requirement, send_to={user_defined_recipient}), user_defined_recipient=user_defined_recipient)
|
||||
|
||||
while not env.is_idle:
|
||||
await env.run()
|
||||
allow_idle_time = allow_idle_time if enable_human_input else 1
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < allow_idle_time:
|
||||
if not env.is_idle:
|
||||
await env.run()
|
||||
start_time = time.time() # reset start time
|
||||
|
||||
if enable_human_input:
|
||||
print("No more human input, terminating, press ENTER for a full termination.")
|
||||
stop_event.set()
|
||||
human_input_thread.join()
|
||||
|
||||
|
||||
def send_human_input(env):
|
||||
def send_human_input(env, stop_event):
|
||||
"""
|
||||
Simulate sending message in chatbox
|
||||
Note in local environment, the message is consumed only after current round of env.run is finished
|
||||
"""
|
||||
|
||||
def send_messages():
|
||||
while True:
|
||||
while not stop_event.is_set():
|
||||
message = input("Enter a message any time: ")
|
||||
env.publish_message(Message(content=message))
|
||||
user_defined_recipient = re.search(r"@(\w+)", message)
|
||||
if user_defined_recipient:
|
||||
recipient_name = user_defined_recipient.group(1)
|
||||
print(f"{recipient_name} will receive the message")
|
||||
env.publish_message(
|
||||
Message(content=message, send_to={recipient_name}), user_defined_recipient=recipient_name
|
||||
)
|
||||
else:
|
||||
env.publish_message(Message(content=message))
|
||||
|
||||
# Start a thread for sending messages
|
||||
send_thread = threading.Thread(target=send_messages, args=())
|
||||
send_thread.start()
|
||||
return send_thread
|
||||
|
||||
|
||||
GAME_REQ = "create a 2048 game"
|
||||
|
|
@ -102,6 +124,16 @@ clone https://github.com/garylin2099/simple_calculator, checkout a new branch na
|
|||
Commit your changes and push, finally, create a PR to the master branch of https://github.com/mannaandpoem/simple_calculator.
|
||||
"""
|
||||
|
||||
TL_CHAT1 = """Summarize the paper for me""" # expecting clarification
|
||||
TL_CHAT2 = """Solve the issue at this link""" # expecting clarification
|
||||
TL_CHAT3 = """Who is the first man landing on Moon""" # expecting answering directly
|
||||
TL_CHAT4 = """Find all zeros in the indicated finite field of the given polynomial with coefficients in that field. x^5 + 3x^3 + x^2 + 2x in Z_5""" # expecting answering directly
|
||||
TL_CHAT5 = """Find the degree for the given field extension Q(sqrt(2), sqrt(3), sqrt(18)) over Q.""" # expecting answering directly
|
||||
TL_CHAT6 = """True or False? Statement 1 | A ring homomorphism is one to one if and only if the kernel is {{0}},. Statement 2 | Q is an ideal in R""" # expecting answering directly
|
||||
TL_CHAT7 = """Jean has 30 lollipops. Jean eats 2 of the lollipops. With the remaining lollipops, Jean wants to package 2 lollipops in one bag. How many bags can Jean fill?""" # expecting answering directly
|
||||
TL_CHAT9 = """What's your name?"""
|
||||
TL_CHAT10 = "Hi"
|
||||
TL_CHAT11 = "Tell me about your team"
|
||||
|
||||
if __name__ == "__main__":
|
||||
# NOTE: Add access_token to test github issue fixing
|
||||
|
|
|
|||
|
|
@ -59,11 +59,11 @@ async def run(instance, swe_result_dir):
|
|||
|
||||
# 前处理
|
||||
terminal = Terminal()
|
||||
terminal.run_command(f"cd {repo_path} && git reset --hard && git clean -n -d && git clean -f -d")
|
||||
terminal.run_command("BRANCH=$(git remote show origin | awk '/HEAD branch/ {print $NF}')")
|
||||
logger.info(terminal.run_command("echo $BRANCH"))
|
||||
logger.info(terminal.run_command('git checkout "$BRANCH"'))
|
||||
logger.info(terminal.run_command("git branch"))
|
||||
await terminal.run_command(f"cd {repo_path} && git reset --hard && git clean -n -d && git clean -f -d")
|
||||
await terminal.run_command("BRANCH=$(git remote show origin | awk '/HEAD branch/ {print $NF}')")
|
||||
logger.info(await terminal.run_command("echo $BRANCH"))
|
||||
logger.info(await terminal.run_command('git checkout "$BRANCH"'))
|
||||
logger.info(await terminal.run_command("git branch"))
|
||||
|
||||
user_requirement_and_issue = INSTANCE_TEMPLATE.format(
|
||||
issue=instance["problem_statement"],
|
||||
|
|
|
|||
|
|
@ -4,16 +4,17 @@ from metagpt.const import DATA_PATH, METAGPT_ROOT
|
|||
from metagpt.tools.libs.terminal import Terminal
|
||||
|
||||
|
||||
def test_terminal():
|
||||
@pytest.mark.asyncio
|
||||
async def test_terminal():
|
||||
terminal = Terminal()
|
||||
|
||||
terminal.run_command(f"cd {METAGPT_ROOT}")
|
||||
output = terminal.run_command("pwd")
|
||||
await terminal.run_command(f"cd {METAGPT_ROOT}")
|
||||
output = await terminal.run_command("pwd")
|
||||
assert output.strip() == str(METAGPT_ROOT)
|
||||
|
||||
# pwd now should be METAGPT_ROOT, cd data should land in DATA_PATH
|
||||
terminal.run_command("cd data")
|
||||
output = terminal.run_command("pwd")
|
||||
await terminal.run_command("cd data")
|
||||
output = await terminal.run_command("pwd")
|
||||
assert output.strip() == str(DATA_PATH)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue