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/Go/Java/TypeScript)中的基本类型,如字符串、数字、布尔值以及仅包含这些基本类型的简单列表或字典。(有关详细信息,请参阅 API 文档)。 - ⚠️ 避免复杂对象: 不要直接在状态中存储不可序列化的对象(自定义类实例、函数、连接等)。如有需要,存储简单标识符,并在其他地方检索复杂对象。
- 数据以
-
可变性:它会变化
state的内容随着对话的发展而变化。
-
持久性:取决于
SessionService-
状态是否在应用程序重启后仍然存在取决于你选择的服务:
-
InMemorySessionService:不持久。 重启后状态丢失。 DatabaseSessionService/VertexAiSessionService:持久。 状态可靠地保存。
-
Note
原语的具体参数或方法名称可能因 SDK 语言而略有不同(例如,Python 中的 session.state['current_intent'] = 'book_flight',Go 中的 context.State().Set("current_intent", "book_flight"),Java 中的 session.state().put("current_intent", "book_flight),或 TypeScript 中的 context.state.set("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'] = {...}
子智能体和调用上下文
当父智能体调用子智能体(例如,使用 SequentialAgent 或 ParallelAgent)时,它将其 InvocationContext 传递给子智能体。这意味着整个智能体调用链共享相同的调用 ID,因此共享相同的 temp: 状态。
智能体如何看待它: 你的智能体代码通过单个 session.state 集合(字典/Map)与组合状态交互。SessionService 负责根据前缀从正确的底层存储中获取/合并状态。
在智能体指令中访问会话状态¶
使用 LlmAgent 实例时,你可以使用简单的模板语法直接将会话状态值注入到智能体的指令字符串中。这允许你创建动态和上下文感知的指令,而不完全依赖自然语言指令。
使用 {key} 模板¶
要从会话状态注入一个值,将所需状态变量的键放在大括号内:{key}。框架会在将指令传递给 LLM 之前自动用来自 session.state 的相应值替换此占位符。
示例:
from google.adk.agents import LlmAgent
story_generator = LlmAgent(
name="StoryGenerator",
model="gemini-2.0-flash",
instruction="""Write a short story about a cat, focusing on the theme: {topic}."""
)
# 假设 session.state['topic'] 设置为 "friendship",LLM
# 将收到以下指令:
# "Write a short story about a cat, focusing on the theme: friendship."
import { LlmAgent } from "@google/adk";
const storyGenerator = new LlmAgent({
name: "StoryGenerator",
model: "gemini-2.5-flash",
instruction: "Write a short story about a cat, focusing on the theme: {topic}."
});
// 假设 session.state['topic'] 设置为 "friendship",LLM
// 将收到以下指令:
// "Write a short story about a cat, focusing on the theme: friendship."
func main() {
ctx := context.Background()
sessionService := session.InMemoryService()
// 1. Initialize a session with a 'topic' in its state.
_, err := sessionService.Create(ctx, &session.CreateRequest{
AppName: appName,
UserID: userID,
SessionID: sessionID,
State: map[string]any{
"topic": "friendship",
},
})
if err != nil {
log.Fatalf("Failed to create session: %v", err)
}
// 2. Create an agent with an instruction that uses a {topic} placeholder.
// The ADK will automatically inject the value of "topic" from the
// session state into the instruction before calling the LLM.
model, err := gemini.NewModel(ctx, modelID, nil)
if err != nil {
log.Fatalf("Failed to create Gemini model: %v", err)
}
storyGenerator, err := llmagent.New(llmagent.Config{
Name: "StoryGenerator",
Model: model,
Instruction: "Write a short story about a cat, focusing on the theme: {topic}.",
})
if err != nil {
log.Fatalf("Failed to create agent: %v", err)
}
r, err := runner.New(runner.Config{
AppName: appName,
Agent: agent.Agent(storyGenerator),
SessionService: sessionService,
})
if err != nil {
log.Fatalf("Failed to create runner: %v", err)
}
重要注意事项¶
- 键的存在性:确保你在指令字符串中引用的键在 session.state 中存在。如果键缺失,智能体将抛出错误。要使用可能或可能不存在的键,你可以在键后包含一个问号 (?)(例如 {topic?})。
- 数据类型:与键关联的值应该是字符串或可以轻松转换为字符串的类型。
- 转义:如果你需要在指令中使用字面量大括号(例如,用于 JSON 格式),你需要转义它们。
使用 InstructionProvider 绕过状态注入¶
在某些情况下,你可能希望在指令中字面意义地使用 {{ 和 }},而不会触发状态注入机制。例如,你可能正在为一个帮助处理使用相同语法的模板语言的智能体编写指令。
要实现这一点,你可以向 instruction 参数提供一个函数而不是字符串。这个函数被称为 InstructionProvider。当你使用 InstructionProvider 时,ADK 将不会尝试注入状态,你的指令字符串将原样传递给模型。
InstructionProvider 函数接收一个 ReadonlyContext 对象,如果你需要动态构建指令,可以使用它来访问会话状态或其他上下文信息。
from google.adk.agents import LlmAgent
from google.adk.agents.readonly_context import ReadonlyContext
# 这是一个 InstructionProvider
def my_instruction_provider(context: ReadonlyContext) -> str:
# 你可以选择性地使用上下文来构建指令
# 对于此示例,我们将返回一个带有字面量大括号的静态字符串。
return "This is an instruction with {{literal_braces}} that will not be replaced."
agent = LlmAgent(
model="gemini-2.0-flash",
name="template_helper_agent",
instruction=my_instruction_provider
)
import { LlmAgent, ReadonlyContext } from "@google/adk";
// 这是一个 InstructionProvider
function myInstructionProvider(context: ReadonlyContext): string {
// 你可以选择性地使用上下文来构建指令
// 对于此示例,我们将返回一个带有字面量大括号的静态字符串。
return "This is an instruction with {{literal_braces}} that will not be replaced.";
}
const agent = new LlmAgent({
model: "gemini-2.5-flash",
name: "template_helper_agent",
instruction: myInstructionProvider
});
// 1. This InstructionProvider returns a static string.
// Because it's a provider function, the ADK will not attempt to inject
// state, and the instruction will be passed to the model as-is,
// preserving the literal braces.
func staticInstructionProvider(ctx agent.ReadonlyContext) (string, error) {
return "This is an instruction with {{literal_braces}} that will not be replaced.", nil
}
如果你想同时使用 InstructionProvider 并 将状态注入到你的指令中,你可以使用 inject_session_state 实用函数。
from google.adk.agents import LlmAgent
from google.adk.agents.readonly_context import ReadonlyContext
from google.adk.utils import instructions_utils
async def my_dynamic_instruction_provider(context: ReadonlyContext) -> str:
template = "This is a {adjective} instruction with {{literal_braces}}."
# 这将注入 'adjective' 状态变量,但保留字面量大括号。
return await instructions_utils.inject_session_state(template, context)
agent = LlmAgent(
model="gemini-2.0-flash",
name="dynamic_template_helper_agent",
instruction=my_dynamic_instruction_provider
)
// 2. This InstructionProvider demonstrates how to manually inject state
// while also preserving literal braces. It uses the instructionutil helper.
func dynamicInstructionProvider(ctx agent.ReadonlyContext) (string, error) {
template := "This is a {adjective} instruction with {{literal_braces}}."
// This will inject the 'adjective' state variable but leave the literal braces.
return instructionutil.InjectSessionState(ctx, template)
}
直接注入的好处
- 清晰性:明确指出指令的哪些部分是动态的且基于会话状态。
- 可靠性:避免依赖 LLM 正确解释自然语言指令来访问状态。
- 可维护性:简化指令字符串,在更新状态变量名称时降低错误风险。
与其他状态访问方法的关系
这种直接注入方法专用于 LlmAgent 指令。有关其他状态访问方法的更多信息,请参阅以下章节。
状态如何更新:推荐方法¶
修改状态的正确方法
当你需要更改会话状态时,正确且最安全的方法是直接修改提供给你的函数的 Context 上的 state 对象(例如,callback_context.state['my_key'] = 'new_value')。这被认为是"直接状态操作"的正确方式,因为框架会自动跟踪这些更改。
这与直接修改从 SessionService 检索的 Session 对象上的 state 有着关键区别(例如,my_session.state['my_key'] = 'new_value')。你应该避免这样做,因为它绕过了 ADK 的事件跟踪并可能导致数据丢失。本页末尾的"警告"部分提供了有关此重要区别的更多详细信息。
状态应该始终作为使用 session_service.append_event() 向会话历史添加 Event 的一部分进行更新。这确保了更改被跟踪,持久性正常工作,更新是线程安全的。
1. 简单方法:output_key(用于智能体文本响应)
这是将会话的最终文本响应直接保存到状态的最简单方法。定义 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
# Define agent with output_key
greeting_agent = LlmAgent(
name="Greeter",
model="gemini-2.0-flash", # Use a valid model
instruction="Generate a short, friendly greeting.",
output_key="last_greeting" # Save response to state['last_greeting']
)
# --- Setup Runner and 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"Initial state: {session.state}")
# --- Run the Agent ---
# Runner handles calling append_event, which uses the output_key
# to automatically create the 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 中
# --- Check Updated State ---
updated_session = await session_service.get_session(app_name=APP_NAME, user_id=USER_ID, session_id=session_id)
print(f"State after agent run: {updated_session.state}")
# 预期输出可能包括:{'last_greeting': 'Hello there! How can I help you today?'}
import { LlmAgent, Runner, InMemorySessionService, isFinalResponse } from "@google/adk";
import { Content } from "@google/genai";
// Define agent with outputKey
const greetingAgent = new LlmAgent({
name: "Greeter",
model: "gemini-2.5-flash",
instruction: "Generate a short, friendly greeting.",
outputKey: "last_greeting" // Save response to state['last_greeting']
});
// --- Setup Runner and Session ---
const appName = "state_app";
const userId = "user1";
const sessionId = "session1";
const sessionService = new InMemorySessionService();
const runner = new Runner({
agent: greetingAgent,
appName: appName,
sessionService: sessionService
});
const session = await sessionService.createSession({
appName,
userId,
sessionId
});
console.log(`Initial state: ${JSON.stringify(session.state)}`);
// --- Run the Agent ---
// Runner handles calling appendEvent, which uses the outputKey
// to automatically create the stateDelta.
const userMessage: Content = { parts: [{ text: "Hello" }] };
for await (const event of runner.runAsync({
userId,
sessionId,
newMessage: userMessage
})) {
if (isFinalResponse(event)) {
console.log("Agent responded."); // 响应文本也在 event.content 中
}
}
// --- Check Updated State ---
const updatedSession = await sessionService.getSession({ appName, userId, sessionId });
console.log(`State after agent run: ${JSON.stringify(updatedSession?.state)}`);
// 预期输出可能包括:{"last_greeting":"Hello there! How can I help you today?"}
// 1. GreetingAgent demonstrates using `OutputKey` to save an agent's
// final text response directly into the session state.
func greetingAgentExample(sessionService session.Service) {
fmt.Println("--- Running GreetingAgent (output_key) Example ---")
ctx := context.Background()
modelGreeting, err := gemini.NewModel(ctx, modelID, nil)
if err != nil {
log.Fatalf("Failed to create Gemini model for greeting agent: %v", err)
}
greetingAgent, err := llmagent.New(llmagent.Config{
Name: "Greeter",
Model: modelGreeting,
Instruction: "Generate a short, friendly greeting.",
OutputKey: "last_greeting",
})
if err != nil {
log.Fatalf("Failed to create greeting agent: %v", err)
}
r, err := runner.New(runner.Config{
AppName: appName,
Agent: agent.Agent(greetingAgent),
SessionService: sessionService,
})
if err != nil {
log.Fatalf("Failed to create runner: %v", err)
}
// Run the agent
userMessage := genai.NewContentFromText("Hello", "user")
for event, err := range r.Run(ctx, userID, sessionID, userMessage, agent.RunConfig{}) {
if err != nil {
log.Printf("Agent Error: %v", err)
continue
}
if isFinalResponse(event) {
if event.LLMResponse.Content != nil {
fmt.Printf("Agent responded with: %q\n", textParts(event.LLMResponse.Content))
} else {
fmt.Println("Agent responded.")
}
}
}
// Check the updated state
resp, err := sessionService.Get(ctx, &session.GetRequest{AppName: appName, UserID: userID, SessionID: sessionID})
if err != nil {
log.Fatalf("Failed to get session: %v", err)
}
lastGreeting, _ := resp.Session.State().Get("last_greeting")
fmt.Printf("State after agent run: last_greeting = %q\n\n", lastGreeting)
}
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 创建必要的带有 state_delta 的 EventActions 并调用 append_event。
2. 标准方法:EventActions.state_delta(用于复杂更新)
对于更复杂的场景(更新多个键、非字符串值、特定作用域如 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
# --- Setup ---
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"Initial state: {session.state}")
# --- Define State Changes ---
current_time = time.time()
state_changes = {
"task_status": "active", # Update session state
"user:login_count": session.state.get("user:login_count", 0) + 1, # Update user state
"user:last_login_ts": current_time, # Add user state
"temp:validation_needed": True # 添加临时状态(将被丢弃)
}
# --- Create Event with 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 或表示采取的操作
)
# --- 追加事件(这会更新状态) ---
await session_service.append_event(session, system_event)
print("`append_event` called with explicit state delta.")
# --- Check Updated State ---
updated_session = await session_service.get_session(app_name=app_name,
user_id=user_id,
session_id=session_id)
print(f"State after event: {updated_session.state}")
# Expected: {'user:login_count': 1, 'task_status': 'active', 'user:last_login_ts': <timestamp>}
# Note: 'temp:validation_needed' is NOT present.
import { InMemorySessionService, createEvent, createEventActions } from "@google/adk";
// --- Setup ---
const sessionService = new InMemorySessionService();
const appName = "state_app_manual";
const userId = "user2";
const sessionId = "session2";
const session = await sessionService.createSession({
appName,
userId,
sessionId,
state: { "user:login_count": 0, "task_status": "idle" }
});
console.log(`Initial state: ${JSON.stringify(session.state)}`);
// --- Define State Changes ---
const currentTime = Date.now();
const stateChanges = {
"task_status": "active", // Update session state
"user:login_count": (session.state["user:login_count"] as number || 0) + 1, // Update user state
"user:last_login_ts": currentTime, // Add user state
"temp:validation_needed": true // Add temporary state (will be discarded)
};
// --- Create Event with Actions ---
const actionsWithUpdate = createEventActions({
stateDelta: stateChanges,
});
// 此事件可能代表内部系统操作,不仅仅是智能体响应
const systemEvent = createEvent({
invocationId: "inv_login_update",
author: "system", // 或 'agent', 'tool' 等。
actions: actionsWithUpdate,
timestamp: currentTime
// content 可能为 null 或表示采取的操作
});
// --- 追加事件(这会更新状态) ---
await sessionService.appendEvent({ session, event: systemEvent });
console.log("`appendEvent` called with explicit state delta.");
// --- Check Updated State ---
const updatedSession = await sessionService.getSession({
appName,
userId,
sessionId
});
console.log(`State after event: ${JSON.stringify(updatedSession?.state)}`);
// Expected: {"user:login_count":1,"task_status":"active","user:last_login_ts":<timestamp>}
// Note: 'temp:validation_needed' is NOT present.
// 2. manualStateUpdateExample demonstrates creating an event with explicit
// state changes (a "state_delta") to update multiple keys, including
// those with user- and temp- prefixes.
func manualStateUpdateExample(sessionService session.Service) {
fmt.Println("--- Running Manual State Update (EventActions) Example ---")
ctx := context.Background()
s, err := sessionService.Get(ctx, &session.GetRequest{AppName: appName, UserID: userID, SessionID: sessionID})
if err != nil {
log.Fatalf("Failed to get session: %v", err)
}
retrievedSession := s.Session
// Define state changes
loginCount, _ := retrievedSession.State().Get("user:login_count")
newLoginCount := 1
if lc, ok := loginCount.(int); ok {
newLoginCount = lc + 1
}
stateChanges := map[string]any{
"task_status": "active",
"user:login_count": newLoginCount,
"user:last_login_ts": time.Now().Unix(),
"temp:validation_needed": true,
}
// Create an event with the state changes
systemEvent := session.NewEvent("inv_login_update")
systemEvent.Author = "system"
systemEvent.Actions.StateDelta = stateChanges
// Append the event to update the state
if err := sessionService.AppendEvent(ctx, retrievedSession, systemEvent); err != nil {
log.Fatalf("Failed to append event: %v", err)
}
fmt.Println("`append_event` called with explicit state delta.")
// Check the updated state
updatedResp, err := sessionService.Get(ctx, &session.GetRequest{AppName: appName, UserID: userID, SessionID: sessionID})
if err != nil {
log.Fatalf("Failed to get session: %v", err)
}
taskStatus, _ := updatedResp.Session.State().Get("task_status")
loginCount, _ = updatedResp.Session.State().Get("user:login_count")
lastLogin, _ := updatedResp.Session.State().Get("user:last_login_ts")
temp, err := updatedResp.Session.State().Get("temp:validation_needed") // This should fail or be nil
fmt.Printf("State after event: task_status=%q, user:login_count=%v, user:last_login_ts=%v\n", taskStatus, loginCount, lastLogin)
if err != nil {
fmt.Printf("As expected, temp state was not persisted: %v\n\n", err)
} else {
fmt.Printf("Unexpected temp state value: %v\n\n", temp)
}
}
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. 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_valuetool_context.state['my_key'] = my_value
这些上下文对象专门设计用于管理各自执行作用域内的状态更改。当你修改 context.state 时,ADK 框架确保这些更改被自动捕获并正确路由到回调或工具生成的事件的 EventActions.state_delta 中。当事件被追加时,此增量由 SessionService 处理,确保适当的持久性和跟踪。
此方法在大多数常见状态更新场景中抽象掉了 EventActions 和 state_delta 的手动创建,使你的代码更简洁且不易出错。
有关上下文对象的更全面详细信息,请参阅上下文文档。
# In an agent callback or tool function
from google.adk.agents import CallbackContext # or ToolContext
def my_callback_or_tool_function(context: CallbackContext, # Or ToolContext
# ... other parameters ...
):
# Update existing state
count = context.state.get("user_action_count", 0)
context.state["user_action_count"] = count + 1
# Add new state
context.state["temp:last_operation_status"] = "success"
# State changes are automatically part of the event's state_delta
# ... rest of callback/tool logic ...
// In an agent callback or tool function
import { CallbackContext } from "@google/adk"; // or ToolContext
function myCallbackOrToolFunction(
context: CallbackContext, // Or ToolContext
// ... other parameters ...
) {
// Update existing state
const count = context.state.get("user_action_count", 0);
context.state.set("user_action_count", count + 1);
// Add new state
context.state.set("temp:last_operation_status", "success");
// State changes are automatically part of the event's stateDelta
// ... rest of callback/tool logic ...
}
// 3. contextStateUpdateExample demonstrates the recommended way to modify state
// from within a tool function using the provided `tool.Context`.
func contextStateUpdateExample(sessionService session.Service) {
fmt.Println("--- Running Context State Update (ToolContext) Example ---")
ctx := context.Background()
// Define the tool that modifies state
updateActionCountTool, err := functiontool.New(
functiontool.Config{Name: "update_action_count", Description: "Updates the user action count in the state."},
func(tctx tool.Context, args struct{}) (struct{}, error) {
actx, ok := tctx.(agent.CallbackContext)
if !ok {
log.Fatalf("tool.Context is not of type agent.CallbackContext")
}
s, err := actx.State().Get("user_action_count")
if err != nil {
log.Printf("could not get user_action_count: %v", err)
}
newCount := 1
if c, ok := s.(int); ok {
newCount = c + 1
}
if err := actx.State().Set("user_action_count", newCount); err != nil {
log.Printf("could not set user_action_count: %v", err)
}
if err := actx.State().Set("temp:last_operation_status", "success from tool"); err != nil {
log.Printf("could not set temp:last_operation_status: %v", err)
}
fmt.Println("Tool: Updated state via agent.CallbackContext.")
return struct{}{}, nil
},
)
if err != nil {
log.Fatalf("Failed to create tool: %v", err)
}
// Define an agent that uses the tool
modelTool, err := gemini.NewModel(ctx, modelID, nil)
if err != nil {
log.Fatalf("Failed to create Gemini model for tool agent: %v", err)
}
toolAgent, err := llmagent.New(llmagent.Config{
Name: "ToolAgent",
Model: modelTool,
Instruction: "Use the update_action_count tool.",
Tools: []tool.Tool{updateActionCountTool},
})
if err != nil {
log.Fatalf("Failed to create tool agent: %v", err)
}
r, err := runner.New(runner.Config{
AppName: appName,
Agent: agent.Agent(toolAgent),
SessionService: sessionService,
})
if err != nil {
log.Fatalf("Failed to create runner: %v", err)
}
// Run the agent to trigger the tool
userMessage := genai.NewContentFromText("Please update the action count.", "user")
for _, err := range r.Run(ctx, userID, sessionID, userMessage, agent.RunConfig{}) {
if err != nil {
log.Printf("Agent Error: %v", err)
}
}
// Check the updated state
resp, err := sessionService.Get(ctx, &session.GetRequest{AppName: appName, UserID: userID, SessionID: sessionID})
if err != nil {
log.Fatalf("Failed to get session: %v", err)
}
actionCount, _ := resp.Session.State().Get("user_action_count")
fmt.Printf("State after tool run: user_action_count = %v\n", actionCount)
}
// In an agent callback or tool method
import com.google.adk.agents.CallbackContext; // or ToolContext
// ... other imports ...
public class MyAgentCallbacks {
public void onAfterAgent(CallbackContext callbackContext) {
// Update existing state
Integer count = (Integer) callbackContext.state().getOrDefault("user_action_count", 0);
callbackContext.state().put("user_action_count", count + 1);
// Add new state
callbackContext.state().put("temp:last_operation_status", "success");
// State changes are automatically part of the event's state_delta
// ... rest of callback logic ...
}
}
append_event 做什么:
- 将
Event添加到session.events。 - 从事件的
actions中读取state_delta。 - 将这些更改应用到
SessionService管理的状态,根据服务类型正确处理前缀和持久性。 - 更新会话的
last_update_time。 - 确保并发更新的线程安全性。
⚠️ 关于直接状态修改的警告¶
避免直接修改从 SessionService 直接获取的 Session 对象上的 session.state 集合(字典/Map)(例如,通过 session_service.get_session() 或 session_service.create_session())在智能体调用的管理生命周期之外(即,不通过 CallbackContext 或 ToolContext)。例如,像 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。