인공지능/AI Agent

랭그래프를 이용한 간단한 챗봇

mino28 2025. 9. 15. 09:31

1. Tool Calling Agent

Tool Calling Agent는 자신이 가진 지식만 사용하는 것이 아니라, 외부 도구(API, 데이터베이스, 코드 실행기 등)를 호출해 문제를 해결하는 에이전트입니다. 사용자의 질문을 이해한 뒤 필요한 경우 적절한 툴을 선택하고, 입력값을 구성해 호출하며, 반환된 결과를 다시 가공해 최종 답변을 만듭니다. 쉽게 말해, 단순히 대화만 하는 AI가 아니라 “필요할 때 계산기, 검색엔진, 데이터 조회 도구 같은 도구를 직접 쓸 수 있는 AI”가 Tool Calling Agent입니다.

 

 

2. 웹 검색을 하는 챗봇

1. Tavily

Tavily는 웹을 실시간으로 검색해 AI가 최신·정확한 정보를 답변할 수 있도록 돕는 AI용 검색·브라우징 API 플랫폼입니다.

 

pip install -U tavily-python

 

API 키발급(https://app.tavily.com/home)

import getpass
import os


def _set_env(var: str):
    if not os.environ.get(var):
        os.environ[var] = getpass.getpass(f"{var}: ")


_set_env("OPENAI_API_KEY")
_set_env("TAVILY_API_KEY")

 

!pip install tavily-python

 

from tavily import TavilyClient

tavily_client = TavilyClient()

 

response = tavily_client.search("What is AI Agent?", max_results=3) # , topic="news", days = 10
response
  • max_results=3  → 검색 결과의 최대 개수를 지정합니다. 3이므로, 검색 결과를 최대 3개까지 가져옵니다.
  • topic="news" → 뉴스 기사 위주로 검색
  • days=10 → 최근 10일 이내의 자료만 검색

 

response['results']

 

# get_search_context: 보통 문자열(string) 형태이며, 여러 개의 검색 결과에서 중요한 내용만 추려서 한 덩어리의 텍스트로 제공합니다.
context = tavily_client.get_search_context(query="What is AI Agent?")
context

 

# qna_search: Tavily가 반환한 최종 답변. 보통 문자열(string) 형태이며, 한두 문장 정도로 정리된 응답을 제공합니다.
answer = tavily_client.qna_search(query="What is AI Agent?")
answer

 

2. TavilySearch

파라미터 :

  • max_results (optional, int): 검색 결과 반환 수
  • topic (optional, str): 검색 카테고리 / "general"(Default), "news", "finance"
  • include_answer (optional, bool): 쿼리에 대한 답변 포함 여부
  • include_raw_content (optional, bool): 결과 HTML 포함 여부
  • include_images (optional, bool): 쿼리 관련 이미지 목록 포함 여부
  • include_image_descriptions (optional, bool): 각 이미지에 대한 설명 텍스트 포함 여부
  • search_depth (optional, str): 검색 깊이 / "basic"(Default),"advanced"
  • time_range (optional, str): 필터링 날짜 범위 - "day", "week", "month", "year"
  • include_domains (optional, List[str]): 구체적으로 포함할 도메인 목록
  • exclude_domains (optional, List[str]): 구체적으로 제외할 도메인 목록
!pip install langchain_tavily

 

from langchain_tavily import TavilySearch

tool = TavilySearch(max_results=3)
tool.invoke("What's a 'node' in LangGraph?")

 

invoke_with_toolcall = tool.invoke({"args": {'query': "What's a 'node' in LangGraph?"}, "type": "tool_call", "id": "foo", "name": "tavily_search"})
invoke_with_toolcall
  • .invoke()는 LangChain Tool 호출 형식으로 입력을 받을 수 있습니다.
  • 단순 문자열을 넣는 대신, JSON(딕셔너리) 구조로 툴 호출 이벤트(tool call event)처럼 전달하는 방식입니다.

 

invoke_with_toolcall.content

 

!pip install langchain_community

 

from langchain_community.tools.tavily_search import TavilySearchResults

tool = TavilySearchResults(max_results=2)
tool.invoke("What's a 'node' in LangGraph?")

 

 

invoke_with_toolcall = tool.invoke({"args": {'query': "What's a 'node' in LangGraph?"}, "type": "tool_call", "id": "foo", "name": "tavily"})
invoke_with_toolcall

 

# results에 들어 있는 정보
invoke_with_toolcall.content

 

# 모델의 모든 실행결과
invoke_with_toolcall.artifact

 

3. 도구 바인딩

from langchain_core.tools import tool

@tool
def add(a: int, b: int) -> int:
    return a + b

@tool
def multiply(a: int, b: int) -> int:
    return a * b

tools = [add, multiply]

 

!pip install langchain_openai

 

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-5-nano") # model="gpt-4o"
llm_with_tools = llm.bind_tools(tools)

 

query = "What is 3 * 12? Also, what is 11 + 49?"

llm_with_tools.invoke(query).tool_calls

 

query = "What is 12 % 2?"

llm_with_tools.invoke(query).tool_calls

 

llm_with_tools = llm.bind_tools(tools,
    tool_choice={"type": "function", "function": {"name": "multiply"}}
)

resp = llm_with_tools.invoke("What is 3 * 12? Use tool.")
print(resp.tool_calls)  # 이제 비어있지 않음

 

from langchain_openai import ChatOpenAI

tool = TavilySearch(max_results=2)
tools = [tool]

llm = ChatOpenAI(model="gpt-5-nano")
llm_with_tools = llm.bind_tools(tools) # TavilySearch(tools) 을 호출할 수 있도록 함

 

llm_with_tools.invoke("안녕")

 

llm_with_tools.invoke("What is Langgraph?")

 

llm_with_tools.invoke("What is Langgraph?").tool_calls

 

!pip install langgraph

 

from typing import Annotated
from typing_extensions import TypedDict

from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages

class State(TypedDict):
    messages: Annotated[list, add_messages]

graph_builder = StateGraph(State)

def chatbot(state: State):
    return {"messages": [llm_with_tools.invoke(state["messages"])]} # 일반적인 질문에 대한 일반 답변 or tool_calls

graph_builder.add_node("chatbot", chatbot)

 

4. ToolNode

ToolNode는 LangGraph에서 도구 호출을 실제로 실행해 주는 노드입니다. LLM이 생성한 AIMessage.tool_calls를 읽어 각 호출의 도구 이름과 인자를 매칭해 실행하고, 결과를 ToolMessage로 반환하여 그래프의 상태(대화 기록)에 추가합니다. 보통 “LLM 노드 → ToolNode → LLM 노드” 형태의 루프에서 사용되며, tools_condition 같은 조건부 엣지와 함께 붙여 LLM이 도구를 요청할 때만 ToolNode가 동작하게 합니다. 요약하면, ToolNode는 LLM의 툴 호출 계획을 실제 코드 실행으로 연결하는 브리지로, 도구 레지스트리(이름→함수/툴)만 넘겨주면 호출·에러 처리·결과 전달까지 표준화된 방식으로 처리해줍니다.

import json
from langchain_core.messages import ToolMessage
from langgraph.prebuilt import ToolNode

class BasicToolNode:
    def __init__(self, tools: list) -> None:
        self.tools_by_name = {tool.name: tool for tool in tools} # ["tavily_search" : TavilySearch()]

    def __call__(self, inputs: dict):
        if messages := inputs.get("messages", []):
            message = messages[-1] # 마지막 message
        else:
            raise ValueError("No message found in input")
        
        outputs = []
        for tool_call in message.tool_calls: # 메시지에서 호출된 도구를 불러옴
            tool_result = self.tools_by_name[tool_call["name"]].invoke( # Tool 호출 실행
                tool_call["args"]
            )
            outputs.append( # Tool 호출 결과(ToolMessage) 추가
                ToolMessage(
                    content=json.dumps(tool_result),
                    name=tool_call["name"],
                    tool_call_id=tool_call["id"],
                )
            )
        return {"messages": outputs}

tool_node = BasicToolNode(tools=[tool])
# tool_node = ToolNode(tools=[tool])
graph_builder.add_node("tools", tool_node)

 

def route_tools(
    state: State,
):

    if isinstance(state, list):
        ai_message = state[-1]
    elif messages := state.get("messages", []):
        ai_message = messages[-1]
    else:
        raise ValueError(f"No messages found in input state to tool_edge: {state}")
    
    if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0:
        return "tools"
    return END


graph_builder.add_conditional_edges(
    "chatbot",
    route_tools,
    {"tools": "tools", END: END},
)

 

# 엣지 연결
graph_builder.add_edge("tools", "chatbot") # 도구가 호출될 때마다 챗봇으로 돌아가 다음 단계를 결정
graph_builder.add_edge(START, "chatbot")
graph = graph_builder.compile()
graph

 

def stream_graph_updates(user_input: str):
    for event in graph.stream({"messages": [{"role": "user", "content": user_input}]}): # graph 노드 호출 결과 받아옴
        for value in event.values():
            print("Assistant:", value["messages"][-1].content) # AI 답변 출력

 

while True:
    try:
        user_input = input("User: ")
        print("User:", user_input)
        if user_input.lower() in ["quit", "exit", "q"]:
            print("Goodbye!")
            break

        stream_graph_updates(user_input)
    except:
        user_input = "What do you know about LangGraph?"
        print("User: " + user_input)
        stream_graph_updates(user_input)
        break

 

 

5. create_react_agent

create_react_agent는 LangChain에서 ReAct 패턴(Reason+Act)을 따르는 에이전트를 손쉽게 구성하는 팩토리로, LLM과 사용할 도구 목록, 그리고 적절한 프롬프트를 결합해 “생각→도구 호출→관찰→최종 답변”의 반복 루프를 수행하는 Agent 객체를 만들어줍니다. 이 에이전트는 질문을 해석해 필요한 도구를 선택하고 인자를 구성해 호출한 뒤, 결과를 반영해 다음 행동을 결정하며, 보통 AgentExecutor와 함께 실행하여 다단계 추론과 복수의 툴 호출을 자동으로 오케스트레이션합니다. 핵심은 프롬프트(지침), LLM, 툴 레지스트리(이름→함수/API), 출력 파서를 표준화해 붙여주는 것이며, OpenAI 스타일의 툴콜을 포함한 다양한 LLM과 호환되어 실용적인 “생각하며 도구를 쓰는” 에이전트를 빠르게 구성할 수 있게 해주는 점입니다.

from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI

tool = TavilySearch(max_results=2)
tools = [tool]

llm = ChatOpenAI(model="gpt-5-nano")
agent = create_react_agent(llm, tools)

 

response = agent.invoke({"messages": [{"role": "user", "content": "What is LangGraph?"}]})

 

'인공지능 > AI Agent' 카테고리의 다른 글

벡터 데이터베이스  (0) 2025.09.17
랭그래프 Reflection  (0) 2025.09.17
랭그래프 기초  (0) 2025.09.15
랭그래프  (0) 2025.09.15
AI Agent  (0) 2025.09.15