에이전트 루프 내부 들여다보기: ReAct, 도구 호출(tool calling), 그리고 모든 에이전트를 구동하는 루프
요약
에이전트 시스템의 핵심 작동 원리인 루프(loop)와 ReAct 패턴을 설명합니다. 모델이 추론(Thought), 행동(Action), 관찰(Observation) 과정을 반복하며 목표를 달성하는 메커니즘을 다룹니다.
핵심 포인트
- 에이전트의 핵심은 목표 달성까지 추론과 행동을 반복하는 루프 구조임
- ReAct 패턴은 추론, 행동, 관찰을 교차하여 환각 현상을 줄임
- 단일 호출(one-shot)과 달리 피드백을 통해 다음 행동을 결정함
- 도구 호출(tool calling)을 통해 실제 세계의 데이터를 반영함
에이전트가 실제로 생각하고 행동하는 방식: ReAct, 도구 호출(tool calling), 그리고 모든 에이전트를 구동하는 루프
첫 번째 포스트에서 우리는 에이전트 시스템(agentic system)을 계획하고, 추론하며, 도구를 사용하고, 다단계 워크플로우(multi-step workflows)를 실행할 수 있는 소프트웨어로 설명했습니다. 이 문장은 이 분야 전체에서 가장 중요한 아이디어를 숨기고 있습니다. 에이전트가 “생각할” 때 실제로 무슨 일이 일어나고 있는 걸까요? 정직한 답변은 놀라울 정도로 간단하며, 일단 이를 이해하고 나면 시장에 있는 모든 에이전트 프레임워크(agent framework)가 마법처럼 보이지 않고 변형을 가한 단일 디자인 패턴(design pattern)처럼 보이기 시작할 것입니다.
그 패턴은 바로 루프(loop)입니다. 일반적인 언어 모델(language model)은 당신의 프롬프트(prompt)에 한 번 대답하고 멈춥니다. 에이전트는 동일한 모델을 루프 안에 감싸서(출력이 다음 입력으로 다시 공급되는 방식 등) 목표가 달성될 때까지 추론하고, 행동을 취하고, 결과를 관찰하고, 다시 추론하는 과정을 계속 실행합니다. 그 루프가 당신이 만들 모든 에이전트의 심장 박동입니다.
원샷(one-shot) 답변에서 루프로
챗봇(chatbot)은 하나의 함수입니다: 텍스트가 입력되면 텍스트가 출력되고 끝납니다. 모델은 자신의 답변이 좋았는지 여부를 결코 알 수 없습니다. 에이전트는 그 단일 샷(single shot)을 하나의 사이클(cycle)로 분해합니다. 각 단계가 끝날 때마다 실제 세계로부터 피드백(feedback)을 받고, 그 피드백을 사용하여 다음에 무엇을 할지 결정합니다. 이것은 시험 답안을 작성하고 떠나버리는 학생과, 볼트를 조이고 엔진이 시동되는지 확인한 뒤 다시 조정하는 정비사의 차이와 같습니다.
이 사이클을 구조화하는 가장 영향력 있는 방법은 2022년 Yao 등이 소개한 ReAct 패턴입니다. ReAct는 **Reason(추론) + Act(행동)**의 약자이며, 매 턴마다 다음 세 가지를 교차하여 수행합니다:
- Thought (생각) - 모델이 상황을 반영하고 무엇을 할지 결정하는 짧은 사고 사슬(chain-of-thought).
- Action (행동) - 주로 도구(tool)나 API를 호출하는 구체적인 단계.
- Observation (관찰) - 해당 행동이 만들어낸 결과로, 모델에 즉시 다시 입력됩니다.
모델이 공개적으로 추론(reason)하도록 강제하고 각 결정을 실제 관찰(observation)에 근거하게 함으로써, ReAct는 환각 (hallucination)을 줄입니다. 모델은 바로 다음 줄의 트랜스크립트(transcript)가 방금 실행한 검색의 실제 출력값인 상황에서, 몰래 사실을 지어낼 수 없기 때문입니다.
추적 예시 (A traced example)
목표가 "Tesla의 CEO가 가장 최근에 트윗한 주식의 종가는 얼마인가?"라고 가정해 봅시다. ReAct 추적(trace)은 다음과 같을 수 있습니다:
Thought: Elon Musk가 주식에 대해 올린 가장 최근 트윗을 찾아야 합니다.
Action: search_tweets(user="elonmusk", topic="stock")
Observation: "$X를 더 매수했습니다 ..." (2시간 전 게시됨)
Thought: 티커(ticker)는 X입니다. 오늘의 종가가 필요합니다.
Action: get_quote(ticker="X")
Observation: { "close": 41.80, "currency": "USD" }
Thought: 이제 답을 알았습니다.
Action: finish("종가는 $41.80였습니다.")
단 한 번의 모델 호출로는 이 질문에 답할 수 없었다는 점에 주목하십시오. 에이전트는 행동해야 했고, 돌아온 결과를 확인해야 했으며, 그 후에야 다음 움직임을 결정할 수 있었습니다. 이것이 바로 루프(loop)가 제공하는 핵심 가치입니다.
처음부터 루프 구축하기 (Building the loop from scratch)
어떤 프레임워크도 사용하지 않는다면, 에이전트 루프는 약 12줄 정도의 코드에 불과합니다. 모델은 구조화된 결정을 반환하고, 여러분의 코드는 이를 실행하여 결과를 다시 입력으로 제공합니다:
def run_agent(goal, tools, max_steps=8):
history = [system_prompt(goal, tools)]
for _ in range(max_steps):
...
위 코드 조각의 세 가지 세부 사항은 보이는 것보다 더 중요합니다. 첫째, 루프에는 엄격한 단계 예산(step budget) (max_steps)이 있습니다. 이것이 없다면, 혼란에 빠진 에이전트는 즐겁게 도구(tools)를 영원히 호출하며 여러분의 API 비용을 탕진할 것입니다. 둘째, 모델은 아무것도 실행하지 않습니다 - 모델은 단지 행동(action)과 인자(arguments)의 이름을 지정할 뿐이며, 이를 실행할지 여부와 방법은 여러분의 코드가 결정합니다. 셋째, 모든 관찰(observation)은 히스토리(history)에 추가되므로, 모델의 컨텍스트(context)는 모델이 학습한 내용과 함께 성장합니다.
ReAct 텍스트 vs 네이티브 도구 호출 (ReAct text vs. native tool calling)
모델로부터 그러한 결정을 이끌어내는 데에는 두 가지 방법이 있습니다. 기존의 ReAct 방식은 모델에게 자신의 생각과 행동을 텍스트로 _작성(write)_하도록 요청하며, 이후 이를 파싱(parse)합니다. 최신 모델들은 네이티브 도구 호출 (native tool calling) (함수 호출 (function calling)이라고도 함) 기능을 제공합니다. 이는 모델에게 타입이 지정된 함수 스키마 (function schemas) 세트를 전달하면, 모델이 함수 이름과 인자(arguments)가 포함된 구조화된 JSON 객체를 반환하는 방식입니다. 이 방식은 취약한 텍스트 파싱 (text parsing) 과정이 필요하지 않습니다.
네이티브 도구 호출 (Native tool calling): 모델은 산문이 아닌 구조화된 의도를 반환합니다.
tools = [{
"name": "get_quote",
"description": "Get the latest market quote for a ticker symbol.",
...
이러한 방식 사이에는 실질적인 트레이드오프 (trade-off)가 존재하며, 이를 이해하는 것이 중요합니다. 텍스트 기반의 ReAct는 투명하고 모델에 구애받지 않습니다 (model-agnostic). 즉, 추론 과정을 읽을 수 있으며, 도구 호출 API가 없는 모델에서도 작동합니다. 네이티브 도구 호출은 더 효율적이고 신뢰할 수 있습니다. 구조화된 출력, 더 적은 왕복 (round trips), 그리고 모델이 한 번에 여러 도구를 요청할 수 있다는 장점이 있지만, 호출 뒤에 숨겨진 추론 과정은 덜 가시적입니다. 실제로 대부분의 프로덕션 에이전트 (production agents)는 네이티브 도구 호출을 사용하며, 트레이싱 (tracing)과 로깅 (logging)을 통해 투명성을 확보합니다.
하나의 루프로는 부족할 때: 계획 및 실행 (plan-and-execute)
순수한 ReAct는 매번 하나의 관찰 (observation)마다 다음 단계를 결정합니다. 이는 유연하지만 긴 작업에서는 방향을 잃고 방황할 수 있습니다. 일반적인 진화 형태는 계획 및 실행 (plan-and-execute) 방식입니다. 에이전트가 먼저 전체 계획을 초안으로 작성한 다음 단계를 실행하며, 현실이 계획과 달라질 때만 다시 계획을 세웁니다. 느리고 비용이 많이 드는 계획 단계를 빠르고 전술적인 실행 단계와 분리하는 것은, 매 턴마다 처음부터 반응하는 것보다 복잡한 다단계 워크플로 (multi-step workflows)를 더 효율적으로 처리하는 경향이 있습니다. ReAct를 즉흥 연주에 비유한다면, 계획 및 실행은 수정이 가능한 체크리스트를 가지고 작업하는 것에 비유할 수 있습니다.
루프가 잘못되는 지점
이 루프는 개방적(open-ended)이기 때문에 강력하지만, 바로 그 점 때문에 실패 모드(failure modes)가 발생하기도 합니다. 에이전트는 실패하는 동일한 행동을 반복하며 갇히거나, 예산이 소진될 때까지 루프를 돌거나, 값비싼 도구(tool)를 수십 번 호출할 수 있습니다. 이에 대한 방어책은 화려하지는 않지만 필수적입니다. 즉, 엄격한 단계 제한(hard step limit), 동일한 반복 행동의 탐지, 비용 상한선(cost ceiling), 그리고 모델이 맹목적으로 재시도하는 대신 _회복(recover)_할 수 있도록 하는 명확한 도구 오류 메시지 등이 있습니다. 모든 관찰(observation)을 모델이 잘못 읽을 수 있는 것으로 간주하고, 도구가 명확하고 정보가 풍부하게 실패하도록 설계하십시오.
시사점 (The takeaway)
고객 지원 봇부터 GitHub Copilot의 에이전트 모드에 이르기까지 모든 에이전트는 본질적으로 "추론(reason), 행동(act), 관찰(observe), 반복(repeat), 완료될 때까지"라는 이 루프를 따릅니다. 프레임워크들은 그 위에 메모리, 멀티 에이전트, 오케스트레이션(orchestration)을 추가하지만, 이들은 모두 동일한 심장 박동의 변형일 뿐입니다. 일단 스스로 루프를 작성할 수 있게 되면, 에이전트를 내부에서부터 이해하게 되며, 모든 프레임워크는 미스터리가 아닌 편의 도구로 다가올 것입니다.
시리즈의 다음 단계에서는, 행동(Act) 단계에 대해 별도의 포스트를 다룰 가치가 있습니다. 우리는 도구(tools), 함수 호출(function calling), 그리고 Model Context Protocol에 대해 심도 있게 살펴볼 것입니다. 즉, 에이전트가 보안상의 위험 요소가 되지 않으면서 실제로 어떻게 세상에 손을 뻗는지에 대해 다룰 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기