λ΄κ° LangGraphλ₯Ό μ’μνλ μ΄μ λ νμν λͺ¨λμ μ¨λΌμΈμμ κ°μ Έμ λ΄ λ§μλλ‘ μ»€μ€ν°λ§μ΄μ§νκΈ° νΈνκΈ° λλ¬Έμ΄λ€. LangChainμ²λΌ λͺ¨λ κΈ°λ₯μ΄ μΌμ²΄νμΌλ‘ λ¬Άμ¬ μμ§ μκ³ , Node λ¨μλ‘ κ΅¬μ±λμ΄ μμ΄ μ½λ μμ κ³Ό νμ₯μ΄ ν¨μ¬ μ μ°νλ€.
λ€λ₯Έ μ¬λλ€μ μ½λλ₯Ό μ°Έκ³ νλ©° λ§μ λμμ λ°μ λ§νΌ, λλ λκ΅°κ°μ νλ‘μ νΈμ μμ κΈ°μ¬λ₯Ό ν μ μμΌλ©΄ μ’κ² λ€λ μκ°μ΄ λ€μλ€. λ¬Όλ‘ μ½λ μ΄μμ€ν΄νΈ μμ λ μ΄λ―Έ LangGraph 곡μ λ¬Έμμ λμ μμ§λ§, νκ΅μ΄ μλ£λ λΆμ‘±νκ³ κ³΅μ λ¬Έμλ λ΄μ©μ΄ λ€μ 무κ²κ² λκ»΄μ§ μ μλ€. κ·Έλμ μ΄ κΈμμλ μ²μ μ νλ μ¬λλ λΆλ΄ μμ΄ μ΄ν΄ν μ μλλ‘ λ κ°λ³κ³ μ€μ©μ μΈ μ½λ μΏ‘λΆμ λ§λ€μ΄λ΄€λ€. νΉν λ°μ΄ν° μκ°ν μΈ‘λ©΄μ μ΄μ μ λ§μΆ° μ½λλ₯Ό μ§λ΄€λ€.
μ΄ μ½λκ° λκ΅°κ°μ νλ‘μ νΈμ μΈμ©λλ€λ©΄ μ λ§ λΏλ―ν κ² κ°λ€.
κ·ΈλΌ μμν΄λ³΄μ! π
Agentic Code Generatorλ₯Ό λ§λ€κΈ° μ μ, LangGraphμ ν΅μ¬ κ°λ λΆν° κ°λ³κ² νμ΄λ³΄μ.
μ΄ κ΅¬μ‘° λλΆμ μμ νλ¦μ μκ°μ μΌλ‘ νμ νκΈ°λ μ½κ³ , μ½λ κ΄λ¦¬λ ν¨μ¬ νΈνλ€. π
Agentic Code Generatorλ μλ νλ¦μΌλ‘ λμκ°λ€.
μμ κ·Έλ¦Όμ Agentic Code Generatorμ νλ‘μ° λ€μ΄μ΄κ·Έλ¨μ΄λ€. μ΅λν κ°μνν΄μ λ§λ€μ΄λ³΄μλ€.
flowchart LR
A(START) --> B[λ°μ΄ν° λ‘λ]
B --> C[λ°μ΄ν° μ μ²λ¦¬]
C --> D[μ½λ μμ±]
D --> E[μκ°ν]
E -->|μλ¬ λ°μ?| D
E -->|μλ¬ μμ| F(END)
μλ¬κ° λ°μνλ©΄ μλμΌλ‘ μ½λ μμ± λ¨κ³λ‘ λμκ° λ°λ³΅ μ€ννλ€. π
λ¨Όμ μμ μ νμν λ°μ΄ν°λ₯Ό μ μ₯ν 곡κ°μ λ§λ€μ΄λ³΄μ.
from typing import TypedDict
class State(TypedDict):
input: str
data: str
processed_data: str
code: str
is_error: bool
result: str
input
: μ¬μ©μμ μμ² (input query) data
: λ‘λλ λ°μ΄ν° processed_data
: μ μ²λ¦¬λ λ°μ΄ν° code
: μμ±λ μ½λ is_error
: μλ¬ λ°μ μ¬λΆ result
: μ€ν κ²°κ³Ό κ°κ°μ μ°μμκ° μλλ°, μλμμ μ‘°κΈμ© μ€λͺ ν΄λ³΄κ² λ€.
κ° μμ λ¨κ³λ³λ‘ ν¨μλ₯Ό λ§λ€μ΄λ³΄μ.
import pandas as pd
def load_data(state: State) -> State:
data = pd.read_csv("data/example.csv")
return {**state, "data": data}
λ°μ΄ν°λ₯Ό κ°μ Έμ€λ κ°λ¨ν μ½λλ€. λ°μ΄ν° μκ°νλ₯Ό μν΄μ CSV νμΌμ κ°μ Έμ¨λ€. μ°Έκ³ λ‘ ν΄λΉ example.csv
νμΌμ Kaggleμ Titanic μμ‘΄μ μμΈ‘ λ°μ΄ν°λ₯Ό μ¬μ©νλ€.
def preprocess_data(state: State) -> State:
processed_data = state["data"].head(100)
return {**state, "processed_data": processed_data}
λ°μ΄ν°κ° λ무 λ§μΌλ©΄ μ²λ¦¬ μκ°μ΄ κΈΈμ΄μ§λκΉ μμ 100κ°λ§ μΆλ €λ³΄μ.
from langchain_openai import ChatOpenAI
def analyze_data(state: State) -> State:
model = ChatOpenAI(model="gpt-4o-mini")
prompt = f"{state['input']} λ°μ΄ν°: {state['processed_data']} λ¨, μ€μ§ Codeλ§ μΆλ ₯νμΈμ."
response = model.invoke(prompt)
return {**state, "code": response.content}
λͺ¨λΈμκ² μ½λ μμ±μ μμ²νλ©΄, μ¬μ©μ μ λ ₯κ³Ό μ μ²λ¦¬λ λ°μ΄ν°λ₯Ό κΈ°λ°μΌλ‘ μ½λλ₯Ό λλ± λ§λ€μ΄μ€λ€.
λ§μ½ μ½λλ₯Ό νλ²λ§ λ§λ λ€λ©΄ μμ²λΌ μ μνλ©΄ λκ² μ§λ§, μ°λ¦¬λ λ€μ΄μ΄κ·Έλ¨μμ μλ¬κ° λ°μνλ©΄ λ€μ μ½λλ₯Ό μμ±νκΈ°λ‘ νμΌλκΉ μ½λλ₯Ό μ‘°κΈ λ μΆκ°ν΄μΌ νλ€.
def analyze_data(state: State) -> State:
# λ°μ΄ν° λΆμ λ‘μ§ κ΅¬ν
model = ChatOpenAI(model="gpt-4o-mini")
if state["is_error"] is False:
# μΌλ° μ½λ μ μ ν둬ννΈ
response = model.invoke(
(
f"{state['input']} "
"λ¨, μ€μ§ Codeλ§ μΆλ ₯νμΈμ. "
f"λ°μ΄ν°: {state['processed_data']} "
)
)
else:
# Visualization λ
Έλμμ μ½λ μλ¬ λ°μμ λ€μ μ½λλ₯Ό μμ±νλ ν둬ννΈ
response = model.invoke(
(
f"{state['input']} "
"λ¨, μ€μ§ Codeλ§ μΆλ ₯νμΈμ. "
f"λ°μ΄ν°: {state['processed_data']} "
"====================== "
f"[AI] {state['code']} "
f"{state['result']} "
"====================== "
"μ€ν μ€ λ°μν λ²κ·Έλ₯Ό μ°Έκ³ νμ¬ λ€μ μ½λλ₯Ό μμ±ν΄μ£ΌμΈμ."
)
)
code = response.content
return State(code=code)
λ§μ½ μ²μμΌλ‘ μ½λλ₯Ό μμ±νλ μνλΌλ©΄ state["is_error"]
κ° False
μ΄λ―λ‘ μ²«λ²μ§Έ if λ¬Έμμ "μΌλ° μ½λ μ μ ν둬ννΈ"λ‘ LLMμ νΈμΆνλ€.
νμ§λ§ visualization λ
Έλμμ μ½λλ₯Ό μ€ννλλ° errorκ° λ¬ μν©μ΄λΌλ©΄ state["is_error"]
κ° True
μ΄λ―λ‘ else λ¬Έμμ "λ€μ μ½λλ₯Ό μμ±νλ ν둬ννΈ"λ‘ LLMμ νΈμΆνλ€. μ¬κΈ°μλ μλ¬ λ©μμ§μ λ€μ μ½λλ₯Ό μμ±νλΌλ λ¬Έκ΅¬κ° ν¬ν¨λμ΄ μλ κ²μ λ³Ό μ μλ€.
def visualization(state: State) -> State:
import seaborn as sns
import matplotlib.pyplot as plt
from langchain_experimental.tools.python.tool import PythonAstREPLTool
code = state["code"]
repl_tool = PythonAstREPLTool(locals={"sns": sns, "plt": plt})
try:
exec_result = repl_tool.invoke(code)
return {**state, "result": exec_result, "is_error": False}
except Exception as e:
return {**state, "result": str(e), "is_error": True}
AIκ° μμ±ν μ½λλ₯Ό μ€νν΄μ μκ°νλ₯Ό μΆλ ₯νλ€. μ€ν μ€ λ¬Έμ κ° μμΌλ©΄ μλ¬ λ©μμ§κ° λ°νλλ€.
π Tips
PythonAstREPLToolμ΄ μ½λλ₯Ό μ€ννλ Toolμ΄λ€. PythonREPLToolκ³Όμ μ°¨μ΄μ μ 보μμ΄ μ‘°κΈ λ κ°νλ€λ μ μ΄λ€. PythonAstREPLToolμ μ½λ μ€ν μ μ μΆμ ꡬ문 νΈλ¦¬(Abstract Syntax Tree, AST) λΆμμ ν΅ν΄ μ μ¬μ μΌλ‘ μνν μ½λλ κΈμ§λ μ°μ°μ μ¬μ μ μ°¨λ¨ν μ μλ€. μ΄λ₯Ό ν΅ν΄ μΈλΆ λΌμ΄λΈλ¬λ¦¬ μ κ·Ό, νμΌ μμ€ν μ‘°μ, 무ν 루ν μ€ν λ± μλμΉ μμ λμμ λ°©μ§νλ©° λ³΄λ€ μμ ν μ½λ μ€ν νκ²½μ μ 곡νλ€. λ°λΌμ, μ¬μ©μ μ λ ₯μ κΈ°λ°μΌλ‘ μ½λλ₯Ό μ€νν΄μΌ νλ μλ리μ€μμ PythonAstREPLToolμ μμ μ±κ³Ό 보μμ λͺ¨λ ν보ν μ μλ μ μ ν μ νμ§μ΄λ€.
κ° λ Έλλ₯Ό μ°κ²°ν΄ νλ¦μ μμ±νμ.
from langgraph.graph import StateGraph, START, END
def build_flow():
flow = StateGraph(State)
flow.add_node("load_data", load_data)
flow.add_node("preprocess_data", preprocess_data)
flow.add_node("analyze_data", analyze_data)
flow.add_node("visualization", visualization)
flow.add_edge(START, "load_data")
flow.add_edge("load_data", "preprocess_data")
flow.add_edge("preprocess_data", "analyze_data")
flow.add_edge("analyze_data", "visualization")
flow.add_conditional_edges(
"visualization",
lambda state: "analyze_data" if state["is_error"] else "end",
{"analyze_data": "analyze_data", "end": END},
)
return flow.compile()
μλ¬κ° λ°μνλ©΄ μλμΌλ‘ μ½λ μμ± λ¨κ³λ‘ λμκ° μ¬μ€νν μ μλ€. flow.add_conditional_edges
μμ "visualization"
λ
Έλλ‘λΆν° μ΄μ΄μ§λ conditional edgesλ₯Ό μ νλ€. state["is_error"]
κ° True
λΌλ©΄ "analyze_data"
λ
Έλλ‘ μ΄μ΄μ§κ³ , False
λΌλ©΄ END
λ‘ μ΄μ΄μ§λ€.
π Tips
LangGraphμ
State
κ°μ²΄λ μ§λ ¬νλ₯Ό κ±°μΉκΈ° λλ¬Έμpandas.DataFrame
μ²λΌ μ§λ ¬νκ° κΉλ€λ‘μ΄ κ°μ²΄λ μ§μ μ μ₯νκΈ° μ΄λ €μΈ μ μλ€. νΉν ν΄λΉ Graphλ₯Ό SubGraphλ‘ μΆκ°ν λ μ§λ ¬νμ κ΄λ ¨λ Pydantic Errorκ° λ°μνλ€λ©΄ μλ μ루μ μ μλν΄λ³΄μ.
β ν΄κ²° λ°©λ²:
- CSV, JSON λ± λ¬Έμμ΄ ννλ‘ λ³ννμ¬ μ μ₯:
μ΄λ κ² νλ©΄ μν μ ν μ λ°μ΄ν° μμ€μ λ°©μ§νλ©΄μλ μμ μ μΈ μ²λ¦¬κ° κ°λ₯νλ€.state["data"] = df.to_json() # μ μ₯ μ df = pd.read_json(state["data"]) # λΆλ¬μ¬ λ
if __name__ == "__main__":
flow = build_flow()
initial_state = State(
input="Titanic λ°μ΄ν°λ₯Ό μκ°μ μΌλ‘ λ©μ§κ² 보μ¬μ€!",
data="",
processed_data="",
code="",
is_error=False,
result=""
)
for event in flow.stream(initial_state):
for step, output in event.items():
print(f"\nπ STEP: {step}\n{output}\n")
π STEP: load_data
λ°μ΄ν° λ‘λ μλ£!
π STEP: preprocess_data
μμ 100κ° λ°μ΄ν° μ μ²λ¦¬ μλ£!
π STEP: analyze_data
μ½λ μμ± μ±κ³΅! π
π STEP: visualization
μκ°ν μ±κ³΅ π (μλ¬ μμ)
λ°μ΄ν° λ‘λ ν Stateμ μ μ₯λ λ΄μ©:
{
'data':
PassengerId Survived Pclass Name Sex ... Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male ... 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female ... 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female ... 7.9250 NaN S
... ... ... ... ... ... ... ... ... ...
890 891 0 3 Dooley, Mr. Patrick male ... 7.7500 NaN Q
[891 rows x 12 columns]
}
μμ 100κ° λ°μ΄ν° μ μ²λ¦¬ ν Stateμ μ μ₯λ λ΄μ©:
{
'processed_data':
PassengerId Survived Pclass Name ... Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris ... 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th...) ... 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina ... 7.9250 NaN S
... ... ... ... ... ... ... ... ...
99 100 0 2 Kantor, Mr. Sinai ... 26.0000 NaN S
[100 rows x 12 columns]
}
λΆμ μ½λ μμ± ν Stateμ μ μ₯λ λ΄μ©:
{
'code':
"""
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# λ°μ΄ν° νλ μ μμ±
data = {
'PassengerId': range(1, 101),
'Survived': [0, 1, 1, 1, 0] * 20,
'Pclass': [3, 1, 3, 1, 3] * 20,
'Fare': [7.25, 71.2833, 7.925, 53.1, 8.05] * 20,
'Embarked': ['S', 'C', 'S', 'S', 'S'] * 20
}
df = pd.DataFrame(data)
# μκ°ν
plt.figure(figsize=(12, 8))
# 1. μμ‘΄μ μ
plt.subplot(2, 2, 1)
sns.countplot(data=df, x='Survived', palette='pastel')
plt.title('Survival Count')
# 2. ν΄λμ€λ³ μμ‘΄μ¨
plt.subplot(2, 2, 2)
sns.barplot(data=df, x='Pclass', y='Survived', palette='pastel')
plt.title('Survival Rate by Class')
# 3. μκΈ λΆν¬
plt.subplot(2, 2, 3)
sns.histplot(data=df, x='Fare', kde=True, bins=30, color='purple')
plt.title('Fare Distribution')
# 4. νμΉ νκ΅¬λ³ μμ‘΄μ μ
plt.subplot(2, 2, 4)
sns.countplot(data=df, x='Embarked', hue='Survived', palette='pastel')
plt.title('Survival Count by Embarked')
plt.tight_layout()
plt.show()
"""
}
μκ°ν κ²°κ³Ό λ° μ€ν μν:
{
'result': '',
'is_error': False
}
β
π μκ°νλ Figure:
Titanic λ°μ΄ν° μκ°ν κ²°κ³Ό:
π§ββοΈ μμ‘΄μ μ λΉκ΅ | π·οΈ ν΄λμ€λ³ μμ‘΄μ¨
π° μκΈ λΆν¬ | π’ νμΉ νκ΅¬λ³ μμ‘΄μ μ
from IPython.display import display, Image
from main import build_flow
flow = build_flow()
display(Image(flow.get_graph(xray=False).draw_mermaid_png()))
load_data
β preprocess_data
β analyze_data
β visualization
)μμ μ²λ¦¬ κ²°κ³Όκ° State
μ μ μ₯λλ€. analyze_data
λ¨κ³λ‘ λλμκ° μ½λκ° μλμΌλ‘ μμ λ° μ¬μ€νλλ€. from typing import TypedDict
import pandas as pd
from langchain_openai import ChatOpenAI
from langchain_experimental.tools.python.tool import PythonAstREPLTool
from langgraph.graph import StateGraph, START, END
# State μ μ
class State(TypedDict):
input: str
data: str
processed_data: str
code: str
is_error: bool
result: str
# κ° Nodeμ ν΄λΉνλ ν¨μ μ μ
def load_data(state: State) -> State:
# λ°μ΄ν° λ‘λ λ‘μ§ κ΅¬ν
data = pd.read_csv("./data/example.csv")
return State(data=data)
def preprocess_data(state: State) -> State:
# λ°μ΄ν° μ μ²λ¦¬ λ‘μ§ κ΅¬ν
data = state["data"]
processed_data = data.head(100)
return State(processed_data=processed_data)
def analyze_data(state: State) -> State:
# λ°μ΄ν° λΆμ λ‘μ§ κ΅¬ν
model = ChatOpenAI(model="gpt-4o-mini")
if state["is_error"] is False:
response = model.invoke(
(
f"{state['input']} "
"λ¨, μ€μ§ Codeλ§ μΆλ ₯νμΈμ. "
f"λ°μ΄ν°: {state['processed_data']} "
)
)
else:
response = model.invoke(
(
f"{state['input']} "
"λ¨, μ€μ§ Codeλ§ μΆλ ₯νμΈμ. "
f"λ°μ΄ν°: {state['processed_data']} "
"====================== "
f"[AI] {state['code']} "
f"{state['result']} "
"====================== "
"μ€ν μ€ λ°μν λ²κ·Έλ₯Ό μ°Έκ³ νμ¬ λ€μ μ½λλ₯Ό μμ±ν΄μ£ΌμΈμ."
)
)
code = response.content
return State(code=code)
def visualization(state: State) -> State:
# μ½λ μ€ν
import seaborn as sns
import matplotlib.pyplot as plt
code = state["code"]
processed_code = None
if code.startswith("```"):
processed_code = "\n".join(code.split("\n")[1:-1])
else:
processed_code = code
python_repl_tool = PythonAstREPLTool(locals={"sns": sns, "plt": plt})
try:
exec_result = str(python_repl_tool.invoke(processed_code))
return State(result=exec_result, is_error=False)
except Exception as e:
return State(result=e, is_error=True)
def build_flow():
# flow init
flow = StateGraph(State)
# node μ μ
flow.add_node("load_data", load_data)
flow.add_node("preprocess_data", preprocess_data)
flow.add_node("analyze_data", analyze_data)
flow.add_node("visualization", visualization)
# edge μ°κ²°
flow.add_edge(START, "load_data")
flow.add_edge("load_data", "preprocess_data")
flow.add_edge("preprocess_data", "analyze_data")
flow.add_edge("analyze_data", "visualization")
# codintional edge μ°κ²°
def is_error_check(state: State) -> str:
return "analyze_data" if state["is_error"] else "end"
flow.add_conditional_edges(
"visualization",
is_error_check,
{"analyze_data": "analyze_data", "end": END},
)
# compile
return flow.compile()
if __name__ == "__main__":
flow = build_flow()
initial_state = State(
input="λ€μ λ°μ΄ν°λ₯Ό μ λ¬Έμ μΌλ‘ μκ°ννμ¬ νλμ Figureλ‘ ννν΄μ£ΌμΈμ.",
is_error=False,
code="",
reulst="",
)
for event in flow.stream(initial_state):
for node_name, value in event.items():
print(f"\n==============\nSTEP: {node_name}\n==============\n")
print(value)
μ΄λ² κΈμμλ LangGraphλ₯Ό νμ©ν λ°μ΄ν° μ²λ¦¬ νμ΄νλΌμΈ κ΅¬μΆ λ°©λ²κ³Ό λ¨κ³λ³ μκ°ν κ³Όμ μ λ€λ€λ€.
κ° λ¨κ³λ³ μλ¬ μ²λ¦¬ νλ¦, μ‘°κ±΄λΆ νλ‘μ° κ΅¬μ±, κ·Έλ¦¬κ³ State μ§λ ¬ν ν λ±μ ν¬ν¨ν΄ μ€μ νλ‘μ νΈμ μ μ©ν μ μλ λ΄μ©μ μ 리νλ€. π
π μ 체 μ½λ GitHubμμ νμΈνκΈ°:
π GitHub - Agentic Code Generator
π μ°Έκ³ μλ£:
π LangGraph Code Assistant Tutorial
π¬ μ½μ΄μ£Όμ
μ κ°μ¬ν©λλ€!
π λμμ΄ λμ
¨λ€λ©΄ βοΈ μ€νλ₯Ό λλ¬μ£Όμλ©΄ ν° νμ΄ λ©λλ€.
β
μΆκ°λ‘ λ€λ€λ³΄λ©΄ μ’μ μ£Όμ λ κΆκΈν λ΄μ©μ΄ μλ€λ©΄ λκΈλ‘ μλ €μ£ΌμΈμ. κ΄λ ¨ κΈμ μμ±ν΄ λ³΄κ² μ΅λλ€. π