在本系列的前三篇文章中,我们见证了从对话式AI到行动式AI代理的转变,深入探索了Google ADK的模块化架构,并完成了第一个代理的实践。现在,我们将目光投向代理智能的核心——其认知架构。构建一个能“行动”的代理相对直接,但要构建一个能智能、可靠地行动,能够处理复杂性、不确定性并从中学习的代理,则必须掌握其“思考”的引擎。本文将深度解析支撑ADK代理进行高级推理、规划和决策的关键认知框架(如CoT, ReAct, ToT),并揭示ADK如何通过其工程化的设计,为这些先进模式提供坚实的实现基础,助力开发者构建真正具备复杂问题解决能力的下一代AI代理。
一、 超越简单响应:为何需要高级认知架构?
我们已经知道,ADK代理的核心是大语言模型 (LLM),它提供了基础的理解和生成能力。然而,面对现实世界任务的复杂性和多变性,仅依赖LLM的“直觉式”响应是远远不够的。我们需要一种机制,让代理能够:
- 分解复杂任务: 将宏大目标拆解为可管理的小步骤。
- 动态规划与调整: 根据环境反馈和中间结果,灵活调整行动计划。
- 有效利用信息: 知道何时需要外部信息(通过工具),如何获取,以及如何整合。
- 评估与选择: 在多个可能的行动或推理路径中做出明智的选择。
高级认知架构,如链式思考(CoT)、ReAct框架和思想之树(ToT),正是为解决这些挑战而生。它们并非取代LLM,而是结构化地引导LLM的推理和决策过程,使其更加深入、可靠和高效。ADK的设计哲学正是要将这些认知模式的实现,从纯粹的“提示工程技巧”提升为可控、可评估、可扩展的工程实践。
二、 基础推理引擎:链式思考 (CoT) 与 ADK 的指令艺术
链式思考 (Chain of Thought, CoT) 是解锁LLM深度推理能力的基石。
- 核心机制: 促使LLM在输出最终答案前,显式地生成一系列中间推理步骤(“Let's think step by step...”)。这种“慢思考”显著提高了其在逻辑、算术和多步推理任务上的表现。
- ADK实现关键: CoT主要通过精巧的提示工程 (Prompt Engineering) 实现,而ADK的
LlmAgent
的instructions
参数正是应用这一技术的关键场所。
- 不仅仅是初始指令: 高级的CoT应用可能需要在多轮交互中动态调整或补充提示,ADK的
Callbacks
机制或自定义的Runner
逻辑,允许开发者在交互过程的关键节点注入引导性提示或检查点,确保LLM的思考链不“跑偏”。
- 结构化输出: 可以引导LLM将CoT的思考步骤以特定格式(如XML标签、JSON)输出,便于ADK框架解析、记录或用于后续的条件判断。
虽然CoT本身原理简单,但在ADK中将其工程化、可控化地应用,是构建更复杂认知流程的基础。
三、 行动与思考的融合:ReAct 框架与 ADK 的执行循环
ReAct (Reason + Act) 已成为现代工具使用型AI代理的标准范式,它完美诠释了“行动派”AI的内涵。
- 核心循环:
Thought -> Action -> Observation
。代理首先思考(Reason)分析现状、制定计划,然后决定执行一个动作(Act,通常是调用工具),最后接收动作的结果(Observation),并基于此进行下一轮思考。
- ADK的强力支撑: ReAct与ADK的核心架构高度契合,ADK不仅支持,更是优化了ReAct模式的实现:
- 规划器组件 (Planner): ADK很可能提供了内置的规划器实现(例如,一个可配置的
adk.planning.ReActPlanner
),它封装了ReAct的核心循环逻辑,开发者只需配置好LLM、工具集和初始目标。
- 工具机制 (
Tool
): ADK的FunctionTool
,AgentTool
等提供了丰富的“行动”选项。其对工具描述(docstrings)的自动解析和传递,使得LLM能更好地理解何时调用哪个工具。框架对工具执行的可靠管理(包括可能的异步长时工具支持)是ReAct成功的关键。
- 执行引擎 (
Runner
):Runner
精确地编排着ReAct循环。它负责将用户的输入、LLM生成的Thought
和Action
(可能需要解析特定格式)、工具调用(Tool Invocation
)及其结果(Tool Result
- 即Observation
),以及重要的上下文(State
,Events
),在循环的各个阶段间可靠地传递。
- 错误处理与适应性: 在ReAct循环中,工具调用可能失败。ADK的架构允许LLM在接收到错误
Observation
后,能够进行Thought
反思,决定是重试、更换工具还是调整计划,这大大增强了代理的鲁棒性。
示例:ADK中的ReAct流转(概念性)
# --- 概念性 Python 伪代码,用于演示 ReAct 流程 ---
# 注意: 此代码为教学演示目的,简化了许多 ADK 的实际细节。
# --- 0. 定义概念组件 ---
class MockLLM:
"""根据 ReAct 交互记录模拟 LLM (大语言模型) 的响应。"""
def generate(self, prompt):
# 打印传递给模拟LLM的提示
print(f"\nLLM接收到的输入 (Prompt):\n{prompt}\n")
# 调整条件顺序:优先检查更具体的、表示后续阶段的状态
if "UA123航班" in prompt: # 对应工具返回观察结果后,代理的思考起点
response = """
Thought: 找到一个航班。在预订前应与用户确认。
Action: RespondToUserTool(message="找到UA123航班: SFO->HND, 周二 10:00 AM, 价格 $1200。需要我为您预订吗?")
"""
elif "从旧金山出发" in prompt: # 对应第二轮对话后,代理的思考起点
response = """
Thought: 已有出发地 (SFO旧金山),目的地 (HND东京羽田),和日期。可以使用 FlightSearchTool 搜索航班。
Action: FlightSearchTool(origin="SFO", destination="HND", date="下周二")
"""
elif "需要航班详情" in prompt: # 对应第一轮对话后,代理的思考起点
response = """
Thought: 需要航班详情。用户已指定目的地和日期,还需要出发地。我会询问用户。
Action: RespondToUserTool(message="当然,请问您从哪里出发?")
"""
else:
response = "Thought: 我不确定如何继续。 Action: RespondToUserTool(message='抱歉,您能再说清楚一些吗?') "
# 打印模拟LLM生成的响应
print(f"LLM生成的输出:\n{response}\n")
return response
class SessionState:
"""最小化的状态/历史记录管理器。"""
def __init__(self):
self.history = [] # 存储事件列表 (用户输入, 代理思考, 动作, 观察结果)
def add_event(self, event_description):
self.history.append(event_description)
print(f"事件记录: {event_description}") # 模拟日志记录
def get_context(self):
# 在真实的 ADK 中,这里会智能地选择相关的历史/状态信息构建上下文
return "\n".join(self.history)
class RespondToUserTool:
"""模拟向用户发送响应的工具。"""
name = "RespondToUserTool" # 工具名称,LLM会引用它
def execute(self, message: str):
print(f"工具执行({self.name}): 正在发送给用户: '{message}'")
# 在真实系统中,这将通过 Runner/UI 发送输出
return f"观察结果: 消息 '{message}' 已成功发送给用户。" # 工具执行后的观察结果
class FlightSearchTool:
"""模拟调用航班搜索 API 的工具。"""
name = "FlightSearchTool" # 工具名称
def execute(self, origin: str, destination: str, date: str):
print(f"工具执行({self.name}): 正在搜索 {origin} 到 {destination},日期 {date} 的航班...")
# 模拟 API 调用延迟/结果
result = f"观察结果: [UA123航班: 从 {origin} 到 {destination}, 日期 {date}, 上午10:00, 价格 $1200]"
print(f"工具执行({self.name}): 搜索结果: {result}")
return result # 工具执行后的观察结果
class MockPlannerAgent: # 模拟 LlmAgent + ReAct Planner(规划器)的逻辑
"""模拟代理使用 ReAct 逻辑处理对话回合。"""
def __init__(self, llm, tools):
self.llm = llm
self.tools = {tool.name: tool for tool in tools} # 工具注册表
def _parse_llm_output(self, text_response_from_llm):
# 用于解析 "Thought: ..." 和 "Action: ToolName(param=value...)" 格式的基础解析逻辑
thought = ""
action_name = "RespondToUserTool" # 默认动作,以防解析失败
action_params = {"message": "抱歉,我内部出错了,无法理解下一步该做什么。"}
try:
thought_marker = "Thought:"
action_marker = "Action:"
thought_start_index = text_response_from_llm.find(thought_marker)
action_start_index = text_response_from_llm.find(action_marker)
if thought_start_index != -1:
# 思考内容从 "Thought:" 之后开始,到 "Action:" 之前(如果存在)或字符串末尾
thought_end_index = action_start_index if action_start_index > thought_start_index else len(
text_response_from_llm)
thought = text_response_from_llm[thought_start_index + len(thought_marker):thought_end_index].strip()
if action_start_index != -1:
action_full_string = text_response_from_llm[action_start_index + len(action_marker):].strip()
tool_name_end_index = action_full_string.find('(')
action_name = action_full_string[:tool_name_end_index]
params_string = action_full_string[tool_name_end_index + 1: action_full_string.rfind(')')]
parsed_params = {}
if params_string: # 确保参数字符串非空
# 非常基础的参数解析 (假设是简单的 key="value" 或 key=value, 逗号分隔)
# 真实的解析会更健壮
param_parts = params_string.split(',')
for part in param_parts:
key_value_pair = part.split('=')
if len(key_value_pair) == 2:
key = key_value_pair[0].strip()
value = key_value_pair[1].strip().strip('"') # 去除可能的引号
parsed_params[key] = value
action_params = parsed_params
return {"thought": thought, "action_name": action_name, "action_params": action_params}
except Exception as e:
print(f"LLM响应解析错误: {e},原始响应: {text_response_from_llm}")
# 返回一个安全的默认动作
return {"thought": "解析LLM输出时发生错误。", "action_name": "RespondToUserTool",
"action_params": {"message": "我好像卡住了,能再说一遍吗?"}}
def run_thought_action_cycle(self, current_context):
# 构建给LLM的提示,包含当前对话上下文和ReAct指令
prompt = f"当前对话上下文:\n{current_context}\n\n你的任务是协助用户。请思考下一步,并决定要执行的动作。请严格按照以下格式回复:\nThought: [你的思考过程]\nAction: [ToolName]([param1=value1, param2=value2...])"
llm_response_text = self.llm.generate(prompt)
parsed_agent_decision = self._parse_llm_output(llm_response_text)
return parsed_agent_decision
class MockRunner:
"""模拟 ADK Runner(运行器)编排 ReAct 循环。"""
def __init__(self, agent_instance, registered_tools):
self.agent = agent_instance
self.tools = {tool.name: tool for tool in registered_tools}
self.session_state = SessionState() # 每个Runner实例维护一个会话状态
def process_user_input(self, user_input_text):
print(f"\n--- 用户对话回合开始 ---")
self.session_state.add_event(f"用户输入: {user_input_text}")
# ReAct 核心循环:在一个用户回合内,代理可能进行多次“思考-行动-观察”
while True:
current_context = self.session_state.get_context()
agent_decision = self.agent.run_thought_action_cycle(current_context)
self.session_state.add_event(f"代理思考: {agent_decision['thought']}")
action_name_to_execute = agent_decision["action_name"]
action_parameters = agent_decision["action_params"]
if action_name_to_execute in self.tools:
selected_tool = self.tools[action_name_to_execute]
self.session_state.add_event(f"代理动作: 准备执行 {action_name_to_execute}({action_parameters})")
try:
observation_result = selected_tool.execute(**action_parameters)
self.session_state.add_event(observation_result) # 将工具结果作为观察结果添加到历史
# 如果代理的动作是直接回应用户,那么这个用户回合的内部ReAct循环结束
if action_name_to_execute == "RespondToUserTool":
print(f"--- 用户对话回合结束 ---")
break # 跳出 while 循环,等待下一个用户输入
# 否则 (例如工具是 FlightSearchTool),循环会继续,
# 代理会带着新的观察结果(工具执行结果)立即进行下一轮思考
except Exception as e:
error_observation = f"观察结果: 执行工具 {action_name_to_execute} 时出错: {e}"
self.session_state.add_event(error_observation)
# 代理将在下一个循环迭代中看到这个错误并处理
else:
unknown_tool_observation = f"观察结果: 错误 - 未找到名为 '{action_name_to_execute}' 的工具。"
self.session_state.add_event(unknown_tool_observation)
# 代理将在下一个循环迭代中看到这个错误并处理
def main():
"""主函数,用于设置和运行ReAct流程模拟。"""
print("--- ReAct 流程模拟开始 ---")
# --- 1. 初始化所有模拟组件 ---
mock_llm_instance = MockLLM()
respond_tool_instance = RespondToUserTool()
search_tool_instance = FlightSearchTool()
# 将工具实例传递给代理
agent_instance = MockPlannerAgent(llm=mock_llm_instance, tools=[respond_tool_instance, search_tool_instance])
# 将代理实例和工具实例传递给运行器
runner_instance = MockRunner(agent_instance=agent_instance,
registered_tools=[respond_tool_instance, search_tool_instance])
print("\n--- 所有组件初始化完毕 ---")
# --- 2. 模拟用户与代理的交互 ---
runner_instance.process_user_input("预订一张下周二去东京的机票")
runner_instance.process_user_input("我从旧金山出发")
# 你可以继续添加更多用户输入来观察后续交互:
# runner_instance.process_user_input("是的,就订这个吧。")
print("\n--- ReAct 流程模拟结束 ---")
if __name__ == "__main__":
main()
输出内容如下:
--- ReAct 流程模拟开始 ---
--- 所有组件初始化完毕 ---
--- 用户对话回合开始 ---
事件记录: 用户输入: 预订一张下周二去东京的机票
LLM接收到的输入 (Prompt):
当前对话上下文:
用户输入: 预订一张下周二去东京的机票
你的任务是协助用户。请思考下一步,并决定要执行的动作。请严格按照以下格式回复:
Thought: [你的思考过程]
Action: [ToolName]([param1=value1, param2=value2...])
LLM生成的输出:
Thought: 我不确定如何继续。 Action: RespondToUserTool(message='抱歉,您能再说清楚一些吗?')
事件记录: 代理思考: 我不确定如何继续。
事件记录: 代理动作: 准备执行 RespondToUserTool({'message': "'抱歉,您能再说清楚一些吗?'"})
工具执行(RespondToUserTool): 正在发送给用户: ''抱歉,您能再说清楚一些吗?''
事件记录: 观察结果: 消息 ''抱歉,您能再说清楚一些吗?'' 已成功发送给用户。
--- 用户对话回合结束 ---
--- 用户对话回合开始 ---
事件记录: 用户输入: 我从旧金山出发
LLM接收到的输入 (Prompt):
当前对话上下文:
用户输入: 预订一张下周二去东京的机票
代理思考: 我不确定如何继续。
代理动作: 准备执行 RespondToUserTool({'message': "'抱歉,您能再说清楚一些吗?'"})
观察结果: 消息 ''抱歉,您能再说清楚一些吗?'' 已成功发送给用户。
用户输入: 我从旧金山出发
你的任务是协助用户。请思考下一步,并决定要执行的动作。请严格按照以下格式回复:
Thought: [你的思考过程]
Action: [ToolName]([param1=value1, param2=value2...])
LLM生成的输出:
Thought: 已有出发地 (SFO旧金山),目的地 (HND东京羽田),和日期。可以使用 FlightSearchTool 搜索航班。
Action: FlightSearchTool(origin="SFO", destination="HND", date="下周二")
事件记录: 代理思考: 已有出发地 (SFO旧金山),目的地 (HND东京羽田),和日期。可以使用 FlightSearchTool 搜索航班。
事件记录: 代理动作: 准备执行 FlightSearchTool({'origin': 'SFO', 'destination': 'HND', 'date': '下周二'})
工具执行(FlightSearchTool): 正在搜索 SFO 到 HND,日期 下周二 的航班...
工具执行(FlightSearchTool): 搜索结果: 观察结果: [UA123航班: 从 SFO 到 HND, 日期 下周二, 上午10:00, 价格 $1200]
事件记录: 观察结果: [UA123航班: 从 SFO 到 HND, 日期 下周二, 上午10:00, 价格 $1200]
LLM接收到的输入 (Prompt):
当前对话上下文:
用户输入: 预订一张下周二去东京的机票
代理思考: 我不确定如何继续。
代理动作: 准备执行 RespondToUserTool({'message': "'抱歉,您能再说清楚一些吗?'"})
观察结果: 消息 ''抱歉,您能再说清楚一些吗?'' 已成功发送给用户。
用户输入: 我从旧金山出发
代理思考: 已有出发地 (SFO旧金山),目的地 (HND东京羽田),和日期。可以使用 FlightSearchTool 搜索航班。
代理动作: 准备执行 FlightSearchTool({'origin': 'SFO', 'destination': 'HND', 'date': '下周二'})
观察结果: [UA123航班: 从 SFO 到 HND, 日期 下周二, 上午10:00, 价格 $1200]
你的任务是协助用户。请思考下一步,并决定要执行的动作。请严格按照以下格式回复:
Thought: [你的思考过程]
Action: [ToolName]([param1=value1, param2=value2...])
LLM生成的输出:
Thought: 找到一个航班。在预订前应与用户确认。
Action: RespondToUserTool(message="找到UA123航班: SFO->HND, 周二 10:00 AM, 价格 $1200。需要我为您预订吗?")
事件记录: 代理思考: 找到一个航班。在预订前应与用户确认。
事件记录: 代理动作: 准备执行 RespondToUserTool({'message': '找到UA123航班: SFO->HND'})
工具执行(RespondToUserTool): 正在发送给用户: '找到UA123航班: SFO->HND'
事件记录: 观察结果: 消息 '找到UA123航班: SFO->HND' 已成功发送给用户。
--- 用户对话回合结束 ---
--- ReAct 流程模拟结束 ---
Process finished with exit code 0
掌握在ADK中高效实现和调试ReAct循环,是构建实用代理的核心技能。
四、 探索复杂可能:思想之树 (ToT) 与 ADK 的高级编排
对于没有唯一最优解、需要广泛探索或进行复杂权衡的任务(如战略制定、创意生成),线性的ReAct可能受限。思想之树 (Tree of Thought, ToT) 提供了一种更强大的、模拟人类深度思考的模式。
- 核心机制: 通过生成多个并行的思考路径(分支)、评估这些路径的优劣、并选择性地深入或剪枝,系统性地探索解空间。
- ADK的实现途径(通常需要高阶技巧): ADK的模块化和可组合性为实现ToT提供了基础,但这通常需要开发者进行更精心的设计:
- 分层代理 (
AgentTool
): 可以设计一个“协调者”代理管理整个思想树的探索过程,并调用多个“探索者”子代理(通过AgentTool
),每个子代理负责一个或多个分支的思考。
- 并行执行 (
ParallelAgent
): ADK的工作流代理,如ParallelAgent
,可能被用来并行执行不同思考分支的探索(如果适用)。
- 状态管理 (
State
): 需要精细地使用State
来保存每个思考分支的独立上下文和历史,以便在不同分支间切换和回溯。
- 评估与剪枝逻辑 (
Tools
/Callbacks
): 需要实现自定义的评估工具或利用Callbacks
来评估每个分支的“价值”,并基于此决定是继续深入、切换分支还是彻底放弃(剪枝)。
- 资源考量: ToT的探索可能产生巨大的计算和Token消耗,ADK框架可能需要提供(或开发者需要自行实现)机制来控制探索的深度、广度以及资源使用。ADK的评估框架此时也至关重要,用于衡量不同ToT策略的成本效益。
实现高效、可控的ToT是当前AI代理领域的前沿探索,ADK提供的灵活性使其成为探索此类高级认知架构的有力平台。
五、 认知流的血脉:状态管理与记忆集成
无论是CoT、ReAct还是ToT,其认知过程的连贯性和有效性都高度依赖于状态管理和记忆。
- 会话状态 (
Session
&State
): 这是代理的“工作记忆”。
- 认知上下文: 为每一轮的
Thought
提供直接的上下文,包括最近的用户输入、LLM的思考记录、工具调用的Observation
等。SessionService
和Runner
确保了这些短期信息的及时传递。
- 中间结果缓存: 开发者可以利用
State
显式存储复杂的中间计算结果或推理结论,避免重复计算,并可能用于管理LLM的有限上下文窗口。ADK可能提供了如adk.sessions.ConversationContext
这样的高级工具来辅助管理传递给LLM的精确上下文。
- 长期记忆 (
Memory
): 这是代理的“知识库”和“经验档案”。
- 个性化引导: 在认知过程开始时(如ReAct的第一个
Thought
),从Memory
中检索用户偏好、历史交互模式或先前任务的结论,可以极大地提升决策的相关性和效率。
- 策略优化: 通过分析
Memory
中记录的成功/失败案例,代理的认知策略(如工具选择偏好、ReAct的规划方式)本身也可以进行迭代优化。
- ADK集成点: ADK提供了与外部
Memory
服务(如向量数据库、知识图谱)集成的接口,使得开发者可以在代理逻辑的关键位置(如通过Tools
或Callbacks
)查询和利用这些长期信息。
健壮的状态和记忆管理是实现持续智能、能够学习和适应的代理的基石。
六、 超越基础模式:反思、自我修正与评估驱动
随着代理能力的提升,简单的认知循环可能不足以保证高质量和高可靠性。业界正在探索更高级的模式:
- 反思与自我修正 (Reflection/Self-Correction): 让代理在完成一步或一个子任务后,能够回顾自己的思考过程(
Thought
)、行动(Action
)和结果(Observation
),评估其质量,并在必要时进行修正或重新规划。这可以通过特定的提示策略、专门的“反思”工具或多代理架构(一个代理执行,另一个代理评估和修正)在ADK中实现。
- 评估驱动开发 (Evaluation-Driven Development): 高级认知架构的设计和调优极其复杂。必须依赖系统的评估来指导。ADK内置的评估框架(如第二篇所述)允许开发者针对特定认知能力(如规划准确性、工具使用效率、多步任务完成率)创建测试集和指标,通过不断的测试-调整循环来优化代理的认知表现和可靠性。认知架构的设计不再是“一次性完成”,而是一个持续迭代优化的过程。
总结:以认知架构驱动下一代智能代理
掌握CoT、ReAct、ToT等高级认知架构,并理解它们如何在ADK框架内通过LLM、工具、状态和记忆系统协同工作,是构建真正智能代理的关键。ADK不仅仅是提供了一个运行环境,它通过其模块化设计、强大的工具链、灵活的编排能力和日益完善的内置支持(如规划器、评估框架),为这些复杂认知模式的工程化实现提供了坚实的支撑。
从简单的指令跟随,到能够进行深度思考、动态规划、探索决策并从中学习的智能体,ADK正助力开发者跨越这条鸿沟。精通这些认知架构及其在ADK中的应用,将使你能够构建出更可靠、更高效、更能解决现实世界复杂问题的下一代AI代理。未来的智能应用,将由这些具备高级认知能力的代理所驱动。