멀티에이전트 시스템 설계 Designing Multi-Agent Systems
Chapter 04 · 구현

첫 번째 에이전트 구축하기

"프랑스의 수도는?"에 답하는 에이전트에서, 주가를 그리고 악의적 요청을 거부하는 에이전트까지 — 추론·행동·적응의 세 능력을 처음부터 빚어낸다.

실행 루프 비동기 우선 모델 클라이언트 구조화된 출력 툴·메모리 미들웨어 컨텍스트 엔지니어링

Section 1디자인 원칙

우리는 picoagents라는 Python 라이브러리를 처음부터 직접 만든다. 효과적인 에이전트 인터페이스는 최소한이면서도 완전해야 한다 — Agent(model=.., tools=[..], memory=..)는 정확히 세 능력을 제공한다. 모델을 통한 추론, 툴을 통한 행동, 메모리를 통한 적응.

Listing 4.1 · 완성된 에이전트의 모습
from picoagents import Agent, OpenAIChatCompletionClient

def get_weather(location: str) -> str:
    """Get current weather for a given location."""
    return f"The weather in {location} is sunny, 75°F"

agent = Agent(
    name="assistant",
    instructions="You are helpful. Use tools when appropriate.",
    model_client=OpenAIChatCompletionClient(model="gpt-4.1-mini"),
    tools=[get_weather]   # 함수가 자동으로 툴이 된다
)

async for event in agent.run_stream("What's the weather in Paris?"):
    print(event)

다섯 가지 아키텍처 원칙

표 · picoagents 디자인 원칙
원칙이유
비동기 우선LLM 호출(500ms~5초)·툴·I/O가 느리다. 동기 3-에이전트 워크플로우 30초가 동시성으로 10초가 된다.
이벤트 기반 스트리밍30초 넘는 과업에서 빈 화면을 피한다. 실시간 진행·디버깅 관측 가능성 제공.
컴포넌트 직렬화모든 컴포넌트가 JSON 직렬화 가능 — 버전 관리, 구성 UI, 세션 복원.
우아한 취소잘못된 프롬프트·무한 루프·피드백 제공 시 장시간 작업을 중도 중단.
추상 기반 클래스벤더 종속 방지, 목 구현 테스트. BaseTool은 함수→REST→MCP까지 코드 변경 없이 확장.

Section 2에이전트 실행 루프

모든 에이전트 프레임워크의 심장은 실행 루프다. 모든 상호작용은 동일한 근본 패턴을 따른다.

  1. 컨텍스트 준비 — 과업 + 지시문 + 메모리 + 대화 기록을 결합한다.
  2. 모델 호출 — 컨텍스트를 LLM에 보내고 응답을 받는다.
  3. 응답 처리 — 텍스트 응답을 처리하거나 툴 호출을 실행한다.
  4. 반복 — 툴이 호출되었다면 결과를 컨텍스트에 추가하고 2단계부터 반복.
  5. 반환 — 최종 응답을 제공하고 메모리를 갱신한다.
Listing 4.2 · 단순화된 에이전트 실행 루프 의사코드
async def agent_execution_loop(task):
    context = prepare_context(task, instructions, memory, history)
    while not done:
        response = await model_client.create(context)
        if response.has_tool_calls:
            for tool_call in response.tool_calls:
                result = await execute_tool(tool_call)
                context.append(result)
        else:
            done = True
    update_memory(context)   # optional
    return response
Motion · 에이전트 실행 루프 — 툴 호출 사이클 STEP 01 / 7
컨텍스트 → 모델 → 툴 → 반복

BaseAgent는 추상 기반 클래스로, 모든 에이전트는 수집형 run()과 스트리밍형 run_stream() 두 모드를 구현해야 한다. 과업 취소는 CancellationToken — 비동기 작업 전반에 중단 신호를 전파하는 스레드 안전 메커니즘 — 으로 구현한다. cancel()이 호출되면 스트리밍 LLM 호출을 중단하고, 새 툴 실행을 막으며, asyncio.CancelledError를 발생시킨다.

Section 3모델 클라이언트와 구조화된 출력

모델 클라이언트는 에이전트가 생성형 AI 모델과 마주하는 인터페이스다. BaseChatCompletionClient는 두 핵심 메서드를 정의한다 — create(생성)와 create_stream(스트리밍). 패턴은 언제나 같다 — 우리 타입을 제공자 형식으로 변환, API 호출, 응답을 다시 통합 형식으로 변환.

구조화된 출력 — 신뢰할 수 있는 에이전트의 열쇠

