우와한 개발자

[LangChain] LangGraph로 Multi-Agent 구현하기 — Supervisor, Planner, Researcher, Writer, Reviewer

by 우와한개발자

1. Multi-Agent

1) 개념

  • Multi-Agent는 여러 개의 자율적인 AI Agent가 역할을 나누어 협력하는 아키텍처이다.
  • 하나의 Agent가 모든 작업을 처리하는 것이 아니라, Planner, Researcher, Writer, Reviewer처럼 역할별 Agent가 각자 맡은 작업을 수행한다.
  • 복잡한 작업을 분해하고, 각 단계의 결과를 연결하여 최종 결과물을 만든다.

2) 사용하는 이유

  • 복잡한 작업을 역할별로 나누어 처리할 수 있다.
  • 각 Agent의 책임이 명확해져 프롬프트와 로직을 설계하기 쉽다.
  • 중간 결과를 검토하고 수정하는 피드백 루프를 만들 수 있다.
  • 조사, 작성, 검토처럼 성격이 다른 작업을 각각의 Agent에 최적화할 수 있다.

 

2. Supervisor Pattern

1) 개념

  • Supervisor Pattern은 상위 Agent가 여러 하위 Agent를 조정하고 제어하는 구조이다.
  • Supervisor는 사용자 요청을 분석하고, 어떤 Agent에게 어떤 작업을 맡길지 결정한다.
  • 각 Agent의 실행 결과를 모아 전체 프로세스를 관리하고 최종 결과를 통합한다.

2) 역할

  • 사용자 요청 분석
  • 작업 분배
  • Agent 실행 순서 제어
  • 중간 결과 수집
  • 결과 통합
  • 필요 시 재작업 요청
사용자 요청 → Supervisor → Planner / Researcher / Writer / Reviewer → 최종 결과

 

 

3. 주요 Agent 역할

1) Planner Agent

  • 사용자의 요청을 분석하고 전체 작업의 목표와 범위를 명확히 정의한다.
  • 복잡한 작업을 작은 하위 작업으로 분해한다.
  • 작업 순서와 의존관계를 설계한다.
  • 실행 중 상황이 바뀌면 계획을 수정하거나 작업을 재분배한다.

2) Research Agent

  • Planner가 설계한 단계에 따라 필요한 정보를 수집하고 분석한다.
  • 외부 검색, 문서 조회, DB 조회, RAG 검색 등을 수행할 수 있다.
  • 수집한 정보를 요약하고, Writer Agent가 사용할 수 있는 형태로 정리한다.

3) Writer Agent

  • Planner와 Research Agent의 결과를 바탕으로 최종 산출물을 작성한다.
  • 보고서, 요약문, 기획서, 답변 초안 등 구조화된 글을 생성한다.
  • Writer Agent는 결과물을 짧게 생성하는 경향이 있을 수 있으므로, 프롬프트에서 분량, 구조, 포함할 항목을 구체적으로 지시하는 것이 중요하다.

4) Reviewer Agent

  • 생성된 결과물의 품질과 정확성을 검증하는 최종 관문 역할을 한다.
  • 내용의 사실성, 출처 일치 여부, 논리적 일관성, 누락된 요구사항을 점검한다.
  • 문제가 발견되면 Writer Agent 또는 Research Agent에게 수정 요청을 보낸다.

 

4. Multi-Agent Graph

1) 개념

  • Multi-Agent Graph는 여러 Agent의 실행 흐름을 그래프 구조로 표현한 것이다.
  • 각 Agent는 하나의 노드로 구성될 수 있고, Agent 간 결과 전달은 엣지로 표현할 수 있다.
  • LangGraph를 사용하면 Multi-Agent 흐름을 State, Node, Edge 기반으로 명확하게 설계할 수 있다.

2) 기본 흐름

START
→ Planner Agent
→ Research Agent
→ Writer Agent
→ Reviewer Agent
→ END

