🤖

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

⚠️

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

이미지 로딩 중...

ReAct 패턴 마스터 완벽 가이드 - 슬라이드 1/6
A

AI Generated

2025. 12. 26. · 2 Views

ReAct 패턴 마스터 완벽 가이드

LLM이 생각하고 행동하는 ReAct 패턴을 처음부터 끝까지 배웁니다. Thought-Action-Observation 루프로 똑똑한 에이전트를 만들고, 실전 예제로 웹 검색과 계산을 결합한 강력한 AI 시스템을 구축합니다.


목차

  1. Reasoning + Acting의 결합
  2. Thought-Action-Observation 루프
  3. 오류 처리와 재시도
  4. 실습: ReAct 에이전트 구축
  5. 실습: 웹 검색 + 계산기 에이전트

1. Reasoning + Acting의 결합

어느 날 김개발 씨가 회사에서 AI 챗봇 프로젝트를 맡았습니다. 간단한 질문에는 잘 답하던 챗봇이 "오늘 날씨에 맞춰서 옷차림을 추천해줘"라는 요청에는 엉뚱한 답을 내놓았습니다.

선배 박시니어 씨가 코드를 살펴보더니 말했습니다. "LLM이 생각만 하고 행동은 안 하네요.

ReAct 패턴을 적용해봅시다."

ReAct 패턴은 Reasoning(추론)과 Acting(행동)을 결합한 LLM 에이전트 설계 방법입니다. 단순히 텍스트를 생성하는 것이 넘어서, 문제를 분석하고 필요한 도구를 사용하며 결과를 반영하는 사이클을 반복합니다.

이를 통해 LLM이 복잡한 작업을 단계적으로 해결할 수 있게 됩니다.

다음 코드를 살펴봅시다.

# ReAct 패턴의 기본 구조
class ReActAgent:
    def __init__(self, llm, tools):
        self.llm = llm  # 언어 모델
        self.tools = tools  # 사용 가능한 도구들

    def run(self, question):
        # Thought: 무엇을 해야 할지 생각
        thought = self.llm.generate(f"Question: {question}\nThought:")

        # Action: 필요한 도구 선택 및 실행
        action = self.select_tool(thought)
        observation = self.tools[action].execute()

        # 관찰 결과를 바탕으로 최종 답변 생성
        answer = self.llm.generate(f"Observation: {observation}\nAnswer:")
        return answer

김개발 씨는 궁금했습니다. 왜 똑똑한 LLM이 간단한 날씨 조회조차 제대로 하지 못하는 걸까요?

박시니어 씨가 화이트보드에 그림을 그리며 설명을 시작했습니다. "LLM은 기본적으로 텍스트 생성 엔진이에요.

학습 데이터에 있는 패턴을 바탕으로 답을 만들어내죠. 하지만 실시간 정보나 계산은 할 수 없어요." 그렇다면 어떻게 해야 할까요?

쉽게 비유하자면, ReAct 패턴은 마치 사람이 문제를 푸는 과정과 같습니다. 어려운 수학 문제를 만났을 때 우리는 먼저 "이 문제는 어떻게 풀어야 하지?"라고 생각합니다.

그 다음 계산기를 꺼내 계산하고, 결과를 보고 다음 단계를 결정합니다. ReAct 패턴도 정확히 이런 방식으로 동작합니다.

ReAct라는 이름은 **Reasoning(추론)**과 **Acting(행동)**의 합성어입니다. 전통적인 LLM은 어땠을까요?

사용자가 질문을 던지면 LLM은 단번에 답을 생성합니다. 중간 과정이 없습니다.

학습 데이터에 비슷한 내용이 있으면 그럴듯한 답을 내놓지만, 실시간 데이터가 필요하거나 복잡한 계산이 필요하면 할루시네이션이 발생합니다. "2024년 1월 15일 서울 날씨"를 물어보면, 학습 데이터에 없는 내용이니 그냥 추측해서 답하는 것이죠.

바로 이런 문제를 해결하기 위해 ReAct 패턴이 등장했습니다. ReAct를 사용하면 LLM이 중간 사고 과정을 드러냅니다.

