找回密码
 立即注册
首页 业界区 业界 AI Agent框架探秘:拆解 OpenHands(10)--- Runtime ...

AI Agent框架探秘:拆解 OpenHands(10)--- Runtime

数察啜 昨天 21:30
AI Agent框架探秘:拆解 OpenHands(10)---  Runtime


目录

  • AI Agent框架探秘:拆解 OpenHands(10)---  Runtime

    • 0x00 摘要
    • 0x01 工作机制

      • 1.1 Environment 和 Sandbox
      • 1.2 安全执行
      • 1.3 解决方案
      • 1.4 核心功能
      • 1.5 工作机制

    • 0x02 核心逻辑

      • 2.1. base.py
      • 2.2 ActionExecutionClient
      • 2.3 运行时类型

        • 2.3.1 Docker运行时环境
        • 2.3.2 本地运行时环境
        • 2.3.3 远程运行时环境
        • 2.3.4 单例模式

      • 2.4 工作流程
      • 2.5. Runtime与其他组件关系

        • 2.5.1 EventStream
        • 2.5.2 AgentController
        • 2.5.3 Session
        • 2.5.4 插件系统
        • 2.5.5 文件系统和存储
        • 2.5.6 git仓库
        • 2.5.7 Python & MCP


    • 0x03 代码

      • 3.1 定义&初始化
      • 3.2 关键代码

        • 3.2.1 环境初始化
        • 3.2.2 事件处理
        • 3.2.3 微代理支持


    • 0xFF 参考


0x00 摘要

对于Agent ,Google 白皮书给出一个简洁而实用的定义:Agent = 模型 + 工具 + 编排层 + 部署运行时,这里和目前大部分的 AI Agent 的定义(LLM + Tool + Memory)多了一层部署运行时。因此可见Runtime的重要性。
在 OpenHands 里,真正让“AI 想法”落地就是Runtime。它像一座可移动的实验室:四面墙把主机世界隔开,却给 Agent 留下齐全的操作台(文件、终端、网络)。Agent 只需递上一张写满指令的纸条,Runtime 便接管全部脏活:启动进程、捕捉回显、隔离风险、回收资源,再把结果封装成“观察报告”送回前台。由于所有动作都在统一沙箱里完成,外部系统既不用担心权限越界,也不必为环境差异头疼。
简言之,Runtime 是决策与执行之间的安全转换器,它是Agent与外部环境交互的桥梁,也是整个系统稳定运转的压舱石,保障了整个系统的高效协同运作。
因为本系列借鉴的文章过多,可能在参考文献中有遗漏的文章,如果有,还请大家指出。
0x01 工作机制

在当今数字化时代,代码的安全执行已成为至关重要的议题。OpenHands 项目为应对这一挑战,精心设计了一套基于 Docker 容器的沙盒系统,旨在为代码执行提供一个既安全又隔离的环境。这一系统不仅保障了代码的安全性,还确保了执行的一致性和可复现性,同时实现了资源的有效控制和不同项目之间的隔离。
1.1 Environment 和 Sandbox

Environment 指的是 Agent 可操作的容器,相当于给了 Agent 一台可自行操作的计算机,Agent 可以在其中端到端地完成任务,这个赛道包括 Sandbox、Browser Infra、Agent 操作系统等不同的细分领域。
其中,Sandbox 是一种安全机制,为执行中的程序提供隔离环境,即为 Agent 提供了一个可以隔离运行的虚拟机环境,开发者可以在这个环境中实现 Agent 的开发、部署、运行。但随着 Agent 的不断发展,传统的虚拟机并不能很好地满足 Agent 需求,原因在于:

  • Agent 对虚拟机的性能提出了更高的要求,比如需要更高的隔离性、更快的启动速度、更强的稳定性;
  • Agent 的虚拟机还要求具备一定的 AI 性能,例如需要具备代码解释器(code interpretor)的功能,或需要整合开发者常用的 AI 架构,如 Vercel AI SDK。
1.2 安全执行

