Open4

OpenHandsのコードリーディング

MaronMaron

OpenHandsは旧名称(OpenDevin)から分かるように、Devinのオープンソース版。
最近流行っているDevinやClineなど、ソフトウェア開発に特化した自律型AIエージェントの作りが気になるので、実際のユーザーの操作に合わせてどのコードが動いているのか読み解いてみる。

レポジトリ

https://github.com/All-Hands-AI/OpenHands

アーキテクチャ概要

OpenHandsのアーキテクチャ

MaronMaron

バックエンド起動

make-backendでバックエンドを起動してみる。
バックエンドだけ起動するのかなと思いきや、普通にフロントエンドも起動していた。http://127.0.0.1:3000でアクセスできる。

ただ、動かそうとするとmemory/condenser周りでエラーが起きた。
memory/condenser/__init__.pyを下記のように修正すると、動くようになった。

__init__.py
from openhands.memory.condenser.condenser import Condenser, get_condensation_metadata
from openhands.memory.condenser.impl import (
    AmortizedForgettingCondenser,
    LLMAttentionCondenser,
    ImportantEventSelection,
    LLMSummarizingCondenser,
    NoOpCondenser,
    ObservationMaskingCondenser,
    RecentEventsCondenser,
)

__all__ = [
    'Condenser',
    'get_condensation_metadata',
    'AmortizedForgettingCondenser',
    'LLMAttentionCondenser',
    'ImportantEventSelection',
    'LLMSummarizingCondenser',
    'NoOpCondenser',
    'ObservationMaskingCondenser',
    'RecentEventsCondenser',
]

フロントエンド起動

make-frontendでフロントエンドを起動してみる。
ssr周りの設定でエラーが起きる。
vite.config.tsのssrの部分を下記のように書き換えれば、起動できた。

{
  // ...
  ssr: {
  noExternal: [
    "react-syntax-highlighter",
    "react-textarea-autosize",
    "use-composed-ref",
  ],
  // ...
}

UIをガチャガチャしたいわけではないので、make-backendで起動するだけで良さそう。

MaronMaron

タスク開始(会話開始)

/api/conversationsリクエスト:

  • _update_timestamp_for_conversationは会話データをConversationStoreに引渡し、同期させている。

conversation_manager.maybe_start_agent_loop

  • SessionAgentSessionを管理している。
  • AgentSessionEventStreamを管理している。
  • Session.on_eventはWebsocketでイベントをFEに引渡し、UI制御を行っている。

session.initialize_agent

AgentStateChangedObservation

agent_session.event_stream.add_event(
    AgentStateChangedObservation('', AgentState.LOADING),
    EventSource.ENVIRONMENT,
)

agent_session.start

  • AgentSessionRuntime, AgentControllerを管理している。
  • AgentControllerCodeActAgentを管理している。
  • Runtime.on_eventActionを受け取り、適切なObservationを発行する。
  • AgentController.on_eventは受け取ったイベントをもとに、CodeActAgentでの処理を制御している。

MessageAction

event_stream.add_event(
    MessageAction(content=initial_user_msg),
    EventSource.USER
)

ChangeAgentStateAction

event_stream.add_event(
    ChangeAgentStateAction(AgentState.RUNNING),
    EventSource.ENVIRONMENT
)

MessageActionAgentControlleron_event発火🔥:

  • Action発行」はagent.stepの結果によって、何のActionなのか動的に決まる。ここでは、コマンド実行のActionが発行されたとして整理している。

AgentStateChangedObservation*1:

event_stream.add_event(
    AgentStateChangedObservation('', AgentState.RUNNING),
    EventSource.ENVIRONMENT,
)

AgentStateChangedObservation*2:

self.event_stream.add_event(
    AgentStateChangedObservation('', AgentState.AWAITING_USER_CONFIRMATION),
    EventSource.ENVIRONMENT,
)

CmdRunAction

event_stream.add_event(
    CmdRunAction(command=arguments['command'], is_input=is_input, confirmation_state=ActionConfirmationStatus.AWAITING_CONFIRMATION),
    EventSource.ENVIRONMENT
)

agent.step

  • 「toolsの登録」は、ミニマム構成だと、CmdRunTool, FinishTool, StrReplaceEditorToolを登録する。
  • codeact_function_calling.response_to_actionsは、llmのレスポンスのtoolsをActionに変換する。

llmのmessages:

[
  {
    "role": "system",
    "content": [
      {
        "type": "text",
        "text": "{{agenthub/codeact_agent/system_prompt.j2}}"
      }
    ]
  },
  {
    "role": "user",
    "content": [
      {
        "type": "text",
        "text": "{{MessageAction.content}}"
      },
      {
        "type": "text",
        "text": "{{microagents/knowledge/*.md}}"
      }
    ]
  }
]
MaronMaron

タスク承認(USER_CONFIRMED)

タスク承認:

  • frontend/src/components/shared/buttons/confirmation-buttons.tsxからActionを発行している。
send({
    action: ActionType.CHANGE_AGENT_STATE,
    args: { agent_state: AgentState.USER_CONFIRMED },
})

AgentStateChangedActionAgentControlleron_event発火🔥:

pending_action

_pending_action.confirmation_state = ActionConfirmationStatus.CONFIRMED
_pending_action._id = None
event_stream.add_event(_pending_action, EventSource.AGENT)

AgentStateChangedObservation

event_stream.add_event(
    AgentStateChangedObservation('', AgentState.USER_CONFIRMED),
    EventSource.ENVIRONMENT,
)

pending_action(CmdRunAction)Runtimeon_event発火🔥:
TBD...

  • CmdRunActionの場合、observation = getattr(self, action_type)(action)runを呼び出すことになる。
  • /execute_actionリクエストでActionExecutor.execute_actionを呼び出している。