반응형

AI 에이전트는 LLM(Large Language Model)을 활용하여 복잡한 작업을 수행하는 자동화된 시스템입니다. 일반적인 LLM 사용이 단순 프롬프트-응답 방식이라면, 에이전트는 더 복잡하고 반복적인 작업을 수행할 수 있습니다.

AI 에이전트의 핵심 디자인 패턴

  1. 계획(Planning): 작업을 수행하기 위한 단계를 미리 생각하고 계획합니다.
  2. 도구 사용(Tool Use): 검색, 계산 등 필요한 도구들을 활용합니다.
  3. 반성(Reflection): 결과를 반복적으로 개선하며, 여러 LLM이 결과를 검토하고 제안할 수 있습니다.
  4. 다중 에이전트 통신: 각각의 LLM이 고유한 역할을 수행하며 서로 협력합니다.
  5. 메모리: 여러 단계에 걸친 진행 상황과 결과를 추적합니다.
 

AI와 AI 에이전트의 차이: 깊이 '생각하는' AI의 시대가 온다

인간의 사고 과정에 대한 획기적인 통찰을 제공한 노벨상 수상자 다니엘 카너먼은 그의 저서 "생각에 관한 생각(Thinking, Fast and Slow)"에서 인간의 사고방식을 '시스템 1'과 '시스템 2'로 구분했습니

yunwoong.tistory.com


LangGraph 소개

LangGraph는 LangChain의 확장으로, 에이전트와 다중 에이전트 워크플로우를 구축하기 위해 특별히 설계되었습니다. 주요 특징은 다음과 같습니다.

 

Home

🦜🕸️LangGraph ⚡ Building language agents as graphs ⚡ Note Looking for the JS version? Click here (JS docs). Overview LangGraph is a library for building stateful, multi-actor applications with LLMs, used to create agent and multi-agent workflows

langchain-ai.github.io

핵심 컴포넌트

  1. 노드(Nodes)
    • 에이전트나 함수를 나타냅니다.
    • LLM 호출, 도구 실행 등의 작업을 수행합니다.
  2. 엣지(Edges)
    • 노드들을 연결합니다.
    • 워크플로우의 흐름을 정의합니다.
  3. 조건부 엣지(Conditional Edges)
    • 다음 노드로의 이동을 결정하는 조건을 포함합니다.
    • 워크플로우의 분기를 관리합니다.

State 관리

LangGraph의 중요한 특징 중 하나는 상태 관리입니다. 에이전트 상태(Agent State)는 아래와 같습니다.

  • 그래프의 모든 부분에서 접근 가능
  • 지속성 레이어에 저장 가능
  • 나중에 상태를 복구하여 작업 재개 가능

AI 에이전트와 LangGraph 이해하기

AI 에이전트는 LLM을 사용해 복잡한 작업을 처리하는 자동화된 시스템입니다. 단순한 프롬프트-응답이 아닌, 여러 단계의 작업을 수행할 수 있죠. LangGraph를 사용해 이런 에이전트를 만들어보겠습니다.

먼저 필요한 라이브러리들을 가져옵니다.

from typing import List, TypeVar, Annotated, TypedDict
from operator import add
from langchain_openai import ChatOpenAI
from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage, ToolMessage
from langgraph.graph import StateGraph, END
from langchain.tools import Tool
import getpass
import os

# API 키 설정
if "OPENAI_API_KEY" not in os.environ:
    os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API key: ")

1. 도구 설정: 계산기 예제

간단한 계산기 도구를 만들어보겠습니다. 이 계산기는 하나의 수식을 받거나 여러 인자를 받아 계산할 수 있습니다.

def calculator(*args) -> float:
    """수학 계산을 수행하는 함수"""
    if len(args) == 1:
        return eval(str(args[0]))
    return eval(' '.join(str(arg) for arg in args))

tool = Tool(
    name="calculator",
    func=calculator,
    description="Useful for performing mathematical calculations"
)

2. 상태 관리

LangGraph의 핵심 기능 중 하나는 상태 관리입니다. 대화 이력을 messages 리스트에 저장하고, operator.add로 새로운 메시지를 추가합니다.

class AgentState(TypedDict):
    messages: Annotated[List[AnyMessage], add]

3. 에이전트 클래스

에이전트의 핵심 로직을 담당하는 클래스를 만듭니다.