代码安全执行的五大理由

  • 安全性:在执行不受信任的代码时,必须确保这些代码不会对主机系统造成损害。沙盒环境通过严格的访问控制,防止恶意代码访问或修改主机系统的资源,从而保护主机免受潜在威胁。
  • 一致性:沙盒环境确保代码在不同机器和配置下的执行结果具有一致性。这种一致性消除了“在我的机器上可以运行”的常见问题,使得代码在任何环境下都能稳定运行。
  • 资源控制:通过沙盒化,可以精确控制资源的分配和使用。这不仅防止了失控的进程对主机系统造成影响,还确保了资源的合理分配,提升了系统的整体性能。
  • 隔离性:不同的项目或用户可以在各自的隔离环境中工作,互不干扰。这种隔离性不仅保护了主机系统,还确保了不同项目之间的独立性,避免了资源竞争和潜在的冲突。
  • 可复现性:沙盒环境的一致性和可控性使得复现错误和问题变得更为容易。这在调试和问题解决过程中尤为重要,因为一致的环境可以确保问题的重现和解决。
1.3 解决方案

OpenHands 通过基于 Docker 容器的沙盒环境,为代码执行构建了一道坚固的“安全防线”。每个项目在启动时,系统会自动创建一个独立的 Docker 容器作为其专属沙盒。这个沙盒拥有隔离的文件系统、网络环境和资源配额,确保代码只能访问容器内部的文件,网络请求被限制在预设的安全域内,CPU 和内存的使用也受到严格管控。
这种隔离性带来了三重保障:

  • 避免项目间干扰:某一项目的代码错误(如无限循环导致的内存溢出)只会影响其所在的沙盒,不会波及其他项目或主机系统。
  • 防范恶意代码风险:即使 AI 生成的代码中包含潜在的危险操作(如删除系统文件),也会被沙盒环境拦截,无法对主机造成实质损害。
  • 简化环境一致性管理:沙盒的基础镜像可以预先配置好特定版本的编程语言、依赖库和工具链,确保代码在开发、测试、生产环境中的执行结果一致,避免了“在我电脑上能运行”的问题。
尽管沙盒环境提供了高度的安全性和隔离性,但它并非一个孤立的“孤岛”。OpenHands 框架提供了精细的“端口映射”和“目录挂载”机制,允许开发者将沙盒内的特定端口暴露给主机(便于调试),或将主机的指定目录挂载到沙盒中(用于输入输出文件传递)。这种机制在确保安全性的同时,也提供了足够的灵活性,使得开发和调试过程更加高效。
通过这种设计,OpenHands 不仅确保了代码的安全执行,还提升了开发效率和环境管理的便利性。这种平衡是现代软件开发中不可或缺的一部分,为开发者提供了一个既安全又高效的开发环境。
1.4 核心功能

Runtime的核心功能可以概括为四个主要方面:

  • 工作环境的构建与管理:Runtime负责创建和管理代理的工作区域,无论是隔离性更强的容器环境还是便捷的本地环境,都能根据需求提供定制化的工作空间,确保代理在执行任务时不会受到外部因素的干扰。
  • 动作的执行:代理发出的指令,如文件编辑、命令执行等,都由Runtime解析并精确执行,它充当了决策与实践之间的桥梁。
  • 环境变量的维护:Runtime负责维护任务执行所需的环境变量,为任务执行提供必要的配置支持。
  • 环境的生命周期管理:Runtime全程管理环境的生命周期,从初始化到断开连接,形成完整的闭环。同时,通过EventStream实时输出执行日志和观察结果,为控制器、记忆系统、MCP等组件提供关键的状态反馈,确保整个系统的协同运作。
1.5 工作机制

OpenHands 运行时系统采用基于 Docker 容器实现的客户端 - 服务器架构,其工作机制概述如下:

  • 用户输入:用户提供一个自定义的基础 Docker 镜像。
  • 镜像构建:OpenHands 以用户提供的镜像为基础,构建一个新的 Docker 镜像(即 “OH 运行时镜像”)。该新镜像包含 OpenHands 专属代码,核心为 “运行时客户端”。
  • 容器启动:当 OpenHands 启动时,会使用 OH 运行时镜像启动一个 Docker 容器。
  • 动作执行服务器初始化:动作执行服务器在容器内部初始化一个 ActionExecutor(动作执行器),配置必要组件(如 Bash Shell)并加载指定的插件。
  • 通信过程:OpenHands 后端通过 RESTful API 与动作执行服务器通信,发送动作指令并接收执行反馈数据。
  • 动作执行:运行时客户端接收来自后端的动作指令,在沙箱环境中执行这些指令,并将执行反馈数据回传。
  • 反馈数据返回:动作执行服务器将执行结果以反馈数据(Observation)的形式发送回 OpenHands 后端。
