형사가 사건을 수사하는 장면을 떠올려보자. 단순히 머릿속으로만 추리하는 탐정은 결국 틀린 결론에 빠지기 쉽다. 반대로 아무 생각 없이 현장을 뛰어다니기만 하는 수사관도 단서를 놓친다. 뛰어난 형사는 "이 혈흔의 위치로 보아 범인은 왼손잡이일 것이다(추론) → 용의자 명단에서 왼손잡이를 조회한다(행동) → 세 명이 검색됐다(관찰) → 그중 현장에 있었던 인물을 확인한다(추론) → 목격자 진술을 검색한다(행동)"는 식으로 생각과 행동을 교차한다.
ReAct는 이 패턴을 AI에 적용한 프레임워크다. 기존 언어 모델은 질문을 받으면 학습된 지식만으로 답을 생성했다. 지식 컷오프 이후의 사실, 실시간 날씨, 특정 데이터베이스의 수치는 알 수 없었다. ReAct는 이 한계를 "중간에 도구를 쓰면 되잖아?"라는 단순한 발상으로 깬다. 모델이 추론 단계에서 "이건 위키피디아를 검색해야 해"라고 판단하면 실제로 검색 API를 호출하고, 결과를 받아 다음 추론을 이어간다.
실생활에서 가장 가까운 예시는 스마트폰을 든 사람이다. 어떤 질문을 받았을 때 순수하게 기억에 의존하는 사람보다, 필요한 순간에 검색하고 계산기를 켜고 지도를 확인하면서 답하는 사람이 훨씬 정확하다. ReAct는 AI가 "언제 도구를 쓸지 스스로 판단하는 능력"을 갖추게 만든 설계 원칙이다.
ReAct는 Shunyu Yao, Jeffrey Zhao, Dian Yu, Nan Du, Izhak Shafran, Karthik Narasimhan, Yuan Cao가 2022년 발표한 논문 "ReAct: Synergizing Reasoning and Acting in Language Models"에서 제안됐다.1 Google Brain과 Princeton University의 공동 연구로, ICLR 2023에 게재됐다.
핵심 아이디어는 언어 모델의 행동 공간(action space)을 기존 태스크 전용 행동뿐 아니라 자연어 추론 트레이스(thought trace)까지 확장하는 것이다.
기본 루프 구조:
Thought_t : 현재 상태에 대한 추론, 다음 행동 계획
Action_t : 외부 환경(도구, API)에 대한 호출
Observation_t : 행동의 결과
이 세 요소가 반복되며 최종 답에 도달한다:
(o_1, ..., o_t) -> Thought_t -> Action_t -> Observation_t
-> Thought_{t+1} -> Action_{t+1} -> ...
-> Action_finish(answer)
POMDP 관점의 정형화:
ReAct는 부분 관찰 마르코프 결정 과정(POMDP, Partially Observable Markov Decision Process)으로 모델링할 수 있다.
정책은 다음과 같이 정의된다:
π_θ(a_t | o_1, a_1, ..., o_{t-1}, a_{t-1}, o_t)
여기서 컨텍스트 윈도우가 사실상 에피소드 메모리 역할을 한다. Thought는 행동 공간에 속하지만 환경에 영향을 주지 않으므로 관찰값을 변경하지 않는다. 이 점에서 Thought는 내부 상태를 명시적으로 기록하는 "언어적 워킹 메모리"로 볼 수 있다.
프롬프트 구조 (Few-shot 예시):
Question: 콜로라도 조폐국 동쪽 섹터에 가장 가까운 도시는?
Thought 1: 콜로라도 조폐국 동쪽 섹터의 위치를 검색해야 한다.
Action 1: Search[Colorado State Mint eastern sector]
Observation 1: 콜로라도 조폐국은 덴버에 위치하며...
Thought 2: 덴버의 동쪽에 있는 주요 도시를 확인해야 한다.
Action 2: Search[cities east of Denver Colorado]
Observation 2: 오로라(Aurora)가 덴버 동쪽에 위치한다.
Thought 3: 답은 오로라다.
Action 3: Finish[Aurora]
논문에서 세 가지 벤치마크에 걸쳐 네 가지 방법을 비교했다.
지식 집약적 추론 (HotpotQA, FEVER)
| 방법 | HotpotQA EM | FEVER Acc |
|---|---|---|
| Standard prompting | 28.7 | 57.1 |
| CoT only | 29.4 | 56.3 |
| Act only | 25.7 | 58.9 |
| ReAct | 35.1 | 64.3 |
| ReAct + CoT-SC (best-of) | 40.7 | 69.6 |
HotpotQA에서 ReAct는 CoT 대비 약 5.7 EM 포인트 개선을 보였다. FEVER에서는 Act only보다 높은 정확도를 달성하면서도 CoT의 추론 능력을 유지했다.
의사결정 벤치마크 (AlfWorld, WebShop)
| 방법 | AlfWorld 성공률 | WebShop 점수 |
|---|---|---|
| BUTLER (IL+RL) | 22% | - |
| Act only | 45% | 33.4 |
| ReAct | 71% | 40.2 |
AlfWorld는 텍스트 기반 가상 환경에서 가구 조작 등의 태스크를 수행하는 벤치마크다. ReAct는 모방학습+강화학습 조합인 BUTLER보다 약 3배 높은 성공률을 기록했다. 이는 명시적 추론 트레이스가 희소 보상 환경에서 특히 효과적임을 보여준다.
CoT vs Act vs ReAct 오류 분석:
장점
해석 가능성(interpretability): Thought 트레이스가 그대로 남으므로 모델이 왜 특정 도구를 호출했는지 추적 가능하다. 디버깅과 감사(audit)가 용이하다.
동적 지식 접근: 학습 데이터 컷오프를 넘어선 실시간 정보에 접근 가능하다. 검색, 계산, 코드 실행 등 다양한 도구와 결합할 수 있다.
오류 복구 능력: 관찰 결과가 예상과 다르면 Thought 단계에서 전략을 수정할 수 있다. 단순 Act 루프는 이 자기 수정 기능이 없다.
Few-shot 적용: 별도의 파인튜닝 없이 몇 개의 예시 프롬프트만으로 동작한다. 모델 가중치를 건드리지 않아 배포 비용이 낮다.
한계
무한 루프 위험: 도구가 원하는 정보를 반환하지 않거나 모델이 같은 쿼리를 반복할 때 루프를 탈출하지 못한다. 별도의 최대 스텝 수 제한이 필수다.
도구 선택 오류 (tool hallucination): 존재하지 않는 도구를 호출하거나, 올바른 도구를 잘못된 인자로 호출하는 경우가 있다. 도구 명세(spec)를 프롬프트에 명확히 정의해야 한다.
컨텍스트 누적 비용: 스텝이 늘어날수록 Thought/Action/Observation이 쌓여 입력 토큰이 기하급수적으로 증가한다. 긴 에피소드에서는 비용과 지연(latency) 문제가 심각해진다.
추론 품질 의존성: Thought의 질이 낮으면 의미 없는 행동을 계속 반복한다. 모델 자체의 추론 능력이 ReAct 성능의 상한선이다.
병렬 처리 불가: 기본 ReAct는 순차적 루프이므로 여러 도구를 동시에 호출할 수 없다. 독립적인 정보 수집을 병렬화하려면 별도 설계가 필요하다.
LangChain에서의 구현
LangChain은 create_react_agent 함수로 ReAct 에이전트를 제공한다:
from langchain import hub
from langchain.agents import create_react_agent, AgentExecutor
from langchain_community.tools import DuckDuckGoSearchRun
tools = [DuckDuckGoSearchRun()]
prompt = hub.pull("hwchase17/react") # ReAct 표준 프롬프트
agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(
agent=agent,
tools=tools,
max_iterations=10, # 무한 루프 방지
handle_parsing_errors=True, # 도구 호출 오류 복구
verbose=True
)
result = executor.invoke({"input": "2025년 노벨 물리학상 수상자는 누구인가?"})
LlamaIndex에서의 구현
LlamaIndex는 ReActAgent를 내장 제공한다:
from llama_index.core.agent import ReActAgent
from llama_index.core.tools import FunctionTool
def search_web(query: str) -> str:
"""웹에서 정보를 검색한다."""
...
agent = ReActAgent.from_tools(
[FunctionTool.from_defaults(fn=search_web)],
max_iterations=15,
verbose=True
)
response = agent.chat("최신 AI 모델 벤치마크 결과를 알려줘")
Claude의 tool_use와 ReAct의 관계
@Claude Code를 포함해 Anthropic의 Claude API가 제공하는 tool_use 메시지 타입은 구조적으로 ReAct 패턴과 동일하다. Claude의 <thinking> 블록이 Thought에 해당하고, tool_use 컨텐츠 블록이 Action에, tool_result 블록이 Observation에 대응한다. 차이는 Thought가 별도 텍스트가 아닌 내부 추론(extended thinking)으로 처리된다는 점이다. @Extended Thinking 기능 활성화 시 이 Thought 과정이 명시적으로 노출된다.
@Agentic Workflow와의 관계
ReAct는 단일 에이전트 루프의 핵심 패턴이고, Agentic Workflow는 여러 ReAct 에이전트를 조합해 복잡한 파이프라인을 구성하는 상위 개념이다. 오케스트레이터-서브에이전트 구조에서 각 서브에이전트는 내부적으로 ReAct 루프로 동작한다.
프로덕션 배포 시 체크리스트:
ReAct의 발전형:
Reflexion (Shinn et al., 2023): 에피소드 종료 후 실패를 언어로 반성(reflection)하여 다음 에피소드에 반영한다. ReAct가 에피소드 내 자기 수정이라면 Reflexion은 에피소드 간 자기 개선이다.
LATS (Language Agent Tree Search) (Zhou et al., 2023): ReAct를 트리 탐색과 결합한다. 각 Thought/Action 선택지를 트리 노드로 확장하고 MCTS(Monte Carlo Tree Search) 방식으로 탐색한다. 단일 경로 실패에 취약한 ReAct의 한계를 극복한다.
Tree of Thoughts (Yao et al., 2023): 단일 추론 경로 대신 여러 Thought를 동시에 생성하고 BFS/DFS로 탐색한다. 수학 문제, 창의적 글쓰기 등 탐색이 필요한 태스크에서 유리하다.
ReWOO (Xu et al., 2023): 도구 호출 전에 전체 계획을 먼저 수립한다(Plan-then-Execute). 불필요한 도구 호출을 줄여 토큰 비용을 절감한다.
Yao, S., Zhao, J., Yu, D., Du, N., Shafran, I., Narasimhan, K., & Cao, Y. (2022). ReAct: Synergizing Reasoning and Acting in Language Models. ICLR 2023. arXiv:2210.03629. ↩