langgraph의 공식문서를 번역해 놓은 자료입니다. 이해하기 쉽게 예제는 변경하였습니다. 또한 필요한 경우 부연 설명을 추가하였습니다. 문제가 되면 삭제하겠습니다.
https://langchain-ai.github.io/langgraph/tutorials/introduction/
Quick Start
Quick Start 가이드에서는 LangGraph를 사용하여 다음과 같은 기능을 갖춘 지원 챗봇을 구축할 것이다.
- 웹 검색을 통해 자주 묻는 질문에 답변하기
- 여러 호출 간 대화 상태 유지하기
- 복잡한 질문을 사람에게 전달하여 검토받기
- 맞춤 상태를 사용하여 챗봇의 동작 제어하기
- 대화 내용을 되돌려보고 다른 대화 경로를 탐색하기
기본 챗봇으로 시작하여 점진적으로 더 정교한 기능을 추가하면서, 주요 LangGraph 개념들을 함께 소개할 것이다.
Part1: 기본 Chatbot 만들기
우리는 먼저 LangGraph를 사용하여 간단한 챗봇을 만들 것이다. 이 챗봇은 사용자 메시지에 직접 응답한다. 비록 단순하지만, LangGraph로 빌드하는 핵심 개념들을 보여준다. 이 섹션이 끝나면 기본적인 챗봇을 완성하게 될 것이다.
우선 StateGraph를 생성하자. StateGraph 객체는 우리의 챗봇을 "상태 기계(state machine)"로 정의하는 구조를 나타낸다. 우리는 노드를 추가하여 LLM과 챗봇이 호출할 수 있는 함수들을 나타내고, 이 함수들 간의 전환을 어떻게 처리할지를 결정하는 엣지를 추가할 것이다.
from typing import Annotated
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
load_dotenv()
class State(TypedDict):
# add_messages 함수는 langgraph에 있는 내장 함수이며, 기존 메시지가 있으면 update하고 없으면 새로 추가한다.
messages: Annotated[list, add_messages]
graph_builder = StateGraph(State)
Note
그래프를 정의할 때 가장 먼저 해야 할 일은 그래프의 State(상태)를 정의하는 것이다. State는 그래프의 스키마와 상태 업데이트를 적용하는 방법을 지정하는 리듀서 함수(reducer functions)로 구성된다. 예시에서 State는 messages의 단일 키를 가지는 TypedDict로 정의된다. 이 messages 키는 add_messages 리듀서 함수로 구성되어 있으며, 이는 새로운 메시지를 기존 목록에 추가한다. 즉, 메시지를 덮어쓰는 대신 추가하는 것이다. Annotated가 없는 State 키는 각 업데이트마다 덮어써지며, 가장 최근 값을 저장하게 된다.
상태, 리듀서 및 기타 저수준 개념에 대해 더 알고 싶다면 이 개념 가이드를 확인해 보자.
이제 우리의 그래프는 두 가지를 알고 있다.
- 우리가 정의하는 모든 노드는 현재 State(상태)를 입력으로 받고, 그 상태를 업데이트하는 값을 반환한다.
- messages는 기존 목록에 덧붙여지며, 직접 덮어쓰지 않는다. 이는 Annotated 구문에서 제공되는 add_messages 함수로 처리된다.
다음 단계는 "chatbot" 노드를 추가하는 것이다. 노드는 작업 단위를 나타내며, 일반적으로 Python 함수로 구현된다.
llm = ChatOpenAI(model="gpt-3.5-turbo")
def chatbot(state: State):
# state["messages"] = [HumanMessage(content='메시지 내용',additional_kwargs={})]
return {"messages": [llm.invoke(state["messages"])]}
graph_builder.add_node("chatbot", chatbot)
챗봇 노드 함수가 현재 State(상태)를 입력으로 받고, "messages"라는 키 아래 업데이트된 메시지 목록을 포함하는 딕셔너리를 반환한다. 이는 모든 LangGraph 노드 함수의 기본 패턴이다.
State에 있는 add_messages 함수는 LLM의 응답 메시지를 이미 상태에 있는 메시지 목록에 덧붙이는 역할을 한다.
다음 단계는 시작 지점(entry point)을 추가하는 것이다. 이는 그래프가 실행될 때마다 작업을 어디서 시작할지 정의하는 역할을 한다.
graph_builder.add_edge(START, "chatbot")
동일하게, 종료 지점(finish point)을 설정하자. 이것은 그래프에 "이 노드가 실행될 때마다, 여기서 종료할 수 있다"는 지시를 내리는 역할을 한다. 즉, 해당 노드가 실행되면 그래프가 더 이상 진행하지 않고 종료하게 된다.
graph_builder.add_edge("chatbot", END)
마지막으로, 그래프를 실행할 수 있도록 해야 한다. 이를 위해 그래프 빌더에서 compile()
을 호출한다. 이 함수는 CompiledGraph
를 생성하며, 이를 사용하여 상태를 기반으로 그래프를 호출할 수 있다.
graph = graph_builder.compile()
그래프는 get_graph
메서드와 draw_ascii
또는 draw_png
같은 draw
메서드를 사용하여 시각화할 수 있다. 각 draw 메서드는 추가적인 의존성(dependencies)을 필요로 하니, 사용하기 전에 해당 의존성을 설치해야 한다.
from IPython.display import Image, display
try:
display(Image(graph.get_graph().draw_mermaid_png(output_file_path="./chatbot.png")))
except Exception:
# This requires some extra dependencies and is optional
pass
참고
IPython을 못찾는 경우 아래 명령어를 통해 설치를 하자.
pip install ipython
이제 챗봇을 실행해보자.
다음과 같은 코드를 사용해 챗봇을 실행할 수 있다.
def stream_graph_updates(user_input: str):
messages = []
for event in graph.stream({"messages": [("user", user_input)]}):
for value in event.values():
messages.append(value["messages"][-1].content)
return "\n".join(messages)
question = "한국의 수도는 어디야?"
result = stream_graph_updates(question)
print(result)
실행결과
한국의 수도는 서울입니다.
위에서 event의 실행결과는 아래와 같다. (AddableUpdatesDict)
{
"chatbot": {
"messages": [
AIMessage(content="한국의 수도는 서울입니다.",
...
]
}
}
이것으로 LangGraph를 사용하여 첫 번째 챗봇을 구축했다. 이 챗봇은 사용자 입력을 받아 LLM을 사용하여 응답을 생성함으로써 기본적인 대화를 나눌 수 있다. 위의 호출에 대한 LangSmith Trace를 제공된 링크에서 확인할 수 있다.
그러나 챗봇의 지식이 훈련 데이터에 제한되어 있다는 점을 눈치챘을 수도 있다. 다음 단계에서는 웹 검색 도구를 추가하여 챗봇의 지식을 확장하고 더 많은 기능을 갖출 수 있도록 할 것이다.