위키피디아 웹 스크래핑: LLM 에이전트와 도구를 활용한 효율적인 정보 추출

위키피디아는 방대한 양의 구조화되고 비구조화된 데이터를 보유하고 있어, 가치 있는 정보를 추출하는 데 매우 유용한 플랫폼입니다. 전통적인 셀레니움(Selenium)과 같은 도구는 효과적이지만, 수동적이고 시간이 많이 소요되는 경향이 있습니다.

최근 대형 언어 모델(Large Language Models, LLMs)의 놀라운 능력과 인터넷 연결 기능은 웹 스크래핑을 포함한 많은 분야에서 새로운 가능성을 열어주고 있습니다. LLM은 방대한 양의 텍스트 데이터를 학습하여 인간과 유사한 언어 이해 및 생성 능력을 갖추고 있어, 웹 페이지의 내용을 이해하고 원하는 정보를 추출하는 데 큰 도움이 됩니다.

이번 글에서는 LLM 에이전트, 도구, 함수 호출의 강력한 조합을 활용하여 위키피디아에서 손쉽게 데이터를 추출하는 방법을 소개하고자 합니다.

LLM 에이전트와 도구란?

LLM 에이전트는 대형 언어 모델을 기반으로 특정 태스크를 수행하도록 설계된 소프트웨어 에이전트를 말합니다. 이들은 사용자의 요청을 이해하고, 필요한 정보를 수집 및 분석한 후, 적절한 응답을 생성할 수 있습니다.

도구(Tool)는 에이전트가 태스크를 수행하는 데 사용하는 개별 기능 또는 액션을 의미합니다. 예를 들어, 웹 페이지를 가져오는 도구, 텍스트에서 특정 패턴을 검색하는 도구 등이 있습니다.

함수 호출은 에이전트가 도구를 실행하고 결과를 받아오는 과정을 말합니다. 이를 통해 에이전트는 동적으로 필요한 도구를 선택하고 조합하여 태스크를 완수할 수 있게 됩니다.

위키피디아 웹 스크래핑 프로세스

LLM 에이전트를 활용한 위키피디아 웹 스크래핑은 다음과 같은 과정으로 이루어집니다:

  1. 사용자가 에이전트에게 위키피디아에서 추출하고자 하는 정보를 요청합니다.
  2. 에이전트는 요청을 분석하여 필요한 도구와 액션을 식별합니다.
  3. 에이전트는 위키피디아 페이지를 가져오는 도구를 호출하여 관련 페이지의 HTML을 받아옵니다.
  4. 에이전트는 HTML 내용을 분석하여 원하는 정보를 추출하는 도구들을 순차적으로 호출합니다.
  5. 추출된 정보를 사용자에게 원하는 형식으로 반환합니다.

이 과정에서 에이전트는 LLM의 강력한 언어 이해 능력을 활용하여 비정형 텍스트에서도 원하는 정보를 효과적으로 찾아낼 수 있습니다.

활용 사례

LLM 에이전트를 활용한 위키피디아 웹 스크래핑은 다양한 분야에서 활용될 수 있습니다. 몇 가지 예시를 들면:

  • 연구자가 특정 주제에 대한 정보를 수집하고 요약할 때
  • 기업이 경쟁사나 산업 동향을 모니터링하고자 할 때
  • 학생이 리포트나 프로젝트를 위한 자료를 수집할 때
  • 콘텐츠 제작자가 블로그나 영상의 주제와 관련된 배경 지식을 빠르게 습득하고자 할 때

(1) 컨텍스트와 데이터

LLM 웹 스크래핑 접근법의 실제 적용을 시연하기 위해, 다양한 노래에 대한 상세한 메타데이터(예: 장르, 레이블, 언어, 프로듀서, 작곡가)를 위키피디아에서 검색할 것입니다. 특히 노래 제목과 해당 아티스트를 포함하는 데이터셋을 기반으로 2010년대 상위 200개 노래의 메타데이터를 추출할 예정입니다.

2010년대 상위 200개 노래 데이터셋 스크린샷

노래 데이터는 2000년 이후 음악 차트 상위 앨범과 노래의 통합 목록을 제공하는 사이트인 Chart2000.com에서 얻었습니다. 데이터는 공정 사용이 가능하며, CSV 파일은 여기에 위치해 있습니다.

(2) 툴킷

