AI 프롬프트
코딩/개발

LangGraph 멀티 에이전트 사칙연산 계산기

중년개발자
중년개발자

@loxo

3일 전

7

LangGraph 멀티 에이전트 사칙연산 계산기

이 프로젝트는 LangGraph를 이용해 다음 구조를 구현합니다.

  • 사용자가 사칙연산 수식을 입력
  • Parser가 숫자와 연산자를 분석
  • Supervisor가 담당 Agent를 선택
  • 전문 Agent가 계산
  • Formatter가 최종 결과를 출력
    이 예제에서는 사칙연산을 각각 별도의 전문 Agent로 분리합니다.
text
덧셈 → Addition Agent 뺄셈 → Subtraction Agent 곱셈 → Multiplication Agent 나눗셈 → Division Agent 잘못된 입력 → Error Agent

1. 프로젝트 구조

먼저 다음과 같은 구조로 프로젝트를 만듭니다.

text
langgraph-multi-agent-calculator/ ├── app/ │ ├── agents/ │ │ ├── __init__.py │ │ └── arithmetic_agents.py │ ├── __init__.py │ ├── formatter.py │ ├── graph.py │ ├── main.py │ ├── parser.py │ ├── router.py │ └── state.py ├── tests/ │ └── test_calculator.py ├── .gitignore ├── requirements.txt └── README.md

각 파일의 역할은 다음과 같습니다.

파일역할
app/state.pyAgent들이 공유하는 State 정의
app/parser.py사용자가 입력한 수식 분석
app/router.py담당 Agent를 선택하는 Supervisor
app/agents/arithmetic_agents.py사칙연산 전문 Agent
app/formatter.py계산 결과를 사용자용 문장으로 변환
app/graph.pyLangGraph Node와 Edge 구성
app/main.py프로그램 실행 시작점
tests/test_calculator.py사칙연산 테스트
requirements.txt필요한 Python 라이브러리

2. 전체 실행 흐름

프로그램은 다음 순서로 동작합니다.

text
START Parser Node Supervisor Router ├─ add ─────────> Addition Agent ├─ subtract ────> Subtraction Agent ├─ multiply ────> Multiplication Agent ├─ divide ──────> Division Agent └─ unsupported ─> Error Agent Formatter Node END

예를 들어 사용자가 다음 수식을 입력합니다.

text
10 + 20

실행 과정은 다음과 같습니다.

text
1. Parser가 10, +, 20을 추출한다. 2. operation을 add로 저장한다. 3. Supervisor가 operation을 확인한다. 4. Addition Agent로 분기한다. 5. Addition Agent가 10 + 20을 계산한다. 6. Formatter가 결과를 문자열로 만든다. 7. 최종 결과 10 + 20 = 30을 출력한다.

3. 필요한 패키지

파일명: requirements.txt

text
langgraph>=1.0,<2.0 typing-extensions>=4.12

테스트까지 실행하려면 pytest도 추가할 수 있습니다.

text
langgraph>=1.0,<2.0 typing-extensions>=4.12 pytest>=8.0

4. Git 제외 파일 설정

파일명: .gitignore

gitignore
.venv/ __pycache__/ *.pyc .pytest_cache/

5. app 패키지 초기화

파일명: app/__init__.py

python
undefined

내용이 없는 빈 파일입니다.
Python이 app 디렉터리를 패키지로 인식하도록 만듭니다.

6. State 정의

파일명: app/state.py

python
from typing import Literal from typing_extensions import TypedDict Operation = Literal[ "add", "subtract", "multiply", "divide", "unsupported", ] class CalculatorState(TypedDict, total=False): """ LangGraph의 모든 Node가 공유하는 State. 각 Node는 이 State를 읽고 필요한 값을 추가하거나 변경한다. """ # 사용자가 입력한 원본 수식 expression: str # 수식의 왼쪽 숫자 left: float # 수식의 오른쪽 숫자 right: float # 수행할 연산 operation: Operation # 계산 결과 result: float # 사용자에게 보여줄 최종 답변 answer: str # 오류 메시지 error: str # 어떤 Agent를 거쳤는지 기록 visited_agents: list[str]

State란 무엇인가?

State는 LangGraph의 각 Node가 공유하는 데이터입니다.
예를 들어 사용자가 다음을 입력합니다.

text
10 + 20

Parser를 통과한 State는 다음과 비슷합니다.

python
{ "expression": "10 + 20", "left": 10.0, "right": 20.0, "operation": "add", "visited_agents": [ "parser" ] }

