|
|
@@ -26,54 +26,292 @@ uvicorn openhands.server.listen:app --host 0.0.0.0 --port 3000
|
|
|
|
|
|
尝试连接到之前的容器,如果之前保留了会话,可根据容器名连接: `async def connect(self):` , `await call_sync_from_async(self._attach_to_container)`
|
|
|
如果容器不存在,则初始化一个新容器: `await call_sync_from_async(self._init_container)`
|
|
|
-容器名来自前缀+sid `self.container_name = CONTAINER_NAME_PREFIX + sid` ,sid 来自 `listen.py` 会话或者 jwt_token `sid = get_sid_from_token(jwt_token, config.jwt_secret)` `sid = str(uuid.uuid4())`
|
|
|
-> ./openhands/runtime/impl/docker/docker_runtime.py
|
|
|
-> ./openhands/server/listen.py
|
|
|
+容器名来自前缀+sid `self.container_name = CONTAINER_NAME_PREFIX + sid` ,sid 来自 `listen.py` 会话或者 jwt_token
|
|
|
+```python
|
|
|
+async def connect(connection_id: str, environ, auth)
|
|
|
+
|
|
|
+event_stream = await session_manager.join_conversation
|
|
|
+
|
|
|
+async def maybe_start_agent_loop
|
|
|
|
|
|
-在 runtime 内部,`python -u -m openhands.runtime.action_execution_server` 用 api server 来操作文件
|
|
|
-添加所有插件 `plugins_to_load.append(ALL_PLUGINS[plugin]())`
|
|
|
-加载插件 ` (self._init_plugin(plugin) for plugin in self.plugins_to_load),`
|
|
|
-初始化插件 `await plugin.initialize(self.username)`
|
|
|
-> ./openhands/runtime/action_execution_server.py
|
|
|
+asyncio.create_task(session.initialize_agent(settings))
|
|
|
+
|
|
|
+await self.agent_session.start
|
|
|
+
|
|
|
+await self._create_runtime
|
|
|
+
|
|
|
+runtime_cls = get_runtime_cls
|
|
|
+
|
|
|
+class DockerRuntime(ActionExecutionClient)
|
|
|
+```
|
|
|
+> ./openhands/runtime/impl/docker/docker_runtime.py
|
|
|
+> ./openhands/server/listen_socket.py
|
|
|
|
|
|
-在 runtime 内部,初始化 vscode 插件。 `f'exec /openhands/.openvscode-server/bin/openvscode-server --host 0.0.0.0 --connection-token {self.vscode_connection_token} --port {self.vscode_port}\n'`
|
|
|
-port 地址是 port_mapping 在文件 `eventstream_runtime.py`中 `self.container = self.docker_client.containers.run` 时传参数
|
|
|
-> ./openhands/runtime/plugins/vscode/__init__.py
|
|
|
|
|
|
### runtime 内部的初始化
|
|
|
`self.container = self.docker_client.containers.run`
|
|
|
> OpenHands/openhands/runtime/action_execution_server.py
|
|
|
|
|
|
|
|
|
-### 文件操作
|
|
|
+### 文件修改
|
|
|
+
|
|
|
+```python
|
|
|
+# 行号:16
|
|
|
+await session_manager.send_to_event_stream
|
|
|
+await session.dispatch(data)
|
|
|
+```
|
|
|
+> OpenHands/openhands/server/listen_socket.py
|
|
|
+**添加事件**
|
|
|
+openhands/server/session/session.py
|
|
|
+testm/logs/openhands_2025-01-03.log:36129
|
|
|
+```python
|
|
|
+# 行号:18
|
|
|
+self.agent_session.event_stream.add_event(event, EventSource.USER)
|
|
|
+```
|
|
|
+
|
|
|
+**回调事件**:
|
|
|
+OpenHands/openhands/events/stream.py
|
|
|
+```python
|
|
|
+async def _process_queue(self)
|
|
|
+```
|
|
|
+
|
|
|
+**回调事件是如何被订阅的**
|
|
|
+参考 [###启动 runtime](###启动 runtime)
|
|
|
+
|
|
|
+OpenHands/openhands/server/listen_socket.py
|
|
|
+```python
|
|
|
+async def connect(connection_id: str, environ, auth)
|
|
|
+
|
|
|
+event_stream = await session_manager.join_conversation
|
|
|
+
|
|
|
+return await self.maybe_start_agent_loop(sid, settings)
|
|
|
+
|
|
|
+session = Session(
|
|
|
+ sid=sid, file_store=self.file_store, config=self.config, sio=self.sio
|
|
|
+ )
|
|
|
+self.agent_session = AgentSession(
|
|
|
+ sid, file_store, status_callback=self.queue_status_message
|
|
|
+ )
|
|
|
+# AgentSession event_stream
|
|
|
+self.event_stream = EventStream(sid, file_store)
|
|
|
+
|
|
|
+
|
|
|
+asyncio.create_task(session.initialize_agent(settings))
|
|
|
+
|
|
|
+await self.agent_session.start
|
|
|
+
|
|
|
+await self._create_runtime(
|
|
|
+
|
|
|
+runtime_cls = get_runtime_cls(runtime_name)
|
|
|
+# AgentSession event_stream
|
|
|
+self.runtime = runtime_cls(
|
|
|
+ config=config,
|
|
|
+ event_stream=self.event_stream,
|
|
|
+ sid=self.sid,
|
|
|
+ plugins=agent.sandbox_plugins,
|
|
|
+ status_callback=self._status_callback,
|
|
|
+ headless_mode=False,
|
|
|
+ )
|
|
|
+
|
|
|
+class DockerRuntime(ActionExecutionClient)
|
|
|
+
|
|
|
+class Runtime(FileEditRuntimeMixin)
|
|
|
+self.event_stream.subscribe(
|
|
|
+ EventStreamSubscriber.RUNTIME, self.on_event, self.sid
|
|
|
+ )
|
|
|
+
|
|
|
+async def _handle_action(self, event: Action)
|
|
|
+
|
|
|
+observation = getattr(self, action_type)(action)
|
|
|
+
|
|
|
+
|
|
|
+# 因为 DockerRuntime 继承了
|
|
|
+class DockerRuntime(ActionExecutionClient):
|
|
|
+
|
|
|
+OpenHands/openhands/runtime/impl/action_execution/action_execution_client.py
|
|
|
+class ActionExecutionClient(Runtime)
|
|
|
+return self.send_action_for_execution(action)
|
|
|
|
|
|
-1. 前端通过 WebSocket 发送文件更新请求到 `listen_socket.py`
|
|
|
- - 请求通过 `websocket_endpoint` 接收
|
|
|
- - 消息格式包含操作类型(创建/修改/删除)和文件内容
|
|
|
- - 消息被路由到对应的 session handler
|
|
|
+with send_request(
|
|
|
+ self.session,
|
|
|
+ 'POST',
|
|
|
+ f'{self._get_action_execution_server_host()}/execute_action',
|
|
|
+ json={'action': event_to_dict(action)},
|
|
|
+ # wait a few more seconds to get the timeout error from client side
|
|
|
+ timeout=action.timeout + 5,
|
|
|
+ ) as response:
|
|
|
+ output = response.json()
|
|
|
+ obs = observation_from_dict(output)
|
|
|
+ obs._cause = action.id # type: ignore[attr-defined]
|
|
|
|
|
|
-2. 在 `session_manager.py` 中处理请求
|
|
|
- - `init_or_join_session` 获取或创建对应会话
|
|
|
- - `handle_message` 方法解析消息类型
|
|
|
- - 文件操作请求被转发到 `agent_session`
|
|
|
|
|
|
-3. `agent_session.py` 处理文件操作
|
|
|
- - `process_file_operation` 方法处理具体操作
|
|
|
- - 创建/修改操作通过 `write_file` 方法
|
|
|
- - 删除操作通过 `delete_file` 方法
|
|
|
- - 所有操作通过 runtime 的 API 执行
|
|
|
+OpenHands/openhands/runtime/action_execution_server.py
|
|
|
+async def execute_action(action_request: ActionRequest):
|
|
|
|
|
|
-4. 文件操作通过 runtime 执行
|
|
|
- - 通过 `action_execution_server.py` 的 API 接口
|
|
|
- - 使用 `FileOperationsPlugin` 处理具体文件操作
|
|
|
- - 操作在容器内的 workspace 目录执行
|
|
|
- - 结果通过 WebSocket 返回给前端
|
|
|
+observation = await getattr(self, action_type)(action)
|
|
|
|
|
|
-5. 文件同步机制
|
|
|
- - 使用 inotify 监控文件变化
|
|
|
- - 变化通过 `FileWatcherPlugin` 处理
|
|
|
- - 重要变化会通知前端更新
|
|
|
- - 双向同步确保一致性
|
|
|
+```
|
|
|
+
|
|
|
+### CodeActAgent
|
|
|
+
|
|
|
+
|
|
|
+```python
|
|
|
+Agent.register('CodeActAgent', CodeActAgent)
|
|
|
+# CodeActAgent 默认使用插件
|
|
|
+class CodeActAgent(Agent):
|
|
|
+ sandbox_plugins: list[PluginRequirement] = [
|
|
|
+ # NOTE: AgentSkillsRequirement need to go before JupyterRequirement, since
|
|
|
+ # AgentSkillsRequirement provides a lot of Python functions,
|
|
|
+ # and it needs to be initialized before Jupyter for Jupyter to use those functions.
|
|
|
+ AgentSkillsRequirement(),
|
|
|
+ JupyterRequirement(),
|
|
|
+ ]
|
|
|
+
|
|
|
+agent = Agent.get_cls(agent_cls)(llm, agent_config)
|
|
|
+await self._create_runtime(
|
|
|
+ self.runtime = runtime_cls
|
|
|
+ self.controller = self._create_controller(
|
|
|
+
|
|
|
+controller = AgentController(
|
|
|
+
|
|
|
+async def _step(self) -> None:
|
|
|
+
|
|
|
+ action = self.agent.step(self.state)
|
|
|
+```
|
|
|
+
|
|
|
+OpenHands/openhands/events/stream.py
|
|
|
+```python
|
|
|
+def subscribe
|
|
|
+```
|
|
|
+
|
|
|
+event_stream 如何被订阅,**首先要了解初始化 event_stream 的过程**
|
|
|
+OpenHands/openhands/server/session/agent_session.py
|
|
|
+```python
|
|
|
+self.event_stream = EventStream(sid, file_store)
|
|
|
+```
|
|
|
+
|
|
|
+OpenHands/openhands/server/listen_socket.py
|
|
|
+```python
|
|
|
+async def connect(connection_id: str, environ, auth)
|
|
|
+
|
|
|
+ event_stream = await session_manager.join_conversation
|
|
|
+```
|
|
|
+OpenHands/openhands/server/session/manager.py
|
|
|
+```python
|
|
|
+return await self.maybe_start_agent_loop(sid, settings)
|
|
|
+
|
|
|
+session = Session(
|
|
|
+```
|
|
|
+**订阅事件 SERVER**
|
|
|
+OpenHands/openhands/server/session/session.py
|
|
|
+```python
|
|
|
+self.agent_session = AgentSession
|
|
|
+
|
|
|
+self.agent_session.event_stream.subscribe(
|
|
|
+ EventStreamSubscriber.SERVER, self.on_event, self.sid
|
|
|
+)
|
|
|
+```
|
|
|
+
|
|
|
+
|
|
|
+OpenHands/openhands/server/session/agent_session.py
|
|
|
+```python
|
|
|
+self.event_stream = EventStream(sid, file_store)
|
|
|
+```
|
|
|
+
|
|
|
+
|
|
|
+OpenHands/openhands/server/session/session.py
|
|
|
+```python
|
|
|
+self.event_stream.subscribe(
|
|
|
+ EventStreamSubscriber.AGENT_CONTROLLER, self.on_event, self.id
|
|
|
+)
|
|
|
+
|
|
|
+处理各类事件
|
|
|
+
|
|
|
+await self._handle_action(event)
|
|
|
+
|
|
|
+async def _handle_message_action(self, action: MessageAction)
|
|
|
+```
|
|
|
+
|
|
|
+
|
|
|
+1. **运行时创建 (`agent_session.py`)**:
|
|
|
+ - 根据指定的运行时名称初始化运行时环境。
|
|
|
+ - 连接到运行时并设置插件。
|
|
|
+ - 创建代理控制器以管理运行时中的任务执行。
|
|
|
+ > OpenHands/openhands/server/session/agent_session.py
|
|
|
+
|
|
|
+ ```python
|
|
|
+ # 行号:21
|
|
|
+ self._start_thread,
|
|
|
+ # 行号:22
|
|
|
+ await self._create_runtime
|
|
|
+ ```
|
|
|
+
|
|
|
+2. **Docker 运行时实现 (`docker_runtime.py`)**:
|
|
|
+ - 初始化和管理 Docker 容器,其中任务被执行。
|
|
|
+ - 处理启动或连接到现有 Docker 容器。
|
|
|
+ - 设置端口映射和环境变量。
|
|
|
+ - 确保容器准备好后再继续执行任务。
|
|
|
+ - 提供 VSCode URL 用于远程开发。
|
|
|
+ > OpenHands/openhands/runtime/impl/docker/docker_runtime.py
|
|
|
+
|
|
|
+ ```python
|
|
|
+ # 行号:29
|
|
|
+ self.container_name = CONTAINER_NAME_PREFIX + sid
|
|
|
+ # 行号:30
|
|
|
+ sid = get_sid_from_token(jwt_token, config.jwt_secret)
|
|
|
+ # 行号:31
|
|
|
+ sid = str(uuid.uuid4())
|
|
|
+ ```
|
|
|
+
|
|
|
+通过以上步骤,OpenHands 项目能够从前端接收到信息并启动 Docker 运行时,从而执行各种任务。这个流程确保了系统的灵活性和可扩展性,同时提供了强大的开发和调试工具支持。
|
|
|
+
|
|
|
+### 构建自己的 Runtime 镜像
|
|
|
+ - 启动新的会话或重用现有的会话。
|
|
|
+
|
|
|
+3. **代理会话初始化 (`session.py`)**:
|
|
|
+ - 初始化代理会话,设置必要的配置。
|
|
|
+ - 设置事件流订阅者以处理来自代理的事件。
|
|
|
+ - 启动运行时容器,在其中执行任务。
|
|
|
+
|
|
|
+4. **运行时创建 (`agent_session.py`)**:
|
|
|
+ - 根据指定的运行时名称初始化运行时环境。
|
|
|
+ - 连接到运行时并设置插件。
|
|
|
+ - 创建代理控制器以管理运行时中的任务执行。
|
|
|
+
|
|
|
+5. **Docker 运行时实现 (`docker_runtime.py`)**:
|
|
|
+ - 初始化和管理 Docker 容器,其中任务被执行。
|
|
|
+ - 处理启动或连接到现有 Docker 容器。
|
|
|
+ - 设置端口映射和环境变量。
|
|
|
+ - 确保容器准备好后再继续执行任务。
|
|
|
+ - 提供 VSCode URL 用于远程开发。
|
|
|
+
|
|
|
+##### 详细流程说明
|
|
|
+
|
|
|
+1. **前端通信 (`listen_socket.py`)**:
|
|
|
+ - 当前端通过 WebSocket 发送请求时,`listen_socket.py` 处理这些请求并验证用户身份。
|
|
|
+ - 如果验证通过,它会加入相应的会话(conversation),并通过事件流与客户端进行通信。
|
|
|
+
|
|
|
+2. **会话管理 (`manager.py`)**:
|
|
|
+ - `manager.py` 负责管理所有活动和已分离的会话。
|
|
|
+ - 它确保会话在多个服务器节点之间正确同步,并且可以重用现有的会话或启动新的会话。
|
|
|
+ - 例如,当一个新的 WebSocket 连接到来时,`manager.py` 会检查是否已经存在对应的会话,如果存在则重用,否则创建新的会话。
|
|
|
+
|
|
|
+3. **代理会话初始化 (`session.py`)**:
|
|
|
+ - `session.py` 初始化代理会话,设置必要的配置,如代理类型、最大迭代次数等。
|
|
|
+ - 它还设置了事件流订阅者,以便处理来自代理的事件。
|
|
|
+ - 最后,它调用 `agent_session.py` 中的方法来启动运行时容器。
|
|
|
+
|
|
|
+4. **运行时创建 (`agent_session.py`)**:
|
|
|
+ - `agent_session.py` 根据指定的运行时名称初始化运行时环境。
|
|
|
+ - 它尝试连接到现有的运行时容器,如果不存在则初始化一个新的容器。
|
|
|
+ - 它还会创建代理控制器,用于管理运行时中的任务执行。
|
|
|
+
|
|
|
+5. **Docker 运行时实现 (`docker_runtime.py`)**:
|
|
|
+ - `docker_runtime.py` 实现了基于 Docker 的运行时环境。
|
|
|
+ - 它负责启动或连接到 Docker 容器,并设置必要的端口映射和环境变量。
|
|
|
+ - 它还确保容器准备好后再继续执行任务,并提供 VSCode URL 用于远程开发。
|
|
|
+
|
|
|
+通过以上步骤,OpenHands 项目能够从前端接收到信息并启动 Docker 运行时,从而执行各种任务。这个流程确保了系统的灵活性和可扩展性,同时提供了强大的开发和调试工具支持。
|
|
|
|
|
|
|
|
|
## vscode
|