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예시
복잡한 쿼리: "일론 머스크가 태어난 나라의 현재 대통령은 누구이며, 임기는 언제 끝나나요?"
분해된 하위 질문:
- 일론 머스크는 어느 나라에서 태어났나요?
- 그 나라의 현재 대통령은 누구인가요?
- 그 나라의 대통령 임기는 얼마인가요?
- 현재 대통령의 임기는 언제 끝나나요?
순차적 해결
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_answerPart 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_stateToT 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 기초
간단한 루프를 상태 그래프로 변환
재계획 추가
동적 계획 조정을 위한 조건부 엣지 구현
핵심 요점
- Decomposition이 단순화 - 문제를 부분으로 나누면 다루기 쉬워짐
- ToT가 너비 탐색 - 여러 경로가 창의적 작업에서 더 나은 해결책을 찾음
- LangGraph가 구조 추가 - 상태 머신이 에이전트 로직을 명시적이고 디버깅 가능하게 만듦
- 패턴 결합 - ToT 내에서 decomposition 사용, 또는 LangGraph로 둘 다 오케스트레이션
참고 자료 & 추가 학습
학술 논문
-
"Tree of Thoughts: Deliberate Problem Solving with Large Language Models" - Yao et al., 2023
- arXiv:2305.10601 (opens in a new tab)
- 트리 기반 추론의 기초
-
"Least-to-Most Prompting Enables Complex Reasoning in Large Language Models" - Zhou et al., 2023
- arXiv:2205.10625 (opens in a new tab)
- 복잡한 추론을 위한 분해
-
"Graph of Thoughts: Solving Elaborate Problems with Large Language Models" - Besta et al., 2023
- arXiv:2308.09687 (opens in a new tab)
- ToT를 임의의 그래프 구조로 확장
-
"Language Agent Tree Search" - Zhou et al., 2023
- arXiv:2310.04406 (opens in a new tab)
- 언어 에이전트를 위한 MCTS 스타일 검색
관련 도구
- LangGraph: GitHub (opens in a new tab)
- AutoGen: GitHub (opens in a new tab)
- DSPy: GitHub (opens in a new tab)
다음 단계
구조화된 추론을 이해했으니, Advanced Self-RAG로 이동하여 자가 수정이 가능한 검색 증강 생성을 배워보세요!