ARCH · REACT
ReAct 심화
ReAct(Reasoning + Acting)의 Thought-Action-Observation 루프를 내부에서 분해하고, LangGraph로 커스터마이징 가능한 ReAct를 구현합니다.
ReAct 루프 내부 구조
ReAct는 LLM이 추론(Thought) → 행동(Action) → 관찰(Observation)을 반복하며 문제를 해결하는 패러다임입니다. 단순해 보이지만, 각 단계에서 정밀한 프롬프트 엔지니어링과 상태 관리가 필요합니다.
그림 1. ReAct Thought-Action-Observation 루프 — 완료 조건 판단이 핵심
LangGraph로 커스텀 ReAct 구현
create_react_agent는 편리하지만 커스터마이징이 제한됩니다. LangGraph StateGraph로 직접 구현하면 각 노드에 로깅, 검증, 캐싱을 추가할 수 있습니다.
pythoncustom_react.py — 커스텀 ReAct 에이전트
from langgraph.graph import StateGraph, END
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
from typing import TypedDict, Annotated
from operator import add
import json
MAX_ITERATIONS = 10 # 무한 루프 방지 상한
class AgentState(TypedDict):
messages: Annotated[list, add]
iteration: int
tool_calls_made: list[str] # 중복 도구 호출 추적
llm = ChatAnthropic(model="claude-opus-4-7")
# ─── THOUGHT 노드 ────────────────────────────────────
async def reasoning_node(state: AgentState) -> dict:
"""LLM이 다음 행동을 추론"""
iteration = state["iteration"] + 1
# 시스템 프롬프트에 현재 반복 횟수 주입
system = f"""당신은 도구를 사용하여 질문에 답하는 AI 에이전트입니다.
현재 반복: {iteration}/{MAX_ITERATIONS}
규칙:
1. 확실한 정보가 있으면 바로 Final Answer 작성
2. 동일한 도구를 동일한 인수로 두 번 호출하지 마세요
3. 불필요한 도구 호출은 피하세요"""
llm_with_tools = llm.bind_tools(tools)
response = await llm_with_tools.ainvoke(
state["messages"],
system=system
)
return {"messages": [response], "iteration": iteration}
# ─── ACTION 노드 ────────────────────────────────────
async def action_node(state: AgentState) -> dict:
"""도구 실행 + 중복 호출 감지"""
last_message = state["messages"][-1]
tool_messages = []
calls_made = state["tool_calls_made"][:]
for call in last_message.tool_calls:
call_signature = f"{call['name']}:{json.dumps(call['args'], sort_keys=True)}"
if call_signature in calls_made:
# 중복 호출 — 캐시된 결과 반환
result = "[캐시] 이전과 동일한 결과"
else:
calls_made.append(call_signature)
result = await execute_tool(call["name"], call["args"])
tool_messages.append(ToolMessage(
tool_call_id=call["id"],
content=str(result)
))
return {"messages": tool_messages, "tool_calls_made": calls_made}
# ─── 라우팅 ────────────────────────────────────────
def should_continue(state: AgentState) -> str:
last = state["messages"][-1]
# 강제 종료: 반복 초과
if state["iteration"] >= MAX_ITERATIONS:
return "end"
# 도구 호출이 있으면 action으로
if hasattr(last, "tool_calls") and last.tool_calls:
return "action"
return "end"
# ─── 그래프 구성 ─────────────────────────────────────
builder = StateGraph(AgentState)
builder.add_node("reasoning", reasoning_node)
builder.add_node("action", action_node)
builder.set_entry_point("reasoning")
builder.add_conditional_edges("reasoning", should_continue, {
"action": "action",
"end": END
})
builder.add_edge("action", "reasoning")
agent = builder.compile()
ReAct 프롬프트 엔지니어링
ReAct의 성능은 시스템 프롬프트 품질에 크게 의존합니다. 특히 도구 사용 가이드라인과 최종 답변 형식을 명확히 지정해야 합니다.
pythonreact_prompt.py — 고품질 ReAct 시스템 프롬프트
REACT_SYSTEM_PROMPT = """당신은 정확하고 효율적인 AI 에이전트입니다.
## 사고 과정 (Thought)
각 단계에서 다음을 명확히 합니다:
- 현재까지 파악한 정보
- 아직 확인이 필요한 정보
- 다음 행동의 이유
## 도구 사용 원칙
1. **최소 호출**: 한 번의 도구 호출로 필요한 모든 정보를 얻으세요
2. **병렬 가능 도구**: 독립적인 정보는 Parallel Tool Calling 활용
3. **오류 처리**: 도구 실패 시 대체 방법을 시도하거나 현재 정보로 답변
## 최종 답변 기준
다음 조건 중 하나라도 충족하면 즉시 최종 답변을 작성하세요:
- 질문에 답하기에 충분한 정보를 수집한 경우
- 더 이상 새로운 정보를 얻을 수 없는 경우
- 5회 이상 도구를 사용한 경우
## 금지 사항
- 동일한 도구를 같은 인수로 두 번 호출
- 이미 알고 있는 정보를 재확인하기 위한 도구 호출
- 과도한 자기 의심으로 인한 루프"""
# 도구별 예시 포함 프롬프트 생성
def build_system_prompt(tools: list, domain: str = "") -> str:
tool_descriptions = "\n".join(
f"- {t.name}: {t.description}" for t in tools
)
domain_context = f"\n## 도메인: {domain}" if domain else ""
return f"""{REACT_SYSTEM_PROMPT}
## 사용 가능한 도구
{tool_descriptions}{domain_context}"""
무한 루프 방지 전략
전략 1
반복 횟수 상한
MAX_ITERATIONS(보통 10~15)를 초과하면 강제 종료 후 현재까지 수집된 정보로 최선의 답변 생성. 프로덕션에서는 반드시 설정.
전략 2
도구 호출 시그니처 추적
함수명 + 직렬화된 인수를 해시로 저장. 중복 호출 감지 시 캐시 반환 또는 루프 경고 메시지를 LLM에 전달.
전략 3
진행도 검증
N회 반복마다 LLM에게 "현재까지 얼마나 진행됐나?"를 묻는 메타 추론 노드 삽입. 루프 감지 시 전략 전환.
전략 4
총 토큰 예산
전체 대화 토큰 합계가 임계치에 도달하면 요약 후 종료. 비용 통제와 루프 방지를 동시에 달성.
ReAct vs 대안 아키텍처
| 항목 | ReAct | Plan-and-Execute | LATS |
|---|---|---|---|
| 계획 방식 | 즉각적(온라인) | 사전 계획 후 실행 | 트리 탐색 |
| 적응성 | 높음 — 즉시 재계획 | 중간 — 오류 시 재계획 | 높음 — 백트래킹 |
| LLM 호출 수 | 적음 | 중간 | 많음 |
| 복잡 문제 | 중간 | 좋음 | 최고 |
| 비용 | 낮음 | 중간 | 높음 |
| 구현 복잡도 | 낮음 | 중간 | 높음 |
💡 언제 ReAct를 선택하나
- 사용 — 단일 단계 질문-답변, 3~5개 도구, 빠른 응답 필요, 예산 제한
- 대체 고려 — 10단계 이상의 복잡한 계획, 여러 하위 목표를 가진 프로젝트형 작업
- 대부분 프로덕션 Agent는 ReAct를 기본으로 사용하고, 복잡한 작업에만 Planning을 추가합니다