RAG2026-5일17차시 · LangGraph 입문9 / 20← 이전목차다음 →MetaContents

17차시. LangGraph 입문

실제 코드로 만들어보기

출처: 원본 PDF 17차시_LangGraph입문.pdf 7~9쪽 · 쪽 그림 7쪽 8쪽 9쪽

Step 1: State 정의하기

Python
from typing import TypedDict

class SimpleRAGState(TypedDict):
    query: str
    results: list
    answer: str

Step 2: 각 Node를 함수로 만들기

출처: 원본 PDF 8쪽

Python
def search_node(state: SimpleRAGState):
    # 사용자의 질문을 받음
    query = state["query"]

    # 실제 검색 수행 (여기서는 더미)
    results = ["문서1", "문서2", "문서3"]

    # State 수정
    state["results"] = results
    return state

def answer_node(state: SimpleRAGState):
    # 검색 결과를 받음
    results = state["results"]

    # LLM으로 답변 생성
    answer = "검색 결과를 바탕으로 만든 답변입니다"

    # State 수정
    state["answer"] = answer
    return state

Step 3: Graph 만들기

출처: 원본 PDF 8쪽

Python
from langgraph.graph import StateGraph

# Graph 생성
graph = StateGraph(SimpleRAGState)

# Node 추가 (이름, 함수)
graph.add_node("search", search_node)
graph.add_node("answer", answer_node)

# Edge 추가 (어디에서 어디로)
graph.add_edge("search", "answer")

# 시작점 설정
graph.set_entry_point("search")

# 끝점 설정
graph.set_finish_point("answer")

실습 코드 · 17/simple_graph.py

실습 코드: 실습/src/17/simple_graph.py · 위의 원본 PDF 17차시 8쪽 「Step 3: Graph 만들기」과 이어집니다.

Python
# ============================================================
# 17차시 | LangGraph 입문
# 교안: 17차시_LangGraph입문.pptx
# 역할: 검색 노드와 답변 노드를 직선으로 잇는 가장 작은 그래프.
# 입력: State 키 query, results, answer
# 출력: results 와 answer 가 채워진 같은 State
#
#   State  = 노드들이 함께 쓰는 공용 딕셔너리. 공유 노트.
#            노드는 노트 전체를 다시 쓰지 않고, 고친 키만 return 한다.
#   Node   = State를 인자로 받는 함수.
#   Edge   = 다음에 실행할 노드. add_edge(지금, 다음).
#   START  = 시작 지점. END = 더 이상 갈 노드가 없음.
#   compile = 노드와 엣지를 묶어 실행 앱(app)으로 확정.
#   invoke  = 초기 State를 넣고 END까지 한 번에 실행. 최종 State를 돌려준다.
#
# 흐름
#   START
#     |
#     v
#   search      읽기 query / 쓰기 results
#     |
#     v
#   answer      읽기 results / 쓰기 answer
#     |
#     v
#    END
# ============================================================

from typing import TypedDict
from langgraph.graph import StateGraph, START, END

class SimpleRAGState(TypedDict):
    query: str      # 사용자의 질문
    results: list   # 검색 결과 목록
    answer: str     # 최종 답변

# 노드 search
#   읽기: query
#   쓰기: results
def search_node(state: SimpleRAGState):
    # 현재 State에서 질문을 읽습니다.
    print("[검색 노드] 질문:", state["query"])

    # 실제 검색 대신 고정된 문서 목록을 준비합니다.
    results = [
        "문서1: 제품 가격은 10,000원입니다.",
        "문서2: 배송비는 무료입니다.",
        "문서3: 현재 할인 행사가 진행 중입니다."
    ]

    # LangGraph가 results 항목을 갱신하도록 반환합니다.
    return {"results": results}

# 노드 answer
#   읽기: results
#   쓰기: answer
def answer_node(state: SimpleRAGState):
    # 앞의 검색 노드가 반환한 결과를 읽습니다.
    print("[답변 노드] 검색 결과:", state["results"])

    # 실제 LLM 대신 고정된 답변을 준비합니다.
    answer = "검색 결과를 바탕으로 만든 답변입니다."

    # LangGraph가 answer 항목을 갱신하도록 반환합니다.
    return {"answer": answer}