사용할 도구와 프레임워크에 대한 개요는 다음과 같습니다.

(i) 파이썬용 위키피디아 API
파이썬용 위키피디아 API는 개발자가 위키피디아의 콘텐츠에 쉽게 접근하고 관리할 수 있도록 해주는 사용자 친화적인 라이브러리입니다. 이 API는 문서 요약 및 메타데이터의 검색과 검색, 그리고 관련 문서에 대한 접근을 용이하게 합니다.

>>> import wikipedia
>>> print wikipedia.summary("Wikipedia")
# Wikipedia (/ˌwɪkɨˈpiːdiə/ or /ˌwɪkiˈpiːdiə/ WIK-i-PEE-dee-ə) is a collaboratively edited, multilingual, free Internet encyclopedia supported by the non-profit Wikimedia Foundation...

>>> wikipedia.search("Barack")
# [u'Barak (given name)', u'Barack Obama', u'Barack (brandy)', u'Presidency of Barack Obama', u'Family of Barack Obama', u'First inauguration of Barack Obama', u'Barack Obama presidential campaign, 2008', u'Barack Obama, Sr.', u'Barack Obama citizenship conspiracy theories', u'Presidential transition of Barack Obama']

>>> ny = wikipedia.page("New York")
>>> ny.title
# u'New York'
>>> ny.url
# u'http://en.wikipedia.org/wiki/New_York'
>>> ny.content
# u'New York is a state in the Northeastern region of the United States. New York is the 27th-most exten'...
>>> ny.links[0]
# u'1790 United States Census'

>>> wikipedia.set_lang("fr")
>>> wikipedia.summary("Facebook", sentences=1)
# Facebook est un service de réseautage social en ligne sur Internet permettant d'y publier des informations (photographies, liens, textes, etc.) en contrôlant leur visibilité par différentes catégories de personnes.

(ii) OpenAI의 GPT 3.5 LLM과 함수 호출

우리가 사용할 LLM은 OpenAI에서 발표한 GPT 3.5 Turbo로, API에서는 `gpt-3.5-turbo-1106`으로 알려져 있습니다.

2023년 11월에 출시된 `gpt-3.5-turbo-1106`은 기본적으로 16K의 컨텍스트 윈도우를 지원합니다. 뿐만 아니라 향상된 명령어 수행, JSON 모드, 그리고 병렬 함수 호출을 지원하는데, 이는 우리가 OpenAI의 함수 호출 기능을 사용할 예정이기 때문에 중요한 특징입니다.

함수 호출은 사용자 정의 함수를 입력으로 설정하고, OpenAI의 LLM이 지능적으로 함수의 인자를 포함하는 구조화된 JSON 객체를 출력하는 기능입니다. 이를 통해 LLM은 주어진 함수를 이해하고 필요한 입력값을 추론하여 함수 호출에 필요한 JSON 형식의 데이터를 생성할 수 있게 됩니다.

OpenAI 함수 호출

거대 언어 모델을 외부 도구와 연결하는 방법에 대해 알아봅시다.

소개
API 호출에서, 함수를 설명하고 모델이 지능적으로 하나 또는 여러 함수를 호출할 인자를 포함하는 JSON 객체를 출력하도록 선택할 수 있습니다. Chat Completions API는 함수를 직접 호출하지 않습니다. 대신 모델은 코드에서 함수를 호출하는 데 사용할 수 있는 JSON을 생성합니다.

최신 모델(gpt-4o, gpt-4-turbo 및 gpt-3.5-turbo)은 함수가 호출되어야 하는 시점을 감지하고(입력에 따라), 이전 모델보다 함수 서명을 더 잘 준수하는 JSON으로 응답하도록 훈련되었습니다. 이러한 기능과 함께 잠재적 위험도 있습니다. 사용자를 대신하여 세계에 영향을 미치는 행동(이메일 보내기, 온라인에 게시물 올리기, 구매하기 등)을 취하기 전에 사용자 확인 흐름을 구축하는 것을 강력히 권장합니다.

이 가이드는 Chat Completions API에서의 함수 호출에 중점을 두고 있습니다. Assistants API에서의 함수 호출에 대한 자세한 내용은 Assistants Tools 페이지를 참조하세요.