客户端的核心作用:

  • 作为 OpenHands 后端与沙箱环境之间的中间媒介,实现双向数据传递。
  • 在容器内安全执行各类动作指令(包括 Shell 命令、文件操作、Python 代码等)。
  • 管理沙箱环境的状态,涵盖当前工作目录和已加载插件等信息。
  • 对反馈数据进行格式化处理后返回给后端,为结果处理提供统一的接口规范。
工作机制参见下图。
1.png

0x02 核心逻辑

Runtime是在用户交互期间为用户的智能体应用程序提供动力的底层引擎。它是一个系统,接收用户定义的智能体、工具和回调,并协调它们对用户输入的执行,管理信息流、状态变化以及与外部服务(如 LLM 或存储)的交互。可以将运行时视为你的智能体应用程序的"引擎"。用户定义部件(智能体、工具),而运行时处理它们如何连接并一起运行以满足用户请求。
Runtime支持多种执行环境,包括Docker容器、本地环境等,使Agent能够安全地执行代码和命令。其派生类有:DockerRuntime、RemoteRuntime、LocalRuntime、KubernetesRuntime、CLIRuntime。
核心功能

  • 命令执行:提供Bash shell访问能力。
  • 浏览器交互:支持网页浏览和交互操作。
  • 文件系统操作:文件读写,编辑等操作。
  • git 操作管理:仓库克隆、分支管理、变更跟踪。
  • 环境变量管理:运行时环境变量配置。
  • 插件系统管理:支持VSCode、Jupyter等插件集成。
2.1. base.py

base.py 文件定义了Runtime类,它作为代理与外部环境交互的主要接口。它处理各种操作,包括:

  • 沙盒执行
  • 浏览器交互
  • 文件系统操作
  • 环境变量管理
  • 插件管理
Runtime类的关键特性:

  • 使用配置和事件流进行初始化
  • 使用ainit方法异步初始化环境变量
  • 针对不同类型的操作执行方法(运行、读取、写入、浏览等)
  • 文件操作的抽象方法(由子类实现)
2.2 ActionExecutionClient

action_execution_client.py 文件包含ActionExecutionClient类,它实现了运行时接口。它是一个抽象实现,意味着仍需要通过具体实现来扩展才能使用。
此客户端通过HTTP调用与action_execution_server交互以实际执行运行时操作。
  1. class ActionExecutionClient(Runtime):
  2.     """Base class for runtimes that interact with the action execution server.
  3.     This class contains shared logic between DockerRuntime and RemoteRuntime
  4.     for interacting with the HTTP server defined in action_execution_server.py.
  5.     """
  6.     def __init__(
  7.         self,
  8.         config: OpenHandsConfig,
  9.         event_stream: EventStream,
  10.         llm_registry: LLMRegistry,
  11.         sid: str = 'default',
  12.         plugins: list[PluginRequirement] | None = None,
  13.         env_vars: dict[str, str] | None = None,
  14.         status_callback: Any | None = None,
  15.         attach_to_existing: bool = False,
  16.         headless_mode: bool = True,
  17.         user_id: str | None = None,
  18.         git_provider_tokens: PROVIDER_TOKEN_TYPE | None = None,
  19.     ):
  20.         self.session = HttpSession()
  21.         self.action_semaphore = threading.Semaphore(1)  # Ensure one action at a time
  22.         self._runtime_closed: bool = False
  23.         self._vscode_token: str | None = None  # initial dummy value
  24.         self._last_updated_mcp_stdio_servers: list[MCPStdioServerConfig] = []
  25.         super().__init__(
  26.             config,
  27.             event_stream,
  28.             llm_registry,
  29.             sid,
  30.             plugins,
  31.             env_vars,
  32.             status_callback,
  33.             attach_to_existing,
  34.             headless_mode,
  35.             user_id,
  36.             git_provider_tokens,
  37.         )
复制代码
其余的所有Runtime均继承 ActionExecutionClient
  1. class KubernetesRuntime(ActionExecutionClient):
  2. class LocalRuntime(ActionExecutionClient):
  3. class RemoteRuntime(ActionExecutionClient):
  4. class DockerRuntime(ActionExecutionClient):