"이 질문에 답하려면 먼저 날씨 API를 호출해야겠다"라고 생각합니다. 그 다음 실제로 API를 호출하는 행동을 취합니다.

API 결과를 관찰하고, 그 정보를 바탕으로 다음 행동을 결정합니다. 이 사이클을 반복하면서 최종 답에 도달합니다.

위의 코드를 한 줄씩 살펴보겠습니다. 먼저 __init__ 메서드에서 LLM과 사용 가능한 도구들을 저장합니다.

도구는 날씨 API, 계산기, 웹 검색 등 LLM이 직접 할 수 없는 작업을 대신 수행하는 함수들입니다. run 메서드가 핵심인데, 질문을 받으면 먼저 LLM에게 "어떻게 해야 할지 생각해봐"라고 요청합니다.

그러면 LLM이 사고 과정을 텍스트로 출력합니다. 이를 분석해서 어떤 도구를 사용할지 결정하고, 실제로 도구를 실행합니다.

마지막으로 도구의 실행 결과를 다시 LLM에게 전달해서 최종 답변을 생성합니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 여행 추천 서비스를 개발한다고 가정해봅시다. 사용자가 "다음 주말에 부산 여행 가려는데 날씨 좋을까?

좋으면 맛집 추천해줘"라고 물어봅니다. 기존 LLM은 학습 데이터에서 적당한 맛집을 추천하겠지만, 실시간 날씨는 확인할 수 없습니다.

하지만 ReAct 패턴을 적용하면 먼저 날씨 API를 호출하고, 날씨가 좋다는 것을 확인한 후, 맛집 데이터베이스를 검색해서 정확한 추천을 할 수 있습니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 모든 질문에 도구를 사용하려는 것입니다. 간단히 상식으로 답할 수 있는 질문에도 불필요하게 API를 호출하면 비용과 시간이 낭비됩니다.

LLM이 스스로 판단해서 필요할 때만 도구를 사용하도록 프롬프트를 잘 설계해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "아, LLM에게 생각하는 과정을 거치게 하고, 필요한 도구를 직접 사용하게 만드는 거군요!" ReAct 패턴을 제대로 이해하면 단순한 챗봇을 넘어서 실제로 일을 처리하는 AI 에이전트를 만들 수 있습니다.

여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - LLM이 생각 과정을 명확히 출력하도록 프롬프트를 설계하세요

  • 도구는 간단하고 명확한 인터페이스로 제공해야 LLM이 올바르게 사용합니다
  • 무한 루프를 방지하기 위해 최대 반복 횟수를 설정하세요

2. Thought-Action-Observation 루프

ReAct 패턴을 공부하던 김개발 씨는 코드를 보다가 문득 궁금해졌습니다. "생각하고, 행동하고, 관찰한다는 건 알겠는데, 이게 정확히 어떻게 반복되는 거지?" 박시니어 씨가 옆에서 말했습니다.

"바로 그게 ReAct의 핵심이에요. 한 번의 사이클로 끝나는 게 아니라 목표를 달성할 때까지 계속 반복되는 거죠."

Thought-Action-Observation(TAO) 루프는 ReAct 패턴의 실행 사이클입니다. LLM이 현재 상황을 분석하고(Thought), 필요한 행동을 선택하고(Action), 결과를 관찰한 후(Observation) 다시 생각하는 과정을 반복합니다.

이 루프는 최종 답을 찾거나 최대 반복 횟수에 도달할 때까지 계속됩니다.

다음 코드를 살펴봅시다.

def react_loop(question, max_steps=5):
    context = f"Question: {question}\n"

    for step in range(max_steps):
        # Thought: 현재 상황 분석 및 계획
        thought = llm.generate(context + "Thought:")
        context += f"Thought: {thought}\n"

        # 답을 찾았으면 종료
        if "Final Answer:" in thought:
            return extract_answer(thought)

        # Action: 도구 선택 및 실행
        action = parse_action(thought)
        observation = execute_tool(action)
        context += f"Action: {action}\nObservation: {observation}\n"

    return "최대 시도 횟수를 초과했습니다"

