🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

ReAct 프롬프팅 완벽 가이드 - 슬라이드 1/6
A

AI Generated

2025. 12. 25. · 2 Views

ReAct 프롬프팅 완벽 가이드

LLM이 생각하고 행동하는 ReAct 패턴을 실무 중심으로 배워봅니다. Thought-Action-Observation 루프를 이해하고, 도구를 활용하는 에이전트 시스템을 직접 구현해봅니다.


목차

  1. Reasoning + Acting 패턴
  2. Thought-Action-Observation 루프
  3. 도구 사용과 결합
  4. 실습: ReAct 프롬프트 템플릿
  5. 실습: 웹 검색 + ReAct 시스템

1. Reasoning + Acting 패턴

어느 날 김개발 씨가 ChatGPT API를 활용한 챗봇을 만들고 있었습니다. 사용자가 "오늘 서울 날씨 알려줘"라고 물으면, AI는 최신 정보를 모르기 때문에 엉뚱한 답을 내놓았습니다.

선배 개발자 박시니어 씨가 다가와 물었습니다. "ReAct 패턴을 적용해봤어요?"

ReAct는 Reasoning(추론)과 Acting(행동)을 결합한 프롬프팅 패턴입니다. LLM이 단순히 답변만 생성하는 것이 아니라, 문제를 분석하고 필요한 행동을 취한 뒤 그 결과를 바탕으로 다시 생각하는 과정을 반복합니다.

마치 사람이 문제를 해결할 때 "생각 → 행동 → 관찰"을 반복하는 것처럼 작동합니다.

다음 코드를 살펴봅시다.

# ReAct 기본 프롬프트 구조
prompt = """
Question: {question}

Thought: 먼저 문제를 분석하고 어떤 정보가 필요한지 판단합니다.
Action: 필요한 도구나 API를 호출합니다.
Observation: 도구 실행 결과를 확인합니다.

Thought: 결과를 바탕으로 다음 단계를 계획합니다.
Action: 추가 정보가 필요하면 다시 도구를 호출합니다.
Observation: 최종 결과를 얻습니다.

Answer: 모든 정보를 종합하여 최종 답변을 생성합니다.
"""

# 실제 사용 예시
question = "2024년 서울의 평균 기온은 몇 도였나요?"

김개발 씨는 입사 6개월 차 주니어 개발자입니다. 최근 회사에서 고객 지원 챗봇 프로젝트를 맡게 되었는데, 기본적인 질의응답은 잘 작동하지만 실시간 정보가 필요한 질문에는 제대로 답하지 못했습니다.

"사용자가 날씨를 물어보면 AI가 2021년 데이터로 답하네요..." 김개발 씨는 고민에 빠졌습니다. 바로 그때 선배 개발자 박시니어 씨가 모니터를 보며 말했습니다.

"아, 그건 일반적인 프롬프팅의 한계예요. ReAct 패턴을 사용해보세요." ReAct 패턴이란 정확히 무엇일까요?

쉽게 비유하자면, ReAct는 마치 탐정이 사건을 해결하는 과정과 같습니다. 탐정은 먼저 사건을 분석하고(Reasoning), 현장에 가서 증거를 수집하고(Acting), 그 증거를 바탕으로 다시 추론합니다(Reasoning).

이 과정을 반복하며 진실에 다가갑니다. LLM도 마찬가지입니다.

단순히 학습된 지식으로만 답하는 것이 아니라, 필요한 정보를 능동적으로 찾아가며 문제를 해결합니다. 전통적인 프롬프팅 방식은 어땠을까요?

예전에는 "이 질문에 답해줘"라고 하면 LLM이 학습된 데이터 내에서만 답변을 생성했습니다. 최신 정보나 실시간 데이터는 전혀 활용할 수 없었습니다.

더 큰 문제는 복잡한 질문일수록 중간 과정 없이 바로 답을 내려고 하다 보니 정확도가 떨어졌다는 점입니다. "2024년 서울 날씨"를 물으면 "죄송하지만 2021년 이후 데이터는 모릅니다"라고 답하거나, 심지어 잘못된 정보를 그럴듯하게 지어내기도 했습니다.

바로 이런 문제를 해결하기 위해 ReAct 패턴이 등장했습니다. ReAct를 사용하면 LLM이 중간 사고 과정을 명시적으로 표현하게 됩니다.

"이 질문을 해결하려면 날씨 API를 호출해야겠다"라고 생각하고, 실제로 API를 호출하고, 그 결과를 보고 다시 생각합니다. 이런 순환 과정을 통해 훨씬 정확하고 신뢰할 수 있는 답변을 만들어냅니다.

