diff --git a/examples/exp_pool/init_exp_pool.py b/examples/exp_pool/init_exp_pool.py index 14c415be7..1601abe0b 100644 --- a/examples/exp_pool/init_exp_pool.py +++ b/examples/exp_pool/init_exp_pool.py @@ -85,7 +85,7 @@ def query_exps_count(): async def main(): - await add_exps_from_file("TeamLeader.llm_cached_aask", EXAMPLE_DATA_PATH / "exp_pool/team_leader_exps.json"), + await add_exps_from_file("TeamLeader.llm_cached_aask", EXAMPLE_DATA_PATH / "exp_pool/team_leader_exps.json") await add_exps_from_file("Engineer2.llm_cached_aask", EXAMPLE_DATA_PATH / "exp_pool/engineer_exps.json") query_exps_count() diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py index 62b0d435d..3eb8fe04d 100644 --- a/metagpt/actions/design_api.py +++ b/metagpt/actions/design_api.py @@ -279,4 +279,4 @@ class WriteDesign(Action): md_output_filename = output_pathname.with_suffix(".md") await save_json_to_markdown(content=design.content, output_filename=md_output_filename) await reporter.async_report(md_output_filename, "path") - return f'System Design filename: "{str(output_pathname)}"' + return f'System Design filename: "{str(output_pathname)}". \n The System Design has been completed.' diff --git a/metagpt/actions/design_api_an.py b/metagpt/actions/design_api_an.py index 5977cbd95..2541d0469 100644 --- a/metagpt/actions/design_api_an.py +++ b/metagpt/actions/design_api_an.py @@ -33,8 +33,8 @@ PROJECT_NAME = ActionNode( FILE_LIST = ActionNode( key="File list", expected_type=List[str], - instruction="Only need relative paths. ALWAYS write a main.py or app.py here", - example=["main.py", "game.py"], + instruction="Only need relative paths. Succinctly designate the correct entry file for your project based on the programming language: use main.js for JavaScript, main.py for Python, and so on for other languages.", + example=["a.js", "b.py", "c.css", "d.html"], ) REFINED_FILE_LIST = ActionNode( diff --git a/metagpt/actions/project_management_an.py b/metagpt/actions/project_management_an.py index 0417c0ce4..01b92b7fc 100644 --- a/metagpt/actions/project_management_an.py +++ b/metagpt/actions/project_management_an.py @@ -27,7 +27,8 @@ LOGIC_ANALYSIS = ActionNode( key="Logic Analysis", expected_type=List[List[str]], instruction="Provide a list of files with the classes/methods/functions to be implemented, " - "including dependency analysis and imports.", + "including dependency analysis and imports." + "Ensure consistency between System Design and Logic Analysis; the files must match exactly.", example=[ ["game.py", "Contains Game class and ... functions"], ["main.py", "Contains main function, from game import Game"], diff --git a/metagpt/actions/write_code_review.py b/metagpt/actions/write_code_review.py index e72fe5cd1..ca174f2ee 100644 --- a/metagpt/actions/write_code_review.py +++ b/metagpt/actions/write_code_review.py @@ -283,7 +283,10 @@ class ReviewAndRewriteCode(Action): await awrite(filename=code_path, data=code) - return code + return ( + f"The review and rewriting of the code in the file '{os.path.basename(code_path)}' has been completed." + + code + ) @staticmethod async def _try_aread(input: str) -> str: diff --git a/metagpt/actions/write_prd.py b/metagpt/actions/write_prd.py index 42154837a..ae1b5dd81 100644 --- a/metagpt/actions/write_prd.py +++ b/metagpt/actions/write_prd.py @@ -323,4 +323,4 @@ class WritePRD(Action): md_output_filename = output_pathname.with_suffix(".md") await save_json_to_markdown(content=new_prd.content, output_filename=md_output_filename) await reporter.async_report(md_output_filename, "path") - return f'PRD filename: "{str(output_pathname)}"' + return f'PRD filename: "{str(output_pathname)}". The product requirement document (PRD) has been completed.' diff --git a/metagpt/configs/llm_config.py b/metagpt/configs/llm_config.py index 39f6e61f1..48130eedc 100644 --- a/metagpt/configs/llm_config.py +++ b/metagpt/configs/llm_config.py @@ -32,6 +32,8 @@ class LLMType(Enum): MISTRAL = "mistral" YI = "yi" # lingyiwanwu OPEN_ROUTER = "open_router" + DEEPSEEK = "deepseek" + SILICONFLOW = "siliconflow" def __missing__(self, key): return self.OPENAI diff --git a/metagpt/prompts/di/engineer2.py b/metagpt/prompts/di/engineer2.py index c4dec8d5e..d81ef5691 100644 --- a/metagpt/prompts/di/engineer2.py +++ b/metagpt/prompts/di/engineer2.py @@ -11,7 +11,11 @@ EXTRA_INSTRUCTION = """ 11. Write out EVERY CODE DETAIL, DON'T LEAVE TODO. 12. To modify code in a file, read the entire file, make changes, and update the file with the complete code, ensuring that no line numbers are included in the final write. 13. When a system design or project schedule is provided, at the end of the plan, add a CodeRview Task for each file; for example, if there are three files, add three CodeRview Tasks. For each CodeRview Task, just call ReviewAndRewriteCode.run. -14. When you are making plan.it is hightly recommand to plan all the coding plan and reviews plan in first response. +14. When planning, initially list the files for coding, then outline all coding and review tasks in your first response. +15. Note 'Task for {file_name} completed.' — signifies the {file_name} coding task is done. +16. Avoid re-reviewing or re-coding the same code. When you decide to take a write or review action, include the command 'finish current task' in the same response. +17. When coding JavaScript, avoid using '\'' in strings. +18. If you plan to read a file, do not include other plans in the same response. """ diff --git a/metagpt/prompts/di/role_zero.py b/metagpt/prompts/di/role_zero.py index 408bb838d..d3f978c15 100644 --- a/metagpt/prompts/di/role_zero.py +++ b/metagpt/prompts/di/role_zero.py @@ -10,8 +10,11 @@ Note: 4. Don't forget to append task first when all existing tasks are finished and new tasks are required. 5. Avoid repeating tasks you have already completed. And end loop when all requirements are met. """ -# To ensure compatibility with hard-coded experience, do not add any other content between "# Example" and "# Available Commands". +# To ensure compatibility with hard-coded experience, do not add any other content between "# Example" and "# Instruction". CMD_PROMPT = """ +# Latest Observation +{latest_observation} + # Data Structure class Task(BaseModel): task_id: str = "" @@ -42,10 +45,16 @@ Special Command: Use {{"command_name": "end"}} to do nothing or indicate complet 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. If you finish current task, you will automatically take the next task in the existing plan, use Plan.finish_task, DON'T append a new task. -Pay close attention to what you have done. Be different with your previous action. +Review the latest plan's outcome, focusing on achievements. If your completed task matches the current, consider it finished. +In your response, include at least one command. # Your commands in a json array, in the following output format with correct command_name and args. If there is nothing to do, use the pass or end command: Some text indicating your thoughts before JSON is required, such as what tasks have been completed, what tasks are next, how you should update the plan status, respond to inquiry, or seek for help. Then a json array of commands. You must output ONE and ONLY ONE json array. DON'T output multiple json arrays with thoughts between them. +Output should adhere to the following format. +Firstly, describe the actions you have taken recently. +Secondly, describe the messages you have received recently, with a particular emphasis on messages from users. +Thirdly, describe your current task . Review the histroy, if you find that the current task is identical to a previously completed one, it indicates that the current task has already been accomplished. If all tasks are finished and current task is empty, use the end command to terminate. +Then, articulate your thoughts and list the commands, adhering closely to the instructions provided. ```json [ {{ @@ -67,12 +76,18 @@ JSON_REPAIR_PROMPT = """ ```json ``` +Do not use escape characters in json data, particularly within file paths. Help check if there are any formatting issues with the JSON data? If so, please help format it. +If no issues are detected, the original json data should be returned unchanged. +Output the JSON data in a format that can be loaded by the json.loads() function. """ 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: +Decide if the latest user message previously is a quick question. +Quick questions include common-sense, logical, math, multiple-choice questions, greetings, or casual chat that you can answer directly. +Questions about you or your team info are also quick questions. +Time- or location-sensitive questions such as wheather or news inquiry are NOT quick questions. +Software development tasks are NOT quick questions. +However, these programming-related tasks are quick questions: writing trivial code snippets (fewer than 30 lines), filling a single function or class, explaining concepts, writing tutorials and documentation. +Respond with a concise thought then a YES if the question is a quick question, otherwise, a NO. Your response: """ diff --git a/metagpt/prompts/di/swe_agent.py b/metagpt/prompts/di/swe_agent.py index fd95dc997..7455cf30a 100644 --- a/metagpt/prompts/di/swe_agent.py +++ b/metagpt/prompts/di/swe_agent.py @@ -124,12 +124,9 @@ Thought: The bug has been fixed. Let's submit the changes. ##### 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": "git_push", + "command_name": "Bash.run", "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" + "cmd": "git push origin test-fix" }} }} -> @@ -220,7 +217,7 @@ IMPORTANT_TIPS = """ - Based on feedback of observation or bash command in trajectory to guide adjustments in your search strategy. 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 need to submit changes to the remote repository, first use the regular git commit command to save the changes locally, then use git push for pushing, and if requested, `git_create_pull` in Available Commands for creating pull request. - If you don't need to submit code changes to the remote repository. use the command Bash.run('submit') to commit the changes locally. diff --git a/metagpt/prompts/di/team_leader.py b/metagpt/prompts/di/team_leader.py index 484727936..acd44d56c 100644 --- a/metagpt/prompts/di/team_leader.py +++ b/metagpt/prompts/di/team_leader.py @@ -13,15 +13,26 @@ When creating a new plan involving multiple members, create all tasks at once. If plan is created, you should track the progress based on team member feedback message, and update plan accordingly, such as Plan.finish_current_task, Plan.reset_task, Plan.replace_task, etc. You should use TeamLeader.publish_team_message to team members, asking them to start their task. DONT omit any necessary info such as path, link, environment, programming language, framework, requirement, constraint from original content to team members because you are their sole info source. Pay close attention to new user message, review the conversation history, use RoleZero.reply_to_human to respond to the user directly, DON'T ask your team members. - +Pay close attention to messages from team members. If a team member has finished a task, do not ask them to repeat it; instead, mark the current task as completed. Note: -1. If the requirement is a pure DATA-RELATED requirement, such as bug fixes, issue reporting, environment setup, terminal operations, pip install, web browsing, web scraping, web searching, web imitation, data science, data analysis, machine learning, deep learning, text-to-image etc. DON'T decompose it, assign a single task with the original user requirement as instruction directly to Data Analyst. +1. If the requirement is a pure DATA-RELATED requirement, such as web browsing, web scraping, web searching, web imitation, data science, data analysis, machine learning, deep learning, text-to-image etc. DON'T decompose it, assign a single task with the original user requirement as instruction directly to Data Analyst. 2. If the requirement is developing a software, game, app, or website, excluding the above data-related tasks, you should decompose the requirement into multiple tasks and assign them to different team members based on their expertise, usually the sequence of Product Manager -> Architect -> Project Manager -> Engineer -> (optional: QaEngine if present) -> (optional: DataAnalyst if user requests deployment), each assigned ONE task. When publishing message to Product Manager, you should directly copy the full original user requirement. 3. If the requirement contains both DATA-RELATED part mentioned in 1 and software development part mentioned in 2, you should decompose the software development part and assign them to different team members based on their expertise, and assign the DATA-RELATED part to Data Analyst David directly. 4. If the requirement is a common-sense, logical, or math problem, you should respond directly without assigning any task to team members. 5. If you think the requirement is not clear or ambiguous, you should ask the user for clarification immediately. Assign tasks only after all info is clear. 6. It is helpful for Engineer to have both the system design and the project schedule for writing the code, so include paths of both files (if available) and remind Engineer to definitely read them when publishing message to Engineer. 7. If the requirement is writing a TRD and software framework, you should assign it to Architect. When publishing message to Architect, you should directly copy the full original user requirement. +8. If the receiver message reads 'from {{team member}} to {{\'\'}}, it indicates that someone has completed the current task. Note this in your thoughts. +9. Do not use the 'end' command when the current task remains unfinished; instead, use the 'finish_current_task' command to indicate completion before switching to the next task. +10. If you have made a plan, simply follow it without creating a new one. +11. Do not use escape characters in json data, particularly within file paths. +""" + +QUICK_THINK_SYSTEM_PROMPT = """ +{role_info} +Your team member: +{team_info} +However, you MUST respond to the user message by yourself directly, DON'T ask your team members. """ FINISH_CURRENT_TASK_CMD = """ diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index a41c8b0a6..fa689d54f 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -49,6 +49,8 @@ from metagpt.utils.token_counter import ( LLMType.MISTRAL, LLMType.YI, LLMType.OPEN_ROUTER, + LLMType.DEEPSEEK, + LLMType.SILICONFLOW, ] ) class OpenAILLM(BaseLLM): @@ -105,6 +107,9 @@ class OpenAILLM(BaseLLM): elif hasattr(chunk.choices[0], "usage"): # The usage of some services is an attribute of chunk.choices[0], such as Moonshot usage = CompletionUsage(**chunk.choices[0].usage) + if "openrouter.ai" in self.config.base_url and hasattr(chunk, "usage") and chunk.usage is not None: + # due to it get token cost from api + usage = chunk.usage log_llm_stream("\n") full_reply_content = "".join(collected_messages) diff --git a/metagpt/roles/di/role_zero.py b/metagpt/roles/di/role_zero.py index 40bc2e3c7..40c03ccad 100644 --- a/metagpt/roles/di/role_zero.py +++ b/metagpt/roles/di/role_zero.py @@ -146,6 +146,7 @@ class RoleZero(Role): tool_info = json.dumps({tool.name: tool.schemas for tool in tools}) ### Make Decision Dynamically ### + memory = self.rc.memory.get(self.memory_k) instruction = self.instruction.strip() prompt = self.cmd_prompt.format( example=example, @@ -154,10 +155,9 @@ class RoleZero(Role): plan_status=plan_status, current_task=current_task, instruction=instruction, + latest_observation=memory[-1].content, ) - memory = self.rc.memory.get(self.memory_k) memory = await self.parse_browser_actions(memory) - req = self.llm.format_msg(memory + [UserMessage(content=prompt)]) async with ThoughtReporter(enable_llm_stream=True) as reporter: await reporter.async_report({"type": "react"}) @@ -169,7 +169,6 @@ class RoleZero(Role): self.command_rsp = await self.llm_cached_aask(req=req, system_msgs=self.system_msg, state_data=state_data) self.rc.memory.add(AIMessage(content=self.command_rsp)) - return True @exp_cache(context_builder=RoleZeroContextBuilder(), serializer=RoleZeroSerializer()) @@ -236,20 +235,21 @@ class RoleZero(Role): 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): + if self.rc.news[-1].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) + # routing + memory = self.get_memories(k=4) + context = self.llm.format_msg(memory + [UserMessage(content=QUICK_THINK_PROMPT)]) + rsp = await self.llm.aask(context) - pattern = r"#YES#,? ?" - if re.search(pattern, rsp): - answer = re.sub(pattern, "", rsp).strip() + if "yes" in rsp.lower(): + # llm call with the original context + async with ThoughtReporter(enable_llm_stream=True) as reporter: + await reporter.async_report({"type": "quick"}) + answer = await self.llm.aask(self.llm.format_msg(memory)) self.rc.memory.add(AIMessage(content=answer, cause_by=RunCommand)) await self.reply_to_human(content=answer) rsp_msg = AIMessage( @@ -339,7 +339,7 @@ class RoleZero(Role): elif cmd["command_name"] == "end": self._set_state(-1) - command_output = "Everything Done" + command_output = "" return command_output diff --git a/metagpt/roles/di/swe_agent.py b/metagpt/roles/di/swe_agent.py index e3cca3330..7be794265 100644 --- a/metagpt/roles/di/swe_agent.py +++ b/metagpt/roles/di/swe_agent.py @@ -9,7 +9,7 @@ from metagpt.prompts.di.swe_agent import ( SWE_AGENT_SYSTEM_TEMPLATE, ) from metagpt.roles.di.role_zero import RoleZero -from metagpt.tools.libs.git import git_create_pull, git_push +from metagpt.tools.libs.git import git_create_pull from metagpt.tools.libs.terminal import Bash @@ -23,7 +23,6 @@ class SWEAgent(RoleZero): "Bash", "Browser:goto,scroll", "RoleZero", - "git_push", "git_create_pull", ] terminal: Bash = Field(default_factory=Bash, exclude=True) @@ -42,7 +41,6 @@ class SWEAgent(RoleZero): self.tool_execution_map.update( { "Bash.run": self.terminal.run, - "git_push": git_push, "git_create_pull": git_create_pull, } ) diff --git a/metagpt/roles/di/team_leader.py b/metagpt/roles/di/team_leader.py index 7915edcf6..6dde4565e 100644 --- a/metagpt/roles/di/team_leader.py +++ b/metagpt/roles/di/team_leader.py @@ -3,6 +3,7 @@ from __future__ import annotations from metagpt.actions.di.run_command import RunCommand from metagpt.prompts.di.team_leader import ( FINISH_CURRENT_TASK_CMD, + QUICK_THINK_SYSTEM_PROMPT, SYSTEM_PROMPT, TL_INSTRUCTION, ) @@ -16,6 +17,7 @@ from metagpt.tools.tool_registry import register_tool class TeamLeader(RoleZero): name: str = "Mike" profile: str = "Team Leader" + goal: str = "Manage a team to assist users" system_msg: list[str] = [SYSTEM_PROMPT] # TeamLeader only reacts once each time, but may encounter errors or need to ask human, thus allowing 2 more turns @@ -33,16 +35,26 @@ class TeamLeader(RoleZero): } ) - def set_instruction(self): + def _get_team_info(self) -> str: + if not self.rc.env: + return "" team_info = "" for role in self.rc.env.roles.values(): # if role.profile == "Team Leader": # continue team_info += f"{role.name}: {role.profile}, {role.goal}\n" - self.instruction = TL_INSTRUCTION.format(team_info=team_info) + return team_info + + async def _quick_think(self) -> Message: + # insert team info for quick question + self.llm.system_prompt = QUICK_THINK_SYSTEM_PROMPT.format( + role_info=super()._get_prefix(), + team_info=self._get_team_info(), + ) + return await super()._quick_think() async def _think(self) -> bool: - self.set_instruction() + self.instruction = TL_INSTRUCTION.format(team_info=self._get_team_info()) return await super()._think() def publish_message(self, msg: Message, send_to="no one"): diff --git a/metagpt/roles/product_manager.py b/metagpt/roles/product_manager.py index 5f70bf390..7933e74b8 100644 --- a/metagpt/roles/product_manager.py +++ b/metagpt/roles/product_manager.py @@ -33,9 +33,9 @@ class ProductManager(RoleZero): constraints: str = "utilize the same language as the user requirements for seamless communication" todo_action: str = any_to_name(WritePRD) - instruction: str = """Use WritePRD tool to write PRD if a PRD is required; Use `Pic2Txt` tool to write out an intact textual user requirements if an intact textual user requiremnt is required given some images alongside the contextual textual descriptions;""" + instruction: str = """Use WritePRD tool to write PRD if a PRD is required, users may asks for a software without mentioning PRD, but you should output the PRD of that software; Use `Pic2Txt` tool to write out an intact textual user requirements if an intact textual user requiremnt is required given some images alongside the contextual textual descriptions""" max_react_loop: int = 1 # FIXME: Read and edit files requires more steps, consider later - tools: list[str] = ["Editor:write,read,write_content", "RoleZero", "WritePRD", Pic2Txt.__name__] + tools: list[str] = ["RoleZero", "WritePRD", Pic2Txt.__name__] def __init__(self, **kwargs) -> None: super().__init__(**kwargs) diff --git a/metagpt/strategy/experience_retriever.py b/metagpt/strategy/experience_retriever.py index b87f97f37..33f72df0e 100644 --- a/metagpt/strategy/experience_retriever.py +++ b/metagpt/strategy/experience_retriever.py @@ -611,7 +611,7 @@ Explanation: The user is asking for a general update on the project status. Give ] ``` -## example 4 +## example 5 OBSERVATION : current task is none and all task is finished. Explanation: Last task is "Plan.finish_current_task" or 'RoleZero.reply_to_human' and now the current task is none, it means everything is done.Just coutput command "end". ```json @@ -620,6 +620,29 @@ Explanation: Last task is "Plan.finish_current_task" or 'RoleZero.reply_to_human "command_name": "end" } ] + +## example 6 +OBSERVATION : The previously completed task is identical to the current task. +Explanation: The current task has been accomplished previously. +```json +[ + { + "command_name": "Plan.finish_current_task", + "args": {} + }, +] +``` + +## example 7 +OBSERVATION : the task assigned to Alice is still ongoing as it has not been marked as finished. The current task in the plan is for Alice to create the PRD. +Explanation: "I attempted to locate historical records containing 'send to []', and discovered an entry stating 'PRD is finished and masked.' This indicates that Alice's task has been completed. +```json +[ + { + "command_name": "Plan.finish_current_task", + "args": {} + }, +] ``` """ diff --git a/metagpt/tools/libs/editor.py b/metagpt/tools/libs/editor.py index 24eca65fc..4c6d2b1dc 100644 --- a/metagpt/tools/libs/editor.py +++ b/metagpt/tools/libs/editor.py @@ -36,9 +36,10 @@ class Editor: with open(path, "w", encoding="utf-8") as f: f.write(content) # self.resource.report(path, "path") + return f"The writing/coding the of the file {os.path.basename(path)}' is now completed. The file '{os.path.basename(path)}' has been successfully created." def read(self, path: str) -> FileBlock: - """Read the whole content of a file. It is strongly advised to utilize absolute paths""" + """Read the whole content of a file. Using absolute paths as the argument for specifying the file location.""" with open(path, "r") as f: self.resource.report(path, "path") lines = f.readlines() diff --git a/metagpt/tools/libs/git.py b/metagpt/tools/libs/git.py index a2fd87a92..9a33ee4c1 100644 --- a/metagpt/tools/libs/git.py +++ b/metagpt/tools/libs/git.py @@ -2,8 +2,7 @@ # -*- coding: utf-8 -*- from __future__ import annotations -from pathlib import Path -from typing import Optional, Union +from typing import Optional from github.Issue import Issue from github.PullRequest import PullRequest @@ -11,56 +10,6 @@ from github.PullRequest import PullRequest from metagpt.tools.tool_registry import register_tool -@register_tool(tags=["software development", "git", "Push to remote git repository."]) -async def git_push( - local_path: Union[str, Path], - app_name: str, - comments: str = "Commit", - new_branch: str = "", -) -> "GitBranch": - """ - Pushes changes from a local Git repository to its remote counterpart. - - Args: - 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 = "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 - >>> head = branch.head - >>> 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 - - if not GitRepository.is_git_dir(local_path): - raise ValueError("Invalid local git repository") - - repo = GitRepository(local_path=local_path, auto_init=False) - # Read access token from environment variables. - access_token = await get_env(key="access_token", app_name=app_name) - branch = await repo.push(new_branch=new_branch, comments=comments, access_token=access_token) - return branch - - @register_tool(tags=["software development", "git", "create a git pull request or merge request"]) async def git_create_pull( base: str, diff --git a/metagpt/utils/common.py b/metagpt/utils/common.py index e8f150556..eea16bb2e 100644 --- a/metagpt/utils/common.py +++ b/metagpt/utils/common.py @@ -660,7 +660,7 @@ def role_raise_decorator(func): if re.match(r"^openai\.", name) or re.match(r"^httpx\.", name): raise last_error - raise Exception(format_trackback_info(limit=None)) + raise Exception(format_trackback_info(limit=None)) from e return wrapper diff --git a/metagpt/utils/token_counter.py b/metagpt/utils/token_counter.py index 1019a17af..5be6b5f61 100644 --- a/metagpt/utils/token_counter.py +++ b/metagpt/utils/token_counter.py @@ -13,6 +13,7 @@ ref5: https://ai.google.dev/models/gemini import tiktoken TOKEN_COSTS = { + "anthropic/claude-3.5-sonnet": {"prompt": 0.003, "completion": 0.015}, "gpt-3.5-turbo": {"prompt": 0.0015, "completion": 0.002}, "gpt-3.5-turbo-0301": {"prompt": 0.0015, "completion": 0.002}, "gpt-3.5-turbo-0613": {"prompt": 0.0015, "completion": 0.002}, @@ -31,9 +32,11 @@ 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}, + "gpt-4o-mini": {"prompt": 0.00015, "completion": 0.0006}, + "gpt-4o-mini-2024-07-18": {"prompt": 0.00015, "completion": 0.0006}, "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 @@ -53,6 +56,18 @@ TOKEN_COSTS = { "claude-3-opus-20240229": {"prompt": 0.015, "completion": 0.075}, "yi-34b-chat-0205": {"prompt": 0.0003, "completion": 0.0003}, "yi-34b-chat-200k": {"prompt": 0.0017, "completion": 0.0017}, + "openai/gpt-4": {"prompt": 0.03, "completion": 0.06}, # start, for openrouter + "openai/gpt-4-turbo": {"prompt": 0.01, "completion": 0.03}, + "openai/gpt-4o": {"prompt": 0.005, "completion": 0.015}, + "openai/gpt-4o-2024-05-13": {"prompt": 0.005, "completion": 0.015}, + "openai/gpt-4o-mini": {"prompt": 0.00015, "completion": 0.0006}, + "openai/gpt-4o-mini-2024-07-18": {"prompt": 0.00015, "completion": 0.0006}, + "google/gemini-pro-1.5": {"prompt": 0.0025, "completion": 0.0075}, + "google/gemini-flash-1.5": {"prompt": 0.00025, "completion": 0.00075}, + "deepseek/deepseek-coder": {"prompt": 0.00014, "completion": 0.00028}, + "deepseek/deepseek-chat": {"prompt": 0.00014, "completion": 0.00028}, # end, for openrouter + "deepseek-chat": {"prompt": 0.00014, "completion": 0.00028}, + "deepseek-coder": {"prompt": 0.00014, "completion": 0.00028}, } @@ -149,6 +164,8 @@ FIREWORKS_GRADE_TOKEN_COSTS = { TOKEN_MAX = { "gpt-4o-2024-05-13": 128000, "gpt-4o": 128000, + "gpt-4o-mini": 128000, + "gpt-4o-mini-2024-07-18": 128000, "gpt-4-0125-preview": 128000, "gpt-4-turbo-preview": 128000, "gpt-4-1106-preview": 128000, @@ -185,6 +202,19 @@ TOKEN_MAX = { "claude-3-opus-20240229": 200000, "yi-34b-chat-0205": 4000, "yi-34b-chat-200k": 200000, + "openai/gpt-4": 8192, # start, for openrouter + "openai/gpt-4-turbo": 128000, + "openai/gpt-4o": 128000, + "openai/gpt-4o-2024-05-13": 128000, + "openai/gpt-4o-mini": 128000, + "openai/gpt-4o-mini-2024-07-18": 128000, + "anthropic/claude-3.5-sonnet": 200000, + "google/gemini-pro-1.5": 2800000, + "google/gemini-flash-1.5": 2800000, + "deepseek/deepseek-coder": 128000, + "deepseek/deepseek-chat": 128000, # end, for openrouter + "deepseek-chat": 128000, + "deepseek-coder": 128000, } @@ -214,6 +244,8 @@ def count_message_tokens(messages, model="gpt-3.5-turbo-0125"): "gpt-4-1106-vision-preview", "gpt-4o-2024-05-13", "gpt-4o", + "gpt-4o-mini", + "gpt-4o-mini-2024-07-18", }: tokens_per_message = 3 # # every reply is primed with <|start|>assistant<|message|> tokens_per_name = 1 diff --git a/tests/metagpt/roles/di/test_routing.py b/tests/metagpt/roles/di/test_routing.py new file mode 100644 index 000000000..c476fe9ad --- /dev/null +++ b/tests/metagpt/roles/di/test_routing.py @@ -0,0 +1,125 @@ +import asyncio + +from metagpt.environment.mgx.mgx_env import MGXEnv +from metagpt.logs import logger +from metagpt.roles import Architect, ProductManager, ProjectManager +from metagpt.roles.di.data_analyst import DataAnalyst +from metagpt.roles.di.engineer2 import Engineer2 +from metagpt.roles.di.team_leader import TeamLeader +from metagpt.schema import Message + +NORMAL_QUESTION = [ + "create a 2048 game", + "write a snake game", + "Write a 2048 game using JavaScript without using any frameworks, user can play with keyboard.", + "print statistic summary of sklearn iris dataset", + "Run data analysis on sklearn Wine recognition dataset, and train a model to predict wine class (20% as validation), and show validation accuracy.", + """ + Get data from `paperlist` table in https://papercopilot.com/statistics/iclr-statistics/iclr-2024-statistics/, + and save it to a csv file. paper title must include `multiagent` or `large language model`. *notice: print key variables* + """, + """ + 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 + """, + ## info searching ## + """When is the Olympic football final this year, where will it be held, and where can I buy tickets? If possible, please provide me with a link to buy tickets""", + """Help me search for Inter Miami CF home games in the next 2 months and give me the link to buy tickets""", + """请为我查找位于深圳大学附近1000米范围内,价格适中(性价比最高),且晚上关门时间晚于22:00的健身房。""", + "今天的天气怎样", + "奥运会的开幕式是什么时候", +] + +QUICK_QUESTION = [ + ## general knowledge qa, logical, math ## + """Who is the first man landing on Moon""", + """In DNA adenine normally pairs with: A. cytosine. B. guanine. C. thymine. D. uracil. Answer:""", + """________________ occur(s) where there is no prior history of exchange and no future exchanges are expected between a buyer and seller. A. Relationship marketing. B. Service mix. C. Market exchanges. D. Service failure. Answer:""", + """Within American politics, the power to accord official recognition to other countries belongs to A. the Senate. B. the president. C. the Secretary of State. D. the chairman of the Joint Chiefs. Answer:""", + """Find the degree for the given field extension Q(sqrt(2), sqrt(3), sqrt(18)) over Q.""", + """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""", + """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?""", + """Alisa biked 12 miles per hour for 4.5 hours. Stanley biked at 10 miles per hour for 2.5 hours. How many miles did Alisa and Stanley bike in total?""", + ## function filling (humaneval) ## + """ + def has_close_elements(numbers: List[float], threshold: float) -> bool: + ''' Check if in given list of numbers, are any two numbers closer to each other than + given threshold. + >>> has_close_elements([1.0, 2.0, 3.0], 0.5) + False + >>> has_close_elements([1.0, 2.8, 3.0, 4.0, 5.0, 2.0], 0.3) + True + ''' + """, + """ + def is_palindrome(string: str) -> bool: + ''' Test if given string is a palindrome ''' + return string == string[::-1] + + + def make_palindrome(string: str) -> str: + ''' Find the shortest palindrome that begins with a supplied string. + Algorithm idea is simple: + - Find the longest postfix of supplied string that is a palindrome. + - Append to the end of the string reverse of a string prefix that comes before the palindromic suffix. + >>> make_palindrome('') + '' + >>> make_palindrome('cat') + 'catac' + >>> make_palindrome('cata') + 'catac' + ''' + """, + # casual chat + """What's your name?""", + "Who are you", + "What can you do", + "Hi", + "1+1", + # programming-related but not requiring software development SOP + "请写一个python入门教程", + "python里的装饰器是怎么用的,给我个例子", + "写一个java的hello world程序", +] + + +async def test_routing_acc(): + role = TeamLeader() + + env = MGXEnv() + env.add_roles( + [ + role, + ProductManager(), + Architect(), + ProjectManager(), + Engineer2(), + DataAnalyst(), + ] + ) + + for q in QUICK_QUESTION: + msg = Message(content=q) + role.put_message(msg) + # await env.run() + await role._observe() + rsp = await role._quick_think() + role.rc.memory.clear() + if not rsp: + logger.error(f"Quick question failed: {q}") + # assert rsp + + for q in NORMAL_QUESTION: + msg = Message(content=q) + role.put_message(msg) + await role._observe() + rsp = await role._quick_think() + role.rc.memory.clear() + # assert not rsp + if rsp: + logger.error(f"Normal question failed: {q}") + + +if __name__ == "__main__": + asyncio.run(test_routing_acc())