adk-docs icon indicating copy to clipboard operation
adk-docs copied to clipboard

Doc(Custom-Agent): Directly modify the session.state in example storyflow_agent.py

Open carpediemtal opened this issue 3 months ago • 0 comments

Describe the bug

Directly modify the session.state in example adk-docs/examples/python/snippets/agents/custom-agent/storyflow_agent.py.

storyflow_agent.py:258

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

doc url

Versions

  • OS: All
  • ADK version: 1.6.1
  • Python version: 3.13.4

Additional context

None

carpediemtal avatar Aug 24 '25 13:08 carpediemtal