위의 프롬프트 구조를 살펴보겠습니다. 가장 먼저 Question 부분에 사용자의 질문이 들어갑니다.

그다음 Thought 부분에서 LLM은 "이 질문을 해결하려면 어떤 정보가 필요한가?"를 분석합니다. 이것이 바로 Reasoning 단계입니다.

다음 Action 부분에서는 실제로 도구를 호출하거나 검색을 수행합니다. 이것이 Acting 단계입니다.

그리고 Observation에서 그 결과를 확인합니다. 중요한 점은 이 과정이 한 번으로 끝나지 않는다는 것입니다.

첫 번째 관찰 결과가 충분하지 않으면 다시 Thought로 돌아가 "추가로 어떤 정보가 필요한가?"를 생각하고, 또 다른 Action을 취합니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 여행 추천 서비스를 개발한다고 가정해봅시다. 사용자가 "다음 주말 제주도 여행 추천해줘"라고 물으면, ReAct 에이전트는 먼저 "날씨 정보가 필요하겠다"고 생각하고 날씨 API를 호출합니다.

그 결과를 보고 "비가 온다면 실내 활동을 추천해야겠다"고 판단하고, 이번에는 관광지 데이터베이스를 검색합니다. 구글, 마이크로소프트 같은 대기업들이 이미 자사 AI 어시스턴트에 ReAct 패턴을 적극 활용하고 있습니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 Thought 단계를 너무 간단하게 처리하는 것입니다.

"정보가 필요하다" 정도로만 표현하면 LLM이 왜 그 행동이 필요한지 제대로 이해하지 못합니다. 구체적으로 "서울의 2024년 평균 기온 데이터는 학습 데이터에 없으므로, 기상청 API를 호출해야 한다"처럼 명확하게 표현해야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 "아, 그래서 단순히 답만 요청하면 안 되는 거군요!"라고 이해했습니다.

ReAct 패턴을 제대로 활용하면 LLM이 단순한 질의응답 봇을 넘어 실제로 문제를 해결하는 에이전트로 진화합니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - Thought 단계는 구체적으로 작성할수록 LLM의 추론 품질이 높아집니다

  • Action은 실제로 실행 가능한 도구나 API와 연결되어야 합니다
  • 무한 루프를 방지하기 위해 최대 반복 횟수를 설정하세요

2. Thought-Action-Observation 루프

김개발 씨가 ReAct 패턴을 적용해보니 한 가지 문제가 생겼습니다. LLM이 한 번 생각하고 한 번 행동한 뒤 바로 답변을 내버렸습니다.

"여러 단계를 거쳐야 하는 복잡한 질문은 어떻게 처리하죠?" 박시니어 씨가 웃으며 답했습니다. "그게 바로 루프의 핵심이에요."

Thought-Action-Observation 루프는 ReAct 패턴의 핵심 메커니즘입니다. 한 번의 사고-행동-관찰로 끝나는 것이 아니라, 목표를 달성할 때까지 이 과정을 반복합니다.

각 관찰 결과가 다음 사고의 입력이 되며, 점진적으로 문제 해결에 접근합니다.

다음 코드를 살펴봅시다.

def react_loop(question, max_iterations=5):
    """ReAct 루프 구현"""
    context = f"Question: {question}\n\n"

    for i in range(max_iterations):
        # Thought: LLM이 다음 행동 계획
        thought_prompt = context + "Thought:"
        thought = llm.generate(thought_prompt)
        context += f"Thought: {thought}\n"

        # Action: 도구 호출 결정
        action_prompt = context + "Action:"
        action = llm.generate(action_prompt)
        context += f"Action: {action}\n"

        # Observation: 도구 실행 결과
        observation = execute_tool(action)
        context += f"Observation: {observation}\n\n"

        # 종료 조건 체크
        if "Answer:" in thought:
            break

    return context

김개발 씨는 첫 번째 ReAct 구현을 완료했지만, 테스트 중 이상한 점을 발견했습니다. "서울에서 부산까지 기차로 가는데 걸리는 시간과 비용을 알려줘"라는 복잡한 질문에 제대로 답하지 못했습니다.

AI는 "기차 시간표를 검색해야겠다"고 생각하고 한 번 검색한 뒤 바로 답을 내버렸습니다. 비용 정보는 완전히 누락되었습니다.

박시니어 씨가 코드를 보더니 고개를 끄덕였습니다. "아, 루프를 제대로 구현하지 않았네요.

ReAct는 한 번에 끝나는 게 아니라 계속 반복해야 해요." Thought-Action-Observation 루프란 무엇일까요? 이것을 이해하려면 먼저 사람이 복잡한 문제를 어떻게 해결하는지 생각해봐야 합니다.

