browser-use:AI 驱动的浏览器自动化神器——AI 智能体机制详解
一、核心组件
browser-use 的核心组件主要有以下几个:
Agent (智能体核心):作为系统的“大脑”和“指挥官”,负责接收任务、协调其他组件、执行决策循环(观察-思考-行动)。
Browser / BrowserContext (浏览器交互层):提供与浏览器(如 Playwright)进行交互的接口,负责执行动作(点击、输入等)并获取浏览器当前状态(URL、DOM 结构、截图等)。
Controller (动作控制器):管理和执行具体的浏览器动作(如 click_element, input_text, go_to_url 等)。它知道有哪些动作可用,以及如何执行它们。
MessageManager (消息管理器):负责构建和管理与 LLM 的对话历史。这是至关重要的,因为它需要将复杂的浏览器状态转换为 LLM 能理解的格式,并处理上下文长度限制。
LLM (大型语言模型):提供智能决策能力。Agent 将当前状态和任务信息通过 MessageManager 发送给 LLM,LLM 则返回下一步的思考和行动指令。
AgentHistory (历史记录器):记录智能体执行过程中的每一步状态、思考、行动和结果,用于调试、分析和回放。
Prompts (提示工程):精心设计的提示词,用于指导 LLM 的行为,确保其理解任务、遵循规则并以正确的格式输出结果。
视图 (views.py) 和工具 (utils.py):定义数据结构(如 AgentOutput, ActionResult, BrowserState)和提供辅助功能(如 JSON 解析、消息转换)。
AI 智能体在整个工作流中是核心调度者,起着至关重要的作用。
二、核心调度
Agent 类是整个工作流的核心调度者,负责调度全局。
生成一个 Agent 对象时,需要提供核心组件以及各种配置。
# Agent 初始化(简化示意)
class Agent(Generic[Context]):
def __init__(
self,
task: str,
llm: BaseChatModel,
browser: Browser | None = None,
browser_context: BrowserContext | None = None,
controller: Controller[Context] = Controller(),
settings: AgentSettings = AgentSettings(), # 包含各种配置
sensitive_data: Optional[Dict[str, str]] = None,
initial_actions: Optional[List[Dict[str, Dict[str, Any]]]] = None,
# ... 其他参数如回调函数、状态注入等
):
self.task = task
self.llm = llm
self.controller = controller
self.settings = settings
self.state = AgentState() # 智能体运行时状态
# 设置动态动作模型 (基于 Controller 注册的动作)
self._setup_action_models()
# 设置模型名称、工具调用方式等
self._set_model_names()
self.tool_calling_method = self._set_tool_calling_method()
# 初始化消息管理器
self._message_manager = MessageManager(...)
# 设置浏览器实例和上下文
self.browser, self.browser_context = self._setup_browser(browser, browser_context)
# ... 初始化回调、上下文、遥测等
需要关注 _setup_action_models 方法:它会根据 Controller 中注册的可用动作(如 click_element, input_text, done 等)动态地创建 Pydantic 模型 (ActionModel 和 AgentOutput)。这意味着智能体的能力(能执行哪些动作)是可扩展的,并且 LLM 的输出会被严格校验。
三、核心循环
step 方法是智能体执行任务的基本单元,体现了经典的“观察-思考-行动”循环。
# Agent step 方法(简化示意)
async def step(self, step_info: Optional[AgentStepInfo] = None) -> None:
try:
# 1. 观察 (Observe): 获取当前浏览器状态
state = await self.browser_context.get_state()
# 检查是否暂停或停止
await self._raise_if_stopped_or_paused()
# 2. 思考准备 (Prepare Thought): 将状态添加到消息历史
self._message_manager.add_state_message(state, self.state.last_result, step_info, self.settings.use_vision)
# (可选) 运行规划器 (Planner)
if self.settings.planner_llm and ...:
plan = await self._run_planner()
self._message_manager.add_plan(plan, position=-1) # 插入规划建议
# 3. 思考 (Think): 调用 LLM 获取下一步行动
input_messages = self._message_manager.get_messages()
model_output: AgentOutput = await self.get_next_action(input_messages)
# 移除临时的状态消息,避免历史记录过长
self._message_manager._remove_last_state_message()
# 将模型的输出(思考和行动)加入历史
self._message_manager.add_model_output(model_output)
# 4. 行动 (Act): 执行 LLM 返回的动作序列
result: list[ActionResult] = await self.multi_act(model_output.action)
self.state.last_result = result
# 处理成功/失败状态
self.state.consecutive_failures = 0
except Exception as e:
# 错误处理
result = await self._handle_step_error(e)
self.state.last_result = result
finally:
# 5. 记录 (Record): 将这一步的完整信息存入历史
if state:
metadata = StepMetadata(...)
self._make_history_item(model_output, state, result, metadata)
观察 (get_state): 获取当前页面的 URL、标题、标签页、DOM 结构(特别是可交互元素)以及可选的页面截图。
思考准备 (add_state_message): MessageManager 将观察到的信息,连同上一步的结果/错误,格式化成 LLM 能理解的消息(详见 MessageManager 部分)。
规划器 (_run_planner): 一个可选的高级功能。如果配置了 planner_llm,智能体会定期调用一个“规划师”LLM,对当前整体进展进行分析,并给出更高层次的策略建议。这就像在执行具体步骤时,定期向导师请教大方向。
思考 (get_next_action): 将构建好的消息列表发送给 LLM。LLM 返回一个结构化的 AgentOutput 对象,包含它的“思考”(对上一步的评估 evaluation_previous_goal、当前的记忆 memory、下一步的目标 next_goal)和“行动”(action 列表)。
行动 (multi_act): Agent 指挥 Controller 按照 AgentOutput 中的 action 列表顺序执行浏览器动作。
记录 (_make_history_item): 将这一步的所有信息(LLM 的思考、选择的动作、动作执行结果、浏览器状态快照、耗时等)打包成 AgentHistory 对象,存入 AgentState 的 history 列表中。
四、与 LLM 交互
get_next_action 方法负责调用 LLM。它会根据配置 (tool_calling_method) 选择不同的方式与 LLM 交互:
function_calling / Tool Calling: 利用 OpenAI 等模型原生的工具调用能力。AgentOutput 被定义为一个“工具”,LLM 会直接返回调用该工具所需的参数。这是推荐的方式,因为模型经过专门训练,效果通常最好。
json_mode: 强制 LLM 输出 JSON 格式的字符串,然后代码库再解析这个 JSON 字符串并验证其是否符合 AgentOutput 的结构。适用于支持 JSON 模式但不支持原生工具调用的模型。
raw: LLM 直接输出包含 JSON 的文本(可能混杂其他思考过程),代码库需要从中提取 JSON 部分(如 extract_json_from_model_output)并解析。这是兼容性最广但也最容易出错的方式,需要 LLM 严格遵循输出格式。
None (或 Langchain 的 with_structured_output): 使用 Langchain 提供的结构化输出功能,它会根据模型能力自动选择最佳策略(可能是工具调用、JSON 模式或内部提示调整)来获取符合 AgentOutput 结构的输出。
# get_next_action 简化逻辑
async def get_next_action(self, input_messages: list[BaseMessage]) -> AgentOutput:
# (可选) 转换消息格式以适应特定模型
input_messages = self._convert_input_messages(input_messages)
if self.tool_calling_method == 'raw':
output = self.llm.invoke(input_messages)
# 提取并解析 JSON
parsed_json = extract_json_from_model_output(str(output.content))
parsed = self.AgentOutput(**parsed_json)
elif self.tool_calling_method is None:
# 使用 Langchain 的 with_structured_output (自动选择方法)
structured_llm = self.llm.with_structured_output(self.AgentOutput, include_raw=True)
response = await structured_llm.ainvoke(input_messages)
parsed = response['parsed']
else: # 'function_calling' or 'json_mode'
# 使用 Langchain 的 with_structured_output (指定方法)
structured_llm = self.llm.with_structured_output(self.AgentOutput, include_raw=True, method=self.tool_calling_method)
response = await structured_llm.ainvoke(input_messages)
parsed = response['parsed']
# ... 处理解析结果,可能裁剪过多动作 ...
log_response(parsed) # 记录 LLM 的思考和决策
return parsed
五、执行流程
multi_act 方法负责按顺序执行 LLM 返回多个需要连续执行的动作(例如,填写用户名、填写密码、点击登录)
# multi_act 简化逻辑
async def multi_act(self, actions: list[ActionModel], ...) -> list[ActionResult]:
results = []
# 获取当前页面元素哈希作为基准
cached_selector_map = await self.browser_context.get_selector_map()
cached_path_hashes = set(e.hash.branch_path_hash for e in cached_selector_map.values())
for i, action in enumerate(actions):
# 如果动作需要元素索引,并且不是第一个动作
if action.get_index() is not None and i != 0:
# 获取新状态并比较元素哈希
new_state = await self.browser_context.get_state()
new_path_hashes = set(e.hash.branch_path_hash for e in new_state.selector_map.values())
# 如果出现新元素 (新哈希不在旧哈希集合中),则中断
if check_for_new_elements and not new_path_hashes.issubset(cached_path_hashes):
msg = f'页面在动作 {i}/{len(actions)} 后发生变化,中断序列'
logger.info(msg)
results.append(ActionResult(extracted_content=msg, include_in_memory=True))
break
# 执行单个动作
result = await self.controller.act(action, ...)
results.append(result)
# 如果动作完成任务、出错或已是最后一个动作,则结束
if results[-1].is_done or results[-1].error or i == len(actions) - 1:
break
# 等待一小段时间
await asyncio.sleep(self.browser_context.config.wait_between_actions)
return results
需要关注的是:在执行每个动作(除了第一个)之前,都会检查页面上的元素是否发生了变化(通过比较元素的路径哈希 branch_path_hash)。如果页面出现了新的元素,意味着之前的动作可能触发了动态加载、弹窗或其他变化,此时再按旧的元素索引执行后续动作可能会出错。因此,它会中断执行序列,让 Agent 在下一个 step 中基于新的页面状态重新决策。
想象你告诉朋友:“去冰箱拿牛奶,然后关上冰箱门,再把牛奶递给我。” 如果朋友打开冰箱门时,里面突然跳出一只猫,他可能就不会继续执行“关门”和“递牛奶”了,而是会先处理这个意外情况(猫!)。multi_act 中的检查机制就类似这个过程,确保智能体在环境变化时能及时停下,重新评估。
五、运行与控制
run 方法是执行整个任务的入口点。它在一个循环中调用 step,直到任务完成(is_done 状态为 True)、达到最大步数限制或连续失败次数过多。它还处理初始动作执行、最终的清理工作(关闭浏览器)以及可选的 GIF 生成。pause, resume, stop 方法则提供了对智能体执行流程的外部控制。