김개발 씨는 화면에 표시된 로그를 보며 신기해했습니다. LLM이 한 번에 답을 내는 게 아니라, 여러 단계를 거쳐 점진적으로 문제를 풀어가고 있었습니다.

박시니어 씨가 로그를 가리키며 설명했습니다. "보세요.

첫 번째 Thought에서 '날씨를 확인해야겠다'고 생각하죠. 그 다음 Action으로 날씨 API를 호출합니다.

Observation에서 '맑음, 25도'라는 결과를 받으면, 다시 Thought로 돌아가서 '날씨가 좋으니 야외 활동을 추천하자'고 생각하는 거예요." 이것이 바로 TAO 루프입니다. 쉽게 비유하자면, TAO 루프는 마치 탐정이 사건을 해결하는 과정과 같습니다.

탐정은 단서를 보고 "이 사람이 범인일 수도 있다"고 생각합니다(Thought). 그 다음 그 사람의 알리바이를 조사합니다(Action).

조사 결과 알리바이가 확실하다는 것을 알게 됩니다(Observation). 그러면 다시 "그럼 다른 용의자를 찾아봐야겠다"고 생각하며 다음 조사를 계획합니다.

이 과정을 범인을 찾을 때까지 반복하는 것이죠. TAO 루프가 없던 시절에는 어땠을까요?

초기 AI 에이전트들은 미리 정해진 순서대로만 작업을 수행했습니다. A를 하고, B를 하고, C를 하는 식이었죠.

중간에 예상치 못한 결과가 나와도 계획을 수정할 수 없었습니다. 더 큰 문제는 유연성이 없다는 것이었습니다.

조금만 다른 질문이 들어와도 전혀 대응하지 못했습니다. 바로 이런 문제를 해결하기 위해 TAO 루프가 등장했습니다.

TAO 루프를 사용하면 동적으로 계획을 수정할 수 있습니다. 첫 번째 행동의 결과를 보고 "아, 이 방법은 안 되네.

다른 방법을 써야겠다"라고 판단할 수 있습니다. 또한 복잡한 문제를 작은 단계로 분할할 수 있습니다.

한 번에 모든 것을 해결하려 하지 않고, 한 걸음씩 나아가며 최종 목표에 도달합니다. 무엇보다 중간 과정이 투명하다는 큰 이점이 있습니다.

위의 코드를 한 줄씩 살펴보겠습니다. 먼저 context 변수에 질문을 저장합니다.

이 변수는 루프가 진행되면서 계속 확장됩니다. 매 단계마다 Thought와 Action, Observation이 추가되면서 LLM이 참고할 수 있는 대화 히스토리가 만들어지는 것이죠.

for 루프로 최대 5번까지 시도합니다. 무한 루프를 방지하기 위한 안전장치입니다.

각 단계에서 LLM에게 현재 상황을 주고 다음에 무엇을 해야 할지 물어봅니다. 만약 LLM이 "Final Answer:"라는 키워드와 함께 최종 답을 제시하면 루프를 종료하고 답을 반환합니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 고객 지원 챗봇을 개발한다고 가정해봅시다.

고객이 "주문 취소하고 환불 받고 싶어요"라고 요청합니다. TAO 루프를 사용하면 먼저 주문 번호를 확인하고(Action), 주문이 배송 전 상태인지 확인합니다(Observation).

배송 전이면 취소 API를 호출하고(Action), 취소 성공 여부를 확인합니다(Observation). 그 다음 환불 절차를 안내하는 식으로 단계적으로 처리할 수 있습니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 너무 많은 반복을 허용하는 것입니다.

최대 반복 횟수를 너무 크게 설정하면 LLM이 무의미한 시도를 계속하며 비용이 폭증할 수 있습니다. 일반적으로 3-7회 정도가 적당합니다.

따라서 명확한 종료 조건을 설정하고, 각 단계의 결과를 검증하는 로직을 추가해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "아, 그래서 로그를 보면 LLM이 시행착오를 거치면서 답을 찾아가는 것처럼 보이는 거군요!" TAO 루프를 제대로 이해하면 LLM이 단순히 패턴을 외운 답을 내놓는 게 아니라, 실제로 추론하며 문제를 해결하는 에이전트를 만들 수 있습니다.

