LLM 기반 코드 생성 에이전트의 핵심 아키텍처 설계 및 구현

시스템 개요

최근 다양한 AI 프로그래밍 도구가 등장하면서, 지능형 에이전트 + 개발 환경 통합은 주목받는 기술 트렌드로 자리 잡았다. 본 문서에서는 실제 사용 가능한 코드 생성 에이전트 시스템을 구성하는 전체 아키텍처와 핵심 모듈들을 심층적으로 분석한다.

사용된 기술 스택: Python, Flask, Socket.IO, React 18, TypeScript, DeepSeek API

모듈화된 4계층 아키텍처

┌──────────────────────────────────────────────┐
│             Frontend (React + TS)             │
│  파일 트리 │ 대화 흐름 + 도구 카드 │ 작업 관리판  │
└────────────────────┬─────────────────────────┘
                     │ WebSocket (Socket.IO)
┌────────────────────▼─────────────────────────┐
│           api_server.py (Flask + SocketIO)    │
│       REST API + 실시간 이벤트 전달            │
└────────────────────┬─────────────────────────┘
                     │
┌────────────────────▼─────────────────────────┐
│                 Agent Core                    │
│   loop.py (주루프) │ subagent.py (하위 에이전트)  │
└────────────────────┬─────────────────────────┘
                     │
┌────────────────────▼─────────────────────────┐
│              Components                       │
│  memory · safety · todo · task · team · compactor │
└────────────────────┬─────────────────────────┘
                     │
┌────────────────────▼─────────────────────────┐
│                Tools                          │
│ 10+ 기능: 메모리/스냅샷/작업/팀/스킬/압축 등     │
└──────────────────────────────────────────────┘
  

각 계층은 명확한 책임을 가지며 독립적으로 테스트 및 확장 가능하다. 이 구조는 복잡한 에이전트 동작을 단순화하고 유지보수성을 극대화한다.

핵심 기능 구현

에이전트 주루프 (core/loop.py)

에이전트의 핵심은 반복적인 추론-행동-관찰-추론 사이클이다.

async def run_agent_cycle(messages, tools, event_handler):
    while True:
        # 1. LLM 호출 (스트리밍 출력)
        response = await call_llm_api(messages, tools, stream=True)

        # 2. 실시간 이벤트 전달
        await event_handler.on_chunk(response.delta)

        # 3. 도구 호출 여부 확인
        if response.stop_reason == "tool_use":
            tool_output = await invoke_tool(response.tool_call)
            await event_handler.on_tool_result(tool_output)
            messages.append(tool_output)
            continue  # 도구 결과 포함 후 재시도

        # 4. 종료 조건 확인
        if response.stop_reason == "end_turn":
            break
  • 이벤트 콜백 체계: 각 단계(체크포인트, 도구 시작/결과)에서 상태 업데이트를 트리거하여 UI와 분리된 로직 설계 가능
  • 중단 처리: asyncio.CancelledError를 활용해 사용자 중단 요청을 안정적으로 처리

실시간 통신 (Flask-SocketIO)

웹소켓 기반의 양방향 커뮤니케이션으로 높은 반응성 보장.

{
  "type": "chunk",
  "data": {
    "content": "생성 중...",
    "tool_name": "file_write",
    "input": { "path": "main.py", "content": "..." },
    "output": { "status": "success" },
    "elapsed_ms": 1200
  }
}

SSE 대비 장점: 클라이언트로부터 중단 신호 전송이 가능하며, 상태 변경 알림을 수신할 수 있다.

도구 시스템 (tools/)

등록 기반의 도구 패턴으로 확장성 향상.

class BaseTool:
    name: str
    description: str
    input_schema: dict

    async def execute(self, **kwargs) -> dict:
        raise NotImplementedError

# 도구 레지스트리
TOOL_REGISTRY = {}

def register_tool(tool: BaseTool):
    TOOL_REGISTRY[tool.name] = tool
도구명기능
memory지속적 기억 저장/검색
safety파일 스냅샷 생성 및 복원
task_board작업 상태 관리
todoTodo 리스트 동기화
team다중 에이전트 협업
compress대화 컨텍스트 압축
skill기능 스킬 로딩 및 실행
worktree작업 디렉터리 관리

컨텍스트 최적화 (components/compactor.py)

LLM의 입력 길이 제한을 초과하지 않도록 지능적으로 역사 데이터를 압축한다.

def condense_conversation(messages, budget):
    total_tokens = count_tokens(messages)
    if total_tokens <= budget:
        return messages

    # 최근 몇 번의 대화 유지
    recent = messages[-N:]

    # 중간 역사 요약
    summary = summarize(messages[1:-N])

    return [system_msg, summary] + recent

주의사항: 도구 호출과 결과가 짝을 이루도록 보장해야 하며, 그렇지 않으면 API 오류 발생.

안전성 관리 (components/safety_manager.py)

