在实际 AI 应用开发中,构建一个能够理解用户意图、自主操作网页并完成复杂任务的智能体(AI Agent)是当前技术探索的前沿。这类网页 Agent 的核心挑战在于如何将大语言模型(LLM)的推理能力与浏览器环境的精确控制能力无缝衔接,实现从“说”到“做”的跨越。传统的自动化脚本僵硬且无法应对动态变化的网页结构,而单纯调用 API 的聊天模型又缺乏执行具体操作的能力。因此,一个能够理解自然语言指令、规划操作步骤、执行浏览器交互并验证结果的“上网办事 AI”系统,成为了提升自动化水平的关键。
本文将深入探讨如何从零开始构建一个简易但功能完整的网页操作 AI Agent。我们将以 Spring AI 作为与 LLM 交互的框架基础,模拟一个需要登录、查询、填写表单等操作的业务场景。通过拆解任务规划、动作执行、状态验证等核心模块,你将理解 AI Agent 的工作原理,并能够动手实现一个可运行的原型。本文适合有一定 Java/Spring Boot 基础,并对 AI 应用集成、自动化测试或智能流程自动化感兴趣的开发者。
1. 理解网页 AI Agent 的核心架构与工作流程
在开始编码之前,必须厘清一个网页 AI Agent 是如何工作的。它不是一个单一的模型,而是一个由多个组件协同工作的系统。
1.1 从用户指令到浏览器动作的转化链
用户的一句自然语言指令,如“帮我登录系统并查询上个月的订单”,需要被转化为一系列具体的、可执行的浏览器操作。这个转化过程通常遵循感知-规划-执行-验证的循环(ReAct 模式的一种体现)。
- 任务解析与规划:AI Agent 首先需要理解用户的终极目标,并将其分解为一系列原子化的子任务。例如,“登录系统”可分解为“打开登录页”、“定位用户名输入框”、“输入用户名”、“定位密码输入框”、“输入密码”、“定位并点击登录按钮”。规划器(通常是 LLM)需要理解网页操作的常见原子动作(如 click, type, navigate, extract_text 等)。
- 环境感知与状态获取:在执行每个动作前,Agent 需要知道当前网页的状态。这通常通过获取页面的 DOM 结构、可视元素信息或截图来实现。精简且结构化的页面信息(如关键元素的 ID、类名、文本内容)会被提供给 LLM,作为其决策的“眼睛”。
- 动作选择与参数生成:基于当前状态和子任务目标,LLM 需要决定下一步执行哪个原子动作,并生成该动作所需的精确参数。例如,决定执行
type动作,并指定参数selector: “#username”和text: “myUser”。 - 动作执行:由浏览器自动化驱动工具(如 Selenium、Playwright)接收并执行上一步生成的动作指令。
- 结果验证与循环:动作执行后,Agent 再次感知页面状态,判断子任务是否完成(例如,是否出现了“登录成功”的提示或页面跳转),并决定是继续下一个子任务,还是重新规划当前任务。
1.2 关键组件与技术选型
为了构建这个系统,我们需要选择合适的工具链:
- 大脑(LLM 接口):Spring AI。它提供了统一的 API 来接入多种大模型(如 OpenAI GPT、Azure OpenAI、Ollama 本地模型等),简化了对话、提示词工程和函数调用的集成。我们将用它来实现任务规划和动作选择。
- 手脚(浏览器自动化):Selenium或Playwright。两者都能驱动浏览器进行自动化操作。Playwright 在现代化、速度和稳定性方面更优,本文示例将使用 Playwright for Java。
- 协调中枢(Agent 核心逻辑):自定义的 Spring Boot 服务。它将串联 LLM 调用和浏览器操作,维护任务状态,处理异常循环。
注意:本文旨在阐述原理和提供可运行的示例,其性能、稳定性和处理复杂网页的能力无法与新闻中提到的商用级产品(如 Handoff)相提并论。后者通常集成了更强大的视觉模型(VLM)用于理解页面、更鲁棒的异常处理机制以及针对海量网站的训练数据。
2. 环境准备与项目初始化
我们将创建一个标准的 Spring Boot 项目,并集成 Spring AI 和 Playwright。
2.1 基础环境与工具
确保你的开发环境满足以下要求:
| 组件 | 要求 | 说明 |
|---|---|---|
| JDK | 17 或更高版本 | Spring Boot 3.x 的基线要求。 |
| Maven | 3.6+ 或 Gradle | 用于项目构建和依赖管理。 |
| IDE | IntelliJ IDEA 或 Eclipse | 推荐使用 IntelliJ IDEA,其对 Spring Boot 支持更好。 |
| 浏览器 | Chrome/Edge | Playwright 需要安装对应的浏览器驱动。 |
2.2 创建 Spring Boot 项目并配置依赖
使用 start.spring.io 或 IDE 创建新项目,选择以下依赖:
- Spring Web
- Spring AI (如果列表中有,否则需手动添加)
在pom.xml中,我们需要添加 Spring AI 和 Playwright 的依赖。由于 Spring AI 生态在快速演进,请根据官方文档使用最新稳定版本。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.0</version> <!-- 使用与Spring AI兼容的版本 --> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>web-ai-agent</artifactId> <version>0.0.1-SNAPSHOT</version> <name>web-ai-agent</name> <description>Demo project for Web AI Agent</description> <properties> <java.version>17</java.version> <spring-ai.version>0.8.1</spring-ai.version> <!-- 请检查最新版本 --> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring AI OpenAI 集成 (示例使用OpenAI API) --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> <version>${spring-ai.version}</version> </dependency> <!-- Playwright for Java --> <dependency> <groupId>com.microsoft.playwright</groupId> <artifactId>playwright</artifactId> <version>1.40.0</version> </dependency> <!-- Lombok 简化代码 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>2.3 配置 Spring AI 连接
在application.yml或application.properties中配置 Spring AI,这里以 OpenAI 为例。你需要一个有效的 OpenAI API Key。
# application.yml spring: ai: openai: api-key: ${OPENAI_API_KEY:your-api-key-here} # 建议使用环境变量 chat: options: model: gpt-4-turbo-preview # 或 gpt-3.5-turbo,根据需求选择 temperature: 0.1 # 降低随机性,使Agent行为更确定重要:将
your-api-key-here替换为你自己的 API Key,或通过环境变量OPENAI_API_KEY设置。出于安全考虑,切勿将真实密钥提交到代码仓库。
3. 构建网页 AI Agent 核心模块
我们的 Agent 将包含几个核心类:定义动作的工具类、描述页面状态的类、协调整个流程的 Agent 服务类。
3.1 定义原子动作(工具)
首先,我们定义 Agent 可以执行的一系列原子操作。这些操作将被封装成“工具”(Tools),供 LLM 通过函数调用(Function Calling)来调用。
// com.example.agent.tools.BrowserActions.java package com.example.agent.tools; import com.microsoft.playwright.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.tool.annotation.Tool; import org.springframework.stereotype.Component; import java.util.List; import java.util.stream.Collectors; @Component @Slf4j @RequiredArgsConstructor public class BrowserActions { private final Page page; // Playwright 页面对象,通过配置注入 @Tool(description = “导航至指定的URL”) public String navigateTo(String url) { log.info(“执行导航: {}”, url); page.navigate(url); return “已导航至: “ + url; } @Tool(description = “使用CSS选择器点击页面元素”) public String click(String selector) { log.info(“尝试点击: {}”, selector); try { page.locator(selector).first().click(); return “成功点击元素: “ + selector; } catch (PlaywrightException e) { return “点击失败,元素可能不存在或不可点击: “ + selector + “。错误: “ + e.getMessage(); } } @Tool(description = “使用CSS选择器向输入框输入文本”) public String type(String selector, String text) { log.info(“在 {} 中输入: {}”, selector, text); try { page.locator(selector).first().fill(text); return “已在 “ + selector + “ 中输入文本: “ + text; } catch (PlaywrightException e) { return “输入失败: “ + selector + “。错误: “ + e.getMessage(); } } @Tool(description = “获取当前页面可见的主要文本内容,用于理解页面状态”) public String getPageContent() { // 获取body内所有可见文本,可进行简化处理 String allText = page.locator(“body”).innerText(); // 截取前2000字符避免上下文过长,实际可根据模型限制调整 String simplifiedContent = allText.length() > 2000 ? allText.substring(0, 2000) + “...” : allText; log.info(“获取页面内容,长度: {}”, simplifiedContent.length()); return “当前页面主要内容: \n” + simplifiedContent; } @Tool(description = “使用CSS选择器获取特定元素的文本内容”) public String getElementText(String selector) { try { String text = page.locator(selector).first().innerText(); return “元素 “ + selector + “ 的文本是: “ + text; } catch (PlaywrightException e) { return “无法获取元素 “ + selector + “ 的文本: “ + e.getMessage(); } } @Tool(description = “获取当前页面的URL”) public String getCurrentUrl() { String url = page.url(); return “当前URL是: “ + url; } }@Tool注解是 Spring AI 提供的,它会将这个方法描述注册给 LLM,使 LLM 知道可以调用这些功能。方法的返回字符串将成为 LLM 下一步推理的依据。
3.2 配置 Playwright 运行环境
我们需要一个配置类来初始化 Playwright 的 Browser 和 Page 对象,并将其注入到 Spring 容器中。
// com.example.agent.config.PlaywrightConfig.java package com.example.agent.config; import com.microsoft.playwright.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import javax.annotation.PreDestroy; @Configuration public class PlaywrightConfig { private Playwright playwright; private Browser browser; @Bean @Scope(“prototype”) // 每次注入时创建新的Page,适合并发场景。这里为简单用单例。 public Page page() { if (playwright == null) { playwright = Playwright.create(); } if (browser == null) { // 使用Chromium,可配置为firefox或webkit browser = playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(false)); // 设为true则无头模式 } return browser.newPage(); } @PreDestroy public void close() { if (browser != null) { browser.close(); } if (playwright != null) { playwright.close(); } } }将setHeadless(false)改为true可以在服务器上无界面运行。开发阶段设为false便于观察 Agent 的操作过程。
3.3 实现 Agent 协调服务
这是最核心的部分,它负责接收用户指令,调用 LLM 进行规划,并管理工具执行的循环。
// com.example.agent.service.WebAgentService.java package com.example.agent.service; import com.example.agent.tools.BrowserActions; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.SystemPromptTemplate; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; @Service @Slf4j @RequiredArgsConstructor public class WebAgentService { private final ChatClient chatClient; private final BrowserActions browserActions; // 系统提示词,定义了Agent的角色、能力和规则 private final String SYSTEM_PROMPT = “”” 你是一个专业的网页操作AI助手。你的目标是根据用户的指令,通过操作浏览器来完成网页任务。 你可以使用的工具(动作)有: - navigateTo(url): 导航到指定网址。 - click(selector): 点击一个CSS选择器指定的元素。 - type(selector, text): 向一个CSS选择器指定的输入框输入文本。 - getPageContent(): 获取当前页面的主要文本内容,用于了解页面状态。 - getElementText(selector): 获取特定元素的文本。 - getCurrentUrl(): 获取当前页面URL。 操作规则: 1. 在决定下一步动作前,通常先使用`getPageContent`了解当前页面。 2. 使用CSS选择器来定位元素。选择器应尽量精确(如`#loginBtn`优于`button`)。 3. 每次调用工具后,我会把工具执行的结果反馈给你。 4. 根据反馈结果,判断当前子任务是否完成,并决定下一步。 5. 如果工具执行失败(返回错误信息),请分析原因并尝试其他策略(如使用不同的选择器)。 6. 当用户指令中的所有任务都完成,或者你判断无法继续时,用一段总结性的话回复用户,结束本次任务。 当前任务:{userInstruction} “””; public String executeTask(String userInstruction) { log.info(“开始执行任务: {}”, userInstruction); StringBuilder fullHistory = new StringBuilder(); // 初始化系统提示词 String systemMessage = new SystemPromptTemplate(SYSTEM_PROMPT) .createMessage(Map.of(“userInstruction”, userInstruction)) .getContents(); // 构建ChatClient,启用工具调用 var client = chatClient.prompt() .system(s -> s.text(systemMessage)) .advisors(new SimpleLoggerAdvisor()) // 可选,用于日志记录请求响应 .tools(browserActions) // 注册工具 .build(); // 初始获取页面状态(假设从某个起始页或about:blank开始) // 这里为了示例,我们让Agent自己决定第一步。更复杂的实现可以预设首页。 String initialThought = “我将开始执行任务:” + userInstruction + “。首先,我需要了解当前页面状态。”; fullHistory.append(“AI: “).append(initialThought).append(“\n”); // 与LLM进行多轮交互,模拟思考-行动循环 // 为了示例,我们限制最大交互轮数 int maxTurns = 10; ChatResponse response = null; for (int i = 0; i < maxTurns; i++) { log.info(“—— 第 {} 轮交互 ——”, i + 1); // 构建用户消息:包含历史对话和最新的工具执行结果 String userMessageForThisTurn = (i == 0) ? “开始任务。” : “请根据以上工具执行结果,继续下一步。”; if (fullHistory.length() > 0) { userMessageForThisTurn = fullHistory.toString() + “\n” + userMessageForThisTurn; } response = client.call(userMessageForThisTurn); // 处理响应 String aiResponse = response.getResult().getOutput().getContent(); fullHistory.append(“AI: “).append(aiResponse).append(“\n”); log.info(“AI 响应: {}”, aiResponse); // 检查响应中是否包含工具调用 // 在Spring AI中,工具调用的结果会自动附加到下一轮对话的上下文中。 // 我们这里简化处理:如果AI的响应看起来是在描述动作而非最终答案,并且没有检测到明显的最终结论,就继续循环。 // 更严谨的做法是解析ChatResponse中的ToolCall对象。 if (isTaskFinished(aiResponse)) { log.info(“任务完成或AI决定结束。”); break; } // 等待一小段时间,模拟人类操作间隔,也让页面有足够时间加载 try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } String finalResult = (response != null) ? response.getResult().getOutput().getContent() : “任务执行超时或未完成。”; log.info(“任务最终结果: {}”, finalResult); return finalResult; } private boolean isTaskFinished(String aiResponse) { // 简单的启发式规则:如果AI的回复是总结性、告知性话语,且不包含明显的工具调用意图,则认为任务结束。 // 这是一个非常简化的实现。生产环境需要更复杂的逻辑,例如检测LLM返回的特定结束标记。 String lowerResponse = aiResponse.toLowerCase(); return lowerResponse.contains(“任务完成”) || lowerResponse.contains(“已完成”) || lowerResponse.contains(“无法继续”) || lowerResponse.contains(“总结”) || (lowerResponse.contains(“成功”) && lowerResponse.length() < 150); // 简短的成功消息 } }3.4 创建 REST 控制器
提供一个简单的 HTTP 端点来触发 Agent 执行任务。
// com.example.agent.controller.AgentController.java package com.example.agent.controller; import com.example.agent.service.WebAgentService; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Map; @RestController @RequestMapping(“/api/agent”) @RequiredArgsConstructor public class AgentController { private final WebAgentService agentService; @PostMapping(“/execute”) public Map<String, String> executeTask(@RequestBody Map<String, String> request) { String instruction = request.get(“instruction”); if (instruction == null || instruction.isBlank()) { return Map.of(“error”, “Instruction cannot be empty”); } String result = agentService.executeTask(instruction); return Map.of(“result”, result); } }4. 运行验证与结果分析
4.1 启动应用并测试
- 确保
OPENAI_API_KEY环境变量已设置,或直接在application.yml中配置正确的 API Key。 - 运行 Spring Boot 主类
WebAiAgentApplication。 - 应用启动后,Playwright 会启动一个 Chromium 浏览器窗口(如果
headless设为false)。 - 使用
curl、Postman 或任何 HTTP 客户端向http://localhost:8080/api/agent/execute发送 POST 请求。
请求示例:
curl -X POST http://localhost:8080/api/agent/execute \ -H “Content-Type: application/json” \ -d ‘{“instruction”: “打开百度首页,在搜索框里输入‘Spring AI’,然后点击搜索按钮。”}’预期行为:你将看到浏览器自动完成以下操作:
- 导航到
https://www.baidu.com。 - 尝试定位搜索输入框(可能使用类似
#kw的选择器)。 - 输入 “Spring AI”。
- 尝试定位搜索按钮(可能使用类似
#su的选择器)并点击。 - 页面跳转到搜索结果页。
控制台日志示例:
开始执行任务: 打开百度首页,在搜索框里输入‘Spring AI’,然后点击搜索按钮。 —— 第 1 轮交互 —— 获取页面内容,长度: 1850 AI 响应: 当前页面是空白页(about:blank)。我需要先导航到百度首页。我将使用 navigateTo 工具。 —— 第 2 轮交互 —— 执行导航: https://www.baidu.com AI 响应: 已成功导航至百度首页。现在我需要找到搜索框并输入文本。我先获取一下页面内容来定位元素。 —— 第 3 轮交互 —— 获取页面内容,长度: 1250 AI 响应: 我看到页面了。搜索框的CSS选择器可能是 `#kw`。我将尝试向它输入‘Spring AI’。 —— 第 4 轮交互 —— 在 #kw 中输入: Spring AI AI 响应: 输入成功。现在我需要找到搜索按钮并点击。按钮的选择器可能是 `#su`。 —— 第 5 轮交互 —— 尝试点击: #su AI 响应: 点击成功。页面应该正在跳转到搜索结果。任务完成。我已成功在百度搜索了‘Spring AI’。 任务最终结果: 任务完成。我已成功在百度搜索了‘Spring AI’。4.2 结果分析与局限性
通过上述测试,我们验证了 Agent 的基本工作流程:理解指令 -> 规划步骤(导航->获取内容->定位输入框->输入->定位按钮->点击)-> 执行动作 -> 验证结果。
然而,这个简易 Agent 存在明显的局限性:
- 元素定位脆弱:完全依赖 LLM 从文本内容中猜测 CSS 选择器,极不可靠。百度首页的选择器是已知的,但对于未知网站,LLM 很难猜对。
- 状态判断简单:
isTaskFinished方法过于简单,无法准确判断复杂任务的完成状态。 - 无视觉感知:仅依赖页面文本,无法“看到”按钮、图标、验证码等视觉元素。
- 错误处理薄弱:工具调用失败后,仅将错误信息返回给 LLM,缺乏重试、回退等策略。
- 提示词工程初级:系统提示词可以优化,例如加入更详细的动作规范、网站特定知识(结构化数据)等。
5. 常见问题排查与优化方向
在实际运行中,你可能会遇到以下问题:
5.1 问题排查清单
| 问题现象 | 可能原因 | 检查与解决思路 |
|---|---|---|
应用启动失败,提示No such bean | Spring AI 或 Playwright 依赖未正确引入或版本冲突。 | 1. 检查pom.xml依赖版本兼容性。2. 运行 mvn clean compile查看编译错误。3. 确认 @SpringBootApplication主类位置正确。 |
| 调用 API 返回 401 错误 | OpenAI API Key 无效或未设置。 | 1. 检查application.yml中的spring.ai.openai.api-key。2. 确认环境变量 OPENAI_API_KEY已设置并生效。3. 在 OpenAI 平台检查 API Key 的余额和状态。 |
| 浏览器没有启动或瞬间关闭 | Playwright 浏览器配置问题。 | 1. 检查PlaywrightConfig中setHeadless(false)是否被设置。2. 检查系统是否安装了 Chromium/Chrome。 3. Playwright 首次运行会自动安装浏览器,可能需要网络。查看日志。 |
| Agent 执行失败,LLM 无法理解任务 | 提示词(Prompt)不够清晰或指令模糊。 | 1. 在WebAgentService的SYSTEM_PROMPT中增加更具体的例子。2. 确保用户指令是明确、可操作的网页任务。 3. 尝试更换更强大的模型(如 gpt-4)。 |
| Agent 卡住,不断重复获取页面内容 | isTaskFinished逻辑有缺陷,或 LLM 陷入循环。 | 1. 增加交互轮数限制maxTurns并记录日志。2. 优化 isTaskFinished方法,或让 LLM 在最终回复中必须包含特定结束符。3. 在提示词中强调“避免重复操作”。 |
| 元素定位失败,点击或输入不生效 | LLM 猜错了 CSS 选择器,或页面动态加载导致元素未就绪。 | 1. 在工具方法中加入更详细的错误日志和等待逻辑(如page.waitForSelector)。2. 考虑提供更强大的页面状态描述,如关键元素的 ID、Name 列表,而不仅仅是全文。 3.这是核心难点,见下文优化方向。 |
5.2 核心优化方向:增强元素定位能力
简易 Agent 最大的瓶颈是元素定位。以下是几种进阶方案:
方案一:提供结构化页面描述在getPageContent工具中,不返回纯文本,而是返回一个结构化 JSON,描述页面上所有可交互元素(按钮、输入框、链接)及其属性(id, name, class, text, type)。
@Tool(description = “获取当前页面的结构化可交互元素信息”) public String getPageStructure() { // 使用 Playwright 执行 JavaScript 来收集元素信息 Object result = page.evaluate(“”” () => { const elements = []; const interactives = document.querySelectorAll(‘input, button, a, select, textarea, [role=”button”]’); interactives.forEach(el => { elements.push({ tag: el.tagName.toLowerCase(), id: el.id, name: el.name, classes: Array.from(el.classList), type: el.type, text: el.innerText || el.value || el.placeholder, // 计算一个简单的基于位置和属性的选择器 selector: el.id ? `#${el.id}` : `[name=”${el.name}”]` || `${el.tagName}.${Array.from(el.classList)[0]}` || el.tagName }); }); return JSON.stringify(elements.slice(0, 50)); // 限制数量 } “””); return “可交互元素列表: “ + result.toString(); }然后,在系统提示词中要求 LLM 优先使用这些结构化信息中的selector字段。
方案二:集成视觉模型(VLM)这是商用级 Agent(如 Handoff)可能采用的方法。对页面进行截图,然后使用视觉语言模型(如 GPT-4V)来描述页面布局和可点击区域,甚至直接输出元素的坐标或选择器。这需要集成额外的 VLM API 和图像处理逻辑,复杂度较高。
方案三:混合定位策略结合多种方式:
- 首选固定选择器:为常见网站(如登录页)预配置关键元素的选择器映射。
- 回退到语义匹配:使用 LLM 分析元素文本(
innerText)和用户指令的语义相似度来选择最可能的元素。 - 最终使用坐标点击:如果以上都失败,可以描述区域让用户确认,或使用 VLM。
5.3 生产环境考量
如果计划将此类 Agent 用于更严肃的场景,必须考虑以下几点:
稳定性与容错:
- 超时与重试:为每个工具调用和 LLM 请求设置超时,并实现重试机制。
- 异常边界:定义清晰的异常类型(如元素未找到、网络超时、任务不可解),并制定恢复策略(如重置页面、重新开始子任务)。
- 状态持久化:对于长任务,需要保存执行状态,支持断点续跑。
性能与成本:
- LLM 调用优化:减少不必要的
getPageContent调用,使用更精简的页面描述。 - 模型选型:任务规划可以用强模型(如 GPT-4),简单的状态判断可以用便宜模型(如 GPT-3.5-Turbo)。
- 缓存:对常见页面的结构描述进行缓存。
- LLM 调用优化:减少不必要的
安全与权限:
- 指令过滤:严格校验用户指令,防止其要求访问非法网站或执行危险操作。
- 沙箱环境:在隔离的浏览器环境中运行 Agent,限制其访问本地文件系统或内部网络。
- 操作确认:对于敏感操作(如支付、删除),设计人工确认环节。
可观测性:
- 详细日志:记录每一轮 LLM 的请求响应、工具调用参数和结果、页面快照(或URL)。
- 链路追踪:为每个用户会话生成唯一 ID,便于追踪完整执行路径和排查问题。
构建一个鲁棒的网页 AI Agent 是一个系统工程,涉及提示词工程、大模型能力、浏览器自动化、计算机视觉和软件架构的深度融合。本文提供的简易实现揭示了其核心工作流程与挑战,可以作为你深入探索的起点。从解决一个具体的、定义良好的小任务(如自动填写某个固定表单)开始,逐步迭代其元素定位、状态判断和异常处理能力,是迈向更通用 Agent 的务实路径。