예를 들어 "저녁 메뉴를 정한다"는 단순해 보이지만 실제로는 여러 단계를 거칩니다. 먼저 "뭐 먹을까?" 생각하고(Thought), 냉장고를 열어봅니다(Action).

재료가 부족하다는 걸 확인하고(Observation), "그럼 배달을 시킬까?" 다시 생각합니다(Thought). 배달 앱을 열어보고(Action), 예산을 초과한다는 걸 알게 됩니다(Observation).

"그럼 간단히 라면을 끓여 먹자"라고 최종 결정합니다. 이처럼 하나의 목표를 달성하기 위해 여러 번의 생각-행동-관찰을 반복하는 것이 바로 루프의 본질입니다.

기존 방식은 왜 부족했을까요? 단일 Thought-Action만 사용하면 LLM은 첫 번째 생각만으로 문제를 해결하려고 합니다.

"기차 시간을 알려줘"라는 질문에 시간표만 검색하고 끝냅니다. 비용, 좌석 종류, 환승 정보 같은 추가 정보는 고려하지 않습니다.

더 심각한 문제는 첫 번째 시도가 실패했을 때입니다. API 호출이 에러를 반환하면 그대로 멈춰버립니다.

"죄송합니다, 정보를 찾을 수 없습니다"라고 포기해버리는 것이죠. 루프 구조가 이 문제를 어떻게 해결할까요?

루프를 사용하면 LLM은 각 단계의 결과를 보고 다음 행동을 조정합니다. 첫 번째 검색에서 시간 정보를 얻었다면, 두 번째 Thought에서 "이제 비용 정보가 필요하다"고 판단합니다.

그리고 가격 API를 호출하는 새로운 Action을 취합니다. 만약 중간에 에러가 발생해도 포기하지 않습니다.

"API 에러가 발생했다"는 Observation을 받으면, "그럼 다른 API를 시도해봐야겠다"고 생각하고 대안을 찾습니다. 위의 코드를 단계별로 분석해보겠습니다.

먼저 max_iterations=5로 최대 반복 횟수를 설정합니다. 무한 루프를 방지하기 위한 안전장치입니다.

그다음 context 변수에 모든 이전 단계의 기록을 누적합니다. 이것이 핵심입니다.

각 루프에서 LLM은 지금까지의 모든 context를 보고 다음 Thought를 생성합니다. 첫 번째 루프에서는 원래 질문만 보지만, 두 번째 루프에서는 첫 번째 Thought, Action, Observation까지 모두 참고합니다.

execute_tool(action) 함수는 LLM이 결정한 행동을 실제로 수행합니다. API 호출, 데이터베이스 쿼리, 계산 등 다양한 도구를 실행할 수 있습니다.

종료 조건도 중요합니다. LLM이 "Answer:"라는 키워드를 출력하면 충분한 정보를 얻었다고 판단하고 루프를 종료합니다.

실무에서는 어떻게 활용될까요? 금융 서비스 챗봇을 예로 들어봅시다.

사용자가 "내 계좌에서 100만 원을 친구에게 송금하고, 영수증을 이메일로 받고 싶어요"라고 요청합니다. 첫 번째 루프: "계좌 잔액을 확인해야겠다" → 잔액 조회 API 호출 → "잔액 충분함" 확인 두 번째 루프: "송금을 실행해야겠다" → 송금 API 호출 → "송금 완료, 거래 ID 12345" 확인 세 번째 루프: "영수증을 생성해야겠다" → 영수증 생성 API 호출 → "PDF 생성됨" 확인 네 번째 루프: "이메일로 전송해야겠다" → 메일 API 호출 → "전송 완료" 확인 이렇게 4단계의 루프를 거쳐 복잡한 요청을 완벽하게 처리합니다.

주의해야 할 함정도 있습니다. 초보자들이 자주 하는 실수는 context를 누적하지 않는 것입니다.

매 루프마다 새로운 프롬프트를 만들면 LLM은 이전 단계를 기억하지 못합니다. 같은 질문을 반복하거나 모순된 행동을 취할 수 있습니다.

또 다른 실수는 종료 조건을 명확히 하지 않는 것입니다. "최종 답변을 얻었는지" 판단하는 로직이 없으면 max_iterations까지 무의미하게 반복하거나, 답을 얻고도 계속 돌아가는 문제가 생깁니다.

김개발 씨는 루프를 제대로 구현한 뒤 다시 테스트했습니다. 이번에는 복잡한 질문도 단계별로 완벽하게 처리했습니다.

Thought-Action-Observation 루프는 ReAct 패턴의 심장입니다. 이것을 이해하면 진짜 똑똑한 AI 에이전트를 만들 수 있습니다.

실전 팁

