1편 https://letspl.me/quest/2354/shortcut
3편 https://letspl.me/quest/2356/shortcut
User
↓
Orchestrator
↓
LLM
↓
Tools
↓
Memory
간단히 말하면
Agent의 실행을 관리하는 엔진이다.
LLM은 생각을 하고 Tool은 행동을 한다.
하지만 그 사이에서 어떤 순서로 실행할지 어떤 Tool을 호출할지 언제 멈출지
를 관리하는 것이 Orchestrator다.
실제 서비스에서 Orchestrator는 보통 다음 역할을 한다. 아래 다섯 가지가 핵심이다.
1. 요청 분석
2. Agent loop 실행
3. Tool 호출 관리
4. 병렬 작업 관리
5. 결과 정리 및 전달
사용자가 이렇게 요청한다고 해보자.
“다음 주 도쿄 출장 일정 만들어줘”
Orchestrator는 보통 이런 순서로 동작한다.
1 user request
2 planner 실행
3 task 생성
4 tool 실행
5 결과 수집
6 결과 정리
7 사용자 응답 생성이걸 코드 흐름으로 단순화하면 이렇게 된다.
tasks = planner(user_request)
results = []
for task in tasks:
tool = select_tool(task)
result = tool.run(task)
results.append(result)
response = llm.generate(results)물론 실제 시스템은 이것보다 훨씬 복잡하다.
특히 Tool 실행 방식이 중요하다.
많은 예시에서는 Tool이 순차적으로 실행되는 것처럼 보인다.
flight search
↓
hotel search
↓
calendar event하지만 실제 서비스에서는 병렬 실행이 꽤 많다.
예를 들어 여행 일정 생성이라면
search_flight
search_hotel
search_restaurant이 세 개는 서로 의존성이 없다.
그래서 보통 이렇게 실행한다.
search_flight
/
Planner →
\
search_hotel
search_restaurant즉 병렬 Tool 실행이다. Python 예시로 보면 이런 구조가 된다.
results = await asyncio.gather(
search_flight(),
search_hotel(),
search_restaurant()
)이렇게 하면 응답 속도가 훨씬 빨라진다.

여기서 또 하나 중요한 질문이 있다.
Tool 실행 결과는 보통 이런 형태다.
flight API result
hotel API result
restaurant API result하지만 사용자는 이런 데이터를 원하지 않는다.
사용자가 원하는 것은 보통 이런 형태다.
“서울 → 도쿄 항공편은 ANA 10:30 출발이 가장 저렴합니다.
추천 호텔은 신주쿠 지역의 XXX 호텔입니다.”
즉 API 결과 → 사용자 메시지 로 변환해야 한다.
이 역할도 보통 Orchestrator가 담당한다.
보통 구조는 이렇게 된다.
Tools
↓
Raw results
↓
Result Aggregator
↓
LLM response generation예를 들어
flight API
hotel API
restaurant API결과를 모아서
LLM에게 이렇게 전달한다.
Flight results: ...
Hotel results: ...
Restaurant results: ...
Create a travel itinerary.LLM이 최종 사용자 응답을 생성한다.
Agent 시스템을 실제 제품에 붙이면
또 하나의 단계가 추가된다. UI 업데이트 단계다.
예를 들어 Chat UI라면 이런 흐름이 된다.
User message
↓
Agent processing
↓
Tool execution
↓
Intermediate events
↓
UI streaming예를 들어 사용자가 화면에서 보게 되는 것은 이런 흐름이다.
AI is searching flights...
AI is checking hotels...
AI is generating itinerary...이런 메시지는 보통 Orchestrator가 이벤트 형태로 전달한다.
예
event: TOOL_START
event: TOOL_RESULT
event: AGENT_STEP
event: FINAL_RESPONSE이 이벤트를 WebSocket이나 SSE로 프론트엔드에 전달한다.
그래서 백엔드에서 돌아가는 프로세스
User
↓
Frontend (React / Next.js)
↓
API Gateway
↓
Agent Service
↓
Orchestrator
↓
Tools
↓
External APIs실제 UI에서 유저들이 보는 화면
User:
출장 일정 만들어줘
AI:
✈️ 항공편 검색 중...
AI:
🏨 호텔 검색 중...
AI:
일정을 생성했습니다.즉 Agent 시스템은 단순히
LLM → Tool 이 아니라 Event 기반 시스템이 된다.

