mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-06-11 15:15:18 +02:00
Merge branch 'main' into 'mgx_ops'
Routinely merge main See merge request pub/MetaGPT!18
This commit is contained in:
commit
3b9feeec3f
11 changed files with 115 additions and 33 deletions
11
README.md
11
README.md
|
|
@ -26,6 +26,8 @@ # MetaGPT: The Multi-Agent Framework
|
|||
</p>
|
||||
|
||||
## News
|
||||
🚀 Mar. 29, 2024: [v0.8.0](https://github.com/geekan/MetaGPT/releases/tag/v0.8.0) released. Now you can use Data Interpreter via pypi package import. Meanwhile, we integrated RAG module and supported multiple new LLMs.
|
||||
|
||||
🚀 Mar. 14, 2024: Our **Data Interpreter** paper is on [arxiv](https://arxiv.org/abs/2402.18679). Check the [example](https://docs.deepwisdom.ai/main/en/DataInterpreter/) and [code](https://github.com/geekan/MetaGPT/tree/main/examples/di)!
|
||||
|
||||
🚀 Feb. 08, 2024: [v0.7.0](https://github.com/geekan/MetaGPT/releases/tag/v0.7.0) released, supporting assigning different LLMs to different Roles. We also introduced [Data Interpreter](https://github.com/geekan/MetaGPT/blob/main/examples/di/README.md), a powerful agent capable of solving a wide range of real-world problems.
|
||||
|
|
@ -145,10 +147,13 @@ ## Tutorial
|
|||
|
||||
## Support
|
||||
|
||||
### Discard Join US
|
||||
📢 Join Our [Discord Channel](https://discord.gg/ZRHeExS6xv)!
|
||||
### Discord Join US
|
||||
|
||||
Looking forward to seeing you there! 🎉
|
||||
📢 Join Our [Discord Channel](https://discord.gg/ZRHeExS6xv)! Looking forward to seeing you there! 🎉
|
||||
|
||||
### Contributor form
|
||||
|
||||
📝 [Fill out the form](https://airtable.com/appInfdG0eJ9J4NNL/pagK3Fh1sGclBvVkV/form) to become a contributor. We are looking forward to your participation!
|
||||
|
||||
### Contact Information
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ from metagpt.schema import Message
|
|||
|
||||
class SimpleWriteCode(Action):
|
||||
PROMPT_TEMPLATE: str = """
|
||||
Write a python function that can {instruction} and provide two runnnable test cases.
|
||||
Write a python function that can {instruction} and provide two runnable test cases.
|
||||
Return ```python your_code_here ``` with NO other texts,
|
||||
your code:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -128,6 +128,9 @@ CODE_PLAN_AND_CHANGE_CONTEXT = """
|
|||
## User New Requirements
|
||||
{requirement}
|
||||
|
||||
## Issue
|
||||
{issue}
|
||||
|
||||
## PRD
|
||||
{prd}
|
||||
|
||||
|
|
@ -211,7 +214,8 @@ class WriteCodePlanAndChange(Action):
|
|||
design_doc = await self.repo.docs.system_design.get(filename=self.i_context.design_filename)
|
||||
task_doc = await self.repo.docs.task.get(filename=self.i_context.task_filename)
|
||||
context = CODE_PLAN_AND_CHANGE_CONTEXT.format(
|
||||
requirement=self.i_context.requirement,
|
||||
requirement=f"```text\n{self.i_context.requirement}\n```",
|
||||
issue=f"```text\n{self.i_context.issue}\n```",
|
||||
prd=prd_doc.content,
|
||||
design=design_doc.content,
|
||||
task=task_doc.content,
|
||||
|
|
|
|||
|
|
@ -133,10 +133,10 @@ REQUIREMENT_ANALYSIS = ActionNode(
|
|||
REFINED_REQUIREMENT_ANALYSIS = ActionNode(
|
||||
key="Refined Requirement Analysis",
|
||||
expected_type=List[str],
|
||||
instruction="Review and refine the existing requirement analysis to align with the evolving needs of the project "
|
||||
instruction="Review and refine the existing requirement analysis into a string list to align with the evolving needs of the project "
|
||||
"due to incremental development. Ensure the analysis comprehensively covers the new features and enhancements "
|
||||
"required for the refined project scope.",
|
||||
example=["Require add/update/modify ..."],
|
||||
example=["Require add ...", "Require modify ..."],
|
||||
)
|
||||
|
||||
REQUIREMENT_POOL = ActionNode(
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
|
@ -78,12 +78,6 @@ class Context(BaseModel):
|
|||
# env.update({k: v for k, v in i.items() if isinstance(v, str)})
|
||||
return env
|
||||
|
||||
# def use_llm(self, name: Optional[str] = None, provider: LLMType = LLMType.OPENAI) -> BaseLLM:
|
||||
# """Use a LLM instance"""
|
||||
# self._llm_config = self.config.get_llm_config(name, provider)
|
||||
# self._llm = None
|
||||
# return self._llm
|
||||
|
||||
def _select_costmanager(self, llm_config: LLMConfig) -> CostManager:
|
||||
"""Return a CostManager instance"""
|
||||
if llm_config.api_type == LLMType.FIREWORKS:
|
||||
|
|
@ -108,3 +102,38 @@ class Context(BaseModel):
|
|||
if llm.cost_manager is None:
|
||||
llm.cost_manager = self._select_costmanager(llm_config)
|
||||
return llm
|
||||
|
||||
def serialize(self) -> Dict[str, Any]:
|
||||
"""Serialize the object's attributes into a dictionary.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: A dictionary containing serialized data.
|
||||
"""
|
||||
return {
|
||||
"workdir": str(self.repo.workdir) if self.repo else "",
|
||||
"kwargs": {k: v for k, v in self.kwargs.__dict__.items()},
|
||||
"cost_manager": self.cost_manager.model_dump_json(),
|
||||
}
|
||||
|
||||
def deserialize(self, serialized_data: Dict[str, Any]):
|
||||
"""Deserialize the given serialized data and update the object's attributes accordingly.
|
||||
|
||||
Args:
|
||||
serialized_data (Dict[str, Any]): A dictionary containing serialized data.
|
||||
"""
|
||||
if not serialized_data:
|
||||
return
|
||||
workdir = serialized_data.get("workdir")
|
||||
if workdir:
|
||||
self.git_repo = GitRepository(local_path=workdir, auto_init=True)
|
||||
self.repo = ProjectRepo(self.git_repo)
|
||||
src_workspace = self.git_repo.workdir / self.git_repo.workdir.name
|
||||
if src_workspace.exists():
|
||||
self.src_workspace = src_workspace
|
||||
kwargs = serialized_data.get("kwargs")
|
||||
if kwargs:
|
||||
for k, v in kwargs.items():
|
||||
self.kwargs.set(k, v)
|
||||
cost_manager = serialized_data.get("cost_manager")
|
||||
if cost_manager:
|
||||
self.cost_manager.model_validate_json(cost_manager)
|
||||
|
|
|
|||
|
|
@ -86,9 +86,13 @@ class DataInterpreter(Role):
|
|||
return Message(content=code, role="assistant", cause_by=WriteAnalysisCode)
|
||||
|
||||
async def _plan_and_act(self) -> Message:
|
||||
rsp = await super()._plan_and_act()
|
||||
await self.execute_code.terminate()
|
||||
return rsp
|
||||
try:
|
||||
rsp = await super()._plan_and_act()
|
||||
await self.execute_code.terminate()
|
||||
return rsp
|
||||
except Exception as e:
|
||||
await self.execute_code.terminate()
|
||||
raise e
|
||||
|
||||
async def _act_on_task(self, current_task: Task) -> TaskResult:
|
||||
"""Useful in 'plan_and_act' mode. Wrap the output in a TaskResult for review and confirmation."""
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ from __future__ import annotations
|
|||
import json
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Set
|
||||
from typing import Optional, Set
|
||||
|
||||
from metagpt.actions import Action, WriteCode, WriteCodeReview, WriteTasks
|
||||
from metagpt.actions.fix_bug import FixBug
|
||||
|
|
@ -30,6 +30,7 @@ from metagpt.actions.project_management_an import REFINED_TASK_LIST, TASK_LIST
|
|||
from metagpt.actions.summarize_code import SummarizeCode
|
||||
from metagpt.actions.write_code_plan_and_change_an import WriteCodePlanAndChange
|
||||
from metagpt.const import (
|
||||
BUGFIX_FILENAME,
|
||||
CODE_PLAN_AND_CHANGE_FILE_REPO,
|
||||
REQUIREMENT_FILENAME,
|
||||
SYSTEM_DESIGN_FILE_REPO,
|
||||
|
|
@ -208,9 +209,9 @@ class Engineer(Role):
|
|||
code_plan_and_change = node.instruct_content.model_dump_json()
|
||||
dependencies = {
|
||||
REQUIREMENT_FILENAME,
|
||||
self.rc.todo.i_context.prd_filename,
|
||||
self.rc.todo.i_context.design_filename,
|
||||
self.rc.todo.i_context.task_filename,
|
||||
str(self.project_repo.docs.prd.root_path / self.rc.todo.i_context.prd_filename),
|
||||
str(self.project_repo.docs.system_design.root_path / self.rc.todo.i_context.design_filename),
|
||||
str(self.project_repo.docs.task.root_path / self.rc.todo.i_context.task_filename),
|
||||
}
|
||||
code_plan_and_change_filepath = Path(self.rc.todo.i_context.design_filename)
|
||||
await self.project_repo.docs.code_plan_and_change.save(
|
||||
|
|
@ -248,11 +249,11 @@ class Engineer(Role):
|
|||
msg = self.rc.news[0]
|
||||
if self.config.inc and msg.cause_by in write_plan_and_change_filters:
|
||||
logger.debug(f"TODO WriteCodePlanAndChange:{msg.model_dump_json()}")
|
||||
await self._new_code_plan_and_change_action()
|
||||
await self._new_code_plan_and_change_action(cause_by=msg.cause_by)
|
||||
return self.rc.todo
|
||||
if msg.cause_by in write_code_filters:
|
||||
logger.debug(f"TODO WriteCode:{msg.model_dump_json()}")
|
||||
await self._new_code_actions(bug_fix=msg.cause_by == any_to_str(FixBug))
|
||||
await self._new_code_actions()
|
||||
return self.rc.todo
|
||||
if msg.cause_by in summarize_code_filters and msg.sent_from == any_to_str(self):
|
||||
logger.debug(f"TODO SummarizeCode:{msg.model_dump_json()}")
|
||||
|
|
@ -267,7 +268,7 @@ class Engineer(Role):
|
|||
dependencies = {Path(i) for i in await dependency.get(old_code_doc.root_relative_path)}
|
||||
task_doc = None
|
||||
design_doc = None
|
||||
code_plan_and_change_doc = None
|
||||
code_plan_and_change_doc = await self._get_any_code_plan_and_change() if await self._is_fixbug() else None
|
||||
for i in dependencies:
|
||||
if str(i.parent) == TASK_FILE_REPO:
|
||||
task_doc = await self.project_repo.docs.task.get(i.name)
|
||||
|
|
@ -294,7 +295,8 @@ class Engineer(Role):
|
|||
)
|
||||
return coding_doc
|
||||
|
||||
async def _new_code_actions(self, bug_fix=False):
|
||||
async def _new_code_actions(self):
|
||||
bug_fix = await self._is_fixbug()
|
||||
# Prepare file repos
|
||||
changed_src_files = self.project_repo.srcs.all_files if bug_fix else self.project_repo.srcs.changed_files
|
||||
changed_task_files = self.project_repo.docs.task.changed_files
|
||||
|
|
@ -371,15 +373,32 @@ class Engineer(Role):
|
|||
self.set_todo(self.summarize_todos[0])
|
||||
self.summarize_todos.pop(0)
|
||||
|
||||
async def _new_code_plan_and_change_action(self):
|
||||
async def _new_code_plan_and_change_action(self, cause_by: str):
|
||||
"""Create a WriteCodePlanAndChange action for subsequent to-do actions."""
|
||||
files = self.project_repo.all_files
|
||||
requirement_doc = await self.project_repo.docs.get(REQUIREMENT_FILENAME)
|
||||
requirement = requirement_doc.content if requirement_doc else ""
|
||||
code_plan_and_change_ctx = CodePlanAndChangeContext.loads(files, requirement=requirement)
|
||||
options = {}
|
||||
if cause_by != any_to_str(FixBug):
|
||||
requirement_doc = await self.project_repo.docs.get(REQUIREMENT_FILENAME)
|
||||
options["requirement"] = requirement_doc.content
|
||||
else:
|
||||
fixbug_doc = await self.project_repo.docs.get(BUGFIX_FILENAME)
|
||||
options["issue"] = fixbug_doc.content
|
||||
code_plan_and_change_ctx = CodePlanAndChangeContext.loads(files, **options)
|
||||
self.rc.todo = WriteCodePlanAndChange(i_context=code_plan_and_change_ctx, context=self.context, llm=self.llm)
|
||||
|
||||
@property
|
||||
def action_description(self) -> str:
|
||||
"""AgentStore uses this attribute to display to the user what actions the current role should take."""
|
||||
return self.next_todo_action
|
||||
|
||||
async def _is_fixbug(self) -> bool:
|
||||
fixbug_doc = await self.project_repo.docs.get(BUGFIX_FILENAME)
|
||||
return bool(fixbug_doc and fixbug_doc.content)
|
||||
|
||||
async def _get_any_code_plan_and_change(self) -> Optional[Document]:
|
||||
changed_files = self.project_repo.docs.code_plan_and_change.changed_files
|
||||
for filename in changed_files.keys():
|
||||
doc = await self.project_repo.docs.code_plan_and_change.get(filename)
|
||||
if doc and doc.content:
|
||||
return doc
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -684,13 +684,14 @@ class BugFixContext(BaseContext):
|
|||
|
||||
class CodePlanAndChangeContext(BaseModel):
|
||||
requirement: str = ""
|
||||
issue: str = ""
|
||||
prd_filename: str = ""
|
||||
design_filename: str = ""
|
||||
task_filename: str = ""
|
||||
|
||||
@staticmethod
|
||||
def loads(filenames: List, **kwargs) -> CodePlanAndChangeContext:
|
||||
ctx = CodePlanAndChangeContext(requirement=kwargs.get("requirement", ""))
|
||||
ctx = CodePlanAndChangeContext(requirement=kwargs.get("requirement", ""), issue=kwargs.get("issue", ""))
|
||||
for filename in filenames:
|
||||
filename = Path(filename)
|
||||
if filename.is_relative_to(PRDS_FILE_REPO):
|
||||
|
|
|
|||
|
|
@ -56,8 +56,10 @@ class Team(BaseModel):
|
|||
def serialize(self, stg_path: Path = None):
|
||||
stg_path = SERDESER_PATH.joinpath("team") if stg_path is None else stg_path
|
||||
team_info_path = stg_path.joinpath("team.json")
|
||||
serialized_data = self.model_dump()
|
||||
serialized_data["context"] = self.env.context.serialize()
|
||||
|
||||
write_json_file(team_info_path, self.model_dump())
|
||||
write_json_file(team_info_path, serialized_data)
|
||||
|
||||
@classmethod
|
||||
def deserialize(cls, stg_path: Path, context: Context = None) -> "Team":
|
||||
|
|
@ -71,6 +73,7 @@ class Team(BaseModel):
|
|||
|
||||
team_info: dict = read_json_file(team_info_path)
|
||||
ctx = context or Context()
|
||||
ctx.deserialize(team_info.pop("context", None))
|
||||
team = Team(**team_info, context=ctx)
|
||||
return team
|
||||
|
||||
|
|
|
|||
4
setup.py
4
setup.py
|
|
@ -27,7 +27,7 @@ extras_require = {
|
|||
"selenium": ["selenium>4", "webdriver_manager", "beautifulsoup4"],
|
||||
"search-google": ["google-api-python-client==2.94.0"],
|
||||
"search-ddg": ["duckduckgo-search~=4.1.1"],
|
||||
"ocr": ["paddlepaddle==2.4.2", "paddleocr>=2.0.1", "tabulate==0.9.0"],
|
||||
"ocr": ["paddlepaddle==2.4.2", "paddleocr~=2.7.3", "tabulate==0.9.0"],
|
||||
"rag": [
|
||||
"llama-index-core==0.10.15",
|
||||
"llama-index-embeddings-azure-openai==0.1.6",
|
||||
|
|
@ -67,7 +67,7 @@ extras_require["dev"] = (["pylint~=3.0.3", "black~=23.3.0", "isort~=5.12.0", "pr
|
|||
|
||||
setup(
|
||||
name="metagpt",
|
||||
version="0.7.6",
|
||||
version="0.8.0",
|
||||
description="The Multi-Agent Framework",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from metagpt.context import Context
|
||||
from metagpt.logs import logger
|
||||
from metagpt.roles import Architect, ProductManager, ProjectManager
|
||||
from metagpt.team import Team
|
||||
|
|
@ -146,5 +147,21 @@ async def test_team_recover_multi_roles_save(mocker, context):
|
|||
await new_company.run(n_round=4)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_context(context):
|
||||
context.kwargs.set("a", "a")
|
||||
context.cost_manager.max_budget = 9
|
||||
company = Team(context=context)
|
||||
|
||||
save_to = context.repo.workdir / "serial"
|
||||
company.serialize(save_to)
|
||||
|
||||
company.deserialize(save_to, Context())
|
||||
assert company.env.context.repo
|
||||
assert company.env.context.repo.workdir == context.repo.workdir
|
||||
assert company.env.context.kwargs.a == "a"
|
||||
assert company.env.context.cost_manager.max_budget == context.cost_manager.max_budget
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue