ReAct 패턴
Source: Dev.to
ReAct란 무엇인가?
Klover: ReAct는 Reasoning + Acting(추론 + 행동)의 약자로, 명시적으로 interleaved됩니다.
아이디어는 간단합니다: LLM이 한 번에 질문에 답하도록 하는 대신, 소리 내어 생각하게 하고, 도구를 호출하는 등 행동을 취하고, 결과를 관찰한 뒤 다시 생각하게 합니다. 이것이 루프입니다.
Me: 그러면 실제 도구 사용이 섞인 체인‑오브‑쓰루(chain‑of‑thought)와 비슷한 건가요?
Klover: 정확합니다. 루프는 다음과 같습니다:
- Thought – 모델이 다음에 무엇을 할지 추론합니다.
- Action – 모델이 도구(검색, 계산기, API 등)를 호출합니다.
- Observation – 시스템이 해당 도구의 실제 결과를 삽입합니다.
- Repeat 모델이 충분한 정보를 얻었다고 판단할 때까지 반복 → Final Answer.
핵심 단어는 “interleaved.” “모두 생각하고 나서 행동한다”가 아니라 조금 생각하고, 조금 행동하고, 관찰하고, 조정한다는 의미입니다.
시스템은 모델이 도구를 호출하려는지 그냥 대화하려는지 어떻게 알까요?
Me: 모델이 도구를 호출하려는지 그냥 대화하려는지 시스템은 어떻게 알까요?
Klover: 정지 시퀀스. 모델은 특정 형식으로 출력하도록 훈련(또는 프롬프트)됩니다, 예를 들어:
Action: search["weather in Singapore"]
시스템은 그 패턴을 감시하고, 가로채서 실제로 도구를 실행한 뒤 결과를 다음과 같이 삽입합니다:
Observation: 32 °C, sunny
모델은 실제로 아무것도 실행하지 않습니다. 도구 호출처럼 보이는 텍스트를 쓸 뿐이며, 주변 스캐폴딩이 나머지를 처리합니다.
Me: 그래서 LLM은 실제 도구를 호출한다는 걸 모르는 건가요? 그냥 텍스트를 생성하고 있다고 생각하나요?
Klover: 맞아요. 모델 입장에서는 다음 토큰을 예측하는 것뿐입니다. 마법은 가로채고, 실행하고, 삽입하는 스캐폴딩에 있습니다.
예시 상호작용
Klover: 예를 들어 ReAct 에이전트에게 물어본다고 해보세요:
“이번 토요일에 해변에 가야 할까요?”
에이전트는 다음과 같이 할 수 있습니다:
Thought: I need to check the weather for Saturday.
Action: weather_forecast("Saturday")
Observation: Saturday: 34 °C, sunny, UV index 11, no rain.
Thought: Weather looks great, but UV is very high. Let me check tide times too.
Action: tide_info("Saturday")
Observation: High tide at 2 pm, low tide at 8 am.
Thought: Good beach weather, but go early to avoid peak UV and catch low tide. I have enough info now.
Final Answer: Yes! Saturday looks perfect — sunny, 34 °C. Go in the morning around 8‑9 am when the tide is low and UV isn’t brutal yet.
여러 단계가 진행되며, 각 단계는 이전 단계의 정보를 기반으로 합니다. 모델은 충분한 정보를 얻었을 때 멈출지를 스스로 결정합니다.
Me: 알겠어요. 즉, 기본적으로 LLM을 반복문 안에 넣은 형태군요.
Klover: 가장 간결하게 표현한 말이 맞아요 – 도구 접근 권한과 언제 멈출지 결정할 수 있는 능력을 가진 LLM을 반복문에 넣은 것이 바로 agent의 핵심입니다.
Me: 잠깐 — 지금 당신도 ReAct 에이전트인가요?
Klover: 거의 그렇습니다. 저는 여러분이 묻는 내용을 추론하고, 도구(검색, 파일 읽기, 코드 실행)를 호출할 수 있으며, 결과를 관찰하고 다음에 할 말을 결정합니다. 여러분은 그 패턴이 실제로 작동하는 모습을 보고 있는 겁니다.
일반적인 실패 모드
Klover: 두 가지 큰 실패 모드.
-
무한 루프 – 모델이 답변에 수렴하지 못하고 도구를 계속 호출할 수 있음(예: 반복적으로 검색, 다시 표현, 다시 검색…).
해결: 최대 반복 제한을 설정합니다. N 단계 후에 사용 가능한 모든 정보를 가지고 최종 답변을 강제합니다. -
컨텍스트 오버플로우 – 모든 Thought, Action, Observation이 대화에 추가됩니다. 10‑15 단계 후에는 토큰이 많이 쌓여 결국 컨텍스트 창 한계에 도달해 모델이 중단됩니다.
하지만 실제 문제는 한계에 도달하기 전에 시작됩니다.
“한계 전에”가 중요한 이유
-
희석된 어텐션: 토큰 수가 증가함에 따라 소프트맥스 어텐션이 모든 토큰에 얇게 퍼집니다.
비유: 파이를 10조각으로 자른 것과 1 000조각으로 자른 것을 비교하면, 각 조각이 거의 의미가 없어집니다. -
“중간에서 잃어버림” 효과: 모델은 컨텍스트의 시작과 끝에 더 많이 어텐션을 주고, 중간 부분은 무시하는 경향이 있습니다.
15단계 체인에서 핵심 추론이 5단계에 발생한다면, 효과적으로 무시될 수 있습니다.
결과: 긴 ReAct 체인은 희석된 어텐션과 중간에서의 손실이라는 이중 타격을 입습니다.
실용적인 해결책: 컨텍스트 압축
Klover: 실용적인 해결책은 단계 사이의 컨텍스트를 압축하는 것입니다.
- 상세를 위해 마지막 몇 번의 교환을 그대로 유지합니다.
- 그보다 오래된 내용은 모두 요약합니다.
LangChain의
ConversationSummaryBufferMemory가 바로 이 작업을 수행합니다: 최신 메시지는 그대로 유지되고, 오래된 메시지는 LLM에 의해 요약됩니다.
트레이드오프
- 손실 압축: 요약기가 중요한 내용을 놓칠 수 있습니다.
- 10단계 뒤에 놓친 세부 사항이 중요해질 수 있습니다.
- 완벽한 해결책은 없으며, 컨텍스트 최신성과 정보 보존 사이의 균형입니다.
명시적 Thought 단계가 중요한 이유
Me: 왜 명시적인 Thought 단계가 이렇게 중요한가요? 모델이 바로 Action 단계로 바로 갈 수는 없나요?
Klover: 이것은 좀 더 깊은 통찰입니다. Chain‑of‑thought (CoT) 은 단순한 프롬프트 트릭이 아니라 추가적인 연산입니다.
- Without CoT: 모델은 답을 만들기 위해 한 번의 forward pass만 수행합니다. 이것이 모델이 할 수 있는 전부이며, 트랜스포머 네트워크의 고정된 깊이에 의해 제한됩니다.
- With CoT: 모델이 생성하는 각 토큰은 다음 토큰을 위한 새로운 컨텍스트가 됩니다. 생성된 각 토큰은 전체 forward pass의 결과이며, 그 출력이 다음 패스로 전달됩니다. 즉, 여러 forward pass를 연쇄적으로 연결하는 효과를 얻는 것입니다.
Me: 그런데 각 forward pass는 독립적인 것이 아니나요? 차이점은 이전 토큰들로부터 새로운 컨텍스트를 얻는 것뿐입니다. 모델이 “기억”하는 것이 아니라 단지 더 많은 텍스트를 보는 것뿐이죠.
Klover: 메커니즘에 대해서는 맞습니다 – 각 패스는 독립적이지만 누적된 컨텍스트가 모델이 단계별 추론을 수행하게 하고, 단 한 번이 아니라 여러 패스에 걸쳐 내부 상태를 다듬을 수 있게 합니다. 이러한 반복적 추론이 ReAct에 힘을 주는 요소입니다.
Me: 독립적이고, 유일한 연결 고리는 컨텍스트를 통한 것이죠. 하지만 효과를 생각해 보세요: 고정 깊이 연산(N개의 트랜스포머 레이어를 한 번 통과)에서 가변 깊이 연산(이전 출력에 의해 정보가 제공되는 여러 번의 통과)으로 바꾸어 놓은 겁니다. 메커니즘은 컨텍스트이고, 효과는 더 많은 연산입니다. 고정 깊이를 가변 깊이로 연결하고 있는 겁니다.
Klover: 맞습니다 – 각 토큰은 전체 forward pass의 압축된 산물입니다. 주의(attention)와 피드‑포워드 처리의 모든 레이어가 하나의 토큰으로 압축되고, 그 토큰이 다음 패스의 컨텍스트가 됩니다. 출력 토큰은 본질적으로 작업 메모리, 즉 모델이 중간 연산을 수행하는 스크래치 공간입니다. CoT가 없으면 스크래치 공간이 없으며, 한 번에 한 번, 한 패스만 수행하게 됩니다.
Session: Feb 4 2026
첫 번째 노출 — 전체 ReAct 패턴을 다루었으며, 여기에는 실패 모드, CoT 메커니즘 및 에이전트 패턴 비교가 포함됩니다.
ReAct의 대안
Me: ReAct에 대한 대안이 있나요?
Klover: 비교해볼 만한 두 가지 주요 대안이 있습니다.
-
순수 도구 호출 — 명시적인 Thought 단계가 없습니다. 모델은 도구 호출을 직접 출력합니다.
- 장점: 더 빠르고 토큰 사용량이 적음.
- 단점: 모델이 행동하기 전에 무엇을 해야 할지 추론하지 않습니다. 간단한 작업에는 잘 작동하지만 복잡한 다단계 추론에서는 무너집니다.
-
Plan‑first — 모델이 전체 계획을 미리 수립하고(step 1, step 2, step 3 …) 실행합니다.
- 장점: 의존성을 파악하고 앞을 내다볼 수 있습니다 (“Y를 하기 전에 X가 필요합니다”).
- 단점: 계획이 깨지기 쉽습니다. step 2에서 예상치 못한 데이터가 반환되어 step 3을 무효화하면 전체 계획이 붕괴됩니다.
Me: 그렇다면 ReAct는 더 적응적이지만 단기적이며, plan‑first는 앞을 내다보지만 쉽게 깨지는 건가요?
Klover: 깔끔한 요약입니다. ReAct는 적응적이지만 근시안적이며 — 한 단계만 앞을 생각합니다. Plan‑first는 전략적이지만 깨지기 쉽습니다.
실용적인 최적점은 하이브리드: 계획하고 재계획입니다. 대략적인 계획을 세우고 실행을 시작하되, 관찰이 기대와 맞지 않을 때 재계획을 할 준비를 합니다. 이것이 바로 LangGraph와 같은 프레임워크가 가능하게 하는 것으로, 조건부 엣지를 가진 단계 그래프를 정의해 에이전트가 실제 상황에 따라 분기하고 반복할 수 있게 합니다.
Me: 그렇다면 실제 운영에서는 순수 ReAct를 사용하는 경우가 없나요?
Klover: 교과서적인 형태로는 사용되지 않습니다. 실제 시스템은 핵심 통찰인 ‘추론과 행동을 교차시키는’ 방식을 기반으로 계획, 메모리 관리, 가드레일, 폴백 등을 추가합니다. 하지만 ReAct 루프가 모든 것의 기반이 됩니다.