Addition Agent를 통과하면 다음 값이 추가됩니다.

python
{ "result": 30.0 }

Formatter를 통과하면 최종 답변이 추가됩니다.

python
{ "answer": "10 + 20 = 30" }

7. 사용자 수식 분석

파일명: app/parser.py

python
import re from app.state import CalculatorState, Operation OPERATOR_MAP: dict[str, Operation] = { "+": "add", "-": "subtract", "*": "multiply", "×": "multiply", "x": "multiply", "X": "multiply", "/": "divide", "÷": "divide", } def parse_expression_node( state: CalculatorState, ) -> CalculatorState: """ 사용자가 입력한 수식에서 숫자 2개와 연산자 1개를 추출한다. 지원 예: 10 + 20 30 - 5 7 * 8 100 / 4 -10 + 5 5.5 * 2 """ expression = state.get("expression", "").strip() pattern = ( r"^\s*" r"(-?\d+(?:\.\d+)?)" r"\s*" r"([+\-*/×÷xX])" r"\s*" r"(-?\d+(?:\.\d+)?)" r"\s*$" ) match = re.match(pattern, expression) if not match: return { "operation": "unsupported", "error": ( "수식을 해석할 수 없습니다. " "예: 10 + 20, 30 - 5, " "7 * 8, 100 / 4" ), "visited_agents": ["parser"], } left_text, operator, right_text = match.groups() return { "left": float(left_text), "right": float(right_text), "operation": OPERATOR_MAP[operator], "error": "", "visited_agents": ["parser"], }

Parser는 다음 역할만 담당합니다.

text
입력 문자열 분석 숫자 두 개 추출 연산자 추출 연산자를 operation 값으로 변환

연산자 변환 결과는 다음과 같습니다.

입력 연산자State의 operation
+add
-subtract
*, ×, xmultiply
/, ÷divide
Parser는 실제 계산을 하지 않습니다.
입력을 분석하고 State를 만드는 역할만 수행합니다.

8. Supervisor 분기 처리

파일명: app/router.py

python
from typing import Literal from app.state import CalculatorState RouteName = Literal[ "addition_agent", "subtraction_agent", "multiplication_agent", "division_agent", "error_agent", ] def supervisor_router( state: CalculatorState, ) -> RouteName: """ State의 operation 값을 확인하여 다음에 실행할 전문 Agent의 Node 이름을 반환한다. """ operation = state.get( "operation", "unsupported", ) route_map: dict[str, RouteName] = { "add": "addition_agent", "subtract": "subtraction_agent", "multiply": "multiplication_agent", "divide": "division_agent", } return route_map.get( operation, "error_agent", )

이 파일이 LangGraph 분기 처리의 핵심입니다.
일반적인 if 문으로 표현하면 다음과 같습니다.

python
def supervisor_router( state: CalculatorState, ) -> RouteName: operation = state.get("operation") if operation == "add": return "addition_agent" if operation == "subtract": return "subtraction_agent" if operation == "multiply": return "multiplication_agent" if operation == "divide": return "division_agent" return "error_agent"

Supervisor는 계산하지 않습니다.
다음에 어떤 Agent를 실행할지만 결정합니다.

text
Supervisor 업무를 직접 처리하지 않음 State를 확인 담당 전문 Agent 선택

9. agents 패키지 초기화

파일명: app/agents/__init__.py

python
undefined

내용이 없는 빈 파일입니다.

10. 사칙연산 전문 Agent

파일명: app/agents/arithmetic_agents.py

python
from app.state import CalculatorState def append_agent( state: CalculatorState, agent_name: str, ) -> list[str]: """ 현재까지 방문한 Agent 목록에 새로운 Agent 이름을 추가한다. """ return [ *state.get("visited_agents", []), agent_name, ] def addition_agent( state: CalculatorState, ) -> CalculatorState: """ 덧셈만 담당하는 전문 Agent. """ result = state["left"] + state["right"] return { "result": result, "visited_agents": append_agent( state, "addition_agent", ), } def subtraction_agent( state: CalculatorState, ) -> CalculatorState: """ 뺄셈만 담당하는 전문 Agent. """ result = state["left"] - state["right"] return { "result": result, "visited_agents": append_agent( state, "subtraction_agent", ), } def multiplication_agent( state: CalculatorState, ) -> CalculatorState: """ 곱셈만 담당하는 전문 Agent. """ result = state["left"] * state["right"] return { "result": result, "visited_agents": append_agent( state, "multiplication_agent", ), } def division_agent( state: CalculatorState, ) -> CalculatorState: """ 나눗셈만 담당하는 전문 Agent. """ if state["right"] == 0: return { "error": "0으로 나눌 수 없습니다.", "visited_agents": append_agent( state, "division_agent", ), } result = state["left"] / state["right"] return { "result": result, "visited_agents": append_agent( state, "division_agent", ), } def error_agent( state: CalculatorState, ) -> CalculatorState: """ 지원하지 않는 입력이나 연산을 처리하는 Agent. """ return { "error": ( state.get("error") or "지원하지 않는 연산입니다." ), "visited_agents": append_agent( state, "error_agent", ), }

