ReAct 패턴이란? AI 에이전트의 추론과 행동 원리
AI가 스스로 생각하고, 도구를 쓰고, 결과를 보고 다시 판단한다. ReAct 패턴의 구조와 설계법.
들어가며: 11편이 남긴 질문
11편을 마무리하며 이런 예고를 남겼습니다.
"AI가 스스로 '어떤 도구를 쓸지'를 결정하고, 직접 검색하고, 결과를 보고 다음 행동을 판단하는 패턴이 있습니다. 이것이 ReAct입니다."
RAG는 강력하지만 한계가 있습니다. 검색할 시점을 외부 시스템이 결정하고, 무엇을 검색할지를 사람이 설계합니다. 모델은 이미 찾아진 문서를 받아서 답할 뿐입니다.
그런데 현실의 문제는 단순하지 않습니다. "오늘 서울 날씨가 어때?"라는 질문에 답하려면 날씨 API를 호출해야 합니다. "이 코드의 버그를 찾아줘"라는 요청에 응하려면 코드를 실행해봐야 합니다. "경쟁사 A와 B의 최신 가격을 비교해줘"라는 질문에 답하려면 두 사이트를 순서대로 검색해야 합니다.
이런 문제들은 사전에 "무엇을 검색할지"를 결정할 수 없습니다. 모델이 추론하면서 스스로 무엇이 필요한지를 판단하고, 직접 도구를 사용하고, 결과를 보고 다음 행동을 결정해야 합니다.
ReAct는 바로 이 과정을 구조화한 패턴입니다.
1. ReAct란 무엇인가
1.1. 이름의 의미
ReAct는 Reasoning과 Acting의 합성어입니다. 2022년 구글 리서치와 프린스턴 대학교가 발표한 논문에서 처음 제안된 개념으로, 이후 AI 에이전트 설계의 표준 패턴이 되었습니다.
이름 그대로 두 가지를 엮습니다.
- Reasoning: 현재 상황을 분석하고, 무엇을 해야 할지 추론한다
- Acting: 추론한 결과를 바탕으로 도구를 사용하거나 행동한다
그리고 여기에 세 번째 요소가 더해집니다.
- Observation: 행동의 결과를 관찰하고, 다시 추론에 반영한다
이 세 가지가 반복되는 사이클이 ReAct의 핵심입니다.
1.2. CoT와 무엇이 다른가
5편에서 다뤘던 Chain-of-Thought(CoT)는 추론 과정을 단계별로 펼쳐 모델이 더 정확하게 생각하도록 유도하는 기법입니다. 하지만 CoT는 추론만 합니다. 외부 세계와 상호작용하지 않습니다.
# CoT — 추론만 한다
생각: 서울의 현재 기온을 알려면 날씨 데이터가 필요하다.
생각: 훈련 데이터에서 서울의 평균 기온을 떠올리면...
생각: 4월이니까 대략 15도 전후일 것이다.
답변: 서울의 날씨는 약 15도 정도입니다. (← 추측)
# ReAct — 추론하고 행동한다
생각: 서울의 현재 기온을 알려면 실시간 날씨 API가 필요하다.
행동: weather_api("서울")
관찰: {"temperature": 11, "condition": "맑음", "humidity": 45}
생각: 실제 기온은 11도이고 맑다.
답변: 현재 서울의 날씨는 맑음, 기온 11도입니다.
CoT는 모델의 내부 지식만으로 추론합니다. ReAct는 추론하다가 외부 정보가 필요하면 도구를 사용해서 실제 데이터를 가져옵니다. 그리고 그 결과를 보고 다시 추론을 이어갑니다.
2. ReAct 사이클: Thought → Action → Observation