여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - 최대 반복 횟수는 작업 복잡도에 따라 3-7회로 설정하세요

  • 각 단계의 Observation을 명확히 포맷팅해서 LLM이 쉽게 이해하도록 하세요
  • "Final Answer:" 같은 명확한 종료 키워드를 프롬프트에 포함시키세요

3. 오류 처리와 재시도

김개발 씨가 ReAct 에이전트를 처음 실행했을 때, 갑자기 프로그램이 멈춰버렸습니다. 로그를 보니 날씨 API가 타임아웃 오류를 반환했는데, 에이전트는 이를 처리하지 못하고 그냥 중단된 것이었습니다.

박시니어 씨가 말했습니다. "실전에서는 오류가 항상 발생해요.

에이전트가 오류를 똑똑하게 처리하도록 만들어야 합니다."

오류 처리와 재시도는 ReAct 에이전트의 안정성을 위한 핵심 메커니즘입니다. 도구 실행이 실패했을 때 LLM이 오류 메시지를 이해하고, 다른 방법을 시도하거나 파라미터를 수정해서 재시도하도록 만듭니다.

이를 통해 일시적인 오류에도 작업을 계속 진행할 수 있습니다.

다음 코드를 살펴봅시다.

def robust_react_loop(question, max_steps=5, max_retries=3):
    context = f"Question: {question}\n"

    for step in range(max_steps):
        thought = llm.generate(context + "Thought:")
        context += f"Thought: {thought}\n"

        if "Final Answer:" in thought:
            return extract_answer(thought)

        action = parse_action(thought)

        # 재시도 로직
        for retry in range(max_retries):
            try:
                observation = execute_tool(action)
                break  # 성공하면 재시도 중단
            except Exception as e:
                observation = f"Error: {str(e)}"
                if retry == max_retries - 1:  # 마지막 재시도
                    observation += " (모든 재시도 실패)"

        context += f"Action: {action}\nObservation: {observation}\n"

    return "최대 시도 횟수 초과"

김개발 씨는 좌절했습니다. 열심히 만든 에이전트가 사소한 네트워크 오류 하나 때문에 작동을 멈춰버리다니요.

박시니어 씨가 모니터를 가리키며 말했습니다. "실전 시스템에서 오류는 예외가 아니라 일상이에요.

API 서버가 잠깐 바쁠 수도 있고, 네트워크가 순간적으로 끊길 수도 있죠. 중요한 건 이런 오류를 어떻게 처리하느냐입니다." 그렇다면 어떻게 해야 할까요?

쉽게 비유하자면, 오류 처리와 재시도는 마치 전화를 걸다가 통화중일 때 다시 거는 것과 같습니다. 한 번 통화중이라고 해서 포기하지 않습니다.

잠시 기다렸다가 다시 시도하죠. 그래도 안 되면 다른 번호로 걸어보거나, 문자를 보내는 등 대안을 찾습니다.

ReAct 에이전트도 마찬가지로 실패했을 때 포기하지 않고 다른 방법을 시도하도록 설계할 수 있습니다. 오류 처리가 없던 시절에는 어땠을까요?

초기 에이전트들은 작업 중 오류가 발생하면 그냥 멈춰버렸습니다. 사용자는 "뭔가 잘못됐습니다"라는 메시지만 보고 답답해했죠.

더 큰 문제는 디버깅이 어렵다는 것이었습니다. 어디서 무엇이 잘못됐는지 파악하기 힘들었고, 같은 오류가 반복적으로 발생했습니다.

바로 이런 문제를 해결하기 위해 똑똑한 오류 처리가 등장했습니다. 오류 처리를 제대로 구현하면 일시적 오류를 극복할 수 있습니다.

네트워크가 잠깐 끊겼다가 복구되는 경우, 자동으로 재시도해서 성공할 수 있습니다. 또한 LLM이 오류를 학습할 수 있습니다.

"이 API는 파라미터 형식이 잘못됐다"는 오류 메시지를 보고, 다음 시도에서는 올바른 형식으로 다시 호출합니다. 무엇보다 사용자 경험이 크게 개선됩니다.

