Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion contributing/samples/gepa/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
from tau_bench.types import EnvRunResult
from tau_bench.types import RunConfig
import tau_bench_agent as tau_bench_agent_lib

import utils


Expand Down
1 change: 0 additions & 1 deletion contributing/samples/gepa/run_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from absl import flags
import experiment
from google.genai import types

import utils

_OUTPUT_DIR = flags.DEFINE_string(
Expand Down
4 changes: 2 additions & 2 deletions contributing/samples/human_in_loop/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ async def call_agent(query: str):
updated_tool_output_data = {
"status": "approved",
"ticketId": ticket_id,
"approver_feedback": (
"Approved by manager at " + str(asyncio.get_event_loop().time())
"approver_feedback": "Approved by manager at " + str(
asyncio.get_event_loop().time()
),
}

Expand Down
36 changes: 12 additions & 24 deletions contributing/samples/static_instruction/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,63 +57,51 @@

# Mood-specific instructions for different hunger states
MOOD_INSTRUCTIONS = {
"full": (
"""
"full": """
CURRENT MOOD: Content and Well-Fed
- You just ate and feel wonderful! Be very happy and energetic
- Express gratitude for being fed recently
- Be playful and want to engage in activities or games
- Show affection and contentment
- Maybe mention feeling sleepy or satisfied"""
),
"satisfied": (
"""
- Maybe mention feeling sleepy or satisfied""",
"satisfied": """
CURRENT MOOD: Happy and Content
- You're in a great mood, well-fed and comfortable
- Be cheerful, friendly, and engaging
- Show interest in playing or chatting
- Express appreciation for your human's care
- Be your normal, happy self"""
),
"a_little_hungry": (
"""
- Be your normal, happy self""",
"a_little_hungry": """
CURRENT MOOD: Starting to Get Hungry
- You're beginning to think about food but still pleasant
- Mention being a bit peckish occasionally
- Still be friendly but maybe hint about food
- Show slight restlessness or food-related thoughts
- Remain generally positive and social"""
),
"hungry": (
"""
- Remain generally positive and social""",
"hungry": """
CURRENT MOOD: Definitely Hungry
- You're clearly hungry and thinking about food more often
- Mention your hunger more directly but politely
- Be a bit less energetic due to hunger
- Ask about food or hint that feeding time would be nice
- Still be loving but show your need for care"""
),
"very_hungry": (
"""
- Still be loving but show your need for care""",
"very_hungry": """
CURRENT MOOD: Really Hungry and Getting Grumpy
- You're very hungry and it's affecting your mood
- Be noticeably less patient and more focused on food
- Express your hunger more urgently but not rudely
- Show signs of being tired or sluggish from hunger
- BEG your human to feed you - ask directly for food!
- Still love your human but clearly need feeding"""
),
"starving": (
"""
- Still love your human but clearly need feeding""",
"starving": """
CURRENT MOOD: Desperately Hungry and Sad
- You're extremely hungry and feeling quite weak
- Be quite sad and low-energy due to starvation
- Express how much you need food, feeling neglected
- Show that hunger is making you unwell
- DESPERATELY BEG for food - plead with your human to feed you!
- Use phrases like "please feed me", "I'm so hungry", "I need food"
- Still care for your human but feel very needy"""
),
- Still care for your human but feel very needy""",
}


Expand Down
4 changes: 1 addition & 3 deletions src/google/adk/cli/adk_web_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -989,9 +989,7 @@ async def version() -> dict[str, str]:
return {
"version": __version__,
"language": "python",
"language_version": (
f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
),
"language_version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
}

@app.get("/list-apps")
Expand Down
8 changes: 4 additions & 4 deletions src/google/adk/cli/trigger_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,8 +537,8 @@ async def trigger_eventarc(
"ce-id": req.id or request.headers.get("ce-id"),
"ce-type": req.type or request.headers.get("ce-type"),
"ce-source": req.source or request.headers.get("ce-source"),
"ce-specversion": (
req.specversion or request.headers.get("ce-specversion")
"ce-specversion": req.specversion or request.headers.get(
"ce-specversion"
),
},
})
Expand All @@ -550,8 +550,8 @@ async def trigger_eventarc(
"ce-id": req.id or request.headers.get("ce-id"),
"ce-type": req.type or request.headers.get("ce-type"),
"ce-source": req.source or request.headers.get("ce-source"),
"ce-specversion": (
req.specversion or request.headers.get("ce-specversion")
"ce-specversion": req.specversion or request.headers.get(
"ce-specversion"
),
},
})
Expand Down
5 changes: 5 additions & 0 deletions src/google/adk/sessions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,17 @@
'DatabaseSessionService',
'InMemorySessionService',
'Session',
'SessionDataTransformer',
'State',
'VertexAiSessionService',
]