ReAct의 핵심은 세 단계의 반복입니다.
[Thought] 현재 상황을 분석하고, 다음에 무엇을 해야 할지 판단한다
↓
[Action] 판단에 따라 도구를 호출하거나, 최종 답변을 생성한다
↓
[Observation] 도구 호출 결과를 받는다
↓
[Thought] 결과를 바탕으로 다시 분석하고 판단한다
↓
... (반복) ...
↓
[Final Answer] 충분한 정보가 모이면 최종 답변을 생성한다
각 단계를 구체적으로 살펴보겠습니다.
2.1. Thought — 추론 단계
모델이 현재 상황을 분석하고 다음 행동을 계획합니다. 이 단계는 사용자에게 보여지지 않는 내부 추론 공간입니다. 모델이 자신의 생각을 정리하는 공간이기 때문에, 제약 없이 자유롭게 생각할 수 있어야 합니다.
좋은 Thought는 세 가지를 포함합니다.
- 현재 상태 파악: 지금까지 무엇을 알고 있는가
- 다음 필요 정보: 무엇이 더 필요한가
- 행동 계획: 어떤 도구로 그 정보를 얻을 것인가
Thought: 사용자가 A 주식과 B 주식 중 어느 것이 더 나은지 묻고 있다.
이를 판단하려면 두 회사의 현재 주가와 최근 실적이 필요하다.
먼저 A 주식의 현재 정보를 조회하겠다.
2.2. Action — 행동 단계
Thought에서 계획한 행동을 실행합니다. 행동은 크게 두 종류입니다.
도구 호출: 외부 시스템에 요청을 보냅니다.
Action: stock_search(ticker="AAPL")
Action: web_search(query="삼성전자 2026년 1분기 실적")
Action: calculator(expression="(1250 - 980) / 980 * 100")
Action: code_executor(code="import pandas as pd\ndf.describe()")
최종 답변 생성: 충분한 정보가 모였다고 판단하면 루프를 종료하고 사용자에게 답합니다.
Action: final_answer("A 주식의 현재 주가는 ... B 주식의 주가는 ...")
이 두 가지를 명확히 구분하는 것이 중요합니다. 모델이 도구 호출과 최종 답변을 혼동하면 루프가 의도치 않게 종료되거나 반대로 끝나지 않습니다.
2.3. Observation — 관찰 단계
도구 호출 결과를 모델에게 돌려줍니다. 이 단계는 모델이 직접 수행하는 것이 아니라 외부 시스템(코드, API, 데이터베이스)이 결과를 생성하고 컨텍스트에 주입합니다.
Observation: {"ticker": "AAPL", "price": 193.45, "change": "+1.2%",
"pe_ratio": 28.4, "market_cap": "2.9T"}
Observation이 컨텍스트에 추가되면 모델은 다시 Thought 단계로 돌아가 이 정보를 바탕으로 다음 행동을 계획합니다.
2.4. 실제 전체 흐름
질문: "삼성전자 주가와 SK하이닉스 주가를 비교해줘"
Thought: 두 회사의 현재 주가를 비교해야 한다.
먼저 삼성전자 주가를 조회한다.
Action: stock_search(ticker="005930")
Observation: {"name": "삼성전자", "price": 74200, "change": "-0.8%",
"52w_high": 88400, "52w_low": 54100}
Thought: 삼성전자 주가는 74,200원이다.
이제 SK하이닉스 주가를 조회한다.
Action: stock_search(ticker="000660")
Observation: {"name": "SK하이닉스", "price": 198500, "change": "+2.1%",
"52w_high": 238000, "52w_low": 141500}
Thought: SK하이닉스 주가는 198,500원이다.
두 주가를 비교할 수 있는 충분한 정보가 모였다.
52주 고점 대비 현재 위치도 함께 계산해서 답하면 유용하다.
Action: final_answer(
"삼성전자: 74,200원 (52주 고점 88,400원 대비 -16.1%)\n
SK하이닉스: 198,500원 (52주 고점 238,000원 대비 -16.6%)\n
두 종목 모두 52주 고점 대비 약 16% 하락한 상태입니다."
)
3. 도구 설계: 모델이 쓸 수 있는 도구를 어떻게 만들 것인가
ReAct의 성능은 도구의 품질에 크게 의존합니다. 아무리 좋은 추론 능력도, 도구가 잘못 설계되어 있으면 제대로 활용되지 않습니다.
3.1. 도구 스펙의 4요소
모델에게 도구를 알려줄 때는 네 가지 요소를 반드시 포함해야 합니다.
도구명: weather_api
설명: 도시명을 받아 현재 날씨 정보를 반환한다. 실시간 데이터를 사용하므로 날씨 관련 질문에 항상 이 도구를 사용해야 한다.
파라미터:
- city (string, 필수): 조회할 도시명. 한국어 또는 영어 모두 지원.
- unit (string, 선택): 온도 단위. "celsius"(기본값) 또는 "fahrenheit"
반환값: {"temperature": number, "condition": string, "humidity": number, "wind_speed": number}
- 도구명: 함수명처럼 명확하고 의미가 분명해야 합니다
- 설명: 언제 이 도구를 써야 하는지 명시합니다. "날씨 관련 질문에 항상"처럼 사용 조건을 구체적으로 적어야 합니다
- 파라미터: 각 파라미터의 타입, 필수 여부, 허용 값을 명시합니다
- 반환값: 어떤 형태의 데이터가 오는지 알아야 모델이 결과를 제대로 해석합니다
3.2. 좋은 도구 설명과 나쁜 도구 설명
# 나쁜 도구 설명
도구명: search
설명: 검색한다
# 좋은 도구 설명
도구명: web_search
설명: 최신 뉴스, 실시간 정보, 훈련 데이터 이후 사건을 조회할 때 사용한다.
모델이 알 수 없는 최신 정보가 필요한 모든 상황에 이 도구를 사용한다.
검색어는 핵심 키워드 위주로 간결하게 작성할수록 결과 품질이 높다.
좋은 도구 설명은 모델이 "언제 이 도구를 쓸지"를 스스로 판단할 수 있게 합니다. 설명이 모호하면 모델이 비슷한 도구 중 어떤 것을 써야 할지 혼란스러워합니다.
3.3. 도구 개수는 최소화한다
도구가 많을수록 모델이 잘못된 도구를 선택할 확률이 높아집니다. 실용적인 기준으로 한 에이전트에 5~7개 이하의 도구를 권장합니다.
기능이 비슷한 도구는 하나로 합칩니다. "뉴스 검색"과 "웹 검색"을 따로 두는 대신, "web_search"에 source 파라미터(news / general)를 추가하는 것이 낫습니다.
4. ReAct 프롬프트 설계
4.1. 시스템 프롬프트 구조
ReAct 에이전트의 시스템 프롬프트는 세 영역으로 구성됩니다.
<role>
당신은 사용자의 질문에 답하기 위해 도구를 사용하는 AI 에이전트다.
충분한 정보를 수집한 뒤에만 최종 답변을 생성한다.
</role>
<tools>
아래 도구들을 사용할 수 있다. 도구는 반드시 아래 형식으로 호출한다.
[web_search]
설명: 최신 뉴스 및 실시간 정보를 검색한다.
파라미터: query (string, 필수)
반환: 검색 결과 상위 5개의 제목과 내용 요약
[calculator]
설명: 수식을 계산한다. 숫자 계산이 필요한 모든 상황에 사용한다.
파라미터: expression (string, 필수)
반환: 계산 결과 (number)
[final_answer]
설명: 충분한 정보가 모였을 때 사용자에게 최종 답변을 전달한다.
파라미터: answer (string, 필수)
이 도구를 호출하면 루프가 종료된다.
</tools>
<format>
반드시 아래 형식을 따른다.
Thought: [현재 상황 분석 및 다음 행동 계획]
Action: [도구명]([파라미터])
도구 결과를 받으면:
Observation: [도구 반환값]
Thought: [결과 분석 및 다음 계획]
Action: [다음 도구 호출 또는 final_answer]
최종 답변 준비 시:
Thought: [충분한 정보 확인]
Action: final_answer([답변])
</format>
4.2. 루프 제어
ReAct는 사이클이 반복되는 구조이기 때문에 루프 제어가 필수입니다. 두 가지를 반드시 설정해야 합니다.
최대 스텝 수 제한
MAX_STEPS = 10
for step in range(MAX_STEPS):
response = llm(context)
if "final_answer" in response:
break # 정상 종료
tool_result = execute_tool(response)
context.append(tool_result)
else:
# MAX_STEPS 초과 시 강제 종료
context.append("최대 스텝 수를 초과했습니다. 지금까지의 정보로 답변합니다.")
response = llm(context)
조기 종료 조건
모델이 final_answer를 호출하거나, 오류가 반복되거나, 같은 도구를 같은 파라미터로 세 번 이상 호출하면 루프를 종료합니다. 무한 루프를 방지하는 가장 실용적인 방법입니다.
4.3. 컨텍스트 누적 방식
ReAct는 매 스텝마다 컨텍스트가 늘어납니다. Thought, Action, Observation이 쌓이면서 토큰이 빠르게 소모됩니다. 스텝이 많을수록 비용이 증가하고 컨텍스트 한도에 도달할 위험이 높아집니다.
실용적인 해결책은 요약(Summarization)입니다. 스텝이 5회를 넘어가면 이전 Thought/Action/Observation을 간결하게 요약해서 누적 토큰을 줄입니다.
# 요약 전 (5 스텝)
Thought: 삼성전자 주가를 조회해야 한다.
Action: stock_search(ticker="005930")
Observation: {"price": 74200, ...}
Thought: SK하이닉스를 조회해야 한다.
Action: stock_search(ticker="000660")
Observation: {"price": 198500, ...}
...
# 요약 후
[지금까지 수집된 정보]
- 삼성전자 현재가: 74,200원, 52주 고점 대비 -16.1%
- SK하이닉스 현재가: 198,500원, 52주 고점 대비 -16.6%
5. ReAct 실패 패턴
ReAct를 처음 도입할 때 자주 마주치는 실패 패턴입니다.
5.1. 무한 루프 — 종료 조건을 못 찾는다
모델이 final_answer를 호출해야 할 시점을 판단하지 못하고 도구를 계속 호출합니다. 이미 충분한 정보가 있음에도 "더 확인해야 할 것 같다"는 판단을 반복하는 경우입니다.
Thought: 삼성전자 주가를 확인했다. SK하이닉스도 확인했다.
LG전자도 확인하면 비교가 더 풍부할 것 같다.
Action: stock_search(ticker="066570")
Observation: ...
Thought: LG디스플레이도 확인하면 더 좋을 것 같다.
... ← 이미 질문에 필요한 정보는 다 모였는데 계속 진행
해결책은 시스템 프롬프트에 종료 조건을 명시하는 것입니다.
질문에 직접 답하는 데 필요한 정보가 모였다고 판단되면
즉시 final_answer를 호출한다. 추가 정보를 수집하는 것이
항상 더 좋은 것은 아니다. 핵심 정보로 간결하게 답하는 것을 우선한다.
5.2. 환각 도구 호출 — 존재하지 않는 도구를 쓴다
모델이 제공되지 않은 도구를 호출하거나, 존재하는 도구를 다른 이름으로 호출합니다.
Action: google_search("삼성전자 주가") ← 제공된 도구 이름은 "web_search"
Action: stock_price_api(ticker="005930") ← 존재하지 않는 도구
도구 설명에 "반드시 아래 목록에 있는 도구만 사용한다"는 제약을 명시하고, 존재하지 않는 도구 호출 시 에러 메시지를 Observation으로 돌려주어 모델이 수정할 기회를 줍니다.
def execute_tool(action: str) -> str:
tool_name = parse_tool_name(action)
if tool_name not in AVAILABLE_TOOLS:
return f"Observation: 오류 — '{tool_name}'은 사용 가능한 도구 목록에 없습니다. 사용 가능한 도구: {list(AVAILABLE_TOOLS.keys())}"
return AVAILABLE_TOOLS[tool_name](action)
5.3. 조기 종료 — 정보가 부족한데 답한다
충분한 정보를 모으기 전에 final_answer를 호출합니다. 한 번의 검색으로 얻은 불완전한 정보로 답변을 생성하는 경우입니다.
Action: web_search("삼성전자 SK하이닉스 주가")
Observation: 삼성전자 관련 기사가 주로 검색됨. SK하이닉스 정보 없음.
Action: final_answer("삼성전자 주가는 ...입니다.") ← SK하이닉스 정보 없이 종료
시스템 프롬프트에 "질문에 나온 모든 항목에 대한 정보가 모인 경우에만 final_answer를 호출한다"는 조건을 추가합니다.
5.4. Observation을 무시한다
도구 결과를 제대로 반영하지 않고 이전에 가진 지식으로 답합니다.
Action: stock_search(ticker="005930")
Observation: {"price": 74200}
Thought: 삼성전자 주가는 훈련 데이터 기준으로 약 6만원대다.
Answer: 삼성전자 주가는 약 6만원입니다. ← Observation 무시
이 경우 시스템 프롬프트에 "Observation의 데이터가 내 사전 지식과 다를 경우 항상 Observation을 우선한다"는 규칙을 추가합니다.
6. RAG와 ReAct: 언제 무엇을 쓸 것인가
RAG와 ReAct는 모두 외부 정보를 활용하는 패턴이지만 역할이 다릅니다.
| 항목 | RAG | ReAct |
|---|---|---|
| 검색 주체 | 외부 시스템 | 모델 스스로 |
| 도구 종류 | 벡터 검색 DB | 다양한 도구 (API, DB, 코드 실행 등) |
| 적합한 질문 | "이 문서에서 X를 찾아줘" | "X와 Y를 비교해서 알려줘" |
| 스텝 수 | 단일 스텝 | 다중 스텝 |
| 비용 | 낮음 | 높음 (스텝마다 LLM 호출) |
| 복잡도 | 낮음 | 높음 |
단순한 검색과 문서 기반 답변이 필요하면 RAG로 충분합니다. 여러 단계의 판단과 다양한 도구 사용이 필요하면 ReAct가 적합합니다. 실전에서는 두 패턴을 결합하는 경우가 많습니다. ReAct 루프 안에서 필요할 때 RAG 도구를 사용하는 방식입니다.
# RAG + ReAct 결합
[도구 목록]
- vector_search: 내부 문서 데이터베이스에서 관련 문서를 검색한다 (← RAG)
- web_search: 실시간 외부 정보를 검색한다
- calculator: 수식을 계산한다
- final_answer: 최종 답변을 생성한다
결론: 단순 응답에서 에이전트로
1편부터 11편까지는 "모델이 더 잘 답하게 하는 법"을 다뤘습니다. 더 좋은 질문, 더 명확한 구조, 더 정확한 컨텍스트를 통해 모델의 답변 품질을 높이는 것이 목표였습니다.
ReAct는 다른 차원의 이야기입니다. 모델이 단순히 입력을 받고 출력을 내는 것이 아니라, 스스로 판단하고 행동하고 결과를 보고 다시 판단합니다. 프롬프트에 답하는 기계가 아니라, 목표를 향해 스스로 경로를 찾아가는 에이전트가 됩니다.
Thought → Action → Observation. 이 세 단계의 반복이 AI 에이전트의 기반입니다.
핵심 원칙 요약
| 원칙 | 핵심 |
|---|---|
| 사이클 구조 | Thought → Action → Observation을 반복한다. 충분한 정보가 모이면 final_answer로 종료한다 |
| 도구 설계 | 설명에 "언제 쓸지"를 명시한다. 도구 개수는 5~7개 이하로 유지한다 |
| 루프 제어 | 최대 스텝 수를 반드시 설정한다. 같은 행동 반복 시 강제 종료한다 |
| Observation 우선 | 도구 결과는 항상 사전 지식보다 우선한다 |
| RAG + ReAct | 두 패턴을 결합하면 내부 문서와 외부 실시간 정보를 모두 활용할 수 있다 |
13편을 향하여
ReAct는 단일 에이전트가 스스로 추론하고 행동하는 패턴입니다. 하지만 복잡한 목표를 달성하려면 단일 에이전트로는 한계가 있습니다. 계획을 세우는 에이전트, 실행하는 에이전트, 검토하는 에이전트를 분리하면 더 정교한 결과를 얻을 수 있습니다.
[13편: Agentic 프롬프팅 – 여러 에이전트가 협력하는 시스템 설계] 에서는 단일 에이전트를 넘어, 복수의 에이전트가 역할을 나누어 협력하는 멀티 에이전트 시스템의 설계와 프롬프트 전략을 다룹니다.