위의 코드를 한 줄씩 살펴보겠습니다. 기본 구조는 이전 TAO 루프와 비슷하지만, execute_tool 호출 부분에 try-except 블록이 추가됐습니다.

도구 실행이 실패하면 예외를 잡아서 오류 메시지를 observation에 저장합니다. 중요한 건 이 오류 메시지가 다음 Thought로 전달된다는 점입니다.

LLM이 "아, 이 방법은 안 되는구나. 다른 방법을 써야겠다"고 판단할 수 있게 되는 거죠.

max_retries로 같은 작업을 최대 3번까지 재시도합니다. 마지막 재시도까지 실패하면 "모든 재시도 실패"라는 메시지를 추가해서 LLM에게 명확히 알려줍니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 여행 예약 에이전트를 개발한다고 가정해봅시다.

사용자가 "제주도 호텔 예약해줘"라고 요청합니다. 에이전트가 첫 번째 호텔 API를 호출했는데 "만실"이라는 응답을 받습니다.

오류 처리가 잘 구현되어 있다면, LLM은 이를 보고 "그럼 다른 호텔을 찾아야겠다"고 판단하고 두 번째 API를 호출합니다. 이런 식으로 여러 대안을 시도하며 최적의 결과를 찾아갑니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 모든 오류를 똑같이 처리하는 것입니다.

네트워크 타임아웃 같은 일시적 오류는 재시도할 만하지만, "유효하지 않은 API 키" 같은 오류는 몇 번을 재시도해도 성공할 수 없습니다. 따라서 오류 유형을 구분하고, 재시도할 가치가 있는 오류인지 판단하는 로직이 필요합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.

"아, LLM에게 오류 메시지를 보여주면 스스로 대안을 찾는 거군요!" 오류 처리와 재시도를 제대로 이해하면 프로토타입이 아닌 실전에서 사용할 수 있는 견고한 에이전트를 만들 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - 재시도할 가치가 있는 오류와 없는 오류를 구분하세요

  • 재시도 사이에 짧은 지연(0.5-2초)을 두어 일시적 오류가 해소될 시간을 주세요
  • 오류 메시지를 LLM이 이해하기 쉬운 형태로 포맷팅하세요

4. 실습: ReAct 에이전트 구축

이론 공부를 마친 김개발 씨는 이제 직접 에이전트를 만들어보고 싶어졌습니다. 박시니어 씨가 말했습니다.

"좋아요. 간단한 수학 문제를 푸는 에이전트부터 만들어봅시다.

LLM은 복잡한 계산을 잘 못하니까, 계산기 도구를 주고 스스로 사용하게 만드는 거죠."

실전 ReAct 에이전트 구축은 이론을 코드로 구현하는 과정입니다. LLM에게 사용 가능한 도구를 설명하고, 프롬프트를 통해 ReAct 패턴을 따르도록 유도하며, TAO 루프를 실행하는 완전한 시스템을 만듭니다.

계산기 도구를 예시로 단계별 구현 방법을 익힙니다.

다음 코드를 살펴봅시다.

import openai

# 계산기 도구 정의
def calculator(expression):
    """수식을 계산합니다. 예: '2 + 2' -> 4"""
    try:
        return str(eval(expression))
    except Exception as e:
        return f"계산 오류: {str(e)}"

# ReAct 프롬프트
REACT_PROMPT = """다음 형식으로 문제를 해결하세요:

Thought: 무엇을 해야 할지 생각
Action: calculator[수식] 또는 Final Answer[답변]
Observation: 도구 실행 결과

사용 가능한 도구:
- calculator[수식]: 수학 계산 수행

Question: {question}
"""