复制代码
2.3 运行时类型

2.3.1 Docker运行时环境

为使用Docker容器设计的Docker运行时环境,OpenHands做如下配置:

  • 为每个会话创建和管理Docker容器
  • 在容器内执行动作
  • 支持直接文件系统访问和本地资源管理
  • 适合开发、测试和需要完全控制执行环境的场景
Docker运行时环的关键特性为:

  • 实时日志和调试能力
  • 直接访问本地文件系统
  • 由于本地资源执行速度更快
  • 容器隔离以提高安全性
2.3.2 本地运行时环境

本地运行时环境设计用于本地机器上的直接执行。目前仅支持以本地用户身份运行:

  • 直接在主机上运行action_execution_server
  • 无Docker容器开销
  • 直接访问本地系统资源
  • 适合Docker不可用或不需要开发和测试
关键特性:

  • 需要的设置最少
  • 直接访问本地资源
  • 无容器开销
  • 最快执行速度
重要:此运行时不提供隔离,因为它直接在主机上运行。所有动作以运行OpenHands的用户的相同权限执行。对于需要适当隔离的安全执行,请改用Docker运行时。
2.3.3 远程运行时环境

远程运行时环境设计用于远程环境中的执行:

  • 连接到运行ActionExecutor的远程服务器
  • 通过向远程客户端发送请求执行动作
  • 支持分布式执行和基于云的部署
  • 适合生产环境、可扩展性和本地资源限制是问题的场景
关键特性:

  • 可扩展性和资源灵活性
  • 减少本地资源使用
  • 支持基于云的部署
  • 通过隔离提高安全性的潜力
目前,这主要用于并行评估,例如这个SWE-Bench示例。
2.3.4 单例模式

Runtime的子类不是单例模式。从函数_create_runtime 可以看出,每次运行都会创建一个新的实例。每个Runtime都有自己的状态,如sid(会话ID)。每个实例也可以调用close()来清理资源。
  1.     async def _create_runtime(
  2.         self,
  3.         runtime_name: str,
  4.         config: OpenHandsConfig,
  5.         agent: Agent,
  6.         git_provider_tokens: PROVIDER_TOKEN_TYPE | None = None,
  7.         custom_secrets: CUSTOM_SECRETS_TYPE | None = None,
  8.         selected_repository: str | None = None,
  9.         selected_branch: str | None = None,
  10.     ) -> bool:
  11.         """Creates a runtime instance
  12.         Parameters:
  13.         - runtime_name: The name of the runtime associated with the session
  14.         - config:
  15.         - agent:
  16.         Return True on successfully connected, False if could not connect.
  17.         Raises if already created, possibly in other situations.
  18.         """
  19.             self.runtime = runtime_cls(
  20.                 config=config,
  21.                 event_stream=self.event_stream,
  22.                 llm_registry=self.llm_registry,
  23.                 sid=self.sid,
  24.                 plugins=agent.sandbox_plugins,
  25.                 status_callback=self._status_callback,
  26.                 headless_mode=False,
  27.                 attach_to_existing=False,
  28.                 env_vars=env_vars,
  29.                 git_provider_tokens=git_provider_tokens,
  30.             )
  31.         return True
复制代码
2.4 工作流程

Runtime作为EventStreamSubscriber.RUNTIME订阅者,处理来自事件流的Action并生成Observation。Runtime的工作流程如下:

  • 初始化

    • Runtime使用配置和事件流进行初始化。
    • 设置环境变量。
    • 加载并初始化插件。

  • 动作处理

    • Runtime通过事件流接收动作。
    • 验证并路由到适当的执行方法。

  • 动作执行

    • 执行不同类型的动作:

      • 使用run方法执行bash命令
      • 使用run_ipython方法执行IPython单元
      • 使用read和write方法执行文件操作
      • 使用browse和browse_interactive方法浏览网页


  • 观察生成

    • 动作执行后,生成相应的观察结果。
    • 观察结果被添加到事件流中。

  • 插件集成

    • 插件如Jupyter和AgentSkills被初始化并集成到运行时。

  • 沙盒环境

    • ActionExecutor在Docker容器内设置沙盒环境。
    • 初始化用户环境和bash shell。
    • 从OpenHands后端接收的动作在此沙盒环境中执行。

  • 浏览器交互

    • 使用BrowserEnv类处理网络浏览动作。

