[Part 3] 2시간 안에 업무 자동화 AI Agent 만들기: 구글 검색 기능

AI Agent 만들기의 마지막 파트입니다.

Part 1: 전체 프로그램 셋업, OpenAI API 연결, Vector Store 연결

Part 2: 커스텀 LLM 체인 직접 만들고나서 실행하기

Part 3: Agent가 나 대신 실제 구글 검색하는 기능 만들기


SerpAPI key 받아오기

저번 파트에서 성공적으로 코드를 수행했다면 이제 AI 에이전트가 업무를 하기 위해 먼저 to-do list를 만들고 태스크 중 중요도가 높은 태스크부터 진행할 것입니다.

하지만 실제 구글 검색을 통해서 모은 자료가 아니기 때문에 정보가 정확하지 않고 hallucination 결과값들을 내뱉을 수 있습니다.

이번 파트에서는 이를 방지하고자 Google SerpAPI라는 것을 써서 에이전트가 실제 웹과 연결되어 구글링을 할 수 있도록 할 것입니다.

웹 검색을 하기 위해서는 Part 1에서 OpenAI를 쓰기 위해 OpenAI API secret key를 받은것처럼 SerpAPI key를 받아야합니다.

https://serpapi.com/ 여기로 가서 계정을 만든 다음, 옆에 API Dashboard라는 곳에서 Api key 버튼을 누르고 API key를 만드세요. OpenAI API key와 마찬가지로 이걸 복사해서 코드에 붙여넣을 겁니다.

import os
os.environ["OPENAI_API_KEY"] = " "
os.environ["SERPAPI_API_KEY"] = " "

기존 코드의 두번째 줄 바로 밑에 os.environ[”SERPAPI_API_KEY”] = “” 를 복붙하고 “ “ 사이에 방금 만든 SerpAPI key를 붙여넣으세요.

Part 1에서 나왔듯이 저희는 구글 검색을 가능하게 하려면 알맞는 모듈들을 불러와야합니다.

from collections import deque
from typing import Dict, List, Optional, Any

from langchain import LLMChain, OpenAI, PromptTemplate
from langchain.embeddings import OpenAIEmbeddings
from langchain.llms import BaseLLM
from langchain.vectorstores.base import VectorStore
from pydantic import BaseModel, Field
from langchain.chains.base import Chain

from langchain.vectorstores import FAISS
from langchain.docstore import InMemoryDocstore

기존의 모듈 코드(위 코드) 밑에 아래 코드를 붙여넣습니다.

from langchain.agents import ZeroShotAgent, Tool, AgentExecutor
from langchain import OpenAI, SerpAPIWrapper, LLMChain

이제 구글 검색이 가능한 LLM 체인을 만들겁니다.

Part 2에서 세번째 체인인 class ExecutionChain(LLMChain)은 현재 구글 검색이 불가능합니다. (아래 코드)

class ExecutionChain(LLMChain):
    """Chain to execute tasks."""

    @classmethod
    def from_llm(cls, llm: BaseLLM, verbose: bool = True) -> LLMChain:
        """Get the response parser."""
        execution_template = (
            "You are an AI who performs one task based on the following objective: {objective}."
            " Take into account these previously completed tasks: {context}."
            " Your task: {task}."
            " Response:"
        )
        prompt = PromptTemplate(
            template=execution_template,
            input_variables=["objective", "context", "task"],
        )
        return cls(prompt=prompt, llm=llm, verbose=verbose)

그래서 이 코드를 아래 코드로 대체할 것입니다.

todo_prompt = PromptTemplate.from_template(
    "You are a planner who is an expert at coming up with a todo list for a given objective. Come up with a todo list for this objective: {objective}"
)
todo_chain = LLMChain(llm=OpenAI(temperature=0), prompt=todo_prompt)
search = SerpAPIWrapper()
tools = [
    Tool(
        name="Search",
        func=search.run,
        description="useful for when you need to answer questions about current events",
    ),
    Tool(
        name="TODO",
        func=todo_chain.run,
        description="useful for when you need to come up with todo lists. Input: an objective to create a todo list for. Output: a todo list for that objective. Please be very clear what the objective is!",
    ),
]

