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
This commit is contained in:
jinliyl 2026-03-31 16:22:39 +08:00 • committed by GitHub
parent 2a999ce4f4
commit 9ad8120959
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 60 additions and 29 deletions

View file

@ -83,7 +83,7 @@ litellm = [
]
light = [
"agentscope==1.0.17",
"agentscope==1.0.18",
"flowllm[reme]>=0.2.0.10",
]

View file

@ -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",

View file

@ -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:

View file

@ -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(

View file

@ -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(