💡 - context를 반드시 누적해서 LLM에게 전달하세요

  • 종료 조건을 명확히 정의하고, max_iterations로 안전망을 만드세요
  • 각 Observation 결과를 다음 Thought에서 참고할 수 있도록 구조화하세요

3. 도구 사용과 결합

루프를 구현한 김개발 씨는 이제 실제 도구와 연결하고 싶었습니다. "날씨 API, 검색 API, 계산기...

이런 걸 어떻게 ReAct와 연결하죠?" 박시니어 씨가 자신의 코드를 보여주며 말했습니다. "도구를 정의하고, LLM이 선택하게 하면 됩니다."

**도구 사용(Tool Use)**은 ReAct 패턴을 실용적으로 만드는 핵심 요소입니다. LLM이 외부 API, 데이터베이스, 계산 함수 등을 호출할 수 있게 하여 실시간 정보와 복잡한 연산을 처리할 수 있게 합니다.

도구는 이름, 설명, 파라미터로 정의되며, LLM은 상황에 맞는 도구를 자동으로 선택합니다.

다음 코드를 살펴봅시다.

# 도구 정의
tools = {
    "search": {
        "description": "웹에서 최신 정보를 검색합니다",
        "parameters": ["query"],
        "function": lambda query: web_search(query)
    },
    "calculator": {
        "description": "수학 계산을 수행합니다",
        "parameters": ["expression"],
        "function": lambda expr: eval(expr)
    },
    "weather": {
        "description": "특정 도시의 날씨를 조회합니다",
        "parameters": ["city"],
        "function": lambda city: get_weather(city)
    }
}

def execute_tool(action_text):
    """LLM의 Action에서 도구를 파싱하고 실행"""
    # "search[파이썬 최신 버전]" 형식 파싱
    tool_name = action_text.split('[')[0]
    params = action_text.split('[')[1].rstrip(']')

    return tools[tool_name]["function"](params)

김개발 씨는 완벽한 ReAct 루프를 만들었지만, 실제로 실행할 수 있는 도구가 없었습니다. Action 단계에서 "날씨를 검색한다"라고 텍스트만 생성할 뿐, 실제 API는 호출되지 않았습니다.

"이거... 그냥 상상만 하는 AI잖아요?" 김개발 씨는 답답했습니다.

박시니어 씨가 웃으며 말했습니다. "맞아요.

이제 진짜 도구를 연결해야죠. Tool Use가 바로 그 역할을 합니다." **도구 사용(Tool Use)**이란 무엇일까요?

쉽게 비유하자면, 도구는 LLM에게 주어진 연장통과 같습니다. 사람에게 망치, 드라이버, 톱을 주면 목공 작업을 할 수 있듯이, LLM에게 검색 API, 계산기, 데이터베이스 쿼리 함수를 주면 다양한 작업을 수행할 수 있습니다.

핵심은 LLM이 스스로 어떤 도구를 사용할지 결정한다는 점입니다. 개발자가 "이 상황에서는 이 API를 써라"라고 하드코딩하지 않습니다.

대신 도구 목록과 설명을 제공하면, LLM이 상황에 맞는 도구를 선택합니다. 도구 없이는 어떤 문제가 있었을까요?

LLM은 아무리 똑똑해도 학습 데이터에 없는 정보는 모릅니다. "지금 비트코인 가격은?"이라는 질문에 "2021년 기준으로..."라고 답하거나 아예 모른다고 할 수밖에 없었습니다.

또한 복잡한 계산도 할 수 없었습니다. "123456 곱하기 789를 계산해줘"라고 하면 근사값을 추측하거나 틀린 답을 내놓았습니다.

언어 모델은 계산기가 아니기 때문입니다. 도구 시스템은 이 한계를 완전히 극복합니다.

실시간 정보가 필요하면 검색 API를 호출하고, 정확한 계산이 필요하면 계산기 함수를 실행합니다. 사용자 데이터가 필요하면 데이터베이스를 쿼리합니다.

LLM의 언어 이해 능력과 외부 도구의 실행 능력이 결합되면 강력한 시너지가 발생합니다. 위의 코드를 자세히 살펴보겠습니다.

먼저 tools 딕셔너리에 사용 가능한 모든 도구를 정의합니다. 각 도구는 세 가지 정보를 포함합니다.

description은 LLM이 도구를 선택할 때 참고하는 설명서입니다. "웹에서 최신 정보를 검색합니다"라는 설명을 보고 LLM은 "아, 실시간 정보가 필요할 때 이 도구를 쓰면 되겠구나"라고 판단합니다.

parameters는 도구가 필요로 하는 입력값을 정의합니다. 검색 도구는 query가 필요하고, 날씨 도구는 city가 필요합니다.

