#
다중 에이전트 계층 구조에서 여러 에이전트가 협업하도록 하기

작성자: Aymeric Roucher, 번역: 김하림

> 이 튜토리얼은 고급 수준이므로, 시작하기 전에 이 에이전트 쿡북에서 다루는 기본 개념을 알고가는 것이 좋습니다!

우리는 이 노트북에서 다중 에이전트 웹 브라우저를 만들 것입니다. 여러 에이전트가 웹을 사용하여 문제를 해결하기 위해 협업하는 에이전트 시스템입니다!

이 시스템은 간단한 계층 구조로 되어있습니다. 여기서 `ManagedAgent` 객체를 사용하여 웹 검색 에이전트를 관리합니다. 이 구조를 통해 웹 검색 에이전트의 기능을 효과적으로 제어하고 조정할 수 있습니다.

                  +-----------------+
                  |   관리자 에이전트   |
                  +-----------------+
                           |
            _______________|______________
           |                              |
      코드 인터프리터        +--------------------------------+
          도구            |         관리되는 에이전트           |
                         |      +------------------+      |
                         |      |   웹 검색 에이전트   |      |
                         |      +------------------+      |
                         |         |            |         |
                         |    웹 검색 도구         |         |
                         |               웹페이지 방문 도구    |
                         +--------------------------------+

환경 구성부터 시작해보겠습니다.

다음 명령어를 실행하여 필요한 패키지를 설치하세요.

    !pip install markdownify duckduckgo-search "transformers[agents]" --upgrade -q

Hugging Face Hub API를 사용하기 위해서는 로그인이 필요합니다.

아래 코드를 실행하여 로그인해주세요.

    from huggingface_hub import notebook_login

    notebook_login()

⚡️ 우리의 에이전트는 Qwen/Qwen2.5-72B-Instruct 모델을 사용합니다. 이 모델은 `HfApiEngine` 클래스를 통해 Hugging Face의 Inference API로 호출됩니다. Inference API를 활용하면 다양한 오픈 소스 모델을 빠르고 쉽게 실행할 수 있습니다.

Note: Inference API는 다양한 기준에 따라 모델을 호스팅하며, 배포된 모델은 사전 통지 없이 업데이트되거나 교체될 수 있으므로 여기에서 자세한 내용을 확인해보세요.

역주: 이 노트북을 실행하려면 Hugging Face에서 발급받은 HF_TOKEN을 Google Colab의 보안 변수(Secrets)로 등록해야 합니다. 또한, 여기서 사용되는 모델들은 Hugging Face의 Pro 구독이 필요합니다. 무료 계정으로는 접근이 제한될 수 있으니 주의해 주세요.

    model = "Qwen/Qwen2.5-72B-Instruct"

###
🔍 웹 검색 도구 만들기

웹 브라우징을 위해 우리는 기존에 있는 `DuckDuckGoSearchTool` 도구를 사용할 것입니다. 이 도구는 Google 검색과 비슷한 기능을 제공합니다.

그러나 `DuckDuckGoSearchTool`이 찾은 웹페이지의 내용을 자세히 볼 수 있는 기능도 필요합니다. 이를 위해 라이브러리에 있는 `VisitWebpageTool`을 사용할 수도 있지만, 우리는 이 도구를 직접 만들어보며 그 과정을 이해해 보려고 합니다.

그래서 `markdownify`를 사용하여 `VisitWebpageTool` 도구를 처음부터 만들어 보도록 하겠습니다.

    import re
    import requests
    from markdownify import markdownify as md
    from requests.exceptions import RequestException
    from transformers.agents import tool
    from urllib.parse import unquote

    @tool
    def visit_webpage(url: str) -> str:
        """주어진 URL 웹페이지를 방문하고 그 내용을 마크다운 문자열로 반환합니다.

        Args:
            url: 방문할 웹페이지의 URL

        Returns:
            마크다운으로 변환된 웹페이지 내용, 또는 요청 실패 시 오류 메시지
        """
        try:
            # URL로 GET 요청 보내기
            response = requests.get(url)
            response.raise_for_status()  # 잘못된 상태 코드에 대한 예외 처리

            # HTML 내용을 마크다운으로 변환하기
            markdown_content = md(response.text).strip()

            # 중복된 개행 문자 제거하기
            markdown_content = re.sub(r"\n{3,}", "\n\n", markdown_content)

            # URL 디코딩하기
            markdown_content = re.sub(r"\((.*?)\)", lambda m: "(" + unquote(m.group(1)) + ")", markdown_content)

            return markdown_content

        except RequestException as e:
            return f"웹페이지 가져오기 오류: {str(e)}"
        except Exception as e:
            return f"예상치 못한 오류 발생: {str(e)}"

이제, 우리의 도구를 테스트해봅시다!

