Skip to content

Conversation

@Jainish-S
Copy link

1. Link to an existing issue (if applicable):

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

Problem:
Previously, inject_session_state() only supported flat state access (e.g., {user_name}), preventing users from accessing nested properties within state objects. This limitation forced developers to either flatten their state structure or manually handle template replacement, reducing code readability and flexibility when working with complex, hierarchical state structures.

Solution:
Added support for nested state access in template injection using dot notation with optional chaining. The implementation adds a _get_nested_value() helper function that:

  • Traverses dot-separated paths through nested dictionaries and objects
  • Supports both dictionary access (__getitem__) and attribute access (getattr)
  • Handles optional chaining with ? operator for safe navigation
  • Returns empty strings for None values or missing optional paths
  • Raises KeyError for missing required paths
  • Maintains compatibility with existing prefixed state variables (app:, user:, temp:)

This solution was chosen because it:

  • Maintains backward compatibility with existing flat state access
  • Follows common patterns from JavaScript/TypeScript (optional chaining)
  • Provides clear error messages for debugging
  • Works seamlessly with both dictionary-based and object-based state

Testing Plan

Unit Tests:

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

Summary of pytest results:

$ uv run pytest ./tests/unittests/utils/test_instructions_utils.py -v
OUT

=========================================================================================================================================== test session starts ============================================================================================================================================
platform darwin -- Python 3.11.13, pytest-9.0.1, pluggy-1.6.0 -- /Users/jainish/os/adk-python/.venv/bin/python3
cachedir: .pytest_cache
rootdir: /Users/jainish/os/adk-python
configfile: pyproject.toml
plugins: mock-3.15.1, langsmith-0.4.29, xdist-3.8.0, anyio-4.10.0, asyncio-1.3.0
asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=function, asyncio_default_test_loop_scope=function
collected 27 items                                                                                                                                                                                                                                                                                         

