Skip to content

State:会话的草稿板

在每个 Session(我们的对话线程)中,state 属性就像智能体专用于该特定交互的草稿板。虽然 session.events 保存完整历史,session.state 是智能体存储和更新对话期间所需动态细节的地方。

什么是 session.state

从概念上讲,session.state 是保存键值对的字典。它设计用于智能体需要回忆或跟踪以使当前对话有效的信息:

  • 个性化交互: 记住之前提到的用户偏好(例如,'user_preference_theme': 'dark')。
  • 跟踪任务进度: 记录多轮流程中的步骤(例如,'booking_step': 'confirm_payment')。
  • 累积信息: 构建列表或摘要(例如,'shopping_cart_items': ['book', 'pen'])。
  • 做出明智决策: 存储影响下一次响应的标志或值(例如,'user_is_authenticated': True)。

State 的关键特性

  1. 结构:可序列化的键值对 {: #structure-serializable-key-value-pairs}

    • 数据存储为 key: value
    • 键: 始终为字符串(str)。使用清晰的名称(例如,'departure_city''user:language_preference')。
    • 值: 必须可序列化。这意味着它们可以被 SessionService 轻松保存和加载。坚持使用基本 Python 类型,如字符串、数字、布尔值和仅包含这些基本类型的简单列表或字典。(有关精确详细信息,请参阅 API 文档)。
    • ⚠️ 避免复杂对象: 不要直接在状态中存储不可序列化的 Python 对象(自定义类实例、函数、连接等)。如果需要,存储简单标识符,并在其他地方检索复杂对象。
  2. 可变性:它会改变 {: #mutability-it-changes}

    • 随着对话的演进,state 的内容预计会发生变化。
  3. 持久性:取决于 SessionService {: #persistence-depends-on-sessionservice}

    • 状态是否在应用程序重启后幸存取决于你选择的服务:
    • InMemorySessionService不持久。重启时状态丢失。
    • DatabaseSessionService / VertexAiSessionService持久。状态可靠保存。

使用前缀组织状态:作用域很重要

状态键上的前缀定义了它们的作用域和持久性行为,特别是对于持久性服务:

  • 无前缀(会话状态): {: #no-prefix-session-state}

    • 作用域: 特定于当前会话(id)。
    • 持久性: 仅当 SessionService 是持久的(DatabaseVertexAI)时才持久化。
    • 用例: 跟踪当前任务的进度(例如,'current_booking_step'),此交互的临时标志(例如,'needs_clarification')。
    • 示例: session.state['current_intent'] = 'book_flight'
  • user: 前缀(用户状态): {: #user-prefix-user-state}

    • 作用域: 绑定到 user_id,在该用户的所有会话中共享(在同一 app_name 内)。
    • 持久性:DatabaseVertexAI 持久。(由 InMemory 存储但重启时丢失)。
    • 用例: 用户偏好(例如,'user:theme'),个人资料详细信息(例如,'user:name')。
    • 示例: session.state['user:preferred_language'] = 'fr'
  • app: 前缀(应用状态): {: #app-prefix-app-state}

    • 作用域: 绑定到 app_name,在该应用程序的所有用户和会话之间共享。
    • 持久性:DatabaseVertexAI 持久。(由 InMemory 存储但重启时丢失)。
    • 用例: 全局设置(例如,'app:api_endpoint'),共享模板。
    • 示例: session.state['app:global_discount_code'] = 'SAVE10'
  • temp: 前缀(临时会话状态): {: #temp-prefix-temporary-session-state}

    • 作用域: 特定于当前会话处理回合。
    • 持久性: 永不持久。即使与持久服务一起使用也保证被丢弃。
    • 用例: 仅立即需要的中间结果,你明确不想存储的数据。
    • 示例: session.state['temp:raw_api_response'] = {...}

智能体如何看待它: 你的智能体代码通过单一的 session.state 字典与组合状态交互。SessionService 根据前缀处理从正确的底层存储中获取/合并状态。

状态始终应该作为使用 session_service.append_event() 向会话历史添加 Event 的一部分进行更新。这确保变更被跟踪,持久性正常工作,并且更新是线程安全的。

1. 简单方法:output_key(用于智能体文本响应) {: #the-easy-way-output_key-for-agent-text-responses}

这是将智能体的最终文本响应直接保存到状态的最简单方法。在定义 LlmAgent 时,指定 output_key

from google.adk.agents import LlmAgent
from google.adk.sessions import InMemorySessionService, Session
from google.adk.runners import Runner
from google.genai.types import Content, Part

# 定义带有 output_key 的智能体
greeting_agent = LlmAgent(
    name="Greeter",
    model="gemini-2.0-flash", # 使用有效模型
    instruction="Generate a short, friendly greeting.",
    output_key="last_greeting" # 将响应保存到 state['last_greeting']
)

# --- 设置 Runner 和 Session ---
app_name, user_id, session_id = "state_app", "user1", "session1"
session_service = InMemorySessionService()
runner = Runner(
    agent=greeting_agent,
    app_name=app_name,
    session_service=session_service
)
session = session_service.create_session(app_name=app_name, 
                                        user_id=user_id, 
                                        session_id=session_id)
print(f"初始状态:{session.state}")

# --- 运行智能体 ---
# Runner 处理调用 append_event,使用 output_key
# 自动创建 state_delta。
user_message = Content(parts=[Part(text="Hello")])
for event in runner.run(user_id=user_id, 
                        session_id=session_id, 
                        new_message=user_message):
    if event.is_final_response():
      print(f"智能体已响应。") # 响应文本也在 event.content 中

# --- 检查更新后的状态 ---
updated_session = session_service.get_session(app_name, user_id, session_id)
print(f"智能体运行后的状态:{updated_session.state}")
# 预期输出可能包括:{'last_greeting': '你好!今天我能帮你什么忙?'}

在后台,Runner 使用 output_key 创建必要的 EventActionsstate_delta 并调用 append_event

2. 标准方法:EventActions.state_delta(用于复杂更新) {: #the-standard-way-eventactionsstate_delta-for-complex-updates}

对于更复杂的场景(更新多个键、非字符串值、特定作用域如 user:app:,或者不直接与智能体最终文本相关的更新),你需要手动在 EventActions 中构建 state_delta

from google.adk.sessions import InMemorySessionService, Session
from google.adk.events import Event, EventActions
from google.genai.types import Part, Content
import time

# --- 设置 ---
session_service = InMemorySessionService()
app_name, user_id, session_id = "state_app_manual", "user2", "session2"
session = session_service.create_session(
    app_name=app_name,
    user_id=user_id,
    session_id=session_id,
    state={"user:login_count": 0, "task_status": "idle"}
)
print(f"初始状态:{session.state}")

# --- 定义状态更改 ---
current_time = time.time()
state_changes = {
    "task_status": "active",              # 更新会话状态
    "user:login_count": session.state.get("user:login_count", 0) + 1, # 更新用户状态
    "user:last_login_ts": current_time,   # 添加用户状态
    "temp:validation_needed": True        # 添加临时状态(将被丢弃)
}

# --- 创建带有 Actions 的事件 ---
actions_with_update = EventActions(state_delta=state_changes)
# 此事件可能代表内部系统操作,而不仅仅是智能体响应
system_event = Event(
    invocation_id="inv_login_update",
    author="system", # 或 'agent', 'tool' 等
    actions=actions_with_update,
    timestamp=current_time
    # content 可能为 None 或表示所采取的操作
)

# --- 追加事件(这会更新状态) ---
session_service.append_event(session, system_event)
print("`append_event` 使用显式状态增量调用。")

# --- 检查更新后的状态 ---
updated_session = session_service.get_session(app_name=app_name,
                                            user_id=user_id, 
                                            session_id=session_id)
print(f"事件后的状态:{updated_session.state}")
# 预期:{'user:login_count': 1, 'task_status': 'active', 'user:last_login_ts': <timestamp>}
# 注意:'temp:validation_needed' 不存在。

append_event 的作用:

  • Event 添加到 session.events
  • 从事件的 actions 中读取 state_delta
  • 将这些更改应用到由 SessionService 管理的状态,根据服务类型正确处理前缀和持久性。
  • 更新会话的 last_update_time
  • 确保并发更新的线程安全。

⚠️ 关于直接状态修改的警告

避免在检索会话后直接修改 session.state 字典(例如,retrieved_session.state['key'] = value)。

为什么强烈不建议这样做:

  1. 绕过事件历史: 更改不会被记录为 Event,失去可审计性。
  2. 破坏持久性: 以这种方式进行的更改很可能不会被保存DatabaseSessionServiceVertexAiSessionService。它们依赖 append_event 触发保存。
  3. 不是线程安全的: 可能导致竞态条件和丢失更新。
  4. 忽略时间戳/逻辑: 不更新 last_update_time 或触发相关事件逻辑。

建议: 坚持通过 output_keyEventActions.state_deltaappend_event 流程中更新状态,以获得可靠、可跟踪和持久的状态管理。仅将直接访问用于读取状态。

状态设计最佳实践概述

  • 最小主义: 仅存储必要的、动态的数据。
  • 序列化: 使用基本的、可序列化的类型。
  • 描述性键和前缀: 使用清晰的名称和适当的前缀(user:app:temp: 或无前缀)。
  • 浅层结构: 尽可能避免深层嵌套。
  • 标准更新流程: 依赖 append_event