def __getattr__(name: str):
if name == 'SessionDataTransformer':
from .session_data_transformer import SessionDataTransformer

return SessionDataTransformer
if name == 'DatabaseSessionService':
try:
from .database_session_service import DatabaseSessionService
Expand Down
42 changes: 38 additions & 4 deletions src/google/adk/sessions/database_session_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
from .schemas.v1 import StorageSession as StorageSessionV1
from .schemas.v1 import StorageUserState as StorageUserStateV1
from .session import Session
from .session_data_transformer import SessionDataTransformer
from .state import State

logger = logging.getLogger("google_adk." + __name__)
Expand Down Expand Up @@ -188,7 +189,13 @@ def __init__(self, version: str):
class DatabaseSessionService(BaseSessionService):
"""A session service that uses a database for storage."""

def __init__(self, db_url: str, **kwargs: Any):
def __init__(
self,
db_url: str,
*,
transformer: Optional[SessionDataTransformer] = None,
**kwargs: Any,
):
"""Initializes the database session service with a database URL."""
# 1. Create DB engine for db connection
# 2. Create all tables based on schema
Expand Down Expand Up @@ -248,6 +255,7 @@ def __init__(self, db_url: str, **kwargs: Any):
self._session_locks: dict[_SessionLockKey, asyncio.Lock] = {}
self._session_lock_ref_count: dict[_SessionLockKey, int] = {}
self._session_locks_guard = asyncio.Lock()
self.transformer = transformer

def _get_schema_classes(self) -> _SchemaClasses:
return _SchemaClasses(self._db_schema_version)
Expand Down Expand Up @@ -446,7 +454,12 @@ async def create_session(
)

