한국어
Week 2: 추론
04. 구조화된 추론 (Graph)

04. 구조화된 추론 & LangGraph

개요

이번 세션에서는 선형적 사고를 넘어서는 구조화된 추론 패턴을 탐구합니다:

패턴설명사용 사례
Decomposition복잡한 문제를 하위 작업으로 분해다단계 질문
Tree of Thoughts여러 추론 경로 탐색창의적/전략적 작업
LangGraph에이전트 워크플로우용 상태 머신복잡한 에이전트 오케스트레이션

Part 1: Decomposition (Least-to-Most)

복잡한 쿼리에 직면했을 때 LLM은 혼란스러워질 수 있습니다. 쿼리를 별개의 하위 질문으로 분해하는 것이 핵심입니다.

구현

from pydantic import BaseModel, Field
from typing import List
 
class SubQueries(BaseModel):
    queries: List[str] = Field(description="원래 쿼리를 해결하기 위한 하위 질문들")
 
def decompose_query(query: str) -> List[str]:
    """복잡한 쿼리를 하위 쿼리로 분해합니다."""
    completion = client.beta.chat.completions.parse(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "복잡한 문제를 3-4개의 순차적 하위 질문으로 분해하세요."},
            {"role": "user", "content": query}
        ],
        response_format=SubQueries
    )
    return completion.choices[0].message.parsed.queries

예시

복잡한 쿼리: "일론 머스크가 태어난 나라의 현재 대통령은 누구이며, 임기는 언제 끝나나요?"

분해된 하위 질문:

  1. 일론 머스크는 어느 나라에서 태어났나요?
  2. 그 나라의 현재 대통령은 누구인가요?
  3. 그 나라의 대통령 임기는 얼마인가요?
  4. 현재 대통령의 임기는 언제 끝나나요?

순차적 해결

def solve_decomposed_queries(original_query: str):
    sub_qs = decompose_query(original_query)
    context = ""
 
    for i, q in enumerate(sub_qs):
        print(f"[Step {i+1}] Q: {q}")
        answer = answer_question(q, context)
        print(f"  ✅ A: {answer}")
        context += f"Q: {q}\nA: {answer}\n\n"
 
    # 최종 답변 종합
    final_answer = answer_question(f"답변: '{original_query}'", context)
    return final_answer

Part 2: Tree of Thoughts (ToT)

Tree of Thoughts는 여러 추론 경로를 동시에 탐색합니다—체스에서 여러 수를 고려한 후 선택하는 것처럼.

ToT 구성요소

Proposer (제안자)

N개의 가능한 다음 단계/생각 생성

Evaluator (평가자)

각 후보에 점수 부여 (1-10 척도)

Selector (선택자)

최선의 후보 선택 (그리디 또는 빔 검색)

구현

# 1. Proposer: 후보 연속 생성
def propose_next_steps(current_state: str, n: int = 3) -> List[str]:
    prompt = f"""현재 상태: {current_state}
 
{n}개의 가능한 다음 단계를 제안하세요. 1, 2, 3으로 번호를 매기세요.
"""
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}]
    )
    # 번호가 매겨진 응답 파싱
    lines = response.choices[0].message.content.strip().split("\n")
    return [line.split(". ", 1)[1] for line in lines if ". " in line][:n]
 
# 2. Evaluator: 후보 점수 부여
def evaluate_candidates(candidates: List[str], goal: str) -> List[int]:
    scores = []
    for cand in candidates:
        prompt = f"""목표: {goal}
후보: {cand}
 
관련성과 품질을 1-10점으로 평가하세요. 숫자만 반환하세요.
"""
        response = client.chat.completions.create(...)
        scores.append(int(response.choices[0].message.content.strip()))
    return scores
 
# 3. ToT 검색 루프
def run_tot(goal: str, steps: int = 3):
    current_state = ""
 
    for i in range(steps):
        candidates = propose_next_steps(current_state)
        scores = evaluate_candidates(candidates, goal)
 
        # 최선의 후보 선택
        best_idx = scores.index(max(scores))
        current_state += candidates[best_idx] + " "
 
    return current_state

ToT vs 그리디

접근법방법장점단점
그리디항상 최고 점수 선택빠름, 단순함지역 최적해에 갇힐 수 있음
빔 검색상위 k개 후보 유지더 많은 경로 탐색더 높은 계산 비용
백트래킹점수가 낮으면 뒤로 돌아감나쁜 선택에서 복구 가능더욱 높은 비용

Part 3: LangGraph

LangGraph는 에이전트 로직을 시각적 상태 머신으로 변환하여 복잡한 워크플로우를 이해하고 유지보수하기 쉽게 만듭니다.

왜 LangGraph인가?

기능Python 루프LangGraph
시각화코드만시각적 그래프
상태 관리수동내장 TypedDict
조건부 로직if/else조건부 엣지
디버깅print 문그래프 검사
체크포인팅수동내장

핵심 개념

from langgraph.graph import StateGraph
from typing import TypedDict, List
 
# 1. 상태 스키마 정의
class AgentState(TypedDict):
    query: str
    plan: List[str]
    results: List[str]
    done: bool
 
# 2. 그래프 생성
graph = StateGraph(AgentState)
 
# 3. 노드 추가
graph.add_node("planner", planner_node)
graph.add_node("executor", executor_node)
graph.add_node("checker", checker_node)
 
# 4. 엣지 추가
graph.add_edge("planner", "executor")
graph.add_edge("executor", "checker")
 
# 5. 조건부 엣지 추가
graph.add_conditional_edges(
    "checker",
    should_replan,  # 다음 노드 이름을 반환하는 함수
    {
        "replan": "planner",
        "done": END
    }
)
 
# 6. 컴파일 및 실행
app = graph.compile()
result = app.invoke({"query": "...", "plan": [], "results": [], "done": False})

예시: LangGraph로 Plan-and-Execute

def planner_node(state: AgentState) -> AgentState:
    """실행 계획 생성"""
    plan = create_plan(state["query"])
    return {"plan": plan}
 
def executor_node(state: AgentState) -> AgentState:
    """계획의 다음 단계 실행"""
    results = execute_step(state["plan"][0])
    remaining_plan = state["plan"][1:]
    return {"plan": remaining_plan, "results": state["results"] + [results]}
 
def checker_node(state: AgentState) -> AgentState:
    """완료 여부 확인"""
    done = len(state["plan"]) == 0
    return {"done": done}
 
def should_replan(state: AgentState) -> str:
    if state["done"]:
        return "done"
    return "continue"

실습 내용

노트북에서 다음을 수행합니다:

Decomposition 구현

복잡한 쿼리를 하위 질문으로 분해하고 순차적으로 해결

Tree of Thoughts 구축

여러 경로를 탐색하는 창의적 글쓰기 에이전트 생성

LangGraph 기초

간단한 루프를 상태 그래프로 변환

재계획 추가

동적 계획 조정을 위한 조건부 엣지 구현

핵심 요점

  1. Decomposition이 단순화 - 문제를 부분으로 나누면 다루기 쉬워짐
  2. ToT가 너비 탐색 - 여러 경로가 창의적 작업에서 더 나은 해결책을 찾음
  3. LangGraph가 구조 추가 - 상태 머신이 에이전트 로직을 명시적이고 디버깅 가능하게 만듦
  4. 패턴 결합 - ToT 내에서 decomposition 사용, 또는 LangGraph로 둘 다 오케스트레이션

참고 자료 & 추가 학습

학술 논문

관련 도구

다음 단계

구조화된 추론을 이해했으니, Advanced Self-RAG로 이동하여 자가 수정이 가능한 검색 증강 생성을 배워보세요!