MetaGPT/metagpt/team.py

137 lines
4.6 KiB
Python
Raw Normal View History

2023-06-30 17:10:48 +08:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2023/5/12 00:30
@Author : alexanderwu
@File : team.py
2023-11-27 16:18:39 +08:00
@Modified By: mashenquan, 2023/11/27. Add an archiving operation after completing the project, as specified in
Section 2.2.3.3 of RFC 135.
2023-06-30 17:10:48 +08:00
"""
2023-12-19 14:22:52 +08:00
import warnings
from pathlib import Path
from typing import Any, Optional
2023-12-18 16:39:02 +08:00
2023-12-26 14:44:09 +08:00
from pydantic import BaseModel, ConfigDict, Field
2023-06-30 17:10:48 +08:00
from metagpt.actions import UserRequirement
from metagpt.const import MESSAGE_ROUTE_TO_ALL, SERDESER_PATH
from metagpt.context import Context
2023-06-30 17:10:48 +08:00
from metagpt.environment import Environment
2023-07-22 11:28:22 +08:00
from metagpt.logs import logger
from metagpt.roles import Role
2023-06-30 17:10:48 +08:00
from metagpt.schema import Message
from metagpt.utils.common import (
NoMoneyException,
read_json_file,
serialize_decorator,
write_json_file,
)
2023-06-30 17:10:48 +08:00
class Team(BaseModel):
2023-06-30 17:10:48 +08:00
"""
2023-12-19 17:14:50 +08:00
Team: Possesses one or more roles (agents), SOP (Standard Operating Procedures), and a env for instant messaging,
dedicated to env any multi-agent activity, such as collaboratively writing executable code.
2023-06-30 17:10:48 +08:00
"""
2023-12-26 14:44:09 +08:00
model_config = ConfigDict(arbitrary_types_allowed=True)
env: Optional[Environment] = None
2023-07-12 15:46:51 +08:00
investment: float = Field(default=10.0)
idea: str = Field(default="")
2023-07-07 10:30:53 +08:00
def __init__(self, context: Context = None, **data: Any):
super(Team, self).__init__(**data)
ctx = context or Context()
if not self.env:
self.env = Environment(context=ctx)
else:
self.env.context = ctx # The `env` object is allocated by deserialization
if "roles" in data:
self.hire(data["roles"])
if "env_desc" in data:
self.env.desc = data["env_desc"]
2023-12-22 13:05:27 +08:00
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")
write_json_file(team_info_path, self.model_dump())
2023-11-28 09:29:00 +08:00
@classmethod
def deserialize(cls, stg_path: Path, context: Context = None) -> "Team":
"""stg_path = ./storage/team"""
2023-11-28 09:29:00 +08:00
# recover team_info
team_info_path = stg_path.joinpath("team.json")
2023-11-28 09:29:00 +08:00
if not team_info_path.exists():
raise FileNotFoundError(
"recover storage meta file `team.json` not exist, " "not to recover and please start a new project."
)
team_info: dict = read_json_file(team_info_path)
ctx = context or Context()
team = Team(**team_info, context=ctx)
return team
2023-11-28 09:29:00 +08:00
2023-06-30 17:10:48 +08:00
def hire(self, roles: list[Role]):
"""Hire roles to cooperate"""
self.env.add_roles(roles)
2023-06-30 17:10:48 +08:00
2024-01-04 21:16:23 +08:00
@property
def cost_manager(self):
"""Get cost manager"""
return self.env.context.cost_manager
2023-07-03 13:51:59 +08:00
def invest(self, investment: float):
2023-06-30 17:10:48 +08:00
"""Invest company. raise NoMoneyException when exceed max_budget."""
self.investment = investment
2024-01-04 21:16:23 +08:00
self.cost_manager.max_budget = investment
logger.info(f"Investment: ${investment}.")
2023-06-30 17:10:48 +08:00
2024-01-04 21:16:23 +08:00
def _check_balance(self):
2024-01-19 16:02:09 +08:00
if self.cost_manager.total_cost >= self.cost_manager.max_budget:
2024-01-04 21:16:23 +08:00
raise NoMoneyException(self.cost_manager.total_cost, f"Insufficient funds: {self.cost_manager.max_budget}")
2023-06-30 17:10:48 +08:00
2023-11-29 16:22:05 +08:00
def run_project(self, idea, send_to: str = ""):
"""Run a project from publishing user requirement."""
2023-06-30 17:10:48 +08:00
self.idea = idea
1. 动作优化 1. SummarizeCode动作:用于基于代码进行总结,思考bug、逻辑、todo 2. CodeReview动作优化:目前强制要求回答问题,有更高的成功率了 1. 增加了LGTM/LBTM的回答,在LGTM时会及时停止,不重写代码 2. 目前增加了设置中的参数code_review_k_times,与reflexion类似,设置为2 3. 仍然有概率发生指令不遵循,尤其是会有比较高的概率发生同时review多个代码文件,还没想好怎么解决 #FIXME 3. 增加了env到Action结构中,现在可以直接调用环境接口了 4. WriteDesign:去除了对project_name的纠正代码,现在引导下可以一次生成对 1. 修改了提示词中的##格式,改为了JSON格式 2. 数据结构 1. Document的标准化:Env->Repo->Document,其中Document/Asset/Code都是Document 1. 原用于检索的Document改为IndexableDocument 2. Repo结构引入:用于Document装载与元数据装载 3. RepoParser引入:写了一个简单的AST parser(后续可能要换tree-sitter),给出了整库symbol 4. Env中增加了set/get/set_doc/get_doc接口,用于set/get单个变量或者一个Document。这个逻辑后续或许会进一步简化 3. 配置优化 1. 默认更换为gpt-4-1106-preview,以获得最好的效果与成本 2. 提供~/.metagpt作为配置最高优先级目录,从中读取config.yaml 3. workspace可以灵活指定了,在config中配置 4. project_name可以由命令行指定,并且改为由ProductManager生成 4. metagpt作为默认命令行,而非python startup.py metagpt --help metagpt --project-name game_2048 "make a 2048 game" metagpt "make a 2048 game" metagpt --project-name game_2048 --inc "将2048改为4096" metagpt --project-name game_2048 --auto-inc "make a 2048 game" 1. 使用新的METAGPT_ROOT生成方式,而非寻找git,以便cli安装 2. 命令行由fire换为了typer,它会带来相对更好的体验 3. project_name可以灵活指定了,在metagpt命令行输入中配置 5. 其他 1. 现在支持多国语言了,中文已测试 2. BossRequirement -> UserRequirement 3. 大量错误文本的修正,增加了可读性 4. 中量提示词优化,稍微提升了一些准确率 5. 暂时屏蔽了LongtermMemory相关逻辑,这个逻辑底层调用了langchain的FAISS,会带来~5秒加载耗时 6. 修复了安装包中的部分描述错误 7. 去除了config中在openai_proxy设定时对base的重复修改,这个修改应该在openai初始化时发生 8. 修复了JSON在中文存储时的特定问题,ensure_ascii=False
2023-11-27 15:36:50 +08:00
# Human requirement.
2023-11-29 16:22:05 +08:00
self.env.publish_message(
2023-12-13 19:18:38 +08:00
Message(role="Human", content=idea, cause_by=UserRequirement, send_to=send_to or MESSAGE_ROUTE_TO_ALL),
peekable=False,
2023-11-29 16:22:05 +08:00
)
2023-06-30 17:10:48 +08:00
def start_project(self, idea, send_to: str = ""):
"""
Deprecated: This method will be removed in the future.
Please use the `run_project` method instead.
"""
2023-12-18 16:39:02 +08:00
warnings.warn(
"The 'start_project' method is deprecated and will be removed in the future. "
"Please use the 'run_project' method instead.",
DeprecationWarning,
stacklevel=2,
)
return self.run_project(idea=idea, send_to=send_to)
2023-07-07 10:30:53 +08:00
def _save(self):
logger.info(self.model_dump_json())
2023-07-07 10:30:53 +08:00
@serialize_decorator
2023-12-23 19:49:05 +08:00
async def run(self, n_round=3, idea="", send_to="", auto_archive=True):
2023-07-07 10:43:04 +08:00
"""Run company until target round or no money"""
2023-12-22 13:05:27 +08:00
if idea:
2023-12-23 19:49:05 +08:00
self.run_project(idea=idea, send_to=send_to)
2023-12-22 13:05:27 +08:00
2023-07-07 10:30:53 +08:00
while n_round > 0:
# self._save()
2023-06-30 17:10:48 +08:00
n_round -= 1
2023-12-15 11:36:24 +08:00
logger.debug(f"max {n_round=} left.")
2023-07-07 10:30:53 +08:00
self._check_balance()
2023-12-18 18:13:42 +08:00
await self.env.run()
2023-12-22 16:40:04 +08:00
self.env.archive(auto_archive)
return self.env.history