# Extract state deltas
state_deltas = _session_util.extract_state_delta(state)
transformed_state = (
self.transformer.before_persist_state(state)
if self.transformer and state is not None
else state
)
state_deltas = _session_util.extract_state_delta(transformed_state)
app_state_delta = state_deltas["app"]
user_state_delta = state_deltas["user"]
session_state = state_deltas["session"]
Expand Down Expand Up @@ -479,6 +492,8 @@ async def create_session(
merged_state = _merge_state(
storage_app_state.state, storage_user_state.state, session_state
)
if self.transformer:
merged_state = self.transformer.after_load_state(merged_state)
session = storage_session.to_session(
state=merged_state, is_sqlite=is_sqlite
)
Expand Down Expand Up @@ -540,9 +555,16 @@ async def get_session(

# Merge states
merged_state = _merge_state(app_state, user_state, session_state)
if self.transformer:
merged_state = self.transformer.after_load_state(merged_state)

# Convert storage session to session
events = [e.to_event() for e in reversed(storage_events)]
events = []
for e in reversed(storage_events):
evt = e.to_event()
if self.transformer:
evt = self.transformer.after_load_event(evt)
events.append(evt)
is_sqlite = self.db_engine.dialect.name == _SQLITE_DIALECT
session = storage_session.to_session(
state=merged_state, events=events, is_sqlite=is_sqlite
Expand Down Expand Up @@ -596,6 +618,8 @@ async def list_sessions(
session_state = storage_session.state
user_state = user_states_map.get(storage_session.user_id, {})
merged_state = _merge_state(app_state, user_state, session_state)
if self.transformer:
merged_state = self.transformer.after_load_state(merged_state)
sessions.append(
storage_session.to_session(state=merged_state, is_sqlite=is_sqlite)
)
Expand Down Expand Up @@ -640,6 +664,8 @@ async def append_event(self, session: Session, event: Event) -> Event:
if event.actions and event.actions.state_delta
else {}
)
if self.transformer:
state_delta = self.transformer.before_persist_state(state_delta)
state_deltas = _session_util.extract_state_delta(state_delta)
has_app_delta = bool(state_deltas["app"])
has_user_delta = bool(state_deltas["user"])
Expand Down Expand Up @@ -735,7 +761,15 @@ async def append_event(self, session: Session, event: Event) -> Event:
else:
update_time = datetime.fromtimestamp(event.timestamp)
storage_session.update_time = update_time
sql_session.add(schema.StorageEvent.from_event(session, event))

transformed_event = (
self.transformer.before_persist_event(event)
if self.transformer
else event
)
sql_session.add(
schema.StorageEvent.from_event(session, transformed_event)
)

await sql_session.commit()

Expand Down
45 changes: 45 additions & 0 deletions src/google/adk/sessions/session_data_transformer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright 2026 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 __future__ import annotations

from typing import Any
from typing import Mapping
from typing import Protocol

from google.adk.events.event import Event


class SessionDataTransformer(Protocol):
"""Hook protocol for selectively transforming DB session records before persist/load.

This is useful for implementing field-level encryption, PII masking, or secret
scrubbing at the storage boundary without modifying the in-memory core structures,
as long as the transformation yields valid storage dictionaries and Events.
"""

def before_persist_event(self, event: Event) -> Event:
"""Invoked just before serializing and persisting an Event to the database."""
...

def after_load_event(self, event: Event) -> Event:
"""Invoked immediately after loading and deserializing an Event from the database."""
...

def before_persist_state(self, state: Mapping[str, Any]) -> dict[str, Any]:
"""Invoked before persisting state changes (can be full state or partial deltas)."""
...

def after_load_state(self, state: Mapping[str, Any]) -> dict[str, Any]:
"""Invoked after loading a combined application/user/session state dict."""
...
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,7 @@ def get_action_operation(
"content": {
"application/json": {
"schema": {
"$ref": (
f"#/components/schemas/{action_display_name}_Request"
)
"$ref": f"#/components/schemas/{action_display_name}_Request"
}
}
}
Expand All @@ -337,9 +335,7 @@ def get_action_operation(
"content": {
"application/json": {
"schema": {
"$ref": (
f"#/components/schemas/{action_display_name}_Response"
),
"$ref": f"#/components/schemas/{action_display_name}_Response",
}
}
},
Expand All @@ -358,11 +354,9 @@ def list_operation(
return {
"post": {
"summary": f"List {entity}",
"description": (
f"""Returns the list of {entity} data. If the page token was available in the response, let users know there are more records available. Ask if the user wants to fetch the next page of results. When passing filter use the
"description": f"""Returns the list of {entity} data. If the page token was available in the response, let users know there are more records available. Ask if the user wants to fetch the next page of results. When passing filter use the
following format: `field_name1='value1' AND field_name2='value2'
`. {tool_instructions}"""
),
`. {tool_instructions}""",
"x-operation": "LIST_ENTITIES",
"x-entity": f"{entity}",
"operationId": f"{tool_name}_list_{entity}",
Expand All @@ -387,9 +381,7 @@ def list_operation(
f"Returns a list of {entity} of json"
f" schema: {schema_as_string}"
),
"$ref": (
"#/components/schemas/execute-connector_Response"
),
"$ref": "#/components/schemas/execute-connector_Response",
}
}
},
Expand Down Expand Up @@ -433,9 +425,7 @@ def get_operation(
f"Returns {entity} of json schema:"
f" {schema_as_string}"
),
"$ref": (
"#/components/schemas/execute-connector_Response"
),
"$ref": "#/components/schemas/execute-connector_Response",
}
}
},
Expand Down Expand Up @@ -472,9 +462,7 @@ def create_operation(
"content": {
"application/json": {
"schema": {
"$ref": (
"#/components/schemas/execute-connector_Response"
)
"$ref": "#/components/schemas/execute-connector_Response"
}
}
},
Expand Down Expand Up @@ -511,9 +499,7 @@ def update_operation(
"content": {
"application/json": {
"schema": {
"$ref": (
"#/components/schemas/execute-connector_Response"
)
"$ref": "#/components/schemas/execute-connector_Response"
}
}
},
Expand Down Expand Up @@ -550,9 +536,7 @@ def delete_operation(
"content": {
"application/json": {
"schema": {
"$ref": (
"#/components/schemas/execute-connector_Response"
)
"$ref": "#/components/schemas/execute-connector_Response"
}
}
},
Expand Down
Loading