2.5. Runtime与其他组件关系

Runtime与其他组件的主要关系如下:

  • 运行时与openhands.events模块中定义的事件系统紧密交互。
  • 依赖于openhands.core.config中的配置类。
  • 日志通过openhands.core.logger处理。
我们详细看看。
2.5.1 EventStream

Runtime通过与EventStream与其他模块进行事件驱动的交互。
  1. class Runtime(FileEditRuntimeMixin):     
  2.      # Runtime订阅事件流
  3.      event_stream.subscribe(
  4.                         EventStreamSubscriber.RUNTIME, self.on_event, self.sid
  5.             )
  6.      # Runtime处理传入的事件      
  7.      def on_event(self, event: Event) -> None:
  8.         if isinstance(event, Action):
  9.             asyncio.get_event_loop().run_until_complete(self._handle_action(event))           # Runtime返回观察结果给事件流
  10.     self.event_stream.add_event(observation, source)
复制代码
2.5.2 AgentController

AgentController通过EventStream向Runtime发送操作命令,Runtime执行后将结果返回。

  • AgentController 发送Action  → EventStream
  • Runtime 接到 Action 后执行
  • Rutime 发送 Observation  → EventStream
  • AgentController  接收到 Observation  并继续执行决策流程
2.5.3 Session

WebSession 和 AgentSession 负责管理 Runtime 的生命周期
  1. # AgentSession
  2. runtime_cls = get_runtime_cls(runtime_name)
  3. # 创建Runtime实例
  4. self.runtime = runtime_cls(
  5.                 config=config,
  6.                 event_stream=self.event_stream,
  7.                 llm_registry=self.llm_registry,
  8.                 sid=self.sid,
  9.                 plugins=agent.sandbox_plugins,
  10.                 status_callback=self._status_callback,
  11.                 headless_mode=False,
  12.                 attach_to_existing=False,
  13.                 git_provider_tokens=overrided_tokens,
  14.                 env_vars=env_vars,
  15.                 user_id=self.user_id,
  16.             )
  17. # 连接到Runtime
  18. await self.runtime.connect()
  19. # 关闭Runtime
  20. EXECUTOR.submit(self.runtime.close)
复制代码
2.5.4 插件系统

Runtime管理和执行各种插件功能。
  1. # 初始化加载插件
  2.         self.plugins = (
  3.             copy.deepcopy(plugins) if plugins is not None and len(plugins) > 0 else []
  4.         )
  5.         # add VSCode plugin if not in headless mode
  6.         if not headless_mode:
  7.             self.plugins.append(VSCodeRequirement())
  8.             
  9. # 执行插件相关操作
  10.         if any(isinstance(plugin, JupyterRequirement) for plugin in self.plugins):
  11.             code = 'import os\n'
  12.             for key, value in env_vars.items():
  13.                 # Note: json.dumps gives us nice escaping for free
  14.                 code += f'os.environ["{key}"] = {json.dumps(value)}\n'
  15.             code += '\n'
  16.             self.run_ipython(IPythonRunCellAction(code))
  17.             ......
复制代码
2.5.5 文件系统和存储

Runtime 提供文件操作能力。
  1.     def read(self, action: FileReadAction) -> Observation:
  2.     def write(self, action: FileWriteAction) -> Observation:
复制代码
2.5.6 git仓库

Runtime 提供git仓库操作能力。
  1.     async def clone_or_init_repo(
  2.         self,
  3.         git_provider_tokens: PROVIDER_TOKEN_TYPE | None,
  4.         selected_repository: str | None,
  5.         selected_branch: str | None,
  6.     ) -> str:
复制代码
2.5.7 Python & MCP

Runtime 提供运行python代码和call_tool_mcp操作能力。
  1. def run_ipython(self, action: IPythonRunCellAction) -> Observation:
  2. async def call_tool_mcp(self, action: MCPAction) -> Observation:
复制代码
0x03 代码

