上下文
什么是上下文
在Agent Development Kit (ADK)中,"context"(上下文)指的是您的智能体及其工具在特定操作期间可用的关键信息包。可以将其视为有效处理当前任务或对话轮次所需的必要背景知识和资源。
智能体通常不仅需要最新的用户消息才能表现良好。上下文至关重要,因为它能够实现:
- 维护状态:在对话的多个步骤中记住细节(例如用户偏好、之前的计算、购物车中的物品)。这主要通过会话状态来管理。
- 传递数据:将在某一步骤(如LLM调用或工具执行)中发现或生成的信息与后续步骤共享。会话状态在此也至关重要。
- Accessing Services: Interacting with framework capabilities like:
- 工件存储: 保存或加载与会话关联的文件或数据块(如PDF、图像、配置文件)。
- 记忆: 从过去的交互记录或连接到用户的外部知识源中搜索相关信息。
- 认证:请求并获取工具安全访问外部API所需的凭证。
- 身份识别与追踪:了解当前运行的智能体(
agent.name)并唯一标识当前请求-响应周期(invocation_id)以便进行日志记录和调试。 - 工具特定操作: 支持在工具内执行专门的操作,例如请求认证或搜索记忆,这些操作需要访问当前交互的详细信息。
将所有这些信息整合在一起,形成一个完整的用户请求到最终响应周期(称为调用)的核心组件是InvocationContext。不过,通常您不需要直接创建或管理这个对象。ADK框架会在调用开始时(例如通过runner.run_async)创建它,并将相关的上下文信息隐式传递给您的智能体代码、回调函数和工具。
# Conceptual Pseudocode: How the framework provides context (Internal Logic)
# runner = Runner(agent=my_root_agent, session_service=..., artifact_service=...)
# user_message = types.Content(...)
# session = session_service.get_session(...) # Or create new
# --- Inside runner.run_async(...) ---
# 1. Framework creates the main context for this specific run
# invocation_context = InvocationContext(
# invocation_id="unique-id-for-this-run",
# session=session,
# user_content=user_message,
# agent=my_root_agent, # The starting agent
# session_service=session_service,
# artifact_service=artifact_service,
# memory_service=memory_service,
# # ... other necessary fields ...
# )
# 2. Framework calls the agent's run method, passing the context implicitly
# (The agent's method signature will receive it, e.g., _run_async_impl(self, ctx: InvocationContext))
# await my_root_agent.run_async(invocation_context)
# --- End Internal Logic ---
# As a developer, you work with the context objects provided in method arguments.
不同类型的上下文
虽然InvocationContext作为全面的内部容器,ADK提供了针对特定场景量身定制的专用上下文对象。这确保您拥有适合当前任务的工具和权限,而无需处处处理内部上下文的全部复杂性。以下是您将遇到的不同"类型":
-
InvocationContext- 使用场景:作为
ctx参数直接接收于智能体的核心实现方法中(_run_async_impl,_run_live_impl)。 - 用途:提供访问当前调用的完整状态。这是最全面的上下文对象。
- 主要内容: 直接访问
session(包括state和events)、当前agent实例、invocation_id、初始user_content、对已配置服务的引用(artifact_service、memory_service、session_service),以及与实时/流模式相关的字段。 - 使用场景:主要用于当智能体的核心逻辑需要直接访问整个会话或服务时,尽管状态和工件交互通常被委托给使用自身上下文的回调/工具。也用于控制调用本身(例如,设置
ctx.end_invocation = True)。
# Pseudocode: Agent implementation receiving InvocationContext from google.adk.agents import BaseAgent, InvocationContext from google.adk.events import Event from typing import AsyncGenerator class MyAgent(BaseAgent): async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, None]: # Direct access example agent_name = ctx.agent.name session_id = ctx.session.id print(f"Agent {agent_name} running in session {session_id} for invocation {ctx.invocation_id}") # ... agent logic using ctx ... yield # ... event ... - 使用场景:作为
-
ReadonlyContext- 使用场景:在仅需读取基本信息且不允许修改的情况下提供(例如
InstructionProvider函数)。它也是其他上下文类的基类。 - 用途:提供对基础上下文细节的安全、只读视图。
- 主要内容:
invocation_id,agent_name, 以及当前state的只读视图。
# Pseudocode: Instruction provider receiving ReadonlyContext from google.adk.agents import ReadonlyContext def my_instruction_provider(context: ReadonlyContext) -> str: # Read-only access example user_tier = context.state.get("user_tier", "standard") # Can read state # context.state['new_key'] = 'value' # This would typically cause an error or be ineffective return f"Process the request for a {user_tier} user." - 使用场景:在仅需读取基本信息且不允许修改的情况下提供(例如
-
CallbackContext- 使用场景:作为
callback_context传递给智能体生命周期回调(before_agent_callback、after_agent_callback)和模型交互回调(before_model_callback、after_model_callback)。 - 用途:便于检查和修改状态、与工件交互以及访问调用详情特别是在回调函数内部。
- Key Capabilities (Adds to
ReadonlyContext):- 可变的
state属性: 允许读取和写入会话状态。在此处所做的更改(callback_context.state['key'] = value)会被跟踪,并与框架在回调后生成的事件相关联。 - 工件方法:
load_artifact(filename)和save_artifact(filename, part)方法用于与配置的artifact_service进行交互。 - 直接访问
user_content。
- 可变的
# Pseudocode: Callback receiving CallbackContext from google.adk.agents import CallbackContext from google.adk.models import LlmRequest from google.genai import types from typing import Optional def my_before_model_cb(callback_context: CallbackContext, request: LlmRequest) -> Optional[types.Content]: # Read/Write state example call_count = callback_context.state.get("model_calls", 0) callback_context.state["model_calls"] = call_count + 1 # Modify state # Optionally load an artifact # config_part = callback_context.load_artifact("model_config.json") print(f"Preparing model call #{call_count + 1} for invocation {callback_context.invocation_id}") return None # Allow model call to proceed - 使用场景:作为
-
ToolContext- 使用场景:作为
tool_context传递给支持FunctionTool的函数以及工具执行回调函数(before_tool_callback、after_tool_callback)。 - 用途:提供
CallbackContext的所有功能,外加工具执行所需的关键方法,如处理认证、搜索内存和列出工件。 - Key Capabilities (Adds to
CallbackContext):- 认证方法:
request_credential(auth_config)用于触发认证流程,get_auth_response(auth_config)用于获取用户/系统提供的凭证。 - 制品列表:
list_artifacts()用于发现会话中可用的制品。 - 记忆搜索:
search_memory(query)用于查询配置的memory_service。 function_call_id属性:用于标识触发此工具执行的LLM特定函数调用,对于正确关联认证请求或响应至关重要。actions属性: 直接访问此步骤的EventActions对象,允许工具发出状态变更、授权请求等信号。
- 认证方法:
# Pseudocode: Tool function receiving ToolContext from google.adk.agents import ToolContext from typing import Dict, Any # Assume this function is wrapped by a FunctionTool def search_external_api(query: str, tool_context: ToolContext) -> Dict[str, Any]: api_key = tool_context.state.get("api_key") if not api_key: # Define required auth config # auth_config = AuthConfig(...) # tool_context.request_credential(auth_config) # Request credentials # Use the 'actions' property to signal the auth request has been made # tool_context.actions.requested_auth_configs[tool_context.function_call_id] = auth_config return {"status": "Auth Required"} # Use the API key... print(f"Tool executing for query '{query}' using API key. Invocation: {tool_context.invocation_id}") # Optionally search memory or list artifacts # relevant_docs = tool_context.search_memory(f"info related to {query}") # available_files = tool_context.list_artifacts() return {"result": f"Data for {query} fetched."} - 使用场景:作为
理解这些不同的上下文对象及其适用场景,是有效管理状态、访问服务以及控制ADK应用流程的关键。下一节将详细介绍如何利用这些上下文执行常见任务。
使用上下文的常见任务
现在您已经了解了不同的上下文对象,接下来我们将重点介绍在构建智能体和工具时如何使用它们来完成常见任务。
访问信息
您经常需要读取存储在上下文中的信息。
-
Reading Session State: Access data saved in previous steps or user/app-level settings. Use dictionary-like access on the
stateproperty.# Pseudocode: In a Tool function from google.adk.agents import ToolContext def my_tool(tool_context: ToolContext, **kwargs): user_pref = tool_context.state.get("user_display_preference", "default_mode") api_endpoint = tool_context.state.get("app:api_endpoint") # Read app-level state if user_pref == "dark_mode": # ... apply dark mode logic ... pass print(f"Using API endpoint: {api_endpoint}") # ... rest of tool logic ... # Pseudocode: In a Callback function from google.adk.agents import CallbackContext def my_callback(callback_context: CallbackContext, **kwargs): last_tool_result = callback_context.state.get("temp:last_api_result") # Read temporary state if last_tool_result: print(f"Found temporary result from last tool: {last_tool_result}") # ... callback logic ... -
获取当前标识符: 适用于基于当前操作的日志记录或自定义逻辑。
# 伪代码:在任何上下文中(以ToolContext为例) from google.adk.agents import ToolContext def log_tool_usage(tool_context: ToolContext, **kwargs): agent_name = tool_context.agent_name inv_id = tool_context.invocation_id func_call_id = getattr(tool_context, 'function_call_id', 'N/A') # 特定于ToolContext print(f"Log: Invocation={inv_id}, Agent={agent_name}, FunctionCallID={func_call_id} - Tool Executed.") -
访问初始用户输入: 回溯触发当前调用的初始消息。
# 伪代码:在回调函数中 from google.adk.agents import CallbackContext def check_initial_intent(callback_context: CallbackContext, **kwargs): initial_text = "N/A" if callback_context.user_content and callback_context.user_content.parts: initial_text = callback_context.user_content.parts[0].text or "Non-text input" print(f"This invocation started with user input: '{initial_text}'") # 伪代码:在智能体的_run_async_impl方法中 # async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, None]: # if ctx.user_content and ctx.user_content.parts: # initial_text = ctx.user_content.parts[0].text # print(f"Agent logic remembering initial query: {initial_text}") # ...
管理会话状态
状态对于记忆和数据流至关重要。当你使用CallbackContext或ToolContext修改状态时,框架会自动跟踪并持久化这些变更。
- 工作原理: 写入
callback_context.state['my_key'] = my_value或tool_context.state['my_key'] = my_value会将此变更添加到与当前步骤事件关联的EventActions.state_delta中。SessionService在持久化事件时会应用这些增量变更。 -
Passing Data Between Tools:
# Pseudocode: Tool 1 - Fetches user ID from google.adk.agents import ToolContext import uuid def get_user_profile(tool_context: ToolContext) -> dict: user_id = str(uuid.uuid4()) # Simulate fetching ID # Save the ID to state for the next tool tool_context.state["temp:current_user_id"] = user_id return {"profile_status": "ID generated"} # Pseudocode: Tool 2 - Uses user ID from state def get_user_orders(tool_context: ToolContext) -> dict: user_id = tool_context.state.get("temp:current_user_id") if not user_id: return {"error": "User ID not found in state"} print(f"Fetching orders for user ID: {user_id}") # ... logic to fetch orders using user_id ... return {"orders": ["order123", "order456"]} -
更新用户偏好设置:
# 伪代码:工具或回调识别出一个偏好设置 from google.adk.agents import ToolContext # 或CallbackContext def set_user_preference(tool_context: ToolContext, preference: str, value: str) -> dict: # 使用'user:'前缀表示用户级状态(如果使用持久化SessionService) state_key = f"user:{preference}" tool_context.state[state_key] = value print(f"Set user preference '{preference}' to '{value}'") return {"status": "Preference updated"} -
状态前缀:虽然基础状态是会话特定的,但可以使用像
app:和user:这样的前缀与持久化的SessionService实现(如DatabaseSessionService或VertexAiSessionService)一起使用,以表示更广泛的范围(跨会话的应用范围或用户范围)。temp:可以表示仅与当前调用相关的数据。
使用工件
使用工件(artifacts)来处理与会话关联的文件或大型数据块。常见用例:处理上传的文档。
-
文档摘要生成示例流程:
-
Ingest Reference (e.g., in a Setup Tool or Callback): Save the path or URI of the document, not the entire content, as an artifact.
# Pseudocode: In a callback or initial tool from google.adk.agents import CallbackContext # Or ToolContext from google.genai import types def save_document_reference(context: CallbackContext, file_path: str) -> None: # Assume file_path is something like "gs://my-bucket/docs/report.pdf" or "/local/path/to/report.pdf" try: # Create a Part containing the path/URI text artifact_part = types.Part(text=file_path) version = context.save_artifact("document_to_summarize.txt", artifact_part) print(f"Saved document reference '{file_path}' as artifact version {version}") # Store the filename in state if needed by other tools context.state["temp:doc_artifact_name"] = "document_to_summarize.txt" except ValueError as e: print(f"Error saving artifact: {e}") # E.g., Artifact service not configured except Exception as e: print(f"Unexpected error saving artifact reference: {e}") # Example usage: # save_document_reference(callback_context, "gs://my-bucket/docs/report.pdf") -
Summarizer Tool: Load the artifact to get the path/URI, read the actual document content using appropriate libraries, summarize, and return the result.
# Pseudocode: In the Summarizer tool function from google.adk.agents import ToolContext from google.genai import types # Assume libraries like google.cloud.storage or built-in open are available # Assume a 'summarize_text' function exists # from my_summarizer_lib import summarize_text def summarize_document_tool(tool_context: ToolContext) -> dict: artifact_name = tool_context.state.get("temp:doc_artifact_name") if not artifact_name: return {"error": "Document artifact name not found in state."} try: # 1. Load the artifact part containing the path/URI artifact_part = tool_context.load_artifact(artifact_name) if not artifact_part or not artifact_part.text: return {"error": f"Could not load artifact or artifact has no text path: {artifact_name}"} file_path = artifact_part.text print(f"Loaded document reference: {file_path}") # 2. Read the actual document content (outside ADK context) document_content = "" if file_path.startswith("gs://"): # Example: Use GCS client library to download/read # from google.cloud import storage # client = storage.Client() # blob = storage.Blob.from_string(file_path, client=client) # document_content = blob.download_as_text() # Or bytes depending on format pass # Replace with actual GCS reading logic elif file_path.startswith("/"): # Example: Use local file system with open(file_path, 'r', encoding='utf-8') as f: document_content = f.read() else: return {"error": f"Unsupported file path scheme: {file_path}"} # 3. Summarize the content if not document_content: return {"error": "Failed to read document content."} # summary = summarize_text(document_content) # Call your summarization logic summary = f"Summary of content from {file_path}" # Placeholder return {"summary": summary} except ValueError as e: return {"error": f"Artifact service error: {e}"} except FileNotFoundError: return {"error": f"Local file not found: {file_path}"} # except Exception as e: # Catch specific exceptions for GCS etc. # return {"error": f"Error reading document {file_path}: {e}"}
-
-
列出工件: 查看有哪些文件可用。
# 伪代码:在工具函数中 from google.adk.agents import ToolContext def check_available_docs(tool_context: ToolContext) -> dict: try: artifact_keys = tool_context.list_artifacts() print(f"Available artifacts: {artifact_keys}") return {"available_docs": artifact_keys} except ValueError as e: return {"error": f"Artifact service error: {e}"}
处理工具认证
安全地管理工具所需的API密钥或其他凭证。
# Pseudocode: Tool requiring auth
from google.adk.agents import ToolContext
from google.adk.auth import AuthConfig # Assume appropriate AuthConfig is defined
# Define your required auth configuration (e.g., OAuth, API Key)
MY_API_AUTH_CONFIG = AuthConfig(...)
AUTH_STATE_KEY = "user:my_api_credential" # Key to store retrieved credential
def call_secure_api(tool_context: ToolContext, request_data: str) -> dict:
# 1. Check if credential already exists in state
credential = tool_context.state.get(AUTH_STATE_KEY)
if not credential:
# 2. If not, request it
print("Credential not found, requesting...")
try:
tool_context.request_credential(MY_API_AUTH_CONFIG)
# The framework handles yielding the event. The tool execution stops here for this turn.
return {"status": "Authentication required. Please provide credentials."}
except ValueError as e:
return {"error": f"Auth error: {e}"} # e.g., function_call_id missing
except Exception as e:
return {"error": f"Failed to request credential: {e}"}
# 3. If credential exists (might be from a previous turn after request)
# or if this is a subsequent call after auth flow completed externally
try:
# Optionally, re-validate/retrieve if needed, or use directly
# This might retrieve the credential if the external flow just completed
auth_credential_obj = tool_context.get_auth_response(MY_API_AUTH_CONFIG)
api_key = auth_credential_obj.api_key # Or access_token, etc.
# Store it back in state for future calls within the session
tool_context.state[AUTH_STATE_KEY] = auth_credential_obj.model_dump() # Persist retrieved credential
print(f"Using retrieved credential to call API with data: {request_data}")
# ... Make the actual API call using api_key ...
api_result = f"API result for {request_data}"
return {"result": api_result}
except Exception as e:
# Handle errors retrieving/using the credential
print(f"Error using credential: {e}")
# Maybe clear the state key if credential is invalid?
# tool_context.state[AUTH_STATE_KEY] = None
return {"error": "Failed to use credential"}
request_credential pauses the tool and signals the need for authentication. The user/system provides credentials, and on a subsequent call, get_auth_response (or checking state again) allows the tool to proceed. The tool_context.function_call_id is used implicitly by the framework to link the request and response.
利用记忆功能
从过去或外部来源获取相关信息。
# Pseudocode: Tool using memory search
from google.adk.agents import ToolContext
def find_related_info(tool_context: ToolContext, topic: str) -> dict:
try:
search_results = tool_context.search_memory(f"Information about {topic}")
if search_results.results:
print(f"Found {len(search_results.results)} memory results for '{topic}'")
# Process search_results.results (which are SearchMemoryResponseEntry)
top_result_text = search_results.results[0].text
return {"memory_snippet": top_result_text}
else:
return {"message": "No relevant memories found."}
except ValueError as e:
return {"error": f"Memory service error: {e}"} # e.g., Service not configured
except Exception as e:
return {"error": f"Unexpected error searching memory: {e}"}
高级:直接使用InvocationContext
虽然大多数交互通过CallbackContext或ToolContext进行,但有时智能体的核心逻辑(_run_async_impl/_run_live_impl)需要直接访问。
# Pseudocode: Inside agent's _run_async_impl
from google.adk.agents import InvocationContext, BaseAgent
from google.adk.events import Event
from typing import AsyncGenerator
class MyControllingAgent(BaseAgent):
async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, None]:
# Example: Check if a specific service is available
if not ctx.memory_service:
print("Memory service is not available for this invocation.")
# Potentially change agent behavior
# Example: Early termination based on some condition
if ctx.session.state.get("critical_error_flag"):
print("Critical error detected, ending invocation.")
ctx.end_invocation = True # Signal framework to stop processing
yield Event(author=self.name, invocation_id=ctx.invocation_id, content="Stopping due to critical error.")
return # Stop this agent's execution
# ... Normal agent processing ...
yield # ... event ...
设置ctx.end_invocation = True是一种优雅的方式,可以从智能体或其回调/工具内部(通过它们各自的上下文对象,这些对象也可以修改底层InvocationContext的标志)停止整个请求-响应周期。
关键要点与最佳实践
- 使用正确的上下文:始终使用提供的最具体的上下文对象(工具/工具回调中使用
ToolContext,智能体/模型回调中使用CallbackContext,适用时使用ReadonlyContext)。仅在必要时直接在_run_async_impl/_run_live_impl中使用完整的InvocationContext(ctx)。 - 数据流状态:
context.state是主要方式,用于在单次调用内部共享数据、记住偏好设置和管理对话记忆。使用持久化存储时,请谨慎选择前缀(app:、user:、temp:)。 - 文件工件: 使用
context.save_artifact和context.load_artifact来管理文件引用(如路径或URI)或较大的数据块。存储引用,按需加载内容。 - 跟踪变更:通过上下文方法对状态或工件所做的修改会自动关联到当前步骤的
EventActions,并由SessionService处理。 - 从简单开始: 首先专注于
state和基本工件使用。随着需求变得更加复杂,再探索认证、内存和高级InvocationContext字段(如用于实时流的字段)。
通过理解和有效使用这些上下文对象,您可以使用ADK构建更复杂、有状态且功能强大的智能体。