각 Agent는 하나의 역할만 담당합니다.

text
addition_agent 덧셈만 수행 subtraction_agent 뺄셈만 수행 multiplication_agent 곱셈만 수행 division_agent 나눗셈만 수행 error_agent 잘못된 요청 처리

이렇게 역할을 분리하면 나중에 각 Agent를 별도의 Subgraph나 LLM Agent로 확장하기 쉽습니다.

11. 결과 문자열 생성

파일명: app/formatter.py

python
from app.state import CalculatorState OPERATION_SYMBOLS = { "add": "+", "subtract": "-", "multiply": "×", "divide": "÷", } def format_result_node( state: CalculatorState, ) -> CalculatorState: """ 전문 Agent의 계산 결과를 사용자에게 보여줄 문자열로 변환한다. """ visited_agents = [ *state.get("visited_agents", []), "formatter", ] if state.get("error"): return { "answer": f"오류: {state['error']}", "visited_agents": visited_agents, } operation = state["operation"] symbol = OPERATION_SYMBOLS[operation] result = state["result"] if float(result).is_integer(): display_result: int | float = int(result) else: display_result = result return { "answer": ( f"{state['left']:g} " f"{symbol} " f"{state['right']:g} " f"= {display_result}" ), "visited_agents": visited_agents, }

Formatter는 계산을 하지 않습니다.
전문 Agent가 계산한 결과를 보기 좋은 문자열로 만드는 역할만 담당합니다.

text
계산 결과 30.0 Formatter 10 + 20 = 30

12. LangGraph 구성

파일명: app/graph.py

python
from langgraph.graph import END, START, StateGraph from app.agents.arithmetic_agents import ( addition_agent, division_agent, error_agent, multiplication_agent, subtraction_agent, ) from app.formatter import format_result_node from app.parser import parse_expression_node from app.router import supervisor_router from app.state import CalculatorState def build_calculator_graph(): """ 사칙연산 멀티 에이전트 Graph를 생성한다. """ graph = StateGraph(CalculatorState) # ----------------------------------- # 1. Node 등록 # ----------------------------------- graph.add_node( "parser", parse_expression_node, ) graph.add_node( "addition_agent", addition_agent, ) graph.add_node( "subtraction_agent", subtraction_agent, ) graph.add_node( "multiplication_agent", multiplication_agent, ) graph.add_node( "division_agent", division_agent, ) graph.add_node( "error_agent", error_agent, ) graph.add_node( "formatter", format_result_node, ) # ----------------------------------- # 2. 시작 Node 연결 # ----------------------------------- graph.add_edge( START, "parser", ) # ----------------------------------- # 3. 조건부 분기 # ----------------------------------- graph.add_conditional_edges( "parser", supervisor_router, { "addition_agent": "addition_agent", "subtraction_agent": "subtraction_agent", "multiplication_agent": "multiplication_agent", "division_agent": "division_agent", "error_agent": "error_agent", }, ) # ----------------------------------- # 4. 전문 Agent 실행 후 Formatter 연결 # ----------------------------------- graph.add_edge( "addition_agent", "formatter", ) graph.add_edge( "subtraction_agent", "formatter", ) graph.add_edge( "multiplication_agent", "formatter", ) graph.add_edge( "division_agent", "formatter", ) graph.add_edge( "error_agent", "formatter", ) # ----------------------------------- # 5. 종료 연결 # ----------------------------------- graph.add_edge( "formatter", END, ) # 실행 가능한 Graph로 컴파일 return graph.compile()

이 파일에서 가장 중요한 부분은 다음 코드입니다.

python
graph.add_conditional_edges( "parser", supervisor_router, { "addition_agent": "addition_agent", "subtraction_agent": "subtraction_agent", "multiplication_agent": "multiplication_agent", "division_agent": "division_agent", "error_agent": "error_agent", }, )

