LLM 에이전트를 위한 두 가지 실용적인 오케스트레이션 루프 설계 방법
Source: Dev.to
항상 구분해야 할 세 가지 레이어
실행 레이어
에이전트와 응답자가 여기서 동작합니다.
Agent는 작업을 수행하는 모든 단위를 의미합니다: 모델 호출, 도구, 휴리스틱 함수, 라우터 등.
Responder는 한 턴 또는 세션에 대해 최종 사용자에게 보여지는 출력을 생성하는 에이전트입니다.
통신 레이어
에이전트들이 서로, 그리고 오케스트레이터와 어떻게 대화하는지를 정의합니다.
예시: 큐, 이벤트, 내부 RPC 호출, 함수 콜백 등.
에이전트가 서로 직접 호출하도록 하는 경우는 거의 없습니다. 모든 흐름을 이 레이어를 통해 라우팅하면 추적과 제어가 용이합니다.
메모리 레이어
시간에 따라 상태를 저장하고 검색하는 곳입니다.
벡터 스토어, 키‑값 스토어, 데이터베이스, 로그 등이 될 수 있습니다.
프롬프트에 “숨겨져 있어서는 안 됩니다”. 메모리를 별도의 컴포넌트로 취급하세요.
시간을 1급 객체로 다루기
두 루프 모두 시간을 명시적으로 다룹니다:
- 선형 루프 – 이산 단계: T0, T1, T2, T3.
- 원형 루프 – 대화가 활성화된 동안 지속되는 연속 스트림.
이 요소들을 갖추면 두 가지 오케스트레이션 패턴을 설계할 수 있습니다.
루프 1: 컨텍스트 추출 및 분석을 위한 선형 오케스트레이터

선형 루프를 사용해야 할 때
고정된 입력(텍스트, 전사본, 문서, 로그 집합)이 있고, 그 위에 여러 차례 분석을 수행하고 싶을 때 사용합니다. 지연 시간이 중요하지만 서브‑초 수준의 인터랙티브는 필요하지 않습니다. 일반적인 출력은 요약, 보고서, 분류, 구조화된 데이터 등입니다.
좋은 예시
- 통화 종료 후 대화 분석.
- 채팅 로그에서 엔터티와 토픽 추출.
- 다단계 문서 처리(OCR → 정제 → 분류 → 요약).
- 이전 세션에 대한 오프라인 품질 검사.
사고 모델
가로형 다이어그램을 떠올리세요:
- INPUT → (시간 단계 T0 … Tn) → Responder(최종 구조화 출력)
- 입력과 Responder 사이에:
- 시간 슬라이스별 실행 레이어 에이전트.
- 중간의 통신 밴드.
- 상단의 메모리 밴드.
각 단계에서 에이전트는 메모리를 읽고, 새로운 사실이나 요약을 메모리에 저장할 수 있습니다. 오케스트레이터는 이 단계들을 하나씩 순차적으로 진행합니다.
단계별 설계
단계 1: 최종 출력 정의
Responder가 생성할 내용을 결정합니다. 예시:
{
"intent": "...",
"sentiment": "...",
"entities": {...},
"summary": "..."
}
또는 사람이 읽을 수 있는 보고서, 혹은 다른 시스템을 위한 라벨/점수 등. 다른 모든 에이전트는 이 Responder가 성공하도록 돕는 역할을 합니다.
단계 2: 작업을 단계별로 나누기
의존 관계와 독립 작업을 식별합니다. 대화 분석 예시:
- 정규화 및 언어 감지.
- 엔터티 추출(이름, 계정 ID, 제품).
- 토픽 및 인텐트 감지.
- 감정 및 에스컬레이션 위험 판단.
- 최종 요약 및 제안.
각 단계가 하나 이상의 에이전트를 포함하는 시간 슬라이스가 됩니다.
단계 3: 메모리 스키마 설계
각 단계에서 에이전트가 읽고 쓰는 내용을 정의합니다. 간단한 스키마 예시:
{
"language": "en",
"entities": {...},
"topics": [...],
"sentiment": {...},
"summary": "..."
}
session_id, user_id, time_window 등으로 메모리 범위를 제한할 수도 있습니다(롤링 분석용).
핵심 규칙: 에이전트는 깨끗한 입력과 구조화된 메모리 조각만을 받으며, 프롬프트 안에 숨은 컨텍스트가 없어야 합니다.
단계 4: 읽기·쓰기 연결
각 에이전트마다 두 개의 작은 함수를 정의합니다:
def read(memory) -> context:
...
def write(memory, result) -> memory:
...
최소한의 오케스트레이션 루프는 다음과 같습니다:
for step in steps:
# 1. 이 단계가 필요로 하는 데이터를 로드
ctx = step.read(memory)
# 2. 입력과 컨텍스트를 함께 에이전트 실행
result = step.agent.run(raw_input, ctx)
# 3. 새로운 사실을 메모리에 기록
memory = step.write(memory, result)
일부 단계는 읽기만, 일부는 쓰기만 할 수 있습니다.
단계 5: 마지막 단계에 Responder 구현
Responder는 특별한 역할을 가진 또 다른 에이전트입니다:
- 메모리에서 필요한 모든 정보를 읽음.
- 최종 답변을 생성(보통 원본 입력, 이전 분석 에이전트들의 출력, 장기 사용자/세션 메모리를 결합한 단일 채팅 완성 호출).
- 추가 메타데이터를 메모리에 기록할 수도 있음.
예시: 대화 분석 파이프라인
| Agent | Reads | Writes |
|---|---|---|
| LanguageDetectorAgent | raw transcript | memory["language"] |
| EntityExtractorAgent | transcript, language | memory["entities"] |
| TopicClassifierAgent | transcript, entities | memory["topics"] |
| SentimentAgent | transcript | memory["sentiment"] |
| SummaryResponder | transcript, entities, topics, sentiment | final human‑readable summary & JSON record |
이 표는 선형 다이어그램과 직접 연결되며, 단계별 디버깅이 쉽습니다.
루프 2: 실시간 채팅·음성을 위한 원형 스트리밍 오케스트레이터

두 번째 패턴은 오프라인 분석에서 실시간 상호작용(음성 또는 인터랙티브 채팅)으로 전환될 때 등장합니다. 요구 사항은 다음과 같습니다:
- 사용자가 말하거나 입력하는 도중에 빠르게 반응해야 함.
- 여러 배경 분석을 병렬로 수행해야 함.
- 매 턴마다 전체 전사본을 모든 에이전트에 전달하지 않아야 함.
원형 루프 패턴은 이러한 요구를 충족하도록 설계되었습니다.
원형 루프를 사용해야 할 때
다음 상황에 적용합니다:
- 오디오 혹은 토큰을 스트리밍으로 입출력할 때.
- 사용자와 대화하는 중앙 “assistant”가 있을 때.
- 감정 변화, 안전·규정 위반, 인텐트 변동, CRM용 엔터티 업데이트, 혹은 흥미로운 순간을 자동으로 북마크하는 백그라운드 에이전트가 필요할 때.
음성 비서나 실시간 회의 전사 시스템을 떠올려 보세요. 메인 어시스턴트가 사용자에게 답변을 제공하면서, 보조 에이전트들이 지속적으로 컨텍스트를 풍부하게 만들어 주는 구조입니다.