
1. 项目概述为什么我们需要一个“长跑”测试场最近和几个做Web Agent网页智能体的朋友聊天大家普遍有个感觉现在的评测有点“应试教育”那味儿了。模型在几个精心设计的、步骤有限的网页任务上表现亮眼比如“登录邮箱”、“搜索商品”但一放到真实、复杂、需要多步决策的网页流程里比如“规划一次跨国旅行包括比价机票、预订有特定设施的酒店、并租一辆车”立马就露怯了。这就像让一个学生只做选择题得了高分但一写作文就抓瞎。我们缺的正是一个能模拟真实世界网页操作复杂度的“长跑”测试场。这就是“Odysseys”这个基准测试想解决的核心问题。“Odysseys”这个名字起得很妙它源自荷马史诗《奥德赛》寓意着漫长而充满挑战的旅程。这个基准测试的核心目标就是为Web Agents设立一系列“史诗级”的长期任务逼着它们去学习如何规划、记忆、纠错而不仅仅是执行单一步骤的命令。它关注的不是“能不能点对按钮”而是“能不能在纷繁复杂的网页迷宫中找到一条通往目标的可行路径并且高效地走完它”。对于任何希望将Web Agent投入实际应用——无论是自动化办公、电商比价、信息收集还是复杂流程填写——的开发者来说理解并优化Agent在“长视野任务”上的表现都是无法绕过的一课。2. 核心设计思路如何定义一场“奥德赛”构建一个有效的长视野任务基准远比设计几个孤立任务复杂。它需要一套系统性的设计哲学来确保评测既真实又公平。Odysseys的设计思路可以从以下几个维度来拆解。2.1 任务复杂度的多维构建长视野任务的核心特征是“长”但“长”不仅仅是步骤多。Odysseys从多个层面构建复杂度状态空间的广度与动态性一个简单的登录页面状态空间所有可能的页面元素和状态组合很小。而一个旅游预订网站包含了日期选择器、价格筛选、酒店设施勾选、用户评价过滤等多个交互模块其状态空间呈指数级增长。更关键的是网页状态是动态的选择不同的出发日期返回的航班列表和价格完全不同勾选“免费取消”可能使一些酒店选项消失。Agent必须能理解和适应这种动态变化。子目标间的依赖与耦合在“规划旅行”任务中子目标之间存在强依赖关系。例如你必须先确定航班日期才能有意义地搜索那几天的酒店。酒店的位置如靠近机场或市中心又可能影响租车的选择是否需要租几天。这种耦合要求Agent具备任务分解和规划能力而不能机械地线性执行指令。稀疏与延迟的奖励反馈在长任务中积极的反馈奖励往往是稀疏且延迟的。在完成最终预订并收到确认邮件之前Agent可能经历了数十次搜索、筛选、点击但中间步骤很难获得明确的“成功”信号。这要求Agent有更强的长期规划能力和基于不确定性的决策能力。常识与领域知识的必要性要完成“预订一家适合家庭入住、带有游泳池且评价在4.5星以上的酒店”Agent需要理解“适合家庭”可能意味着需要寻找“家庭房”或“连通房”选项“带有游泳池”是一个设施筛选条件而“4.5星以上”指的是用户评分。这些常识和领域知识是理解任务和与网页元素正确交互的基础。2.2 评估指标超越“成功率”在长视野任务中仅仅用最终“任务成功与否”作为指标是粗糙且信息量不足的。Odysseys引入了一套更精细的评估体系核心是“轨迹效率”。任务完成率最基础的指标衡量Agent是否能在一定步数内达到任务定义的最终成功状态。平均路径长度成功完成任务所花费的平均交互步骤如点击、输入、滚动数。这个数字越低说明Agent的决策越高效、越直接。与最优路径的偏离度通过人工标注或利用环境模型可以估算出一个任务的理论最优或接近最优的解决路径最短步骤。将Agent的实际轨迹与最优路径进行比较如计算编辑距离或步骤差可以量化其决策的“绕远”程度。关键子目标达成率分析在任务轨迹中各个必须的子目标如“成功搜索航班”、“筛选酒店”、“填写旅客信息”是否被正确、按顺序地完成。这有助于定位Agent是在哪个环节出了问题。无效操作比率统计Agent执行了多少次无效或冗余的操作例如点击了没有反应的元素、在错误的输入框重复输入、进行了不必要的页面刷新等。这个比率直接反映了Agent对网页状态的理解能力和决策质量。注意评估长视野任务时必须在一个无头浏览器或网页模拟环境中进行并全程记录完整的交互轨迹包括DOM快照、操作动作、时间戳。事后分析这些轨迹日志是计算上述指标的基础。直接让Agent在真实网站上进行大规模测试是不道德且不稳定的。2.3 环境构建的真实性与可复现性为了保证评测的公平和可复现Odysseys通常不会直接使用实时、在线的生产网站因为网站UI和内容随时会变。而是采用以下两种方式之一交互式模拟环境使用像WebShop、MiniWoB或自定义的Playwright/Selenium脚本搭建一个高度仿真的网页任务环境。环境完全可控状态可重置且不会对真实网站造成负载。静态网站镜像与重放对目标网站进行镜像并利用工具如mitmproxy记录或直接保存HTML资源构建一个本地的、静态的交互环境。通过JavaScript注入来模拟后端API的响应使Agent感觉在与真实网站交互但实际上所有数据都是确定性的。这是平衡真实性与可复现性的常用折中方案。3. 实操构建手把手搭建一个简易的“Odysseys”评测环境理论说了很多我们来点实际的。假设我们现在要为一个“旅游产品比价与预订”Agent构建一个评测任务。我们不追求像完整Odysseys基准那样庞大但会实现其核心思想。3.1 环境准备与工具选型我们选择Playwright作为浏览器自动化工具因为它对现代Web技术支持好速度快且API简洁。同时我们会用FastAPI快速搭建一个模拟的旅游预订网站后端。# 创建项目目录并初始化 mkdir odysseys-eval-demo cd odysseys-eval-demo python -m venv venv source venv/bin/activate # Windows: venv\Scripts\activate # 安装核心依赖 pip install playwright fastapi uvicorn pydantic playwright install chromium # 安装浏览器驱动3.2 构建模拟任务网站我们创建一个简单的模拟网站包含航班搜索和酒店搜索两个有耦合关系的页面。simulated_site.py:from fastapi import FastAPI, Request from fastapi.responses import HTMLResponse from pydantic import BaseModel import uvicorn app FastAPI() # 模拟数据库 flights_db [ {id: 1, date: 2024-06-01, from: 北京, to: 上海, price: 650}, {id: 2, date: 2024-06-01, from: 北京, to: 上海, price: 720}, {id: 3, date: 2024-06-02, from: 北京, to: 上海, price: 600}, ] hotels_db [ {id: 1, city: 上海, date: 2024-06-01, name: 外滩酒店, price: 800, pool: True}, {id: 2, city: 上海, date: 2024-06-01, name: 浦东公寓, price: 500, pool: False}, ] # 存储会话状态模拟Agent需要记忆的信息 user_session {} app.get(/, response_classHTMLResponse) async def home(): return htmlbody h1模拟旅行预订网站/h1 p任务请预订一张2024-06-01从北京到上海的机票然后预订当天上海一家带游泳池的酒店。/p a href/flights1. 先搜索航班/a /body/html app.get(/flights, response_classHTMLResponse) async def flight_search(): html htmlbody h2航班搜索/h2 form action/flights/results methodget 出发日期: input typedate namedate value2024-06-01br 出发城市: input typetext namefrom value北京br 到达城市: input typetext nameto value上海br button typesubmit搜索航班/button /form /body/html return html app.get(/flights/results, response_classHTMLResponse) async def flight_results(date: str, from_city: str, to_city: str): filtered [f for f in flights_db if f[date] date and f[from] from_city and f[to] to_city] if not filtered: return HTMLResponse(p未找到航班。/pa href/flights返回/a) options_html .join([fli航班{f[id]}: {f[from]} - {f[to]} 价格:{f[price]}元 a href/book/flight/{f[id]}选择/a/li for f in filtered]) # 关键点将搜索条件存入会话供酒店搜索使用 user_session[trip_date] date user_session[destination] to_city return f htmlbody h2航班搜索结果/h2 ul{options_html}/ul p会话信息已记录日期{date}, 目的地{to_city}/p a href/hotels2. 继续搜索酒店/a /body/html app.get(/book/flight/{flight_id}) async def book_flight(flight_id: int): flight next((f for f in flights_db if f[id] flight_id), None) if not flight: return HTMLResponse(p航班不存在。/p) user_session[booked_flight] flight_id return HTMLResponse(f p已成功选择航班 {flight_id}。/p a href/hotels下一步预订酒店/a ) app.get(/hotels, response_classHTMLResponse) async def hotel_search(): # 检查是否已选择航班子目标依赖 if booked_flight not in user_session: return HTMLResponse(p请先选择航班。/pa href/flights返回航班搜索/a) date user_session.get(trip_date, 2024-06-01) city user_session.get(destination, 上海) html f htmlbody h2酒店搜索 (目的地: {city}, 日期: {date})/h2 form action/hotels/results methodget 城市: input typetext namecity value{city}br 入住日期: input typedate namedate value{date}br 需要游泳池: input typecheckbox namehas_poolbr button typesubmit搜索酒店/button /form /body/html return html # 后续酒店结果和预订页面类似此处省略... # 最终成功页面是完成酒店预订。 if __name__ __main__: uvicorn.run(app, host127.0.0.1, port8000)这个模拟网站虽然简单但已经包含了长视野任务的关键要素多页面流程首页 - 航班搜索 - 航班结果/选择 - 酒店搜索 - 酒店结果/选择 - 成功页。状态依赖访问/hotels页面会检查booked_flight是否存在于会话中强制了任务顺序。信息传递航班搜索的条件日期、目的地通过user_session传递给酒店搜索页面作为默认值。条件过滤酒店搜索提供了“需要游泳池”的复选框对应任务中的约束条件。3.3 实现一个基础Web Agent并运行评测接下来我们实现一个简单的、基于规则也可替换为LLM驱动的Agent来尝试完成这个任务并记录其轨迹。simple_agent.py:from playwright.sync_api import sync_playwright import time from urllib.parse import urljoin BASE_URL http://127.0.0.1:8000 class SimpleWebAgent: def __init__(self): self.playwright sync_playwright().start() self.browser self.playwright.chromium.launch(headlessFalse) # 设为True可无头运行 self.context self.browser.new_context() self.page self.context.new_page() self.trajectory [] # 记录轨迹[(action, url, timestamp)] def log_action(self, action): self.trajectory.append({ action: action, url: self.page.url, timestamp: time.time() }) print(f[Agent] {action}) def run_task(self): 执行预定义的长视野任务 try: # 步骤1: 访问首页理解任务 self.page.goto(BASE_URL /) self.log_action(goto_home) time.sleep(1) # 步骤2: 点击航班搜索链接 self.page.click(text1. 先搜索航班) self.log_action(click_flight_search_link) time.sleep(1) # 步骤3: 提交航班搜索表单使用默认值 self.page.click(button[typesubmit]) self.log_action(submit_flight_search_form) time.sleep(1) # 步骤4: 选择第一个航班 self.page.click(a[href^/book/flight/]) self.log_action(click_first_flight_option) time.sleep(1) # 步骤5: 点击“下一步预订酒店” self.page.click(text下一步预订酒店) self.log_action(click_proceed_to_hotels) time.sleep(1) # 步骤6: 在酒店搜索页面勾选“需要游泳池”并提交 self.page.check(input[namehas_pool]) self.log_action(check_swimming_pool) self.page.click(button[typesubmit]) self.log_action(submit_hotel_search_form) time.sleep(1) # 步骤7: 选择第一个酒店假设页面有 # 注意这里我们的模拟网站没有完全实现酒店结果页所以这是一个会失败的操作 # 这正好用于演示轨迹记录和错误处理 hotel_links self.page.locator(a[href^/book/hotel/]) if hotel_links.count() 0: hotel_links.first.click() self.log_action(click_first_hotel_option) success True else: print([Agent] 未找到酒店选项任务可能未完全实现或Agent路径错误。) success False self.log_action(hotel_options_not_found) return success, self.trajectory except Exception as e: print(f[Agent] 运行出错: {e}) self.log_action(ferror: {str(e)}) return False, self.trajectory finally: self.page.close() self.context.close() self.browser.close() self.playwright.stop() if __name__ __main__: # 先启动模拟网站在另一个终端运行 uvicorn simulated_site:app print(确保模拟网站已在 http://127.0.0.1:8000 运行...) agent SimpleWebAgent() success, trajectory agent.run_task() print(f\n 任务完成状态: {成功 if success else 失败/未完成} ) print( 交互轨迹 ) for i, step in enumerate(trajectory): print(f{i1}. [{step[timestamp]:.2f}] {step[action]} {step[url]}) # 计算简单指标 print(f\n 基础评估指标 ) print(f总交互步骤数: {len(trajectory)}) print(f任务成功: {success})这个Agent非常基础它只是按我们预设的固定路径执行。但在真实Odysseys评测中Agent应该接收自然语言指令如“预订一张2024-06-01从北京到上海的机票然后预订当天上海一家带游泳池的酒店”然后自主规划并执行。你可以用LLM如GPT-4、Claude驱动这个Agent让它根据当前页面HTML内容决定下一步操作。3.4 评估与轨迹分析运行上述代码后我们会得到一份详细的交互轨迹。基于这份轨迹我们可以进行手动或自动化的评估轨迹效率分析我们的规则Agent走了7步。但如果一个更智能的Agent可能会在航班结果页直接点击“继续搜索酒店”的链接而不是返回再点击可能只需要6步。我们可以计算其与“最优路径”的偏差。无效操作检测检查轨迹中是否有重复刷新、点击无效元素、在错误页面进行表单提交等操作。在我们的简单例子里没有但复杂的Agent常犯这类错误。子目标达成验证子目标1选择航班轨迹中包含click_first_flight_option且后续成功进入了酒店页面说明会话状态正确达成。子目标2搜索带泳池酒店轨迹中包含check_swimming_pool达成。子目标3完成酒店预订由于模拟网站未完全实现此子目标失败。这反映了任务环境完整性的重要性。4. 挑战、常见问题与进阶思考在实际构建和运行Odysseys类基准测试时你会遇到一系列挑战。4.1 主要技术挑战网页状态的表示与理解如何让Agent“看懂”一个复杂的、动态的网页简单用HTML文本喂给LLM会丢失大量视觉和交互上下文。解决方案包括简化DOM使用工具如BeautifulSoup或专门库过滤掉无关的脚本、样式、装饰性元素提取语义化的结构。视觉感知结合屏幕截图使用多模态模型如GPT-4V来理解页面布局和元素功能。可访问性树利用浏览器的可访问性树它提供了更简洁、语义化的页面元素描述。动作空间的复杂性网页交互动作点击、输入、滚动、选择等如何定义动作参数点击哪个坐标或元素如何确定通常需要将动作离散化例如将页面可交互元素按钮、链接、输入框通过某种方式编码为候选列表让Agent选择索引。长上下文与记忆在长达数十步的任务中Agent如何记住之前做了什么、看到了什么需要设计有效的记忆机制例如摘要记忆定期用LLM总结之前的交互历史和当前状态。外部记忆体使用向量数据库存储历史页面关键信息供后续检索。显式子目标栈让Agent显式地维护一个待完成的子目标列表。4.2 评测中的常见陷阱过拟合特定网站结构Agent可能通过硬编码或学习到某个测试网站特有的元素ID或CSS选择器来“作弊”而不是真正理解任务。解决方案是使用多个不同设计但功能相似的网站来测试同一类任务或者对同一网站的HTML结构进行随机扰动如改变class名、包装层级。评估指标的片面性只关注最终成功率可能鼓励Agent采取“穷举式”或“暴力”策略虽然最终能成功但效率极低。必须结合轨迹效率指标进行综合评估。模拟环境与真实环境的鸿沟在模拟环境中表现良好的Agent在真实网站上可能寸步难行因为真实网站的JavaScript更复杂、网络延迟不确定、会有弹窗和验证码等。因此基准测试的最终验证阶段必须在受控的真实网站环境如测试沙盒或特定合作网站中进行小规模测试。4.3 从Odysseys基准到实际应用构建或使用Odysseys这样的基准最终是为了打造更强大的实用Web Agent。在这个过程中有几个经验性的建议从简单任务开始迭代不要一开始就挑战最复杂的任务。先让Agent可靠地完成“登录”、“简单搜索”等原子操作再逐步组合成更长的流程。每增加一个复杂度维度如状态依赖、条件分支都要仔细测试和调整。设计鲁棒的错误处理与恢复机制长任务中出错是常态。Agent必须能检测到异常如元素未找到、页面未按预期加载并执行恢复策略例如刷新页面、回退到上一步、或向监督系统请求帮助。引入人类反馈与强化学习纯粹的模仿学习或指令跟随可能不足以应对复杂情况。可以考虑在关键决策点引入人类反馈或者使用强化学习将“任务完成”和“步骤效率”作为奖励信号让Agent在探索中学习更优的策略。关注“可解释性”一个黑盒Agent即使成功了如果不知道它为什么这么决策出了问题也很难调试。要求Agent在每一步输出其决策的“思考过程”Chain-of-Thought或者记录其对当前页面和任务的理解对于开发和运维至关重要。长视野网页任务评测是一个正在快速发展的领域Odysseys代表了朝着更真实、更严谨评估方向的重要努力。对于开发者而言理解其设计理念亲手搭建一个简单的测试环境是深入理解Web Agent能力边界和提升其实际应用价值的最佳途径。真正的挑战不在于让Agent在几个测试用例上获得高分而在于让它具备在开放、动态、复杂的真实网络世界中稳健完成复杂目标的能力。这条路很长但每一步都值得深耕。