워크플로우 패턴 (Workflow Patterns)
LangGraph에서 자주 사용되는 워크플로우 설계 패턴을 소개합니다. 각 패턴은 특정 유형의 문제를 효과적으로 해결합니다.프롬프트 체이닝 (Prompt Chaining)
하나의 작업을 여러 단계로 분해하여 순차적으로 실행합니다. 각 단계의 출력이 다음 단계의 입력이 됩니다.Copy
from typing import TypedDict
from langchain.chat_models import init_chat_model
from langgraph.graph import StateGraph, START, END
llm = init_chat_model("gpt-4o-mini", temperature=0)
class State(TypedDict):
topic: str
draft: str
review: str
final: str
def write_draft(state: State) -> State:
response = llm.invoke(f"'{state['topic']}'에 대해 블로그 초안을 작성하세요.")
return {"draft": response.content}
def review_draft(state: State) -> State:
response = llm.invoke(
f"다음 초안을 검토하고 개선점을 반영하여 수정하세요:\n\n{state['draft']}"
)
return {"review": response.content}
def format_final(state: State) -> State:
response = llm.invoke(
f"다음 글을 마크다운 형식으로 깔끔하게 포맷팅하세요:\n\n{state['review']}"
)
return {"final": response.content}
graph = StateGraph(State)
graph.add_node("draft", write_draft)
graph.add_node("review", review_draft)
graph.add_node("format", format_final)
graph.add_edge(START, "draft")
graph.add_edge("draft", "review")
graph.add_edge("review", "format")
graph.add_edge("format", END)
app = graph.compile()
result = app.invoke({"topic": "RAG의 미래"})
병렬화 (Parallelization)
독립적인 작업을 동시에 실행하여 처리 시간을 단축합니다.Send API 활용
Copy
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, START, END
from langgraph.types import Send
import operator
class State(TypedDict):
topics: list[str]
summaries: Annotated[list[str], operator.add]
class TopicState(TypedDict):
topic: str
def generate_summary(state: TopicState) -> dict:
response = llm.invoke(f"'{state['topic']}'을 한 문장으로 요약하세요.")
return {"summaries": [response.content]}
def fan_out(state: State):
return [Send("summarize", {"topic": t}) for t in state["topics"]]
graph = StateGraph(State)
graph.add_node("summarize", generate_summary)
graph.add_conditional_edges(START, fan_out, ["summarize"])
graph.add_edge("summarize", END)
app = graph.compile()
result = app.invoke({"topics": ["RAG", "LangGraph", "Vector DB"]})
# → 3개 주제가 병렬로 요약됨
라우팅 (Routing)
입력을 분석하여 적절한 처리 경로로 분기합니다.Copy
from typing import Literal
def router(state: State) -> Literal["technical", "general", "code"]:
"""질문 유형을 분류하여 라우팅"""
response = llm.invoke(
f"다음 질문을 분류하세요 (technical/general/code): {state['question']}"
)
category = response.content.strip().lower()
if "technical" in category:
return "technical"
elif "code" in category:
return "code"
return "general"
graph = StateGraph(State)
graph.add_node("technical", technical_handler)
graph.add_node("general", general_handler)
graph.add_node("code", code_handler)
graph.add_conditional_edges(START, router, {
"technical": "technical",
"general": "general",
"code": "code",
})
graph.add_edge("technical", END)
graph.add_edge("general", END)
graph.add_edge("code", END)
app = graph.compile()
오케스트레이터-워커 (Orchestrator-Worker)
중앙 오케스트레이터가 작업을 분배하고 조율합니다.Copy
class OrchestratorState(TypedDict):
task: str
subtasks: list[str]
results: Annotated[list[str], operator.add]
final_output: str
def orchestrator(state: OrchestratorState) -> OrchestratorState:
if not state.get("subtasks"):
# 작업 분해
response = llm.invoke(
f"다음 작업을 3개의 하위 작업으로 분해하세요:\n{state['task']}"
)
subtasks = response.content.strip().split("\n")
return {"subtasks": subtasks}
else:
# 결과 종합
combined = "\n\n".join(state["results"])
response = llm.invoke(f"다음 결과들을 종합하세요:\n{combined}")
return {"final_output": response.content}
평가자-최적화자 (Evaluator-Optimizer)
생성 결과를 평가하고 반복적으로 개선합니다.Copy
class EvalState(TypedDict):
task: str
output: str
feedback: str
score: float
iteration: int
def generator(state: EvalState) -> EvalState:
prompt = f"작업: {state['task']}"
if state.get("feedback"):
prompt += f"\n\n이전 피드백: {state['feedback']}\n이전 결과를 개선하세요."
response = llm.invoke(prompt)
return {"output": response.content, "iteration": state.get("iteration", 0) + 1}
def evaluator(state: EvalState) -> EvalState:
response = llm.invoke(
f"다음 결과를 1~10점으로 평가하고 피드백을 제공하세요:\n\n{state['output']}"
)
# 점수와 피드백 파싱
return {"score": 8.0, "feedback": response.content}
def should_continue(state: EvalState) -> str:
if state["score"] >= 8.0 or state["iteration"] >= 3:
return "end"
return "improve"
graph = StateGraph(EvalState)
graph.add_node("generate", generator)
graph.add_node("evaluate", evaluator)
graph.add_edge(START, "generate")
graph.add_edge("generate", "evaluate")
graph.add_conditional_edges("evaluate", should_continue, {
"improve": "generate",
"end": END,
})
app = graph.compile()
패턴 선택 가이드
| 패턴 | 적합한 경우 | 복잡도 |
|---|---|---|
| 프롬프트 체이닝 | 단계적 처리 (초안→검토→완성) | 낮음 |
| 병렬화 | 독립적 작업 동시 처리 | 중간 |
| 라우팅 | 입력 유형별 다른 처리 | 낮음 |
| 오케스트레이터-워커 | 복잡한 작업 분배와 조율 | 높음 |
| 평가자-최적화자 | 반복적 품질 개선 | 중간 |
시작 추천: 프롬프트 체이닝과 라우팅부터 시작하세요. 처리 시간이 중요하면 병렬화를, 품질이 중요하면 평가자-최적화자를 추가하세요.

