Skip to content

上下文

什么是上下文

在智能体开发工具包(ADK)中,"上下文"指的是在特定操作期间可供你的智能体及其工具使用的关键信息包。可以将其视为有效处理当前任务或对话轮次所需的必要背景知识和资源。

智能体通常需要的不仅仅是最新的用户消息才能表现良好。上下文至关重要,因为它能够:

  1. 维持状态: 记住对话中多个步骤的详细信息(例如,用户偏好、之前的计算、购物车中的物品)。这主要通过会话状态管理。
  2. 传递数据: 在一个步骤(如 LLM 调用或工具执行)中发现或生成的信息与后续步骤共享。会话状态在这里也是关键。
  3. 访问服务: 与框架功能交互,如:
    • 制品(Artifacts)存储: 保存或加载与会话相关的文件或数据块(如 PDF、图像、配置文件)。
    • 记忆(Memory): 从过去的交互或与用户相关的外部知识源中搜索相关信息。
    • 认证: 请求并检索工具安全访问外部 API 所需的凭证。
  4. 身份和跟踪: 知道当前运行的是哪个智能体(agent.name)以及唯一标识当前请求 - 响应周期(invocation_id)以进行日志记录和调试。
  5. 工具特定操作: 启用工具内的专门操作,例如请求认证或搜索记忆(Memory),这些操作需要访问当前交互的详细信息。

保存单个完整的用户请求到最终响应周期(一次调用)所有信息的核心部分是InvocationContext。但是,你通常不会直接创建或管理此对象。ADK 框架在调用开始时创建它(例如,通过runner.run_async),并将相关上下文信息隐式传递给你的智能体代码、回调和工具。

# 概念伪代码:框架如何提供上下文(内部逻辑)

# runner = Runner(agent=my_root_agent, session_service=..., artifact_service=...)
# user_message = types.Content(...)
# session = session_service.get_session(...) # 或创建新的

# --- 在 runner.run_async(...) 内部 ---
# 1. 框架为此特定运行创建主上下文
# invocation_context = InvocationContext(
#     invocation_id="此次运行的唯一 ID",
#     session=session,
#     user_content=user_message,
#     agent=my_root_agent, # 起始智能体
#     session_service=session_service,
#     artifact_service=artifact_service,
#     memory_service=memory_service,
#     # ... 其他必要字段 ...
# )

# 2. 框架调用智能体的运行方法,隐式传递上下文
#    (智能体的方法签名将接收它,例如,_run_async_impl(self, ctx: InvocationContext))
# await my_root_agent.run_async(invocation_context)
# --- 内部逻辑结束 ---

# 作为开发者,你使用方法参数中提供的上下文对象。

不同类型的上下文

虽然InvocationContext作为全面的内部容器,但 ADK 提供了根据特定情况定制的专门上下文对象。这确保了你拥有适合手头任务的正确工具和权限,而无需在任何地方处理完整内部上下文的全部复杂性。以下是你将遇到的不同"风格":

  1. InvocationContext

    • 使用场所: 在智能体的核心实现方法(_run_async_impl_run_live_impl)中直接接收为ctx参数。
    • 目的: 提供对当前调用整个状态的访问。这是最全面的上下文对象。
    • 关键内容: 直接访问session(包括stateevents)、当前agent实例、invocation_id、初始user_content、对已配置服务的引用(artifact_servicememory_servicesession_service),以及与实时/流式模式相关的字段。
    • 使用场景: 主要在智能体的核心逻辑需要直接访问整体会话或服务时使用,尽管状态和制品(Artifacts)交互通常委托给使用自己上下文的回调/工具。也用于控制调用本身(例如,设置ctx.end_invocation = True)。
    # 伪代码:智能体实现接收 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]:
            # 直接访问示例
            agent_name = ctx.agent.name
            session_id = ctx.session.id
            print(f"智能体 {agent_name} 在会话 {session_id} 中运行,调用 ID {ctx.invocation_id}")
            # ... 使用 ctx 的智能体逻辑 ...
            yield # ... 事件 ...
    
  2. ReadonlyContext

    • 使用场所: 在只需要对基本信息进行只读访问且不允许修改的场景中提供(例如,InstructionProvider函数)。它也是其他上下文的基类。
    • 目的: 提供基本上下文详细信息的安全、只读视图。
    • 关键内容: invocation_idagent_name以及当前state的只读视图
    # 伪代码:指令提供者接收 ReadonlyContext
    from google.adk.agents import ReadonlyContext
    
    def my_instruction_provider(context: ReadonlyContext) -> str:
        # 只读访问示例
        user_tier = context.state.get("user_tier", "standard") # 可以读取状态
        # context.state['new_key'] = 'value' # 这通常会导致错误或无效
        return f"处理{user_tier}用户的请求。"
    
  3. CallbackContext

    • 使用场所: 作为callback_context传递给智能体生命周期回调(before_agent_callbackafter_agent_callback)和模型交互回调(before_model_callbackafter_model_callback)。
    • 目的: 便于检查和修改状态、与制品(Artifacts)交互以及特别在回调中访问调用详情。
    • 主要功能(添加到ReadonlyContext):
      • 可变state属性: 允许读取和写入会话状态。在此处所做的更改(callback_context.state['key'] = value)会被跟踪并与回调后框架生成的事件相关联。
      • 制品(Artifacts)方法: 用于与配置的artifact_service交互的load_artifact(filename)save_artifact(filename, part)方法。
      • 直接user_content访问。
    # 伪代码:回调接收 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]:
        # 读/写状态示例
        call_count = callback_context.state.get("model_calls", 0)
        callback_context.state["model_calls"] = call_count + 1 # 修改状态
    
        # 可选地加载制品(Artifacts)
        # config_part = callback_context.load_artifact("model_config.json")
        print(f"准备调用#{call_count + 1},调用 ID {callback_context.invocation_id}")
        return None # 允许模型调用继续
    
  4. ToolContext

    • 使用场所: 作为tool_context传递给支持FunctionTool的函数以及工具执行回调(before_tool_callbackafter_tool_callback)。
    • 目的: 提供CallbackContext所有功能,外加工具执行必需的专门方法,如处理认证、搜索记忆(Memory)和列出制品(Artifacts)。
    • 主要功能(添加到CallbackContext):
      • 认证方法: request_credential(auth_config)触发认证流程,以及get_auth_response(auth_config)检索用户/系统提供的凭证。
      • 制品(Artifacts)列表: list_artifacts()用于发现会话中可用的制品(Artifacts)。
      • 记忆(Memory)搜索: search_memory(query)用于查询配置的memory_service
      • function_call_id属性: 标识触发此工具执行的 LLM 的特定函数调用,对于将认证请求或响应正确地链接回来至关重要。
      • actions属性: 直接访问此步骤的EventActions对象,允许工具发出状态更改、认证请求等信号。
    # 伪代码:接收 ToolContext 的工具函数
    from google.adk.tools import ToolContext
    from typing import Dict, Any
    
    # 假设此函数由 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:
            # 定义所需的认证配置
            # auth_config = AuthConfig(...)
            # tool_context.request_credential(auth_config) # 请求凭证
            # 使用'actions'属性发出已发出认证请求的信号
            # tool_context.actions.requested_auth_configs[tool_context.function_call_id] = auth_config
            return {"status": "需要认证"}
    
        # 使用 API 密钥...
        print(f"工具执行查询'{query}'使用 API 密钥。调用:{tool_context.invocation_id}")
    
        # 可选地搜索记忆(Memory)或列出制品(Artifacts)
        # relevant_docs = tool_context.search_memory(f"与{query}相关的信息")
        # available_files = tool_context.list_artifacts()
    
        return {"result": f"已获取{query}的数据。"}
    

理解这些不同的上下文对象以及何时使用它们是有效管理状态、访问服务以及控制 ADK 应用程序流程的关键。下一节将详细介绍使用这些上下文可以执行的常见任务。

使用上下文的常见任务

现在你已经了解了不同的上下文对象,让我们专注于在构建智能体和工具时如何使用它们来执行常见任务。

访问信息