prefix = """You are an AI who performs one task based on the following objective: {objective}. Take into account these previously completed tasks: {context}."""
suffix = """Question: {task}
{agent_scratchpad}"""
prompt = ZeroShotAgent.create_prompt(
    tools,
    prefix=prefix,
    suffix=suffix,
    input_variables=["objective", "task", "context", "agent_scratchpad"],
)

이 코드로 대체할 건데요, 기존의 코드와 이 새로운 코드랑 비교를 해봅시다.

가장 먼저 코드의 첫줄을 보면 todo_prompt가 있습니다. 이건 기존 코드의 프롬프트 템플릿과 똑같은 기능을 해주고 있습니다. 하지만 prompt의 내용이 바꼈는데요, 기존에는 그저 ‘태스크를 컨텍스트에 맞게 실행해라’였다면 이번에는 조금 더 구체적으로 먼저 todo list을 만들으라고 하네요.

이 템플릿을 기반으로 저희는 todo_chain을 다음 줄에서 만듭니다.

세번째 줄에 있는 search = SerpAPIWrapper()는 저희가 처음에 불러온 SerpAPI 검색 기능을 쓰기 위해 셋업을 해놓는 것입니다.

재미있게도 저희는 todo_chain이 쓰면 좋을만한 툴들을 만들어주고 이걸 적절한 상황에서 각 툴을 알맞게 쓸 수 있도록 할 수 있습니다.

tools = [ … ] 코드 블록을 보면 저희는 크게 두가지 툴, “Search” tool과 “TODO” tool을 만든 것을 볼 수 있습니다.

Search tool은 실제 구글 검색을 하면서 정확한 정보를 불러와야할때 쓰는 툴이 되는거고, TODO tool은 todo list을 만들어야 할때 쓰이는 툴이 되는 것입니다.

마지막으로 기존의 코드와 비슷한 코드 부분은 prefix = “…” 부터 prompt = ZeroShotAgent까지의 코드입니다. 여기서는 이제 만든 todo list을 기반으로 태스크를 하나씩 실행하게끔 하는 Agent을 만든겁니다.


거의 다 왔습니다.

기존 코드에서 바꿔야 할 부분이 몇가지 더 있습니다.

@classmethod
    def from_llm(
        cls, llm: BaseLLM, vectorstore: VectorStore, verbose: bool = False, **kwargs
    ) -> "BabyAGI":
        """Initialize the BabyAGI Controller."""
        task_creation_chain = TaskCreationChain.from_llm(llm, verbose=verbose)
        task_prioritization_chain = TaskPrioritizationChain.from_llm(
            llm, verbose=verbose
        )
        execution_chain = ExecutionChain.from_llm(llm, verbose=verbose)
        return cls(
            task_creation_chain=task_creation_chain,
            task_prioritization_chain=task_prioritization_chain,
            execution_chain=execution_chain,
            vectorstore=vectorstore,
            **kwargs,
        )

위 코드를 아래 코드와 같이 대체합니다.

@classmethod
    def from_llm(
        cls, llm: BaseLLM, vectorstore: VectorStore, verbose: bool = False, **kwargs
    ) -> "BabyAGI":
        """Initialize the BabyAGI Controller."""
        task_creation_chain = TaskCreationChain.from_llm(llm, verbose=verbose)
        task_prioritization_chain = TaskPrioritizationChain.from_llm(
            llm, verbose=verbose
        )
        llm_chain = LLMChain(llm=llm, prompt=prompt)
        tool_names = [tool.name for tool in tools]
        agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names)
        agent_executor = AgentExecutor.from_agent_and_tools(
            agent=agent, tools=tools, verbose=True
        )
        return cls(
            task_creation_chain=task_creation_chain,
            task_prioritization_chain=task_prioritization_chain,
            execution_chain=agent_executor,
            vectorstore=vectorstore,
            **kwargs,
        )

기존 코드에서 만들었던 세번째 체인, execution_chain = ExecutionChain.from_llm(llm, verbose=verbose) 코드 대신 저희가 만든 구글 검색 기능이 가능한 ZeroShotAgent으로 바꿔줘야합니다.

return cls (…) 부분에서 execution_chain=execution_chain도 execution_chain=agent_executor로 대체합니다.

llm_chain = LLMChain(llm=llm, prompt=prompt)
tool_names = [tool.name for tool in tools]
agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names)
agent_executor = AgentExecutor.from_agent_and_tools(
    agent=agent, tools=tools, verbose=True
)