일반적인 사용 사례
함수 호출을 통해 보다 안정적으로 모델에서 구조화된 데이터를 다시 가져올 수 있습니다. 예를 들면 다음과 같습니다:

  • 외부 API를 호출하여 질문에 답하는 어시스턴트 만들기
    예: send_email(to: string, body: string) 또는 get_current_weather(location: string, unit: ‘celsius’ | ‘fahrenheit’)와 같은 함수 정의
  • 자연어를 API 호출로 변환
    예: “내 최고 고객은 누구인가요?”를 get_customers(min_revenue: int, created_before: string, limit: int)로 변환하고 내부 API 호출
  • 텍스트에서 구조화된 데이터 추출
    예: extract_data(name: string, birthday: string) 또는 sql_query(query: string)라는 함수 정의
  • 기타 다양한 용도!

함수 호출의 기본 단계 순서는 다음과 같습니다:

  1. functions 매개변수에 정의된 함수 집합과 사용자 쿼리를 사용하여 모델을 호출합니다.
  2. 모델은 하나 이상의 함수를 호출하도록 선택할 수 있습니다. 호출하는 경우 content는 사용자 지정 스키마를 준수하는 문자열화된 JSON 객체가 됩니다(참고: 모델은 매개변수를 환각할 수 있음).
  3. 코드에서 문자열을 JSON으로 구문 분석하고, 매개변수가 존재하는 경우 제공된 인수로 함수를 호출합니다.
  4. 함수 응답을 새 메시지로 추가하여 모델을 다시 호출하고, 모델이 결과를 사용자에게 요약하도록 합니다.

지원되는 모델
모든 모델 버전이 함수 호출 데이터로 훈련되는 것은 아닙니다. 함수 호출은 다음 모델에서 지원됩니다: gpt-4o, gpt-4o-2024-05-13, gpt-4-turbo, gpt-4-turbo-2024-04-09, gpt-4-turbo-preview, gpt-4-0125 -미리보기, gpt-4-1106-미리보기, gpt-4, gpt-4-0613, gpt-3.5-터보, gpt-3.5-터보-0125, gpt-3.5-turbo-1106 및 gpt-3.5-turbo-0613.

또한 병렬 함수 호출은 다음 모델에서 지원됩니다: gpt-4o, gpt-4o-2024-05-13, gpt-4-turbo, gpt-4-turbo-2024-04-09, gpt-4-turbo-preview, gpt-4-0125-미리보기 , gpt-4-1106-미리보기, gpt-3.5-터보-0125 및 gpt-3.5-터보-1106.

함수 호출 동작
tool_choice에 대한 기본 동작은 tool_choice: “auto”입니다. 이를 통해 모델은 함수를 호출할지 여부와 호출할 함수를 결정할 수 있습니다.

사용 사례에 따라 기본 동작을 사용자 정의하는 세 가지 방법을 제공합니다:

  1. 모델이 항상 하나 이상의 함수를 호출하도록 강제하려면 tool_choice: “required”로 설정할 수 있습니다. 그러면 모델은 호출할 함수를 선택합니다.
  2. 모델이 특정 함수 하나만 호출하도록 강제하려면 tool_choice: {“type”: “function”, “function”: {“name”: “my_function”}}으로 설정할 수 있습니다.
  3. 함수 호출을 비활성화하고 모델이 사용자 대면 메시지만 생성하도록 강제하려면 tool_choice: “none”으로 설정할 수 있습니다.

병렬 함수 호출
병렬 함수 호출은 여러 함수 호출을 함께 수행하여 이러한 함수 호출의 효과와 결과를 병렬로 해결할 수 있는 모델의 기능입니다. 이는 특히 함수 실행에 오랜 시간이 걸리는 경우 유용하며 API와의 왕복을 줄입니다. 예를 들어, 모델은 동시에 3개의 다른 위치에서 날씨를 얻기 위해 함수를 호출할 수 있으며, 이는 tool_calls 배열에 각각 id를 가진 3개의 함수 호출이 포함된 메시지가 됩니다. 이러한 함수 호출에 응답하려면 tool_calls의 id를 참조하는 tool_call_id와 함께 각 함수 호출 결과를 포함하는 3개의 새 메시지를 대화에 추가하십시오.