class Agent:
    def __init__(self, model, tools, system=""):
        self.system = system
        self.tools = {t.name: t for t in tools}
        self.model = model.bind_tools(tools)
        self.graph = self._create_graph()

그래프 생성

    def _create_graph(self):
        workflow = StateGraph(AgentState)

        # 노드 추가
        workflow.add_node("llm", self.call_model)
        workflow.add_node("action", self.take_action)

        # 엣지 추가
        workflow.add_conditional_edges(
            "llm",
            self.should_use_tool,
            {True: "action", False: END}
        )
        workflow.add_edge("action", "llm")

        workflow.set_entry_point("llm")
        return workflow.compile()

여기서 중요한 컴포넌트들을 살펴보겠습니다.

  1. 노드(Nodes)
    • llm: 사용자 입력을 처리하고 도구 사용을 결정합니다.
    • action: 실제 도구를 실행합니다.
  2. 엣지(Edges)
    • 조건부 엣지: 도구 사용 여부에 따라 다음 단계를 결정합니다.
    • 일반 엣지: 도구 실행 후 다시 LLM으로 돌아갑니다.

도구 실행 로직

    def take_action(self, state: AgentState):
        tool_calls = state["messages"][-1].tool_calls
        results = []

        for t in tool_calls:
            if t["name"] not in self.tools:
                result = "bad tool name, retry"
            else:
                args_values = list(t["args"].values())
                try:
                    result = self.tools[t["name"]].invoke(*args_values)
                except Exception as e:
                    result = f"Error executing tool: {str(e)}"
            results.append(
                ToolMessage(
                    tool_call_id=t["id"],
                    name=t["name"],
                    content=str(result)
                )
            )
        return {"messages": results}

4. 실행 및 테스트

에이전트를 초기화하고 테스트해 봅시다.

system_prompt = """You are a helpful assistant that can use tools to accomplish tasks.
When you need to perform calculations, use the calculator tool.
Always explain your reasoning before and after using tools."""

model = ChatOpenAI(temperature=0)
agent = Agent(model=model, tools=[tool], system=system_prompt)

def run_agent(question: str):
    print(f"\nQuestion: {question}")
    messages = [HumanMessage(content=question)]
    result = agent.graph.invoke({"messages": messages})
    print(f"Final Answer: {result['messages'][-1].content}")
    return result

이 구조의 장점은:

  1. 명확한 실행 흐름: 그래프 구조로 실행 흐름을 쉽게 이해할 수 있습니다.
  2. 유연한 확장성: 새로운 도구나 노드를 쉽게 추가할 수 있습니다.
  3. 상태 추적: 대화 이력과 작업 진행 상황을 효과적으로 관리합니다.

실제로 에이전트를 테스트해 보고 그 결과를 시각화해 보겠습니다.

# 테스트할 질문들 준비
test_questions = [
    "What is 25 * 48?",
    "If I have 3 groups of 17 items each, how many items do I have in total?"
]

print("=== 에이전트 테스트 시작 ===")
for question in test_questions:
    run_agent(question)
print("=== 테스트 완료 ===\n")

# 그래프 시각화
print("=== 그래프 구조 시각화 ===")
from IPython.display import Image
Image(agent.graph.get_graph().draw_png())

첫 번째 질문 "What is 25 * 48?"

  • LLM이 계산 필요성을 인식
  • calculator 도구 호출
  • 결과를 받아 최종 답변 생성

두 번째 질문 "If I have 3 groups of 17 items each, how many items do I have in total?"

  • LLM이 문제를 이해하고 수식으로 변환
  • calculator(3 * 17) 실행
  • 결과를 문맥에 맞게 설명

그래프 구조를 보면

  • LLM 노드 (시작점)
  • Action 노드 (도구 실행)
  • 조건부 분기와 순환 구조

이러한 시각화를 통해 에이전트의 의사결정 과정과 작업 흐름을 명확하게 이해할 수 있습니다.


이 기본 구조를 바탕으로 다음과 같은 기능을 추가할 수 있습니다.

  • 여러 도구를 사용하는 복잡한 작업 처리
  • 중간 결과를 저장하고 불러오는 기능
  • 사용자와의 대화형 상호작용
  • 외부 API나 데이터베이스와의 연동

이렇게 만든 에이전트는 단순 계산부터 복잡한 정보 처리까지 다양한 작업을 자동화하는 데 활용할 수 있습니다.

반응형