Datawhale干货
作者:孙韬,Datawhale鲸英助教
最近 AI 圈最炸的瓜,毫无疑问是——Manus。
前天我们邀请了 OWL 和 OpenManus 团队为大家带来了全网首个 Manus 开源复现直播,直播回放在视频号。
今天为大家带来 Manus 开源复刻框架 OWL 的实测体验及技术拆解。
OWL 项目地址:https://github.com/camel-ai/owl
🦉 OWL 是什么?
🦉 OWL 是一个基于 CAMEL-AI 框架(https://github.com/camel-ai/camel)的多智能体协作的尖端框架,它突破了任务自动化的界限,在其官方的介绍中表示:
“我们的愿景是彻底改变 AI 代理协作解决实际任务的方式。通过利用动态代理交互,OWL 可以在不同领域实现更自然、更高效和更强大的任务自动化。”
目前 OWL 在 GAIA 基准测试中取得 58.18 平均分,在开源框架中排名第一。
这里是一个演示 demo:
其实如果大家看过 Manus 的演示视频就可以发现,因为一些任务现在的大模型没有办法一次性全都处理完,Manus 在完成我们提出的问题的时候会首先对任务进行任务拆分,之后形成一个 TODO.md 用来记录自己已经完成的进度。
在 OWL 中,也是类似的思想,OWL 整个流程基于 CAMEL 框架中 RolePlaying 方法,RolePlaying 是 CAMEL 框架的独特合作式智能体框架。该框架通过预定义的提示词为不同的智能体创建唯一的初始设置。
在 RolePlaying 中 User 角色负责将讲任务拆解,并且每次给出一个指令到 Assistant 角色,Assistant 角色负责实施指令,在这个过程中 Assistant 角色可以使用各种我们给它配置好的工具,比如获取实时信息(如天气、新闻)的工具、计算的工具以及在 OWL 中定义的一些特定的工具(包括控制 web 页面的工具、处理文件、表格、图像等工具)。
下面我们先来看一个 case,我的问题是“帮我在小红书上找一些3.8女神节适合送妈妈的礼物”:
可以发现在运行过程中,我们的 agent 首先调用了WebToolkit,WebToolkit 实际上是一个封装了智能体的一个工具,其中包括一个planning_agent和一个web_agent。在浏览器模拟交互任务开始时,planning_agent会首先给出一个详细的规划,这个规划之后会被用于指导web_agent选择具体的执行动作。
那么我们的 agent 是怎么实现控制我们的浏览器的呢,首先任务明确之后,我们首先会跳转到一个网站,该网站是由start_url来决定的,该参数是可以由用户指定,也可以是 agent 在自我规划中提出的。跳转到网站之后,我们的WebToolkit会将当前的页面截图保存下来,并且给其中的元素都打上标签。
该步骤的实现方法如下:
1. 交互元素通过 JavaScript 脚本获取:
def get_interactive_elements(self) -> List[Dict[str, Any]]: try: self.page.evaluate(self.page_script) except Exception as e: logger.warning(f"Error evaluating page script: {e}") result = cast( Dict[str, Dict[str, Any]], self.page.evaluate("MultimodalWebSurfer.getInteractiveRects();") ) typed_results: Dict[str, InteractiveRegion] = {} for k in result: typed_results[k] = interactiveregion_from_dict(result[k]) return typed_results
2. add_set_of_mark 函数负责在截图上绘制标记:
def _add_set_of_mark( screenshot: Image.Image, ROIs: Dict[str, InteractiveRegion]) -> Tuple[Image.Image, List[str], List[str], List[str]]: # ... 初始化代码 ... # 为每个交互区域绘制标记 for idx, (identifier, region) in enumerate(ROIs.items()): for rect in region.rects: _draw_roi(draw, idx, font, rect) # ... 其他处理 ... # ... 返回结果 ...
3. _draw_roi 函数负责实际绘制:
def _draw_roi( draw: ImageDraw.ImageDraw, idx: int, font: ImageFont.FreeTypeFont | ImageFont.ImageFont, rect: DOMRectangle) -> None: # 计算矩形坐标 x, y = rect["x"], rect["y"] width, height = rect["width"], rect["height"] # 选择颜色 color = _color(idx) # 绘制矩形边框 draw.rectangle([(x, y), (x + width, y + height)], outline=color, width=2) # 绘制标签 label = str(idx) text_width, text_height = font.getsize(label) # 确定标签位置 label_x = x + width / 2 - text_width / 2 label_y = max(y - text_height - 2, TOP_NO_LABEL_ZONE) # 绘制标签背景和文本 draw.rectangle([(label_x - 2, label_y - 2), (label_x + text_width + 2, label_y + text_height + 2)], fill=color) draw.text((label_x, label_y), label, fill=(255, 255, 255, 255), font=font)
该函数会在元素周围绘制彩色矩形边框,为每个元素分配数字 ID 标签,最后在元素上方绘制带背景的标签,如下图所示。
生成的带标签的页面(该页面存储在./tmp目录里)就是我们web_agent将要接收到图片输入啦,之后web_agent会根据图片和指令来进行下一步动作。要了解web_agent的工作逻辑,我们需要先了解一下planning_agent是如何工作的,下面是它的提示词:
planning_system_prompt = """You are a helpful planning agent that can assist users in planning complex tasks which need multi-step browser interaction."""
这里定义了planning_agent的基本角色:一个帮助用户规划复杂任务的代理,专注于需要多步骤浏览器交互的任务。planning_agent在初始规划阶段使用以下提示:
planning_prompt = f"""
这个提示要求planning_agent:
重述任务
提供详细的解决方案计划
考虑部分可观察马尔可夫决策过程的特性(网页只能部分观察)
在任务执行过程中,planning_agent可能需要根据新情况调整计划:
replanning_prompt = f"""We are using browser interaction to solve a complex task which needs multi-step actions.Here are the overall task:
其中{task_prompt}就是我们提供的原始任务输入:“帮我在小红书上找一些3.8女神节适合送妈妈的礼物”。
{detailed_plan}则是上一次生成的规划内容,{self.history_window}默认为5,也就是说planning_agent会动态地结合最近地一些信息来进行下一步规划。在prompt中要求了planning_agent要使用结构化的输出。生成的规划内容会传输给web_agent来帮助它完成任务。
接下来,我们可以来看一下web_agent的提示词来进一步了解它的工作逻辑。
system_prompt = """You are a helpful web agent that can assist users in browsing the web.Given a high-level task, you can leverage predefined browser tools to help users achieve their goals."""
使用一个简洁的系统提示定义了web_agent的基本角色:一个帮助用户浏览网页的代理,能够利用预定义的浏览器工具完成用户目标。
之后如果 agent 认为需要进行操作电脑,就使用我们给它配备的browser_simulation方法,该方法会循环地去观察我们的网页,web_agent在每次观察网页时会收到以下格式的提示:
observe_prompt =f"""Please act as a web agent to help me complete the following high-level task:
简单使用 GPT 翻译的结果如下:
observe_prompt = f""" 请充当一个Web代理,帮助我完成以下高级任务:
其中{task_prompt}就是我们提供的原始任务输入:“帮我在小红书上找一些3.8女神节适合送妈妈的礼物”,
{detailed_plan_prompt}是由planning_agent生成的,{AVAILABLE_ACTIONS_PROMPT}中定义了web_agent可以使用的方法列表。
WebToolkit中的浏览器控制方法主要通过 Playwright 库来实现,在 owl 中,主要实现了以下方法:
1. fill_input_id(identifier: Union[str, int], text: str):在输入框(例如搜索框)中填入给定的文本并按下回车键。
2. click_id(identifier: Union[str, int]):点击具有给定ID的元素。
3. hover_id(identifier: Union[str, int]):将鼠标悬停在具有给定 ID 的元素上。
4. download_file_id(identifier: Union[str, int]):下载具有给定 ID 的文件。它返回下载文件的路径。如果文件成功下载,你可以停止模拟并报告下载文件的路径以供进一步处理。
5. scroll_to_bottom():滚动到页面底部。
6. scroll_to_top():滚动到页面顶部。
7. scroll_up():向上滚动页面。适用于你想查看当前视口上方元素的情况。
8. scroll_down():向下滚动页面。适用于你想查看当前视口下方元素的情况。如果网页没有变化,意味着网页已经滚动到底部。
9. back():导航回上一页。当你想返回上一页时很有用,因为当前页面没有用处。
10. stop():停止操作过程,因为任务已完成或失败(无法找到答案)。在这种情况下,你应在输出中提供你的答案。
11. get_url():获取当前页面的URL。
12. find_text_on_page(search_text: str):在当前整个页面上查找下一个给定的文本,并将页面滚动到目标文本。这相当于按下 Ctrl + F 并搜索文本,当你想快速检查当前页面是否包含某些特定文本时非常有用。
13. visit_page(url: str):访问特定的 URL 页面。
14. click_blank_area():点击页面的空白区域以取消当前元素的焦点。当你点击了一个元素但它无法自动取消焦点时(例如菜单栏),这很有用,以便自动渲染更新后的网页。
15. ask_question_about_video(question: str):询问关于当前包含视频的网页的问题,例如 YouTube 网站。
{self.history_window}默认为 5,也就是说web_agent会动态地结合最近它做过的操作来进行下一步。在 prompt 中要求了web_agent要使用结构化的输出。
{{"observation": [IMAGE_DESCRIPTION], "reasoning": [YOUR_REASONING], "action_code": `fill_input_id([ID], [TEXT])` }}
web_agent输出之后,WebToolkit会对输出的内容进行解析,之后则由_act来执行action_code,后续的一系列_act大多使用 Playwright 库来完成。Playwright 是由 Microsoft 开发的开源浏览器自动化工具,具有以下特点:
跨浏览器支持:支持 Chromium、Firefox和WebKit
现代化 API:提供简洁、强大的 API
多标签页和框架支持:可以处理复杂的网页结构
移动设备模拟:支持模拟移动设备
简单来说,Playwright 提供了完整的键盘和鼠标 API,可以模拟各种用户交互。
在这个 case 中,web_agent首先使用了click_id方法,也就是点击,后面的标记是 130,可以看到web_agent发现了登陆界面阻碍了它进行下一步搜索,所以它首先会去关闭这个窗口,也就是点击 130 号元素。
关闭掉阻碍的窗口之后,可以看到WebToolkit又重新对当前页面进行了截图并标注。
我们的web_agent也重新规划了自己的动作,使用了fill_input_id(137, “3.8女神节送妈妈礼物”),它将会在在输入框(例如搜索框)中填入给定的文本并按下回车键来完成搜索功能。
但是最后这个 case 其实执行失败了,眼尖的小伙伴可能发现了,其实 137 号不是搜索框,是搜索键,搜索框是蓝绿背景的,被 137 号盖住了,导致该任务失败。后续 OWL 团队也会优化set of mark方法并结合在命令行中进行 web search +document extractio 这种方式来使整个过程更加 solid。
再尝试一个 case“帮我在GIthub找下CMAEL团队开发的OASIS项目,给我介绍下这个项目” 由于整个过程比较长,笔者在这里放一些片段内容:
可以看到 OWL 在自主地去浏览整个网页,这次 OWL 成功地完成了整个任务,并且完成了报告。
左图为过程截图,右图为最终报告:
再来看另一个 case,在这个 case 中我们希望 OWL 给我们制定一个长沙的三天旅游方案:
可以发现这一次,OWL 并没有使用WebToolkit,而是走了另一套流程,它使用了 Google 搜索的 API 来检索到了 6 条内容,然后从中筛选出来了 3 条结果。
初步检索
筛选的结果
检索到足够的结果之后,会将这些结果的 url 交给我们的DocumentProcessingToolkit来处理,该工具包集成了Firecrawl 及 Chunkr等处理工具,可以对 url 进行内容解析。
最后再将整个过程的内容总结成我们最后的旅游攻略:
作为在长沙上过学的我来说,这份攻略还是相当实用的~
体验感受
通过多个案例的实际测试,OWL 展现出令人惊喜的多模态处理能力和智能体协作机制,尤其是它会自主去关闭一些阻碍的页面,这点真的令我感到很兴奋!因为之后再体验 Manus 的时候,在遇到相同的情况 Manus 会直接退出该界面,换另一个方式,哪怕我们在 prompt 要求他必须要在某个网站上进行搜索。
OWL 会根据你的任务自主去决定最快捷的方式来完成。并且整体的流程也给了我们很大的想象空间。比如是不是未来可以代替人类进行一些图片标注任务,可以省去大量人工成本。
对于WebToolkit的实现也非常的巧妙,它实现了两个 Agent 的协作机制:PlanningAgent:负责”战略”层面,提供整体规划,而WebAgent负责”战术”层面,执行具体操作。整体的协作流程也非常像人类由大脑先思考然后下达指令,人体再去执行的过程。
但其实我们可以感受到,有的情况下,直接使用一些 API 来完成任务实际上会比使用WebToolkit速度快很多,但WebToolkit的优势在于能做的事情更加的多(如操作未开放接口的网站),并且对于多模态的支持可能更加丰富。
OWL 另外强大的地方在于对各种工具包的集成与适配做的非常好,包括各种搜索工具、文档处理工具、视频、图像、音频理解工具等等。这使得 OWL 在应对各种情况都显得游刃有余。
使用教程
说了这么多,小伙伴一定也迫不及待想要一块来体验一下我们的 OWL 吧!下面,笔者将带着大家一起配置好我们需要的环境。
先我们下载与好 owl 的源码并且配置好我们的环境。
git clone https://github.com/camel-ai/owl.gitcd owl
设置虚拟环境
使用 Conda(推荐):conda create -n owl python=3.11conda activate owl
使用 venv(备用):python -m venv owl_env# Windows 系统owl_env\Scripts\activate# Unix 或 MacOS 系统source owl_env/bin/activate
安装依赖:
python -m pip install -r requirements.txtplaywright install
对于模型的设置可以参考笔者的配置,其中模型的选择都使用 Qwen 系列模型,其中需要注意的是WebAgent必须使用多模态模型,因为会接收到图片的输入,关于 API 的申请一并放在下方:
快速体验(只需要配置 QWEN_API_KEY 即可)
from dotenv import load_dotenvload_dotenv()from camel.models import ModelFactoryfrom camel.toolkits import WebToolkit,SearchToolkit,FunctionToolfrom camel.types import ModelPlatformType,ModelTypefrom loguru import loggerfrom utils import OwlRolePlaying, run_societyimport osqwen_api_key = '你申请到的API'def construct_society(question: str) -> OwlRolePlaying: r"""Construct the society based on the question.""" user_role_name = "user" assistant_role_name = "assistant" user_model = ModelFactory.create( model_platform=ModelPlatformType.QWEN, model_type="qwen-max", model_config_dict={"temperature": 0}, api_key=qwen_api_key, ) assistant_model = ModelFactory.create( model_platform=ModelPlatformType.QWEN, model_type="qwen-max", model_config_dict={"temperature": 0}, api_key=qwen_api_key, ) search_model = ModelFactory.create( model_platform=ModelPlatformType.QWEN, model_type="qwen-max", model_config_dict={"temperature": 0}, api_key=qwen_api_key, ) planning_model = ModelFactory.create( model_platform=ModelPlatformType.QWEN, model_type="qwen-max", model_config_dict={"temperature": 0}, api_key=qwen_api_key, ) web_model = ModelFactory.create( model_platform=ModelPlatformType.QWEN, model_type="qwen-vl-plus-latest", model_config_dict={"temperature": 0}, api_key=qwen_api_key, ) tools_list = [ *WebToolkit( headless=False, web_agent_model=web_model, planning_agent_model=planning_model, output_language='中文' ).get_tools(), ] user_role_name = 'user' user_agent_kwargs = dict(model=user_model) assistant_role_name = 'assistant' assistant_agent_kwargs = dict(model=assistant_model, tools=tools_list) task_kwargs = { 'task_prompt': question, 'with_task_specify': False, 'output_language': '中文', } society = OwlRolePlaying( **task_kwargs, user_role_name=user_role_name, user_agent_kwargs=user_agent_kwargs, assistant_role_name=assistant_role_name, assistant_agent_kwargs=assistant_agent_kwargs, ) return society# Example casequestion = "在百度热搜上,查看第一条新闻,然后给我一个总结报告"society = construct_society(question)answer, chat_history, token_count = run_society(society)logger.success(f"Answer: {answer}")
完整功能的配置(需要配置更多的 API_KEY)
from dotenv import load_dotenvload_dotenv()from camel.models import ModelFactoryfrom camel.toolkits import ( WebToolkit, DocumentProcessingToolkit, VideoAnalysisToolkit, AudioAnalysisToolkit, CodeExecutionToolkit, ImageAnalysisToolkit, SearchToolkit, ExcelToolkit, FunctionTool )from camel.types import ModelPlatformTypefrom camel.configs import QwenConfigfrom loguru import loggerfrom utils import OwlRolePlaying, run_societyimport osqwen_api_key = '你申请到的API'def construct_society(question: str) -> OwlRolePlaying: r"""Construct the society based on the question.""" user_role_name = "user" assistant_role_name = "assistant" user_model = ModelFactory.create( model_platform=ModelPlatformType.QWEN, model_type="qwen-max", model_config_dict={"temperature": 0}, api_key=qwen_api_key, ) assistant_model = ModelFactory.create( model_platform=ModelPlatformType.QWEN, model_type="qwen-max", model_config_dict={"temperature": 0}, api_key=qwen_api_key, ) search_model = ModelFactory.create( model_platform=ModelPlatformType.QWEN, model_type="qwen-max", model_config_dict={"temperature": 0}, api_key=qwen_api_key, ) planning_model = ModelFactory.create( model_platform=ModelPlatformType.QWEN, model_type="qwen-max", model_config_dict={"temperature": 0}, api_key=qwen_api_key, ) web_model = ModelFactory.create( model_platform=ModelPlatformType.QWEN, model_type="qwen-vl-plus-latest", model_config_dict={"temperature": 0}, api_key=qwen_api_key, ) multimodal_model = ModelFactory.create( model_platform=ModelPlatformType.QWEN, model_type="qwen-vl-plus-latest", model_config_dict={"temperature": 0}, api_key=qwen_api_key, ) tools_list = [ *WebToolkit( headless=False, web_agent_model=web_model, planning_agent_model=planning_model ).get_tools(), *DocumentProcessingToolkit().get_tools(), *VideoAnalysisToolkit(model=multimodal_model).get_tools(), # This requires multimodal model *AudioAnalysisToolkit().get_tools(), # This requires OpenAI Key *CodeExecutionToolkit().get_tools(), *ImageAnalysisToolkit(model=multimodal_model).get_tools(), # This requires multimodal model *SearchToolkit(model=search_model).get_tools(), #FunctionTool(SearchToolkit(model=search_model).search_duckduckgo),如果没有Google相关api可以用duckduckgo代替 *ExcelToolkit().get_tools() ] user_role_name = 'user' user_agent_kwargs = dict(model=user_model) assistant_role_name = 'assistant' assistant_agent_kwargs = dict(model=assistant_model, tools=tools_list) task_kwargs = { 'task_prompt': question, 'with_task_specify': False, 'output_language': '中文', } society = OwlRolePlaying( **task_kwargs, user_role_name=user_role_name, user_agent_kwargs=user_agent_kwargs, assistant_role_name=assistant_role_name, assistant_agent_kwargs=assistant_agent_kwargs, ) return society# Example casequestion = "在百度热搜上,查看第一条新闻,然后给我一个总结报告"society = construct_society(question)answer, chat_history, token_count = run_society(society)logger.success(f"Answer: {answer}")
其中使用到的 API 在以下网站可以申请到 QWEN_API_KEY:
🔗:https://help.aliyun.com/zh/model-studio/new-free-quota
使用 API 调用大模型需要 API 密钥,这里我们以 Qwen 为例,您可以从百炼平台获取 API_KEY
可选模型范围
在阿里云百炼的模型库(https://bailian.console.aliyun.com/model-market#/model-market)中选择推理 API-Inference ,里面的模型都可以选择。
之后在./owl目录下新建一个.env文件,可以参考.env_template(https://github.com/camel-ai/owl/blob/main/owl/.env_template),在里面配置好相关的 KEY,如果只想快速体验我们的第一个案例,则只需要配置QWEN_API_KEY,如果想体验相对完整的功能,需要配置QWEN_API_KEY、CHUNKR_API_KEY、FIRECRAWL_API_KEY、GOOGLE_API_KEY、SEARCH_ENGINE_ID(如果没有GOOGLE相关 API 可以使用 duckduckgo来代替,参考上文的快速体验)。
一切都配置好之后我们就可以运行啦:
python run.py
Datawhale 多智能体教程、CAMEL、OWL 开源地址:
教程地址:https://github.com/datawhalechina/handy-multi-agent
CAMEL: https://github.com/camel-ai/camel
OWL: https://github.com/camel-ai/owl
一起“点赞”三连↓
(文:Datawhale)