mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-06-29 15:59:42 +02:00
Merge branch 'dev' of https://github.com/geekan/MetaGPT into geekan/dev
This commit is contained in:
commit
2ed7c50822
26 changed files with 282 additions and 117 deletions
|
|
@ -25,7 +25,7 @@ from metagpt.utils.project_repo import ProjectRepo
|
|||
|
||||
|
||||
class Action(SerializationMixin, ContextMixin, BaseModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True, exclude=["llm"])
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
name: str = ""
|
||||
i_context: Union[dict, CodingContext, CodeSummarizeContext, TestingContext, RunCodeContext, str, None] = ""
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from metagpt.configs.redis_config import RedisConfig
|
|||
from metagpt.configs.s3_config import S3Config
|
||||
from metagpt.configs.search_config import SearchConfig
|
||||
from metagpt.configs.workspace_config import WorkspaceConfig
|
||||
from metagpt.const import METAGPT_ROOT
|
||||
from metagpt.const import CONFIG_ROOT, METAGPT_ROOT
|
||||
from metagpt.utils.yaml_model import YamlModel
|
||||
|
||||
|
||||
|
|
@ -81,6 +81,11 @@ class Config(CLIParams, YamlModel):
|
|||
AZURE_TTS_REGION: str = ""
|
||||
mermaid_engine: str = "nodejs"
|
||||
|
||||
@classmethod
|
||||
def from_home(cls, path):
|
||||
"""Load config from ~/.metagpt/config.yaml"""
|
||||
return Config.model_validate_yaml(CONFIG_ROOT / path)
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
"""Load default config
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ def get_metagpt_root():
|
|||
|
||||
|
||||
# METAGPT PROJECT ROOT AND VARS
|
||||
|
||||
CONFIG_ROOT = Path.home() / ".metagpt"
|
||||
METAGPT_ROOT = get_metagpt_root() # Dependent on METAGPT_PROJECT_ROOT
|
||||
DEFAULT_WORKSPACE_ROOT = METAGPT_ROOT / "workspace"
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,11 @@ class ContextMixin(BaseModel):
|
|||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
# Pydantic has bug on _private_attr when using inheritance, so we use private_* instead
|
||||
# - https://github.com/pydantic/pydantic/issues/7142
|
||||
# - https://github.com/pydantic/pydantic/issues/7083
|
||||
# - https://github.com/pydantic/pydantic/issues/7091
|
||||
|
||||
# Env/Role/Action will use this context as private context, or use self.context as public context
|
||||
private_context: Optional[Context] = Field(default=None, exclude=True)
|
||||
# Env/Role/Action will use this config as private config, or use self.context.config as public config
|
||||
|
|
@ -52,6 +57,8 @@ class ContextMixin(BaseModel):
|
|||
def set_config(self, config: Config, override=False):
|
||||
"""Set config"""
|
||||
self.set("private_config", config, override)
|
||||
if config is not None:
|
||||
_ = self.llm # init llm
|
||||
|
||||
def set_llm(self, llm: BaseLLM, override=False):
|
||||
"""Set llm"""
|
||||
|
|
|
|||
|
|
@ -5,13 +5,15 @@
|
|||
@Author : alexanderwu
|
||||
@File : llm.py
|
||||
"""
|
||||
from typing import Optional
|
||||
|
||||
|
||||
from metagpt.configs.llm_config import LLMConfig
|
||||
from metagpt.context import CONTEXT
|
||||
from metagpt.provider.base_llm import BaseLLM
|
||||
|
||||
|
||||
def LLM() -> BaseLLM:
|
||||
def LLM(llm_config: Optional[LLMConfig] = None) -> BaseLLM:
|
||||
"""get the default llm provider if name is None"""
|
||||
# context.use_llm(name=name, provider=provider)
|
||||
if llm_config is not None:
|
||||
CONTEXT.llm_with_cost_manager_from_llm_config(llm_config)
|
||||
return CONTEXT.llm()
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from metagpt.provider.zhipuai_api import ZhiPuAILLM
|
|||
from metagpt.provider.azure_openai_api import AzureOpenAILLM
|
||||
from metagpt.provider.metagpt_api import MetaGPTLLM
|
||||
from metagpt.provider.human_provider import HumanProvider
|
||||
from metagpt.provider.spark_api import SparkLLM
|
||||
|
||||
__all__ = [
|
||||
"FireworksLLM",
|
||||
|
|
@ -26,4 +27,5 @@ __all__ = [
|
|||
"MetaGPTLLM",
|
||||
"OllamaLLM",
|
||||
"HumanProvider",
|
||||
"SparkLLM",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -59,7 +59,9 @@ class BaseLLM(ABC):
|
|||
if system_msgs:
|
||||
message = self._system_msgs(system_msgs)
|
||||
else:
|
||||
message = [self._default_system_msg()] if self.use_system_prompt else []
|
||||
message = [self._default_system_msg()]
|
||||
if not self.use_system_prompt:
|
||||
message = []
|
||||
if format_msgs:
|
||||
message.extend(format_msgs)
|
||||
message.append(self._user_msg(msg))
|
||||
|
|
|
|||
|
|
@ -220,10 +220,12 @@ class OpenAILLM(BaseLLM):
|
|||
|
||||
@handle_exception
|
||||
def _update_costs(self, usage: CompletionUsage):
|
||||
if self.config.calc_usage and usage:
|
||||
if self.config.calc_usage and usage and self.cost_manager:
|
||||
self.cost_manager.update_cost(usage.prompt_tokens, usage.completion_tokens, self.model)
|
||||
|
||||
def get_costs(self) -> Costs:
|
||||
if not self.cost_manager:
|
||||
return Costs()
|
||||
return self.cost_manager.get_costs()
|
||||
|
||||
def _get_max_tokens(self, messages: list[dict]):
|
||||
|
|
|
|||
|
|
@ -26,14 +26,14 @@ from metagpt.provider.llm_provider_registry import register_provider
|
|||
class SparkLLM(BaseLLM):
|
||||
def __init__(self, config: LLMConfig):
|
||||
self.config = config
|
||||
logger.warning("当前方法无法支持异步运行。当你使用acompletion时,并不能并行访问。")
|
||||
logger.warning("SparkLLM:当前方法无法支持异步运行。当你使用acompletion时,并不能并行访问。")
|
||||
|
||||
def get_choice_text(self, rsp: dict) -> str:
|
||||
return rsp["payload"]["choices"]["text"][-1]["content"]
|
||||
|
||||
async def acompletion_text(self, messages: list[dict], stream=False, timeout: int = 3) -> str:
|
||||
# 不支持
|
||||
logger.warning("当前方法无法支持异步运行。当你使用acompletion时,并不能并行访问。")
|
||||
# logger.warning("当前方法无法支持异步运行。当你使用acompletion时,并不能并行访问。")
|
||||
w = GetMessageFromWeb(messages, self.config)
|
||||
return w.run()
|
||||
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class Engineer(Role):
|
|||
# Code review
|
||||
if review:
|
||||
action = WriteCodeReview(i_context=coding_context, context=self.context, llm=self.llm)
|
||||
self._init_action_system_message(action)
|
||||
self._init_action(action)
|
||||
coding_context = await action.run()
|
||||
await self.project_repo.srcs.save(
|
||||
filename=coding_context.filename,
|
||||
|
|
|
|||
|
|
@ -132,6 +132,13 @@ class Role(SerializationMixin, ContextMixin, BaseModel):
|
|||
|
||||
role_id: str = ""
|
||||
states: list[str] = []
|
||||
|
||||
# scenarios to set action system_prompt:
|
||||
# 1. `__init__` while using Role(actions=[...])
|
||||
# 2. add action to role while using `role.set_action(action)`
|
||||
# 3. set_todo while using `role.set_todo(action)`
|
||||
# 4. when role.system_prompt is being updated (e.g. by `role.system_prompt = "..."`)
|
||||
# Additional, if llm is not set, we will use role's llm
|
||||
actions: list[SerializeAsAny[Action]] = Field(default=[], validate_default=True)
|
||||
rc: RoleContext = Field(default_factory=RoleContext)
|
||||
addresses: set[str] = set()
|
||||
|
|
@ -146,6 +153,10 @@ class Role(SerializationMixin, ContextMixin, BaseModel):
|
|||
self.pydantic_rebuild_model()
|
||||
super().__init__(**data)
|
||||
|
||||
if self.is_human:
|
||||
self.llm = HumanProvider(None)
|
||||
|
||||
self._check_actions()
|
||||
self.llm.system_prompt = self._get_prefix()
|
||||
self._watch(data.get("watch") or [UserRequirement])
|
||||
|
||||
|
|
@ -225,7 +236,16 @@ class Role(SerializationMixin, ContextMixin, BaseModel):
|
|||
def _setting(self):
|
||||
return f"{self.name}({self.profile})"
|
||||
|
||||
def _init_action_system_message(self, action: Action):
|
||||
def _check_actions(self):
|
||||
"""Check actions and set llm and prefix for each action."""
|
||||
self.set_actions(self.actions)
|
||||
return self
|
||||
|
||||
def _init_action(self, action: Action):
|
||||
if not action.private_config:
|
||||
action.set_llm(self.llm, override=True)
|
||||
else:
|
||||
action.set_llm(self.llm, override=False)
|
||||
action.set_prefix(self._get_prefix())
|
||||
|
||||
def set_action(self, action: Action):
|
||||
|
|
@ -241,7 +261,7 @@ class Role(SerializationMixin, ContextMixin, BaseModel):
|
|||
self._reset()
|
||||
for action in actions:
|
||||
if not isinstance(action, Action):
|
||||
i = action(name="", llm=self.llm)
|
||||
i = action()
|
||||
else:
|
||||
if self.is_human and not isinstance(action.llm, HumanProvider):
|
||||
logger.warning(
|
||||
|
|
@ -250,7 +270,7 @@ class Role(SerializationMixin, ContextMixin, BaseModel):
|
|||
f"try passing in Action classes instead of initialized instances"
|
||||
)
|
||||
i = action
|
||||
self._init_action_system_message(i)
|
||||
self._init_action(i)
|
||||
self.actions.append(i)
|
||||
self.states.append(f"{len(self.actions)}. {action}")
|
||||
|
||||
|
|
@ -308,6 +328,7 @@ class Role(SerializationMixin, ContextMixin, BaseModel):
|
|||
if env:
|
||||
env.set_addresses(self, self.addresses)
|
||||
self.llm.system_prompt = self._get_prefix()
|
||||
self.set_actions(self.actions) # reset actions to update llm and prefix
|
||||
|
||||
def _get_prefix(self):
|
||||
"""Get the role prefix"""
|
||||
|
|
@ -320,7 +341,8 @@ class Role(SerializationMixin, ContextMixin, BaseModel):
|
|||
prefix += CONSTRAINT_TEMPLATE.format(**{"constraints": self.constraints})
|
||||
|
||||
if self.rc.env and self.rc.env.desc:
|
||||
other_role_names = ", ".join(self.rc.env.role_names())
|
||||
all_roles = self.rc.env.role_names()
|
||||
other_role_names = ", ".join([r for r in all_roles if r != self.name])
|
||||
env_desc = f"You are in {self.rc.env.desc} with roles({other_role_names})."
|
||||
prefix += env_desc
|
||||
return prefix
|
||||
|
|
@ -480,7 +502,6 @@ class Role(SerializationMixin, ContextMixin, BaseModel):
|
|||
if not msg.cause_by:
|
||||
msg.cause_by = UserRequirement
|
||||
self.put_message(msg)
|
||||
|
||||
if not await self._observe():
|
||||
# If there is no new information, suspend and wait
|
||||
logger.debug(f"{self._setting}: no news. waiting.")
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from pathlib import Path
|
|||
import typer
|
||||
|
||||
from metagpt.config2 import config
|
||||
from metagpt.const import METAGPT_ROOT
|
||||
from metagpt.const import CONFIG_ROOT, METAGPT_ROOT
|
||||
|
||||
app = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ def startup(
|
|||
|
||||
def copy_config_to(config_path=METAGPT_ROOT / "config" / "config2.yaml"):
|
||||
"""Initialize the configuration file for MetaGPT."""
|
||||
target_path = Path.home() / ".metagpt" / "config2.yaml"
|
||||
target_path = CONFIG_ROOT / "config2.yaml"
|
||||
|
||||
# 创建目标目录(如果不存在)
|
||||
target_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue