Skip to content

State:会话的草稿板

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

什么是 session.state

从概念上讲,session.state 是一个保存键值对的集合(字典或 Map)。它用于存放智能体为让当前对话顺利进行需要记住或追踪的信息:

  • 个性化交互: 记住之前提到的用户偏好(例如,'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/Java)中的基础类型,如字符串、数字、布尔值,以及包含这些基础类型的简单列表或字典。(详见 API 文档)
    • ⚠️ 避免复杂对象: 不要直接在 state 中存储不可序列化的对象(自定义类实例、函数、连接等)。如有需要,请仅存储简单标识符,并在其他地方检索复杂对象。
  2. 可变性:它会改变 {: #mutability-it-changes}

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

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

Note

不同 SDK 语言的参数或方法名可能略有不同(如 Python 中为 session.state['current_intent'] = 'book_flight',Java 中为 session.state().put("current_intent", "book_flight"))。详情请参阅各语言的 API 文档。

用前缀组织 State:作用域很重要

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

  • 无前缀(会话状态): {: #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'] = {...}

智能体如何看到 State: 你的智能体代码通过单一的 session.state 集合(dict/Map)与合并后的状态交互。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 = await session_service.create_session(app_name=app_name, 
                                    user_id=user_id, 
                                    session_id=session_id)
print(f"初始 state: {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"Agent responded.") # 响应文本也在 event.content 中

# --- 检查更新后的 State ---
updated_session = await session_service.get_session(app_name=APP_NAME, user_id=USER_ID, session_id=session_id)
print(f"智能体运行后 state: {updated_session.state}")
# 预期输出如:{'last_greeting': 'Hello there! How can I help you today?'}
import com.google.adk.agents.LlmAgent;
import com.google.adk.agents.RunConfig;
import com.google.adk.events.Event;
import com.google.adk.runner.Runner;
import com.google.adk.sessions.InMemorySessionService;
import com.google.adk.sessions.Session;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import java.util.List;
import java.util.Optional;

public class GreetingAgentExample {

  public static void main(String[] args) {
    // Define agent with output_key
    LlmAgent greetingAgent =
        LlmAgent.builder()
            .name("Greeter")
            .model("gemini-2.0-flash")
            .instruction("Generate a short, friendly greeting.")
            .description("Greeting agent")
            .outputKey("last_greeting") // Save response to state['last_greeting']
            .build();

    // --- Setup Runner and Session ---
    String appName = "state_app";
    String userId = "user1";
    String sessionId = "session1";

    InMemorySessionService sessionService = new InMemorySessionService();
    Runner runner = new Runner(greetingAgent, appName, null, sessionService); // artifactService can be null if not used

    Session session =
        sessionService.createSession(appName, userId, null, sessionId).blockingGet();
    System.out.println("Initial state: " + session.state().entrySet());

    // --- Run the Agent ---
    // Runner handles calling appendEvent, which uses the output_key
    // to automatically create the stateDelta.
    Content userMessage = Content.builder().parts(List.of(Part.fromText("Hello"))).build();

    // RunConfig is needed for runner.runAsync in Java
    RunConfig runConfig = RunConfig.builder().build();

    for (Event event : runner.runAsync(userId, sessionId, userMessage, runConfig).blockingIterable()) {
      if (event.finalResponse()) {
        System.out.println("Agent responded."); // Response text is also in event.content
      }
    }

    // --- Check Updated State ---
    Session updatedSession =
        sessionService.getSession(appName, userId, sessionId, Optional.empty()).blockingGet();
    assert updatedSession != null;
    System.out.println("State after agent run: " + updatedSession.state().entrySet());
    // Expected output might include: {'last_greeting': 'Hello there! How can I help you today?'}
  }
}

在后台,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 = await 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"初始 state: {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 的 Event ---
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 或描述所采取的动作
)

# --- 追加事件(这会更新 state)---
await session_service.append_event(session, system_event)
print("已用显式 state delta 调用 `append_event`。");

# --- 检查更新后的 State ---
updated_session = await session_service.get_session(app_name=app_name,
                                            user_id=user_id, 
                                            session_id=session_id)
print(f"事件后 state: {updated_session.state}")
# 预期:{'user:login_count': 1, 'task_status': 'active', 'user:last_login_ts': <timestamp>}
# 注意:'temp:validation_needed' 不会出现在最终 state 中。
import com.google.adk.events.Event;
import com.google.adk.events.EventActions;
import com.google.adk.sessions.InMemorySessionService;
import com.google.adk.sessions.Session;
import java.time.Instant;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class ManualStateUpdateExample {

  public static void main(String[] args) {
    // --- Setup ---
    InMemorySessionService sessionService = new InMemorySessionService();
    String appName = "state_app_manual";
    String userId = "user2";
    String sessionId = "session2";

    ConcurrentMap<String, Object> initialState = new ConcurrentHashMap<>();
    initialState.put("user:login_count", 0);
    initialState.put("task_status", "idle");

    Session session =
        sessionService.createSession(appName, userId, initialState, sessionId).blockingGet();
    System.out.println("Initial state: " + session.state().entrySet());

    // --- Define State Changes ---
    long currentTimeMillis = Instant.now().toEpochMilli(); // Use milliseconds for Java Event

    ConcurrentMap<String, Object> stateChanges = new ConcurrentHashMap<>();
    stateChanges.put("task_status", "active"); // Update session state

    // Retrieve and increment login_count
    Object loginCountObj = session.state().get("user:login_count");
    int currentLoginCount = 0;
    if (loginCountObj instanceof Number) {
      currentLoginCount = ((Number) loginCountObj).intValue();
    }
    stateChanges.put("user:login_count", currentLoginCount + 1); // Update user state

    stateChanges.put("user:last_login_ts", currentTimeMillis); // Add user state (as long milliseconds)
    stateChanges.put("temp:validation_needed", true); // Add temporary state

    // --- Create Event with Actions ---
    EventActions actionsWithUpdate = EventActions.builder().stateDelta(stateChanges).build();

    // This event might represent an internal system action, not just an agent response
    Event systemEvent =
        Event.builder()
            .invocationId("inv_login_update")
            .author("system") // Or 'agent', 'tool' etc.
            .actions(actionsWithUpdate)
            .timestamp(currentTimeMillis)
            // content might be None or represent the action taken
            .build();

    // --- Append the Event (This updates the state) ---
    sessionService.appendEvent(session, systemEvent).blockingGet();
    System.out.println("`appendEvent` called with explicit state delta.");

    // --- Check Updated State ---
    Session updatedSession =
        sessionService.getSession(appName, userId, sessionId, Optional.empty()).blockingGet();
    assert updatedSession != null;
    System.out.println("State after event: " + updatedSession.state().entrySet());
    // Expected: {'user:login_count': 1, 'task_status': 'active', 'user:last_login_ts': <timestamp_millis>}
    // Note: 'temp:validation_needed' is NOT present because InMemorySessionService's appendEvent
    // applies delta to its internal user/app state maps IF keys have prefixes,
    // and to the session's own state map (which is then merged on getSession).
  }
}

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