Repository navigation
Conversation
Summary of ChangesHello @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 Highlights
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
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.
33b3465 to
21b66a4
Compare
ea451fc to
96e2f82
Compare
for the PR google#3673 & commit: 96e2f82
|
Hi @Jainish-S , Thank you for your work on this pull request. We appreciate the effort you've invested. |
for the PR google#3673 & commit: 96e2f82
|
Hi @ryanaiagent , Did the formatting. First I had decided to not include the sample and then added it in the 2nd commit and had not formatted it there. Let me know if we want to keep the sample or not. |
1eb3887 to
e7e8192
Compare
for the PR google#3673 & commit: 96e2f82
for the PR google#3673 & commit: 96e2f82
|
Hi @Jainish-S , thanks for the fix. You can leave the sample for now. |
|
Hi @ankursharmas , can you please review this. |
e7e8192 to
d9a3177
Compare
for the PR google#3673 & commit: 96e2f82
for the PR google#3673 & commit: 96e2f82
for the PR google#3673 & commit: 96e2f82
for the PR google#3673 & commit: 96e2f82
d9a3177 to
804bc38
Compare
|
@ryanaiagent @ankursharmas , any updates on this? |
804bc38 to
aedf076
Compare
for the PR google#3673 & commit: 96e2f82
for the PR google#3673 & commit: 96e2f82
aedf076 to
b001717
Compare
for the PR google#3673 & commit: 96e2f82
for the PR google#3673 & commit: 96e2f82
|
Hi @Jainish-S , We're still looking into this, so we appreciate your patience and support. |
- 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
for the PR google#3673 & commit: 96e2f82
for the PR google#3673 & commit: 96e2f82
b001717 to
2fe5321
Compare
Merge #3673 **1. Link to an existing issue (if applicable):** - Closes: #575 - Solves: google/adk-python-community#6 **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:** - [x] I have added or updated unit tests for my change. - [x] All unit tests pass locally. **Summary of pytest results:** ```bash $ 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: ```bash cd contributing/samples/nested_state_agent adk run . ``` Agent code: ```python3 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](https://github.com/google/adk-python/blob/main/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 Co-authored-by: Bo Yang <ybo@google.com> COPYBARA_INTEGRATE_REVIEW=#3673 from Jainish-S:feat/575-nested-state-template 2fe5321 PiperOrigin-RevId: 933958647
|
Thank you @Jainish-S for your contribution! 🎉 Your changes have been successfully imported and merged via Copybara in commit 94c43a2. Closing this PR as the changes are now in the main branch. |
Merge google#3673 **1. Link to an existing issue (if applicable):** - Closes: google#575 - Solves: google/adk-python-community#6 **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:** - [x] I have added or updated unit tests for my change. - [x] All unit tests pass locally. **Summary of pytest results:** ```bash $ 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: ```bash cd contributing/samples/nested_state_agent adk run . ``` Agent code: ```python3 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](https://github.com/google/adk-python/blob/main/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 google#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 Co-authored-by: Bo Yang <ybo@google.com> COPYBARA_INTEGRATE_REVIEW=google#3673 from Jainish-S:feat/575-nested-state-template 2fe5321 PiperOrigin-RevId: 933958647
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:__getitem__) and attribute access (getattr)?operator for safe navigationKeyErrorfor missing required pathsThis solution was chosen because it:
Testing Plan
Unit Tests:
Summary of pytest results:
Added 12 comprehensive test cases covering:
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:Agent code:
Expected behavior:
Actual output:
✅ Result: Nested state values correctly injected into instruction template
Checklist
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:
Files changed:
Key improvements made: