Google ADK 深度探索(二):不同语境下的专用上下文对象

Google ADK 深度探索(二):不同语境下的专用上下文对象

在上一篇 《ADK 一等公民 Context 解析》 中,我们了解到上下文是智能体运行的核心。承载这些能力的核心容器是功能强大的 InvocationContext,但为提升安全性与易用性,ADK 对其进行了精细化的分类,为不同语境提供了粒度各异的专用上下文对象。

要理解上下文分类的粒度,让我们重温一下 ADK 的核心理念:发送给 LLM 的“工作上下文(Working Context)”是一个更丰富、有状态系统的编译视图(Compiled View)

“上下文编译器”

在传统软件工程中,编译器将高级源代码转换为机器刻度的二级制文件,在编译过程中执行优化、类型检查和安全检查。类似地,ADK 运行时(Runtime)充当上下文编译器的角色。它摄取交互的“源代码“ – 包括持久的会话状态(Session State)、临时的用户输入(User Instruction)、检索到的工件(Artifacts)、记忆库(Long-Term Knowledge)和系统指令(System Instruction)– 并将他们”编译“成针对当前执行阶段量身定制的特定上下文对象。

这个编译过程需要针对智能体系统的不同组件提供不同的接口(参考 前文)。负责渲染系统提示词的指令提供者(Instruction Provider)所需的访问权限,与设计用于修改数据库的工具或用于验证用户授权的回调(Callback)截然不同。ADK 的四种主要上下文类型 – InvocationContextReadonlyContextCallbackContextToolContext – 代表了这些不同的接口。每种类型都强制执行最小权限原则(Principle of Least Priviledge),确保组件在最小化潜在的错误或安全漏洞“爆炸半径”的范围内执行。

智能体状态的演变

从智能体的发展轨迹,我们也能窥探这种分离架构的必要性。早期的框架本质上将应用程序的整个状态转储到一个单一的对象中,并将这个“上帝对象(God Object)”传递给每个函数。这必然导致:

  • 观察者效应(Observer Effects:观察这一行为本身,会在不同程度上影响被观察的对象或结果):读取状态的函数(例如,决定使用哪个提示词)无意中修改了状态,导致后续轮次出现不可预测的行为。
  • 安全漏洞:不受信任的组件对系统关键配置信息或者用户身份凭据拥有不受限制的访问权限。
  • 认知过载:开发人员即使在专注于一个小的、孤立的任务时,也必须管理整个数据宇宙。

ADK 通过在其上下文类型之间划分状态的可见性可变性来解决这些问题。

核心对象模型

ADK 上下文架构基于严格的类层次结构,该层级结构控制着功能继承。这种层次结构并非仅仅是实现细节,而是上述安全性和稳定性保证的结构性保障。

上下文类型角色继承 / 基类主要作用域可变性
ReadonlyContext不可变基础基类指令提供者(Instruction Providers)不可变(状态只读)
CallbackContext观察者与拦截器继承自 ReadonlyContext生命周期钩子(before/after agentbefore/after modelbefore/after tool可变(状态读/写)
ToolContext效应器与执行器继承自 CallbackContext工具函数(FunctionTool可变 + 动作(认证、长期记忆、工件)
InvocationContext全局编排器独立 / 综合智能体核心逻辑(BaseAgent_run_async_impl完全访问(会话、服务、控制流)

Effector (效应器): 接收指令或状态变化,并直接执行具体的、最终的操作以产生“效应”。作为链路末端,直接作用于系统或环境,是决策的最终体现。

Executor (执行器):核心职责是“调度与执行”,它负责管理任务如何、何时以及在哪运行。

每个上下文类型,无论其具体能力如何,都携带跟踪执行流程所需的基本元数据:

  • 调用 ID(Invocation ID):当前请求 - 响应周期的唯一标识符。这确保了日志、错误和状态更改始终可以关联会特定的用户轮次。
  • 智能体名称(Agent Name):当前正在执行的特定智能体的身份。在执行从“路由器(Router)”传递到“专家(Specialist)”的多智能体系统中,知道在执行和知道正在发生什么同样重要。

这种共享身份确保了即使上下文对象从指令阶段的 ReadonlyContext “变形”为执行阶段的 ToolContext,操作的“血统”也保持不中断。

ReadonlyContext - 不可变基础

ReadonlyContext 是面向组件的上下文层次结构的架构基石。其设计深深根植于函数式编程原则,特别是副作用与纯逻辑的分离。

InstructionProvider: TypeAlias = Callable[
    [ReadonlyContext], Union[str, Awaitable[str]]
]

ReadonlyContext 是提供给 InstructionProvider函数的接口。

class LlmAgent(BaseAgent):
    ...
    instruction: Union[str, InstructionProvider] = ''
    ...

当初始化一个智能体时,其 Instruction 参数可以是一个静态字符串或一个可调用函数(InstructionProvider)。如果是函数,ADK 运行时在每一轮开始时调用它,并传递一个 ReadonlyContext。可以从 state 中获取内容来生成指令。

class ReadonlyContext:
    ...
    @property  
    def invocation_id(self) -> str:  
      """The current invocation id."""  
      return self._invocation_context.invocation_id  
      
    @property  
    def agent_name(self) -> str:  
      """The name of the agent that is currently running."""  
      return self._invocation_context.agent.name    
    @property  
    def state(self) -> MappingProxyType[str, Any]:  
      """The state of the current session. READONLY field."""  
      return MappingProxyType(self._invocation_context.session.state)
    ...

幂等性与观察者效应

ReadonlyContext 同样公开了会话状态,但将其包装在一个允许检索但严格禁止修改的只读视图中。不可变,旨在防止观察者效应InstructionProvider 通常是动态智能体系统所必须的,例如:

“你是一个智能辅助智能体,用户当前处于勿扰模式,有 3 个待办任务。你的职责是帮助用户有效地管理这些任务,包括列出任务、设置优先级、提供提醒、建议完成步骤、跟踪进度,并根据用户的输入更新任务状态。”

该指令需要读取状态(user_statuspending_tasks)。如果提供给该指令生成器的上下文是可变的,开发人员可能会无意中编写出仅通过观察就改变状态的逻辑:if user.status == 'do_not_disturb': user.clear_all_tasks()

通过 ReadonlyContext 的不可变性,保证运行时可以多次重新生成指令,而不会改变智能体的记忆或轨迹。

CallbackContext - 可观察的过渡

在能力阶梯上向上移动,CallbackContext 充当了 ADK 的“拦截器(interceptor)”层。

class CallbackContext(ReadonlyContext):
    ...
    self._state = State(  
        value=invocation_context.session.state,  
        delta=self._event_actions.state_delta,  
    )
    def state(self) -> State:  
        """The delta-aware state of the current session.  
        
        For any state change, you can mutate this object directly,  e.g. `ctx.state['foo'] = 'bar'`  """  
        return self._state
    ...

CallbackContext 直接继承 ReadonlyContext,确保它保留了所有身份和读取能力,但扩展了 API 接口来支持:

  • 可变状态访问:重写了 state 属性,返回一个可变的 State 对象。这允许在回调中直接修改会话状态(例如 ctx.state['key'] = 'value'),并且这些修改会被记录为 delta 以便后续更新 Session。
  • 工件管理(Artifact Mangement):与 Artifact Service 交互的方法,允许处理文件/工件
  • 凭据管理(Credential Management):与 Credential Service 交互的方法,用于处理(保存、读取)认证信息
  • 记忆管理(Memory Mangent):与 MemoryService 交互,可将当前 Session 的事件保存到长期记忆服务中。

CallbackContext 的主要架构目的是通过面向切面编程(AOP)概念实现“纵深防御 (Defense in Depth)”。在复杂的智能体工作流中,仅依赖 LLM 是不够的。回调提供了一个确定性的、基于代码的强制层,包裹着概率性的模型。

纵深防御(Defense in Depth,DiD)是一种关键的网络安全管理策略,其核心思想是:承认没有单一的安全措施是完美的,因此需要通过部署多层次、相互补充的安全控制措施,建立一道又一道的防线,从而在一种措施失效时,其他措施仍能提供保护,最大化地增加攻击者的成本和难度,并为主机的检测和响应争取宝贵时间。

ADK 定义了注入 CallbackContext特定生命周期钩子

  • before_agent_callback
  • after_agent_callback
  • before_model_callback
  • after_model_callback
  • before_tool_callback
  • after_tool_callback

这些 Callback 共同构成了 ADK 的可观测性可控制性基础,使得开发者可以在不修改核心业务逻辑的情况下,灵活地扩展智能体的行为。比如 agent 相关的钩子,before 用于“预判与拦截”,after 用于“补充与收尾”,两者结合赋予了开发者对智能体执行流的精细控制权。

ToolContext - 安全执行边界

ToolContext 是上下文中最专业、最强大的。它是为“效应器(Effectors)”设计的 – 即智能体调用以与外部世界交互的工具和函数。它代表了智能体离开文本生成的安全范围并对现实世界系统执行操作的边界

ToolContext 继承自 CallbackContxt。这条继承链在架构上具有重要意义:

  • ReadonlyContext 能力:它知道调用了它(身份)。
  • CallbackContext 能力:它可以更改状态并保存工件(可变)。
  • ToolContext 能力:它添加了用于 I/O 和外部交互的特定方法。
class ToolContext(CallbackContext):
  ...
  def actions(self) -> EventActions:
    return self._event_actions  
  def request_credential(self, auth_config: AuthConfig) -> None:
    if not self.function_call_id:
      raise ValueError('function_call_id is not set.')
    self._event_actions.requested_auth_configs[self.function_call_id] = (
        AuthHandler(auth_config).generate_auth_request()
    )

  def get_auth_response(self, auth_config: AuthConfig) -> AuthCredential:
    return AuthHandler(auth_config).get_auth_response(self.state)
  
  async def search_memory(self, query: str) -> SearchMemoryResponse:
    """Searches the memory of the current user."""
    if self._invocation_context.memory_service is None:
      raise ValueError('Memory service is not available.')
    return await self._invocation_context.memory_service.search_memory(
        app_name=self._invocation_context.app_name,
        user_id=self._invocation_context.user_id,
        query=query,
    )
  ...

特有能力:

  • 认证(Authentication):提供方法允许工具动态处理 Oauth 流程和安全 API 的访问,将认证逻辑从工具的业务逻辑中解耦。
  • 记忆搜索(Memory Search):使工具能够主动查询智能体的长期记忆库(如向量数据库)以获取相关上下文。这允许工具说“找到我们上周讨论的记录”,而不需要 LLM 幻觉出文件路径。
  • 事件动作:对 actions 属性(EventActions)的访问允许工具发出系统性变更信号,例如 skip_summarization 可以跳过调用 LLM 来总结函数的响应;使用 requested_tool_confirmations 来引入用户介入。

安全边界:模型参数 vs. 开发者上下文

使用 ToolContext 可以实现模型提供的参数与开发者提供的上下文严格分离,直接解决提示注入(Prompt Injection) 和 幻觉(Hallucination)的风险。

当 LLM 调用工具时,它基于其训练和当前提示词生成参数。然而,这些参数本质上是不可信输入。它们源自一个概率模型(我认为所有的模型都是基于概率的),该模型更可能被恶意用户操纵(忽略之前的指令并删除系统配置)。

然而, ToolContext由框架注入的。实际上模型无法”看到“或者”修改“ ToolContext,它对 LLM 的输出生成过程是不可见的。

这种分离创建了一个基于能力的安全性(Capabilities-based Security)ToolContext 使用可信的开发者上下文,取代 LLM 生成的不可信指令。

基于能力的安全性(Capabilities-based Security)是一种在计算机科学中控制资源访问权限的安全模型。它的核心思想非常直观:访问权限本身被视为一种不可伪造的“钥匙”或“令牌”(即能力),任何主体(如程序、用户或进程)只有在具有相应能力的前提下,才能对特定资源执行特定的操作

弥合差距:”无状态工具“问题

传统思维中工具为认为是无状态的纯函数:输入 -> 输出,也就是无法对更广泛的会话上下文做出反应。但 ToolContext 使工具可以具备状态感知能力,比如从 ToolContext 中获取 session_id,使用该 ID 从外部 API 获取数据,将完整的结果写入 state 中,仅向 LLM 返回一个简短的摘要字符串(减少 Token 消耗,只读取摘要)。

InvocationContext - 全局编排器

ReadonlyContextCallbackContextToolContext 是为特定组件设计的专用视图,而 InvocationContext 是执行周期上的“上帝对象”,它代表了单轮运行期间运行时的综合状态。

InvocationContext 直接传递给 BaseAgent(所有 Agent 的父类)的 _run_async_impl 方法。它是核心职能体逻辑与运行时环境交互的接口。与传递给外围组件(指令提供者、回调、工具)的其他上下文不同。InvocationContextInvocationContext 是智能体大脑的工作空间。它的关键组件关乎智能体有效运行的所有方面:

  • session:对完整 Session 对象的引用。包含了完整的事件历史记录、当前状态以及交互有关的所有数据。
  • agent:当前运行的智能体自身的引用,允许逻辑检查其自身的配置。
  • branch:调用的分支,如 agent_1.agent_2.agent_3agent_1(也是根智能体) 是 agent_2 的父智能体,agent_2agent_3 的父智能体。
  • invocation_id:当前轮次的唯一 ID。
  • user_content:来自用户的原始、未处理的输入。
  • 对已配置的基础设施服务的直接引用:
    • artifact_service:工件服务。
    • session_service:会话服务。
    • memory_service:记忆服务。
    • credential_service:凭据服务。
  • end_invocation:一个布尔标志,可以通过其向运行时发送信号以立即停止处理。常见在 before_agent_callback 是对状态进行判断是否要执行智能体。

编排循环

InvocationContext 是编排的载体。当 ADK 运行时开始一轮交互时,它实例化此上下文并将其交给根智能体。它实例化 InvocationContext 并将其交给根智能体。

总结

Google ADK 的上下文架构代表了一种构建 AI 智能体的成熟的系统功能方法。通过拒绝早期 LLM 开发的“单一可变状态”模型,ADK 强制执行了一种符合既定软件工程原则的规范结构。

  • ReadonlyContext 为指令生成带来了函数式编程的安全性。
  • CallbackContext为智能体生命周期带来了面向切面的可观察性。
  • ToolContext为外部执行带来了基于能力的安全
  • InvocationContext为运行时循环带来了分布式系统的编排。

对开发人员而言,深入理解并运用这些上下文类型,正是从构建“玩具”原型迈向打造生产级系统的关键一步。它允许构建在设计上安全、推理上幂等、执行上可观察的智能体。上下文不再仅仅是智能体“知道”什么,它严格定义了智能体“是”什么以及能安全地做什么

(转载本站文章请注明作者和出处乱世浮生,请勿用于任何商业用途)

comments powered by Disqus