LLM은 이 정보를 보고 올바른 형식으로 도구를 호출합니다. function은 실제로 실행될 파이썬 함수입니다.

lambda를 사용해 간단히 정의할 수도 있고, 복잡한 로직은 별도 함수로 만들 수도 있습니다. execute_tool 함수는 LLM이 생성한 Action 텍스트를 파싱합니다.

"search[파이썬 최신 버전]" 같은 형식에서 도구 이름과 파라미터를 추출하고, 해당 도구를 실행합니다. 실무에서는 훨씬 다양한 도구가 활용됩니다.

전자상거래 플랫폼을 예로 들어봅시다. 고객이 "30만 원 이하 노트북 추천해줘, 내 주소로 무료 배송 가능한 걸로"라고 요청합니다.

첫 번째 Thought: "가격대 조건에 맞는 제품을 검색해야겠다" Action: product_search[노트북, max_price=300000] Observation: 15개 제품 목록 반환 두 번째 Thought: "사용자 주소를 확인해야겠다" Action: get_user_address[user_id=12345] Observation: 서울시 강남구 반환 세 번째 Thought: "각 제품의 배송비를 계산해야겠다" Action: calculate_shipping[product_ids=[...], address=강남구] Observation: 5개 제품이 무료 배송 가능 네 번째 Thought: "최종 추천 목록을 생성한다" Answer: 조건에 맞는 5개 노트북 추천 이렇게 여러 도구를 조합해 복잡한 비즈니스 로직을 처리합니다. 흔한 실수와 주의사항이 있습니다.

초보 개발자들은 도구 설명을 대충 작성하는 경향이 있습니다. "검색"이라고만 쓰면 LLM은 언제 이 도구를 써야 할지 모릅니다.

"웹에서 최신 정보를 검색합니다. 실시간 뉴스, 날씨, 가격 등을 찾을 때 사용하세요"처럼 구체적으로 작성해야 합니다.

또 다른 실수는 에러 처리를 안 하는 것입니다. API 호출이 실패하면 프로그램이 죽어버립니다.

대신 "API 에러: 연결 실패"라는 Observation을 반환하면, LLM이 "다른 도구를 시도해야겠다"고 판단할 수 있습니다. 김개발 씨는 도구 시스템을 구현한 뒤 감탄했습니다.

"와, 이제 진짜 똑똑한 AI네요!" 실시간 날씨를 검색하고, 복잡한 계산을 수행하고, 데이터베이스에서 정보를 가져오는 모든 것이 자동으로 이루어졌습니다. 도구 사용을 마스터하면 LLM은 단순한 언어 모델을 넘어 실제로 업무를 처리하는 에이전트가 됩니다.

실전 팁

💡 - 도구 설명은 구체적으로 작성하여 LLM이 올바르게 선택하도록 하세요

  • 모든 도구에 에러 처리를 추가하여 실패 시에도 루프가 계속되도록 하세요
  • 자주 사용되는 도구일수록 설명을 더욱 명확하게 작성하세요

4. 실습: ReAct 프롬프트 템플릿

박시니어 씨가 김개발 씨에게 말했습니다. "이제 이론은 충분히 배웠으니, 실전에서 바로 쓸 수 있는 템플릿을 만들어봅시다.

프롬프트 설계가 ReAct의 성능을 결정합니다."

ReAct 프롬프트 템플릿은 LLM이 일관되게 올바른 형식으로 응답하도록 안내하는 구조화된 프롬프트입니다. 시스템 메시지에서 역할과 도구를 정의하고, Few-shot 예시로 패턴을 학습시키며, 명확한 형식 지침으로 파싱 가능한 출력을 보장합니다.

다음 코드를 살펴봅시다.

# ReAct 시스템 프롬프트 템플릿
SYSTEM_PROMPT = """당신은 문제 해결 AI 에이전트입니다.
다음 도구를 사용할 수 있습니다:
- search[query]: 웹 검색
- calculator[expression]: 계산
- weather[city]: 날씨 조회

반드시 다음 형식을 따르세요:
Thought: (무엇을 해야 하는지 분석)
Action: (도구_이름[파라미터])
Observation: (도구 실행 결과, 자동으로 채워짐)
... (필요시 반복)
Answer: (최종 답변)

예시:
Question: 서울 날씨가 좋으면 추천 활동 알려줘
Thought: 먼저 서울 날씨를 확인해야 한다
Action: weather[서울]
Observation: 맑음, 25도
Thought: 날씨가 좋으니 야외 활동을 추천한다
Answer: 날씨가 화창하니 한강 공원 산책이나 자전거 타기를 추천합니다.
"""