새롭게 추가된 코드를 좀 더 자세히 들여다보면, tool_names에서 저희가 만든 두가지 툴들, Search tool과 TODO tool을 둘다 쓸 수 있도록 루프를 돌고 있습니다.

agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names) 이 코드에서는 llm_chain 기반, 저희가 만든 두개의 툴 기반으로 ZeroShotAgent를 만든다는 것입니다.

그러고 다음 줄, agent_executor 에서는 이 agent를 실행한다는 코드입니다.


마지막으로 Part 2에서 만들었던 BabyAGI Controller 부분에서 아래 코드를 찾으세요.

class BabyAGI(Chain, BaseModel):
    """Controller model for the BabyAGI agent."""

    task_list: deque = Field(default_factory=deque)
    task_creation_chain: TaskCreationChain = Field(...)
    task_prioritization_chain: TaskPrioritizationChain = Field(...)
    execution_chain: ExecutionChain = Field(...)
    task_id_counter: int = Field(1)
    vectorstore: VectorStore = Field(init=False)
    max_iterations: Optional[int] = None

여기서도 저희가 처음에 만들었던 세번째 execution 체인 대신 새롭게 만든 구글 검색 기능 체인으로 대체해줘야합니다. 그래서 execution_chain: ExecutionChain = Field(...) 코드를 execution_chain: AgentExecutor = Field(...) 코드로 바꿔주세요.

그럼 아래 코드와 같이 생겼습니다.

class BabyAGI(Chain, BaseModel):
    """Controller model for the BabyAGI agent."""

    task_list: deque = Field(default_factory=deque)
    task_creation_chain: TaskCreationChain = Field(...)
    task_prioritization_chain: TaskPrioritizationChain = Field(...)
    execution_chain: AgentExecutor = Field(...)
    task_id_counter: int = Field(1)
    vectorstore: VectorStore = Field(init=False)
    max_iterations: Optional[int] = None


테스트해보기!

이제 실제로 구글 검색 기능이 되는지 테스트 해보겠습니다.

파일을 돌리기 위해서는 Part 1에서 만든 가상환경 안에 들어가 있어야합니다.

가상 환경 안에 들어와있다면 아래 사진처럼 뜹니다. 만약 들어와있지 않다면 터미널에서 source venv/bin/activate을 입력하고 엔터를 누르세요.

다음 저희는 serpAPI를 쓰기 위해 다운로드해야하는 패키지가 있습니다. 터미널에 pip install google-search-results를 입력해주세요.

이제 python agent.py 를 입력하면 코드가 돌려집니다.

첫번째 결과값으로 오늘 서울의 날씨를 구글에서 검색을 했고, 결과값으로 ”오늘의 날씨는 화창하며 최저 기온은 16도, 최고 기온은 24도입니다. 바람은 약하고 대기질 지수가 낮습니다.”가 나왔습니다.

두번째 태스크를 돌리면 8월달에 서울의 평균 기온이 -6도에서 30도 사이라는 결과값이 나왔는데, 별로 정확하진 않네요. 아무래도 hallucination이 발생한 것 같습니다.

마지막 태스크로 서울의 현재 습도를 검색한 결곽로 83% 정도의 습도, extremely humid라는 결과값이 나왔습니다.

Part 3 마무리

이로써 Part 3가 마무리되었습니다!

SerpAPI에서는 구글 검색 뿐만 아니라 네이버 검색, 구글 지도, 유투브 검색 기능 등 정말 다양한 API를 제공하고 있으니 SerpAPI 사이트에서 documentation을 확인하면서 써보세요 :)

오늘 저희가 만들었던 agent와 같이 비슷한 구조로 만드시면 됩니다.

"Write a weather report for Seoul today”라는 프롬트 대신 “conduct Facebook market analysis”라는 프롬트도 해보면서 코드를 돌려보세요! 생각보다 재미있는 결과값들이 나옵니다. ㅎㅎ

오늘도 긴 글 읽어주셔서 감사합니다. 문제가 생기거나 코드가 잘 이해되지 않는 부분이 있으면 언제든지 포스트에 댓글로 달아주세요!

3

👉 이 게시글도 읽어보세요

모집 중인 AI 스터디