From 41c6b7236044c0f09305bf04a918cbe3017f5868 Mon Sep 17 00:00:00 2001 From: Yashraj Shukla Date: Fri, 22 May 2026 16:32:40 +0000 Subject: [PATCH] fix(compaction): preserve namespaced tool names in LLM summarizer prompt after context compaction the LlmEventSummarizer rewrote conversation history using an LLM. also the default ADK prompt had no instruction to preserve exact tool names so the LLM abbreviated namespaced tool names like 'kagent__default__agent_name_aa' to just 'agent_name_aa'.when the orchestrator agent resumed after compaction it could not dispatch to the sub-agent because the short name does not match the registered tool name Fix: inject a kagent-specific default prompt_template that explicitly instructs the summarizer LLM to preserve exact tool/agent names including namespace prefixes.users who supply a custom prompt_template are unaffected (their value takes precedence via 'or') Fixes #1550 Signed-off-by: Yashraj Shukla --- .../kagent-adk/src/kagent/adk/types.py | 24 +++++++- .../tests/unittests/test_context_config.py | 57 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/python/packages/kagent-adk/src/kagent/adk/types.py b/python/packages/kagent-adk/src/kagent/adk/types.py index 5e2f4a97af..f246c39da8 100644 --- a/python/packages/kagent-adk/src/kagent/adk/types.py +++ b/python/packages/kagent-adk/src/kagent/adk/types.py @@ -615,6 +615,27 @@ def _create_llm_from_model_config(model_config: ModelUnion): raise ValueError(f"Invalid model type: {model_config.type}") +_KAGENT_TOOL_NAME_WARNING = ( + "\n\nIMPORTANT: When referencing any tools or agents that were used," + " you MUST preserve their exact registered names including any" + " namespace prefixes (for example: 'kagent__default__agent_name')." + " Never shorten, abbreviate, or rephrase tool or agent names." +) + +_KAGENT_COMPACTION_PROMPT = ( + "The following is a conversation history between a user and an AI" + " agent. Please summarize the conversation, focusing on key" + " information and decisions made, as well as any unresolved" + " questions or tasks. The summary should be concise and capture the" + " essence of the interaction.\n\n" + "IMPORTANT: When referencing any tools or agents that were used," + " you MUST preserve their exact registered names including any" + " namespace prefixes (for example: 'kagent__default__agent_name')." + " Never shorten, abbreviate, or rephrase tool or agent names.\n\n" + "{conversation_history}" +) + + def build_adk_context_configs( context_config: ContextConfig, ) -> tuple: @@ -636,7 +657,8 @@ def build_adk_context_configs( summarizer = LlmEventSummarizer( llm=_create_llm_from_model_config(comp.summarizer_model), - prompt_template=comp.prompt_template, + prompt_template=(comp.prompt_template if comp.prompt_template else _KAGENT_COMPACTION_PROMPT) + + (str() if not comp.prompt_template else _KAGENT_TOOL_NAME_WARNING), ) events_compaction_config = EventsCompactionConfig( diff --git a/python/packages/kagent-adk/tests/unittests/test_context_config.py b/python/packages/kagent-adk/tests/unittests/test_context_config.py index 95dd09f0d5..b0a7551649 100644 --- a/python/packages/kagent-adk/tests/unittests/test_context_config.py +++ b/python/packages/kagent-adk/tests/unittests/test_context_config.py @@ -24,6 +24,14 @@ def _make_agent_config_json(**context_kwargs) -> str: return json.dumps(config) +def _get_prompt_template(summarizer): + """Use public attribute if available, fall back to private (resilient across google-adk versions).""" + pub = getattr(summarizer, "prompt_template", None) + if pub is not None: + return pub + return getattr(summarizer, "_prompt_template", None) + + class TestContextConfigParsing: def test_no_context_config(self): config = AgentConfig.model_validate_json(_make_agent_config_json()) @@ -159,3 +167,52 @@ def test_empty_config(self): events_cfg, cache_cfg = build_adk_context_configs(config) assert events_cfg is None assert cache_cfg is None + + def test_summarizer_uses_kagent_default_prompt_when_none_provided(self): + """When no prompt_template is given, the kagent default that preserves + tool names should be used instead of the ADK default.""" + from kagent.adk.types import _KAGENT_COMPACTION_PROMPT + + config = ContextConfig( + compaction=ContextCompressionSettings( + compaction_interval=5, + overlap_size=2, + summarizer_model=OpenAI(type="openai", model="gpt-4o-mini"), + ) + ) + events_cfg, _ = build_adk_context_configs(config) + assert events_cfg is not None + assert events_cfg.summarizer is not None + assert _get_prompt_template(events_cfg.summarizer) == _KAGENT_COMPACTION_PROMPT + assert "{conversation_history}" in _KAGENT_COMPACTION_PROMPT + + def test_summarizer_respects_custom_prompt_template(self): + """A user-supplied prompt_template should have tool name warning appended.""" + from kagent.adk.types import _KAGENT_TOOL_NAME_WARNING + + custom = "My custom prompt: {conversation_history}" + config = ContextConfig( + compaction=ContextCompressionSettings( + compaction_interval=5, + overlap_size=2, + summarizer_model=OpenAI(type="openai", model="gpt-4o-mini"), + prompt_template=custom, + ) + ) + events_cfg, _ = build_adk_context_configs(config) + assert _get_prompt_template(events_cfg.summarizer) == custom + _KAGENT_TOOL_NAME_WARNING + + def test_summarizer_preserves_empty_string_prompt_template(self): + """An empty string prompt_template should fall back to the kagent default.""" + from kagent.adk.types import _KAGENT_COMPACTION_PROMPT + + config = ContextConfig( + compaction=ContextCompressionSettings( + compaction_interval=5, + overlap_size=2, + summarizer_model=OpenAI(type="openai", model="gpt-4o-mini"), + prompt_template="", + ) + ) + events_cfg, _ = build_adk_context_configs(config) + assert _get_prompt_template(events_cfg.summarizer) == _KAGENT_COMPACTION_PROMPT