# Few-shot 프롬프트 구성
def build_react_prompt(question, context=""):
    return f"""{SYSTEM_PROMPT}
{context}
Question: {question}
Thought:"""

김개발 씨는 지금까지 배운 내용을 종합해서 실제 시스템을 만들기 시작했습니다. 하지만 문제가 생겼습니다.

LLM이 때때로 형식을 무시하고 자유롭게 답변했습니다. "Thought:"라고 써야 하는데 "생각:"이라고 한글로 쓰거나, Action 없이 바로 답변을 내놓기도 했습니다.

박시니어 씨가 모니터를 보며 말했습니다. "프롬프트 설계가 부족해요.

LLM에게 명확한 가이드를 줘야 합니다." 프롬프트 템플릿이 왜 중요할까요? LLM은 매우 유연한 도구입니다.

같은 질문이라도 프롬프트를 어떻게 작성하느냐에 따라 완전히 다른 답변을 내놓습니다. 마치 요리사에게 레시피를 주는 것과 같습니다.

"맛있게 해주세요"라고 하면 매번 다른 요리가 나오지만, "소금 5g, 후추 2g, 180도에서 20분"이라고 정확히 지시하면 일관된 결과를 얻을 수 있습니다. ReAct에서 프롬프트는 더욱 중요합니다.

형식이 틀어지면 파싱이 안 되고, 파싱이 안 되면 도구를 실행할 수 없습니다. 전체 시스템이 무너지는 것이죠.

좋은 프롬프트 템플릿의 필수 요소는 무엇일까요? 첫째, 역할 정의입니다.

"당신은 문제 해결 AI 에이전트입니다"라고 명시하면 LLM은 단순 질의응답이 아니라 능동적인 문제 해결 모드로 전환됩니다. 둘째, 도구 목록입니다.

사용 가능한 모든 도구를 설명과 함께 나열합니다. 이것이 LLM의 "능력 범위"를 정의합니다.

셋째, 형식 지침입니다. "반드시 다음 형식을 따르세요"라고 강조하고, Thought, Action, Observation, Answer의 구조를 명확히 보여줍니다.

넷째, Few-shot 예시입니다. 실제 예시를 보여주면 LLM은 패턴을 빠르게 학습합니다.

"이렇게 하면 된다"는 것을 직접 보여주는 것이죠. 위의 코드를 분석해보겠습니다.

SYSTEM_PROMPT는 모든 대화에서 공통으로 사용되는 기본 지침입니다. 역할, 도구, 형식, 예시를 모두 포함합니다.

이것을 한 번 정의해두면 매번 반복할 필요가 없습니다. 도구 목록 부분을 보면 각 도구가 한 줄로 간결하게 설명됩니다.

search[query]: 웹 검색처럼 이름, 파라미터, 용도를 명확히 합니다. 형식 지침에서는 각 단계가 무엇을 의미하는지 괄호 안에 설명합니다.

Thought: (무엇을 해야 하는지 분석)처럼 LLM에게 힌트를 제공합니다. 예시 부분이 특히 중요합니다.

실제 질문-사고-행동-관찰-답변의 전체 흐름을 보여줍니다. LLM은 이 패턴을 모방하려고 합니다.

build_react_prompt 함수는 새로운 질문이 들어올 때마다 완전한 프롬프트를 구성합니다. context 파라미터로 이전 루프의 기록을 전달하여 연속성을 유지합니다.

실무에서 프롬프트를 어떻게 개선할까요? 처음에는 간단한 템플릿으로 시작합니다.

실제 사용하면서 LLM이 자주 실수하는 부분을 발견하면, 그 부분을 프롬프트에 추가합니다. 예를 들어 LLM이 존재하지 않는 도구를 호출하는 실수를 자주 하면, "주의: 목록에 없는 도구는 사용할 수 없습니다"라는 경고를 추가합니다.

LLM이 너무 일찍 답변을 내놓는다면, "충분한 정보를 모을 때까지 Thought-Action을 반복하세요"라는 지침을 추가합니다. 큰 회사들은 수천 번의 테스트를 거쳐 프롬프트를 최적화합니다.

구글 Bard, OpenAI ChatGPT 같은 제품의 프롬프트는 엔지니어들이 몇 달간 다듬은 결과물입니다. 주의할 점도 있습니다.

프롬프트가 너무 길면 오히려 성능이 떨어질 수 있습니다. LLM은 긴 지시사항을 완벽히 따르기 어려워합니다.

핵심만 간결하게 표현하는 것이 좋습니다. 또한 프롬프트는 계속 진화해야 합니다.

한 번 만들어두고 끝이 아닙니다. 새로운 도구를 추가하거나, 새로운 사용 패턴을 발견하면 프롬프트도 업데이트해야 합니다.

