feat: Add user_id property to ReadonlyContext

Merge https://github.com/google/adk-python/pull/2875

related: https://github.com/google/adk-python/issues/2876

  ## Reason for this change:

  Currently, there is no direct way to access the user_id from within agent contexts, plugins, or callbacks. This limitation prevents several important use cases:

  1. **User-specific logging and tracing**: When debugging or monitoring agent behavior, it's crucial to associate actions with specific users for better observability.
  2. **User-scoped operations**: Plugins and callbacks often need to perform user-specific operations, such as accessing user-specific resources or applying user-level configurations.
  3. **Session management**: The user_id is a key component of session identification, but it's not accessible through the ReadonlyContext interface, requiring workarounds to access it.

  ## Changes made:

  - Added a `user_id` property to the `ReadonlyContext` class in `src/google/adk/agents/readonly_context.py`
  - The property exposes the user_id from the underlying invocation context as a readonly field

  ## Impact:

  This change will:
  - Enable plugins and callbacks to access the current user's ID directly through the context
  - Improve logging and tracing capabilities by allowing user-specific tracking
  - Simplify code that needs to perform user-scoped operations without requiring access to internal implementation details
  - Maintain backward compatibility as this is an additive change

  ### Before:
- No direct way to access user_id from ReadonlyContext

###   After:
  ```python

  @property
  def user_id(self) -> str:
      """The id of the user. READONLY field."""
      return self._invocation_context.user_id
```
  This is a non-breaking change that adds a new readonly property to the existing ReadonlyContext interface.

Co-authored-by: Hangfei Lin <hangfei@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/2875 from ammmr:ammmr-add-user_id-property-to-readonly-context 771734ebf39cb0748a5dc826436236268fbb58f8
PiperOrigin-RevId: 830170908
This commit is contained in:
GenkiNoguchi
2025-11-09 14:51:52 -08:00
committed by Copybara-Service
parent 116b26c33e
commit a3b4ec69d8
2 changed files with 25 additions and 0 deletions
@@ -60,6 +60,11 @@ class ReadonlyContext:
"""The current session for this invocation.""" """The current session for this invocation."""
return self._invocation_context.session return self._invocation_context.session
@property
def user_id(self) -> str:
"""The id of the user. READONLY field."""
return self._invocation_context.user_id
@property @property
def run_config(self) -> Optional[RunConfig]: def run_config(self) -> Optional[RunConfig]:
"""The run config of the current invocation. READONLY field.""" """The run config of the current invocation. READONLY field."""
@@ -1,3 +1,17 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from types import MappingProxyType from types import MappingProxyType
from unittest.mock import MagicMock from unittest.mock import MagicMock
@@ -11,6 +25,7 @@ def mock_invocation_context():
mock_context.invocation_id = "test-invocation-id" mock_context.invocation_id = "test-invocation-id"
mock_context.agent.name = "test-agent-name" mock_context.agent.name = "test-agent-name"
mock_context.session.state = {"key1": "value1", "key2": "value2"} mock_context.session.state = {"key1": "value1", "key2": "value2"}
mock_context.user_id = "test-user-id"
return mock_context return mock_context
@@ -31,3 +46,8 @@ def test_state_content(mock_invocation_context):
assert isinstance(state, MappingProxyType) assert isinstance(state, MappingProxyType)
assert state["key1"] == "value1" assert state["key1"] == "value1"
assert state["key2"] == "value2" assert state["key2"] == "value2"
def test_user_id(mock_invocation_context):
readonly_context = ReadonlyContext(mock_invocation_context)
assert readonly_context.user_id == "test-user-id"