Skip to content

fix(plugins): allow complete LoggingPlugin output - #7079

Open
Manitchahar wants to merge 2 commits into
google:mainfrom
Manitchahar:fix/logging-plugin-truncation
Open

fix(plugins): allow complete LoggingPlugin output#7079
Manitchahar wants to merge 2 commits into
google:mainfrom
Manitchahar:fix/logging-plugin-truncation

Conversation

@Manitchahar

Copy link
Copy Markdown

Link to Issue or Description of Change

Closes #6056.

LoggingPlugin cuts off message text and system instructions at 200 characters,
and tool arguments/results at 300. A diagnostic at the end of a long tool result
is therefore missing from the console output.

Add two keyword-only constructor options, max_content_length=200 and
max_args_length=300. Existing defaults are unchanged; None disables
truncation. The change only touches the existing plugin and its test file.

LoggingPlugin(max_content_length=None, max_args_length=None)

This is a draft pending feedback on the constructor options requested in the
issue discussion. No maintainer agreement on this API is assumed.

Testing Plan

  • Added regression tests for custom limits, boundary lengths, zero, and
    unlimited output through the public callbacks.
  • All repository unit tests pass locally.

Results:

  • Before the patch: 20 new cases fail, 11 existing tests pass.
  • Built-wheel logging tests: 31 passed on each of Python 3.10, 3.11, 3.12,
    3.13, and 3.14
    , using isolated environments.
  • Plugin suite on Python 3.11: 802 passed, 2 skipped, 8 failed. The eight
    failures are in TestSafetyLifecycleHardening in the BigQuery analytics
    plugin. Re-running that class against unchanged upstream d9b57bf reproduced
    the same eight failures, with 91 passing tests.
  • Formatting, lint, and compliance checks pass. The new-file hook cannot invoke
    /bin/bash on Windows; its underlying command,
    python scripts/check_new_py_files.py --new-dir ., passes when run directly.
    The optional addlicense executable was not installed; existing license headers
    were preserved.
  • uv build --wheel succeeds. The documentation example executes successfully.
  • The full tox matrix was attempted but stopped while its first environment was
    still running without a result. It is not reported as passing. The broad
    mypy run also did not finish and is not reported as passing.

Manual End-to-End Evidence

A real InMemoryRunner uses a local scripted BaseLlm to call a diagnostic
tool, then emit a model response. This exercises actual runner/plugin dispatch
without credentials or a remote model. The same reproduction passes from the
built wheel in an isolated environment.

Marker after the default cutoff Defaults Limits of 600 Limits of None
Tool result error detail Hidden Visible Visible
Model response detail Hidden Visible Visible
System instruction detail Hidden Visible Visible

Checklist

  • Read the contribution guide and self-reviewed the diff.
  • Added regression tests and exercised the runner end to end.
  • Preserved defaults and kept unrelated code unchanged.
  • Maintainer agreement on the proposed options.
  • Complete full-suite validation before marking ready for review.
  • Documentation change merged and published.
Runner reproduction

Save as logging_repro.py and run with the built wheel installed.

"""Exercise LoggingPlugin through a real Runner without a model API call."""

import asyncio
from contextlib import redirect_stdout
from io import StringIO

from google.adk.agents import LlmAgent
from google.adk.models.base_llm import BaseLlm
from google.adk.models.llm_response import LlmResponse
from google.adk.plugins.logging_plugin import LoggingPlugin
from google.adk.runners import InMemoryRunner
from google.genai import types


def diagnose() -> dict:
    """Return a diagnostic with the failure detail at the end."""
    return {"details": "x" * 400, "error": "TOOL_ERROR_AT_END"}


class ScriptedModel(BaseLlm):
    async def generate_content_async(self, llm_request, stream=False):
        has_result = any(
            part.function_response
            for content in llm_request.contents
            for part in content.parts or []
        )
        part = (
            types.Part(text="x" * 250 + "MODEL_DETAIL_AT_END")
            if has_result
            else types.Part.from_function_call(name="diagnose", args={})
        )
        yield LlmResponse(content=types.Content(role="model", parts=[part]))


async def run(label, **limits):
    agent = LlmAgent(
        name="diagnostic",
        model=ScriptedModel(model="scripted"),
        instruction="x" * 250 + "INSTRUCTION_AT_END",
        tools=[diagnose],
    )
    output = StringIO()
    async with InMemoryRunner(
        agent=agent, plugins=[LoggingPlugin(**limits)]
    ) as runner:
        session = await runner.session_service.create_session(
            app_name=runner.app_name, user_id="test"
        )
        with redirect_stdout(output):
            async for event in runner.run_async(
                user_id="test", session_id=session.id,
                new_message=types.Content(
                    role="user", parts=[types.Part(text="Run the diagnostic.")]
                ),
            ):
                pass
    for marker in ("TOOL_ERROR_AT_END", "MODEL_DETAIL_AT_END", "INSTRUCTION_AT_END"):
        visible = marker in output.getvalue()
        assert visible == (label != "default"), (label, marker, output.getvalue())
        print(f"{label}: {marker} visible={visible}")


async def main():
    await run("default")
    await run("custom", max_content_length=600, max_args_length=600)
    await run("unlimited", max_content_length=None, max_args_length=None)


asyncio.run(main())

Companion documentation: google/adk-docs#2215

@google-cla

google-cla Bot commented Sep 9, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Expose the existing content and argument limits as keyword-only options. Preserve current defaults and accept None to disable truncation.

Fixes google#6056
@Manitchahar
Manitchahar force-pushed the fix/logging-plugin-truncation branch from f5c96a6 to 36f32ef Compare September 9, 2026 23:40
@Manitchahar
Manitchahar marked this pull request as ready for review September 9, 2026 23:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

What's the purpose of logging if messages are truncated ? At least it should be configurable

2 participants