OpenManus 내부 구조와 모듈 설계 파헤치기
OpenManus의 백엔드는 단순한 프롬프트 연동 스크립트가 아니라, 비동기 파이프라인과 객체지향 설계 패턴으로 구성되어 있어요. 프로젝트 전체를 지탱하는 주요 시스템 파이프라인을 다이어그램으로 살펴보죠.
%%{init: {"theme":"base","themeVariables":{"primaryColor":"#F0EEE9","primaryBorderColor":"#2a78d6","primaryTextColor":"#2b2926","secondaryColor":"#e8f0fb","secondaryBorderColor":"#4a3aa7","secondaryTextColor":"#2b2926","tertiaryColor":"#eafaf3","tertiaryBorderColor":"#1baf7a","tertiaryTextColor":"#2b2926","lineColor":"#8a8578","textColor":"#2b2926","edgeLabelBackground":"#F0EEE9","noteBkgColor":"#F0EEE9","noteTextColor":"#2b2926","noteBorderColor":"#8a8578","clusterBkg":"#faf9f6","clusterBorder":"#d8d4c8","fontFamily":"Pretendard, sans-serif"}}}%%
flowchart TD
A["사용자 프롬프트 입력"] --> B["작업 분석 및 단계별 계획 수립"]
B --> C["필요 도구 선택 및 매개변수 생성"]
C --> D["도구 실행 엔진"]
D --> E["웹 브라우저 및 터미널 제어"]
E --> F["실행 결과 및 피드백 수집"]
F --> G["결과 평가 및 추가 실행 판단"]
G -->|"작업 미완료"| C
G -->|"작업 완료"| H["최종 답변 정리 및 제출"]
1) 클래스 상속 체계와 역할 분담
OpenManus는 재사용성을 높이기 위해 에이전트 클래스를 단계별로 추상화했어요. 기본 에이전트 클래스에서부터 도구 호출 기능이 추가된 상위 에이전트로 확장되는 구조죠.
%%{init: {"theme":"base","themeVariables":{"primaryColor":"#F0EEE9","primaryBorderColor":"#2a78d6","primaryTextColor":"#2b2926","secondaryColor":"#e8f0fb","secondaryBorderColor":"#4a3aa7","secondaryTextColor":"#2b2926","tertiaryColor":"#eafaf3","tertiaryBorderColor":"#1baf7a","tertiaryTextColor":"#2b2926","lineColor":"#8a8578","textColor":"#2b2926","edgeLabelBackground":"#F0EEE9","noteBkgColor":"#F0EEE9","noteTextColor":"#2b2926","noteBorderColor":"#8a8578","clusterBkg":"#faf9f6","clusterBorder":"#d8d4c8","fontFamily":"Pretendard, sans-serif"}}}%%
classDiagram
class BASE_AGENT {
+string name
+string description
+run()
}
class TOOL_AGENT {
+list available_tools
+call_tool()
}
class MANUS_AGENT {
+execute_workflow()
+reflect_step()
}
class LLM_CLIENT {
+string model_name
+generate_response()
}
class TOOL_REGISTRY {
+register_tool()
+get_tool()
}
BASE_AGENT <|-- TOOL_AGENT
TOOL_AGENT <|-- MANUS_AGENT
TOOL_AGENT --> LLM_CLIENT
TOOL_AGENT --> TOOL_REGISTRY
- BASE_AGENT: 에이전트의 상태 관리 및 기본 실행 입출력을 담당하는 최상위 추상 클래스예요.
- TOOL_AGENT: LLM이 반환하는 JSON 형태의 함수 호출 규격을 해석하여 등록된 도구를 안전하게 호출해 줘요.
- MANUS_AGENT: OpenManus의 메인 에이전트로, 브라우저 탐색, 터미널 명령 실행, 파일 시스템 읽기 및 쓰기 등 모든 시스템 도구를 총괄 제어하며 자율 반추 작업을 수행해요.
2) 작업 생명주기와 상태 전이
에이전트가 단일 요청을 처리하는 과정에서 거치는 내부 상태 흐름을 상태도(State Diagram)로 표현하면 다음과 같아요.
%%{init: {"theme":"base","themeVariables":{"primaryColor":"#F0EEE9","primaryBorderColor":"#2a78d6","primaryTextColor":"#2b2926","secondaryColor":"#e8f0fb","secondaryBorderColor":"#4a3aa7","secondaryTextColor":"#2b2926","tertiaryColor":"#eafaf3","tertiaryBorderColor":"#1baf7a","tertiaryTextColor":"#2b2926","lineColor":"#8a8578","textColor":"#2b2926","edgeLabelBackground":"#F0EEE9","noteBkgColor":"#F0EEE9","noteTextColor":"#2b2926","noteBorderColor":"#8a8578","clusterBkg":"#faf9f6","clusterBorder":"#d8d4c8","fontFamily":"Pretendard, sans-serif"}}}%%
stateDiagram-v2
[*] --> IDLE_STATE
IDLE_STATE --> PLANNING_STATE : 사용자 지시 수신
PLANNING_STATE --> EXECUTING_STATE : 액션 도구 결정
EXECUTING_STATE --> OBSERVING_STATE : 결과 관찰
OBSERVING_STATE --> REFLECTING_STATE : 자율 반추
REFLECTING_STATE --> EXECUTING_STATE : 추가 단계 필요
REFLECTING_STATE --> COMPLETED_STATE : 목표 달성
REFLECTING_STATE --> FAILED_STATE : 예외 발생
COMPLETED_STATE --> [*]
FAILED_STATE --> [*]
에이전트는 각 단계에서 발생한 도구 실행 결과를 단기 기억 버퍼에 누적해요. 만약 OBSERVING_STATE에서 도구 실행 실패나 타임아웃이 감지되면 REFLECTING_STATE로 전이하여 원인을 분석하고 대체 전략을 세우더라고요.
3) 데이터 모델과 엔티티 관계
OpenManus 내부 데이터 구조는 Pydantic을 통해 엄격하게 검증돼요. 에이전트 설정, 작업 상태, 도구 호출 기록 간의 관계는 아래 엔티티 관계도와 같아요.
%%{init: {"theme":"base","themeVariables":{"primaryColor":"#F0EEE9","primaryBorderColor":"#2a78d6","primaryTextColor":"#2b2926","secondaryColor":"#e8f0fb","secondaryBorderColor":"#4a3aa7","secondaryTextColor":"#2b2926","tertiaryColor":"#eafaf3","tertiaryBorderColor":"#1baf7a","tertiaryTextColor":"#2b2926","lineColor":"#8a8578","textColor":"#2b2926","edgeLabelBackground":"#F0EEE9","noteBkgColor":"#F0EEE9","noteTextColor":"#2b2926","noteBorderColor":"#8a8578","clusterBkg":"#faf9f6","clusterBorder":"#d8d4c8","fontFamily":"Pretendard, sans-serif"}}}%%
erDiagram
AGENT_CONFIG ||--o{ TASK_STATE : manages
TASK_STATE ||--|{ TOOL_CALL : contains
TOOL_CALL }|--|| LLM_RESPONSE : triggers
TASK_STATE {
string task_id
string current_step
string status
}
AGENT_CONFIG {
string agent_id
string model_provider
float temperature
}
TOOL_CALL {
string tool_name
string input_args
string output_result
}
LLM_RESPONSE {
string response_id
int token_count
string reasoning_content
}
4) 실시간 상호작용 및 시퀀스 흐름
사용자가 요청을 보냈을 때 백엔드 비동기 엔진이 LLM과 외부 도구 간에 상호작용하는 시퀀스를 정리했어요.
%%{init: {"theme":"base","themeVariables":{"primaryColor":"#F0EEE9","primaryBorderColor":"#2a78d6","primaryTextColor":"#2b2926","secondaryColor":"#e8f0fb","secondaryBorderColor":"#4a3aa7","secondaryTextColor":"#2b2926","tertiaryColor":"#eafaf3","tertiaryBorderColor":"#1baf7a","tertiaryTextColor":"#2b2926","lineColor":"#8a8578","textColor":"#2b2926","edgeLabelBackground":"#F0EEE9","noteBkgColor":"#F0EEE9","noteTextColor":"#2b2926","noteBorderColor":"#8a8578","clusterBkg":"#faf9f6","clusterBorder":"#d8d4c8","fontFamily":"Pretendard, sans-serif"}}}%%
sequenceDiagram
autonumber
actor User as 사용자
participant Manus as OpenManus 에이전트
participant LLM as 대형 언어 모델
participant Tool as 외부 실행 도구
User->>Manus: 자연어 작업 요청
Manus->>LLM: 컨텍스트 및 프롬프트 전달
LLM-->>Manus: 도구 호출 명령 추론
Manus->>Tool: 브라우저/터미널 명령 실행
Tool-->>Manus: 실행 결과 및 환경 상태 반환
Manus->>LLM: 실행 결과 수집 후 다음 단계 문의
LLM-->>Manus: 최종 해결책 도출
Manus-->>User: 요약 보고서 및 결과 제공
5) 소스 코드 구현 디테일
app/agent/toolcall.py에 구현된 실행 루프 코드를 살펴보면, Python의 asyncio 기반으로 도구 실행과 LLM 응답 대기가 비동기적으로 처리되는 것을 확인할 수 있어요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| import asyncio
from typing import List, Dict, Any
from app.agent.base import BaseAgent
from app.llm import LLMClient
from app.tool.base import ToolCollection
class ToolCallAgent(BaseAgent):
def __init__(self, name: str, llm: LLMClient, tools: ToolCollection):
super().__init__(name=name)
self.llm = llm
self.tools = tools
self.messages: List[Dict[str, Any]] = []
async def step(self) -> bool:
response = await self.llm.generate(messages=self.messages, tools=self.tools.to_schema())
self.messages.append(response.to_message())
if not response.tool_calls:
return True
for tool_call in response.tool_calls:
tool_name = tool_call.function.name
args = tool_call.function.arguments
result = await self.tools.execute(name=tool_name, args=args)
self.messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result)
})
return False
|
이처럼 OpenManus는 단순한 프롬프트 조합이 아닌, 비동기 루프 내에서 도구 호출 결과를 받아 다음 추론 단계로 피드백하는 구조를 형성하고 있어요.