결국 Agent 시스템에서 가장 중요한 컴포넌트는 LLM이 아니다.
Orchestrator다.
LLM은 생각을 하지만
Orchestrator는 실행 흐름 관리 Tool 스케줄, 병렬 처리, 상태 관리 , UI 이벤트 전달
을 모두 담당한다.
그래서 실제 제품을 만들다 보면 이런 결론에 도달한다.
Agent 시스템은 사실
LLM 서비스가 아니라 Workflow Engine이다.
처음 Agent를 만들 때 가장 먼저 하는 일은 Tool을 붙이는 것이다.
예를 들어 여행 Agent를 만든다고 하자.
대충 이런 Tool들이 생긴다.
search_flight()
search_hotel()
search_restaurant()
create_calendar_event()
send_email()
get_weather()사용자가 이렇게 말한다고 해보자.
“다음 주 도쿄 출장 일정 만들어줘”
개발자의 기대는 이렇다.
1. 항공편 검색
2. 호텔 검색
3. 일정 생성
4. 캘린더 등록하지만 실제로는 이런 일이 벌어지기도 한다.
LLM output
{
"tool": "send_email",
"arguments": {
"to": "user",
"content": "Your trip plan is ready"
}
}아직 아무것도 안 했는데 이메일부터 보내버린다. 이유는 간단하다.
LLM은 논리적으로 계획을 세우는 시스템이 아니라
확률적으로 다음 토큰을 생성하는 모델이기 때문이다.
즉 Agent는 항상 조금 이상하게 행동할 가능성을 가지고 있다.
Agent 시스템에서 실제로 가장 어려운 부분은 Tool 선택이다.
예를 들어 Tool이 이렇게 있다고 해보자.
search_flight()
search_hotel()
search_trip()
search_travel_plan()사람이 보면 차이가 명확하다.
하지만 LLM 입장에서는 설명이 비슷하면 구분이 어렵다.
그래서 이런 일이 발생한다.
사용자 요청
“서울에서 도쿄 가는 비행기 찾아줘”
LLM 선택
{
"tool": "search_trip"
}그런데 search_trip은 패키지 여행 검색 API였다.
이런 일이 꽤 자주 발생한다.
나쁜 예
search_trip
search_travel
search_flight
search_transport좋은 예
flight_search
hotel_search
restaurant_searchTool 이름조차도 LLM이 이해하기 쉽게 설계해야 한다.
Agent를 처음 실행하면 꽤 자주 보는 장면이 있다.
Agent 로그가 이렇게 찍힌다.
Step 1
search_hotel
Step 2
search_hotel
Step 3
search_hotel계속 같은 행동을 한다. 왜 이런 일이 생길까?
Agent의 기본 구조는 보통 이런 loop다.
plan
action
observation
plan문제는 observation이 충분히 명확하지 않으면
LLM이 이렇게 판단한다. “아직 목표를 달성하지 못했다.”
그래서 같은 행동을 반복한다.
예를 들어
호텔 검색 → 결과 없음Agent 생각
아직 호텔 못 찾음
→ 다시 호텔 검색이 loop가 계속 반복된다. 그래서 실제 서비스에서는 반드시 이런 제한을 둔다.
max_steps = 10
timeout = 30sAgent가 똑똑해서가 아니라 폭주를 막기 위해서다.
이건 처음 보면 꽤 당황스러운 문제다.
Tool 목록이 이렇게 있다고 하자.
search_flight
search_hotel
create_calendar_event그런데 Agent 로그에 이런 것이 찍힌다.
{
"tool": "book_flight"
}문제는 book_flight라는 Tool은 존재하지 않는다.
LLM이 “있을 것 같은 API”를 만들어낸 것이다.
이 현상은 사실 hallucination의 한 형태다. LLM은 없는 것도 자연스럽게 만들어낸다.
그래서 실제 시스템에서는 보통 이런 레이어가 추가된다.
LLM
↓
Tool Validator
↓
Tool ExecutionAgent 시스템을 처음 만들면 대부분 이런 구조를 생각한다.
User
↓
LLM
↓
Tool하지만 실제로는 이렇게 된다.
LLM (plan)
Tool
LLM (analysis)
Tool
LLM (evaluation)한 작업에 LLM 호출이 여러 번 들어간다.
예를 들어 여행 일정 생성 하나가
LLM 호출 6회
API 호출 5회이런 구조가 된다.
그래서 Agent 시스템은 생각보다 비용이 빨리 증가한다.
이 때문에 실제 서비스에서는
- step limit
- cost limit
- caching
같은 장치가 들어간다.
이 문제들을 겪다 보면 대부분 이런 결론에 도달한다.
Agent를 완전히 자유롭게 두면 시스템이 불안정해진다.
그래서 실제 서비스에서는 보통 이렇게 만든다.
Planner
↓
Task Queue
↓
Executor
↓
Tool Validation
↓
Guardrails즉 완전히 자율적인 Agent가 아니라 통제된 Agent 시스템이다.
지금까지의 Agent 시스템은 대부분 이런 구조였다.
LLM
→ Tool 호출하지만 최근 등장한 시스템들은 컴퓨터 자체를 조작하는 Agent를 만들기 시작했다.
예를 들어
1. 브라우저를 직접 조작하고
2. 파일을 생성하고
3. 스크립트를 실행한다.
대표적인 사례가 OpenClaw 같은 시스템이다.
이 구조는 기존 Agent보다 훨씬 강력하지만 동시에 훨씬 위험하기도 하다.
이 이야기는 다음 글에서 조금 더 자세히 다뤄보려고 한다.