From 36f32ef834863615c0946c1826a0a5407dd01567 Mon Sep 17 00:00:00 2001 From: Manit Kumar <83183355+Manitchahar@users.noreply.github.com> Date: Thu, 10 Sep 2026 05:06:08 +0530 Subject: [PATCH] fix(plugins): allow complete LoggingPlugin output Expose the existing content and argument limits as keyword-only options. Preserve current defaults and accept None to disable truncation. Fixes #6056 --- src/google/adk/plugins/logging_plugin.py | 34 +++++--- .../unittests/plugins/test_logging_plugin.py | 79 +++++++++++++++++++ 2 files changed, 103 insertions(+), 10 deletions(-) diff --git a/src/google/adk/plugins/logging_plugin.py b/src/google/adk/plugins/logging_plugin.py index 1041c4330f8..3e8820fa3f7 100644 --- a/src/google/adk/plugins/logging_plugin.py +++ b/src/google/adk/plugins/logging_plugin.py @@ -59,13 +59,25 @@ class LoggingPlugin(BasePlugin): ... ) """ - def __init__(self, name: str = "logging_plugin"): + def __init__( + self, + name: str = "logging_plugin", + *, + max_content_length: Optional[int] = 200, + max_args_length: Optional[int] = 300, + ): """Initialize the logging plugin. Args: name: The name of the plugin instance. + max_content_length: Maximum characters per text part and rendered system + instruction. Set to None to disable truncation. + max_args_length: Maximum characters for tool arguments and results. + Set to None to disable truncation. """ super().__init__(name) + self._max_content_length = max_content_length + self._max_args_length = max_args_length @override async def on_user_message_callback( @@ -177,9 +189,11 @@ async def before_model_callback( system_instruction = llm_request.config.system_instruction if system_instruction: rendered_instruction = self._render_system_instruction(system_instruction) - sys_instruction = rendered_instruction[:200] - if len(rendered_instruction) > 200: - sys_instruction += "..." + max_length = self._max_content_length + if max_length is not None and len(rendered_instruction) > max_length: + sys_instruction = rendered_instruction[:max_length] + "..." + else: + sys_instruction = rendered_instruction self._log(f" System Instruction: '{sys_instruction}'") # Note: Content logging removed due to type compatibility issues @@ -311,18 +325,17 @@ def _render_system_instruction(self, system_instruction: Any) -> str: ) return str(system_instruction) - def _format_content( - self, content: Optional[types.Content], max_length: int = 200 - ) -> str: + def _format_content(self, content: Optional[types.Content]) -> str: """Format content for logging, truncating if too long.""" if not content or not content.parts: return "None" parts = [] + max_length = self._max_content_length for part in content.parts: if part.text: text = part.text.strip() - if len(text) > max_length: + if max_length is not None and len(text) > max_length: text = text[:max_length] + "..." parts.append(f"text: '{text}'") elif part.function_call: @@ -336,12 +349,13 @@ def _format_content( return " | ".join(parts) - def _format_args(self, args: dict[str, Any], max_length: int = 300) -> str: + def _format_args(self, args: dict[str, Any]) -> str: """Format arguments dictionary for logging.""" if not args: return "{}" formatted = str(args) - if len(formatted) > max_length: + max_length = self._max_args_length + if max_length is not None and len(formatted) > max_length: formatted = formatted[:max_length] + "...}" return formatted diff --git a/tests/unittests/plugins/test_logging_plugin.py b/tests/unittests/plugins/test_logging_plugin.py index 9cc8b52bb7b..c8229c1f715 100644 --- a/tests/unittests/plugins/test_logging_plugin.py +++ b/tests/unittests/plugins/test_logging_plugin.py @@ -255,3 +255,82 @@ async def test_before_model_callback_formats_list_system_instruction( out = capsys.readouterr().out assert 'Stay concise.' in out assert 'Cite sources.' in out + + +@pytest.mark.parametrize( + ('limit', 'as_content', 'expected'), + [ + (0, False, '...'), + (0, True, '...'), + (250, False, 'a' * 250), + (250, True, "text: '" + 'a' * 243 + '...'), + (500, False, 'a' * 250), + (500, True, "text: '" + 'a' * 250 + "'"), + (None, False, 'a' * 250), + (None, True, "text: '" + 'a' * 250 + "'"), + ], +) +async def test_system_instruction_respects_content_limit( + limit, as_content, expected, callback_context, capsys +): + """The configured limit applies to string and Content instructions.""" + text = 'a' * 250 + instruction = ( + types.Content(parts=[types.Part(text=text)]) if as_content else text + ) + plugin = LoggingPlugin(max_content_length=limit) + request = LlmRequest( + config=types.GenerateContentConfig(system_instruction=instruction) + ) + + await plugin.before_model_callback( + callback_context=callback_context, llm_request=request + ) + + assert f"System Instruction: '{expected}'" in capsys.readouterr().out + + +@pytest.mark.parametrize( + ('limit', 'expected'), + [(0, '...'), (250, 'a' * 250), (500, 'a' * 250), (None, 'a' * 250)], +) +async def test_event_respects_content_limit(limit, expected, capsys): + """Events use the configured text limit, including unlimited output.""" + plugin = LoggingPlugin(max_content_length=limit) + event = Event( + author='test-agent', + content=types.Content(parts=[types.Part(text='a' * 250)]), + ) + + await plugin.on_event_callback(invocation_context=None, event=event) + + assert f"Content: text: '{expected}'" in capsys.readouterr().out + + +@pytest.mark.parametrize( + ('limit', 'expected'), + [ + (0, '...}'), + (400, "{'payload': '" + 'a' * 387 + '...}'), + (500, str({'payload': 'a' * 400})), + (None, str({'payload': 'a' * 400})), + ], +) +@pytest.mark.parametrize('after', [False, True]) +async def test_tool_logging_respects_args_limit( + limit, expected, after, tool_context, capsys +): + """Tool arguments and results both use the configured dictionary limit.""" + plugin = LoggingPlugin(max_args_length=limit) + payload = {'payload': 'a' * 400} + kwargs = dict( + tool=_tool('my_tool'), tool_args=payload, tool_context=tool_context + ) + + if after: + await plugin.after_tool_callback(**kwargs, result=payload) + else: + await plugin.before_tool_callback(**kwargs) + + label = 'Result' if after else 'Arguments' + assert f'{label}: {expected}' in capsys.readouterr().out