Skip to content

feat: configure include_contents="none" in subagents and implement history pruning callback for plan, implement and autotune agents - #83

Open
shangkunwang01 wants to merge 1 commit into
mainfrom
shangkun-history-pruning
Open

feat: configure include_contents="none" in subagents and implement history pruning callback for plan, implement and autotune agents#83
shangkunwang01 wants to merge 1 commit into
mainfrom
shangkun-history-pruning

Conversation

@shangkunwang01

Copy link
Copy Markdown
Collaborator

No description provided.

…story pruning callback for plan, implement and autotune agents
@shangkunwang01
shangkunwang01 requested a review from stingram August 7, 2026 17:30

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

Copy link
Copy Markdown

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 a mechanism to prune intermediate tool history from LLM requests to optimize context window usage. It adds a helper function prune_intermediate_tool_history in callbacks.py and integrates it across various subagents, alongside setting include_contents="none" for specific agents. The review feedback highlights potential runtime errors, such as AttributeError when a part's text attribute is None and TypeError if parts is None. It provides actionable suggestions to safely handle these cases and recommends using dynamic class instantiation (c.__class__) to avoid importing google.genai.types directly.

Comment on lines +393 to +399
parts = getattr(c, "parts", [])
# Remove tool call/response parts (both raw attributes and serialized logs)
new_parts = [p for p in parts if not _is_tool_part(p)]
# Keep the turn if there is at least one meaningful part remaining
# (not just an orphaned 'For context:' header left after removing tool logs)
if any(getattr(p, "text", "").strip() != "For context:" for p in new_parts):
filtered_history.append(types.Content(role=c.role, parts=new_parts))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

There are three key improvements here:

  1. Avoid AttributeError: In the Google GenAI SDK, Part objects for non-text components (such as function calls or inline data) can have their text attribute set to None. Calling getattr(p, "text", "").strip() will return None (since the attribute exists but is None), leading to an AttributeError: 'NoneType' object has no attribute 'strip' at runtime. Using (getattr(p, "text", None) or "").strip() safely handles this.
  2. Dynamic Class Instantiation: Reconstructing the content using types.Content introduces an unnecessary dependency on google.genai.types and might cause class mismatch issues if the upstream ADK uses a different wrapper class. Using c.__class__ dynamically preserves the original class type and allows removing the unused import of types on line 11.
  3. Safety Fallback: Adding or [] to getattr(c, "parts", []) prevents potential TypeError crashes if parts is None.
Suggested change
parts = getattr(c, "parts", [])
# Remove tool call/response parts (both raw attributes and serialized logs)
new_parts = [p for p in parts if not _is_tool_part(p)]
# Keep the turn if there is at least one meaningful part remaining
# (not just an orphaned 'For context:' header left after removing tool logs)
if any(getattr(p, "text", "").strip() != "For context:" for p in new_parts):
filtered_history.append(types.Content(role=c.role, parts=new_parts))
parts = getattr(c, "parts", []) or []
# Remove tool call/response parts (both raw attributes and serialized logs)
new_parts = [p for p in parts if not _is_tool_part(p)]
# Keep the turn if there is at least one meaningful part remaining
# (not just an orphaned 'For context:' header left after removing tool logs)
if any((getattr(p, "text", None) or "").strip() not in ("", "For context:") for p in new_parts):
filtered_history.append(c.__class__(role=getattr(c, "role", None), parts=new_parts))

Comment on lines +382 to +383
parts = getattr(last_content, "parts", [])
if any(_is_tool_part(p) for p in parts):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If parts is explicitly set to None or is missing and defaults to None in the content object, getattr(last_content, "parts", []) might return None. This will cause a TypeError: 'NoneType' object is not iterable when evaluated in the any() generator expression. Ensuring a fallback to an empty list or [] prevents this potential crash.

Suggested change
parts = getattr(last_content, "parts", [])
if any(_is_tool_part(p) for p in parts):
parts = getattr(last_content, "parts", []) or []
if any(_is_tool_part(p) for p in parts):

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.

1 participant