From 9ad81209592062320aa49d53b90ae64801b95a00 Mon Sep 17 00:00:00 2001 From: jinliyl <6469360+jinliyl@users.noreply.github.com> Date: Tue, 31 Mar 2026 16:22:39 +0800 Subject: [PATCH] feat(compactor): add extra instruction support and improve error handing (#190) * feat(compactor): add extra instruction support and improve error handling - Add extra_instruction parameter to compactor for custom guidance during message compaction - Implement try-catch blocks around AS LLM initialization with detailed error logging - Add extra_instruction parameter to ReMe.compact method with comprehensive documentation - Update agentscope dependency from 1.0.17 to 1.0.18 in light installation - Bump version number from 0.3.1.6 to 0.3.1.7 - Pass extra_instruction parameter through compactor instantiation and execution flow * fix(core): add error handling for AS LLM formatters and token counters initialization - Wrapped AS LLM formatters initialization in try-except blocks - Added specific error logging for failed AS LLM formatter initialization - Wrapped AS token counters initialization in try-except blocks - Added specific error logging for failed AS token counter initialization - Applied same error handling pattern to both initial setup and restart operations - Maintained existing warning logs for unsupported backends --- pyproject.toml | 2 +- reme/__init__.py | 2 +- reme/core/application.py | 72 ++++++++++++------- .../memory/file_based/components/compactor.py | 5 ++ reme/reme_light.py | 8 +++ 5 files changed, 60 insertions(+), 29 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 36b12e98..91956ce2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,7 +83,7 @@ litellm = [ ] light = [ - "agentscope==1.0.17", + "agentscope==1.0.18", "flowllm[reme]>=0.2.0.10", ] diff --git a/reme/__init__.py b/reme/__init__.py index f9ade852..3c235eb0 100644 --- a/reme/__init__.py +++ b/reme/__init__.py @@ -6,7 +6,7 @@ from . import extension from . import memory from .reme import ReMe -__version__ = "0.3.1.6" +__version__ = "0.3.1.7" __all__ = [ "config", diff --git a/reme/core/application.py b/reme/core/application.py index e2e5b3c0..8b5b1932 100644 --- a/reme/core/application.py +++ b/reme/core/application.py @@ -173,28 +173,37 @@ class Application: if config.backend not in R.as_llms: logger.warning(f"AS LLM backend {config.backend} is not supported.") else: - config_dict = config.model_dump(exclude={"backend"}) - if not config_dict.get("api_key", ""): - config_dict["api_key"] = self.llm_api_key - if "client_kwargs" not in config_dict: - config_dict["client_kwargs"] = {} - if not config_dict["client_kwargs"].get("base_url", ""): - config_dict["client_kwargs"]["base_url"] = self.llm_base_url - self.service_context.as_llms[name] = R.as_llms[config.backend](**config_dict) + try: + config_dict = config.model_dump(exclude={"backend"}) + if not config_dict.get("api_key", ""): + config_dict["api_key"] = self.llm_api_key + if "client_kwargs" not in config_dict: + config_dict["client_kwargs"] = {} + if not config_dict["client_kwargs"].get("base_url", ""): + config_dict["client_kwargs"]["base_url"] = self.llm_base_url + self.service_context.as_llms[name] = R.as_llms[config.backend](**config_dict) + except Exception as e: + logger.error(f"Failed to initialize AS LLM '{name}': {e}") for name, config in self.service_config.as_llm_formatters.items(): if config.backend not in R.as_llm_formatters: logger.warning(f"AS LLM formatter backend {config.backend} is not supported.") else: - config_dict = config.model_dump(exclude={"backend"}) - self.service_context.as_llm_formatters[name] = R.as_llm_formatters[config.backend](**config_dict) + try: + config_dict = config.model_dump(exclude={"backend"}) + self.service_context.as_llm_formatters[name] = R.as_llm_formatters[config.backend](**config_dict) + except Exception as e: + logger.error(f"Failed to initialize AS LLM formatter '{name}': {e}") for name, config in self.service_config.as_token_counters.items(): if config.backend not in R.as_token_counters: logger.warning(f"Token counter backend {config.backend} is not supported.") else: - config_dict = config.model_dump(exclude={"backend"}) - self.service_context.as_token_counters[name] = R.as_token_counters[config.backend](**config_dict) + try: + config_dict = config.model_dump(exclude={"backend"}) + self.service_context.as_token_counters[name] = R.as_token_counters[config.backend](**config_dict) + except Exception as e: + logger.error(f"Failed to initialize AS token counter '{name}': {e}") for name, config in self.service_config.llms.items(): if config.backend not in R.llms: @@ -287,15 +296,18 @@ class Application: logger.warning(f"AS LLM backend {config.get('backend')} is not supported.") continue - config_dict = {k: v for k, v in config.items() if k != "backend"} - if not config_dict.get("api_key", ""): - config_dict["api_key"] = self.llm_api_key - if "client_kwargs" not in config_dict: - config_dict["client_kwargs"] = {} - if not config_dict["client_kwargs"].get("base_url", ""): - config_dict["client_kwargs"]["base_url"] = self.llm_base_url - self.service_context.as_llms[name] = R.as_llms[config["backend"]](**config_dict) - logger.info(f"Restarted AS LLM: {name}") + try: + config_dict = {k: v for k, v in config.items() if k != "backend"} + if not config_dict.get("api_key", ""): + config_dict["api_key"] = self.llm_api_key + if "client_kwargs" not in config_dict: + config_dict["client_kwargs"] = {} + if not config_dict["client_kwargs"].get("base_url", ""): + config_dict["client_kwargs"]["base_url"] = self.llm_base_url + self.service_context.as_llms[name] = R.as_llms[config["backend"]](**config_dict) + logger.info(f"Restarted AS LLM: {name}") + except Exception as e: + logger.error(f"Failed to restart AS LLM '{name}': {e}") # as_llm_formatters if "as_llm_formatters" in restart_config: @@ -308,9 +320,12 @@ class Application: if config.get("backend") not in R.as_llm_formatters: logger.warning(f"AS LLM formatter backend {config.get('backend')} is not supported.") continue - config_dict = {k: v for k, v in config.items() if k != "backend"} - self.service_context.as_llm_formatters[name] = R.as_llm_formatters[config["backend"]](**config_dict) - logger.info(f"Restarted AS LLM formatter: {name}") + try: + config_dict = {k: v for k, v in config.items() if k != "backend"} + self.service_context.as_llm_formatters[name] = R.as_llm_formatters[config["backend"]](**config_dict) + logger.info(f"Restarted AS LLM formatter: {name}") + except Exception as e: + logger.error(f"Failed to restart AS LLM formatter '{name}': {e}") # as_token_counters if "as_token_counters" in restart_config: @@ -323,9 +338,12 @@ class Application: if config.get("backend") not in R.as_token_counters: logger.warning(f"Token counter backend {config.get('backend')} is not supported.") continue - config_dict = {k: v for k, v in config.items() if k != "backend"} - self.service_context.as_token_counters[name] = R.as_token_counters[config["backend"]](**config_dict) - logger.info(f"Restarted AS token counter: {name}") + try: + config_dict = {k: v for k, v in config.items() if k != "backend"} + self.service_context.as_token_counters[name] = R.as_token_counters[config["backend"]](**config_dict) + logger.info(f"Restarted AS token counter: {name}") + except Exception as e: + logger.error(f"Failed to restart AS token counter '{name}': {e}") # llms if "llms" in restart_config: diff --git a/reme/memory/file_based/components/compactor.py b/reme/memory/file_based/components/compactor.py index 7e8b31ad..e8fe9383 100644 --- a/reme/memory/file_based/components/compactor.py +++ b/reme/memory/file_based/components/compactor.py @@ -35,6 +35,7 @@ class Compactor(BaseOp): console_enabled: bool = False, return_dict: bool = False, add_thinking_block: bool = True, + extra_instruction: str = "", **kwargs, ): super().__init__(**kwargs) @@ -42,6 +43,7 @@ class Compactor(BaseOp): self.console_enabled: bool = console_enabled self.return_dict: bool = return_dict self.add_thinking_block: bool = add_thinking_block + self.extra_instruction: str = extra_instruction # pylint: disable=too-many-return-statements async def execute(self): @@ -84,6 +86,9 @@ class Compactor(BaseOp): ) else: user_message: str = f"# conversation\n{history_formatted_str}\n\n" + self.get_prompt("initial_user_message") + + if self.extra_instruction: + user_message += f"\n\n# extra-instruction\n{self.extra_instruction}" logger.info(f"Compactor sys_prompt={agent.sys_prompt} user_message={user_message}") compact_msg: Msg = await agent.reply( diff --git a/reme/reme_light.py b/reme/reme_light.py index 5cad2f64..91e738ed 100644 --- a/reme/reme_light.py +++ b/reme/reme_light.py @@ -369,6 +369,7 @@ class ReMeLight(Application): previous_summary: str = "", return_dict: bool = False, add_thinking_block: bool = True, + extra_instruction: str = "", ) -> str | dict: """ Compact a list of messages into a condensed summary. @@ -395,6 +396,12 @@ class ReMeLight(Application): summary for continuity. Defaults to empty string. return_dict (bool): If True, returns a dict with user_message, history_compact, and is_valid. Defaults to False. + add_thinking_block (bool): If True, adds a thinking block to the summary. + extra_instruction (str): Optional additional instruction appended to the + compaction prompt. Use this to guide what information to keep or + remove. For example: "Remove debug logs and tool-call details. Keep + requirements, decisions, and pending tasks." Defaults to empty string + (no extra instruction, preserving default behavior). Returns: str | dict: The condensed summary string, or a dict containing @@ -410,6 +417,7 @@ class ReMeLight(Application): language=language if language == "zh" else "", return_dict=return_dict, add_thinking_block=add_thinking_block, + extra_instruction=extra_instruction, ) return await compactor.call(