3.1 定义&初始化
  1. class Runtime(FileEditRuntimeMixin):
  2.     """智能代理运行时环境的抽象基类。
  3.     这是OpenHands中的一个扩展点,允许应用程序自定义代理与外部环境的交互方式。
  4.     该运行时提供一个沙箱环境,包含以下功能:
  5.     - Bash shell访问
  6.     - 浏览器交互
  7.     - 文件系统操作
  8.     - Git操作
  9.     - 环境变量管理
  10.     应用程序可通过以下方式替换为自定义实现:
  11.     1. 创建一个继承自Runtime的类
  12.     2. 实现所有必需的方法
  13.     3. 在配置中设置运行时名称或使用get_runtime_cls()方法
  14.     该类通过get_runtime_cls()中的get_impl()方法实例化。
  15.     内置实现包括:
  16.     - DockerRuntime:基于Docker的容器化环境
  17.     - RemoteRuntime:远程执行环境
  18.     - LocalRuntime:用于开发的本地执行环境
  19.     - KubernetesRuntime:基于Kubernetes的执行环境
  20.     - CLIRuntime:命令行界面运行时
  21.     参数:
  22.         sid:唯一标识当前用户会话的会话ID
  23.     """
  24.     sid: str  # 会话ID,唯一标识用户会话
  25.     config: OpenHandsConfig  # OpenHands配置对象
  26.     initial_env_vars: dict[str, str]  # 初始环境变量字典
  27.     attach_to_existing: bool  # 是否连接到现有运行时环境
  28.     status_callback: Callable[[str, RuntimeStatus, str], None] | None  # 状态回调函数,接收会话ID、运行时状态和消息
  29.     runtime_status: RuntimeStatus | None  # 当前运行时状态
  30.     _runtime_initialized: bool = False  # 运行时初始化状态标记
  31.     security_analyzer: 'SecurityAnalyzer | None' = None  # 安全分析器实例,用于检测潜在风险
  32.     def __init__(
  33.         self,
  34.         config: OpenHandsConfig,
  35.         event_stream: EventStream,
  36.         llm_registry: LLMRegistry,
  37.         sid: str = 'default',
  38.         plugins: list[PluginRequirement] | None = None,
  39.         env_vars: dict[str, str] | None = None,
  40.         status_callback: Callable[[str, RuntimeStatus, str], None] | None = None,
  41.         attach_to_existing: bool = False,
  42.         headless_mode: bool = False,
  43.         user_id: str | None = None,
  44.         git_provider_tokens: PROVIDER_TOKEN_TYPE | None = None,
  45.     ):
  46.         # 初始化Git处理器,绑定shell执行和文件创建的回调方法
  47.         self.git_handler = GitHandler(
  48.             execute_shell_fn=self._execute_shell_fn_git_handler,  # Git操作所需的shell执行函数
  49.             create_file_fn=self._create_file_fn_git_handler,  # Git操作所需的文件创建函数
  50.         )
  51.         # 初始化会话ID
  52.         self.sid = sid
  53.         # 绑定事件流(组件间通信的核心)
  54.         self.event_stream = event_stream
  55.         # 若事件流存在,订阅运行时相关事件
  56.         if event_stream:
  57.             event_stream.subscribe(
  58.                 EventStreamSubscriber.RUNTIME,  # 订阅者类型(运行时)
  59.                 self.on_event,  # 事件处理回调函数
  60.                 self.sid  # 订阅者ID(当前会话ID)
  61.             )
  62.         # 初始化插件列表(深拷贝传入的插件,为空时设为默认空列表)
  63.         self.plugins = (
  64.             copy.deepcopy(plugins) if plugins is not None and len(plugins) > 0 else []
  65.         )
  66.         # 非无头模式下添加VSCode插件
  67.         if not headless_mode:
  68.             self.plugins.append(VSCodeRequirement())
  69.         # 绑定状态回调函数
  70.         self.status_callback = status_callback
  71.         # 记录是否连接到现有运行时
  72.         self.attach_to_existing = attach_to_existing
  73.         # 深拷贝配置对象(避免外部修改影响内部状态)
  74.         self.config = copy.deepcopy(config)
  75.         # 注册程序退出时的关闭回调(确保资源正确释放)
  76.         atexit.register(self.close)
  77.         # 初始化默认环境变量(基于沙箱配置)
  78.         self.initial_env_vars = _default_env_vars(config.sandbox)
  79.         # 合并用户传入的环境变量(覆盖默认值)
  80.         if env_vars is not None:
  81.             self.initial_env_vars.update(env_vars)
  82.         # 初始化Provider处理器(管理Git等服务的访问令牌)
  83.         self.provider_handler = ProviderHandler(
  84.             provider_tokens=git_provider_tokens
  85.             or cast(PROVIDER_TOKEN_TYPE, MappingProxyType({})),  # 令牌为空时使用空映射
  86.             external_auth_id=user_id,  # 外部认证ID(关联用户)
  87.             external_token_manager=True,  # 启用外部令牌管理
  88.         )
  89.         # 同步调用异步方法获取Provider相关环境变量
  90.         raw_env_vars: dict[str, str] = call_async_from_sync(
  91.             self.provider_handler.get_env_vars,  # 获取环境变量的异步方法
  92.             GENERAL_TIMEOUT,  # 超时时间
  93.             True,  # 允许重试
  94.             None,  # 重试间隔(使用默认)
  95.             False  # 不抛出异常
  96.         )
  97.         # 合并Provider返回的环境变量
  98.         self.initial_env_vars.update(raw_env_vars)
  99.         # 检查是否启用VSCode插件(通过判断插件列表中是否包含VSCodeRequirement实例)
  100.         self._vscode_enabled = any(
  101.             isinstance(plugin, VSCodeRequirement) for plugin in self.plugins
  102.         )
  103.         # 初始化文件编辑混合类(提供文件编辑相关功能)
  104.         FileEditRuntimeMixin.__init__(
  105.             self,
  106.             enable_llm_editor=config.get_agent_config().enable_llm_editor,  # 是否启用LLM辅助编辑
  107.             llm_registry=llm_registry,  # LLM注册中心(用于获取语言模型实例)
  108.         )
  109.         # 记录用户ID
  110.         self.user_id = user_id
  111.         # 记录Git服务提供商令牌
  112.         self.git_provider_tokens = git_provider_tokens
  113.         # 初始化运行时状态(默认为None)
  114.         self.runtime_status = None
  115.         # 初始化安全分析器(若配置启用)
  116.         self.security_analyzer = None
  117.         if self.config.security.security_analyzer:
  118.             # 根据配置获取安全分析器类(默认使用SecurityAnalyzer)
  119.             analyzer_cls = options.SecurityAnalyzers.get(
  120.                 self.config.security.security_analyzer, SecurityAnalyzer
  121.             )
  122.             # 实例化安全分析器
  123.             self.security_analyzer = analyzer_cls()
  124.             # 为安全分析器绑定事件流(用于发布安全相关事件)
  125.             self.security_analyzer.set_event_stream(self.event_stream)
