Skip to content

perf(telemetry): skip serializing the request history when content capture is off (20% faster at 300 turns) - #7579

Open
deadstrobe5 wants to merge 1 commit into
google:mainfrom
deadstrobe5:perf/skip-request-content-telemetry-when-not-captured
Open

deadstrobe5 wants to merge 1 commit into
google:mainfrom
deadstrobe5:perf/skip-request-content-telemetry-when-not-captured

Conversation

@deadstrobe5

Copy link
Copy Markdown

Link to Issue or Description of Change

2. Or, if no issue exists, describe the change:

Problem:
The experimental GenAI semconv is the default with the v2 schema. Under it, every LLM call runs set_operation_details_attributes_from_request, which converts the full request history (contents) and the system instructions into OTel values. In the default NO_CONTENT mode, both the completion log and the span go through _operation_details_attributes_no_content, which keeps only GEN_AI_TOOL_DEFINITIONS. Everything else is thrown away. Each step therefore costs O(history), and a session costs O(n²) in its number of turns.

Solution:
When neither logs nor experimental spans capture content, only the tool definitions are built (the same _resolve_tool_definitions + _to_any_value as the full path). The full path is unchanged when content capture is on, and the emitted telemetry is identical in both modes.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

I added a test that checks that, with content capture off, the attributes contain only the tool definitions and match the no-content view of the full path. The telemetry suite has the same results with and without this change (886 passed). Its 100 pre-existing local failures also happen on main.

Manual End-to-End (E2E) Tests:

InMemoryRunner with a fake BaseLlm, one tool call per turn, median of 5 runs:

turns before after
200 2.83 s 2.27 s
300 6.15 s 4.92 s

The time saved grows with the session length. A dump of all events of a 100-turn session hashes identically before and after.

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules.

…e is off

With the experimental GenAI semconv (default on schema v2) every LLM call
converted the full request contents and system instruction into OTel
AnyValues, even in the default NO_CONTENT mode where only the tool
definitions survive redaction. That made each step O(history), so a
session paid O(n^2). Build only the tool definitions when neither logs
nor spans capture content; emitted telemetry is unchanged.
@google-cla

google-cla Bot commented Oct 16, 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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an include_content parameter to set_operation_details_attributes_from_request to avoid serializing the full conversation history when content capture is disabled, while still retaining tool definitions. This is integrated into the tracing logic and verified with a new unit test. Feedback suggests adding a defensive check to prevent a potential AttributeError if llm_request.config is None.

# Without content capture only the tool definitions survive redaction
# (see _operation_details_attributes_no_content), so skip serializing the
# whole conversation history on every request.
tool_definitions = _resolve_tool_definitions(llm_request.config.tools or [])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential AttributeError if llm_request.config is None (e.g., in unit tests or dynamic configurations), it is safer to use defensive programming and check if config is present before accessing its tools attribute.

    tools = llm_request.config.tools if llm_request.config else None
    tool_definitions = _resolve_tool_definitions(tools or [])

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LlmRequest.config is a required field with default_factory=types.GenerateContentConfig, so it is never None here, and the existing content-capture path a few lines up reads llm_request.config.tools the same way. I kept the two consistent.

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.

2 participants