你将经常需要读取存储在上下文中的信息。

  • 读取会话状态: 访问之前步骤中保存的数据或用户/应用级别设置。在state属性上使用类似字典的访问。

    # 伪代码:在一个工具函数中
    from google.adk.tools 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") # 读取应用级状态
    
        if user_pref == "dark_mode":
            # ... 应用深色模式逻辑 ...
            pass
        print(f"使用 API 端点:{api_endpoint}")
        # ... 工具的其余逻辑 ...
    
    # 伪代码:在回调函数中
    from google.adk.agents import CallbackContext
    
    def my_callback(callback_context: CallbackContext, **kwargs):
        last_tool_result = callback_context.state.get("temp:last_api_result") # 读取临时状态
        if last_tool_result:
            print(f"从上一个工具找到临时结果:{last_tool_result}")
        # ... 回调逻辑 ...
    
  • 获取当前标识符: 对于基于当前操作的日志记录或自定义逻辑很有用。

    # 伪代码:在任何上下文中(展示 ToolContext)
    from google.adk.tools 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"日志:调用={inv_id},智能体={agent_name},函数调用 ID={func_call_id} - 工具已执行。")
    
  • 访问初始用户输入: 参考开始当前调用的消息。

    # 伪代码:在回调中
    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 "非文本输入"
    
        print(f"此调用由用户输入开始:'{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"智能体逻辑记住初始查询:{initial_text}")
    #     ...
    

管理会话状态

状态对于记忆(Memory)和数据流至关重要。当你使用CallbackContextToolContext修改状态时,框架会自动跟踪并持久化这些更改。

  • 工作原理: 写入callback_context.state['my_key'] = my_valuetool_context.state['my_key'] = my_value将此更改添加到与当前步骤事件关联的EventActions.state_delta中。然后SessionService在持久化事件时应用这些增量变化。
  • 在工具之间传递数据:

    # 伪代码:工具 1 - 获取用户 ID
    from google.adk.tools import ToolContext
    import uuid
    
    def get_user_profile(tool_context: ToolContext) -> dict:
        user_id = str(uuid.uuid4()) # 模拟获取 ID
        # 将 ID 保存到状态中供下一个工具使用
        tool_context.state["temp:current_user_id"] = user_id
        return {"profile_status": "ID 已生成"}
    
    # 伪代码:工具 2 - 使用状态中的用户 ID
    def get_user_orders(tool_context: ToolContext) -> dict:
        user_id = tool_context.state.get("temp:current_user_id")
        if not user_id:
            return {"error": "在状态中未找到用户 ID"}
    
        print(f"获取用户 ID 的订单:{user_id}")
        # ... 使用 user_id 获取订单的逻辑 ...
        return {"orders": ["order123", "order456"]}
    
  • 更新用户偏好:

    # 伪代码:工具或回调识别偏好
    from google.adk.tools import ToolContext # Or 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"设置用户偏好'{preference}'为'{value}'")
        return {"status": "偏好已更新"}
    
  • 状态前缀: 虽然基本状态是会话特定的,但像app:user:这样的前缀可以与持久SessionService实现(如DatabaseSessionServiceVertexAiSessionService)一起使用,以指示更广泛的范围(应用范围或跨会话的用户范围)。temp:可以表示仅在当前调用中相关的数据。

使用制品(Artifacts)

使用制品(Artifacts)处理与会话相关的文件或大型数据块。常见用例:处理上传的文档。

  • 文档摘要生成器示例流程:

    1. 引入引用(例如,在设置工具或回调中): 将文档的路径或 URI保存为制品(Artifacts),而不是整个内容。

      # 伪代码:在回调或初始工具中
      from google.adk.agents import CallbackContext # 或 ToolContext
      from google.genai import types
      
      def save_document_reference(context: CallbackContext, file_path: str) -> None:
          # 假设 file_path 是类似"gs://my-bucket/docs/report.pdf"或"/local/path/to/report.pdf"
          try:
              # 创建包含路径/URI 文本的 Part
              artifact_part = types.Part(text=file_path)
              version = context.save_artifact("document_to_summarize.txt", artifact_part)
              print(f"已将文档引用'{file_path}'保存为制品(Artifacts)版本{version}")
              # 如果需要被其他工具使用,将文件名存储在状态中
              context.state["temp:doc_artifact_name"] = "document_to_summarize.txt"
          except ValueError as e:
              print(f"保存制品(Artifacts)错误:{e}") # 例如,未配置制品服务(Artifacts Service)
          except Exception as e:
              print(f"保存制品(Artifacts)引用时出现意外错误:{e}")
      
      # 使用示例:
      # save_document_reference(callback_context, "gs://my-bucket/docs/report.pdf")
      
    2. 摘要工具: 加载制品(Artifacts)以获取路径/URI,使用适当的库读取实际文档内容,总结,并返回结果。

      # 伪代码:在摘要工具函数中
      from google.adk.tools import ToolContext
      from google.genai import types
      # 假设像 google.cloud.storage 或内置 open 这样的库可用
      # 假设存在'summarize_text'函数
      # 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": "在状态中未找到文档制品(Artifacts)名称。"}
      
          try:
              # 1. 加载包含路径/URI 的制品(Artifacts)部分
              artifact_part = tool_context.load_artifact(artifact_name)
              if not artifact_part or not artifact_part.text:
                  return {"error": f"无法加载制品(Artifacts)或制品(Artifacts)没有文本路径:{artifact_name}"}
      
              file_path = artifact_part.text
              print(f"已加载文档引用:{file_path}")
      
              # 2. 读取实际文档内容(在 ADK 上下文之外)
              document_content = ""
              if file_path.startswith("gs://"):
                  # 示例:使用 GCS 客户端库下载/读取
                  # from google.cloud import storage
                  # client = storage.Client()
                  # blob = storage.Blob.from_string(file_path, client=client)
                  # document_content = blob.download_as_text() # 或 bytes,取决于格式
                  pass # 替换为实际的 GCS 读取逻辑
              elif file_path.startswith("/"):
                   # 示例:使用本地文件系统
                   with open(file_path, 'r', encoding='utf-8') as f:
                       document_content = f.read()
              else:
                  return {"error": f"不支持的文件路径方案:{file_path}"}
      
              # 3. 总结内容
              if not document_content:
                   return {"error": "读取文档内容失败。"}
      
              # summary = summarize_text(document_content) # 调用你的摘要逻辑
              summary = f"来自{file_path}的内容摘要" # 占位符
      
              return {"summary": summary}
      
          except ValueError as e:
               return {"error": f"制品服务(Artifacts Service)错误:{e}"}
          except FileNotFoundError:
               return {"error": f"未找到本地文件:{file_path}"}
          # except Exception as e: # 捕获 GCS 等特定异常
          #      return {"error": f"读取文档{file_path}时出错:{e}"}
      
  • 列出制品(Artifacts): 发现有哪些文件可用。

    # 伪代码:在工具函数中
    from google.adk.tools import ToolContext
    
    def check_available_docs(tool_context: ToolContext) -> dict:
        try:
            artifact_keys = tool_context.list_artifacts()
            print(f"可用制品(Artifacts):{artifact_keys}")
            return {"available_docs": artifact_keys}
        except ValueError as e:
            return {"error": f"制品服务(Artifacts Service)错误:{e}"}
    

处理工具认证

安全管理工具所需的 API 密钥或其他凭证。

# 伪代码:需要认证的工具
from google.adk.tools import ToolContext
from google.adk.auth import AuthConfig # 假设定义了适当的 AuthConfig

# 定义你需要的认证配置(例如,OAuth、API 密钥)
MY_API_AUTH_CONFIG = AuthConfig(...)
AUTH_STATE_KEY = "user:my_api_credential" # 存储检索到的凭证的键

def call_secure_api(tool_context: ToolContext, request_data: str) -> dict:
    # 1. 检查凭证是否已存在于状态中
    credential = tool_context.state.get(AUTH_STATE_KEY)

    if not credential:
        # 2. 如果不存在,请求它
        print("未找到凭证,正在请求...")
        try:
            tool_context.request_credential(MY_API_AUTH_CONFIG)
            # 框架处理生成事件。工具执行在此处停止本轮。
            return {"status": "需要认证。请提供凭证。"}
        except ValueError as e:
            return {"error": f"认证错误:{e}"} # 例如,缺少 function_call_id
        except Exception as e:
            return {"error": f"请求凭证失败:{e}"}

    # 3. 如果凭证存在(可能来自请求后的上一轮)
    #    或者如果这是外部认证流程完成后的后续调用
    try:
        # 如有需要,可选择重新验证/检索,或直接使用
        # 如果外部流程刚刚完成,这可能会检索凭证
        auth_credential_obj = tool_context.get_auth_response(MY_API_AUTH_CONFIG)
        api_key = auth_credential_obj.api_key # 或 access_token 等

        # 将其存回状态中以供会话内的未来调用使用
        tool_context.state[AUTH_STATE_KEY] = auth_credential_obj.model_dump() # 持久化检索到的凭证

        print(f"使用检索到的凭证调用 API,数据:{request_data}")
        # ... 使用 api_key 进行实际的 API 调用 ...
        api_result = f"{request_data}的 API 结果"

        return {"result": api_result}
    except Exception as e:
        # 处理检索/使用凭证时的错误
        print(f"使用凭证时出错:{e}")
        # 如果凭证无效,可能清除状态键?
        # tool_context.state[AUTH_STATE_KEY] = None
        return {"error": "使用凭证失败"}
记住:request_credential暂停工具并发出需要认证的信号。用户/系统提供凭证,在后续调用中,get_auth_response(或再次检查状态)允许工具继续。 框架隐式使用tool_context.function_call_id来链接请求和响应。

利用记忆(Memory)

访问来自过去或外部来源的相关信息。

# 伪代码:使用记忆(Memory)搜索的工具
from google.adk.tools import ToolContext

def find_related_info(tool_context: ToolContext, topic: str) -> dict:
    try:
        search_results = tool_context.search_memory(f"有关{topic}的信息")
        if search_results.results:
            print(f"为'{topic}'找到了{len(search_results.results)}条记忆(Memory)结果")
            # 处理 search_results.results(这些是 SearchMemoryResponseEntry)
            top_result_text = search_results.results[0].text
            return {"memory_snippet": top_result_text}
        else:
            return {"message": "未找到相关记忆(Memory)。"}
    except ValueError as e:
        return {"error": f"记忆服务(Memory Service)错误:{e}"} # 例如,服务未配置
    except Exception as e:
        return {"error": f"搜索记忆(Memory)时出现意外错误:{e}"}

高级:直接使用InvocationContext

虽然大多数交互通过CallbackContextToolContext进行,但有时智能体的核心逻辑(_run_async_impl/_run_live_impl)需要直接访问。

# 伪代码:在智能体的_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]:
        # 示例:检查特定服务是否可用
        if not ctx.memory_service:
            print("本次调用没有可用的记忆服务(Memory Service)。")
            # 可能改变智能体行为

        # 示例:基于某些条件提前终止
        if ctx.session.state.get("critical_error_flag"):
            print("检测到严重错误,正在结束调用。")
            ctx.end_invocation = True # 向框架发出停止处理的信号
            yield Event(author=self.name, invocation_id=ctx.invocation_id, content="由于严重错误而停止。")
            return # 停止此智能体的执行

        # ... 正常的智能体处理 ...
        yield # ... 事件 ...

设置ctx.end_invocation = True是从智能体内部或其回调/工具(通过它们各自的上下文对象,这些对象也可以访问并修改底层InvocationContext的标志)优雅地停止整个请求 - 响应周期的方法。

关键要点和最佳实践

  • 使用合适的上下文: 始终使用提供的最具体的上下文对象(工具/工具回调中的ToolContext,智能体/模型回调中的CallbackContext,适用情况下的ReadonlyContext)。仅在必要时直接在_run_async_impl / _run_live_impl中使用完整的InvocationContextctx)。
  • 用于数据流的状态: context.state是在调用内部共享数据、记住偏好和管理对话记忆(Memory)的主要方式。使用持久存储时,要深思熟虑地使用前缀(app:user:temp:)。
  • 用于文件的制品(Artifacts): 使用context.save_artifactcontext.load_artifact来管理文件引用(如路径或 URI)或更大的数据块。存储引用,按需加载内容。
  • 跟踪更改: 通过上下文方法对状态或制品(Artifacts)所做的修改会自动链接到当前步骤的EventActions并由SessionService处理。
  • 从简单开始: 首先专注于state和基本制品(Artifacts)用法。随着需求变得更加复杂,再探索认证、记忆(Memory)和高级InvocationContext字段(如用于实时流式处理的字段)。

通过理解并有效使用这些上下文对象,你可以使用 ADK 构建更复杂、状态化且功能强大的智能体。