复制代码
3.2 关键代码

3.2.1 环境初始化

setup_initial_env提供了环境初始化能力
  1.     def setup_initial_env(self) -> None:
  2.         if self.attach_to_existing:
  3.             return
  4.         logger.debug(f'Adding env vars: {self.initial_env_vars.keys()}')
  5.         self.add_env_vars(self.initial_env_vars)
  6.         if self.config.sandbox.runtime_startup_env_vars:
  7.             self.add_env_vars(self.config.sandbox.runtime_startup_env_vars)
  8.         # Configure git settings
  9.         self._setup_git_config()
复制代码
3.2.2 事件处理


  • on_event函数接受来自事件流的 Action,_handle_action函数执行对应的操作,生成Observation。
  1.     def on_event(self, event: Event) -> None:
  2.         if isinstance(event, Action):
  3.             asyncio.get_event_loop().run_until_complete(self._handle_action(event))
复制代码
3.2.3 微代理支持


  • get_microagents_from_selected_repo 从仓库加载微代理配置
  • get_microagents_from_org_or_user
比如get_microagents_from_org_or_user是 OpenHands 系统中组织 / 用户级微智能体加载的核心逻辑,负责从代码仓库的组织或用户级配置仓库中加载微智能体(轻量级智能体组件)。主要功能包括:

  • 仓库路径解析:从目标仓库路径中提取组织 / 用户名,确定配置仓库位置。
  • 平台适配:区分 GitLab 和其他平台(如 GitHub),使用不同的配置仓库名称(GitLab 用 openhands-config,其他用 .openhands)。
  • 仓库克隆:通过带认证的 URL 克隆配置仓库,采用浅克隆(--depth 1)提高效率。
  • 微智能体加载:从克隆仓库的 microagents 目录加载微智能体,并在加载完成后清理临时文件。
  • 异常处理:针对认证失败、克隆错误等场景进行日志记录,确保流程稳健性。
