Transcription
好的。感谢大家参加本次关于使用并行代理自动化大规模重构的会议。今天我非常兴奋地与大家分享我们 OpenHands 在自动化大规模软件工程工作方面的进展。许多与技术债务、代码维护、代码现代化相关的繁琐工作。这些任务非常适合自动化。您可以投入代理来处理它们,但它们通常太大,无法一次性完成。因此,这涉及到我们所说的代理编排。我们将简要介绍我们如何通过 OpenHands 以及更普遍地做到这一点。
关于我。我叫 Robert Brennan,是 OpenHands 的联合创始人兼首席执行官。我的背景是开发工具。我已经在开源开发工具领域工作了十多年。我也在自然语言处理领域工作了大约相同的时间。在过去的几年里,我一直非常兴奋地看到这两个领域突然融合,因为大型语言模型非常擅长编写代码。我很高兴能在这个领域工作。
OpenHands 是一个 MIT 许可的编码代理。OpenHands 大约在一年前半的 OpenDev 上启动,当时 Devon 发布了他们完全自主的软件工程代理的演示视频。我和我的联合创始人看到了这一点,并对可能实现的以及软件工程的未来感到非常兴奋。但我们意识到,这不应该发生在黑箱中,对吧?如果我们的开发模式要改变,我们希望这种改变由软件开发社区驱动。我们希望对这种改变有发言权。因此,我们启动了 OpenHands,作为一种让社区能够帮助驱动在人工智能驱动的世界中软件工程未来可能样貌的方式。
所以,我希望说软件开发正在发生变化并不过分。我知道我自己的工作流程在过去一年半里发生了巨大的变化。我想说,现在,我写的几乎每一行代码都经过了代理。而不是我打开我的 IDE 并逐行输入代码,我现在要求代理为我完成工作。我仍然在进行大量的批判性思考。这份工作的很多心态没有改变,但实际工作看起来已经改变了很多。
但我想说服大家的是,它仍在变化。我们仍处于这场变革的早期阶段。我们还没有完全认识到大型语言模型已经为这份工作带来的所有影响,并且随着它们的改进,它们将继续为这份工作带来影响。我想说,即使今天大型语言模型停止改进,并且不再变得更好,在未来两到三年内,随着我们找到将这项技术投入实际应用的方法,软件工程的工作仍然会发生巨大的变化。我认为在软件工程中采用大型语言模型仍然存在许多心理和组织上的障碍。随着时间的推移,我们看到许多这些障碍正在消失。
简要回顾一下我们是如何走到这一步的。我认为一切都始于我称之为“上下文感知代码片段”。事实证明,一些早期的大型语言模型非常擅长编写代码块,尤其是那些它们反复见过的代码。所以你可以要求它编写冒泡排序。你可以要求它编写小的算法,例如如何访问 SQL 数据库之类的内容。它能够生成小的代码片段。它似乎也能理解一些逻辑。但这是完全不考虑上下文的,对吧?它只是将代码放入您要求的聊天窗口中。它根本不知道您正在处理什么项目,也不知道上下文是什么。
不久之后,我们获得了这些上下文感知的代码生成。例如,GitHub Copilot 作为自动完成功能,可能是这里最好的例子,对吧?它在你的 IDE 中,可以看到你正在输入的位置,知道你正在处理的代码,并且可以生成特定于你的代码库的代码,引用本地变量名,引用你数据库中的本地表名。这对我们的生产力来说是一个巨大的巨大改进。因此,不再需要在 ChatGPT 窗口和你的 IDE 之间来回复制粘贴,现在突然之间,你可以看到那个小机器人有了眼睛。它可以看到你的代码库内部,并且可以实际生成与你的代码库相关的代码。
然后,我认为巨大的飞跃发生在 2024 年初,随着 Devon 的发布,以及第二天 OpenDev 现在是 OpenHands 的发布。这是我们第一次开始看到自主编码代理。这意味着人工智能开始不仅编写代码,而且能够运行它编写的代码,并且能够搜索错误消息,找到 Stack Overflow 文章,将其应用于代码,在代码中添加一些调试语句并运行它,看看会发生什么。基本上自动化了开发的整个内部循环。这是一个巨大的进步。你可以看到这个图片中的小机器人有了手臂。这至少在我的个人生产力方面是一个巨大的飞跃。能够只用几句话的英语,交给一个代理,让它处理任务,直到它得到一些真正有效、正在运行、测试通过的东西。
现在我们看到的是并行代理,我们称之为代理编排。人们正在想办法让多个代理并行工作,有时相互通信,有时在后台启动新的代理。代理创建代理。我想说这是目前可能性的前沿。人们才刚刚开始尝试这一点,才刚刚开始看到大规模的成功,但有一些非常好的任务非常适合这种工作流程。它有潜力真正自动化我们今天在每个当代软件公司之下堆积如山的庞大技术。
关于这里的市场格局。再次,你可以看到从左到右的相同演变,我们确实从 GitHub Copilot 等插件开始,在我们的现有 IDE 中,我们获得了这些 AI 驱动的 IDE,即带有 AI 功能的 IDE。我想说,你的普通开发者现在正在采用本地代理。他们可能正在为一两件事运行云代码。也许是一些临时任务。然而,早期采用者正在开始关注基于云的代理,即拥有自己的沙箱并在云中运行的代理。这使得早期采用者可以并行运行任意数量的代理。它们允许他们比在本地笔记本电脑上运行时更自主地运行这些代理,对吧?如果它在你的本地笔记本电脑上运行,没有任何东西可以阻止代理执行 `rm -rf /` 尝试删除你主目录中的所有内容,或者它会做什么,安装一些奇怪的软件。而如果它在云中的某个地方拥有自己的容器化环境,你可以更安全地运行,知道最坏的情况就是它会毁掉自己的环境,而且你不必一直盯着它,每次它想运行命令时都要按“Y”键。因此,这些基于云的环境更具可扩展性,也更安全。
然后,我想说在这里最右边,我们真正看到的只有顶尖的 1% 的早期采用者开始尝试的是编排。这个想法是,你不仅有这些代理在云中运行,而且它们还在相互通信。你正在协调这些代理,在一个更大的任务上。也许这些代理会在云中启动子代理,这些子代理有自己的沙箱环境。那里有一些非常酷的东西正在发生。我想说,就 OpenHands 而言,我们通常从云代理开始。我们稍微退了一步,构建了本地 CLI,类似于 Cloud Code,以满足开发者目前的需求。你知道,这些类型的体验对开发者来说更舒适。我们几十年来一直在使用自动完成功能,有了 GitHub Copilot,它变得好了一百万倍。我想说,右边的这些体验对开发者来说非常陌生。将通行证交给代理或代理集群,让他们为你完成工作,感觉非常奇怪。对我来说,这就像我从一名独立贡献者转变为一名经理时所做的飞跃,这就是将代码写给自己与将代码交给代理的感觉。所以,工作方式非常非常不同。我认为这是开发者一直很慢地采用的原因之一。但再次,我们看到的采用右侧这些东西的工程师中,大约有 1% 的顶尖工程师,他们能够获得巨大的生产力提升,并处理其他团队根本无法处理的庞大技术债务积压。
一些你希望使用编排而不是单个代理的例子。通常,这些任务将是高度可重复和高度可自动化的。因此,一些例子是基本的代码维护任务,对吧?每个代码库都需要完成一定量的工来保持运行,对吧?保持依赖项最新,确保解决任何漏洞。例如,我们有一个客户正在使用 OpenHands 来修复他们整个代码库中的 CVE。他们有成千上万的开发人员,成千上万的存储库。基本上,每次在开源项目中宣布新的漏洞时,他们都必须检查整个代码库,找出哪些存储库存在漏洞,提交一个拉取请求到该代码库来实际解决 CVE,更新任何依赖项,修复破坏性的 API 更改。通过大规模编排,他们在解决 CVE 的时间上看到了 30 倍的改进。他们现在基本上有一个设置,每次宣布 CVE,新的漏洞出现时。他们会启动一个 OpenHands 会话来扫描存储库中的漏洞。进行任何必要的代码更改并打开一个拉取请求,所有下游团队只需要点击合并,验证更改。
您还可以使用此功能来自动化文档和发布说明。公司面临着许多现代化挑战。例如,如果您正在使用 Python 3,您可能希望为您的 Python 代码库添加类型注解。您可能希望将您的 Java 单体拆分成微服务。这些是仍然需要工程师大量思考的任务。你知道,你不能只是一次性地对代码说“将我的模型重构为微服务”,但它仍然是真实的工作,对吧?你仍然只是在复制粘贴大量的代码。所以,如果你巧妙地或协同地组合代理,它们就可以做到这一点。
大量的迁移工作。例如,从旧版本的 Java 迁移到新版本的 Java。我们正在与一个客户合作,将一批 Spark 2 作业迁移到 Spark 3。我们使用 OpenHands 将我们的整个前端从 React Redux 迁移到了 Zustand。所以你可以进行这些非常大的迁移。再次,有很多非常增长的工作。仍然需要人类对如何编排这些代理进行大量思考。还有很多技术,例如检测未使用的代码并将其删除。我们有一个客户使用我们的 SDK,每次有新的错误模式进入代码库时,都会扫描他们的日志文件,并添加错误处理,修复出现的任何问题。所以有很多事情,它们对于单个代理来说有点太大,无法一次性完成。但它们非常适合自动化,只要你仔细考虑如何编排它们。
关于为什么这些任务无法一站式完成。有些是技术问题,有些更像是人类心理问题。在技术方面,你能够提供给代理的上下文是有限的。因此,极长的运行任务或跨越非常大的代码库的任务。通常你没有足够的东西。你将不得不压缩上下文窗口,直到代理可能会迷失。我们都见过懒惰问题。我尝试启动一些这类任务。代理会说,“好的,我迁移了你 100 个服务中的 3 个。我需要雇佣一个六人团队来完成剩下的工作。”代理通常缺乏代码库中的领域知识,对吧?它们没有你对问题的相同直觉。而且,当你与代理一起进行这些非常长的轨迹时,错误会累积。一开始的一个小错误将随着时间的推移而累积。代理将基本上在其任务的每一步中一遍又一遍地重复这个错误。
而在人类方面,我们确实对问题有直觉,但我们无法传达。你知道,如果你想将你的模型分解成微服务。你可能有一个关于它将如何工作的心理模型。如果你只是告诉代理“将模型分解成微服务”,它只会根据过去看到的模式进行猜测,而不会真正理解你的代码库。我们在分解应用程序以供代理使用以及理解什么代理可以一次性完成方面存在一些困难。我们还需要在代理工作时进行中间审查和中间签入。我们稍后会谈谈这个循环是什么样的。但同样,你不能只是告诉代理去做,然后期望最终结果会出来。你必须在代理进行过程中批准事情。而且,没有真正的完成定义。我认为,如果你不真正知道这个项目的完成是什么样子,就很难告诉代理。
在这些类型的编排路径上,我想明确一点,我们不期望每个开发者都在进行代理编排。我们认为大多数开发者将在本地使用单个代理,用于常见的临时任务,例如构建新功能、修复错误等。我认为在未来几年内,在熟悉的 IDE 环境中运行本地云代码可能是一种常见的流程。我们看到的是,一小部分采用代理的早期采用者,他们对代理非常兴奋,他们正在找到方法来编排代理,以大规模处理巨大的技术债务,并为那一小部分选定的任务获得更大的生产力提升。对吧?你不会看到所有软件工程的生产力提升 3000%。你可能会看到大家一直在报告的那种 20% 的提升。但对于一些选定的任务,例如 CVE 修复或代码库现代化,你可以获得巨大的提升,你可以在几周内完成多年的工作。
我想稍微谈谈这些工作流程在实践中是什么样的。如果你习惯于使用本地代理,这个循环可能看起来很熟悉。这是一种非常典型的循环,它看起来很像非 AI 编码的开发内部循环。但基本上,你给代理一个提示,它会在后台做一些工作。也许你会盯着它看,看着它所做的一切,每次它想运行一个命令时都按“Y”键。然后代理完成,你查看输出。你看到测试通过了。你看到这是否真的满足了你的要求,然后你可能会再次提示代理,让它更接近答案。或者你可能对结果满意。你提交结果并推送。
对于更大的编排任务,这会变得有点复杂。基本上,你需要做的是,或者可能与云协同工作,将你的任务分解成一系列可以由代理单独执行的任务。然后,你将为每个单独的任务发送一个代理,你将为每个单独的任务执行其中一个代理。最后,你可能需要代理的帮助,将所有输出拉到一个更改中,并将其合并到你的代码库中。非常重要的是,这里仍然有很多人的参与。你需要审查的不仅仅是汇总结果的最终输出,还有每个代理的中间输出。我喜欢告诉大家,目标不是 100% 自动化这个过程。它是大约 90% 的自动化。这仍然是数量级上的生产力提升。我认为这非常棘手,很难做好。这就是这个过程中很多思考的地方,例如我将如何分解任务,以便我可以验证每个步骤,并且我可以真正自动化整个过程,而不会最终得到一个高代码的混乱。
这是我喜欢用于此类任务的典型 Git 工作流程。通常我们会为我们的存储库创建一个新分支。我们可能会使用代理或 OpenHands 的微代理概念,在该分支上添加一些高级上下文。但只是一个 Markdown 文件,解释“我们在这里做什么”。这样代理就知道“好的,我们正在从 Redux 迁移到 Zustand”,或者“我们将把这些 Spark 2 作业迁移到 Spark 3”。你可能想建立一些脚手架。我稍后会举一些关于脚手架的例子。你将根据第一个分支创建许多代理。想法是它们将把它们的工作提交到该分支,并且它基本上会随着我们进行而累积我们的工作,然后最终,一旦我们完成,我们就可以移除我们的脚手架并将该分支合并到主分支。
现在,如果你刚开始接触这个,我建议你限制自己同时使用大约三到五个并发代理。我发现超过这个数量你的大脑会开始崩溃。但对于那些真正大规模采用编排的人来说,我们看到他们同时运行数百甚至数千个代理。通常,一个人不会负责审查每一个,但也许那些代理会向各个团队发送拉取请求之类的。所以,一旦你开始了解所有这些是如何工作的,并且你觉得你有一个很好的方法来让人类参与进来,你就可以非常积极地扩展。
现在,我将把话筒交给我的同事 Calvin。他将谈论一个非常非常大规模的迁移,基本上是消除 OpenHands 数据库中的代码异味,他使用我们这里的重构 SDK 完成了这项工作。
OpenHands 在解决开放式任务方面表现出色。给它一个集中的问题,比如“修复我失败的 CI,添加并调试这个端点”,它就能完成。但像所有代理一样,当范围变得太大时,它可能会遇到困难。假设我想重构整个代码库。也许强制执行证书更新,或者甚至从一个框架迁移到另一个框架。这些不是一次性任务。它们是蔓延的、相互关联的更改,可能涉及数百个文件。为了应对这种规模的问题,我们正在使用 OpenHands 代理 SDK 来构建专门用于协调人类和多个代理之间协作的工具。
例如,让我们来消除 OpenHands 存储库中的代码异味。这是存储库结构。仅核心代理定义就有大约 380 个文件,跨越 60,000 行代码。这说明了代码的体积,但对结构说明不多。所以,让我们使用我们的新工具来可视化这个存储库块的依赖图。在这里,每个节点代表一个文件。边表示依赖关系,谁导入谁。当我们不断缩小范围时,很明显,这个纠缠不清的网络就是为什么大规模重构如此困难。为了使其可管理,我们需要将这个混乱分解成人类可理解的块。想象一下 PR 大小的批次,代理可以处理,人类可以理解。
有许多方法可以进行批处理,具体取决于对你来说什么最重要。图论算法对诱导批次之间的边结构有很强的保证,但就我们的目的而言,我们可以简单地使用现有的目录结构来确保语义上相关的文件出现在同一个批次中。
导航回依赖图,我们可以看到节点的代码不再是随机分布的。相反,它们对应于每个关联文件所在的批次。缩小再放大,我们很容易找到一簇相邻的节点,它们都具有相同的颜色,这表明代理将同时访问所有这些文件。当然,这个图仍然很大,而且非常混乱。为了构建一个更简单的视图,我们将构建一个新的图,其中节点是批次,节点之间的边是从每个批次中的文件继承的依赖关系。这个视图要简单得多。我们可以同时在屏幕上看到整个结构。但这是我们使用图时遇到的问题。我们可以识别出没有依赖项的批次,并期望文件会消失。例如,Dispatch 16。看起来它在文件中。它可能为空。让我们检查一下。
现在,这是一个用于人机协作的工具。所以,一旦我们知道这个文件是空的,我们可能会决定将其移到别处。或者也许我们乐于将其保留在这个批次中。而我们所要做的就是给自己或 Rich 添加一个注释,以便我们知道内容。当然,在重构代码时,考虑你正在移动的内容的复杂性很重要。这个批次是微不足道的。让我们找一个稍微复杂一点的。这是一个包含四个文件的批次。它们都做着同样的事情,并且复杂性度量反映了这一点。这些有助于向人类表明,当我们处理这个时,我们应该更加小心,例如第一个例子。你需要首先找出哪里出了问题。
输入验证器。有几种不同的方法来定义验证器,具体取决于你关心什么。你可以将其视为程序化的。它调用一个匹配命令。如果你的验证是检查单元测试、运行 linter 或测试,这很有用。但是,因为我关心代码异味,我将使用一个语言模型,它将查看代码并根据我提供的一组规则来识别任何有问题的模式。
现在,让我们回到第一个批次,并实际使用这个验证器。记住,这个批次是微不足道的,幸运的是,验证器也认识到了这一点。它会返回一个漂亮的报告,说明识别了什么以及没有识别什么。这个批次的的状态变成了已完成的绿色。很好。并且这个状态的变化也反映在批次图中。导航回来并切换颜色显示,我们可以看到我们有正好一个节点已完成,其余的仍有待处理。但这已经让我们对我们所做的工作以及它如何融入更大的图景有了很好的了解。
所以,现在我们确保我们的存储库中没有代码异味的策略很简单。我们只需要确保这个批次图上的每个节点都变成绿色。所以,让我们回到我们的批次,继续验证,直到我们遇到一个失败。我们将继续按依赖关系进行,确保我们选择的节点不依赖于我们尚未分析的其他批次。
下一个批次与第一个批次一样简单,但因为 init 文件稍微复杂一些。生成的报告也稍微冗长一些。继续往下看,我们遇到了之前确定的批次,其中包含一些文件,代码复杂度相对较高。这个批次碰巧是我们的第一个失败者。请注意,状态变成了红色而不是绿色。现在这个批次比我们之前看到的要多文件。所以验证报告也相应地更长。查看一下,它正在逐个文件列出。其中一个文件中的代码尤其令人震惊。我们必须回头处理它。
如果我们一直缩小到批次图,并查看状态指示器,我们将看到两个绿色节点,代表我们已经成功验证的批次。我们还将看到红色,代表我们刚刚看到的验证批次。现在,我们的最终目标是让整个图变成绿色。这个红色节点带来了一点问题。要将这个红色节点变成绿色节点,我们需要解决验证器发现的问题,使用管道的下一步,修复器。就像验证器一样,修复器可以以多种不同的方式定义。程序化修复器可以运行批处理命令,或者你可以将整个批次输入语言模型,并希望它一步解决问题。但到目前为止,我们最强大的修复器是使用 OpenHands 代理 SDK 来创建代码的干净副本,而不是一个拥有各种工具来运行测试、检查代码、查看文档、做任何它需要做的事情来解决这些问题的代理。
所以,让我们回到缩放批次,运行修复器,看看会发生什么。现在,这个演示的这一部分被大大加速了,但由于我们按依赖顺序探索这些批次,所以当我们等待时,我们可以继续向下滚动列表,运行我们的验证器,并启动 OpenHands 代理的新实例,直到我们遇到一个被阻止的节点,因为它的一些极端依赖项仍未完成。当修复器完成时,批次的状态就会被设置。将来我们需要重新运行验证,以确保相关代码再次返回。
查看修复器返回的报告,信息不多,只有 PR 的标题。我们已经设置好了,以便每个修复器都能生成一个漂亮的、整洁的拉取请求,供人类批准。仅仅因为重构是自动化的,并不意味着它不需要被审查。
这是生成的拉取请求。代理在总结它识别的代码异味、为解决这些问题所做的更改以及它所做的任何更改方面做得非常出色。它对审阅者也很有帮助,并为将来在此代码部分工作的任何人提供了一些注释。当我们查看内容时,我们发现它非常精炼。所有更改都紧密地集中在解决我们之前确定的代码异味上。而且我们只修改了几百行代码,其中大部分只是将混乱的代码块重构为自己的函数调用。并非所有范围都如此小,但我们的批次策略和狭窄的指令确保了更改的范围得到了充分考虑。这有助于提高性能,但也很容易从这里进行。
从整个代码库中移除代码异味的完整过程变得清晰。使用验证器识别问题。使用修复器启动以解决这些问题。审查并合并这些 PR。解除对新修复的阻止并重复,直到整个屏幕变绿。我们已经使用这个工具对代码进行了一些相当大的更改,包括类型化和改进测试。而且没有 OpenHands SDK 的支持,我们不可能做到这一切。
好的。这就是 OpenHands 重构 SDK,由我们的 OpenHands 代理 SDK 提供支持。我们稍后将在研讨会中介绍如何构建一个更简单但非常相似的东西,其中并行代理协同工作来修复由初始代理发现的任务。我想稍微谈谈分解任务和在这些代理之间共享上下文的策略。这两者都是代理编排中非常重要且重要的部分。
因此,有效的任务分解,你实际上是在寻找将你的非常大的问题分解成单个代理可以解决的任务,单个代理可以一次性完成的任务。可以适合单个提交、单个拉取请求的东西。非常非常重要,因为你不想不断地与每个子代理进行迭代。你希望每个代理都有一个相当好的保证,即每个代理都将一次性完成任务。你将能够对其进行橡皮图章式审查,并将其合并到你正在进行的开发分支中。
你想寻找可以并行化的东西。这将是提高任务速度的一个巨大方法。你知道,如果你只是串行执行一堆不同的代理,你还不如让一个代理串行地完成任务。你并行化的越多,同时工作的代理越多,你就能越快地完成任务并进行迭代。
你需要那些可以轻松快速地验证为正确的东西。理想情况下,你会有一个可以查看 CI/CD 状态的东西,并且有信心如果一切都是绿色的,那么你就没问题了。也许你还需要点击应用程序本身,做一些事情,自己运行一个命令来验证事情看起来对你来说是好的。但你希望能够非常快速地理解代理是否完成了你要求的工作。而且你希望任务之间有清晰的依赖关系和顺序。
你注意到这些标准与你如何分解工程团队的工作 pretty much 相同,对吧?你需要确保你有可以分离的任务,可以由你团队中的不同人并行执行然后收集结果的任务。你需要知道,一旦我完成了任务 A,它就解锁了任务 B、C 和 D,一旦它们完成了,我们就可以做 E。所以,这与分解工程师团队的工作非常相似。
有几种策略可以分解我们看到的挑战性的大型重构。最简单、最直接的一种是逐个部分地进行。你知道,你可能会迭代存储库中的每个文件、每个目录,也许是每个函数或类。你知道,这是相当直接的做事方式。如果那些依赖关系可以“在不过多依赖彼此的情况下”执行,它就会很好地工作。所以,好的例子可能是为你的管道代码库添加类型注解。然后在最后,一旦你迁移了每一个文件,比如说,你可以将所有这些结果收集到一个 PR 中。
稍微更复杂一点的是创建一个依赖树。这里的想法是为那种逐个部分的方法添加一些排序,你知道,就像 Calvin 所看到的,你从依赖图的叶节点开始,对吧?你可能从你的实用程序文件开始,将它们迁移过来,然后任何依赖于它们的,你知道,它将具有那些初始修复,并且依赖关系可以开始处理你的流程的一部分。你基本上可以一直回溯到应用程序的入口点。这通常是一种更好的处理方式。这是关于如何排序任务的一种更原则性的方法。
另一个例子是创建某种脚手架,允许你在迁移前和迁移后世界中生活。例如,我们在迁移我们的 React 状态管理系统时就是这样做的。我们基本上有一个代理设置了一些脚手架,它允许我们同时使用 Redux 和 Zustand。非常丑陋,你实际上并不想这样做。但它允许我们在每个单独的组件从旧的状态管理系统迁移到新状态管理系统时测试应用程序。然后我们为每个组件发送了并行代理。每个组件都完成了,然后最后,一旦一切都使用了 Zustand,我们就能够移除所有脚手架,这样就再也没有提到 Redux,并且一切都在工作。但是有了这个脚手架,我们就可以在每个代理完成其工作时进行验证,只针对那个组件,我们可以验证应用程序仍然在工作,那个组件仍然在工作。我们不必一次性完成所有事情,我们从代理那里获得了一些人类反馈。
接下来我想谈谈上下文共享。当你进行像这样的一个大型项目时,你会学到东西,对吧?你会发现,“哦,我最初的心理模型实际上并不完整,我实际上并没有正确理解问题。”你的代理可能会遇到这种情况。你知道,你可能有一群代理,有 10 个代理在运行,它们都遇到了完全相同的问题,你想共享这个问题的解决方案,这样它们就不会都卡在那里,对吧?有许多不同的策略可以在代理之间进行这种上下文共享。
一种我认为最天真的策略是共享一切。基本上,每个代理都看到其他所有代理的上下文。这不太好。这基本上与让单个代理通过任务进行迭代相同。如果你这样做,你的上下文窗口会很快耗尽。所以,这样做不会有帮助。
一个更好的方法是让人类手动输入信息到代理中。如果你有一个带有每个代理的聊天消息、聊天窗口,你可以粘贴“嘿,使用库 1.2.3 而不是 1.2.2”。人类也可以修改代理 MD 或微代理来向这些代理传递消息。但这确实需要手动的人工。它需要更多的代理“看管”。所以它不是非常可扩展的。
你也可以让代理通过一个像 agent.md 这样的文件共享上下文。你可以让代理自己修改这个文件。也许当它们学到新东西时,它们会向这个文件发送一个拉取请求。缺点是代理有时会试图学习不重要的事情。它们可能会非常积极地将信息推送到这个文件。所以进行一些人工审查似乎有帮助。
最后,这可能是最前沿的想法。但你基本上可以给每个更改一个工具,让它能够向其他代理发送消息。它可以是一个广播消息,发送给所有其他代理。或者,你可以进行点对点对话。这非常有趣,可以进行实验。我们现在正在用我们的 SDK 进行大量实验。但是,要做好它很棘手。一旦你让代理相互通信,你就增加了系统的非确定性级别。事情可能会变得有点混乱。我右边有一个例子,这是来自一个医生报告,其中有两个代理只是相互交谈。它们陷入了互相祝愿完美状态的循环。
好了。现在我想进行一个练习。我很乐意让你们都跟着做。你可以访问这个演示文稿,用于复制粘贴目的,网址是 dev.openhands-workshop.com。我们将通过 OpenHands SDK 进行一些编码练习,特别是大规模进行 CVE 修复。我们将编写一个脚本,该脚本将接收一个 GitHub 存储库,扫描其中的开源漏洞(CVE),然后为我们找到的每个漏洞设置一个并行代理来解决它并打开一个拉取请求。
所以,dub.openhands-workshop.com。请告诉我任何人是否可以访问它。>> 那将是幻灯片。>> 所以,所以它应该是幻灯片,如果你愿意的话。大约在第 29 张幻灯片周围会有可复制粘贴的提示和链接等。>> 明白了。>> 我们会到达那里的。>> 所以,关于这个过程将如何进行,基本上我们将从一个代理开始,它将对这个存储库运行 CVE 扫描。它将扫描漏洞。使用代理进行此扫描的好处是,它可以查看存储库并决定如何扫描漏洞,对吧?是使用 Trivy 扫描 Docker 镜像?还是运行 npm audit on package.json?所以它可以检测编程语言来确定如何扫描 CVE。然后,一旦我们有了漏洞列表,我们将为每个单独的漏洞运行一个单独的代理。这些代理中的每一个都将研究它是否可解决。它将更新相关的依赖项,修复代码库中的任何破坏性 API 更改,然后打开一个拉取请求。
这样做的好处是,一旦准备好,我们就可以合并这些单独的 PR。>> 再显示一下链接。是的。>> 运行并行求解的好处是,你知道我们得到了很多不同的 PR。所以我们可以随时合并它们。如果一个代理卡住了,其中一个漏洞无法解决。所有其他的仍然会起作用。也许我们解决了 90% 或 95%。我们不必达到 100% 就能有价值。
只是一个快速的伪代码,说明它看起来会是这样。这是一个使用 OpenHands SDK 创建代理的示例。你可以看到我们创建了一个大型语言模型。然后我们将这个大型语言模型与一些工具一起传递给代理对象。终端、文件所有者、用于规划的路径跟踪器。我们给它一个工作区,然后我们只是告诉它我们想运行。这是一个非常基础的“hello world”示例,我们将看到在这个特定任务中它会变得多么复杂。但一旦第一个代理完成,我们将遍历所有返回的漏洞。然后对于每一个,我们将发送一个新的代理,要求它解决那个特定的 CVE。
好的。所以,要开始这里,我会说创建一个新的 GitHub 存储库。我们开始在那里保存我们的工作。你还需要一个 GitHub token 和一个 LLM token。我想,如果你注册 app.openhands.dev,你可以在那里获得 10 美元的免费 LLM 积分。如果你已经是现有用户,请告诉我,我可以增加你的现有积分,以便进行这个练习。然后我们将启动一个代理服务器。这是一个基本上像 Docker 容器的东西,它将容纳我们代理所做的工作。这是再次安全、可扩展地运行代理的好方法。所以,而不是在我们的本地机器上运行代理来解决所有这些 CVE,我们将在容器内运行它们。假设我们要处理数千个 CVE,我们可以将其运行在 Kubernetes 集群中,这样我们就可以拥有任意数量的代理工作站,但就这个练习而言,我们只会运行一个 Docker 容器作为我们代理的主机。然后我们可以创建一个 OpenHands 微代理,以便开始处理这个任务。我将在此过程中使用 OpenHands CLI。你也可以使用 Cursor、Pod Code 或你习惯使用的任何其他工具,当我们通过 OpenHands 进行 CVE 修复过程时。我会给它几分钟时间。我将完成创建我的 GitHub 存储库,获取我的 GitHub token 等等。如果你在设置过程中遇到任何问题,请随时举手,我会过来帮助你完成设置等等。你说 app.openhands.dev app。>> 是的。>> 所以,我在这里有了我的新 GitHub 存储库。>> 所以,我将在这里添加一个快速的 OpenHands 微代理。>> 完美。>> 我只是告诉它一个使用代理进行修复的流程。
OpenHands SDK 的相关工具位于 openhands.dev/sdk。所以,一些数据,OpenHands,一些上下文,类似于代理。我们现在已经有了获取 token 的官方方法。我实际上不会在这里这样做,因为那是我的 token,但你可以去 GitHub 设置,你的个人资料,然后开发者设置,个人访问 token。我喜欢做经典 token。经典 token。给它一个名字,然后你真正需要的是 repo 范围。这样我们就可以打开拉取请求来解决涉及的 CS。>> 我们做了经典 token,不是新的东西。我还没有收到链接,你欢迎这样做。我猜你可以创建一个新的存储库。>> 我也还没收到。所以,>> 我们需要什么权限?>> 只是 repo 权限。另外,它会显示你注册 app.openhands.dev。你可以在这里你的个人资料下的“设置”中获取你的 Open API 密钥,你的 LLM 密钥。我不会展示它,但这将允许你使用我们的代理。最后,我将启动一些代理服务器。你可能需要从演示文稿中复制粘贴。我的存储库已经关闭了。也许在这里。如果你想使用 OpenHands CLI 工具,请安装 OpenHands。我将启动 OpenHands CLI。再次,如果你愿意,你可以使用 Cloud Code、Cursor 或任何其他工具。
如果你需要更多时间来设置,比如获取 token 设置。抱歉,检查一下。所以,我将从第一个提示开始。基本上,我们将做的是,我们将把我们的代理指向 OpenHands SDK,指向文档,然后只是要求它基本上检查我们的 LLM API 密钥是否正常工作,它是否可以进行 LLM 删除。这将是一个非常基础的“hello world”,只是为了开始。我将告诉它,我正在使用 OpenHands。
我生成的密钥在 app.allands.dub。嗯,所以我告诉它使用这个 open handbon 4 模型。呃,如果你想使用普通的 anthropic API 密钥,你可以将其替换为 enthropic。根据你使用的是 open AI 还是 light,你可能需要稍微不同地设置这个模型。你可以查看 light all 文档,找出你是否有 open API 密钥或 open AI 密钥。呃,你可以查看 light all 文档,找出哪个模型适合这个字符串。但我只是会按原样复制粘贴。抱歉,agents.md 的步骤是什么,还是 open hands 的那个?>> 我会说,只需创建一个文件,如果你正在使用与该文件兼容的工具,则为 agents.md,或者对于 open hands,我们有一个称为 micro aent 的东西,我可以找到它。呃,所以 openhands.openhandsmicroagent 按约定,repo.mmd 是你所在存储库的描述。嗯,我刚刚给了它几个指向 SDK 文档和 SDK 存储库的链接,这样它就可以访问那里的 API 文档。这是一个可选的步骤。但它会让事情变得更容易。正在做。好的,它认为它得到了一个好东西。那么,让我们看看发生了什么。Python CV 解算器需要环境变量。我正在使用它来设置我的亮度。确保我不会将它们提交。再说一次。出现了一个小错误。看起来代理没有正确获取 API 文档。让我们把错误粘贴回去。看看会发生什么。再试一次。当然,永远不要。不在那里。她正在工作。版本。>> 让我们使用 club。UV 工具安装它会中断。>> 是的。>> 你知道你使用的是哪个版本的 UV 吗?>> 我使用的是 096.9.6。>> 你收到什么错误?>> 我不知道为什么。包 open hands 没有提供可执行文件。移除工具错误,未能安装入口点。>> 我是 Python 界的新手。所以我以为我做得很傻。>> 你可以尝试更新到 111,也就是我正在使用的版本。但好的。是的,我会试试。>> 另一个问题。>> 是的。>> 嗯,我能够,我看到你在 CLI 上运行。我能够在 all all.dev 上运行它。>> 是的。很酷。并提交了一个 PR 并创建了它。看起来不错。>> 太棒了。>> 你为什么要通过 CLI 进行?>> 嗯,主要是因为嗯,通常我实际上更喜欢在这里通过 Web UI 工作。嗯,我认为能够运行并展示那个脚本在本地工作。嗯,这就像一个更好的介绍。我实际上通常喜欢通过 Web UI 工作,然后让代理推送,如果我真的想在本地工作,我会在本地拉取,但认为这只是为了演示目的而增加了额外的步骤。是的,请随时使用 Web 或工具。看起来我这里的 API 密钥。天哪。200。>> 那是什么?>> 我们应该得到 200 吗?>> 嗯,是的,你应该得到这样的东西。嗯,就像我刚刚终于得到的那样,另一个说你好。只是一个部分。有人设法让连接工作了吗?>> 我想是的。我已经创建了文件。>> 很好。嗯,快速看一下这个样子,在第一个中,基本上你可以看到我们创建了一个告诉我们想要使用什么模型,我们想要使用什么密钥,然后发送一个快速消息给以确保它确实在工作。嗯,好的,第二次我将转向 prompt 2。嗯,在这里我们将开始为嗯,所以我们将告诉嗯,你知道我们正在与之合作的代理嗯,我们想使用 SDK 创建一个新代理,它将接收一个 GitHub 存储库。嗯,它将连接到一个正在 localhost 8000 上运行的远程工作区。再次,这是之前的 docker 启动命令。如果你还没有运行它,现在是启动 Docker 的好时机。嗯,Docker 运行这个代理服务器。嗯,它将克隆我们的存储库到那个 Docker 容器中。嗯,我们将创建一个将在那个 Docker 容器中工作的代理,我们将告诉那个代理扫描这个存储库中是否有任何与 open ends CLA 相关的内容。有没有办法中断并让它停止?>> 嗯,按 Control P 或暂停。是的。>> 然后我可以插入我的更正吗?>> 是的。然后你可以给我发条消息,或者只输入继续。是的。>> 我已经安装了 CLI,但我不得不添加 - AI。>> 看起来在 PI 上有一个 D AI 版本,但然后在文档中说。>> 我不认为 AI 版本已弃用,但它是一个可用的 CLI。你想使用那个服务,那个是我们团队的。你是否尝试过 dash AI 版本?因为我一运行它就崩溃了。哦。>> 哎呀。它安装了,我太高兴了。>> 是的,它安装了,然后它就没用了。>> 当我查看版本时,有一个弃用警告。所以,是的。>> 如果你想在我们的发布页面上下载一个可执行的二进制文件。>> 好的。>> 这可能很简单。你也可以在 Docker 容器中运行它。嗯,如果你的 CLI 文档,我认为也有一个 UV 运行。尝试 UV 运行。那个版本。不是 open AI 的那个常规版本。>> 好的。谢谢。好的,据称我有一个代理在这里工作,让我们看看。将用 repo 运行它。它应该包含一些 CV。让我们看看是否能找到任何易受攻击的默认。Open hands。我们将在这里可视化输出。所以,我们可以看到代理正在工作,即使使用 SDK。与我们看到的 CLI 非常相似。嗯,你可以看到它的任务列表。它是存储库。它本身没有三叶草。所以就像三叶草一样。它基本上在做我们期望代理做的事情。嗯,我们已经完成了一个任务,我们无法完成。所以,我们现在正在运行三叶草。展示一些关于这个生成代码的样子。嗯,你可以看到,我们在第一步中实例化了我们的 LLM。现在我们将这个 LLM 传递给一个代理。我们还给了它一个终端工具和一个文件编辑器工具。嗯,我们正在创建这个远程工作区,它连接到我们的 Docker 容器,以便代理可以在自己的环境中开始工作。嗯,我们创建了一个称为对话的东西,它基本上是代理在工作过程中将管理的上下文块。嗯,我们给它一个任务,并附有清晰的说明,告诉它应该做什么,然后发送那个任务。看起来那个初始扫描代理几乎完成了。看起来那个代理运行得很好。得到了这些结果。我将继续努力。我们有一个扫描漏洞的代理。嗯,所以接下来我要让它做的是基本上我们将进入环境并从中获取漏洞列表。嗯,我们的想法是我们将让它保存为 JSON 文件中的漏洞。嗯,然后我们可以使用工作区对象在 Docker 容器内运行 execute command 以获取那些漏洞。我们还有一些用于在工作区中操作文件的选项。嗯,然后现在我们只是遍历 vulnerabilities.json 文件,将其打印出来,这样我们就可以看到我们能够进入这个工作区并获取一些信息。好的。据称一切正常。看看会发生什么。羊。得到了一些漏洞结果。代理已完成。看看我们的脚本是否能取回结果。人。Jace 嗯,再说一次。>> 观察事件是什么?所以对于每一个状态,都有一个动作,然后是一个观察。所以它可能是运行这个命令,然后回来一个观察,其中包含该命令的输出。>> 嗯,它不仅仅是一个,它基本上是代理所采取的事件的整个轨迹,然后有两种事件:动作和观察。所以,每当我们与 LM 进行调用时,它都会返回一个要采取的动作,或者基本上是一个工具调用,然后观察结果就像一个工具调用。如果有人卡住了,我很乐意过来举手。第三。>> 很好。是的,看起来它正在打印 CV 列表。是的,看起来不错。为我们运行的每个脚本创建一个特定的子代理。你为什么一遍又一遍地重复同一个文件?>> 所以我们在这里通过五个提示进行的这个过程,这真的只是为了展示使用我们的 SDK 进行实际构建的感觉,对吧?嗯,这不是我工作的方式,如果我正在积极解决一个问题,我可能会这样工作,你知道我可以给你这个完整的打包代码库,预先构建好,对吧?是的,它有所有这些构建好的,但这是你问的吗?为什么我们一个接一个地粘贴这些提示?>> 最终我们会得到一个非常大的脚本,对吧?我们应该将其分解成几个单独的文件或部分。>> 是的,是的,是的,不,我认为组织这段代码肯定有更好的方法,而不是只有一个脚本,这只是为了演示目的而更容易。是的,我确实有一个演示存储库,我认为是 openhand CVE demo,它使用了特殊的类。有一个单一的,你知道,CVE 代理子类,它不仅仅是这个脚本。我们仍然在粘贴 JSON。似乎是的。焦点。我们中的足够多。这是个好问题。我们的 SC 是开源模型。我们实际上不知道我会做什么。好的。问题是,我的意思是>> 是的。加热。