add pydantic v2 support and change role's private fields into public

This commit is contained in:
better629 2023-12-27 14:00:54 +08:00
parent 66925dd791
commit afaa7385c4
67 changed files with 518 additions and 555 deletions

View file

@ -26,7 +26,7 @@ action_subclass_registry = {}
class Action(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
model_config = ConfigDict(arbitrary_types_allowed=True, exclude=["llm"])
name: str = ""
llm: BaseGPTAPI = Field(default_factory=LLM, exclude=True)
@ -43,26 +43,20 @@ class Action(BaseModel):
self.node = ActionNode(key=self.name, expected_type=str, instruction=instruction, example="", schema="raw")
return self
def __init__(self, **kwargs: Any):
super().__init__(**kwargs)
def __init__(self, **data: Any):
super().__init__(**data)
# deserialize child classes dynamically for inherited `action`
object.__setattr__(self, "builtin_class_name", self.__class__.__name__)
self.__fields__["builtin_class_name"].default = self.__class__.__name__
self.model_fields["builtin_class_name"].default = self.__class__.__name__
if "instruction" in kwargs:
self.__init_with_instruction(kwargs["instruction"])
if "instruction" in data:
self.__init_with_instruction(data["instruction"])
def __init_subclass__(cls, **kwargs: Any) -> None:
super().__init_subclass__(**kwargs)
action_subclass_registry[cls.__name__] = cls
def dict(self, *args, **kwargs) -> dict[str, Any]:
obj_dict = super().model_dump(*args, **kwargs)
if "llm" in obj_dict:
obj_dict.pop("llm")
return obj_dict
def set_prefix(self, prefix):
"""Set prefix for later usage"""
self.prefix = prefix

View file

@ -1,11 +1,7 @@
from pathlib import Path
from pydantic import Field
from metagpt.actions.write_code import WriteCode
from metagpt.llm import LLM
from metagpt.logs import logger
from metagpt.provider.base_gpt_api import BaseGPTAPI
from metagpt.schema import Message
from metagpt.utils.exceptions import handle_exception
from metagpt.utils.highlight import highlight
@ -33,7 +29,6 @@ def run(*args) -> pd.DataFrame:
class CloneFunction(WriteCode):
name: str = "CloneFunction"
context: list[Message] = []
llm: BaseGPTAPI = Field(default_factory=LLM)
def _save(self, code_path, code):
if isinstance(code_path, str):

View file

@ -15,7 +15,6 @@ from pydantic import Field
from metagpt.actions.action import Action
from metagpt.config import CONFIG
from metagpt.const import TEST_CODES_FILE_REPO, TEST_OUTPUTS_FILE_REPO
from metagpt.llm import LLM, BaseGPTAPI
from metagpt.logs import logger
from metagpt.schema import RunCodeContext, RunCodeResult
from metagpt.utils.common import CodeParser
@ -52,7 +51,6 @@ Now you should start rewriting the code:
class DebugError(Action):
name: str = "DebugError"
context: RunCodeContext = Field(default_factory=RunCodeContext)
llm: BaseGPTAPI = Field(default_factory=LLM)
async def run(self, *args, **kwargs) -> str:
output_doc = await FileRepository.get_file(

View file

@ -13,8 +13,6 @@ import json
from pathlib import Path
from typing import Optional
from pydantic import Field
from metagpt.actions import Action, ActionOutput
from metagpt.actions.design_api_an import DESIGN_API_NODE
from metagpt.config import CONFIG
@ -25,9 +23,7 @@ from metagpt.const import (
SYSTEM_DESIGN_FILE_REPO,
SYSTEM_DESIGN_PDF_FILE_REPO,
)
from metagpt.llm import LLM
from metagpt.logs import logger
from metagpt.provider.base_gpt_api import BaseGPTAPI
from metagpt.schema import Document, Documents, Message
from metagpt.utils.file_repository import FileRepository
from metagpt.utils.mermaid import mermaid_to_file
@ -44,7 +40,6 @@ NEW_REQ_TEMPLATE = """
class WriteDesign(Action):
name: str = ""
context: Optional[str] = None
llm: BaseGPTAPI = Field(default_factory=LLM)
desc: str = (
"Based on the PRD, think about the system design, and design the corresponding APIs, "
"data structures, library tables, processes, and paths. Please provide your design, feedback "
@ -79,7 +74,7 @@ class WriteDesign(Action):
logger.info("Nothing has changed.")
# Wait until all files under `docs/system_designs/` are processed before sending the publish message,
# leaving room for global optimization in subsequent steps.
return ActionOutput(content=changed_files.json(), instruct_content=changed_files)
return ActionOutput(content=changed_files.model_dump_json(), instruct_content=changed_files)
async def _new_system_design(self, context, schema=CONFIG.prompt_schema):
node = await DESIGN_API_NODE.fill(context=context, llm=self.llm, schema=schema)
@ -88,7 +83,7 @@ class WriteDesign(Action):
async def _merge(self, prd_doc, system_design_doc, schema=CONFIG.prompt_schema):
context = NEW_REQ_TEMPLATE.format(old_design=system_design_doc.content, context=prd_doc.content)
node = await DESIGN_API_NODE.fill(context=context, llm=self.llm, schema=schema)
system_design_doc.content = node.instruct_content.json(ensure_ascii=False)
system_design_doc.content = node.instruct_content.model_dump_json()
return system_design_doc
async def _update_system_design(self, filename, prds_file_repo, system_design_file_repo) -> Document:
@ -99,7 +94,7 @@ class WriteDesign(Action):
doc = Document(
root_path=SYSTEM_DESIGN_FILE_REPO,
filename=filename,
content=system_design.instruct_content.json(ensure_ascii=False),
content=system_design.instruct_content.model_dump_json(),
)
else:
doc = await self._merge(prd_doc=prd, system_design_doc=old_system_design_doc)

View file

@ -8,17 +8,12 @@
from typing import Optional
from pydantic import Field
from metagpt.actions.action import Action
from metagpt.llm import LLM
from metagpt.provider.base_gpt_api import BaseGPTAPI
class DesignReview(Action):
name: str = "DesignReview"
context: Optional[str] = None
llm: BaseGPTAPI = Field(default_factory=LLM)
async def run(self, prd, api_design):
prompt = (

View file

@ -6,18 +6,14 @@
@File : execute_task.py
"""
from pydantic import Field
from metagpt.actions import Action
from metagpt.llm import LLM
from metagpt.provider.base_gpt_api import BaseGPTAPI
from metagpt.schema import Message
class ExecuteTask(Action):
name: str = "ExecuteTask"
context: list[Message] = []
llm: BaseGPTAPI = Field(default_factory=LLM)
async def run(self, *args, **kwargs):
pass

View file

@ -42,7 +42,6 @@ class InvoiceOCR(Action):
name: str = "InvoiceOCR"
context: Optional[str] = None
llm: BaseGPTAPI = Field(default_factory=LLM)
@staticmethod
async def _check_file_type(file_path: Path) -> str:

View file

@ -11,13 +11,9 @@ import shutil
from pathlib import Path
from typing import Optional
from pydantic import Field
from metagpt.actions import Action, ActionOutput
from metagpt.config import CONFIG
from metagpt.const import DOCS_FILE_REPO, REQUIREMENT_FILENAME
from metagpt.llm import LLM
from metagpt.provider.base_gpt_api import BaseGPTAPI
from metagpt.schema import Document
from metagpt.utils.file_repository import FileRepository
from metagpt.utils.git_repository import GitRepository
@ -28,7 +24,6 @@ class PrepareDocuments(Action):
name: str = "PrepareDocuments"
context: Optional[str] = None
llm: BaseGPTAPI = Field(default_factory=LLM)
def _init_repo(self):
"""Initialize the Git environment."""

View file

@ -13,8 +13,6 @@
import json
from typing import Optional
from pydantic import Field
from metagpt.actions import ActionOutput
from metagpt.actions.action import Action
from metagpt.actions.project_management_an import PM_NODE
@ -25,9 +23,7 @@ from metagpt.const import (
TASK_FILE_REPO,
TASK_PDF_FILE_REPO,
)
from metagpt.llm import LLM
from metagpt.logs import logger
from metagpt.provider.base_gpt_api import BaseGPTAPI
from metagpt.schema import Document, Documents
from metagpt.utils.file_repository import FileRepository
@ -43,7 +39,6 @@ NEW_REQ_TEMPLATE = """
class WriteTasks(Action):
name: str = "CreateTasks"
context: Optional[str] = None
llm: BaseGPTAPI = Field(default_factory=LLM)
async def run(self, with_messages, schema=CONFIG.prompt_schema):
system_design_file_repo = CONFIG.git_repo.new_file_repository(SYSTEM_DESIGN_FILE_REPO)
@ -73,7 +68,7 @@ class WriteTasks(Action):
logger.info("Nothing has changed.")
# Wait until all files under `docs/tasks/` are processed before sending the publish_message, leaving room for
# global optimization in subsequent steps.
return ActionOutput(content=change_files.json(), instruct_content=change_files)
return ActionOutput(content=change_files.model_dump_json(), instruct_content=change_files)
async def _update_tasks(self, filename, system_design_file_repo, tasks_file_repo):
system_design_doc = await system_design_file_repo.get(filename)
@ -83,7 +78,7 @@ class WriteTasks(Action):
else:
rsp = await self._run_new_tasks(context=system_design_doc.content)
task_doc = Document(
root_path=TASK_FILE_REPO, filename=filename, content=rsp.instruct_content.json(ensure_ascii=False)
root_path=TASK_FILE_REPO, filename=filename, content=rsp.instruct_content.model_dump_json()
)
await tasks_file_repo.save(
filename=filename, content=task_doc.content, dependencies={system_design_doc.root_relative_path}
@ -102,7 +97,7 @@ class WriteTasks(Action):
async def _merge(self, system_design_doc, task_doc, schema=CONFIG.prompt_schema) -> Document:
context = NEW_REQ_TEMPLATE.format(context=system_design_doc.content, old_tasks=task_doc.content)
node = await PM_NODE.fill(context, self.llm, schema)
task_doc.content = node.instruct_content.json(ensure_ascii=False)
task_doc.content = node.instruct_content.model_dump_json()
return task_doc
@staticmethod

View file

@ -82,8 +82,8 @@ class CollectLinks(Action):
name: str = "CollectLinks"
context: Optional[str] = None
llm: BaseGPTAPI = Field(default_factory=LLM)
desc: str = "Collect links from a search engine."
search_engine: SearchEngine = Field(default_factory=SearchEngine)
rank_func: Union[Callable[[list[str]], None], None] = None

View file

@ -22,7 +22,6 @@ from pydantic import Field
from metagpt.actions.action import Action
from metagpt.config import CONFIG
from metagpt.llm import LLM, BaseGPTAPI
from metagpt.logs import logger
from metagpt.schema import RunCodeContext, RunCodeResult
from metagpt.utils.exceptions import handle_exception
@ -79,7 +78,6 @@ standard errors:
class RunCode(Action):
name: str = "RunCode"
context: RunCodeContext = Field(default_factory=RunCodeContext)
llm: BaseGPTAPI = Field(default_factory=LLM)
@classmethod
@handle_exception

View file

@ -12,9 +12,7 @@ from pydantic import Field, model_validator
from metagpt.actions import Action
from metagpt.config import CONFIG, Config
from metagpt.llm import LLM
from metagpt.logs import logger
from metagpt.provider.base_gpt_api import BaseGPTAPI
from metagpt.schema import Message
from metagpt.tools import SearchEngineType
from metagpt.tools.search_engine import SearchEngine
@ -109,7 +107,7 @@ You are a member of a professional butler team and will provide helpful suggesti
class SearchAndSummarize(Action):
name: str = ""
content: Optional[str] = None
llm: BaseGPTAPI = Field(default_factory=LLM)
config: None = Field(default_factory=Config)
engine: Optional[SearchEngineType] = CONFIG.search_engine
search_func: Optional[Any] = None

View file

@ -13,7 +13,6 @@ from tenacity import retry, stop_after_attempt, wait_random_exponential
from metagpt.actions.action import Action
from metagpt.config import CONFIG
from metagpt.const import SYSTEM_DESIGN_FILE_REPO, TASK_FILE_REPO
from metagpt.llm import LLM, BaseGPTAPI
from metagpt.logs import logger
from metagpt.schema import CodeSummarizeContext
from metagpt.utils.file_repository import FileRepository
@ -95,7 +94,6 @@ flowchart TB
class SummarizeCode(Action):
name: str = "SummarizeCode"
context: CodeSummarizeContext = Field(default_factory=CodeSummarizeContext)
llm: BaseGPTAPI = Field(default_factory=LLM)
@retry(stop=stop_after_attempt(2), wait=wait_random_exponential(min=1, max=60))
async def summarize_code(self, prompt):

View file

@ -29,9 +29,7 @@ from metagpt.const import (
TASK_FILE_REPO,
TEST_OUTPUTS_FILE_REPO,
)
from metagpt.llm import LLM
from metagpt.logs import logger
from metagpt.provider.base_gpt_api import BaseGPTAPI
from metagpt.schema import CodingContext, Document, RunCodeResult
from metagpt.utils.common import CodeParser
from metagpt.utils.file_repository import FileRepository
@ -90,7 +88,6 @@ ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenc
class WriteCode(Action):
name: str = "WriteCode"
context: Document = Field(default_factory=Document)
llm: BaseGPTAPI = Field(default_factory=LLM)
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
async def write_code(self, prompt) -> str:

View file

@ -14,9 +14,7 @@ from tenacity import retry, stop_after_attempt, wait_random_exponential
from metagpt.actions import WriteCode
from metagpt.actions.action import Action
from metagpt.config import CONFIG
from metagpt.llm import LLM
from metagpt.logs import logger
from metagpt.provider.base_gpt_api import BaseGPTAPI
from metagpt.schema import CodingContext
from metagpt.utils.common import CodeParser
@ -123,7 +121,6 @@ REWRITE_CODE_TEMPLATE = """
class WriteCodeReview(Action):
name: str = "WriteCodeReview"
context: CodingContext = Field(default_factory=CodingContext)
llm: BaseGPTAPI = Field(default_factory=LLM)
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
async def write_code_review_and_rewrite(self, context_prompt, cr_prompt, filename):

View file

@ -24,11 +24,7 @@ the specified docstring style and adds them to the code.
import ast
from typing import Literal, Optional
from pydantic import Field
from metagpt.actions.action import Action
from metagpt.llm import LLM
from metagpt.provider.base_gpt_api import BaseGPTAPI
from metagpt.utils.common import OutputParser
from metagpt.utils.pycst import merge_docstring
@ -163,7 +159,6 @@ class WriteDocstring(Action):
desc: str = "Write docstring for code."
context: Optional[str] = None
llm: BaseGPTAPI = Field(default_factory=LLM)
async def run(
self,

View file

@ -17,8 +17,6 @@ import json
from pathlib import Path
from typing import Optional
from pydantic import Field
from metagpt.actions import Action, ActionOutput
from metagpt.actions.action_node import ActionNode
from metagpt.actions.fix_bug import FixBug
@ -36,9 +34,7 @@ from metagpt.const import (
PRDS_FILE_REPO,
REQUIREMENT_FILENAME,
)
from metagpt.llm import LLM
from metagpt.logs import logger
from metagpt.provider.base_gpt_api import BaseGPTAPI
from metagpt.schema import BugFixContext, Document, Documents, Message
from metagpt.utils.common import CodeParser
from metagpt.utils.file_repository import FileRepository
@ -67,7 +63,6 @@ NEW_REQ_TEMPLATE = """
class WritePRD(Action):
name: str = ""
content: Optional[str] = None
llm: BaseGPTAPI = Field(default_factory=LLM)
async def run(self, with_messages, schema=CONFIG.prompt_schema, *args, **kwargs) -> ActionOutput | Message:
# Determine which requirement documents need to be rewritten: Use LLM to assess whether new requirements are
@ -79,7 +74,7 @@ class WritePRD(Action):
await docs_file_repo.save(filename=REQUIREMENT_FILENAME, content="")
bug_fix = BugFixContext(filename=BUGFIX_FILENAME)
return Message(
content=bug_fix.json(),
content=bug_fix.model_dump_json(),
instruct_content=bug_fix,
role="",
cause_by=FixBug,
@ -111,7 +106,7 @@ class WritePRD(Action):
# Once all files under 'docs/prds/' have been compared with the newly added requirements, trigger the
# 'publish' message to transition the workflow to the next stage. This design allows room for global
# optimization in subsequent steps.
return ActionOutput(content=change_files.json(), instruct_content=change_files)
return ActionOutput(content=change_files.model_dump_json(), instruct_content=change_files)
async def _run_new_requirement(self, requirements, schema=CONFIG.prompt_schema) -> ActionOutput:
# sas = SearchAndSummarize()
@ -137,7 +132,7 @@ class WritePRD(Action):
CONFIG.project_name = Path(CONFIG.project_path).name
prompt = NEW_REQ_TEMPLATE.format(requirements=new_requirement_doc.content, old_prd=prd_doc.content)
node = await WRITE_PRD_NODE.fill(context=prompt, llm=self.llm, schema=schema)
prd_doc.content = node.instruct_content.json(ensure_ascii=False)
prd_doc.content = node.instruct_content.model_dump_json()
await self._rename_workspace(node)
return prd_doc
@ -149,7 +144,7 @@ class WritePRD(Action):
new_prd_doc = Document(
root_path=PRDS_FILE_REPO,
filename=FileRepository.new_filename() + ".json",
content=prd.instruct_content.json(ensure_ascii=False),
content=prd.instruct_content.model_dump_json(),
)
elif await self._is_relative(requirement_doc, prd_doc):
new_prd_doc = await self._merge(requirement_doc, prd_doc)

View file

@ -8,17 +8,13 @@
from typing import Optional
from pydantic import Field
from metagpt.actions.action import Action
from metagpt.llm import LLM
from metagpt.provider.base_gpt_api import BaseGPTAPI
class WritePRDReview(Action):
name: str = ""
context: Optional[str] = None
llm: BaseGPTAPI = Field(default_factory=LLM)
prd: Optional[str] = None
desc: str = "Based on the PRD, conduct a PRD Review, providing clear and detailed feedback"
prd_review_prompt_template: str = """

View file

@ -6,12 +6,8 @@
"""
from typing import List
from pydantic import Field
from metagpt.actions import Action
from metagpt.actions.action_node import ActionNode
from metagpt.llm import LLM
from metagpt.provider.base_gpt_api import BaseGPTAPI
REVIEW = ActionNode(
key="Review",
@ -38,7 +34,6 @@ class WriteReview(Action):
"""Write a review for the given context."""
name: str = "WriteReview"
llm: BaseGPTAPI = Field(default_factory=LLM)
async def run(self, context):
return await WRITE_REVIEW_NODE.fill(context=context, llm=self.llm, schema="json")

View file

@ -7,20 +7,16 @@
"""
from typing import Optional
from pydantic import Field
from metagpt.actions import Action
from metagpt.config import CONFIG
from metagpt.llm import LLM
from metagpt.logs import logger
from metagpt.provider.base_gpt_api import BaseGPTAPI
class WriteTeachingPlanPart(Action):
"""Write Teaching Plan Part"""
context: Optional[str] = None
llm: BaseGPTAPI = Field(default_factory=LLM)
topic: str = ""
language: str = "Chinese"
rsp: Optional[str] = None

View file

@ -10,14 +10,10 @@
from typing import Optional
from pydantic import Field
from metagpt.actions.action import Action
from metagpt.config import CONFIG
from metagpt.const import TEST_CODES_FILE_REPO
from metagpt.llm import LLM
from metagpt.logs import logger
from metagpt.provider.base_gpt_api import BaseGPTAPI
from metagpt.schema import Document, TestingContext
from metagpt.utils.common import CodeParser
@ -45,7 +41,6 @@ you should correctly import the necessary classes based on these file locations!
class WriteTest(Action):
name: str = "WriteTest"
context: Optional[TestingContext] = None
llm: BaseGPTAPI = Field(default_factory=LLM)
async def write_code(self, prompt):
code_rsp = await self._aask(prompt)

View file

@ -27,7 +27,7 @@ class WriteDirectory(Action):
"""
name: str = "WriteDirectory"
llm: BaseGPTAPI = Field(default_factory=LLM)
language: str = "Chinese"
async def run(self, topic: str, *args, **kwargs) -> Dict:

View file

@ -13,9 +13,9 @@
"""
import asyncio
from pathlib import Path
from typing import Iterable, Set
from typing import Iterable, Set, Union
from pydantic import BaseModel, ConfigDict, Field
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from metagpt.config import CONFIG
from metagpt.logs import logger
@ -32,26 +32,31 @@ class Environment(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
desc: str = Field(default="") # 环境描述
roles: dict[str, Role] = Field(default_factory=dict)
members: dict[Role, Set] = Field(default_factory=dict)
roles: dict[str, Role] = Field(default_factory=dict, validate_default=True)
members: dict[Role, Set] = Field(default_factory=dict, exclude=True)
history: str = "" # For debug
def __init__(self, **kwargs):
roles = []
for role_key, role in kwargs.get("roles", {}).items():
current_role = kwargs["roles"][role_key]
if isinstance(current_role, dict):
item_class_name = current_role.get("builtin_class_name", None)
for name, subclass in role_subclass_registry.items():
registery_class_name = subclass.__fields__["builtin_class_name"].default
if item_class_name == registery_class_name:
current_role = subclass(**current_role)
break
kwargs["roles"][role_key] = current_role
roles.append(current_role)
super().__init__(**kwargs)
@field_validator("roles", mode="before")
@classmethod
def check_roles(cls, roles: dict[str, Union[Role, dict]]) -> dict[str, Role]:
new_roles = dict()
for role_key, role in roles.items():
if isinstance(role, dict):
item_class_name = role.get("builtin_class_name", None)
if item_class_name:
for name, subclass in role_subclass_registry.items():
registery_class_name = subclass.model_fields["builtin_class_name"].default
if item_class_name == registery_class_name:
new_role = subclass(**role)
break
new_roles[role_key] = new_role
else:
new_roles[role_key] = role
return new_roles
self.add_roles(roles) # add_roles again to init the Role.set_env
@model_validator(mode="after")
def init_roles(self):
self.add_roles(self.roles.values())
def serialize(self, stg_path: Path):
roles_path = stg_path.joinpath("roles.json")

View file

@ -4,7 +4,7 @@
@Time : 2023/6/5 01:44
@Author : alexanderwu
@File : skill_manager.py
@Modified By: mashenquan, 2023/8/20. Remove useless `_llm`
@Modified By: mashenquan, 2023/8/20. Remove useless `llm`
"""
from metagpt.actions import Action
from metagpt.const import PROMPT_PATH

View file

@ -68,7 +68,7 @@ class BrainMemory(BaseModel):
redis = Redis(conf=redis_conf)
if not redis.is_valid() or not redis_key:
return False
v = self.json(ensure_ascii=False)
v = self.model_dump_json()
if self.cacheable:
await redis.set(key=redis_key, data=v, timeout_sec=timeout_sec)
logger.debug(f"REDIS SET {redis_key} {v}")
@ -94,7 +94,7 @@ class BrainMemory(BaseModel):
if msg.id:
if self.to_int(msg.id, 0) <= self.to_int(self.last_history_id, -1):
return
self.history.append(msg.dict())
self.history.append(msg.model_dump())
self.last_history_id = str(msg.id)
self.is_dirty = True
@ -150,7 +150,7 @@ class BrainMemory(BaseModel):
if left == 0:
break
m.content = m.content[0:left]
msgs.append(m.dict())
msgs.append(m.model_dump())
break
msgs.append(m)
total_length += delta

View file

@ -65,22 +65,20 @@ class Assistant(Role):
prompt += f"If the text explicitly want you to {desc}, return `[SKILL]: {name}` brief and clear. For instance: [SKILL]: {name}\n"
prompt += 'Otherwise, return `[TALK]: {talk}` brief and clear. For instance: if {talk} is "xxxx" return [TALK]: xxxx\n\n'
prompt += f"Now what specific action is explicitly mentioned in the text: {last_talk}\n"
rsp = await self._llm.aask(prompt, [])
rsp = await self.llm.aask(prompt, [])
logger.info(f"THINK: {prompt}\n, THINK RESULT: {rsp}\n")
return await self._plan(rsp, last_talk=last_talk)
async def act(self) -> Message:
result = await self._rc.todo.run()
result = await self.rc.todo.run()
if not result:
return None
if isinstance(result, str):
msg = Message(content=result, role="assistant", cause_by=self._rc.todo)
msg = Message(content=result, role="assistant", cause_by=self.rc.todo)
elif isinstance(result, Message):
msg = result
else:
msg = Message(
content=result.content, instruct_content=result.instruct_content, cause_by=type(self._rc.todo)
)
msg = Message(content=result.content, instruct_content=result.instruct_content, cause_by=type(self.rc.todo))
self.memory.add_answer(msg)
return msg
@ -99,8 +97,8 @@ class Assistant(Role):
async def talk_handler(self, text, **kwargs) -> bool:
history = self.memory.history_text
text = kwargs.get("last_talk") or text
self._rc.todo = TalkAction(
context=text, knowledge=self.memory.get_knowledge(), history_summary=history, llm=self._llm, **kwargs
self.rc.todo = TalkAction(
context=text, knowledge=self.memory.get_knowledge(), history_summary=history, llm=self.llm, **kwargs
)
return True
@ -110,13 +108,11 @@ class Assistant(Role):
if not skill:
logger.info(f"skill not found: {text}")
return await self.talk_handler(text=last_talk, **kwargs)
action = ArgumentsParingAction(skill=skill, llm=self._llm, ask=last_talk, **kwargs)
action = ArgumentsParingAction(skill=skill, llm=self.llm, ask=last_talk, **kwargs)
await action.run(**kwargs)
if action.args is None:
return await self.talk_handler(text=last_talk, **kwargs)
self._rc.todo = SkillAction(
skill=skill, args=action.args, llm=self._llm, name=skill.name, desc=skill.description
)
self.rc.todo = SkillAction(skill=skill, args=action.args, llm=self.llm, name=skill.name, desc=skill.description)
return True
async def refine_memory(self) -> str:
@ -125,16 +121,16 @@ class Assistant(Role):
return None
if not self.memory.is_history_available:
return last_talk
history_summary = await self.memory.summarize(max_words=800, keep_language=True, llm=self._llm)
if last_talk and await self.memory.is_related(text1=last_talk, text2=history_summary, llm=self._llm):
history_summary = await self.memory.summarize(max_words=800, keep_language=True, llm=self.llm)
if last_talk and await self.memory.is_related(text1=last_talk, text2=history_summary, llm=self.llm):
# Merge relevant content.
merged = await self.memory.rewrite(sentence=last_talk, context=history_summary, llm=self._llm)
merged = await self.memory.rewrite(sentence=last_talk, context=history_summary, llm=self.llm)
return f"{merged} {last_talk}"
return last_talk
def get_memory(self) -> str:
return self.memory.json()
return self.memory.model_dump_json()
def load_memory(self, jsn):
try:

View file

@ -109,7 +109,7 @@ class Engineer(Role):
coding_context = await todo.run()
# Code review
if review:
action = WriteCodeReview(context=coding_context, llm=self._llm)
action = WriteCodeReview(context=coding_context, llm=self.llm)
self._init_action_system_message(action)
coding_context = await action.run()
await src_file_repo.save(
@ -118,9 +118,12 @@ class Engineer(Role):
content=coding_context.code_doc.content,
)
msg = Message(
content=coding_context.json(), instruct_content=coding_context, role=self.profile, cause_by=WriteCode
content=coding_context.model_dump_json(),
instruct_content=coding_context,
role=self.profile,
cause_by=WriteCode,
)
self._rc.memory.add(msg)
self.rc.memory.add(msg)
changed_files.add(coding_context.code_doc.filename)
if not changed_files:
@ -129,12 +132,12 @@ class Engineer(Role):
async def _act(self) -> Message | None:
"""Determines the mode of action based on whether code review is used."""
if self._rc.todo is None:
if self.rc.todo is None:
return None
if isinstance(self._rc.todo, WriteCode):
if isinstance(self.rc.todo, WriteCode):
self.next_todo_action = any_to_name(SummarizeCode)
return await self._act_write_code()
if isinstance(self._rc.todo, SummarizeCode):
if isinstance(self.rc.todo, SummarizeCode):
self.next_todo_action = any_to_name(WriteCode)
return await self._act_summarize()
return None
@ -170,7 +173,7 @@ class Engineer(Role):
tasks.append(todo.context.dict())
await code_summaries_file_repo.save(
filename=Path(todo.context.design_filename).name,
content=todo.context.json(),
content=todo.context.model_dump_json(),
dependencies=dependencies,
)
else:
@ -193,7 +196,7 @@ class Engineer(Role):
)
async def _is_pass(self, summary) -> (str, str):
rsp = await self._llm.aask(msg=IS_PASS_PROMPT.format(context=summary), stream=False)
rsp = await self.llm.aask(msg=IS_PASS_PROMPT.format(context=summary), stream=False)
logger.info(rsp)
if "YES" in rsp:
return True, rsp
@ -204,17 +207,17 @@ class Engineer(Role):
CONFIG.src_workspace = CONFIG.git_repo.workdir / CONFIG.git_repo.workdir.name
write_code_filters = any_to_str_set([WriteTasks, SummarizeCode, FixBug])
summarize_code_filters = any_to_str_set([WriteCode, WriteCodeReview])
if not self._rc.news:
if not self.rc.news:
return None
msg = self._rc.news[0]
msg = self.rc.news[0]
if msg.cause_by in write_code_filters:
logger.debug(f"TODO WriteCode:{msg.json()}")
logger.debug(f"TODO WriteCode:{msg.model_dump_json()}")
await self._new_code_actions(bug_fix=msg.cause_by == any_to_str(FixBug))
return self._rc.todo
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.json()}")
logger.debug(f"TODO SummarizeCode:{msg.model_dump_json()}")
await self._new_summarize_actions()
return self._rc.todo
return self.rc.todo
return None
@staticmethod
@ -241,7 +244,9 @@ class Engineer(Role):
context = await Engineer._new_coding_context(
filename, src_file_repo, task_file_repo, design_file_repo, dependency
)
coding_doc = Document(root_path=str(src_file_repo.root_path), filename=filename, content=context.json())
coding_doc = Document(
root_path=str(src_file_repo.root_path), filename=filename, content=context.model_dump_json()
)
return coding_doc
async def _new_code_actions(self, bug_fix=False):
@ -266,15 +271,15 @@ class Engineer(Role):
filename=task_filename, design_doc=design_doc, task_doc=task_doc, code_doc=old_code_doc
)
coding_doc = Document(
root_path=str(src_file_repo.root_path), filename=task_filename, content=context.json()
root_path=str(src_file_repo.root_path), filename=task_filename, content=context.model_dump_json()
)
if task_filename in changed_files.docs:
logger.warning(
f"Log to expose potential conflicts: {coding_doc.json()} & "
f"{changed_files.docs[task_filename].json()}"
f"Log to expose potential conflicts: {coding_doc.model_dump_json()} & "
f"{changed_files.docs[task_filename].model_dump_json()}"
)
changed_files.docs[task_filename] = coding_doc
self.code_todos = [WriteCode(context=i, llm=self._llm) for i in changed_files.docs.values()]
self.code_todos = [WriteCode(context=i, llm=self.llm) for i in changed_files.docs.values()]
# Code directly modified by the user.
dependency = await CONFIG.git_repo.get_dependency()
for filename in changed_src_files:
@ -288,10 +293,10 @@ class Engineer(Role):
dependency=dependency,
)
changed_files.docs[filename] = coding_doc
self.code_todos.append(WriteCode(context=coding_doc, llm=self._llm))
self.code_todos.append(WriteCode(context=coding_doc, llm=self.llm))
if self.code_todos:
self._rc.todo = self.code_todos[0]
self.rc.todo = self.code_todos[0]
async def _new_summarize_actions(self):
src_file_repo = CONFIG.git_repo.new_file_repository(CONFIG.src_workspace)
@ -304,9 +309,9 @@ class Engineer(Role):
summarizations[ctx].append(filename)
for ctx, filenames in summarizations.items():
ctx.codes_filenames = filenames
self.summarize_todos.append(SummarizeCode(context=ctx, llm=self._llm))
self.summarize_todos.append(SummarizeCode(context=ctx, llm=self.llm))
if self.summarize_todos:
self._rc.todo = self.summarize_todos[0]
self.rc.todo = self.summarize_todos[0]
@property
def todo(self) -> str:

View file

@ -69,8 +69,8 @@ class InvoiceOCRAssistant(Role):
Returns:
A message containing the result of the action.
"""
msg = self._rc.memory.get(k=1)[0]
todo = self._rc.todo
msg = self.rc.memory.get(k=1)[0]
todo = self.rc.todo
if isinstance(todo, InvoiceOCR):
self.origin_query = msg.content
invoice_path: InvoicePath = msg.instruct_content
@ -87,11 +87,11 @@ class InvoiceOCRAssistant(Role):
else:
self._init_actions([GenerateTable])
self._rc.todo = None
self.rc.todo = None
content = INVOICE_OCR_SUCCESS
resp = OCRResults(ocr_result=json.dumps(resp))
msg = Message(content=content, instruct_content=resp)
self._rc.memory.add(msg)
self.rc.memory.add(msg)
return await super().react()
elif isinstance(todo, GenerateTable):
ocr_results: OCRResults = msg.instruct_content
@ -108,5 +108,5 @@ class InvoiceOCRAssistant(Role):
resp = ReplyData(content=resp)
msg = Message(content=content, instruct_content=resp)
self._rc.memory.add(msg)
self.rc.memory.add(msg)
return msg

View file

@ -45,7 +45,7 @@ class ProductManager(Role):
else:
self._set_state(0)
self.todo_action = any_to_name(WritePRD)
return bool(self._rc.todo)
return bool(self.rc.todo)
async def _observe(self, ignore_memory=False) -> int:
return await super()._observe(ignore_memory=True)

View file

@ -69,7 +69,7 @@ class QaEngineer(Role):
)
logger.info(f"Writing {test_doc.filename}..")
context = TestingContext(filename=test_doc.filename, test_doc=test_doc, code_doc=code_doc)
context = await WriteTest(context=context, llm=self._llm).run()
context = await WriteTest(context=context, llm=self.llm).run()
await tests_file_repo.save(
filename=context.test_doc.filename,
content=context.test_doc.content,
@ -86,7 +86,7 @@ class QaEngineer(Role):
)
self.publish_message(
Message(
content=run_code_context.json(),
content=run_code_context.model_dump_json(),
role=self.profile,
cause_by=WriteTest,
sent_from=self,
@ -106,11 +106,11 @@ class QaEngineer(Role):
return
run_code_context.code = src_doc.content
run_code_context.test_code = test_doc.content
result = await RunCode(context=run_code_context, llm=self._llm).run()
result = await RunCode(context=run_code_context, llm=self.llm).run()
run_code_context.output_filename = run_code_context.test_filename + ".json"
await CONFIG.git_repo.new_file_repository(TEST_OUTPUTS_FILE_REPO).save(
filename=run_code_context.output_filename,
content=result.json(),
content=result.model_dump_json(),
dependencies={src_doc.root_relative_path, test_doc.root_relative_path},
)
run_code_context.code = None
@ -120,7 +120,7 @@ class QaEngineer(Role):
mappings = {"Engineer": "Alex", "QaEngineer": "Edward"}
self.publish_message(
Message(
content=run_code_context.json(),
content=run_code_context.model_dump_json(),
role=self.profile,
cause_by=RunCode,
sent_from=self,
@ -130,14 +130,14 @@ class QaEngineer(Role):
async def _debug_error(self, msg):
run_code_context = RunCodeContext.loads(msg.content)
code = await DebugError(context=run_code_context, llm=self._llm).run()
code = await DebugError(context=run_code_context, llm=self.llm).run()
await FileRepository.save_file(
filename=run_code_context.test_filename, content=code, relative_path=TEST_CODES_FILE_REPO
)
run_code_context.output = None
self.publish_message(
Message(
content=run_code_context.json(),
content=run_code_context.model_dump_json(),
role=self.profile,
cause_by=DebugError,
sent_from=self,
@ -159,7 +159,7 @@ class QaEngineer(Role):
code_filters = any_to_str_set({SummarizeCode})
test_filters = any_to_str_set({WriteTest, DebugError})
run_filters = any_to_str_set({RunCode})
for msg in self._rc.news:
for msg in self.rc.news:
# Decide what to do based on observed msg type, currently defined by human,
# might potentially be moved to _think, that is, let the agent decides for itself
if msg.cause_by in code_filters:

View file

@ -41,20 +41,20 @@ class Researcher(Role):
logger.warning(f"The language `{self.language}` has not been tested, it may not work.")
async def _think(self) -> bool:
if self._rc.todo is None:
if self.rc.todo is None:
self._set_state(0)
return True
if self._rc.state + 1 < len(self._states):
self._set_state(self._rc.state + 1)
if self.rc.state + 1 < len(self.states):
self._set_state(self.rc.state + 1)
else:
self._rc.todo = None
self.rc.todo = None
return False
async def _act(self) -> Message:
logger.info(f"{self._setting}: to do {self._rc.todo}({self._rc.todo.name})")
todo = self._rc.todo
msg = self._rc.memory.get(k=1)[0]
logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")
todo = self.rc.todo
msg = self.rc.memory.get(k=1)[0]
if isinstance(msg.instruct_content, Report):
instruct_content = msg.instruct_content
topic = instruct_content.topic
@ -78,14 +78,14 @@ class Researcher(Role):
else:
summaries = instruct_content.summaries
summary_text = "\n---\n".join(f"url: {url}\nsummary: {summary}" for (url, summary) in summaries)
content = await self._rc.todo.run(topic, summary_text, system_text=research_system_text)
content = await self.rc.todo.run(topic, summary_text, system_text=research_system_text)
ret = Message(
content="",
instruct_content=Report(topic=topic, content=content),
role=self.profile,
cause_by=self._rc.todo,
cause_by=self.rc.todo,
)
self._rc.memory.add(ret)
self.rc.memory.add(ret)
return ret
def research_system_text(self, topic, current_task: Action) -> str:

View file

@ -10,8 +10,8 @@
consolidated within the `_observe` function.
2. Standardize the message filtering for string label matching. Role objects can access the message labels
they've subscribed to through the `subscribed_tags` property.
3. Move the message receive buffer from the global variable `self._rc.env.memory` to the role's private variable
`self._rc.msg_buffer` for easier message identification and asynchronous appending of messages.
3. Move the message receive buffer from the global variable `self.rc.env.memory` to the role's private variable
`self.rc.msg_buffer` for easier message identification and asynchronous appending of messages.
4. Standardize the way messages are passed: `publish_message` sends messages out, while `put_message` places
messages into the Role object's private message receive buffer. There are no other message transmit methods.
5. Standardize the parameters for the `run` function: the `test_message` parameter is used for testing purposes
@ -24,9 +24,9 @@ from __future__ import annotations
from enum import Enum
from pathlib import Path
from typing import Any, Iterable, Set, Type
from typing import Any, Iterable, Optional, Set, Type, Union
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from metagpt.actions import Action, ActionOutput
from metagpt.actions.action import action_subclass_registry
@ -92,8 +92,10 @@ class RoleReactMode(str, Enum):
class RoleContext(BaseModel):
"""Role Runtime Context"""
model_config = ConfigDict(arbitrary_types_allowed=True)
# # env exclude=True to avoid `RecursionError: maximum recursion depth exceeded in comparison`
env: "Environment" = Field(default=None, exclude=True)
env: "Environment" = Field(default=None, exclude=True) # # avoid circular import
# TODO judge if ser&deser
msg_buffer: MessageQueue = Field(
default_factory=MessageQueue, exclude=True
@ -108,7 +110,6 @@ class RoleContext(BaseModel):
RoleReactMode.REACT
) # see `Role._set_react_mode` for definitions of the following two attributes
max_react_loop: int = 1
model_config = ConfigDict(arbitrary_types_allowed=True)
def check(self, role_id: str):
# if hasattr(CONFIG, "long_term_memory") and CONFIG.long_term_memory:
@ -132,7 +133,7 @@ role_subclass_registry = {}
class Role(BaseModel):
"""Role/Agent"""
model_config = ConfigDict(arbitrary_types_allowed=True, exclude=["_llm"])
model_config = ConfigDict(arbitrary_types_allowed=True, exclude=["llm"])
name: str = ""
profile: str = ""
@ -141,80 +142,70 @@ class Role(BaseModel):
desc: str = ""
is_human: bool = False
_llm: BaseGPTAPI = PrivateAttr(default_factory=LLM) # Each role has its own LLM, use different system message
_role_id: str = PrivateAttr(default="")
_states: list[str] = PrivateAttr(default=[])
_actions: list[Action] = PrivateAttr(default=[])
_rc: RoleContext = PrivateAttr(default_factory=RoleContext)
llm: BaseGPTAPI = Field(
default_factory=LLM, exclude=True
) # Each role has its own LLM, use different system message
role_id: str = ""
states: list[str] = []
actions: list[Action] = Field(default=[], validate_default=True)
rc: RoleContext = Field(default_factory=RoleContext)
subscription: set[str] = set()
# builtin variables
recovered: bool = False # to tag if a recovered role
latest_observed_msg: Message = None # record the latest observed message when interrupted
latest_observed_msg: Optional[Message] = None # record the latest observed message when interrupted
builtin_class_name: str = ""
_private_attributes = {
# "_llm": None,
# "_role_id": _role_id,
# "_states": [],
# "_actions": [],
# "_rc": RoleContext(),
# "_subscription": set(),
}
__hash__ = object.__hash__ # support Role as hashable type in `Environment.members`
def __init__(self, **kwargs: Any):
for index in range(len(kwargs.get("_actions", []))):
current_action = kwargs["_actions"][index]
if isinstance(current_action, dict):
item_class_name = current_action.get("builtin_class_name", None)
for name, subclass in action_subclass_registry.items():
registery_class_name = subclass.__fields__["builtin_class_name"].default
if item_class_name == registery_class_name:
current_action = subclass(**current_action)
break
kwargs["_actions"][index] = current_action
RoleContext.model_rebuild()
super().__init__(**kwargs)
@field_validator("actions", mode="before")
@classmethod
def check_actions(cls, actions: list[Union[dict, Action]]) -> list[Action]:
new_actions = []
for action in actions:
if isinstance(action, dict):
item_class_name = action.get("builtin_class_name", None)
if item_class_name:
for name, subclass in action_subclass_registry.items():
registery_class_name = subclass.model_fields["builtin_class_name"].default
if item_class_name == registery_class_name:
new_action = subclass(**action)
break
new_actions.append(new_action)
else:
new_actions.append(action)
return new_actions
# 关于私有变量的初始化 https://github.com/pydantic/pydantic/issues/655
self._private_attributes["_llm"] = LLM() if not self.is_human else HumanProvider()
self._private_attributes["_role_id"] = str(self._setting)
self.subscription = {any_to_str(self), self.name} if self.name else {any_to_str(self)}
@model_validator(mode="after")
def check_subscription(self) -> set:
if not self.subscription:
self.subscription = {any_to_str(self), self.name} if self.name else {any_to_str(self)}
return self
# for key in self._private_attributes.keys():
# if key in kwargs:
# object.__setattr__(self, key, kwargs[key])
# if key == "_rc":
# _rc = RoleContext(**kwargs["_rc"])
# object.__setattr__(self, "_rc", _rc)
# else:
# if key == "_rc":
# # # Warning, if use self._private_attributes["_rc"],
# # # self._rc will be a shared object between roles, so init one or reset it inside `_reset`
# object.__setattr__(self, key, RoleContext())
# else:
# object.__setattr__(self, key, self._private_attributes[key])
def __init__(self, **data: Any):
# --- avoid PydanticUndefinedAnnotation name 'Environment' is not defined #
from metagpt.environment import Environment
self._llm.system_prompt = self._get_prefix()
Environment
# ------
Role.model_rebuild()
super().__init__(**data)
self.llm.system_prompt = self._get_prefix()
# deserialize child classes dynamically for inherited `role`
object.__setattr__(self, "builtin_class_name", self.__class__.__name__)
self.model_fields["builtin_class_name"].default = self.__class__.__name__
if "actions" in kwargs:
self._init_actions(kwargs["actions"])
self._watch(kwargs.get("watch") or [UserRequirement])
self._watch(data.get("watch") or [UserRequirement])
def __init_subclass__(cls, **kwargs: Any) -> None:
super().__init_subclass__(**kwargs)
role_subclass_registry[cls.__name__] = cls
def _reset(self):
object.__setattr__(self, "_states", [])
object.__setattr__(self, "_actions", [])
object.__setattr__(self, "states", [])
object.__setattr__(self, "actions", [])
@property
def _setting(self):
@ -227,12 +218,12 @@ class Role(BaseModel):
else stg_path
)
role_info = self.model_dump(exclude={"_rc": {"memory": True, "msg_buffer": True}, "_llm": True})
role_info = self.model_dump(exclude={"rc": {"memory": True, "msg_buffer": True}, "llm": True})
role_info.update({"role_class": self.__class__.__name__, "module_name": self.__module__})
role_info_path = stg_path.joinpath("role_info.json")
write_json_file(role_info_path, role_info)
self._rc.memory.serialize(stg_path) # serialize role's memory alone
self.rc.memory.serialize(stg_path) # serialize role's memory alone
@classmethod
def deserialize(cls, stg_path: Path) -> "Role":
@ -256,13 +247,13 @@ class Role(BaseModel):
action.set_prefix(self._get_prefix())
def refresh_system_message(self):
self._llm.system_prompt = self._get_prefix()
self.llm.system_prompt = self._get_prefix()
def set_recovered(self, recovered: bool = False):
self.recovered = recovered
def set_memory(self, memory: Memory):
self._rc.memory = memory
self.rc.memory = memory
def init_actions(self, actions):
self._init_actions(actions)
@ -272,7 +263,7 @@ class Role(BaseModel):
for idx, action in enumerate(actions):
if not isinstance(action, Action):
## 默认初始化
i = action(name="", llm=self._llm)
i = action(name="", llm=self.llm)
else:
if self.is_human and not isinstance(action.llm, HumanProvider):
logger.warning(
@ -281,10 +272,9 @@ class Role(BaseModel):
f"try passing in Action classes instead of initialized instances"
)
i = action
# i.set_env(self._rc.env)
self._init_action_system_message(i)
self._actions.append(i)
self._states.append(f"{idx}. {action}")
self.actions.append(i)
self.states.append(f"{idx}. {action}")
def _set_react_mode(self, react_mode: str, max_react_loop: int = 1):
"""Set strategy of the Role reacting to observed Message. Variation lies in how
@ -303,20 +293,20 @@ class Role(BaseModel):
Defaults to 1, i.e. _think -> _act (-> return result and end)
"""
assert react_mode in RoleReactMode.values(), f"react_mode must be one of {RoleReactMode.values()}"
self._rc.react_mode = react_mode
self.rc.react_mode = react_mode
if react_mode == RoleReactMode.REACT:
self._rc.max_react_loop = max_react_loop
self.rc.max_react_loop = max_react_loop
def _watch(self, actions: Iterable[Type[Action]] | Iterable[Action]):
"""Watch Actions of interest. Role will select Messages caused by these Actions from its personal message
buffer during _observe.
"""
self._rc.watch = {any_to_str(t) for t in actions}
self.rc.watch = {any_to_str(t) for t in actions}
# check RoleContext after adding watch actions
self._rc.check(self._role_id)
self.rc.check(self.role_id)
def is_watch(self, caused_by: str):
return caused_by in self._rc.watch
return caused_by in self.rc.watch
def subscribe(self, tags: Set[str]):
"""Used to receive Messages with certain tags from the environment. Message will be put into personal message
@ -324,19 +314,19 @@ class Role(BaseModel):
or profile.
"""
self.subscription = tags
if self._rc.env: # According to the routing feature plan in Chapter 2.2.3.2 of RFC 113
self._rc.env.set_subscription(self, self.subscription)
if self.rc.env: # According to the routing feature plan in Chapter 2.2.3.2 of RFC 113
self.rc.env.set_subscription(self, self.subscription)
def _set_state(self, state: int):
"""Update the current state."""
self._rc.state = state
logger.debug(f"actions={self._actions}, state={state}")
self._rc.todo = self._actions[self._rc.state] if state >= 0 else None
self.rc.state = state
logger.debug(f"actions={self.actions}, state={state}")
self.rc.todo = self.actions[self.rc.state] if state >= 0 else None
def set_env(self, env: "Environment"):
"""Set the environment in which the role works. The role can talk to the environment and can also receive
messages by observing."""
self._rc.env = env
self.rc.env = env
if env:
env.set_subscription(self, self.subscription)
self.refresh_system_message() # add env message to system message
@ -344,7 +334,7 @@ class Role(BaseModel):
@property
def action_count(self):
"""Return number of action"""
return len(self._actions)
return len(self.actions)
def _get_prefix(self):
"""Get the role prefix"""
@ -356,38 +346,38 @@ class Role(BaseModel):
if self.constraints:
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())
env_desc = f"You are in {self._rc.env.desc} with roles({other_role_names})."
if self.rc.env and self.rc.env.desc:
other_role_names = ", ".join(self.rc.env.role_names())
env_desc = f"You are in {self.rc.env.desc} with roles({other_role_names})."
prefix += env_desc
return prefix
async def _think(self) -> bool:
"""Consider what to do and decide on the next course of action. Return false if nothing can be done."""
if len(self._actions) == 1:
if len(self.actions) == 1:
# If there is only one action, then only this one can be performed
self._set_state(0)
return True
if self.recovered and self._rc.state >= 0:
self._set_state(self._rc.state) # action to run from recovered state
if self.recovered and self.rc.state >= 0:
self._set_state(self.rc.state) # action to run from recovered state
self.set_recovered(False) # avoid max_react_loop out of work
return True
prompt = self._get_prefix()
prompt += STATE_TEMPLATE.format(
history=self._rc.history,
states="\n".join(self._states),
n_states=len(self._states) - 1,
previous_state=self._rc.state,
history=self.rc.history,
states="\n".join(self.states),
n_states=len(self.states) - 1,
previous_state=self.rc.state,
)
next_state = await self._llm.aask(prompt)
next_state = await self.llm.aask(prompt)
next_state = extract_state_value_from_output(next_state)
logger.debug(f"{prompt=}")
if (not next_state.isdigit() and next_state != "-1") or int(next_state) not in range(-1, len(self._states)):
if (not next_state.isdigit() and next_state != "-1") or int(next_state) not in range(-1, len(self.states)):
logger.warning(f"Invalid answer of state, {next_state=}, will be set to -1")
next_state = -1
else:
@ -398,21 +388,21 @@ class Role(BaseModel):
return True
async def _act(self) -> Message:
logger.info(f"{self._setting}: to do {self._rc.todo}({self._rc.todo.name})")
response = await self._rc.todo.run(self._rc.history)
logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")
response = await self.rc.todo.run(self.rc.history)
if isinstance(response, (ActionOutput, ActionNode)):
msg = Message(
content=response.content,
instruct_content=response.instruct_content,
role=self._setting,
cause_by=self._rc.todo,
cause_by=self.rc.todo,
sent_from=self,
)
elif isinstance(response, Message):
msg = response
else:
msg = Message(content=response, role=self.profile, cause_by=self._rc.todo, sent_from=self)
self._rc.memory.add(msg)
msg = Message(content=response, role=self.profile, cause_by=self.rc.todo, sent_from=self)
self.rc.memory.add(msg)
return msg
@ -422,7 +412,7 @@ class Role(BaseModel):
observed_pure = [msg.dict(exclude={"id": True}) for msg in observed]
existed_pure = [msg.dict(exclude={"id": True}) for msg in existed]
for idx, new in enumerate(observed_pure):
if (new["cause_by"] in self._rc.watch or self.name in new["send_to"]) and new not in existed_pure:
if (new["cause_by"] in self.rc.watch or self.name in new["send_to"]) and new not in existed_pure:
news.append(observed[idx])
return news
@ -433,59 +423,59 @@ class Role(BaseModel):
if self.recovered:
news = [self.latest_observed_msg] if self.latest_observed_msg else []
if not news:
news = self._rc.msg_buffer.pop_all()
news = self.rc.msg_buffer.pop_all()
# Store the read messages in your own memory to prevent duplicate processing.
old_messages = [] if ignore_memory else self._rc.memory.get()
self._rc.memory.add_batch(news)
old_messages = [] if ignore_memory else self.rc.memory.get()
self.rc.memory.add_batch(news)
# Filter out messages of interest.
self._rc.news = [n for n in news if n.cause_by in self._rc.watch and n not in old_messages]
self.latest_observed_msg = self._rc.news[-1] if self._rc.news else None # record the latest observed msg
self.rc.news = [n for n in news if n.cause_by in self.rc.watch and n not in old_messages]
self.latest_observed_msg = self.rc.news[-1] if self.rc.news else None # record the latest observed msg
# Design Rules:
# If you need to further categorize Message objects, you can do so using the Message.set_meta function.
# msg_buffer is a receiving buffer, avoid adding message data and operations to msg_buffer.
news_text = [f"{i.role}: {i.content[:20]}..." for i in self._rc.news]
news_text = [f"{i.role}: {i.content[:20]}..." for i in self.rc.news]
if news_text:
logger.debug(f"{self._setting} observed: {news_text}")
return len(self._rc.news)
return len(self.rc.news)
# async def _observe(self, ignore_memory=False) -> int:
# """Prepare new messages for processing from the message buffer and other sources."""
# # Read unprocessed messages from the msg buffer.
# news = self._rc.msg_buffer.pop_all()
# news = self.rc.msg_buffer.pop_all()
# if self.recovered:
# news = [self.latest_observed_msg] if self.latest_observed_msg else []
# else:
# self.latest_observed_msg = news[-1] if len(news) > 0 else None # record the latest observed msg
#
# # Store the read messages in your own memory to prevent duplicate processing.
# old_messages = [] if ignore_memory else self._rc.memory.get()
# self._rc.memory.add_batch(news)
# old_messages = [] if ignore_memory else self.rc.memory.get()
# self.rc.memory.add_batch(news)
# # Filter out messages of interest.
# self._rc.news = self._find_news(news, old_messages)
# self.rc.news = self._find_news(news, old_messages)
#
# # Design Rules:
# # If you need to further categorize Message objects, you can do so using the Message.set_meta function.
# # msg_buffer is a receiving buffer, avoid adding message data and operations to msg_buffer.
# news_text = [f"{i.role}: {i.content[:20]}..." for i in self._rc.news]
# news_text = [f"{i.role}: {i.content[:20]}..." for i in self.rc.news]
# if news_text:
# logger.debug(f"{self._setting} observed: {news_text}")
# return len(self._rc.news)
# return len(self.rc.news)
def publish_message(self, msg):
"""If the role belongs to env, then the role's messages will be broadcast to env"""
if not msg:
return
if not self._rc.env:
if not self.rc.env:
# If env does not exist, do not publish the message
return
self._rc.env.publish_message(msg)
self.rc.env.publish_message(msg)
def put_message(self, message):
"""Place the message into the Role object's private message buffer."""
if not message:
return
self._rc.msg_buffer.push(message)
self.rc.msg_buffer.push(message)
async def _react(self) -> Message:
"""Think first, then act, until the Role _think it is time to stop and requires no more todo.
@ -494,22 +484,22 @@ class Role(BaseModel):
"""
actions_taken = 0
rsp = Message(content="No actions taken yet") # will be overwritten after Role _act
while actions_taken < self._rc.max_react_loop:
while actions_taken < self.rc.max_react_loop:
# think
await self._think()
if self._rc.todo is None:
if self.rc.todo is None:
break
# act
logger.debug(f"{self._setting}: {self._rc.state=}, will do {self._rc.todo}")
logger.debug(f"{self._setting}: {self.rc.state=}, will do {self.rc.todo}")
rsp = await self._act() # 这个rsp是否需要publish_message
actions_taken += 1
return rsp # return output from the last action
async def _act_by_order(self) -> Message:
"""switch action each time by order defined in _init_actions, i.e. _act (Action1) -> _act (Action2) -> ..."""
start_idx = self._rc.state if self._rc.state >= 0 else 0 # action to run from recovered state
rsp = Message(content="No actions taken yet") # return default message if _actions=[]
for i in range(start_idx, len(self._states)):
start_idx = self.rc.state if self.rc.state >= 0 else 0 # action to run from recovered state
rsp = Message(content="No actions taken yet") # return default message if actions=[]
for i in range(start_idx, len(self.states)):
self._set_state(i)
rsp = await self._act()
return rsp # return output from the last action
@ -521,18 +511,18 @@ class Role(BaseModel):
async def react(self) -> Message:
"""Entry to one of three strategies by which Role reacts to the observed Message"""
if self._rc.react_mode == RoleReactMode.REACT:
if self.rc.react_mode == RoleReactMode.REACT:
rsp = await self._react()
elif self._rc.react_mode == RoleReactMode.BY_ORDER:
elif self.rc.react_mode == RoleReactMode.BY_ORDER:
rsp = await self._act_by_order()
elif self._rc.react_mode == RoleReactMode.PLAN_AND_ACT:
elif self.rc.react_mode == RoleReactMode.PLAN_AND_ACT:
rsp = await self._plan_and_act()
self._set_state(state=-1) # current reaction is complete, reset state to -1 and todo back to None
return rsp
def get_memories(self, k=0) -> list[Message]:
"""A wrapper to return the most recent k memories of this role, return all when k=0"""
return self._rc.memory.get(k=k)
return self.rc.memory.get(k=k)
@role_raise_decorator
async def run(self, with_message=None) -> Message | None:
@ -557,7 +547,7 @@ class Role(BaseModel):
rsp = await self.react()
# Reset the next action to be taken.
self._rc.todo = None
self.rc.todo = None
# Send the response message to the Environment object to have it relay the message to the subscribers.
self.publish_message(rsp)
return rsp
@ -565,12 +555,12 @@ class Role(BaseModel):
@property
def is_idle(self) -> bool:
"""If true, all actions have been executed."""
return not self._rc.news and not self._rc.todo and self._rc.msg_buffer.empty()
return not self.rc.news and not self.rc.todo and self.rc.msg_buffer.empty()
async def think(self) -> Action:
"""The exported `think` function"""
await self._think()
return self._rc.todo
return self.rc.todo
async def act(self) -> ActionOutput:
"""The exported `act` function"""
@ -580,6 +570,6 @@ class Role(BaseModel):
@property
def todo(self) -> str:
"""AgentStore uses this attribute to display to the user what actions the current role should take."""
if self._actions:
return any_to_name(self._actions[0])
if self.actions:
return any_to_name(self.actions[0])
return ""

View file

@ -57,19 +57,19 @@ class Searcher(Role):
async def _act_sp(self) -> Message:
"""Performs the search action in a single process."""
logger.info(f"{self._setting}: to do {self._rc.todo}({self._rc.todo.name})")
response = await self._rc.todo.run(self._rc.memory.get(k=0))
logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")
response = await self.rc.todo.run(self.rc.memory.get(k=0))
if isinstance(response, (ActionOutput, ActionNode)):
msg = Message(
content=response.content,
instruct_content=response.instruct_content,
role=self.profile,
cause_by=self._rc.todo,
cause_by=self.rc.todo,
)
else:
msg = Message(content=response, role=self.profile, cause_by=self._rc.todo)
self._rc.memory.add(msg)
msg = Message(content=response, role=self.profile, cause_by=self.rc.todo)
self.rc.memory.add(msg)
return msg
async def _act(self) -> Message:

View file

@ -7,7 +7,7 @@
@Modified By: mashenquan, 2023-11-1. In accordance with Chapter 2.2.1 and 2.2.2 of RFC 116, utilize the new message
distribution feature for message filtering.
"""
from typing import Any, Type
from typing import Any, Type, Union
from pydantic import Field
from semantic_kernel import Kernel
@ -43,15 +43,15 @@ class SkAgent(Role):
plan: Any = None
planner_cls: Any = None
planner: Any = None
planner: Union[BasicPlanner, SequentialPlanner, ActionPlanner] = None
llm: BaseGPTAPI = Field(default_factory=LLM)
kernel: Kernel = Field(default_factory=Kernel)
import_semantic_skill_from_directory: Type[Kernel.import_semantic_skill_from_directory] = None
import_skill: Type[Kernel.import_skill] = None
def __init__(self, **kwargs) -> None:
def __init__(self, **data: Any) -> None:
"""Initializes the Engineer role with given attributes."""
super().__init__(**kwargs)
super().__init__(**data)
self._init_actions([ExecuteTask()])
self._watch([UserRequirement])
self.kernel = make_sk_kernel()
@ -71,10 +71,10 @@ class SkAgent(Role):
self._set_state(0)
# how funny the interface is inconsistent
if isinstance(self.planner, BasicPlanner):
self.plan = await self.planner.create_plan_async(self._rc.important_memory[-1].content, self.kernel)
self.plan = await self.planner.create_plan_async(self.rc.important_memory[-1].content, self.kernel)
logger.info(self.plan.generated_plan)
elif any(isinstance(self.planner, cls) for cls in [SequentialPlanner, ActionPlanner]):
self.plan = await self.planner.create_plan_async(self._rc.important_memory[-1].content)
self.plan = await self.planner.create_plan_async(self.rc.important_memory[-1].content)
async def _act(self) -> Message:
# how funny the interface is inconsistent
@ -85,6 +85,6 @@ class SkAgent(Role):
result = (await self.plan.invoke_async()).result
logger.info(result)
msg = Message(content=result, role=self.profile, cause_by=self._rc.todo)
self._rc.memory.add(msg)
msg = Message(content=result, role=self.profile, cause_by=self.rc.todo)
self.rc.memory.add(msg)
return msg

View file

@ -42,34 +42,34 @@ class Teacher(Role):
async def _think(self) -> bool:
"""Everything will be done part by part."""
if not self._actions:
if not self._rc.news or self._rc.news[0].cause_by != any_to_str(UserRequirement):
if not self.actions:
if not self.rc.news or self.rc.news[0].cause_by != any_to_str(UserRequirement):
raise ValueError("Lesson content invalid.")
actions = []
print(TeachingPlanBlock.TOPICS)
for topic in TeachingPlanBlock.TOPICS:
act = WriteTeachingPlanPart(context=self._rc.news[0].content, topic=topic, llm=self._llm)
act = WriteTeachingPlanPart(context=self.rc.news[0].content, topic=topic, llm=self.llm)
actions.append(act)
self._init_actions(actions)
if self._rc.todo is None:
if self.rc.todo is None:
self._set_state(0)
return True
if self._rc.state + 1 < len(self._states):
self._set_state(self._rc.state + 1)
if self.rc.state + 1 < len(self.states):
self._set_state(self.rc.state + 1)
return True
self._rc.todo = None
self.rc.todo = None
return False
async def _react(self) -> Message:
ret = Message(content="")
while True:
await self._think()
if self._rc.todo is None:
if self.rc.todo is None:
break
logger.debug(f"{self._setting}: {self._rc.state=}, will do {self._rc.todo}")
logger.debug(f"{self._setting}: {self.rc.state=}, will do {self.rc.todo}")
msg = await self._act()
if ret.content != "":
ret.content += "\n\n\n"
@ -104,7 +104,7 @@ class Teacher(Role):
def course_title(self):
"""Return course title of teaching plan"""
default_title = "teaching_plan"
for act in self._actions:
for act in self.actions:
if act.topic != TeachingPlanBlock.COURSE_TITLE:
continue
if act.rsp is None:

View file

@ -71,9 +71,9 @@ class TutorialAssistant(Role):
Returns:
A message containing the result of the action.
"""
todo = self._rc.todo
todo = self.rc.todo
if type(todo) is WriteDirectory:
msg = self._rc.memory.get(k=1)[0]
msg = self.rc.memory.get(k=1)[0]
self.topic = msg.content
resp = await todo.run(topic=self.topic)
logger.info(resp)

View file

@ -23,9 +23,16 @@ from abc import ABC
from asyncio import Queue, QueueEmpty, wait_for
from json import JSONDecodeError
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Type, TypeVar
from typing import Any, Dict, List, Optional, Type, TypeVar, Union
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
from pydantic import (
BaseModel,
ConfigDict,
Field,
PrivateAttr,
field_serializer,
field_validator,
)
from metagpt.config import CONFIG
from metagpt.const import (
@ -102,33 +109,64 @@ class Documents(BaseModel):
class Message(BaseModel):
"""list[<role>: <content>]"""
id: str # According to Section 2.2.3.1.1 of RFC 135
id: str = Field(default="", validate_default=True) # According to Section 2.2.3.1.1 of RFC 135
content: str
instruct_content: BaseModel = None
instruct_content: Optional[BaseModel] = Field(default=None, validate_default=True)
role: str = "user" # system / user / assistant
cause_by: str = ""
sent_from: str = ""
send_to: Set = Field(default={MESSAGE_ROUTE_TO_ALL})
cause_by: str = Field(default="", validate_default=True)
sent_from: str = Field(default="", validate_default=True)
send_to: set = Field(default={MESSAGE_ROUTE_TO_ALL}, validate_default=True)
def __init__(self, content: str = "", **kwargs):
ic = kwargs.get("instruct_content", None)
@field_validator("id", mode="before")
@classmethod
def check_id(cls, id: str) -> str:
return id if id else uuid.uuid4().hex
@field_validator("instruct_content", mode="before")
@classmethod
def check_instruct_content(cls, ic: Any) -> BaseModel:
if ic and not isinstance(ic, BaseModel) and "class" in ic:
# compatible with custom-defined ActionOutput
mapping = actionoutput_str_to_mapping(ic["mapping"])
actionnode_class = import_class("ActionNode", "metagpt.actions.action_node") # avoid circular import
ic_obj = actionnode_class.create_model_class(class_name=ic["class"], mapping=mapping)
ic_new = ic_obj(**ic["value"])
kwargs["instruct_content"] = ic_new
ic = ic_obj(**ic["value"])
return ic
kwargs["id"] = kwargs.get("id", uuid.uuid4().hex)
kwargs["content"] = kwargs.get("content", content)
kwargs["cause_by"] = any_to_str(
kwargs.get("cause_by", import_class("UserRequirement", "metagpt.actions.add_requirement"))
)
kwargs["sent_from"] = any_to_str(kwargs.get("sent_from", ""))
kwargs["send_to"] = any_to_str_set(kwargs.get("send_to", {MESSAGE_ROUTE_TO_ALL}))
super(Message, self).__init__(**kwargs)
@field_validator("cause_by", mode="before")
@classmethod
def check_cause_by(cls, cause_by: Any) -> str:
return any_to_str(cause_by if cause_by else import_class("UserRequirement", "metagpt.actions.add_requirement"))
@field_validator("sent_from", mode="before")
@classmethod
def check_sent_from(cls, sent_from: Any) -> str:
return any_to_str(sent_from if sent_from else "")
@field_validator("send_to", mode="before")
@classmethod
def check_send_to(cls, send_to: Any) -> set:
return any_to_str_set(send_to if send_to else {MESSAGE_ROUTE_TO_ALL})
@field_serializer("instruct_content", mode="plain")
def ser_instruct_content(self, ic: BaseModel) -> Union[str, None]:
ic_dict = None
if ic:
# compatible with custom-defined ActionOutput
schema = ic.model_json_schema()
# `Documents` contain definitions
if "definitions" not in schema:
# TODO refine with nested BaseModel
mapping = actionoutout_schema_to_mapping(schema)
mapping = actionoutput_mapping_to_str(mapping)
ic_dict = {"class": schema["title"], "mapping": mapping, "value": ic.model_dump()}
return ic_dict
def __init__(self, content: str = "", **data: Any):
data["content"] = data.get("content", content)
super().__init__(**data)
def __setattr__(self, key, val):
"""Override `@property.setter`, convert non-string parameters into string parameters."""
@ -142,22 +180,6 @@ class Message(BaseModel):
new_val = val
super().__setattr__(key, new_val)
def dict(self, *args, **kwargs) -> dict[str, Any]:
"""overwrite the `dict` to dump dynamic pydantic model"""
obj_dict = super(Message, self).model_dump(*args, **kwargs)
ic = self.instruct_content
if ic:
# compatible with custom-defined ActionOutput
schema = ic.model_json_schema()
# `Documents` contain definitions
if "definitions" not in schema:
# TODO refine with nested BaseModel
mapping = actionoutout_schema_to_mapping(schema)
mapping = actionoutput_mapping_to_str(mapping)
obj_dict["instruct_content"] = {"class": schema["title"], "mapping": mapping, "value": ic.model_dump()}
return obj_dict
def __str__(self):
# prefix = '-'.join([self.role, str(self.cause_by)])
if self.instruct_content:
@ -173,7 +195,7 @@ class Message(BaseModel):
def dump(self) -> str:
"""Convert the object to json string"""
return self.json(exclude_none=True)
return self.model_dump_json(exclude_none=True)
@staticmethod
@handle_exception(exception_type=JSONDecodeError, default_return=None)

View file

@ -10,6 +10,7 @@
import warnings
from pathlib import Path
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
@ -40,12 +41,12 @@ class Team(BaseModel):
investment: float = Field(default=10.0)
idea: str = Field(default="")
def __init__(self, **kwargs):
super().__init__(**kwargs)
if "roles" in kwargs:
self.hire(kwargs["roles"])
if "env_desc" in kwargs:
self.env.desc = kwargs["env_desc"]
def __init__(self, **data: Any):
super(Team, self).__init__(**data)
if "roles" in data:
self.hire(data["roles"])
if "env_desc" in data:
self.env.desc = data["env_desc"]
def serialize(self, stg_path: Path = None):
stg_path = SERDESER_PATH.joinpath("team") if stg_path is None else stg_path
@ -55,10 +56,6 @@ class Team(BaseModel):
self.env.serialize(stg_path.joinpath("environment")) # save environment alone
@classmethod
def recover(cls, stg_path: Path) -> "Team":
return cls.deserialize(stg_path)
@classmethod
def deserialize(cls, stg_path: Path) -> "Team":
"""stg_path = ./storage/team"""
@ -74,9 +71,9 @@ class Team(BaseModel):
# recover environment
environment = Environment.deserialize(stg_path=stg_path.joinpath("environment"))
team_info.update({"env": environment})
# team_info.update({"env": environment})
team = Team(**team_info)
team.env = environment
return team
def hire(self, roles: list[Role]):
@ -120,7 +117,7 @@ class Team(BaseModel):
return self.run_project(idea=idea, send_to=send_to)
def _save(self):
logger.info(self.json(ensure_ascii=False))
logger.info(self.model_dump_json())
@serialize_decorator
async def run(self, n_round=3, idea="", send_to="", auto_archive=True):

View file

@ -25,11 +25,12 @@ except ImportError:
class GoogleAPIWrapper(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
google_api_key: Optional[str] = Field(default=None, validate_default=True)
google_cse_id: Optional[str] = Field(default=None, validate_default=True)
loop: Optional[asyncio.AbstractEventLoop] = None
executor: Optional[futures.Executor] = None
model_config = ConfigDict(arbitrary_types_allowed=True)
@field_validator("google_api_key", mode="before")
@classmethod

View file

@ -9,7 +9,7 @@ import json
from typing import Any, Dict, Optional, Tuple
import aiohttp
from pydantic import BaseModel, ConfigDict, Field, field_validator
from pydantic import BaseModel, Field, field_validator
from metagpt.config import CONFIG
@ -19,7 +19,6 @@ class SerperWrapper(BaseModel):
payload: dict = Field(default={"page": 1, "num": 10})
serper_api_key: Optional[str] = Field(default=None, validate_default=True)
aiosession: Optional[aiohttp.ClientSession] = None
model_config = ConfigDict(arbitrary_types_allowed=True)
@field_validator("serper_api_key", mode="before")
@classmethod

View file

@ -27,7 +27,7 @@ from typing import Any, Callable, List, Tuple, Union, get_args, get_origin
import aiofiles
import loguru
from pydantic.json import pydantic_encoder
from pydantic_core import to_jsonable_python
from tenacity import RetryCallState, _utils
from metagpt.const import MESSAGE_ROUTE_TO_ALL
@ -472,7 +472,7 @@ def write_json_file(json_file: str, data: list, encoding=None):
folder_path.mkdir(parents=True, exist_ok=True)
with open(json_file, "w", encoding=encoding) as fout:
json.dump(data, fout, ensure_ascii=False, indent=4, default=pydantic_encoder)
json.dump(data, fout, ensure_ascii=False, indent=4, default=to_jsonable_python)
def import_class(class_name: str, module_name: str) -> type:
@ -512,7 +512,7 @@ def role_raise_decorator(func):
except KeyboardInterrupt as kbi:
logger.error(f"KeyboardInterrupt: {kbi} occurs, start to serialize the project")
if self.latest_observed_msg:
self._rc.memory.delete(self.latest_observed_msg)
self.rc.memory.delete(self.latest_observed_msg)
# raise again to make it captured outside
raise Exception(format_trackback_info(limit=None))
except Exception:
@ -522,7 +522,7 @@ def role_raise_decorator(func):
"we delete the newest role communication message in the role's memory."
)
# remove role newest observed msg to make it observed again
self._rc.memory.delete(self.latest_observed_msg)
self.rc.memory.delete(self.latest_observed_msg)
# raise again to make it captured outside
raise Exception(format_trackback_info(limit=None))

View file

@ -65,7 +65,7 @@ def serialize_message(message: "Message"):
schema = ic.model_json_schema()
mapping = actionoutout_schema_to_mapping(schema)
message_cp.instruct_content = {"class": schema["title"], "mapping": mapping, "value": ic.dict()}
message_cp.instruct_content = {"class": schema["title"], "mapping": mapping, "value": ic.model_dump()}
msg_ser = pickle.dumps(message_cp)
return msg_ser