tests/unittests/utils/test_instructions_utils.py::test_inject_session_state PASSED                                                                                                                                                                                                                   [  3%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_artifact PASSED                                                                                                                                                                                                     [  7%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_optional_state PASSED                                                                                                                                                                                               [ 11%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_missing_state_raises_key_error PASSED                                                                                                                                                                               [ 14%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_missing_artifact_raises_key_error PASSED                                                                                                                                                                            [ 18%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_invalid_state_name_returns_original PASSED                                                                                                                                                                          [ 22%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_invalid_prefix_state_name_returns_original PASSED                                                                                                                                                                   [ 25%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_valid_prefix_state PASSED                                                                                                                                                                                           [ 29%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_multiple_variables_and_artifacts PASSED                                                                                                                                                                             [ 33%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_empty_artifact_name_raises_key_error PASSED                                                                                                                                                                         [ 37%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_artifact_service_not_initialized_raises_value_error PASSED                                                                                                                                                               [ 40%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_optional_missing_artifact_returns_empty PASSED                                                                                                                                                                      [ 44%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_none_state_value_returns_empty PASSED                                                                                                                                                                               [ 48%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_optional_missing_state_returns_empty PASSED                                                                                                                                                                         [ 51%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_nested_dict_access PASSED                                                                                                                                                                                           [ 55%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_deep_nested_access PASSED                                                                                                                                                                                           [ 59%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_optional_nested_access_existing PASSED                                                                                                                                                                              [ 62%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_optional_nested_access_missing PASSED                                                                                                                                                                               [ 66%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_optional_nested_missing_root PASSED                                                                                                                                                                                 [ 70%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_nested_none_value PASSED                                                                                                                                                                                            [ 74%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_optional_nested_none_value PASSED                                                                                                                                                                                   [ 77%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_missing_nested_key_raises_error PASSED                                                                                                                                                                              [ 81%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_required_parent_missing_raises_error PASSED                                                                                                                                                                         [ 85%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_nested_and_prefixed_state PASSED                                                                                                                                                                                    [ 88%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_mixed_nested_and_flat_state PASSED                                                                                                                                                                                  [ 92%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_numeric_nested_values PASSED                                                                                                                                                                                        [ 96%]
tests/unittests/utils/test_instructions_utils.py::test_inject_session_state_with_nested_object_attribute_access PASSED                                                                                                                                                                               [100%]

======================== 27 passed in 0.89s ==========================

Added 12 comprehensive test cases covering:

  • Basic and deep nested dictionary access
  • Optional chaining with existing and missing values
  • None value handling in nested paths
  • Error handling for missing required keys
  • Prefixed state variables with nesting (app:, user:, temp:)
  • Mixed nested and flat state access patterns
  • Numeric nested values
  • Object attribute access vs dictionary access

Manual End-to-End (E2E) Tests: Created a sample agent to demonstrate the feature (located at contributing/samples/nested_state_agent/, not included in this PR). Setup:

cd contributing/samples/nested_state_agent
adk run .

Agent code:

import logging

from google.adk.agents import Agent
from google.adk.agents.callback_context import CallbackContext
from google.adk.agents.readonly_context import ReadonlyContext
from google.adk.utils.instructions_utils import inject_session_state


def inject_nested_state(callback_context: CallbackContext):
  callback_context.state["user"] = {
      "name": "John",
      "profile": {"age": 24, "role": "Software Engineer"},
  }
  logging.info("State populated with nested user object.")


async def build_instruction(readonly_context: ReadonlyContext) -> str:
  template = (
      "Current user is {user?.name} and {user?.profile?.role}. Please greet"
      " them by name and designation."
  )
  return await inject_session_state(template, readonly_context)


root_agent = Agent(
    name="nested_state_agent",
    model="gemini-2.0-flash-lite",
    instruction=build_instruction,
    before_agent_callback=[inject_nested_state],
)

Expected behavior:

  • Agent receives instruction: "Current user is John and Software Engineer. Please greet them by name and designation."
  • Agent responds with greeting including the user's name and role
  • Missing fields with optional chaining (?) return empty strings instead of raising errors

Actual output:

INFO: State populated with nested user object.
Agent: Hello John, Software Engineer! How can I help you today?

Result: Nested state values correctly injected into instruction template

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.

Additional context

Note: This PR re-implements the solution for issue #575. A previous implementation existed but was not merged due to merge conflicts. This is a fresh implementation with the same functionality. Feature highlights:

  • ✅ Backward compatible with existing flat state access
  • ✅ Supports deeply nested structures: {user.profile.settings.theme}
  • ✅ Safe navigation with ?: {user?.profile?.department?} returns "" if missing
  • ✅ Works with both dict and object attributes
  • ✅ Compatible with prefixed state: {app:config.api.endpoint}
  • ✅ Clear error messages for debugging required fields

Files changed:

  • src/google/adk/utils/instructions_utils.py - Core implementation (+92 lines)
  • tests/unittests/utils/test_instructions_utils.py - Test coverage (+278 lines)

Key improvements made:

  1. ✅ Followed the exact template structure with all required sections
  2. ✅ Filled in all checkboxes appropriately
  3. ✅ Included actual pytest output summary as requested
  4. ✅ Provided clear E2E testing instructions with expected vs actual output
  5. ✅ Added context about this being a re-implementation
  6. ✅ Used proper markdown formatting throughout
  7. ✅ Kept your example code but formatted it better within the E2E section
  8. ✅ Made the testing plan more detailed and actionable

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @Jainish-S, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the inject_session_state() utility by introducing support for nested state variable access within instruction templates. Previously, the utility was limited to flat state access, which constrained its use with complex, hierarchical data structures. The new functionality allows developers to easily reference deeply nested properties using dot notation and includes optional chaining for safe navigation, preventing errors when parts of a path might be missing. This improvement makes the state injection mechanism more robust and flexible, aligning it with common patterns for handling structured data.

Highlights

  • Nested State Access: The inject_session_state() utility now supports accessing deeply nested state variables using dot notation (e.g., {user.profile.role}).
  • Optional Chaining: Introduced optional chaining with the ? operator (e.g., {user?.profile?.department?}). If a segment in an optional path is missing or None, it gracefully returns an empty string instead of raising an error.
  • Flexible Access Methods: The new implementation supports both dictionary-style (__getitem__) and attribute-style (getattr) access for navigating nested structures, enhancing compatibility with various state object types.
  • Backward Compatibility & Prefixed State: The changes maintain full backward compatibility with existing flat state access and continue to support prefixed state variables (e.g., {app:config.api.endpoint}) with nested paths.
  • Improved Error Handling: Clear KeyError messages are now provided when a required (non-optional) key or path segment is not found, aiding in debugging.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@adk-bot adk-bot added the mcp [Component] Issues about MCP support label Nov 23, 2025
Copy link
Contributor

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

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 valuable feature for nested state access in template injection, including optional chaining. The implementation is robust, well-documented, and accompanied by a comprehensive set of unit tests. My feedback focuses on minor code consistency improvements to enhance maintainability.

return None

optional = part.endswith('?')
key = part[:-1] if optional else part
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

To improve consistency across the file for stripping the optional marker, you could use removesuffix('?'). This method is already used on line 150 and is generally safer and more readable than slicing.

Suggested change
key = part[:-1] if optional else part
key = part.removesuffix('?')

var_name = full_path.removeprefix('artifact.')
optional = var_name.endswith('?')
if optional:
var_name = var_name[:-1]
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

For consistency with how optional markers are handled for state variables on line 150, consider using removesuffix('?') here as well. It's slightly more descriptive than slicing.

Suggested change
var_name = var_name[:-1]
var_name = var_name.removesuffix('?')

- Add nested state access support using dot notation in inject_session_state
- Fix optional chaining error handling for better robustness
- Add comprehensive test coverage for nested state templates

Resolves: google#575
@Jainish-S Jainish-S force-pushed the feat/575-nested-state-template branch from 33b3465 to 21b66a4 Compare November 24, 2025 11:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mcp [Component] Issues about MCP support

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support templating of nested values in instruction template

2 participants