def react_agent(question, max_steps=5):
    context = REACT_PROMPT.format(question=question)

    for step in range(max_steps):
        # LLM에게 다음 행동 요청
        response = openai.ChatCompletion.create(
            model="gpt-4",
            messages=[{"role": "user", "content": context}]
        )
        text = response.choices[0].message.content

        # Thought 추출
        if "Thought:" in text:
            thought = text.split("Action:")[0].split("Thought:")[1].strip()
            context += f"\nThought: {thought}"

        # Action 파싱
        if "calculator[" in text:
            expr = text.split("calculator[")[1].split("]")[0]
            result = calculator(expr)
            context += f"\nAction: calculator[{expr}]\nObservation: {result}\n"
        elif "Final Answer[" in text:
            answer = text.split("Final Answer[")[1].split("]")[0]
            return answer

    return "최대 단계 초과"

# 실행 예시
print(react_agent("234 곱하기 567은?"))

김개발 씨는 에디터를 열고 코딩을 시작했습니다. 박시니어 씨가 옆에서 하나씩 설명해줍니다.

"먼저 가장 간단한 도구인 계산기부터 만들어봅시다. Python의 eval 함수를 사용하면 문자열 수식을 계산할 수 있어요.

물론 실전에서는 보안 이슈가 있으니 더 안전한 파서를 써야 하지만, 학습용으로는 충분합니다." 도구를 만드는 것은 생각보다 간단했습니다. 쉽게 비유하자면, 도구 정의는 마치 LLM에게 연장통을 주는 것과 같습니다.

연장통에 망치, 드라이버, 렌치가 들어있고, 각 도구에 사용법이 적혀있습니다. LLM은 필요할 때 적절한 도구를 꺼내서 사용하는 거죠.

계산기 함수도 마찬가지입니다. "수식을 문자열로 넘겨주면 결과를 문자열로 돌려준다"는 명확한 인터페이스를 제공합니다.

다음으로 중요한 것은 프롬프트 설계입니다. 박시니어 씨가 설명을 이어갑니다.

"LLM은 똑똑하지만 우리가 원하는 형식을 정확히 알려줘야 해요. Thought, Action, Observation이라는 키워드를 사용하고, Action에서는 calculator[수식] 형태로 도구를 호출하라고 명시해야 합니다." 프롬프트는 ReAct 에이전트의 뼈대입니다.

여기서 LLM의 행동 패턴이 결정됩니다. "사용 가능한 도구" 섹션에서 각 도구의 이름과 사용법을 설명하고, 예시를 보여주면 LLM이 패턴을 학습해서 따라합니다.

위의 코드를 한 줄씩 살펴보겠습니다. calculator 함수는 문자열 수식을 받아서 eval로 계산한 후 결과를 문자열로 반환합니다.

오류가 발생하면 오류 메시지를 반환해서 LLM이 무엇이 잘못됐는지 알 수 있게 합니다. REACT_PROMPT는 LLM에게 주는 지시사항입니다.

형식을 명확히 정의하고, 사용 가능한 도구를 나열합니다. react_agent 함수가 메인 루프인데, 매 단계마다 OpenAI API를 호출해서 LLM의 응답을 받고, 응답을 파싱해서 어떤 Action인지 판단합니다.

calculator[...] 패턴이 발견되면 계산기를 실행하고, Final Answer[...] 패턴이 발견되면 답을 추출해서 반환합니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 쇼핑몰의 가격 비교 에이전트를 만든다고 가정해봅시다. 사용자가 "A 제품과 B 제품 중 어느 게 더 저렴해?"라고 물어봅니다.

에이전트는 먼저 A 제품의 가격을 조회하는 API를 호출하고(Action), 결과를 받습니다(Observation). 그 다음 B 제품 가격을 조회하고, 두 가격을 비교해서 답변합니다.

계산기 대신 "상품 검색 API", "가격 조회 API" 같은 도구를 제공하면 되는 것이죠. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 프롬프트가 너무 모호한 것입니다. "적절한 도구를 사용하세요"라고만 하면 LLM이 혼란스러워합니다.

도구 호출 형식을 예시와 함께 명확히 보여줘야 합니다. 또한 응답 파싱이 취약하면 LLM이 조금만 다른 형식으로 답해도 에이전트가 망가집니다.

정규표현식이나 더 견고한 파서를 사용하는 것이 좋습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

코드를 실행해본 김개발 씨는 신기해하며 말했습니다. "와, LLM이 정말로 계산기를 호출하네요!" ReAct 에이전트를 직접 구축해보면 이론으로만 배울 때와는 완전히 다른 이해를 얻을 수 있습니다.

