diff --git a/metagpt/exp_pool/context_builders/role_zero.py b/metagpt/exp_pool/context_builders/role_zero.py index b492ca5ca..6407314ac 100644 --- a/metagpt/exp_pool/context_builders/role_zero.py +++ b/metagpt/exp_pool/context_builders/role_zero.py @@ -15,16 +15,15 @@ class RoleZeroContextBuilder(BaseContextBuilder): Returns: list[dict]: The updated request with formatted experiences or the original request if no experiences are available. """ - req = kwargs.get("req", []) if not req: return req - exps_str = self.format_exps() - if not exps_str: + exps = self.format_exps() + if not exps: return req - req[-1]["content"] = self.replace_example_content(req[-1].get("content", ""), exps_str) + req[-1]["content"] = self.replace_example_content(req[-1].get("content", ""), exps) return req diff --git a/metagpt/exp_pool/decorator.py b/metagpt/exp_pool/decorator.py index deb3faafc..0a9a83818 100644 --- a/metagpt/exp_pool/decorator.py +++ b/metagpt/exp_pool/decorator.py @@ -1,7 +1,6 @@ """Experience Decorator.""" import asyncio -import copy import functools from typing import Any, Callable, Optional, TypeVar @@ -33,9 +32,11 @@ def exp_cache( ): """Decorator to get a perfect experience, otherwise, it executes the function, and create a new experience. - 1. This can be applied to both synchronous and asynchronous functions. - 2. The function must have a `req` parameter, and it must be provided as a keyword argument. - 3. If `config.exp_pool.enable_read` is False, the decorator will just directly execute the function. + Note: + 1. This can be applied to both synchronous and asynchronous functions. + 2. The function must have a `req` parameter, and it must be provided as a keyword argument. + 3. If `config.exp_pool.enable_read` is False, the decorator will just directly execute the function. + 4. If `config.exp_pool.enable_write` is False, the decorator will skip evaluating and saving the experience. Args: _func: Just to make the decorator more flexible, for example, it can be used directly with @exp_cache by default, without the need for @exp_cache(). @@ -68,11 +69,14 @@ def exp_cache( ) await handler.fetch_experiences() + if exp := await handler.get_one_perfect_exp(): return exp await handler.execute_function() - await handler.process_experience() + + if config.exp_pool.enable_write: + await handler.process_experience() return handler._raw_resp @@ -117,7 +121,7 @@ class ExpCacheHandler(BaseModel): self.serializer = self.serializer or SimpleSerializer() self.tag = self.tag or self._generate_tag() - self._req = self.serializer.serialize_req(copy.deepcopy(self.kwargs["req"])) + self._req = self.serializer.serialize_req(self.kwargs["req"]) return self @@ -140,7 +144,7 @@ class ExpCacheHandler(BaseModel): """Execute the function, and save resp.""" self._raw_resp = await self._execute_function() - self._resp = self.serializer.serialize_resp(copy.deepcopy(self._raw_resp)) + self._resp = self.serializer.serialize_resp(self._raw_resp) @handle_exception async def process_experience(self): diff --git a/metagpt/exp_pool/serializers/base.py b/metagpt/exp_pool/serializers/base.py index 82a0ed8c4..9d00a05b2 100644 --- a/metagpt/exp_pool/serializers/base.py +++ b/metagpt/exp_pool/serializers/base.py @@ -11,11 +11,18 @@ class BaseSerializer(BaseModel, ABC): @abstractmethod def serialize_req(self, req: Any) -> str: - """Serializes the request for storage.""" + """Serializes the request for storage. + + Do not modify req. If modification is necessary, use copy.deepcopy to create a copy first. + Note that copy.deepcopy may raise errors, such as TypeError: cannot pickle '_thread.RLock' object. + """ @abstractmethod def serialize_resp(self, resp: Any) -> str: - """Serializes the function's return value for storage.""" + """Serializes the function's return value for storage. + + Do not modify resp. The rest is the same as `serialize_req`. + """ @abstractmethod def deserialize_resp(self, resp: str) -> Any: diff --git a/metagpt/exp_pool/serializers/role_zero.py b/metagpt/exp_pool/serializers/role_zero.py index 75e5d5ecb..7876ef12a 100644 --- a/metagpt/exp_pool/serializers/role_zero.py +++ b/metagpt/exp_pool/serializers/role_zero.py @@ -1,5 +1,6 @@ """RoleZero Serializer.""" +import copy import json from metagpt.exp_pool.context_builders import RoleZeroContextBuilder @@ -27,7 +28,8 @@ class RoleZeroSerializer(SimpleSerializer): if not req: return "" - last_content = req[-1]["content"] + req_copy = copy.deepcopy(req) + last_content = req_copy[-1]["content"] last_content = RoleZeroContextBuilder.replace_content_between_markers( last_content, "# Data Structure", "# Current Plan", "" ) @@ -35,6 +37,6 @@ class RoleZeroSerializer(SimpleSerializer): last_content, "# Example", "# Instruction", "" ) - req[-1]["content"] = last_content + req_copy[-1]["content"] = last_content - return json.dumps(req) + return json.dumps(req_copy) diff --git a/metagpt/strategy/experience_retriever.py b/metagpt/strategy/experience_retriever.py index 7bcd4be11..32f5c2316 100644 --- a/metagpt/strategy/experience_retriever.py +++ b/metagpt/strategy/experience_retriever.py @@ -798,13 +798,13 @@ Explanation: I will first need to read the system design document and the projec { "command_name": "Editor.read", "args": { - "path": "/tmp/docs/project_schedule.json" + "path": "/tmp/project_schedule.json" } }, { "command_name": "Editor.read", "args": { - "path": "/tmp/docs/system_design.json" + "path": "/tmp/system_design.json" } } ]