# StateGraph(상태 타입) 로 빈 그래프를 만든다.
graph = StateGraph(SimpleRAGState)

# 노드 이름과 함수를 등록한다. 엣지는 이름이 아니라 이 이름을 가리킨다.
graph.add_node("search", search_node)
graph.add_node("answer", answer_node)

# 엣지: START 다음 search, search 다음 answer, answer 다음 END.
graph.add_edge(START, "search")
graph.add_edge("search", "answer")
graph.add_edge("answer", END)

# compile: 위 연결을 실행 가능한 앱으로 확정한다.
app = graph.compile()

initial_state = {
    "query": "가격이 얼마야?",
    "results": [],
    "answer": ""
}

# invoke: 초기 State를 넣고 그래프를 끝까지 실행한다.
result = app.invoke(initial_state)

print("\n===== 최종 결과 =====")
print("질문:", result["query"])
print("검색 결과:", result["results"])
print("답변:", result["answer"])

실행 결과 샘플 (교안 기록. API 키가 필요한 코드는 키 없이 맞춘 예시이고, 모델의 답은 실행할 때마다 달라집니다)

출력
===== 실행 결과 샘플 =====
실행: cd 실습/src/17
      python simple_graph.py
(실제 실행 출력)
[검색 노드] 질문: 가격이 얼마야?
[답변 노드] 검색 결과: ['문서1: 제품 가격은 10,000원입니다.', '문서2: 배송비는 무료입니다.', '문서3: 현재 할인 행사가 진행 중입니다.']

===== 최종 결과 =====
질문: 가격이 얼마야?
검색 결과: ['문서1: 제품 가격은 10,000원입니다.', '문서2: 배송비는 무료입니다.', '문서3: 현재 할인 행사가 진행 중입니다.']
답변: 검색 결과를 바탕으로 만든 답변입니다.
경로: search → answer → END

코드 해설

검색 노드와 답변 노드를 직선으로 잇는 가장 작은 그래프.입니다. 입력은 State 키 query, results, answer이고, 출력은 results 와 answer 가 채워진 같은 State입니다.

주석에 적힌 흐름입니다.

출력
흐름
START
|
v
search      읽기 query / 쓰기 results
|
v
answer      읽기 results / 쓰기 answer
|
v
END

코드 앞에서 고정해 두는 말입니다.

  • 17차시 | LangGraph 입문
  • 교안: 17차시_LangGraph입문.pptx
  • State = 노드들이 함께 쓰는 공용 딕셔너리. 공유 노트.
  • 노드는 노트 전체를 다시 쓰지 않고, 고친 키만 return 한다.
  • Node = State를 인자로 받는 함수.
  • Edge = 다음에 실행할 노드. add_edge(지금, 다음).
  • START = 시작 지점. END = 더 이상 갈 노드가 없음.
  • compile = 노드와 엣지를 묶어 실행 앱(app)으로 확정.

함수와 클래스로 나누어 읽습니다.

  • SimpleRAGState: 이름 그대로 한 가지 일만 합니다. 본문 주석을 같이 읽습니다.
  • search_node: 노드 search 읽기: query 쓰기: results
  • answer_node: 노드 answer 읽기: results 쓰기: answer

파일 맨 아래 실행 결과 샘플 주석은 실행되지 않습니다. 화면 출력은 다음 절에 다시 적어 두었습니다.

실습 코드 · 17/conditional_graph.py

실습 코드: 실습/src/17/conditional_graph.py · 위의 원본 PDF 17차시 8쪽 「Step 3: Graph 만들기」과 이어집니다.