이 예에서는 단일 함수 get_current_weather를 정의합니다. 모델은 함수를 여러 번 호출하고, 함수 응답을 모델에 다시 보낸 후 다음 단계를 결정하도록 합니다. 샌프란시스코, 도쿄, 파리의 기온을 사용자에게 알려주는 사용자 대면 메시지로 응답했습니다. 쿼리에 따라 함수를 다시 호출하도록 선택할 수 있습니다.

from openai import OpenAI
import json

client = OpenAI()

# Example dummy function hard coded to return the same weather
# In production, this could be your backend API or an external API
def get_current_weather(location, unit="fahrenheit"):
    """Get the current weather in a given location"""
    if "tokyo" in location.lower():
        return json.dumps({"location": "Tokyo", "temperature": "10", "unit": unit})
    elif "san francisco" in location.lower():
        return json.dumps({"location": "San Francisco", "temperature": "72", "unit": unit})
    elif "paris" in location.lower():
        return json.dumps({"location": "Paris", "temperature": "22", "unit": unit})
    else:
        return json.dumps({"location": location, "temperature": "unknown"})

def run_conversation():
    # Step 1: send the conversation and available functions to the model
    messages = [{"role": "user", "content": "What's the weather like in San Francisco, Tokyo, and Paris?"}]
    tools = [
        {
            "type": "function",
            "function": {
                "name": "get_current_weather",
                "description": "Get the current weather in a given location",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "location": {
                            "type": "string",
                            "description": "The city and state, e.g. San Francisco, CA",
                        },
                        "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                    },
                    "required": ["location"],
                },
            },
        }
    ]
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        tools=tools,
        tool_choice="auto",  # auto is default, but we'll be explicit
    )
    response_message = response.choices[0].message
    tool_calls = response_message.tool_calls
    # Step 2: check if the model wanted to call a function
    if tool_calls:
        # Step 3: call the function
        # Note: the JSON response may not always be valid; be sure to handle errors
        available_functions = {
            "get_current_weather": get_current_weather,
        }  # only one function in this example, but you can have multiple
        messages.append(response_message)  # extend conversation with assistant's reply
        # Step 4: send the info for each function call and function response to the model
        for tool_call in tool_calls:
            function_name = tool_call.function.name
            function_to_call = available_functions[function_name]
            function_args = json.loads(tool_call.function.arguments)
            function_response = function_to_call(
                location=function_args.get("location"),
                unit=function_args.get("unit"),
            )
            messages.append(
                {
                    "tool_call_id": tool_call.id,
                    "role": "tool",
                    "name": function_name,
                    "content": function_response,
                }
            )  # extend conversation with function response
        second_response = client.chat.completions.create(
            model="gpt-4o",
            messages=messages,
        )  # get a new response from the model where it can see the function response
        return second_response
print(run_conversation())




함수 호출이 중요한 이유는 이것이 함수 및 외부 API 사용(예: 위키피디아 API)과 함께 작동하도록 미세 조정되었기 때문입니다. 또한 모델로부터 구조화된 데이터를 보다 안정적이고 일관되게 받아올 수 있게 해줍니다.

이러한 기능 덕분에 LLM은 사용자의 요청을 이해하고 필요한 함수를 호출하여 위키피디아에서 원하는 정보를 추출한 후, 구조화된 형태로 결과를 반환할 수 있게 됩니다. 이는 기존의 rule-based 웹 스크래핑 방식에 비해 훨씬 유연하고 강력한 정보 추출을 가능케 합니다.

(iii) LangChain
LangChain은 LLM 애플리케이션의 개발과 배포를 용이하게 하기 위해 설계된 프레임워크입니다. 광범위한 통합과 데이터 연결 기능을 제공하여 웹 스크래핑 및 문서 Q&A와 같은 사용 사례를 구축하기 위해 다양한 모듈을 연결하고 조율할 수 있습니다.

보다 구체적으로, 우리가 사용할 LangChain 구성 요소는 다음과 같습니다:

  • 에이전트(Agents): 에이전트는 (LLM과 텍스트 프롬프트에 의해 구동되는) 추론 엔진으로, 어떤 행동을 취할지와 어떤 특정 순서로 할지를 결정합니다. 여기서는 GPT 3.5 Turbo LLM을 에이전트로 사용할 것입니다.
  • 도구(Tools): 이름에서 알 수 있듯이, 도구는 에이전트가 세상과 상호 작용하고 특정 행동을 수행하기 위해 호출할 수 있는 기능입니다. 여기서는 에이전트가 호출할 Wikipedia API 도구를 사용할 것입니다.
  • 출력 파서(Output Parsers): 출력 파서는 에이전트 LLM에 의해 생성된 텍스트 응답을 구조화하여 추가 작업이나 완료(finish)를 위해 다시 에이전트로 전달하는 데 도움을 줍니다. 여기서는 OpenAI 함수 호출을 사용하여 LLM 출력을 파싱하므로 OpenAIFunctionsAgentOutputParser를 사용할 것입니다.