코드 수정 실수 방지를 위해 파일 상태를 사전 스냅샷으로 보존.

class SafetyManager:
    def create_snapshot(self, files: list[str], label: str):
        snapshot = {
            "id": uuid4(),
            "label": label,
            "timestamp": datetime.now(),
            "contents": {f: read(f) for f in files}
        }
        self.snapshots.append(snapshot)

    def revert_to(self, snapshot_id: str):
        snapshot = self.get(snapshot_id)
        for path, content in snapshot["contents"].items():
            write_file(path, content)

다중 에이전트 협업 (core/subagent.py + components/team_manager.py)

복잡한 작업을 분산 처리하기 위해 주 에이전트와 하위 에이전트 간 역할 분담.

주 에이전트
├── 하위 에이전트 1: 요구사항 분석
├── 하위 에이전트 2: 코드 생성
└── 하위 에이전트 3: 테스트 검증

프론트엔드 구조 (React 18 + TypeScript)

삼분면 레이아웃으로 사용자 경험 최적화.

┌─────────────┬──────────────────────┬──────────────┐
│  파일 트리     │  대화 + 도구 카드      │  작업 목록     │
│ (왼쪽)         │ (중앙·스트리밍)        │ (오른쪽)       │
└─────────────┴──────────────────────┴──────────────┘

커스텀 훅을 통해 소켓 상태를 관리.

function useAgentSocket() {
  const [messages, setMessages] = useState([]);
  const [status, setStatus] = useState("idle");
  const socketRef = useRef(null);

  useEffect(() => {
    socketRef.current = io("http://localhost:5000");

    socketRef.current.on("agent_event", (event) => {
      switch(event.type) {
        case "chunk":
          setMessages(prev => [...prev, event.data.content]);
          break;
        case "tool_start":
          addToolCard(event.data);
          break;
        case "tool_result":
          updateToolCard(event.data);
          break;
        case "run_end":
          setStatus("idle");
          break;
      }
    });

    return () => socketRef.current?.disconnect();
  }, []);

  return { messages, status, send, interrupt };
}

도구 카드는 각 도구의 입력, 출력, 수행 시간을 직관적으로 표시하여 에이전트의 작동 과정을 투명하게 제공한다.

API 인터페이스 설계

외부 시스템과의 통합을 위한 완전한 RESTful API 제공.

  • GET /api/status — 현재 에이전트 상태 조회
  • POST /api/chat — 메시지 전송
  • POST /api/chat/interrupt — 실행 중단
  • GET /api/history — 대화 기록
  • GET /api/tasks — 작업 목록
  • GET /api/todos — Todo 항목
  • GET /api/team — 다중 에이전트 상태
  • POST /api/safety/checkpoint — 스냅샷 생성
  • POST /api/safety/restore — 상태 복원
  • GET /api/files/tree — 파일 트리 구조

실행 방법

# 프로젝트 다운로드
git clone https://github.com/myh-1302/code_agent.git
cd code_agent

# API 키 설정
echo "DEEPSEEK_API_KEY=sk-deepsee-k-xxxxx" > .env

# 의존성 설치
pip install -r requirements.txt
cd frontend && npm install && cd ..

# 실행
./start.sh
# http://localhost:5173 접속

주요 문제 해결 경험

  • 도구 호출 일치성: tool_usetool_result는 반드시 짝을 이뤄야 하므로, 컨텍스트 압축 시 순서 보장 필수
  • 중단 후 상태 복원: 중단 시 미완성 메시지가 남을 수 있으므로, 다음 세션을 위해 정상화 처리 필요
  • 이벤트 순서 보장: 고병렬 상황에서 이벤트 순서가 어긋날 수 있음 → sequence_id 추가로 정렬 처리
  • React 상태 경쟁 조건: useState는 비동기 업데이트 → 즉시 읽기 용도로 useRef 사용

향후 개선 방향

  • 에이전트 루프의 안정성 강화 (재시도, 상태 복구)
  • 스마트 컨텍스트 압축 (의미 중심 자동 선택)
  • 프론트엔드 성능 최적화 (가상 스크롤, 지연 로딩)
  • 에러 회복 전략 (재시도 → 백업 → 사용자 알림)
  • 파일 변경 내역 미리보기 (diff 표시)
  • 메모리 검색 개선 (벡터 저장, 의미 검색)
  • 다중 작업 디렉터리 지원 (탭 기반 프로젝트 관리)
  • 테스트 실패 자동 진단 및 복구 제안

이러한 아키텍처는 단순한 API 호출을 넘어서, 실시간 커뮤니케이션, 상태 제어, 도구 연동, 보안, 컨텍스트 관리 등 다양한 기술적 도전을 통합적으로 해결해야 한다는 점을 보여준다. 실제로 구현해보는 것은 이론보다 훨씬 깊은 이해를 가능하게 한다.

태그: LLM Agent python flask Socket.io React

7월 5일 02:41에 게시됨