Python
# ============================================================
# 17차시 | LangGraph 입문
# 교안: 17차시_LangGraph입문.pptx
# 역할: 검색 결과가 있으면 답변으로, 없으면 검색으로 돌아가는 갈림길.
# 입력: State 키 query, results, answer
# 출력: 결과가 있으면 answer 가 채워진 State.
#       이 예제는 질문이 '가격'을 포함하지 않아 결과가 비고, 제한에서 멈춘다.
#
#   State  = 공유 노트(딕셔너리). 노드는 고친 키만 return 한다.
#   Node   = State를 읽고 쓰는 함수. 여기의 노드는 search, answer.
#   Edge   = 다음 노드가 정해진 연결.
#   조건부 엣지 = 함수가 State를 보고 다음에 갈 노드 이름을 고른다.
#            add_conditional_edges(출발노드, 고르는함수, {반환값: 다음노드})
#   START / END = 시작과 끝.
#   compile = 연결 확정. invoke = 초기 State로 끝까지 실행.
#   recursion_limit = 같은 구간을 너무 많이 돌면 멈추는 상한.
#
# 흐름
#   START
#     |
#     v
#   search ---- bad (results 가 빔) ----+
#     |                                  |
#     | good (results 가 있음)           |
#     v                                  |
#   answer                               |
#     |                                  v
#     v                               search (다시)
#    END
# ============================================================

from typing import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.errors import GraphRecursionError

class SimpleRAGState(TypedDict):
    query: str      # 사용자의 질문
    results: list   # 검색 결과 목록
    answer: str     # 최종 답변

# 노드 search
#   읽기: query
#   쓰기: results
def search_node(state: SimpleRAGState):
    query = state["query"]
    # 현재 State에서 질문을 읽습니다.
    print("[검색 노드] 질문:", state["query"])

    # 실제 검색 대신 고정된 문서 목록을 준비합니다.
    if "가격" in query:
        results = [
            "문서1: 제품 가격은 10,000원입니다.",
            "문서2: 배송비는 무료입니다.",
            "문서3: 현재 할인 행사가 진행 중입니다."
        ]
    else:
        results = []

    print("[검색] 결과:", results)

    # LangGraph가 results 항목을 갱신하도록 반환합니다.
    return {"results": results}

# 노드 answer
#   읽기: results
#   쓰기: answer
def answer_node(state: SimpleRAGState):
    # 앞의 검색 노드가 반환한 결과를 읽습니다.
    print("[답변 노드] 검색 결과:", state["results"])

    # 실제 LLM 대신 고정된 답변을 준비합니다.
    answer = "검색 결과를 바탕으로 만든 답변입니다."

    # LangGraph가 answer 항목을 갱신하도록 반환합니다.
    return {"answer": answer}

# 조건부 엣지 함수. 노드가 아니다. State를 고치지 않고 다음 이름만 반환한다.
#   읽기: results
#   쓰기: 없음
#   "good" → answer, "bad" → search
def check_search_quality(state: SimpleRAGState):
    # 검색 결과가 하나 이상이면 답변 경로를 선택합니다.
    if len(state["results"]) > 0:
        print("[판단] 검색 성공 → 답변")
        return "good"

    # 검색 결과가 없으면 재검색 경로를 선택합니다.
    print("[판단] 검색 실패 → 다시 검색")
    return "bad"

graph = StateGraph(SimpleRAGState)

graph.add_node("search", search_node)
graph.add_node("answer", answer_node)

graph.add_edge(START, "search")

# 조건부 엣지: search 가 끝난 뒤 check_search_quality 의 반환값으로 다음 노드를 고른다.
graph.add_conditional_edges(
    "search",
    check_search_quality,
    {
        "good": "answer",  # 성공하면 답변 노드로 이동
        "bad": "search"    # 실패하면 검색 노드로 돌아감
    }
)

graph.add_edge("answer", END)

# compile: 갈림길까지 포함해 실행 앱으로 확정한다.
app = graph.compile()

initial_state = {
    "query": "환불은 되니?",
    "results": [],
    "answer": ""
}

try:
    # invoke 두 번째 인자는 실행 설정. recursion_limit 은 반복 상한이다.
    result = app.invoke(initial_state, {"recursion_limit" : 10})

    print("\n===== 최종 결과 =====")
    print("질문:", result["query"])
    print("검색 결과:", result["results"])
    print("답변:", result["answer"])

except GraphRecursionError:
    print("검색이 반복되어 실행 제한에 도달했습니다.")

실행 결과 샘플 (교안 기록. API 키가 필요한 코드는 키 없이 맞춘 예시이고, 모델의 답은 실행할 때마다 달라집니다)