LangChain은 이러한 구성 요소들을 유연하게 조합하여 LLM 기반의 지능형 에이전트를 구축할 수 있는 강력한 프레임워크입니다. Wikipedia API 도구와 GPT 3.5 Turbo LLM을 연결하여 웹 스크래핑 태스크를 수행하는 것이 이번 프로젝트의 주요 목표 중 하나가 될 것입니다.

(3) 단계별 가이드

이 LLM 웹 스크래핑 애플리케이션을 설정하고 개발하기 위한 쉽고 단계적인 튜토리얼을 살펴보겠습니다.

(i) 초기 설정
Python (≥3.10) 가상 환경을 설정한 후, 다음 종속성을 pip install합니다:

langchain==0.0.352
openai>=1.6.1
openpyxl>=3.1.2
pandas>=2.1.4
python-box>=7.1.1
python-dotenv>=1.0.0
wikipedia>=1.4.0

그런 다음 루트 디렉토리의 .env 파일에 OpenAI API 키를 배치하여 환경 변수로 설정합니다. API 키는 OpenAI 플랫폼의 이곳에서 찾을 수 있습니다.

# .env
OPENAI_API_KEY=sk-1234567890ABC # 실제 API 키로 대체하세요

다음으로, 아래와 같이 구성 설정을 저장할 YAML 파일(config.yaml)을 생성합니다:

# config/config.yaml

ENVDIR: '.env'
INPUT_FILE: 'data/input/chart2000-song-2010-decade-0-3-0070.csv'
MODEL_NAME: 'gpt-3.5-turbo-1106'
SEED: 0
TEMPERATURE: 0
OUTPUT_FILE: 'data/output/songs_metadata.csv'

YAML 파일은 환경 변수, 입력 및 출력 파일 경로, 특정 OpenAI 모델, 모델 시드, 온도와 같은 LLM 설정을 정의합니다.
구성과 환경 변수가 정의되면 다음 코드 청크를 사용하여 Python 스크립트로 가져올 수 있습니다:

import box
import yaml
from dotenv import load_dotenv

with open("config/config.yaml", "r", encoding="utf8") as ymlfile:
    cfg = box.Box(yaml.safe_load(ymlfile))
load_dotenv(dotenv_path=cfg.ENVDIR, verbose=True)

(ii) LLM 인스턴스화
LangChain의 ChatOpenAI 클래스를 사용하여 사용할 LLM(GPT 3.5 Turbo)을 정의할 수 있습니다.
온도(temperature)는 LLM 응답의 무작위성을 조절하고, 시드(seed)는 출력을 더 결정론적으로 만들지만 완전한 예측 가능성과 재현성을 보장하지는 않습니다.

from langchain.chat_models import ChatOpenAI

llm = ChatOpenAI(
    model_name=config.MODEL_NAME,
    temperature=config.TEMPERATURE,
    openai_api_key=config.OPENAI_API_KEY,
)

ChatOpenAI 클래스는 OpenAI의 Chat API를 사용하여 대화형 LLM을 생성합니다. 우리는 구성 파일에 정의된 모델 이름, 온도, API 키를 전달하여 원하는 대로 LLM을 사용자 정의할 수 있습니다.

이렇게 하면 웹 스크래핑 태스크를 수행하는 데 사용할 GPT 3.5 Turbo 기반 LLM 인스턴스가 생성됩니다. LLM은 Wikipedia API로부터 검색된 정보를 이해하고 요약하는 역할을 할 것입니다.

(iii) 도구 정의
LangChain에는 우리가 도구로 활용할 수 있는 다양한 서드파티 통합(langchain-community 패키지 하위 구성 요소)이 함께 제공됩니다. 여러 도구를 정의할 수 있지만, 여기서는 Wikipedia 검색 도구 설정에 집중하겠습니다.
아래와 같이 WikipediaAPIWrapper를 LangChain의 Tool() 클래스의 func 매개변수에 전달하여 wikipedia_tool 객체를 생성합니다:

from langchain.agents.tools import Tool
from langchain_community.wikipedia import WikipediaAPIWrapper

wikipedia_tool = Tool(
    name='Wikipedia Search',
    func=WikipediaAPIWrapper().run,
    description=(
        'A wrapper around Wikipedia API. '
        'Useful for when you need to answer questions about current events. '
        'Input should be a search query.'
    )
)

LLM 에이전트가 도구 설명을 읽고 입력 쿼리에 답하기 위해 도구를 호출할지 여부를 결정하므로 도구 설명은 명확하고 구체적이어야 합니다.
도구를 정의한 후에는 목록에 배치하고 아래와 같이 LLM 객체에 바인딩(인수 전달과 동일)하여 실행합니다:

from langchain.agents import initialize_agent, Tool
from langchain.utilities.openai_functions import format_tool_to_openai_function

tools = [wikipedia_tool]
formatted_tools = format_tool_to_openai_function(tools)

llm_chain = initialize_agent(
    tools=formatted_tools, 
    llm=llm,
    agent='openai-functions',
    verbose=True
)

format_tool_to_openai_function을 사용하여 도구 목록을 OpenAI 함수 호출에 맞게 렌더링합니다. 내부적으로 도구의 이름, 설명 및 전달한 기타 인수를 기반으로 도구를 딕셔너리 구조로 형식화합니다.

이제 Wikipedia API 도구가 LLM에 바인딩되어 에이전트가 필요에 따라 Wikipedia 검색을 호출할 수 있게 되었습니다. 다음 단계에서는 입력 데이터를 로드하고 LLM 체인을 실행하여 노래 메타데이터를 추출하는 방법을 살펴보겠습니다.

(iv) 프롬프트 정의
LLM에 전달하는 프롬프트는 우리가 얻는 응답에 중요한 역할을 합니다. 여기서는 시스템 프롬프트와 사용자 프롬프트, 두 가지 프롬프트 세트를 정의해야 합니다.
시스템 프롬프트에서는 출력 결과에 대한 기본적인 컨텍스트와 설명을 제공합니다.

system_prompt = """
당신은 음악과 노래에 관한 모든 것에 대해 전문적인 지식을 가진 유용한 어시스턴트입니다.
출력은 중괄호로 묶인 JSON 형식이어야 하며 추가 세부 정보나 설명은 포함하지 않습니다.

예시:
'genre': 'Disco, Pop',
'label': 'Sony Music',  
'language': 'Korean'
'producers': 'Alex Boh, Betty, Germaine',
'songwriters': 'John Johnson, Adam Smith'  
"""

사용자 프롬프트의 경우 특정 노래 제목과 아티스트를 기반으로 동적 프롬프트를 생성하는 함수를 작성합니다. 여기에는 우리가 답을 원하는 질문과 예상되는 구조화된 출력이 다시 강조됩니다.

def generate_input_prompts(title: str, artist: str) -> str:
    return f"""
Song Title: {title}
Artist: {artist}

What is the genre of this song?
What record label produced it?
What language is the song in?  
Who are the producers of the song?
Who are the songwriters?

Provide your answers in a concise JSON format. Do not include any other explanations or details.
"""

그런 다음 프롬프트를 ChatPromptTemplate에 배치합니다:

from langchain.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, MessagesPlaceholder, HumanMessagePromptTemplate

system_prompt_template = SystemMessagePromptTemplate.from_template(system_prompt)
human_prompt_template = HumanMessagePromptTemplate.from_template('{user_prompt}')

prompts = ChatPromptTemplate.from_messages([
    system_prompt_template, 
    MessagesPlaceholder(variable_name='chat_history'),
    human_prompt_template
])

위의 프롬프트 템플릿에서 우리는 두 개의 입력 변수를 예상하고 있음을 알 수 있습니다:

  • generate_input_prompts로 생성된 동적 프롬프트를 받아들일 사용자 프롬프트 입력
  • 에이전트가 이전 도구 호출 및 출력을 기록하는 스크래치패드 역할을 하는 MessagesPlaceholder. 이 “중간” 구조화된 사고 공간은 LLM 에이전트의 추론 능력을 향상시킵니다.

