Skip to content

Commit 7567141

Browse files
committed
refact: extract the core logic into core subfolder
Signed-off-by: Lin Yi <[email protected]>
1 parent ad33445 commit 7567141

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+7027
-0
lines changed

metagpt/core/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# @Time : 2023/4/24 22:26
4+
# @Author : alexanderwu
5+
# @File : __init__.py

metagpt/core/actions/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
"""
4+
@Time : 2023/5/11 17:44
5+
@Author : alexanderwu
6+
@File : __init__.py
7+
"""
8+
9+
from metagpt.core.actions.base import Action
10+
from metagpt.core.actions.action_output import ActionOutput
11+
12+
__all__ = [
13+
"Action",
14+
"ActionOutput",
15+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env python
2+
# coding: utf-8
3+
"""
4+
@Time : 2023/7/11 10:03
5+
@Author : chengmaoyu
6+
@File : action_output
7+
"""
8+
9+
from pydantic import BaseModel
10+
11+
12+
class ActionOutput:
13+
content: str
14+
instruct_content: BaseModel
15+
16+
def __init__(self, content: str, instruct_content: BaseModel):
17+
self.content = content
18+
self.instruct_content = instruct_content

metagpt/core/actions/base.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
"""
4+
@Time : 2023/5/11 14:43
5+
@Author : alexanderwu
6+
@File : action.py
7+
"""
8+
9+
from __future__ import annotations
10+
11+
from typing import Optional
12+
13+
from pydantic import BaseModel
14+
15+
16+
class BaseAction(BaseModel):
17+
def __str__(self):
18+
return self.__class__.__name__
19+
20+
def __repr__(self):
21+
return self.__str__()
22+
23+
async def _aask(self, prompt: str, system_msgs: Optional[list[str]] = None) -> str:
24+
"""Append default prefix"""
25+
return await self.llm.aask(prompt, system_msgs)
26+
27+
async def run(self, *args, **kwargs):
28+
"""Run action"""
29+
raise NotImplementedError("The run method should be implemented in a subclass.")

metagpt/core/base/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from metagpt.core.base.base_env import BaseEnvironment
2+
from metagpt.core.base.base_role import BaseRole
3+
4+
5+
__all__ = [
6+
"BaseEnvironment",
7+
"BaseRole",
8+
]

metagpt/core/base/base_env.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# @Desc : base environment
4+
5+
import typing
6+
from abc import abstractmethod
7+
from typing import Any, Optional
8+
9+
from metagpt.base.base_env_space import BaseEnvAction, BaseEnvObsParams
10+
from metagpt.base.base_serialization import BaseSerialization
11+
12+
if typing.TYPE_CHECKING:
13+
from metagpt.schema import Message
14+
15+
16+
class BaseEnvironment(BaseSerialization):
17+
"""Base environment"""
18+
19+
@abstractmethod
20+
def reset(
21+
self,
22+
*,
23+
seed: Optional[int] = None,
24+
options: Optional[dict[str, Any]] = None,
25+
) -> tuple[dict[str, Any], dict[str, Any]]:
26+
"""Implement this to get init observation"""
27+
28+
@abstractmethod
29+
def observe(self, obs_params: Optional[BaseEnvObsParams] = None) -> Any:
30+
"""Implement this if you want to get partial observation from the env"""
31+
32+
@abstractmethod
33+
def step(self, action: BaseEnvAction) -> tuple[dict[str, Any], float, bool, bool, dict[str, Any]]:
34+
"""Implement this to feed a action and then get new observation from the env"""
35+
36+
@abstractmethod
37+
def publish_message(self, message: "Message", peekable: bool = True) -> bool:
38+
"""Distribute the message to the recipients."""
39+
40+
@abstractmethod
41+
async def run(self, k=1):
42+
"""Process all task at once"""
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# @Desc :
4+
5+
from enum import IntEnum
6+
7+
from pydantic import BaseModel, ConfigDict, Field
8+
9+
10+
class BaseEnvActionType(IntEnum):
11+
# # NONE = 0 # no action to run, just get observation
12+
pass
13+
14+
15+
class BaseEnvAction(BaseModel):
16+
"""env action type and its related params of action functions/apis"""
17+
18+
model_config = ConfigDict(arbitrary_types_allowed=True)
19+
20+
action_type: int = Field(default=0, description="action type")
21+
22+
23+
class BaseEnvObsType(IntEnum):
24+
# # NONE = 0 # get whole observation from env
25+
pass
26+
27+
28+
class BaseEnvObsParams(BaseModel):
29+
"""observation params for different EnvObsType to get its observe result"""
30+
31+
model_config = ConfigDict(arbitrary_types_allowed=True)
32+
33+
obs_type: int = Field(default=0, description="observation type")
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from __future__ import annotations
2+
3+
from typing import Any
4+
5+
from pydantic import BaseModel, model_serializer, model_validator
6+
7+
8+
class BaseSerialization(BaseModel, extra="forbid"):
9+
"""
10+
PolyMorphic subclasses Serialization / Deserialization Mixin
11+
- First of all, we need to know that pydantic is not designed for polymorphism.
12+
- If Engineer is subclass of Role, it would be serialized as Role. If we want to serialize it as Engineer, we need
13+
to add `class name` to Engineer. So we need Engineer inherit SerializationMixin.
14+
15+
More details:
16+
- https://docs.pydantic.dev/latest/concepts/serialization/
17+
- https://github.com/pydantic/pydantic/discussions/7008 discuss about avoid `__get_pydantic_core_schema__`
18+
"""
19+
20+
__is_polymorphic_base = False
21+
__subclasses_map__ = {}
22+
23+
@model_serializer(mode="wrap")
24+
def __serialize_with_class_type__(self, default_serializer) -> Any:
25+
# default serializer, then append the `__module_class_name` field and return
26+
ret = default_serializer(self)
27+
ret["__module_class_name"] = f"{self.__class__.__module__}.{self.__class__.__qualname__}"
28+
return ret
29+
30+
@model_validator(mode="wrap")
31+
@classmethod
32+
def __convert_to_real_type__(cls, value: Any, handler):
33+
if isinstance(value, dict) is False:
34+
return handler(value)
35+
36+
# it is a dict so make sure to remove the __module_class_name
37+
# because we don't allow extra keywords but want to ensure
38+
# e.g Cat.model_validate(cat.model_dump()) works
39+
class_full_name = value.pop("__module_class_name", None)
40+
41+
# if it's not the polymorphic base we construct via default handler
42+
if not cls.__is_polymorphic_base:
43+
if class_full_name is None:
44+
return handler(value)
45+
elif str(cls) == f"<class '{class_full_name}'>":
46+
return handler(value)
47+
else:
48+
# f"Trying to instantiate {class_full_name} but this is not the polymorphic base class")
49+
pass
50+
51+
# otherwise we lookup the correct polymorphic type and construct that
52+
# instead
53+
if class_full_name is None:
54+
raise ValueError("Missing __module_class_name field")
55+
56+
class_type = cls.__subclasses_map__.get(class_full_name, None)
57+
58+
if class_type is None:
59+
# TODO could try dynamic import
60+
raise TypeError(f"Trying to instantiate {class_full_name}, which has not yet been defined!")
61+
62+
return class_type(**value)
63+
64+
def __init_subclass__(cls, is_polymorphic_base: bool = False, **kwargs):
65+
cls.__is_polymorphic_base = is_polymorphic_base
66+
cls.__subclasses_map__[f"{cls.__module__}.{cls.__qualname__}"] = cls
67+
super().__init_subclass__(**kwargs)

metagpt/core/config.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
"""
4+
@Time : 2024/1/4 01:25
5+
@Author : alexanderwu
6+
@File : config2.py
7+
"""
8+
import os
9+
from pathlib import Path
10+
from typing import Dict, Iterable, List, Literal, Optional
11+
12+
from pydantic import BaseModel, Field, model_validator
13+
14+
from metagpt.configs.embedding_config import EmbeddingConfig
15+
from metagpt.core.configs.llm_config import LLMConfig, LLMType
16+
from metagpt.configs.omniparse_config import OmniParseConfig
17+
from metagpt.configs.role_custom_config import RoleCustomConfig
18+
from metagpt.configs.workspace_config import WorkspaceConfig
19+
from metagpt.const import CONFIG_ROOT, METAGPT_ROOT
20+
from metagpt.core.utils.yaml_model import YamlModel
21+
22+
23+
class CLIParams(BaseModel):
24+
"""CLI parameters"""
25+
26+
project_path: str = ""
27+
project_name: str = ""
28+
inc: bool = False
29+
reqa_file: str = ""
30+
max_auto_summarize_code: int = 0
31+
git_reinit: bool = False
32+
33+
@model_validator(mode="after")
34+
def check_project_path(self):
35+
"""Check project_path and project_name"""
36+
if self.project_path:
37+
self.inc = True
38+
self.project_name = self.project_name or Path(self.project_path).name
39+
return self
40+
41+
42+
class CoreConfig(CLIParams, YamlModel):
43+
"""Configurations for MetaGPT"""
44+
45+
# Key Parameters
46+
llm: LLMConfig
47+
48+
# Global Proxy. Will be used if llm.proxy is not set
49+
proxy: str = ""
50+
51+
# Misc Parameters
52+
repair_llm_output: bool = False
53+
prompt_schema: Literal["json", "markdown", "raw"] = "json"
54+
workspace: WorkspaceConfig = Field(default_factory=WorkspaceConfig)
55+
56+
@classmethod
57+
def from_home(cls, path):
58+
"""Load config from ~/.metagpt/config2.yaml"""
59+
pathname = CONFIG_ROOT / path
60+
if not pathname.exists():
61+
return None
62+
return CoreConfig.from_yaml_file(pathname)
63+
64+
@classmethod
65+
def default(cls, reload: bool = False, **kwargs) -> "CoreConfig":
66+
"""Load default config
67+
- Priority: env < default_config_paths
68+
- Inside default_config_paths, the latter one overwrites the former one
69+
"""
70+
default_config_paths = (
71+
METAGPT_ROOT / "config/config2.yaml",
72+
CONFIG_ROOT / "config2.yaml",
73+
)
74+
if reload or default_config_paths not in _CONFIG_CACHE:
75+
dicts = [dict(os.environ), *(CoreConfig.read_yaml(path) for path in default_config_paths), kwargs]
76+
final = merge_dict(dicts)
77+
_CONFIG_CACHE[default_config_paths] = CoreConfig(**final)
78+
return _CONFIG_CACHE[default_config_paths]
79+
80+
@classmethod
81+
def from_llm_config(cls, llm_config: dict):
82+
"""user config llm
83+
example:
84+
llm_config = {"api_type": "xxx", "api_key": "xxx", "model": "xxx"}
85+
gpt4 = CoreConfig.from_llm_config(llm_config)
86+
A = Role(name="A", profile="Democratic candidate", goal="Win the election", actions=[a1], watch=[a2], config=gpt4)
87+
"""
88+
llm_config = LLMConfig.model_validate(llm_config)
89+
dicts = [dict(os.environ)]
90+
dicts += [{"llm": llm_config}]
91+
final = merge_dict(dicts)
92+
return CoreConfig(**final)
93+
94+
def update_via_cli(self, project_path, project_name, inc, reqa_file, max_auto_summarize_code):
95+
"""update config via cli"""
96+
97+
# Use in the PrepareDocuments action according to Section 2.2.3.5.1 of RFC 135.
98+
if project_path:
99+
inc = True
100+
project_name = project_name or Path(project_path).name
101+
self.project_path = project_path
102+
self.project_name = project_name
103+
self.inc = inc
104+
self.reqa_file = reqa_file
105+
self.max_auto_summarize_code = max_auto_summarize_code
106+
107+
108+
def merge_dict(dicts: Iterable[Dict]) -> Dict:
109+
"""Merge multiple dicts into one, with the latter dict overwriting the former"""
110+
result = {}
111+
for dictionary in dicts:
112+
result.update(dictionary)
113+
return result
114+
115+
116+
_CONFIG_CACHE = {}

metagpt/core/configs/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
"""
4+
@Time : 2024/1/4 16:33
5+
@Author : alexanderwu
6+
@File : __init__.py
7+
"""

0 commit comments

Comments
 (0)