adk-docs
adk-docs copied to clipboard
Doc(Custom-Agent): Directly modify the session.state in example storyflow_agent.py
Describe the bug
Directly modify the session.state in example adk-docs/examples/python/snippets/agents/custom-agent/storyflow_agent.py.
async def call_agent_async(user_input_topic: str):
session_service, runner = await setup_session_and_runner()
current_session = await session_service.get_session(app_name=APP_NAME,
user_id=USER_ID,
session_id=SESSION_ID)
if not current_session:
logger.error("Session not found!")
return
current_session.state["topic"] = user_input_topic # Bug: Directly modify the `session.state`
logger.info(f"Updated session state topic to: {user_input_topic}")
content = types.Content(role='user', parts=[types.Part(text=f"Generate a story about: {user_input_topic}")])
events = runner.run_async(user_id=USER_ID, session_id=SESSION_ID, new_message=content)
# .....
In the example, current_session is obtained from the SessionService via session_service.get_session() or session_service.create_session(). It's just a copy of the origin session object. Nothing will happen if we modify the current_session directly. The story topic will not be changed in SessionService:
def _get_session_impl(
self,
*,
app_name: str,
user_id: str,
session_id: str,
config: Optional[GetSessionConfig] = None,
) -> Optional[Session]:
if app_name not in self.sessions:
return None
if user_id not in self.sessions[app_name]:
return None
if session_id not in self.sessions[app_name][user_id]:
return None
session = self.sessions[app_name][user_id].get(session_id)
copied_session = copy.deepcopy(session) # NOTE: return value is a deep copy
if config:
if config.num_recent_events:
copied_session.events = copied_session.events[
-config.num_recent_events :
]
if config.after_timestamp:
i = len(copied_session.events) - 1
while i >= 0:
if copied_session.events[i].timestamp < config.after_timestamp:
break
i -= 1
if i >= 0:
copied_session.events = copied_session.events[i + 1 :]
return self._merge_state(app_name, user_id, copied_session)
To Reproduce Run the python code below:
import asyncio
from google.adk.sessions import InMemorySessionService
APP_NAME = "story_app"
USER_ID = "12345"
SESSION_ID = "123344"
async def main():
session_service = InMemorySessionService()
session_created = await session_service.create_session(
app_name=APP_NAME,
user_id=USER_ID,
session_id=SESSION_ID,
state={"topic": "initial"},
)
deep_copied_session = await session_service.get_session(
app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID
)
session_created.state["topic"] = "modified"
deep_copied_session.state["topic"] = "modified"
print(
f"topic in session_created state: {session_created.state["topic"]}\n",
f"topic in deep_copied_session state: {deep_copied_session.state["topic"]}\n",
f"topic in session_service state: {session_service.sessions.get(APP_NAME).get(USER_ID).get(SESSION_ID).state["topic"]}\n",
)
# program output:
# topic in session_created state: modified
# topic in deep_copied_session state: modified
# topic in session_service state: initial
if __name__ == "__main__":
asyncio.run(main())
Expected behavior
Avoid directly modifying the session.state collection (dictionary/Map) on a Session object that was obtained directly from the SessionService (e.g., via session_service.get_session() or session_service.create_session())
Screenshots
Versions
- OS: All
- ADK version: 1.6.1
- Python version: 3.13.4
Additional context
None