출력
===== 실행 결과 샘플 =====
실행: cd 실습/src/17
      python conditional_graph.py
(실제 실행 출력)
[검색 노드] 질문: 환불은 되니?
[검색] 결과: []
[판단] 검색 실패 → 다시 검색
[검색 노드] 질문: 환불은 되니?
[검색] 결과: []
[판단] 검색 실패 → 다시 검색
[검색 노드] 질문: 환불은 되니?
[검색] 결과: []
[판단] 검색 실패 → 다시 검색
[검색 노드] 질문: 환불은 되니?
[검색] 결과: []
[판단] 검색 실패 → 다시 검색
[검색 노드] 질문: 환불은 되니?
[검색] 결과: []
[판단] 검색 실패 → 다시 검색
[검색 노드] 질문: 환불은 되니?
[검색] 결과: []
[판단] 검색 실패 → 다시 검색
[검색 노드] 질문: 환불은 되니?
[검색] 결과: []
[판단] 검색 실패 → 다시 검색
[검색 노드] 질문: 환불은 되니?
[검색] 결과: []
[판단] 검색 실패 → 다시 검색
[검색 노드] 질문: 환불은 되니?
[검색] 결과: []
[판단] 검색 실패 → 다시 검색
[검색 노드] 질문: 환불은 되니?
[검색] 결과: []
[판단] 검색 실패 → 다시 검색
검색이 반복되어 실행 제한에 도달했습니다.
경로: search → search → … (bad 반복, END 미도달)

코드 해설

검색 결과가 있으면 답변으로, 없으면 검색으로 돌아가는 갈림길.입니다. 입력은 State 키 query, results, answer이고, 출력은 결과가 있으면 answer 가 채워진 State.

주석에 적힌 흐름입니다.

출력
흐름
START
|
v
search ---- bad (results 가 빔) ----+
|                                  |
| good (results 가 있음)           |
v                                  |
answer                               |
|                                  v

코드 앞에서 고정해 두는 말입니다.

  • 17차시 | LangGraph 입문
  • 교안: 17차시_LangGraph입문.pptx
  • 이 예제는 질문이 '가격'을 포함하지 않아 결과가 비고, 제한에서 멈춘다.
  • State = 공유 노트(딕셔너리). 노드는 고친 키만 return 한다.
  • Node = State를 읽고 쓰는 함수. 여기의 노드는 search, answer.
  • Edge = 다음 노드가 정해진 연결.
  • 조건부 엣지 = 함수가 State를 보고 다음에 갈 노드 이름을 고른다.
  • add_conditional_edges(출발노드, 고르는함수, {반환값: 다음노드})

함수와 클래스로 나누어 읽습니다.

  • SimpleRAGState: 이름 그대로 한 가지 일만 합니다. 본문 주석을 같이 읽습니다.
  • search_node: 노드 search 읽기: query 쓰기: results
  • answer_node: 노드 answer 읽기: results 쓰기: answer
  • check_search_quality: 조건부 엣지 함수. 노드가 아니다. State를 고치지 않고 다음 이름만 반환한다. 읽기: results 쓰기: 없음 "good" → answer, "bad" → search

파일 맨 아래 실행 결과 샘플 주석은 실행되지 않습니다. 화면 출력은 다음 절에 다시 적어 두었습니다.

Step 4: Graph 실행하기

출처: 원본 PDF 9쪽

Python
# Graph 컴파일 (준비하기)
runnable = graph.compile()

# Graph 실행
input_state = {"query": "가격이 얼마야?"}
output_state = runnable.invoke(input_state)

# 결과 보기
print("질문:", output_state["query"])
print("검색결과:", output_state["results"])
print("답변:", output_state["answer"])

실행 결과 예시 (코드를 읽고 만든 예상 화면입니다. 실제 실행 결과, 특히 모델의 답은 다를 수 있습니다)

출력
질문: 가격이 얼마야?
검색결과: ['문서1', '문서2', '문서3']
답변: 검색 결과를 바탕으로 만든 답변입니다
← 이전 절17차시 · State = "노드들이 공유하는 메모판"다음 절 →17차시 · 조건부 분기가 있는 Graph 만들기