여러분도 오늘 배운 내용을 실제로 코딩해 보세요.

실전 팁

💡 - 프롬프트에 구체적인 예시를 1-2개 포함시키면 LLM이 형식을 더 잘 따릅니다

  • 도구 함수는 항상 문자열을 반환하도록 통일하세요
  • LLM 응답 파싱은 정규표현식보다 구조화된 출력(JSON)을 권장합니다

5. 실습: 웹 검색 + 계산기 에이전트

계산기 에이전트를 완성한 김개발 씨는 더 복잡한 도전을 해보고 싶어졌습니다. 박시니어 씨가 새로운 과제를 제시했습니다.

"이번에는 웹 검색과 계산기를 모두 사용하는 에이전트를 만들어봅시다. '2024년 서울 인구의 10%는 몇 명이지?'같은 질문에 답하려면 두 도구를 조합해야 해요."

멀티 도구 ReAct 에이전트는 여러 도구를 조합해서 복잡한 작업을 수행합니다. 웹 검색으로 실시간 정보를 가져오고, 계산기로 수치를 처리하며, LLM이 전체 과정을 조율합니다.

이를 통해 단일 도구로는 불가능한 복합적인 질문에 답할 수 있습니다.

다음 코드를 살펴봅시다.

import requests
from bs4 import BeautifulSoup

def web_search(query):
    """웹에서 정보를 검색합니다"""
    try:
        # 간단한 DuckDuckGo 검색 (실제로는 API 사용 권장)
        url = f"https://duckduckgo.com/html/?q={query}"
        response = requests.get(url)
        soup = BeautifulSoup(response.text, 'html.parser')
        # 첫 번째 검색 결과의 스니펫 추출
        snippet = soup.find('a', class_='result__snippet')
        return snippet.text if snippet else "검색 결과 없음"
    except Exception as e:
        return f"검색 오류: {str(e)}"

def calculator(expression):
    """수학 계산을 수행합니다"""
    try:
        return str(eval(expression))
    except Exception as e:
        return f"계산 오류: {str(e)}"

TOOLS = {
    "search": web_search,
    "calc": calculator
}

MULTI_TOOL_PROMPT = """다음 도구들을 사용해서 문제를 해결하세요:

Thought: 무엇을 해야 할지 생각
Action: [도구명][입력값]
Observation: 결과

사용 가능한 도구:
- search[검색어]: 웹 검색
- calc[수식]: 계산

최종 답변은 Final Answer[답변] 형식으로 제시하세요.

Question: {question}
"""

def multi_tool_agent(question, max_steps=7):
    context = MULTI_TOOL_PROMPT.format(question=question)

    for step in range(max_steps):
        response = openai.ChatCompletion.create(
            model="gpt-4",
            messages=[{"role": "user", "content": context}]
        )
        text = response.choices[0].message.content
        print(f"\n--- Step {step+1} ---\n{text}")

        # Action 파싱 및 실행
        for tool_name, tool_func in TOOLS.items():
            pattern = f"{tool_name}["
            if pattern in text:
                input_val = text.split(pattern)[1].split("]")[0]
                result = tool_func(input_val)
                context += f"\n{text}\nObservation: {result}\n"
                break
        else:
            # Final Answer 체크
            if "Final Answer[" in text:
                return text.split("Final Answer[")[1].split("]")[0]

    return "최대 단계 초과"

# 실행 예시
answer = multi_tool_agent("2024년 서울 인구의 10%는?")
print(f"\n최종 답변: {answer}")

김개발 씨는 흥미진진해졌습니다. 한 가지 도구만 사용할 때와는 차원이 다른 도전이었습니다.

박시니어 씨가 화이트보드에 프로세스를 그리며 설명했습니다. "생각해보세요.

'서울 인구의 10%'를 계산하려면 먼저 서울 인구가 몇 명인지 알아야 해요. 이건 LLM의 학습 데이터에는 최신 정보가 없을 겁니다.