3) 피드백 루프가 있는 흐름

START
→ Planner Agent
→ Research Agent
→ Writer Agent
→ Reviewer Agent
→ 통과 시 END
→ 수정 필요 시 Writer Agent 또는 Research Agent로 재요청

4) 예시 구조

사용자 요청
→ Planner Agent: 작업 분해
→ Research Agent: 정보 수집
→ Writer Agent: 결과물 작성
→ Reviewer Agent: 품질 검토
→ Supervisor: 최종 결과 통합
→ 사용자 응답

 

5. LangGraph로 Multi-Agent 구현 예시

1) 예제 개요

  • 각 역할별 Agent를 별도로 생성한다.
  • Planner Agent, Researcher Agent, Writer Agent, Reviewer Agent마다 서로 다른 system prompt를 설정한다.
  • LangGraph의 각 Node에서는 직접 LLM을 호출하지 않고, 미리 생성한 Agent를 invoke한다.
  • 각 Agent의 결과를 State에 저장하고 다음 노드로 전달한다.
START
→ planner_node
→ researcher_node
→ writer_node
→ reviewer_node
→ END

2) 전체 코드

from typing import TypedDict
from dotenv import load_dotenv

from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent

load_dotenv()

class ReportState(TypedDict):
    topic: str
    plan: str
    research: str
    draft: str
    review: str

model = ChatOpenAI(
    model="gpt-4o-mini",
    temperature=0
)

planner_agent = create_agent(
    model=model,
    tools=[],
    system_prompt="""
당신은 보고서 기획 전문 Planner Agent입니다.

역할:
- 사용자의 주제를 분석합니다.
- 보고서의 목표와 범위를 정의합니다.
- 목차를 구조적으로 작성합니다.
- 각 목차에서 다룰 핵심 내용을 간단히 제안합니다.

출력 형식:
1. 보고서 목적
2. 목차
3. 각 장별 핵심 내용
"""
)

researcher_agent = create_agent(
    model=model,
    tools=[],
    system_prompt="""
당신은 조사와 분석을 담당하는 Research Agent입니다.

역할:
- Planner Agent가 만든 목차를 기반으로 조사 내용을 작성합니다.
- 각 목차에 들어갈 핵심 개념, 배경, 고려사항을 정리합니다.
- Writer Agent가 보고서를 작성할 수 있도록 충분한 근거와 내용을 제공합니다.

출력 형식:
1. 조사 요약
2. 장별 조사 내용
3. 보고서에 포함하면 좋은 핵심 포인트
"""
)

writer_agent = create_agent(
    model=model,
    tools=[],
    system_prompt="""
당신은 보고서 작성 전문 Writer Agent입니다.

역할:
- Research Agent가 정리한 조사 내용을 바탕으로 보고서를 작성합니다.
- 문장을 자연스럽게 연결하고, 보고서 형식에 맞게 구조화합니다.
- 내용이 너무 짧아지지 않도록 각 장을 충분히 설명합니다.

출력 형식:
1. 제목
2. 서론
3. 본론
4. 결론
"""
)

reviewer_agent = create_agent(
    model=model,
    tools=[],
    system_prompt="""
당신은 결과물 검토를 담당하는 Reviewer Agent입니다.

역할:
- Writer Agent가 작성한 보고서를 검토합니다.
- 내용의 정확성, 논리적 흐름, 누락된 항목, 개선할 표현을 점검합니다.
- 최종 개선사항을 구체적으로 제안합니다.

출력 형식:
1. 전체 평가
2. 잘된 점
3. 개선이 필요한 점
4. 수정 제안
"""
)

def get_agent_answer(result: dict) -> str:
    """Agent 실행 결과에서 마지막 메시지 content를 추출한다."""
    return result["messages"][-1].content