이제 LLM에 전달할 동적 사용자 프롬프트와 이전 대화 기록을 저장할 공간이 마련되었습니다. 다음으로 이러한 프롬프트와 LLM 체인을 사용하여 실제로 노래 메타데이터 추출을 수행하는 방법을 살펴보겠습니다.

(v) 사용자 정의 에이전트와 AgentExecutor 정의
이제 LLM, 프롬프트, 도구를 인스턴스화했으므로 이들을 사용자 정의 에이전트 객체로 결합할 때입니다.
LangChain 표현식 언어의 일부인 파이프 연산자 |를 사용하여 이러한 모든 조각을 연결하여 agent 객체를 생성한 다음 AgentExecutor 클래스로 파싱합니다.

from langchain.agents import AgentExecutor, Tool, AgentType
from langchain.prompts import StringPromptTemplate
from langchain.llms import OpenAI
from langchain.agents import OpenAIFunctionsAgent
from langchain.utilities.openai_functions import OpenAIFunctionsAgentOutputParser

agent_executor = AgentExecutor.from_agent_and_tools(
    agent=OpenAIFunctionsAgent(
        llm=llm,
        tools=tools,
        prompt=StringPromptTemplate.from_template(system_prompt)
    ),
    tools=tools,
    verbose=True
)

# Format agent scratchpad to OpenAI functions
prompt_message_placeholder = MessagesPlaceholder(variable_name="agent_scratchpad")

agent_prompt = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template(system_prompt),  
    prompt_message_placeholder,
    HumanMessagePromptTemplate.from_template(user_prompt)
])

output_parser = OpenAIFunctionsAgentOutputParser()
format_instructions = output_parser.get_format_instructions() 
agent_prompt = agent_prompt.format_messages(agent_scratchpad=format_instructions)

AgentExecutor는 본질적으로 에이전트의 런타임으로, 에이전트를 호출하고 선택한 작업을 실행하며 출력을 에이전트에 다시 전달하고 반복하여 실행 로직을 실행합니다.
위의 단계를 아래와 같이 create_agent_executor() 함수로 래핑할 수 있습니다:

def create_agent_executor(llm: ChatOpenAI, tools: List[Tool], system_prompt: str) -> AgentExecutor:
    prompt = ChatPromptTemplate.from_messages([
        SystemMessagePromptTemplate.from_template(system_prompt),
        MessagesPlaceholder(variable_name="chat_history"),
        HumanMessagePromptTemplate.from_template('{user_prompt}'),
    ])

    agent = initialize_agent(
        agent=AgentType.OPENAI_FUNCTIONS, 
        tools=format_tool_to_openai_function(tools),
        llm=llm,
        prompt=prompt,
        output_parser=OpenAIFunctionsAgentOutputParser(),
    )

    agent_chain = AgentChain.from_agent_and_tools(
        agent,
        format_tool_to_openai_function(tools),
        prompt=format_to_openai_function_messages(prompt)
    )

    return AgentExecutor.from_agent_and_tools(
        agent=agent_chain,
        tools=tools,
        verbose=True
    )

앞서 언급했듯이 OpenAIFunctionsAgentOutputParser()는 특히 OpenAI 함수 호출을 위해 텍스트 응답을 구조화하는 데 도움이 됩니다.
format_to_openai_function_messages() 함수도 같은 역할을 하지만 에이전트 스크래치패드의 내용(LLM 에이전트 실행의 중간 단계로 간주됨)에 적용합니다.

이제 모든 구성요소가 준비되어 입력 데이터에 대해 에이전트를 실행할 수 있습니다. 다음 단계에서는 CSV 파일에서 노래 정보를 로드하고 Wikipedia 메타데이터를 추출하는 방법을 살펴보겠습니다.

(vi) 스크래핑 루프 실행
마지막 단계에서는 노래 데이터셋을 pandas DataFrame으로 읽고, 각 행을 반복하여 노래 제목과 아티스트 이름을 검색한 다음 해당 값을 LLM 에이전트 실행기에 파싱합니다.

import pandas as pd
import json

df = pd.read_csv(config.INPUT_FILE)

# Create an empty DataFrame to store the metadata
metadata_df = pd.DataFrame(columns=['genre', 'label', 'language', 'producers', 'songwriters'])

