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
的关键特性¶
-
结构:可序列化的键值对
- 数据以
key: value
的形式存储。 - 键: 始终是字符串(
str
)。使用清晰的名称(例如,'departure_city'
、'user:language_preference'
)。 - 值: 必须是 可序列化的。这意味着它们可以被
SessionService
轻松保存和加载。坚持使用特定语言(Python / Java)中的基本类型,如字符串、数字、布尔值,以及仅包含这些基本类型的简单列表或字典。(详细信息请参阅 API 文档)。 - ⚠️ 避免复杂对象: 不要在状态中直接存储不可序列化的对象(自定义类实例、函数、连接等)。如果需要,请存储简单标识符,并在其他地方检索复杂对象。
- 数据以
-
可变性:会发生变化
- 随着对话的演进,
state
的内容预计会发生变化。
- 随着对话的演进,
-
持久性:取决于
SessionService
- 状态是否能在应用程序重启后保持取决于你选择的服务:
InMemorySessionService
:不持久。 重启时状态会丢失。DatabaseSessionService
/VertexAiSessionService
:持久。 状态会可靠保存。
Note
不同 SDK 语言的参数或方法名可能略有不同(如 Python 中为 session.state['current_intent'] = 'book_flight'
,Java 中为 session.state().put("current_intent", "book_flight")
)。详情请参阅各语言的 API 文档。
用前缀组织 State:作用域很重要¶
状态键上的前缀定义了它们的作用域和持久性行为,特别是对于持久性服务:
-
无前缀(会话状态):
- 作用域: 特定于当前会话(
id
)。 - 持久性: 仅在
SessionService
是持久性的(Database
、VertexAI
)时才持久化。 - 使用案例: 跟踪当前任务中的进度(例如,
'current_booking_step'
)、此次交互的临时标志(例如,'needs_clarification'
)。 - 示例:
session.state['current_intent'] = 'book_flight'
- 作用域: 特定于当前会话(
-
user:
前缀(用户状态):- 作用域: 绑定到
user_id
,在该用户的所有会话中共享(在同一个app_name
内)。 - 持久性: 在
Database
或VertexAI
中持久化。(由InMemory
存储但在重启时丢失)。 - 使用案例: 用户偏好(例如,
'user:theme'
)、个人资料详情(例如,'user:name'
)。 - 示例:
session.state['user:preferred_language'] = 'fr'
- 作用域: 绑定到
-
app:
前缀(应用状态):- 作用域: 绑定到
app_name
,在该应用程序的所有用户和会话中共享。 - 持久性: 在
Database
或VertexAI
中持久化。(由InMemory
存储但在重启时丢失)。 - 使用案例: 全局设置(例如,
'app:api_endpoint'
)、共享模板。 - 示例:
session.state['app:global_discount_code'] = 'SAVE10'
- 作用域: 绑定到
-
temp:
前缀(临时会话状态):- 作用域: 特定于当前会话处理轮次。
- 持久性: 从不持久化。 保证被丢弃,即使使用持久性服务。
- 使用案例: 仅在立即需要的中间结果、你明确不想存储的数据。
- 示例:
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="生成一个简短、友好的问候语。",
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="你好")])
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 中
# --- 检查更新后的 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': '你好!今天我能为你做什么?'}
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
创建必要的 EventActions
和 state_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).
}
}
3. 通过 CallbackContext
或 ToolContext
(推荐用于回调和工具) {: #via-callbackcontext-or-toolcontext-recommended-for-callbacks-and-tools}
在智能体回调(例如,on_before_agent_call
、on_after_agent_call
)或工具函数中修改状态,最好使用提供给你函数的 CallbackContext
或 ToolContext
的 state
属性。
callback_context.state['my_key'] = my_value
tool_context.state['my_key'] = my_value
这些上下文对象专门设计用于在各自的执行范围内管理状态更改。当你修改 context.state
时,ADK 框架确保这些更改会被自动捕获并正确路由到回调或工具生成的事件的 EventActions.state_delta
中。然后当事件被追加时,这个增量由 SessionService
处理,确保适当的持久性和跟踪。
这种方法在回调和工具内的大多数常见状态更新场景中抽象掉了手动创建 EventActions
和 state_delta
的过程,使你的代码更清洁且不易出错。
有关上下文对象的更全面详细信息,请参阅上下文文档。
# 在智能体回调或工具函数中
from google.adk.agents import CallbackContext # 或 ToolContext
def my_callback_or_tool_function(context: CallbackContext, # 或 ToolContext
# ... 其他参数 ...
):
# 更新现有状态
count = context.state.get("user_action_count", 0)
context.state["user_action_count"] = count + 1
# 添加新状态
context.state["temp:last_operation_status"] = "success"
# 状态更改自动成为事件的 state_delta 的一部分
# ... 回调/工具逻辑的其余部分 ...
// 在智能体回调或工具方法中
import com.google.adk.agents.CallbackContext; // 或 ToolContext
// ... 其他导入 ...
public class MyAgentCallbacks {
public void onAfterAgent(CallbackContext callbackContext) {
// 更新现有状态
Integer count = (Integer) callbackContext.state().getOrDefault("user_action_count", 0);
callbackContext.state().put("user_action_count", count + 1);
// 添加新状态
callbackContext.state().put("temp:last_operation_status", "success");
// 状态更改自动成为事件的 state_delta 的一部分
// ... 回调逻辑的其余部分 ...
}
}
append_event
的作用:
- 将
Event
添加到session.events
。 - 从事件的
actions
中读取state_delta
。 - 将这些更改应用到由
SessionService
管理的状态,根据服务类型正确处理前缀和持久性。 - 更新会话的
last_update_time
。 - 确保并发更新的线程安全。
⚠️ 关于直接状态修改的警告¶
避免在智能体调用的托管生命周期之外(即不通过 CallbackContext
或 ToolContext
)直接修改从 SessionService
直接获取的 Session
对象上的 session.state
集合(字典/Map)(例如,通过 session_service.get_session()
或 session_service.create_session()
)。例如,像 retrieved_session = await session_service.get_session(...); retrieved_session.state['key'] = value
这样的代码是有问题的。
在回调或工具内部使用 CallbackContext.state
或 ToolContext.state
进行状态修改是确保更改被跟踪的正确方法,因为这些上下文对象处理与事件系统的必要集成。
为什么强烈不建议直接修改(在上下文之外):
- 绕过事件历史: 更改不会被记录为
Event
,失去可审计性。 - 破坏持久性: 以这种方式进行的更改很可能不会被保存到
DatabaseSessionService
或VertexAiSessionService
。它们依赖append_event
触发保存。 - 不是线程安全的: 可能导致竞态条件和丢失更新。
- 忽略时间戳/逻辑: 不更新
last_update_time
或触发相关事件逻辑。
建议: 坚持通过 output_key
、EventActions.state_delta
(手动创建事件时)或在各自范围内修改 CallbackContext
或 ToolContext
对象的 state
属性来更新状态。这些方法确保可靠、可跟踪且持久的状态管理。仅将对 session.state
(来自 SessionService
检索的会话)的直接访问用于读取状态。
状态设计最佳实践概述¶
- 最小主义: 仅存储必要的、动态的数据。
- 序列化: 使用基本的、可序列化的类型。
- 描述性键和前缀: 使用清晰的名称和适当的前缀(
user:
、app:
、temp:
或无前缀)。 - 浅层结构: 尽可能避免深层嵌套。
- 标准更新流程: 依赖
append_event
。