当前位置:首页 > 技术文章 > 正文内容

browser-use:AI 驱动的浏览器自动化神器——AI 智能体机制详解

zonemu2个月前 (08-28)技术文章20


一、核心组件

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 方法则提供了对智能体执行流程的外部控制。

相关文章

7 款最佳 Linux 桌面发行版,颜值天花板

一、elementary OS二、Deepin三、Pop!_OS四、Manjaro Linux五、KDE Neon六、Zorin OS七、Nitrux OS想必大家都知道三大常用操作系统:Linux、...

Vue3快速入门(vue3快速上手)

  1.核心语法  1. 1选项式和组合式的区别  Vue2的API设计是Options(选项)风格的。  Vue3的API设计是Composition(组合)风格的。  Options类型的 API...

HTML5+眼球追踪?黑科技颠覆传统手机体验

今天,iH5工具推出一个新的神秘功能——眼动追踪,可以通过摄像头捕捉观众眼球活动!为了给大家具体演示该功能的使用,我做了一个案例,供大家参考。实际效果如下:案例比较简单,就是通过眼动功能获取视觉焦点位...

一键看懂Html5,就这么简单(查看html的app推荐)

HTML5是WEB开发世界的一次重大的改变,事实上不管你是否喜欢,它都是代表着未来趋势。曾几何时,当HTML5出现在web端开发领域的时候,并没有引起太多人的注意,究其原因,一方面是它还没有被广泛的支...

12种JavaScript中最常用的数组操作整理汇总

数组是最常见的数据结构之一,我们需要绝对自信地使用它。在这里,我将列出 JavaScript 中最重要的几个数组常用操作片段,包括数组长度、替换元素、去重以及许多其他内容。1、数组长度大多数人都知道可...

不产生新的数组,删除数组里的重复元素

数组去重的方式有很多,我们可以使用 Set 去重、filter 过滤等,详见 携程&蘑菇街&bilibili:手写数组去重、扁平化函数 ,但三种解法(Set、filter、reduce...