김개발 씨는 템플릿을 적용한 뒤 놀라운 개선을 경험했습니다. 형식 오류가 90% 이상 줄어들었고, LLM이 훨씬 일관되게 동작했습니다.

"프롬프트 설계가 이렇게 중요한 줄 몰랐어요!" 김개발 씨는 이제 프롬프트 엔지니어링의 중요성을 깨달았습니다. 좋은 프롬프트 템플릿은 ReAct 시스템의 성공을 결정합니다.

여러분도 자신의 유즈케이스에 맞는 템플릿을 만들어보세요.

실전 팁

💡 - Few-shot 예시를 2-3개 넣으면 LLM의 형식 준수율이 크게 향상됩니다

  • 도구 설명은 한 줄로 간결하게, 하지만 명확하게 작성하세요
  • 프롬프트는 버전 관리하여 어떤 변경이 성능 개선에 기여했는지 추적하세요

5. 실습: 웹 검색 + ReAct 시스템

마지막으로 박시니어 씨가 김개발 씨에게 최종 미션을 주었습니다. "이제 진짜 동작하는 시스템을 만들어봅시다.

웹 검색 도구를 통합한 ReAct 에이전트를 완성하세요."

웹 검색 ReAct 시스템은 실시간 웹 정보를 활용하는 완전한 AI 에이전트입니다. 검색 API를 도구로 등록하고, 질문을 분석하여 필요한 정보를 검색하고, 검색 결과를 종합하여 최종 답변을 생성하는 전체 파이프라인을 구현합니다.

다음 코드를 살펴봅시다.

import requests
from anthropic import Anthropic

client = Anthropic(api_key="your-api-key")

def web_search(query):
    """실제 웹 검색 API 호출"""
    response = requests.get(
        "https://api.search.com/search",
        params={"q": query, "count": 3}
    )
    results = response.json()["results"]
    return "\n".join([f"- {r['title']}: {r['snippet']}" for r in results])

def react_agent(question):
    """완전한 ReAct 에이전트"""
    messages = [{"role": "user", "content": f"{SYSTEM_PROMPT}\nQuestion: {question}\nThought:"}]

    for _ in range(5):
        # LLM에게 다음 행동 요청
        response = client.messages.create(
            model="claude-3-5-sonnet-20241022",
            max_tokens=500,
            messages=messages
        )

        text = response.content[0].text
        messages.append({"role": "assistant", "content": text})

        # 종료 조건 체크
        if "Answer:" in text:
            return text.split("Answer:")[1].strip()

        # Action 파싱 및 실행
        if "Action:" in text:
            action = text.split("Action:")[1].split("\n")[0].strip()
            if action.startswith("search["):
                query = action.split("[")[1].rstrip("]")
                observation = web_search(query)
                messages.append({"role": "user", "content": f"Observation: {observation}\nThought:"})

    return "최대 반복 횟수 초과"

# 실행
answer = react_agent("2024년 파이썬 최신 버전과 주요 기능은?")
print(answer)

김개발 씨는 드디어 최종 단계에 도달했습니다. 이론도 배웠고, 템플릿도 만들었습니다.

이제 실제로 작동하는 시스템을 구현할 차례입니다. "웹 검색을 통합하면 정말 모든 질문에 답할 수 있겠네요!" 김개발 씨는 설렜습니다.

박시니어 씨가 고개를 끄덕였습니다. "맞아요.

실시간 정보를 활용할 수 있다는 건 엄청난 능력입니다. 자, 시작해봅시다." 웹 검색 ReAct 시스템이란 무엇일까요?

이것은 지금까지 배운 모든 개념을 하나로 합친 완성형 시스템입니다. 사용자가 질문하면, AI는 스스로 판단하여 웹을 검색하고, 검색 결과를 분석하고, 필요하면 추가 검색을 하고, 최종적으로 종합된 답변을 제공합니다.

마치 인간 리서처가 일하는 방식과 똑같습니다. 질문을 받으면 구글에서 검색하고, 여러 자료를 읽고, 핵심을 추출하고, 자신의 언어로 정리합니다.

기존 챗봇과 어떻게 다를까요? 일반 챗봇은 학습된 지식만으로 답합니다.

"2024년 파이썬 최신 버전은?"이라고 물으면 "죄송하지만 2021년 이후 정보는 모릅니다"라고 답하거나, 잘못된 정보를 자신 있게 말합니다. 하지만 웹 검색 ReAct 에이전트는 다릅니다.

"최신 정보가 필요하다"고 판단하고, "파이썬 3.13 2024"로 검색하고, 공식 문서를 찾아 정확한 정보를 제공합니다. 더 나아가 복합적인 질문도 처리합니다.

