인공지능 개발자 수다(유튜브 바로가기) 자세히보기

자연어처리/LangGraph

[LangGraph] LLM + tool 기본구조 실습

Suda_777 2025. 8. 17. 16:08

목차

    반응형

     

    langgraph 버전: 0.6.5

    langchain 버전 : 0.3.27

    내용 출처 : LangGrapn Docs

     

    LangGraph

    LangGraph Trusted by companies shaping the future of agents – including Klarna, Replit, Elastic, and more – LangGraph is a low-level orchestration framework for building, managing, and deploying long-running, stateful agents. Get started Install LangGr

    langchain-ai.github.io

     

    1. 개요 

    그동안 LangGraph의 각각의 구성요소에 대해 공부해 보았습니다.

    이번 시간에는 실제로 사용할 수 있는

    기본적인 Graph를 구현해 봅시다.

     


    2. LLM + Tool 기본 구성

    2.1. 구성

    모델 노드 (LLM)

    • 사용자 메시지를 읽음
    • 필요 시 tool 호출 제안 (tool_calls 생성)
    • tool 결과가 state에 들어오면 최종 답변을 생성

     

    ToolNode

    • 여러 tool(검색, 계산, RAG 등)을 담아둠
    • 모델이 호출한 tool을 실행하고 결과를 state에 추가

     

    종료 (END)

    • 모델이 더 이상 tool 호출을 제안하지 않고 최종 답변을 낼 때 종료

     

    그래프 설계도


    2.2.  코드 순서

    1. 툴 정의

    2. LLM 정의 + 툴와 연결

    3. 그래프 정의

        - state 정의

        - node 정의

        - Graph 정의

    4. Graph 실행

     


    3. 코드

    3.1. 툴 정의

    3.1.1. 계산기 툴 정의

    @tool

    • 툴을 만들때 데코레이터를 사용한다.
    • "parse_docstring=True" 를 넣어주면, docstring을 tool의 설명으로 바로 사용한다.
    • docstring은 Google 스타일 docstring 구문 으로 작성해야 한다

     

    툴의 내용은 사칙연산을 하는 계산기이다.

    import math
    import json
    from langchain.tools import tool
    
    
    @tool(parse_docstring=True)
    def calculator(expression: str) -> str:
        """안전한 사칙연산 및 기본 수학함수 계산기.
    
        Args:
            expression (str): 계산할 수식. 예: "2*(3+4) + sin(0.5)"
    
        Returns:
            str: JSON 문자열. {"expression": str, "result": number}
        """
        allowed_names = {
            "abs": abs, "round": round,
            "pow": pow, "sqrt": math.sqrt,
            "sin": math.sin, "cos": math.cos, "tan": math.tan,
            "log": math.log, "exp": math.exp, "pi": math.pi, "e": math.e
        }
        code = compile(expression, "<calc>", "eval")
        for name in code.co_names:
            if name not in allowed_names:
                raise ValueError(f"사용 불가 식별자: {name}")
        val = eval(code, {"__builtins__": {}}, allowed_names)
        return json.dumps({"expression": expression, "result": val})

     

    3.1.2. 로컬 검색 툴

    간단한 검색을 하는 예시 툴이다.

    툴은 만들면 리스트(list)에 모든 툴을 담아둔다

    # 아주 작은 로컬 지식베이스
    KB = [
        {"title": "LangGraph 개요", "text": "LangGraph는 그래프 기반으로 LLM과 Tool을 오가며 에이전트를 구성하는 프레임워크다."},
        {"title": "ToolNode 역할", "text": "ToolNode는 모델이 제안한 tool_calls를 실제로 실행하고 결과를 메시지로 state에 추가한다."},
        {"title": "bind_tools 역할", "text": "bind_tools는 모델이 사용할 수 있는 도구 목록을 알려주어 함수 호출 형태로 제안하게 만든다."},
    ]
    
    @tool(parse_docstring=True)
    def local_search(query: str) -> str:
        """간단한 로컬 검색을 수행합니다.
    
        Args:
            query (str): 검색 질의 문자열.
    
        Returns:
            str: JSON 문자열. {"query": str, "results": [{"title": str, "text": str, "score": int}, ...]}
        """
        qs = query.lower()
        scored = []
        for item in KB:
            text = f"{item['title']} {item['text']}".lower()
            score = sum(q in text for q in qs.split())
            if score > 0:
                scored.append({"title": item["title"], "text": item["text"], "score": int(score)})
        scored.sort(key=lambda x: x["score"], reverse=True)
        return json.dumps({"query": query, "results": scored[:3]})
    
    
    TOOLS = [calculator, local_search]

     

    실무에서 사용하는 검색도구들은 다음과 같다.

    • retriever를 넣어 RAG
    • 웹검색
    • 데이터베이스 검색
    • 파일 기반 검색

     


    3.2. LLM 모델 정의

    모델을 정의한다.

    그리고, 위에서 정의한 tools를 bind_tools() 메서드로 모델과 연결한다.

    from langchain_openai import ChatOpenAI
    
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
    llm_with_tools = llm.bind_tools(TOOLS)

     


    3.3. Graph 정의

    3.3.1. state 정의

    from langgraph.graph.message import add_messages
    from typing import TypedDict, Annotated, List
    
    
    class State(TypedDict):
        # add_messages 어노테이션을 사용하면 메시지를 자동 머지해 줍니다.
        messages: Annotated[List, add_messages]

     

    3.3.2. node 정의

    모델 노드 정의

    def model_node(state: State) -> State:
        """
        - 사용자/툴 메시지를 읽고
        - 필요 시 tool_calls를 제안하거나
        - 최종 답변을 생성합니다.
        """
        resp = llm_with_tools.invoke(state["messages"])
        return {"messages": [resp]}

     

    툴 노드 정의

    • ToolNode() 를 이용하면 된다.
    from langgraph.prebuilt import ToolNode
    tool_node = ToolNode(TOOLS)

     

    라우터 정의

    def router(state: State):
        """
        모델의 마지막 응답에 tool_calls가 있으면 tools로,
        없으면 end로 보냅니다.
        """
        last = state["messages"][-1]
        # langchain_core.messages.AIMessage 에 tool_calls 속성이 존재
        if isinstance(last, AIMessage) and getattr(last, "tool_calls", None):
            return "tools"
        return "end"

     

    3.3.3. 그래프 구성

    StateGraph() 를 이용해 그래프를 정의한다.

    노드와 엣지를 그래프에 추가해 준다.

    compile() 함수로 그래프를 완성한다.

    from langgraph.graph import StateGraph, START, END
    
    graph = StateGraph(State)
    graph.add_node("model", model_node)
    graph.add_node("tools", tool_node)
    
    graph.add_edge(START, "model")
    graph.add_conditional_edges("model", router, {"tools": "tools", "end": END})
    graph.add_edge("tools", "model")
    
    app = graph.compile()

     


    3.4. 그래프 실행

    from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
    
    # 예시 1, 로컬 검색이 필요한 질문
    out = app.invoke({"messages": [HumanMessage(content="LangGraph에서 ToolNode와 bind_tools 차이를 알려줘")]})
    print("=== 예시 1 응답 ===")
    print(out["messages"][-1].content)

     

    # 예시 2, 계산이 필요한 질문
    out = app.invoke({"messages": [HumanMessage(content="삼각형 내각의 합이 180도일 때, 45도와 65도의 나머지 각은 몇 도야? 식으로 계산해줘")]})
    print("\n=== 예시 2 응답 ===")
    print(out["messages"][-1].content)

     

    # 예시 3, 혼합 질의
    out = app.invoke({"messages": [HumanMessage(content="LangGraph ToolNode를 한 줄로 설명하고, 2*(3+4)+sin(0.5)도 계산해줘")]})
    print("\n=== 예시 3 응답 ===")
    print(out["messages"][-1].content)

     


    4. 실습 코드

    https://colab.research.google.com/drive/1AUUmaXrgRRYmJsmAd8Mpg_FT4htpVTgY?usp=sharing

     

    [LangGraph] 실습 (LLM + tool).ipynb

    Colab notebook

    colab.research.google.com

     

    반응형