각 인자의 의미는 다음과 같습니다.

text
"parser" 어느 Node가 끝난 뒤 분기할 것인지 지정 supervisor_router 분기 판단을 수행할 함수 마지막 dict Router 반환값과 실제 Node를 연결

예를 들어 Supervisor가 다음 값을 반환합니다.

python
return "multiplication_agent"

LangGraph는 다음 Node를 실행합니다.

text
multiplication_agent

13. 프로그램 실행 파일

파일명: app/main.py

python
from app.graph import build_calculator_graph calculator_graph = build_calculator_graph() def run( expression: str, ) -> None: """ 사용자 수식을 LangGraph에 전달하고 최종 결과를 출력한다. """ initial_state = { "expression": expression, "visited_agents": [], } final_state = calculator_graph.invoke( initial_state ) print() print(f"입력: {expression}") print(f"결과: {final_state['answer']}") visited_agents = final_state.get( "visited_agents", [], ) print( "처리 경로:", " -> ".join(visited_agents), ) def main() -> None: print("LangGraph 멀티 에이전트 계산기") print("종료하려면 exit를 입력하세요.") while True: expression = input( "\n수식 입력 > " ).strip() if expression.lower() in { "exit", "quit", }: print("프로그램을 종료합니다.") break run(expression) if __name__ == "__main__": main()

main.py에서는 Graph 내부 구조를 알 필요가 없습니다.
다음처럼 초기 State만 전달합니다.

python
initial_state = { "expression": "10 + 20", "visited_agents": [], }

그리고 invoke()를 호출합니다.

python
final_state = calculator_graph.invoke( initial_state )

LangGraph가 내부적으로 다음 과정을 실행합니다.

text
Parser Supervisor 전문 Agent Formatter END

14. 테스트 코드

파일명: tests/test_calculator.py

python
from app.graph import build_calculator_graph calculator = build_calculator_graph() def invoke( expression: str, ) -> dict: return calculator.invoke({ "expression": expression, "visited_agents": [], }) def test_addition(): result = invoke("10 + 20") assert result["result"] == 30 assert ( "addition_agent" in result["visited_agents"] ) def test_subtraction(): result = invoke("30 - 7") assert result["result"] == 23 assert ( "subtraction_agent" in result["visited_agents"] ) def test_multiplication(): result = invoke("7 * 8") assert result["result"] == 56 assert ( "multiplication_agent" in result["visited_agents"] ) def test_division(): result = invoke("100 / 4") assert result["result"] == 25 assert ( "division_agent" in result["visited_agents"] ) def test_division_by_zero(): result = invoke("10 / 0") assert result["answer"] == ( "오류: 0으로 나눌 수 없습니다." ) def test_invalid_expression(): result = invoke("hello") assert ( result["operation"] == "unsupported" ) assert ( "error_agent" in result["visited_agents"] )

15. 프로젝트 생성 방법

Windows

bash
mkdir langgraph-multi-agent-calculator cd langgraph-multi-agent-calculator python -m venv .venv .venv\Scripts\activate

macOS 또는 Linux

bash
mkdir langgraph-multi-agent-calculator cd langgraph-multi-agent-calculator python -m venv .venv source .venv/bin/activate

필요한 디렉터리를 생성합니다.

Windows PowerShell

powershell
mkdir app mkdir app\agents mkdir tests

macOS 또는 Linux

bash
mkdir -p app/agents mkdir -p tests

16. 패키지 설치

bash
pip install -r requirements.txt

정상적으로 설치되었는지 확인합니다.

bash
pip show langgraph

17. 프로그램 실행

반드시 프로젝트 최상위 디렉터리에서 실행합니다.

text
langgraph-multi-agent-calculator/

실행 명령은 다음과 같습니다.

bash
python -m app.main

python app/main.py가 아니라 다음 방식을 권장합니다.

bash
python -m app.main

패키지 경로 문제를 줄일 수 있기 때문입니다.

18. 실행 결과

덧셈

text
LangGraph 멀티 에이전트 계산기 종료하려면 exit를 입력하세요. 수식 입력 > 10 + 20 입력: 10 + 20 결과: 10 + 20 = 30 처리 경로: parser -> addition_agent -> formatter

뺄셈

text
수식 입력 > 30 - 7 입력: 30 - 7 결과: 30 - 7 = 23 처리 경로: parser -> subtraction_agent -> formatter

곱셈