구조화된 출력은 LLM이 자유 형식 텍스트가 아니라 미리 정의된 스키마(Pydantic 모델)를 따르는 응답을 내놓도록 강제한다. 이것이 신뢰할 수 있는 툴 요청을 가능하게 한다 — 에이전트가 일관되게 같은 구조를 만들어 내므로 우리는 안전하게 툴을 실행할 수 있다. 7장의 계획 기반 오케스트레이션에서 output_format 파라미터가 예측 불가능한 LLM 텍스트를 신뢰할 수 있는 데이터 구조로 탈바꿈시키는 것도 같은 원리다.

Chat Completion Contract
create( messages, tools? ) → { message, usage, structured_output? }
messages 역할(system/user/assistant/tool)이 번갈아 등장하는 대화 시퀀스 · tools 선택적 함수 정의 목록 · structured_output output_format 지정 시 Pydantic 객체로 보장됨

Section 4툴 추가하기

LLM에서의 함수 호출(툴 호출)은 모델이 어떤 함수를, 어떤 인자로 호출할지를 결정하게 한다. 툴은 두 갈래로 나뉜다 — 범용 툴(폭넓은 역량)과 작업 특화 툴(특정 과업).

BaseTool 클래스는 모든 툴의 공통 인터페이스다. FunctionTool은 개발자 친화적 — 평범한 Python 함수를 그대로 툴로 변환한다. 시그니처와 docstring에서 자동으로 파라미터 스키마를 추출한다.

Listing · 구조화된 출력이 신뢰할 수 있는 툴 요청을 가능케 한다
# 에이전트가 이 구조를 안정적으로 생성한다
{
  "tool": "get_weather",
  "arguments": { "location": "Paris" }
}
# 이제 우리는 안전하게 툴을 실행할 수 있다
result = await execute_tool(tool_call)

툴로서의 에이전트

에이전트 자체도 다른 에이전트의 툴이 될 수 있다(4.11절). 결과 정보성(Result Informativeness)의 제어가 중요하다 — 하위 에이전트가 호출자에게 얼마나 풍부한 정보를 돌려줄지 조절한다.

Motion · 함수 호출 — 자연어에서 구조화된 툴 호출로 STEP 01 / 5
구조화된 출력이 안전한 실행을 보장한다

Section 5메모리 더하기

에이전트가 시간이 지나며 향상되려면 메모리 — 과거 상호작용에서 정보를 회상·재활용하는 능력 — 가 필요하다.

표 · 메모리 종류
유형설명
단기 메모리메시지 기록을 통한 현재 과업 작업 메모리
장기 메모리검색 증강 생성(RAG)을 통한 세션 횡단 지식
애플리케이션 관리개발자가 통제 — 사전 정의 전략으로 자동 저장·검색
에이전트 관리에이전트가 툴을 통해 무엇을·언제 저장할지 직접 결정

BaseMemory 인터페이스가 메모리 백엔드를 추상화한다. 에이전트 관리 메모리는 메모리를 툴로 노출해 에이전트가 세션을 가로질러 학습하게 하며, 메모리 연산(view·create·search·append·str_replace)을 제공한다. 다만 에이전트가 자신의 지식을 통제할 때는 보안 고려가 필수다.

Section 6미들웨어와 컨텍스트 엔지니어링

미들웨어는 제어와 관측 가능성을 위한 검사 계층이다. BaseMiddleware 인터페이스는 세 후크를 제공한다 — process_request(), process_response(), process_error(). 입력 검증을 위한 SecurityMiddleware, 관측 가능성을 위한 LoggingMiddleware, 리소스 제어를 위한 RateLimitMiddleware가 구체적 예다.

OpenTelemetry — 업계 표준 관측 가능성

OTelMiddleware는 에이전트 실행을 OpenTelemetry 트레이스로 자동 계측한다. Jaeger 같은 도구로 시각화할 수 있으며, 관측 가능성 플랫폼과 통합된다. 프라이버시를 위해 콘텐츠 캡처를 선택적으로 제어한다.

컨텍스트 엔지니어링

장기 실행 과업은 두 가지 문제에 부딪힌다 — 컨텍스트 부식(Context Rot)(긴 컨텍스트에서 성능 저하)과 컨텍스트 폭발(Explosion)(컨텍스트가 윈도우를 넘침). 관리 전략 — 압축(compaction), 요약, 격리, 선택적 컨텍스트 제공. 루프 수준 훅(Loop-Level Hooks)은 실행 루프의 매 반복에 개입해 컨텍스트를 다듬는다.

휴먼 인 더 루프

툴 승인 요청(Tool Approval Requests) — 고위험 툴 호출 전에 사람의 승인을 요구한다. 무상태 컨텍스트와 인간 입력을 결합해, 에이전트가 사람을 기다리는 동안에도 세션 상태를 보존한다.

멀티에이전트 시스템 설계 제4장 · 첫 번째 에이전트 구축하기