for idx, row in df.iterrows():
    title = row['song']
    artist = row['performer']

    user_prompt = generate_input_prompts(title, artist)

    with get_openai_callback() as cb:
        result = agent_executor.run(user_prompt=user_prompt)
        print(f"Title: {title}, Artist: {artist}")
        print(f"Result: {result}")
        print(f"Total Tokens: {cb.total_tokens}")
        print(f"Prompt Tokens: {cb.prompt_tokens}")
        print(f"Completion Tokens: {cb.completion_tokens}")
        print(f"Total Cost (USD): ${cb.total_cost}")

    # Parse the JSON string into a dictionary  
    result_dict = json.loads(result)

    # Append the metadata to the DataFrame
    metadata_df = metadata_df.append(result_dict, ignore_index=True)

# Concatenate the metadata with the original DataFrame  
output_df = pd.concat([df, metadata_df], axis=1)

# Save the enriched data to a CSV file
output_df.to_csv(config.OUTPUT_FILE, index=False)

에이전트 실행기에 전달되는 입력 프롬프트는 DataFrame 행 반복에서 특정 노래 및 아티스트 이름에 따라 사용자 정의됩니다.
LLM에서 생성된 출력은 JSON 구조를 포함하는 텍스트 문자열이므로 json.loads를 사용하여 JSON 형식의 문자열을 딕셔너리 객체로 파싱해야 합니다.
추출된 데이터는 아래 미리보기에서 볼 수 있듯이 각 노래의 해당 메타데이터 열에 CSV 파일로 반복적으로 저장됩니다:

또한 각 에이전트 실행에 대한 LLM 비용과 토큰을 추적하기 위해 컨텍스트 관리자에서 get_openai_callback을 사용합니다. 전체적으로 이 200곡 세트는 LLM 사용 측면에서 약 0.48 USD 정도의 비용만 듭니다.

이로써 Wikipedia 데이터를 활용한 노래 메타데이터 스크래핑 튜토리얼을 마칩니다. LLM 에이전트, 도구, 함수 호출의 조합을 통해 유연하고 강력한 웹 스크래핑 워크플로를 구축하는 방법을 보여드렸습니다. 이 접근 방식은 다양한 데이터 소스와 사용 사례에 적용할 수 있는 확장성 있는 프레임워크를 제공합니다.

마치며

이 글에서는 LLM 에이전트, 도구, 함수 호출을 활용하여 위키피디아에서 노래 메타데이터를 추출하는 방법을 단계별로 살펴보았습니다. LangChain 프레임워크를 사용하여 GPT 3.5 Turbo 모델과 위키피디아 API를 연결하고, 사용자 정의 프롬프트와 출력 파서를 정의하여 원하는 정보를 구조화된 형식으로 추출할 수 있었습니다.

이 접근 방식의 강점은 유연성과 확장성에 있습니다. LLM 에이전트는 주어진 컨텍스트를 이해하고 동적으로 적절한 도구를 선택할 수 있기 때문에, 다양한 유형의 데이터 소스와 추출 태스크에 적용할 수 있습니다. 또한 함수 호출을 통해 외부 API와 쉽게 통합할 수 있어 더욱 강력한 웹 스크래핑 워크플로를 구축할 수 있습니다.

물론 이 접근 방식에는 한계와 고려 사항도 있습니다. LLM은 때로는 부정확하거나 일관되지 않은 출력을 생성할 수 있으며, 모델 호출에 따른 비용도 감안해야 합니다. 그러나 적절한 프롬프트 엔지니어링과 에러 처리를 통해 이러한 문제를 완화할 수 있습니다.

전반적으로 LLM 기반의 웹 스크래핑은 기존의 rule-based 방식에 비해 더욱 지능적이고 유연한 정보 추출을 가능하게 합니다. 구조화되지 않은 텍스트에서 원하는 정보를 추출하는 작업부터 외부 API와의 통합까지, 다양한 시나리오에서 활용될 수 있는 강력한 도구라 할 수 있습니다.

앞으로 LLM 기술이 더욱 발전하고 정교해짐에 따라, 웹 스크래핑을 포함한 많은 데이터 관련 태스크에서 그 역할과 중요성이 커질 것으로 예상됩니다. 개발자와 데이터 전문가들은 이러한 트렌드를 주시하고 적극적으로 활용함으로써 더욱 효율적이고 가치 있는 인사이트를 얻을 수 있을 것입니다.

로그인하여 댓글을 작성해주세요