text
수식 입력 > 7 * 8 입력: 7 * 8 결과: 7 × 8 = 56 처리 경로: parser -> multiplication_agent -> formatter

나눗셈

text
수식 입력 > 100 / 4 입력: 100 / 4 결과: 100 ÷ 4 = 25 처리 경로: parser -> division_agent -> formatter

0으로 나누기

text
수식 입력 > 10 / 0 입력: 10 / 0 결과: 오류: 0으로 나눌 수 없습니다. 처리 경로: parser -> division_agent -> formatter

잘못된 입력

text
수식 입력 > hello 입력: hello 결과: 오류: 수식을 해석할 수 없습니다. 예: 10 + 20, 30 - 5, 7 * 8, 100 / 4 처리 경로: parser -> error_agent -> formatter

19. 테스트 실행

bash
pytest

상세한 결과를 보려면 다음과 같이 실행합니다.

bash
pytest -v

정상적인 결과는 다음과 비슷합니다.

text
tests/test_calculator.py::test_addition PASSED tests/test_calculator.py::test_subtraction PASSED tests/test_calculator.py::test_multiplication PASSED tests/test_calculator.py::test_division PASSED tests/test_calculator.py::test_division_by_zero PASSED tests/test_calculator.py::test_invalid_expression PASSED

20. 이것이 진짜 멀티 에이전트인가?

엄밀하게 말하면 이 예제의 각 전문 Agent는 LLM을 사용하는 독립 Agent가 아니라 전문 역할을 담당하는 LangGraph Node입니다.
하지만 멀티 에이전트의 핵심 구조는 동일합니다.

text
Supervisor 담당 Agent 선택 전문 Agent 실행 결과 통합

현재 구조는 다음과 같습니다.

text
Supervisor Router Python 함수 기반 전문 Agent

실제 LLM 기반 구조는 다음과 같이 확장할 수 있습니다.

text
LLM Supervisor Addition LLM Agent Subtraction LLM Agent Multiplication LLM Agent Division LLM Agent

다만 사칙연산은 LLM을 사용할 필요가 없습니다.
일반 Python 계산이 더 정확하고 빠르고 저렴하기 때문입니다.
이 예제의 목적은 LLM 사용이 아니라 다음 원리를 이해하는 것입니다.

text
State 공유 Node 분리 Supervisor 분기 조건부 Edge 전문 Agent 실행 공통 Node로 합류

21. 실제 업무 멀티 에이전트로 확장하기

사칙연산 Agent를 업무 Agent로 바꾸면 다음과 같은 구조가 됩니다.

text
사용자 요청 LLM Supervisor ├─ DB 관련 요청 │ ↓ │ Database Agent ├─ 문서 관련 요청 │ ↓ │ Document Agent ├─ 보고서 요청 │ ↓ │ Report Agent └─ 메일 요청 Email Agent

현재 프로젝트와 비교하면 다음과 같습니다.

사칙연산 예제실제 업무 Agent
Addition AgentDatabase Agent
Subtraction AgentDocument Agent
Multiplication AgentReport Agent
Division AgentEmail Agent
Supervisor RouterLLM Supervisor
CalculatorStateBusinessAgentState
결국 핵심 구조는 동일합니다.
text
사용자 요청 분석 State에 분석 결과 저장 Supervisor가 담당 Agent 선택 전문 Agent 실행 결과를 State에 저장 최종 응답 생성

핵심 정리

LangGraph 분기 처리는 세 단계로 이해할 수 있습니다.

첫 번째: State에 판단 기준 저장

python
{ "operation": "add" }

두 번째: Router가 다음 Node 이름 반환

python
return "addition_agent"

세 번째: add_conditional_edges()가 Node 연결

python
graph.add_conditional_edges( "parser", supervisor_router, { "addition_agent": "addition_agent", "subtraction_agent": "subtraction_agent", "multiplication_agent": "multiplication_agent", "division_agent": "division_agent", "error_agent": "error_agent", }, )

전체 원리는 다음 한 문장으로 정리할 수 있습니다.

LangGraph의 조건 분기는 현재 State를 검사한 Router 함수가 다음에 실행할 Node 이름을 반환하고, add_conditional_edges()가 그 반환값을 실제 Node와 연결하는 방식입니다.

#LangGraph#Multi-Agent#LLM#Python#AI Agent

댓글 0

Ctrl + Enter를 눌러 등록할 수 있습니다
※ AI 다듬기는 내용을 정제하는 보조 기능이며, 최종 내용은 사용자가 확인해야 합니다.