流程如下
2.png

代码如下:
  1.     def get_microagents_from_org_or_user(
  2.         self, selected_repository: str
  3.     ) -> List[BaseMicroagent]:
  4.         """从组织或用户级仓库加载微智能体。
  5.         例如:若目标仓库为 github.com/acme-co/api,会检查 github.com/acme-co/.openhands 是否存在。
  6.         若存在,会克隆该仓库并从 ./microagents/ 文件夹加载微智能体。
  7.         对于 GitLab 仓库,会使用 openhands-config 而非 .openhands,因为 GitLab 不支持
  8.         以非字母数字字符开头的仓库名称。
  9.         参数:
  10.             selected_repository: 仓库路径(例如:"github.com/acme-co/api")
  11.         返回:
  12.             从组织/用户级仓库加载的微智能体列表
  13.         """
  14.         loaded_microagents: List[BaseMicroagent] = []
  15.         # 拆分仓库路径为多个部分(按 '/' 分割)
  16.         repo_parts = selected_repository.split('/')
  17.         # 校验路径格式:至少需要包含域名、组织/用户名、仓库名三部分(如 github.com/org/repo)
  18.         if len(repo_parts) < 2:
  19.             return loaded_microagents
  20.         # 提取组织/用户名(路径中倒数第二部分)
  21.         org_name = repo_parts[-2]
  22.         # 判断是否为 GitLab 仓库
  23.         is_gitlab = self._is_gitlab_repository(selected_repository)
  24.         # 确定组织级配置仓库名称:GitLab 用 openhands-config,其他用 .openhands
  25.         if is_gitlab:
  26.             org_openhands_repo = f'{org_name}/openhands-config'
  27.         else:
  28.             org_openhands_repo = f'{org_name}/.openhands'
  29.         # 尝试克隆组织级配置仓库
  30.         try:
  31.             # 创建组织仓库的临时目录(避免冲突)
  32.             org_repo_dir = self.workspace_root / f'org_openhands_{org_name}'
  33.             # 获取带认证的仓库 URL 并执行浅克隆(--depth 1 提高效率)
  34.             try:
  35.                 # 同步调用异步方法获取认证 URL(带超时控制)
  36.                 remote_url = call_async_from_sync(
  37.                     self.provider_handler.get_authenticated_git_url,
  38.                     GENERAL_TIMEOUT,
  39.                     org_openhands_repo,
  40.                 )
  41.             except AuthenticationError as e:
  42.                 raise  # 重新抛出认证异常,终止当前流程
  43.             except Exception as e:
  44.                 raise  # 重新抛出其他异常
  45.             # 构建克隆命令:禁用终端交互提示,浅克隆到临时目录
  46.             clone_cmd = (
  47.                 f'GIT_TERMINAL_PROMPT=0 git clone --depth 1 {remote_url} {org_repo_dir}'
  48.             )
  49.             # 执行克隆命令
  50.             action = CmdRunAction(command=clone_cmd)
  51.             obs = self.run_action(action)
  52.             # 检查克隆结果:退出码为 0 表示成功
  53.             if isinstance(obs, CmdOutputObservation) and obs.exit_code == 0:
  54.                 # 从组织仓库的 microagents 目录加载微智能体
  55.                 org_microagents_dir = org_repo_dir / 'microagents'
  56.                 loaded_microagents = self._load_microagents_from_directory(
  57.                     org_microagents_dir, 'org-level'
  58.                 )
  59.                 # 清理临时目录:加载完成后删除克隆的仓库
  60.                 action = CmdRunAction(f'rm -rf {org_repo_dir}')
  61.                 self.run_action(action)
  62.             else:
  63.                 # 克隆失败:提取错误信息和退出码
  64.                 clone_error_msg = (
  65.                     obs.content
  66.                     if isinstance(obs, CmdOutputObservation)
  67.                     else 'Unknown error'
  68.                 )
  69.                 exit_code = (
  70.                     obs.exit_code if isinstance(obs, CmdOutputObservation) else 'N/A'
  71.                 )
  72.         return loaded_microagents
复制代码
0xFF 参考

https://docs.all-hands.dev/openhands/usage/architecture/runtime
Agent Infra 图谱:哪些组件值得为 Agent 重做一遍?

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册