"파이썬 최신 버전과 주요 기능, 그리고 이전 버전과의 차이점까지 알려줘"라고 하면, 여러 번의 검색을 통해 모든 정보를 수집합니다. 위의 코드를 단계별로 분석해보겠습니다.

먼저 web_search 함수가 실제 검색 API를 호출합니다. 여기서는 가상의 API 엔드포인트를 사용했지만, 실제로는 Google Search API, Bing Search API, 또는 SerpAPI 같은 서비스를 사용합니다.

검색 결과는 여러 개의 제목과 스니펫(요약)으로 반환됩니다. 이것을 텍스트로 포맷팅하여 LLM이 읽을 수 있게 합니다.

react_agent 함수가 핵심입니다. messages 배열로 대화 기록을 관리합니다.

첫 번째 메시지는 시스템 프롬프트와 질문을 포함합니다. 루프 안에서 Claude API를 호출하여 다음 Thought와 Action을 생성합니다.

response.content[0].text에서 LLM의 응답을 추출합니다. 종료 조건 체크가 중요합니다.

LLM이 "Answer:"를 출력하면 충분한 정보를 얻었다고 판단하고, Answer 이후의 텍스트를 최종 답변으로 반환합니다. Action 파싱 부분을 보면, "search[쿼리]" 형식을 파싱하여 실제 검색을 실행합니다.

그 결과를 Observation으로 추가하고, 다음 Thought를 유도합니다. 실제 실행 흐름을 추적해봅시다.

사용자: "2024년 파이썬 최신 버전과 주요 기능은?" 첫 번째 루프: - Thought: "파이썬 최신 버전은 실시간 정보이므로 검색이 필요하다" - Action: search[파이썬 3.13 2024 릴리즈] 두 번째 루프: - Observation: "Python 3.13.0 릴리즈됨, JIT 컴파일러 실험적 지원..." - Thought: "버전 정보는 얻었으니 주요 기능을 더 검색해야겠다" - Action: search[파이썬 3.13 주요 기능] 세 번째 루프: - Observation: "향상된 에러 메시지, 성능 개선, 타입 힌트 업데이트..." - Thought: "충분한 정보를 모았으니 답변을 작성한다" - Answer: "2024년 파이썬 최신 버전은 3.13.0입니다. 주요 기능으로는..." 이렇게 3번의 루프를 거쳐 완벽한 답변을 생성합니다.

실무에서 확장하는 방법은 무엇일까요? 검색 외에도 다양한 도구를 추가할 수 있습니다.

계산기, 날씨 API, 주식 가격 API, 데이터베이스 쿼리 등을 통합하면 진정한 범용 에이전트가 됩니다. 도구 선택 로직도 개선할 수 있습니다.

현재는 "search["로 시작하는지만 체크하지만, 정규표현식이나 JSON 파싱을 사용하면 더 복잡한 도구 호출도 지원할 수 있습니다. 에러 처리도 강화해야 합니다.

API 호출 실패, 타임아웃, 잘못된 형식 등 다양한 예외 상황을 처리하면 프로덕션 환경에서도 안정적으로 동작합니다. 주의해야 할 보안 이슈도 있습니다.

사용자 입력을 그대로 검색 쿼리로 사용하면 인젝션 공격에 취약할 수 있습니다. 입력 검증과 샌드박싱을 추가해야 합니다.

또한 API 비용 관리도 중요합니다. 무한 루프나 과도한 검색으로 API 요금 폭탄을 맞을 수 있습니다.

max_iterations 제한과 사용량 모니터링이 필수입니다. 김개발 씨는 시스템을 완성하고 테스트했습니다.

"서울 날씨와 미세먼지 상태, 그리고 야외 활동 추천해줘"라는 복잡한 질문에도 완벽하게 답했습니다. "와, 제가 만든 AI가 정말 똑똑하게 일하네요!" 김개발 씨는 감격했습니다.

박시니어 씨가 어깨를 두드렸습니다. "축하해요.

이제 진짜 AI 에이전트 개발자가 되었어요." 웹 검색 ReAct 시스템은 현대 AI 에이전트의 핵심 패턴입니다. 이것을 마스터하면 여러분도 ChatGPT, Perplexity 같은 서비스를 직접 만들 수 있습니다.

실전 팁

💡 - 검색 API는 비용이 발생하므로 캐싱을 적극 활용하세요

  • 검색 결과가 너무 길면 요약하여 토큰 사용량을 줄이세요
  • 프로덕션에서는 반드시 에러 처리와 재시도 로직을 추가하세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#LLM#ReAct#Agent#Prompting#ToolUse#LLM,ReAct,에이전트

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.