diff --git a/metagpt/exp_pool/decorator.py b/metagpt/exp_pool/decorator.py index 9cf924779..e559797a3 100644 --- a/metagpt/exp_pool/decorator.py +++ b/metagpt/exp_pool/decorator.py @@ -11,6 +11,7 @@ from metagpt.exp_pool.schema import Experience, Metric, QueryType, Score from metagpt.exp_pool.scorers import ExperienceScorer, SimpleScorer from metagpt.utils.async_helper import NestAsyncio from metagpt.utils.exceptions import handle_exception +from metagpt.utils.reflection import get_class_name ReturnType = TypeVar("ReturnType") @@ -43,7 +44,7 @@ def exp_cache( kwargs=kwargs, exp_manager=manager or exp_manager, exp_scorer=scorer or SimpleScorer(), - pass_exps=pass_exps_to_func, + pass_exps_to_func=pass_exps_to_func, ) await handler.fetch_experiences(query_type) @@ -68,16 +69,17 @@ class ExpCacheHandler(BaseModel): kwargs: Any exp_manager: ExperienceManager exp_scorer: ExperienceScorer - pass_exps: bool + pass_exps_to_func: bool = False _exps: list[Experience] = None _result: Any = None _score: Score = None + _req: str = None async def fetch_experiences(self, query_type: QueryType): """Fetch a potentially perfect existing experience.""" - req = self.generate_req_identifier() + req = self._get_req_identifier() self._exps = await self.exp_manager.query_exps(req, query_type=query_type) def get_one_perfect_experience(self) -> Optional[Experience]: @@ -105,15 +107,26 @@ class ExpCacheHandler(BaseModel): def save_experience(self): """Save the new experience.""" - req = self.generate_req_identifier() + req = self._get_req_identifier() exp = Experience(req=req, resp=self._result, metric=Metric(score=self._score)) self.exp_manager.create_exp(exp) - def generate_req_identifier(self): - """Generate a unique request identifier based on the function and its arguments.""" + def _get_req_identifier(self): + """Generate a unique request identifier based on the function and its arguments. - return f"{self.func.__name__}_{self.args}_{self.kwargs}" + Result Example: + - "write_prd-('2048',)-{}" + - "WritePRD.run-('2048',)-{}" + """ + if not self._req: + cls_name = get_class_name(self.func, *self.args) + func_name = f"{cls_name}.{self.func.__name__}" if cls_name else self.func.__name__ + args = self.args[1:] if cls_name and len(self.args) >= 1 else self.args + + self._req = f"{func_name}-{args}-{self.kwargs}" + + return self._req @staticmethod def choose_wrapper(func, wrapped_func): @@ -129,7 +142,7 @@ class ExpCacheHandler(BaseModel): return async_wrapper if asyncio.iscoroutinefunction(func) else sync_wrapper async def _execute_function(self): - if self.pass_exps: + if self.pass_exps_to_func: return await self._execute_function_with_exps() return await self._execute_function_without_exps() diff --git a/metagpt/exp_pool/manager.py b/metagpt/exp_pool/manager.py index 35ee5fdac..7382fe8f1 100644 --- a/metagpt/exp_pool/manager.py +++ b/metagpt/exp_pool/manager.py @@ -7,7 +7,7 @@ from pydantic import BaseModel, ConfigDict, model_validator from metagpt.config2 import Config, config from metagpt.exp_pool.schema import MAX_SCORE, Experience, QueryType from metagpt.rag.engines import SimpleEngine -from metagpt.rag.schema import ChromaRetrieverConfig, LLMRankerConfig +from metagpt.rag.schema import ChromaRetrieverConfig from metagpt.utils.exceptions import handle_exception @@ -31,7 +31,6 @@ class ExperienceManager(BaseModel): retriever_configs=[ ChromaRetrieverConfig(collection_name="experience_pool", persist_path=".chroma_exp_data") ], - ranker_configs=[LLMRankerConfig()], ) return self diff --git a/metagpt/utils/reflection.py b/metagpt/utils/reflection.py index 2683e5657..9b10a4b3e 100644 --- a/metagpt/utils/reflection.py +++ b/metagpt/utils/reflection.py @@ -19,9 +19,24 @@ def check_methods(C, *methods): return True -def get_func_full_name(func, *args) -> str: - if inspect.ismethod(func) or (inspect.isfunction(func) and "self" in inspect.signature(func).parameters): - cls_name = args[0].__class__.__name__ - return f"{func.__module__}.{cls_name}.{func.__name__}" +def get_class_name(func, *args) -> str: + """Returns the class name of the object that a method belongs to. - return f"{func.__module__}.{func.__name__}" + - If `func` is a bound method, extracts the class name directly from the method. + - If `func` is an unbound method and `args` are provided, assumes the first argument is `self` and extracts the class name. + - Returns an empty string if neither condition is met. + """ + if inspect.ismethod(func): + return func.__self__.__class__.__name__ + + if inspect.isfunction(func) and "self" in inspect.signature(func).parameters and args: + return args[0].__class__.__name__ + + return "" + + +def get_func_or_method_name(func, *args) -> str: + """Function name, or method name with class name.""" + cls_name = get_class_name(func, *args) + + return f"{cls_name}.{func.__name__}" if cls_name else f"{func.__name__}" diff --git a/tests/metagpt/utils/test_reflection.py b/tests/metagpt/utils/test_reflection.py new file mode 100644 index 000000000..e78e1b400 --- /dev/null +++ b/tests/metagpt/utils/test_reflection.py @@ -0,0 +1,29 @@ +from metagpt.utils.reflection import get_func_or_method_name + + +def simple_function(): + pass + + +class SampleClass: + def method(self): + pass + + +class TestFunctionOrMethodName: + def test_simple_function(self): + assert get_func_or_method_name(simple_function) == "simple_function" + + def test_class_method_without_args(self): + sample_instance = SampleClass() + assert get_func_or_method_name(sample_instance.method) == "SampleClass.method" + + def test_class_method_with_args(self): + sample_instance = SampleClass() + assert get_func_or_method_name(SampleClass.method, sample_instance) == "SampleClass.method" + + def test_function_with_no_args(self): + assert get_func_or_method_name(simple_function) == "simple_function" + + def test_method_without_instance(self): + assert get_func_or_method_name(SampleClass.method) == "method"