그래서 웹 검색이 필요하죠. 검색 결과에서 인구수를 찾았으면, 그 다음 계산기로 10%를 계산하면 됩니다." 이것이 바로 도구 조합의 힘입니다.

쉽게 비유하자면, 멀티 도구 에이전트는 마치 요리사가 여러 조리 도구를 사용하는 것과 같습니다. 칼로 재료를 자르고, 믹서로 갈고, 냄비로 끓입니다.

각 도구는 하나의 작업만 잘하지만, 이들을 조합하면 복잡한 요리를 완성할 수 있습니다. ReAct 에이전트도 웹 검색, 계산기, 데이터베이스 조회 등 여러 도구를 필요에 따라 선택하고 조합해서 사용합니다.

단일 도구만 있던 시절에는 어땠을까요? 예를 들어 계산기만 있는 에이전트는 "234 곱하기 567"은 계산할 수 있지만, "삼성전자 주가의 10%는?"이라는 질문에는 답할 수 없습니다.

주가 정보를 얻을 방법이 없으니까요. 반대로 웹 검색만 있으면 정보를 찾을 수는 있지만 복잡한 계산은 못합니다.

더 큰 문제는 유연성 부족이었습니다. 바로 이런 문제를 해결하기 위해 멀티 도구 시스템이 등장했습니다.

멀티 도구를 사용하면 복합적인 질문을 분해할 수 있습니다. 큰 문제를 "정보 수집" 단계와 "계산" 단계로 나누고, 각 단계에 적합한 도구를 사용합니다.

또한 실시간 정보와 계산을 결합할 수 있습니다. 날씨, 주가, 환율 같은 실시간 데이터를 가져와서 바로 계산에 활용합니다.

무엇보다 확장성이 뛰어납니다. 위의 코드를 한 줄씩 살펴보겠습니다.

web_search 함수는 DuckDuckGo에 쿼리를 보내고 첫 번째 검색 결과를 가져옵니다. 실제 프로덕션에서는 Google Search API나 Bing API 같은 공식 API를 사용해야 합니다.

TOOLS 딕셔너리로 도구들을 관리합니다. 새 도구를 추가하려면 이 딕셔너리에 함수를 등록하기만 하면 됩니다.

multi_tool_agent 함수는 이전과 비슷하지만, 이번에는 여러 도구를 체크합니다. for 루프로 모든 도구를 순회하며 응답에 해당 도구 호출 패턴이 있는지 확인하고, 발견되면 실행합니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 금융 어드바이저 챗봇을 개발한다고 가정해봅시다.

사용자가 "애플 주식 100주를 사면 원화로 얼마야?"라고 물어봅니다. 에이전트는 먼저 주가 API로 애플 주가를 조회하고(도구 1), 환율 API로 달러-원 환율을 조회하고(도구 2), 계산기로 "주가 × 100주 × 환율"을 계산합니다(도구 3).

이런 식으로 여러 데이터 소스와 계산을 조합해서 정확한 답을 제공할 수 있습니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 너무 많은 도구를 제공하는 것입니다. 도구가 10개, 20개로 늘어나면 LLM이 어떤 도구를 선택해야 할지 혼란스러워합니다.

일반적으로 5-7개 정도가 적당합니다. 따라서 비슷한 기능의 도구는 하나로 통합하고, 각 도구의 역할을 명확히 구분해야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 에이전트가 실행되는 것을 지켜보던 김개발 씨는 감탄했습니다.

"와, 먼저 검색하고, 그 결과를 가지고 계산하네요. 정말 사람처럼 추론하는 것 같아요!" 멀티 도구 ReAct 에이전트를 제대로 이해하면 단순한 질의응답을 넘어서 실제로 복잡한 업무를 처리하는 AI 어시스턴트를 만들 수 있습니다.

여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - 도구는 5-7개 정도로 제한하고, 각 도구의 목적을 명확히 하세요

  • 도구 실행 순서가 중요한 경우 프롬프트에 예시를 포함시키세요
  • 각 도구의 실행 시간을 모니터링해서 타임아웃을 적절히 설정하세요

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

#LLM#ReAct#Agent#Reasoning#Action#LLM,ReAct,에이전트

댓글 (0)

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