ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

StateAct:基于程序状态的长视野计算机智能体框架设计与实践

StateAct:基于程序状态的长视野计算机智能体框架设计与实践 1. 项目概述从像素到程序状态的范式跃迁如果你尝试过让一个大语言模型LLM去操作电脑比如让它“打开浏览器搜索最近的咖啡店然后把地址复制到记事本里”你大概率会得到一个充满想象力但毫无用处的回答。传统的基于视觉像素的智能体就像蒙着眼睛在操作电脑它只能通过“看”屏幕截图来猜测当前状态每一步操作都充满了不确定性错误会不断累积任务稍微长一点就彻底跑偏了。这正是“StateAct”这个项目要解决的核心痛点它提出在让智能体“看到”像素之前应该先让它“理解”程序状态。简单来说StateAct是一种为长视野计算机使用智能体设计的框架其核心思想是优先利用程序的内在状态Program State而非仅仅依赖外部的像素渲染Pixels来驱动智能体的决策与行动。想象一下你作为人类操作电脑你不仅看到窗口的图片你更“知道”当前焦点在哪个输入框、浏览器打开了哪个标签页、记事本里光标在第几行第几列。这些就是“程序状态”。StateAct试图将这些状态直接、结构化地暴露给智能体从根本上提升其在复杂、多步骤计算机任务中的可靠性、准确性和效率。这个方向之所以火热与几个趋势密不可分。首先是智能体Agents的爆发从AutoGPT到Devin大家迫切希望AI能真正替代人类完成一些重复性电脑工作。其次是评测基准如OSWorld的出现它提供了真实、可复现的计算机任务环境来公平地衡量智能体水平。最后像“chimera”这样的多智能体服务框架以及“actor-attention-critic”等多智能体强化学习技术的演进都为构建更复杂、协同的计算机使用智能体提供了底层支持。StateAct正是在这个背景下试图从“感知”层面进行一次根本性的升级为长视野任务打下坚实基础。它适合所有对AI智能体、自动化脚本、RPA机器人流程自动化进阶感兴趣的研究者、开发者甚至是希望理解下一代人机交互可能性的产品经理。2. StateAct核心设计思路为何状态优于像素要理解StateAct的设计我们必须先深入剖析基于像素的传统方案为何在长视野任务中步履维艰。2.1 像素模式的根本瓶颈基于像素的智能体其工作流程可以概括为“截图-理解-决策-执行”循环。智能体接收当前屏幕的RGB像素阵列一张图片通过一个视觉模型如ViT来理解图片内容识别出按钮、文本框、图标等元素然后决定点击哪里或输入什么最后通过模拟鼠标键盘执行操作。这个模式存在几个致命缺陷信息密度低且噪声高一张1080p的截图包含超过200万个像素点但真正对决策有用的信息如某个按钮是否可点击、某个文本框内的具体文字只占其中极小一部分。大量无关的UI装饰、背景图片都成了干扰噪声。理解代价昂贵且易出错每一帧都需要运行一次庞大的视觉模型进行场景解析计算成本极高。更重要的是视觉识别永远存在误差率可能把“提交”按钮误认为“取消”或者无法识别出特定字体、特殊样式的UI元素。状态信息缺失像素无法直接传达许多关键状态。例如一个列表项是否被选中一个下拉菜单是否展开一个输入框是否有输入验证错误这些状态往往通过颜色、图标等视觉提示间接表达智能体需要“猜”而人类一眼就能通过程序接口直接获知。长视野下的误差累积在需要几十甚至上百步操作的任务中每一步微小的识别或决策错误都会传递并放大。第一步点错了地方后续所有操作都将基于一个错误的环境状态进行导致任务彻底失败。2.2 程序状态的优势与可行性程序状态指的是应用程序和操作系统内部维护的、描述其当前情形的一系列结构化数据。例如窗口管理当前活动窗口的句柄、标题、位置、大小Z-order窗口叠放次序。UI控件树一个包含所有按钮、文本框、列表等控件的层次化结构每个控件有其类型、名称、状态是否启用、是否可见、是否被选中、值文本框内容、复选框是否勾选等属性。系统状态剪贴板内容、当前输入焦点、运行中的进程列表、文件系统路径等。应用程序特定状态浏览器的当前URL、DOM结构代码编辑器的打开文件、光标位置、语法高亮状态数据库客户端的连接状态和查询结果集。StateAct的核心思路就是通过一系列工具后文详述直接抓取这些结构化状态并将其以JSON、XML或特定Schema的形式提供给智能体。这带来了颠覆性优势信息精准、结构化智能体直接拿到“submit_button.enabled true”或“current_url ‘https://...‘”无需猜测。推理成本骤降处理一段JSON文本比分析一张高清图片对LLM来说要轻松几个数量级响应速度更快且更节省API成本如果使用云端LLM。抗视觉变化能力强无论用户换了主题、调整了窗口大小还是使用了不同的字体只要程序逻辑不变其状态接口输出的结构化信息就是稳定的。这极大地提升了智能体的泛化能力。支持更复杂的策略有了精确的状态智能体可以更容易地实现条件判断“如果登录成功则跳转到下一步否则重试”、循环操作“遍历列表中的每一项并处理”等复杂逻辑。注意这并非要完全抛弃像素。在某些场景下视觉信息仍是必要的补充例如验证码识别、处理完全自定义绘制的控件游戏界面、或者进行基于图像的验证确保操作结果与预期截图匹配。StateAct倡导的是“状态优先视觉补充”的混合范式。2.3 StateAct的架构蓝图基于以上思路一个典型的StateAct智能体架构可能包含以下层次状态感知层负责从操作系统和应用程序中提取程序状态。这可能需要用到操作系统API如Windows的UI Automation、macOS的Accessibility API、浏览器DevTools Protocol、特定应用的脚本接口如Office COM、Photoshop Scripting等。状态抽象与编码层将原始状态数据转化为智能体易于理解的统一表示。例如将复杂的UI树简化为一个关键的控件列表并为每个动作点击、输入生成一个唯一的、基于状态的定位符如xpath或accessibility_id而不是屏幕坐标。智能体核心层通常是一个LLM。它接收当前的状态描述、任务历史以及目标指令然后输出下一个要执行的动作动作类型 状态定位符 参数。动作执行层将智能体输出的抽象动作翻译成具体的操作系统事件模拟鼠标点击、键盘输入并调用相应的API执行。混合验证层可选在执行关键动作前后捕获屏幕像素使用轻量级视觉模型进行结果验证或异常检测作为安全兜底。这个架构的核心是让LLM在一个丰富、准确、结构化的状态空间中进行推理和规划从而显著提升其在长视野、多步骤的计算机任务中的表现。3. 核心组件解析与实操要点实现一个StateAct风格的智能体关键在于构建高效、稳定的状态感知与动作执行管道。下面我们拆解几个核心组件。3.1 状态感知如何获取程序内部信息获取程序状态是第一步也是最需要“因地制宜”的一步。不同平台、不同应用的方法差异很大。1. 操作系统级无障碍API这是最通用和推荐的方式。Windows:UI Automation (UIA)是微软官方的无障碍框架。通过pywinauto、uiautomation或直接使用Microsoft UI Automation Client库你可以获取到整个桌面的控件树。每个控件都有ControlType、Name、AutomationId、BoundingRectangle以及丰富的属性如IsEnabled,IsOffscreen,Value。# 示例使用uiautomation库查找记事本编辑框并获取文本 import uiautomation as auto notepad_window auto.WindowControl(searchDepth1, ClassNameNotepad) edit_control notepad_window.EditControl() current_text edit_control.GetValuePattern().Value print(f记事本内容: {current_text})macOS:Accessibility API (AXAPI)提供类似功能。通过pyobjc或atomac库可以调用。苹果系统的应用对无障碍支持通常很好。Linux: 通常依赖AT-SPI(Assistive Technology Service Provider Interface)。pyatspi库是一个选择但生态和易用性上可能不如Windows和macOS。2. 浏览器自动化与DevTools Protocol对于Web任务这是黄金标准。通过Chrome DevTools Protocol (CDP)你可以获得近乎完美的状态感知。获取完整DOM可以拿到结构化的HTML文档。查询元素状态可以直接执行JavaScript来获取元素的任何属性、样式、甚至监听事件。网络活动监控可以捕获HTTP请求/响应这对于判断页面加载是否完成、API调用是否成功至关重要。工具puppeteer(Node.js)、playwright(跨语言) 或selenium(配合CDP) 都提供了高级API来利用这些能力。# 示例使用Playwright获取元素状态 from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessFalse) page browser.new_page() page.goto(https://example.com) # 获取输入框的值和禁用状态 input_selector #search-box value page.input_value(input_selector) is_disabled page.is_disabled(input_selector) print(f搜索框内容: {value}, 禁用: {is_disabled})3. 应用程序特定接口对于专业软件如Adobe套件、Microsoft Office、IDE它们往往提供自己的脚本或自动化接口。COM (Windows) Office应用程序通过COM暴露了极其丰富的对象模型。你可以用pywin32库来操作Word文档、Excel单元格。AppleScript (macOS) 控制Mac应用的传统方式。内部API/插件 一些软件支持插件或内置脚本引擎如Photoshop的JavaScript AutoCAD的LISP。实操心得在项目初期优先选择支持良好的平台和应用如现代浏览器、标准桌面应用。混合使用多种感知方式是常态。一个智能体可能同时使用UIA处理主窗口、用CDP处理内嵌浏览器组件、再用COM处理一个弹出的Office对话框。建立一套状态信息的融合与优先级仲裁机制是关键。3.2 状态抽象与编码给智能体“说人话”直接从API获取的状态数据可能非常冗杂。一个窗口可能有成百上千个控件节点。我们需要将其抽象、过滤、编码成对LLM友好的提示词Prompt。1. 关键信息提取相关性过滤只提取与当前任务可能相关的控件。例如如果任务是填写表单那么只关心输入框、按钮、下拉菜单可以忽略装饰性图片、静态文本标签除非标签是操作的依据。关键属性选择对于每个控件选择最有辨识度和操作意义的属性。通常包括control_type(按钮、文本框)、name/label(显示文本或AccessibleName)、state(enabled/disabled, checked)、value(当前值)、unique_id(如automation_id或xpath)。层次结构扁平化或简化过于深的UI树可能让LLM困惑。有时可以将其扁平化为一个列表并标注关键父子关系如“提交按钮位于表单容器内”。2. 统一动作定位符智能体需要一种方式来唯一指定要操作的对象。基于像素的坐标(x, y)是脆弱且不稳定的。StateAct应使用基于状态的定位符语义化ID优先使用automation_id、name这类开发者为控件赋予的标识。XPath在Web和某些XML化的UI树中非常强大和精确。CSS Selector主要用于Web。基于关系的描述当唯一ID缺失时可以使用如“名为‘用户名’的文本框下方的第一个按钮”。在给LLM的提示中我们应该提供一份当前可操作元素的清单每个元素都附带其定位符。当前可操作元素 1. [文本框] 名称: “用户名”, id: “user”, 值: “”, 状态: 可编辑。定位符: iduser 2. [文本框] 名称: “密码”, id: “pass”, 值: “”, 状态: 可编辑。定位符: idpass 3. [按钮] 名称: “登录”, id: “login-btn”, 状态: 可用。定位符: idlogin-btn这样LLM就可以输出如{action: type, target: iduser, value: myusername}的指令。3. 历史状态与差分更新为了支持长视野任务智能体需要上下文。我们不应在每一步都将全部状态塞给LLM那样会浪费大量Token。有效做法是首次提供完整快照。后续步骤提供状态差分只告诉LLM自上次动作后哪些状态发生了变化例如“‘登录’按钮变为不可用状态”“页面跳转新页面包含以下元素...”。维护一个任务历史包含之前执行的动作序列及其导致的状态变化摘要。3.3 智能体核心提示工程与动作规划有了好的状态表示下一步是设计LLM的提示使其能有效利用这些信息进行规划。1. 系统提示设计系统提示需要明确智能体的角色、可用动作集、状态描述格式以及输出格式规范。你是一个计算机操作智能体。你的目标是通过执行基本操作来完成用户指令。 你每轮会收到当前的“程序状态”描述。请基于状态和任务历史决定下一步做什么。 可用动作click(target), type(target, text), press_key(key_name), scroll(direction), wait(seconds), done()。 状态描述格式为元素列表每个元素包含type, name, id, state, locator。 你必须以严格的JSON格式回复且只包含一个动作。格式{action: ..., target: ..., value: ...}。 当前任务{用户指令}2. 思维链与子目标分解对于复杂任务期望LLM一步规划到底是不现实的。需要在提示中鼓励其进行思维链推理或显式的子目标分解。请按以下步骤思考 1. 理解最终目标。 2. 分析当前状态与目标的差距。 3. 规划接下来最关键的1-2个步骤。 4. 输出对应动作。 例如任务“保存文档到桌面” 思考最终目标是文件保存。当前状态是文档已打开。差距是需要触发保存对话框并选择路径。下一步是点击“文件”菜单。 输出{action: click, target: name文件}3. 处理模糊与异常状态感知不可能100%完美。LLM可能会遇到状态描述不清或动作失败的情况。提示中需要包含异常处理指引例如“如果目标元素未找到请尝试描述性定位或返回wait动作”。注意事项LLM的上下文长度有限。对于极长的任务链你需要设计一套机制来总结或选择性遗忘早期历史或者采用分层规划策略让一个“经理”智能体负责高级目标分解多个“员工”智能体负责短期内的精细操作。4. 构建一个基础StateAct智能体的实操流程让我们以一个具体的例子来串联上述概念构建一个能“在Web版日历中创建新事件”的智能体。我们将使用Playwright进行状态感知因为它能提供完美的DOM状态并搭配一个LLM这里假设使用OpenAI API作为大脑。4.1 环境准备与依赖安装首先创建一个新的Python环境并安装必要库。# 创建并激活虚拟环境可选 python -m venv stateact_env source stateact_env/bin/activate # Linux/macOS # stateact_env\Scripts\activate # Windows # 安装核心库 pip install playwright openai # 安装Playwright的浏览器驱动 playwright install chromium这里选择Playwright是因为它跨平台、支持多浏览器且CDP集成度极高。选择OpenAI GPT-4或Claude 3等高性能模型作为智能体核心它们在遵循指令和结构化输出方面表现更佳。4.2 状态感知模块实现我们创建一个StateObserver类专门负责从Playwright页面中提取关键状态。import asyncio from playwright.async_api import async_playwright import json class WebStateObserver: def __init__(self, page): self.page page async def get_interactive_elements(self): 获取页面上所有可交互元素的状态 # 通过执行JavaScript获取我们关心的元素及其属性 elements await self.page.evaluate( () { const selectors input, button, select, textarea, a[href], [rolebutton], [rolelink], [contenteditabletrue]; const nodes Array.from(document.querySelectorAll(selectors)); return nodes.map(el { // 生成一个稳定的定位符优先用id其次用有意义的selector let locator el.id ? id${el.id} : null; if (!locator) { // 尝试构建一个基于属性和位置的XPath简化版 // 注意生产环境需要更稳健的XPath生成函数 if (el.name) locator [name${el.name}]; else if (el.ariaLabel) locator [aria-label${el.ariaLabel}]; else if (el.innerText el.innerText.trim()) locator text${el.innerText.trim().substring(0,50)}; } // 如果以上都没有使用一个包含标签和索引的简单定位符可能不稳定 if (!locator) { const tag el.tagName.toLowerCase(); const index Array.from(document.querySelectorAll(tag)).indexOf(el); locator ${tag}:nth-of-type(${index1}); } return { tag: el.tagName.toLowerCase(), id: el.id || , name: el.name || , type: el.type || , placeholder: el.placeholder || , value: el.value || el.innerText || , disabled: el.disabled, visible: el.offsetParent ! null, // 简单可见性判断 locator: locator, // 添加一些启发式判断决定它是否“可操作” actionable: !el.disabled el.offsetParent ! null (el.tagName BUTTON || el.tagName A || el.tagName INPUT || el.tagName SELECT || el.tagName TEXTAREA || el.getAttribute(role) button || el.getAttribute(role) link) }; }).filter(el el.actionable); // 只返回可操作元素 } ) return elements async def get_page_context(self): 获取页面级上下文如URL、标题 url self.page.url title await self.page.title() return {url: url, title: title}4.3 智能体核心与动作执行模块接下来创建StateActAgent类它整合状态观察、LLM调用和动作执行。import openai # 假设已设置 openai.api_key your-key class StateActAgent: def __init__(self, observer, llm_modelgpt-4): self.observer observer self.llm_model llm_model self.action_history [] self.task async def think_and_act(self, task_description): 核心循环感知-思考-执行 self.task task_description max_steps 20 for step in range(max_steps): print(f\n 步骤 {step1} ) # 1. 感知状态 page_context await self.observer.get_page_context() elements await self.observer.get_interactive_elements() current_state { page: page_context, interactive_elements: elements[:15] # 限制数量防止token超限 } # 2. 构建Prompt调用LLM思考 prompt self._build_prompt(current_state, self.action_history[-5:]) # 只保留最近5个动作作为历史 llm_response await self._call_llm(prompt) # 3. 解析并执行动作 action_result await self._execute_action(llm_response) self.action_history.append({ step: step, state_snapshot: current_state, llm_response: llm_response, result: action_result }) # 4. 检查任务是否完成 if action_result.get(action) done: print(任务完成) return True # 简单失败检测例如动作执行失败或陷入循环 if action_result.get(status) failed: print(动作执行失败需要处理。) # 这里可以加入重试或恢复逻辑 break print(达到最大步数任务可能未完成。) return False def _build_prompt(self, state, recent_history): history_text for h in recent_history: history_text f步骤{h[step]}: 执行 {h[llm_response]} - 结果: {h[result][status]}\n elements_text \n.join([f{i1}. [{e[tag]}] id:{e[id]}, name:{e[name]}, 值:{e[value]}, 定位符: {e[locator]} for i, e in enumerate(state[interactive_elements])]) prompt f 你是一个网页操作智能体。你的任务是{self.task} 当前页面{state[page][title]} ({state[page][url]}) 历史动作最近几步 {history_text} 当前可交互元素前15个 {elements_text} 请根据当前状态和任务目标决定下一步操作。你只能输出一个JSON对象。 可用动作类型 - click(locator): 点击一个元素。 - type(locator, text): 向输入框输入文字。 - press_key(key_name): 按下键盘键如Enter, Tab。 - wait(seconds): 等待几秒。 - done(): 任务完成。 输出格式必须是严格的JSON{{action: action_name, target: locator_string, value: optional_value}} 例如{{action: click, target: idlogin-button, value: }} 或{{action: type, target: [nameusername], value: alice}} 或{{action: press_key, target: , value: Enter}} 或{{action: done, target: , value: }} 现在请输出你的下一个动作JSON return prompt async def _call_llm(self, prompt): # 这里使用OpenAI API实际使用时请处理异常和速率限制 try: response openai.chat.completions.create( modelself.llm_model, messages[{role: user, content: prompt}], temperature0.1, # 低温度保证输出稳定 max_tokens200 ) content response.choices[0].message.content.strip() # 清理响应提取JSON部分 import re json_match re.search(r\{.*\}, content, re.DOTALL) if json_match: return json_match.group(0) else: return content except Exception as e: print(f调用LLM失败: {e}) return {action: wait, target: , value: 5} async def _execute_action(self, llm_response_str): import json try: action_cmd json.loads(llm_response_str) action action_cmd.get(action) target action_cmd.get(target, ) value action_cmd.get(value, ) if action click: await self.observer.page.click(target) return {status: success, message: f点击了 {target}} elif action type: await self.observer.page.fill(target, value) return {status: success, message: f向 {target} 输入了 {value}} elif action press_key: await self.observer.page.keyboard.press(value) return {status: success, message: f按下了 {value} 键} elif action wait: await asyncio.sleep(float(value)) return {status: success, message: f等待了 {value} 秒} elif action done: return {status: success, action: done, message: 任务标记完成} else: return {status: failed, message: f未知动作: {action}} except json.JSONDecodeError: return {status: failed, message: fLLM返回非JSON格式: {llm_response_str}} except Exception as e: return {status: failed, message: f执行动作时出错: {e}}4.4 主程序与任务执行最后编写主程序来启动浏览器、导航到目标页面例如Google日历并运行智能体。async def main(): async with async_playwright() as p: # 启动浏览器设置慢速以便观察 browser await p.chromium.launch(headlessFalse, slow_mo500) context await browser.new_context() page await context.new_page() # 导航到Google日历 await page.goto(https://calendar.google.com) # 这里假设已经登录或者页面处于可操作状态 # 初始化观察者和智能体 observer WebStateObserver(page) agent StateActAgent(observer, llm_modelgpt-3.5-turbo) # 可根据需要切换模型 # 定义任务 task 在日历中创建一个新的会议事件标题为‘团队周会’时间设为明天下午2点到3点。 # 运行智能体 success await agent.think_and_act(task) if success: print(智能体成功完成任务) else: print(智能体未能完成任务。) # 等待一会儿以便查看结果然后关闭 await asyncio.sleep(5) await browser.close() if __name__ __main__: asyncio.run(main())这个示例提供了一个完整的、可运行的StateAct智能体骨架。在实际应用中你需要处理登录认证、更稳健的元素定位符生成、更复杂的错误恢复机制例如当click失败时尝试其他定位方式以及针对特定网站的优化例如识别日历应用特有的控件。5. 常见问题、挑战与进阶方向即使采用了StateAct范式在实际构建和部署智能体时你依然会面临一系列挑战。5.1 典型问题与排查技巧问题现象可能原因排查与解决思路LLM输出格式错误提示词不够严格模型温度设置过高。1. 在系统提示中反复强调JSON格式并给出明确示例。2. 在代码中添加后处理尝试从非标准响应中提取JSON。3. 将温度temperature参数调低至0.1或0.2。动作执行失败元素未找到状态感知提供的定位符不稳定或已过期页面动态加载导致元素尚未出现。1.优化定位符生成优先使用唯一ID、稳定的>智能体陷入循环或无关操作任务分解不清晰状态描述未能提供足够的任务进度信息。1.在状态中注入任务进度例如明确告诉LLM“你已经输入了标题下一步需要设置时间”。2.设置步数限制和超时。3.引入验证步骤关键步骤后通过状态检查或视觉快照验证操作结果如果不符合预期则触发纠正逻辑。处理模态框或新窗口操作触发了新窗口或弹窗状态观察器仍附着在旧页面上。1. 监听页面的popup或dialog事件。2. 当新窗口出现时将观察器和智能体的焦点切换到新上下文。3. 任务完成后记得切换回原上下文。性能瓶颈每一步都调用LLM成本高、速度慢状态提取太频繁。1.缓存状态如果页面无明显变化可以复用上一次的状态。2.使用更小的模型处理简单决策例如用小型本地模型判断“点击提交按钮后成功页面是否出现”。3.批量动作对于连续的、确定性的操作如填写表单多个字段可以让LLM一次性输出一个动作序列。5.2 进阶方向与优化策略混合感知Hybrid Perception正如前文所述纯状态感知有盲区。结合轻量级视觉模型如YOLO做目标检测或CLIP做图像匹配来处理验证码、识别非标准控件、或进行最终的结果验证能构建更鲁棒的智能体。例如在点击“保存”后截屏并用CLIP判断是否出现了“保存成功”的提示图案。分层与多智能体架构对于超长视野任务可以借鉴“chimera”等框架的思想。一个顶层的“规划智能体”负责将宏观目标“准备季度报告”分解为子任务“打开Excel下载数据制作图表写入Word”。多个底层的“技能智能体”分别精通于操作特定应用Excel智能体、浏览器智能体、Word智能体它们接收子任务和状态执行具体的操作序列。这符合“actor-attention-critic”等多智能体强化学习中的分工协作思想。学习与自适应初始的智能体可能很笨拙。可以通过记录成功和失败的任务轨迹构建一个微调数据集对核心LLM进行指令微调Instruction Tuning使其更擅长输出正确的操作序列。更进一步可以引入强化学习将任务完成与否作为奖励信号让智能体自我优化其策略。工具集成与扩展将智能体能力扩展到操作系统层面。除了点击和输入还可以定义诸如run_command执行命令行、read_file、search_files等高级动作。让智能体能够调用外部工具和API使其能处理涉及多应用、本地文件系统和网络资源的复杂工作流。StateAct所代表的“程序状态优先”范式为构建实用、可靠的计算机使用智能体开辟了一条更可行的道路。它降低了智能体感知世界的不确定性将挑战从“看懂屏幕”转移到了更擅长的“逻辑推理与规划”上。虽然完全通用的“数字员工”尚需时日但针对特定领域如数据录入、软件测试、IT运维的高效智能体已经可以基于这套思路构建并产生实际价值。
返回列表