역주: 한글이 포함된 URL의 디코딩을 처리하기 위해 코드를 추가하였습니다.

    >>> print(visit_webpage("https://ko.wikipedia.org/wiki/Hugging_Face")[:500])

    허깅 페이스 \- 위키백과, 우리 모두의 백과사전

    [본문으로 이동](#bodyContent)

    주 메뉴

    주 메뉴
    사이드바로 이동
    숨기기

     둘러보기

    * [대문](/wiki/위키백과:대문 "대문으로 가기 [z]")
    * [최근 바뀜](/wiki/특수:최근바뀜 "위키의 최근 바뀐 목록 [r]")
    * [요즘 화제](/wiki/포털:요즘_화제 "최근의 소식 알아 보기")
    * [임의의 문서로](/wiki/특수:임의문서 "무작위로 선택된 문서 불러오기 [x]")

     사용자 모임

    * [사랑방](/wiki/위키백과:사랑방)
    * [사용자 모임](/wiki/위키백과:사용자_모임 "위키백과 참여자를 위한 토론/대화 공간입니다.")
    * [관리 요청](/wiki/위키백과:요청)

     편집 안내

    * [소개](/wiki/도움말:소개)
    * [도움말](/wiki/위키백과:도움말 "도움말")
    * [정책과 지침](/wiki/위키백과:정책과_지침)
    * [질문방](/wiki/위키백과:질문방)

    [![](

##
다중 에이전트 시스템 구축하기 🤖🤝🤖

이제 `search`와 `visit_webpage` 도구를 모두 갖추었으니, 이를 이용해 웹 에이전트를 만들 수 있습니다.

이 에이전트를 어떻게 구성하는 것이 좋을까요?

- 웹 브라우징은 병렬 도구 호출이 필요 없는 단일 타임라인 작업이므로, JSON 도구 호출이 적합합니다. 따라서 `ReactJsonAgent`를 선택합니다.
- 또한, 웹 검색은 때때로 정확한 답변을 찾기 위해 여러 페이지를 탐색해야 할 수 있으므로, `max_iterations`를 10으로 늘리는 것이 좋습니다.

    from transformers.agents import (
        ReactCodeAgent,
        ReactJsonAgent,
        HfApiEngine,
        ManagedAgent,
    )
    from transformers.agents.search import DuckDuckGoSearchTool

    llm_engine = HfApiEngine(model)

    web_agent = ReactJsonAgent(
        tools=[DuckDuckGoSearchTool(), visit_webpage],
        llm_engine=llm_engine,
        max_iterations=10,
    )

그런 다음 이 웹 검색 에이전트를 `ManagedAgent`로 감싸줍니다. 이렇게 하면 상위의 관리자 에이전트가 이 에이전트를 쉽게 제어할 수 있습니다.

    managed_web_agent = ManagedAgent(
        agent=web_agent,
        name="search",
        description="Runs web searches for you. Give it your query as an argument.",
    )

이제 관리자 에이전트를 생성합니다. 이때 `managed_agents` 매개변수에 우리가 만든 관리 받는 에이전트를 전달합니다.

관리자 에이전트는 전체적인 계획 수립과 복잡한 사고를 담당해야 하므로, 고급 추론 능력이 필요합니다. 이런 이유로 `ReactCodeAgent`를 사용하는 것이 가장 적합할 것 같습니다.

마지막으로, 현재 연도와 관련된 질문을 처리하기 위해 `additional_authorized_imports=["time", "datetime"]`을 설정에 추가합니다. 이렇게 하면 에이전트가 시간과 관련 정보를 다룰 수 있게 됩니다.

    manager_agent = ReactCodeAgent(
        tools=[],
        llm_engine=llm_engine,
        managed_agents=[managed_web_agent],
        additional_authorized_imports=["time", "datetime"],
    )

이것으로 모든 준비가 끝났습니다! 이제 우리의 시스템을 실행해봅시다. 계산이 필요한 질문을 던져보겠습니다.

    manager_agent.run("Stripe는 몇 년 전에 설립되었나요?")

이렇게 에이전트들이 협력하여 과제를 해결했습니다! ✅

💡 이 시스템은 쉽게 더 많은 에이전트로 확장될 수 있습니다. 예를 들어, 한 에이전트는 코드 실행을 담당하고, 다른 에이전트는 웹 검색을, 또 다른 에이전트는 파일 로딩을 처리하는 식으로 말이죠.

🤔💭 더 나아가 복잡한 트리 구조의 계층을 생각해볼 수도 있습니다. 최고 경영자(CEO) 에이전트가 여러 중간 관리자를 관리하고, 각 중간 관리자는 다시 여러 부하 직원을 관리하는 구조를 만들 수 있죠.

심지어 더 많은 중간 관리 계층을 추가하고, 매일 여러 번의 회의를 하고, 스크럼 마스터와 함께 애자일 방식을 도입할 수도 있습니다. 그리고 각 새로운 구성 요소는 충분한 마찰을 추가하여 결국 아무 일도 완수되지 않도록 할 수 있겠죠… 음, 잠깐만요. 그건 아니겠네요. 우리의 단순한 구조를 유지하는 것이 좋겠습니다.

< > Update on GitHub