AI Agents: 3가지 필수 패턴 (ReAct) 마스터하기. Part 2 of 3
Source: Dev.to
위에 제공된 링크만으로는 번역할 본문이 없습니다. 번역을 원하는 텍스트(본문)를 복사해서 여기 채팅창에 붙여 주시면, 요청하신 대로 마크다운 형식과 코드 블록을 그대로 유지하면서 한국어로 번역해 드리겠습니다.
기사 [파트 1]
The code for these patterns is available on GitHub. [Repo]
“툴‑사용” 패턴 (기사 1)
We gave the AI hands to interact with the outside world.
With ReAct, we are installing a functional brain.
요약
-
In Article 1 the flow was completely linear:
Stimulus → ResponseYou asked for a piece of data, the agent fired a tool, and that was it.
No doubts, no plans. -
The real world is messy.
What happens when a single click isn’t enough?
What if you need to investigate, compare data, and then perform a calculation?
That is where the linear model crashes and ReAct (proposed by researchers at Princeton and Google in 2022) comes into play. It is basically the industry standard today.
The name comes from Reasoning + Acting.
The breakthrough idea isn’t just using tools (we already had that); we force the LLM to have an internal monologue – a Thought Trace.
Instead of rushing to answer blindly, the agent enters a cognitive loop:
The agent literally talks to itself in the logs.
It plans its next step based on what it just discovered a second ago, allowing it to course‑correct on the fly if things don’t go as expected.
내부 동작 – 예시
User query: “What is the square root of the age of the current President of France?”
| Iteration | Action | Observation |
|---|---|---|
| 1 | search_tool("Current President of France") | Returns “Emmanuel Macron”. → 반환 “Emmanuel Macron”. |
| 2 | search_tool("Emmanuel Macron age") | Returns “47”. → 반환 “47”. |
| 3 | calculator_tool("sqrt(47)") | Returns “6.85”. → 반환 “6.85”. |
| Final Answer | — | “The square root of Emmanuel Macron’s age (47) is approximately 6.85.” → “프랑스 현 대통령인 Emmanuel Macron의 나이(47)의 제곱근은 약 6.85입니다.” |
See the magic? → 마법을 보셨나요? Step 3 would be impossible without having discovered Step 2 first. The agent has been connecting the dots. → 에이전트는 점을 연결하고 있습니다.
Agno를 사용한 ReAct 패턴 구현
Agno를 사용하여 위 예제를 만들고, Think → Act → Observe → Repeat 루프를 구현합니다.
import os
import sys
import logging
import traceback
from typing import List, Optional
from dotenv import load_dotenv, find_dotenv
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.tools.tavily import TavilyTools
# ----------------------------------------------------------------------
# 1. Global Logging and Error Handling Configuration
# ----------------------------------------------------------------------
LOG_DIR = os.path.join(os.path.dirname(__file__), "log")
LOG_FILE = os.path.join(LOG_DIR, "logs.txt")
if not os.path.exists(LOG_DIR):
os.makedirs(LOG_DIR)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.FileHandler(LOG_FILE, encoding="utf-8"),
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
def global_exception_handler(exctype, value, tb):
"""Capture unhandled exceptions and record them in the log."""
error_msg = "".join(traceback.format_exception(exctype, value, tb))
logger.error(f"Unhandled exception:\n{error_msg}")
sys.__excepthook__(exctype, value, tb)
sys.excepthook = global_exception_handler
# ----------------------------------------------------------------------
# 2. Environment Variables Loading
# ----------------------------------------------------------------------
env_path = find_dotenv()
if env_path:
load_dotenv(env_path)
logger.info(f".env file loaded from: {env_path}")
else:
logger.warning(".env file not found")
# ----------------------------------------------------------------------
# 3. Tool Definitions
# ----------------------------------------------------------------------
def calculate(expression: str) -> str:
"""
Solve a simple mathematical expression (addition, subtraction,
multiplication, division). Useful for calculating year or date differences.
Args:
expression (str): e.g. "2024 - 1789"
"""
try:
# Allow only safe characters for basic eval
allowed_chars = "0123456789+-*/(). "
if all(c in allowed_chars for c in expression):
result = eval(expression)
return f"Result: {result}"
else:
return "Error: Disallowed characters in expression."
except Exception as e:
return f"Error while calculating: {str(e)}"
# ----------------------------------------------------------------------
# 4. Agno Agent Configuration (ReAct Pattern)
# ----------------------------------------------------------------------
model_id = os.getenv("BASE_MODEL", "gpt-4o")
agent = Agent(
model=OpenAIChat(id=model_id),
tools=[TavilyTools(), calculate],
instructions=[
"You are a researcher using the ReAct (Reason + Act) method.",
"1. **Think** step‑by‑step about what information you need to answer the user's question.",
"2. Use the search tool (Tavily) to find specific dates, facts, or data.",
"3. Use the calculator ('calculate') for any mathematical operation or time calculation.",
"4. Do not guess historical information. If you don't have a piece of data, look it up.",
"5. Show your reasoning clearly: 'Thought:', 'Action:', 'Observation:'.",
"6. Continue investigating until you have a complete and verified answer."
],
)
# ----------------------------------------------------------------------
# 5. User Interface
# ----------------------------------------------------------------------
def main():
logger.info("Starting Historical Detective Agent (ReAct)...")
print("--- Historical Detective - ReAct Pattern ---")
print("Type 'exit' to quit.\n")
while True:
try:
user_input = input("Researcher, what is your question?: ")
if user_input.lower() == "exit":
코드에서 ReAct은 어디에 있나요?
| Component | How it implements ReAct |
|---|---|
| THINK | 에이전트의 instructions에 정의되어 있습니다 – 첫 번째 항목에서 모델에게 step‑by‑step으로 생각하고 자신의 추론을 (Thought:) 표시하도록 지시합니다. |
| ACT | tools 리스트(TavilyTools()와 calculate)가 에이전트가 수행할 수 있는 행동을 제공합니다. 지시문에 “검색 도구 사용 …” 및 “계산기 사용 …”이라고 명시되어 있습니다. |
| OBSERVE | 각 도구 호출 후 에이전트는 observation(도구의 출력)을 받아 다음 반복에서 참조할 수 있습니다. |
| REPEAT | 루프는 암묵적으로 존재합니다: 모델은 최종 지시문(“완전하고 검증된 답변이 나올 때까지 조사 계속”)가 만족될 때까지 Thought → Action → Observation 사이클을 계속 생성합니다. |
도구에 정의된 외부 기능
tools = [TavilyTools(), calculate]
OBSERVE: 프레임워크(Agno)가 도구를 실행하고 결과를 에이전트에 반환합니다.
Agent class: 에이전트는 calculate의 반환값(예: Result: 6.85)을 “읽습니다”.
REPEAT: 에이전트가 충분한 정보를 얻을 때까지 루프가 계속됩니다.
agent.print_response(..., show_tool_calls=True)
show_tool_calls=True는 매우 중요합니다: 콘솔에서 에이전트가 실시간으로 어떻게 “행동을 실행”하는지 확인할 수 있게 해줍니다.
왜 우리는 이 패턴을 이렇게 좋아할까? (장점)
- 다중 홉 문제 해결 – 에이전트는 A가 B로, B가 C로 이어지는 질문에 답할 수 있습니다.
- 자기 치유 능력 – 검색이 실패하면(예: “마크롱의 나이”), 일반 스크립트는 충돌합니다. ReAct 에이전트는 “검색이 실패했습니다. 그의 생년월일을 찾아 직접 나이를 계산하겠습니다.”라고 생각합니다.
- 블랙 박스와 작별 – 개발자는 Thoughts를 읽고 에이전트가 계산기(또는 다른 도구)를 선택한 정확한 이유를 알 수 있습니다.
- 환각 감소 – 모든 단계를 실제 관찰(
Observation)에 기반하도록 강제함으로써 모델이 데이터를 만들어내기가 훨씬 어려워집니다.
Cons & Gotchas
- It’s Slow (Latency) – ReAct은 순차적으로 작동합니다. 생각을 세 번 한다는 것은 LLM을 세 번 호출하고 도구를 실행하는 것을 의미합니다. 10–30 초 정도의 대기 시간을 예상하세요.
- The API Bill (Cost) – “내부 독백”이 많은 토큰을 소모하므로 컨텍스트 히스토리가 빠르게 채워집니다.
- Risk of Loops – 에이전트가 집착할 수 있습니다:
I search for X → Not there → I should search for X again → I search for X… - Delicate Prompting – 모델이 언제 생각을 멈추고 최종 답변을 제공해야 하는지 알 수 있도록 잘 조정된 시스템 프롬프트가 필요합니다.
Production Tips (Agno, LangChain 등)
-
브레이크를 걸어라 – 최대 반복 횟수
항상 반복 제한을 설정하세요 (예: 10 단계). 문제가 10 단계 안에 해결되지 않으면 100 단계에서 마법처럼 해결되지 않으며, 비용만 낭비하게 됩니다. -
멈추는 시점을 가르쳐라 – 정지 시퀀스
LLM은 액션을 실행한 직후에 작성을 멈춰야 합니다. 이를 중단하지 않으면 스스로 관찰값을 만들어내기 시작합니다(도구 결과를 환각함). 최신 프레임워크는 보통 이를 처리하지만, 주의 깊게 살펴보세요. -
쓰레기를 정리하라 – 컨텍스트 관리
긴 대화에서는 오래된 사고 흔적을 삭제하고 최종 답변만 남겨두세요. 그렇지 않으면 에이전트가 즉시 “RAM”(컨텍스트 윈도우)을 다 써버립니다.
예시 사용 사례
- 코딩 에이전트 (예: Devin, Copilot) –
코드 작성 → 실행 → 오류 확인 → 해결 방법 고민 → 재작성 - 레벨‑2 기술 지원 –
티켓 읽기 → 서버 상태 확인 → 사용자 로그 확인 → 데이터 교차 참조 → 답변 - 재무 분석가 –
현재 가격 검색 → 최신 뉴스 검색 → 과거와 비교 → 추천 생성
코딩을 즐기세요! 🤖