def planner_node(state: ReportState) -> ReportState:
    result = planner_agent.invoke({
        "messages": [
            {
                "role": "user",
                "content": f"다음 주제로 보고서 목차를 작성하시오.\n\n주제: {state['topic']}"
            }
        ]
    })

    return {
        "plan": get_agent_answer(result)
    }

def researcher_node(state: ReportState) -> ReportState:
    result = researcher_agent.invoke({
        "messages": [
            {
                "role": "user",
                "content": f"다음 목차를 기반으로 조사 내용을 작성하시오.\n\n{state['plan']}"
            }
        ]
    })

    return {
        "research": get_agent_answer(result)
    }

def writer_node(state: ReportState) -> ReportState:
    result = writer_agent.invoke({
        "messages": [
            {
                "role": "user",
                "content": f"아래 조사 내용을 바탕으로 보고서를 작성하시오.\n\n{state['research']}"
            }
        ]
    })

    return {
        "draft": get_agent_answer(result)
    }

def reviewer_node(state: ReportState) -> ReportState:
    result = reviewer_agent.invoke({
        "messages": [
            {
                "role": "user",
                "content": f"아래 보고서를 검토하고 개선사항을 제안하시오.\n\n{state['draft']}"
            }
        ]
    })

    return {
        "review": get_agent_answer(result)
    }

builder = StateGraph(ReportState)

builder.add_node("planner", planner_node)
builder.add_node("researcher", researcher_node)
builder.add_node("writer", writer_node)
builder.add_node("reviewer", reviewer_node)

builder.add_edge(START, "planner")
builder.add_edge("planner", "researcher")
builder.add_edge("researcher", "writer")
builder.add_edge("writer", "reviewer")
builder.add_edge("reviewer", END)

graph = builder.compile()

result = graph.invoke({
    "topic": "공공기관 생성형 AI 활용 전략",
    "plan": "",
    "research": "",
    "draft": "",
    "review": ""
})

print("===== 목차 =====")
print(result["plan"])

print("===== 조사 내용 =====")
print(result["research"])

print("===== 보고서 초안 =====")
print(result["draft"])

print("===== 검토 결과 =====")
print(result["review"])

3) 코드 설명

  • planner_agent, researcher_agent, writer_agent, reviewer_agent를 각각 따로 생성한다.
  • 각 Agent는 같은 모델을 사용할 수 있지만, 서로 다른 system prompt를 가진다.
  • system prompt가 다르기 때문에 각 Agent는 서로 다른 역할처럼 동작한다.
  • 노드 함수에서는 직접 llm.invoke()를 호출하지 않고, 해당 역할의 Agent를 invoke()한다.
  • 각 노드는 Agent 실행 결과를 State의 특정 필드에 저장한다.
planner_node → plan 저장
researcher_node → research 저장
writer_node → draft 저장
reviewer_node → review 저장

4) 기존 코드와의 차이

구분 기존 코드 Agent 분리 코드
호출 방식 노드에서 직접 llm.invoke() 호출 노드에서 역할별 Agent 호출
역할 분리 prompt만 다름 Agent 단위로 역할 분리
system prompt 각 노드의 prompt에 포함 Agent 생성 시 system_prompt로 고정
확장성 단순한 순차 워크플로우에 적합 Multi-Agent 구조 설명에 적합
도구 사용 각 노드에서 직접 처리 Agent별로 tools를 다르게 줄 수 있음

5) 이 구조의 장점

  • 각 Agent의 역할이 명확해진다.
  • Agent별 system prompt를 따로 관리할 수 있다.
  • Research Agent에는 검색 Tool, Writer Agent에는 문서 작성 Tool처럼 역할별 도구를 다르게 줄 수 있다.
  • LangGraph는 전체 실행 흐름을 관리하고, 각 Agent는 자신의 역할에 집중한다.
LangGraph = 전체 워크플로우 제어
각 Agent = 역할별 작업 수행

블로그의 정보

우와한개발자 님의 블로그

우와한개발자

활동하기