๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

AI ๊ฐœ๋ฐœ

LangChain๊ณผ LangGraph๋ฅผ ํ™œ์šฉํ•œ AI Agent ๊ฐœ๋ฐœ ๊ฐ€์ด๋“œ

๋ฐ˜์‘ํ˜•

๐Ÿง  LangChain๊ณผ LangGraph๋ฅผ ํ™œ์šฉํ•œ AI ์—์ด์ „ํŠธ ๊ตฌํ˜„ ๊ฐ€์ด๋“œ

๐Ÿงฉ ์‹œ๋‚˜๋ฆฌ์˜ค

"๊ณ ๊ฐ ๋ฐ์ดํ„ฐ์—์„œ VIP ๊ณ ๊ฐ์„ ์„ ๋ณ„ํ•˜๊ณ , ๊ฐ ๊ณ ๊ฐ์—๊ฒŒ ๋งž์ถค ์ด๋ฉ”์ผ์„ ์ƒ์„ฑํ•œ ๋’ค, ์ด๋ฅผ ๊ฒ€ํ† ํ•˜๊ณ  ๋ฐœ์†ก ๋Œ€๊ธฐ ๋ฆฌ์ŠคํŠธ์— ์˜ฌ๋ ค๋ผ."

โœ… ์ „์ฒด ๊ตฌ์„ฑ ํ๋ฆ„

[1] Goal ์„ค์ •
 ↓
[2] LLM Reasoning → ์–ด๋–ค ๊ณ ๊ฐ์ด VIP์ธ์ง€ ํŒ๋‹จ
 ↓
[3] Tool ์‚ฌ์šฉ → Google Sheets์—์„œ ๊ณ ๊ฐ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
 ↓
[4] Tool ์‚ฌ์šฉ → OpenAI๋กœ ์ด๋ฉ”์ผ ์ž‘์„ฑ
 ↓
[5] Tool ์‚ฌ์šฉ → ์ด๋ฉ”์ผ ํ’ˆ์งˆ ๊ฒ€ํ†  (Self-critique)
 ↓
[6] ์ €์žฅ ๋˜๋Š” ์ „์†ก ์ค€๋น„

๐Ÿ› ๏ธ ์‚ฌ์šฉ ๊ธฐ์ˆ 

  • LangChain (์—์ด์ „ํŠธ ํ”„๋ ˆ์ž„์›Œํฌ)
  • OpenAI GPT-4 API
  • Google Sheets API (๋˜๋Š” ์ž„์‹œ CSV)
  • @tool ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ํ†ตํ•œ ํ•จ์ˆ˜ ๋“ฑ๋ก
  • Python

๐Ÿง‘‍๐Ÿ’ป ์˜ˆ์ œ ์ฝ”๋“œ (LangChain Agent)

from langchain.agents import initialize_agent, Tool
from langchain.agents.agent_types import AgentType
from langchain.chat_models import ChatOpenAI

# Step 1: ๋„๊ตฌ ์ •์˜
def get_vip_customers():
    # ์˜ˆ์ œ: 100๋งŒ ์› ์ด์ƒ ๊ตฌ๋งค ๊ณ ๊ฐ๋งŒ ์ถ”์ถœ
    return ["ํ™๊ธธ๋™", "๊น€์ฒ ์ˆ˜"]

def write_email(name: str) -> str:
    return f"{name}๋‹˜ ์•ˆ๋…•ํ•˜์„ธ์š”. VIP ๊ณ ๊ฐ๋‹˜๊ป˜ ๊ฐ์‚ฌ ์ธ์‚ฌ ๋“œ๋ฆฝ๋‹ˆ๋‹ค!"

def review_email(content: str) -> str:
    # ๊ฐ„๋‹จํ•œ ์˜ˆ์‹œ: ๋„ˆ๋ฌด ์งง์œผ๋ฉด ์ˆ˜์ • ๊ถŒ์žฅ
    if len(content) < 50:
        return content + " ๋‹ค์‹œ ํ•œ๋ฒˆ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค."
    return content

tools = [
    Tool(name="Get VIP Customers", func=get_vip_customers, description="VIP ๊ณ ๊ฐ ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค."),
    Tool(name="Write Email", func=write_email, description="๊ณ ๊ฐ ์ด๋ฆ„์œผ๋กœ ์ด๋ฉ”์ผ์„ ์ƒ์„ฑํ•œ๋‹ค."),
    Tool(name="Review Email", func=review_email, description="์ด๋ฉ”์ผ ๋‚ด์šฉ์„ ๊ฒ€ํ† ํ•˜๊ณ  ๊ฐœ์„ ํ•œ๋‹ค."),
]

# Step 2: ์—์ด์ „ํŠธ ์ดˆ๊ธฐํ™”
llm = ChatOpenAI(model="gpt-4", temperature=0)
agent_executor = initialize_agent(
    tools,
    llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True,
)

# Step 3: ๋ชฉํ‘œ ์ง€์‹œ
agent_executor.run("VIP ๊ณ ๊ฐ์—๊ฒŒ ๋ณด๋‚ผ ๊ฐ์‚ฌ ์ด๋ฉ”์ผ์„ ์ž‘์„ฑํ•˜๊ณ  ๊ฒ€ํ† ํ•ด์ค˜.")

๐Ÿ” ์„ค๋ช… ์š”์•ฝ

์ปดํฌ๋„ŒํŠธ ์„ค๋ช…
Tool LLM์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋“ฑ๋ก๋œ ๊ธฐ๋Šฅ (ex. DB ์กฐํšŒ, ์ด๋ฉ”์ผ ์ž‘์„ฑ)
initialize_agent ํˆด๊ณผ LLM์„ ์—ฐ๊ฒฐํ•˜๋Š” ๊ตฌ์„ฑ ํ•จ์ˆ˜
ZERO_SHOT_REACT_DESCRIPTION ํˆด ์„ค๋ช…๋งŒ์œผ๋กœ ํ–‰๋™์„ ๊ฒฐ์ •ํ•˜๋Š” ์—์ด์ „ํŠธ ํƒ€์ž…
agent_executor.run() ์‹ค์ œ ์‹คํ–‰ – ๋ชฉํ‘œ(Goal)๋ฅผ LLM์ด ์ดํ•ดํ•˜๊ณ  ํˆด์„ ์„ ํƒ & ์‹คํ–‰

๐Ÿง  LangGraph ๊ธฐ๋ฐ˜ FSM AI ์—์ด์ „ํŠธ ๊ตฌ์„ฑ ์˜ˆ์ œ

๐Ÿ’ก ์‹œ๋‚˜๋ฆฌ์˜ค

"๊ณ ๊ฐ ๋ฐ์ดํ„ฐ์—์„œ VIP ๊ณ ๊ฐ์„ ์„ ๋ณ„ํ•˜๊ณ ,
๊ฐ ๊ณ ๊ฐ์—๊ฒŒ ๋งž์ถคํ˜• ์ด๋ฉ”์ผ์„ ์ž‘์„ฑํ•œ ๋’ค,
๊ทธ ์ด๋ฉ”์ผ์„ AI๊ฐ€ ๋ฆฌ๋ทฐํ•˜๊ณ ,
์ตœ์ข… ์Šน์ธ๋œ ์ด๋ฉ”์ผ๋งŒ ๋ฐœ์†ก ๋Œ€๊ธฐ ๋ฆฌ์ŠคํŠธ์— ์ €์žฅํ•œ๋‹ค."

๋„๊ตฌ๊ฐ€ ๋งŽ์•„์ง€๊ณ , ๋‹จ๊ณ„๋ณ„๋กœ ๊ฒฐ์ • ํ๋ฆ„์ด ๋ถ„๊ธฐ๋˜๋ฏ€๋กœ FSM ๊ตฌ์กฐ๊ฐ€ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“Œ ๊ตฌ์„ฑ ๊ฐœ์š”

LangGraph๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด FSM์ฒ˜๋Ÿผ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค:

[Start] → [GetVIPs] → [GenerateEmail] → [ReviewEmail]
      โ†˜๏ธŽ        โ†˜๏ธŽ             โ†˜๏ธŽ            ↓
     [Error]   [Retry]       [Done] ← [RewriteEmail]

๐Ÿ›  ๊ธฐ์ˆ  ์Šคํƒ

  • LangGraph – ์ƒํƒœ ๊ทธ๋ž˜ํ”„ ์ •์˜
  • ChatOpenAI – Reasoning ๋‹ด๋‹น
  • @tool ๋˜๋Š” LangChain Tool – ์™ธ๋ถ€ ๊ธฐ๋Šฅ ํ˜ธ์ถœ
  • Python – FSM ์ •์˜ ๋ฐ ์‹คํ–‰

๐Ÿง‘‍๐Ÿ’ป ์˜ˆ์ œ ์ฝ”๋“œ: LangGraph FSM ๊ตฌ์„ฑ

from langgraph.graph import StateGraph, END
from langchain.chat_models import ChatOpenAI
from typing import TypedDict, List

# ์ƒํƒœ ์ •์˜
class AgentState(TypedDict):
    customers: List[str]
    email: str
    approved: bool

# LLM ์ดˆ๊ธฐํ™”
llm = ChatOpenAI(model="gpt-4", temperature=0)

# ์ƒํƒœ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜๋“ค
def fetch_vips(state):
    # ์˜ˆ์‹œ: DB๋‚˜ API์—์„œ VIP ๊ณ ๊ฐ ์ถ”์ถœ
    return {"customers": ["ํ™๊ธธ๋™", "๊น€์˜ํฌ"]}

def generate_email(state):
    name = state["customers"][0]
    email = f"{name}๋‹˜ ์•ˆ๋…•ํ•˜์„ธ์š”. VIP ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค!"
    return {"email": email}

def review_email(state):
    email = state["email"]
    if "๊ฐ์‚ฌ" not in email:
        return {"approved": False}
    return {"approved": True}

def rewrite_email(state):
    email = state["email"]
    return {"email": email + " ๋‹ค์‹œ ํ•œ๋ฒˆ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค!"}

# ์ƒํƒœ ๊ทธ๋ž˜ํ”„ ์ •์˜
workflow = StateGraph(AgentState)

workflow.add_node("GetVIPs", fetch_vips)
workflow.add_node("GenerateEmail", generate_email)
workflow.add_node("ReviewEmail", review_email)
workflow.add_node("RewriteEmail", rewrite_email)

# ์ƒํƒœ ์ „์ด ์ •์˜
workflow.set_entry_point("GetVIPs")
workflow.add_edge("GetVIPs", "GenerateEmail")
workflow.add_edge("GenerateEmail", "ReviewEmail")
workflow.add_conditional_edges(
    "ReviewEmail",
    {
        "approved": END,
        "default": "RewriteEmail"
    }
)
workflow.add_edge("RewriteEmail", "ReviewEmail")

# ์‹คํ–‰
app = workflow.compile()
result = app.invoke({})
print(result)

๐Ÿงฉ ๊ตฌ์กฐ ์„ค๋ช…

๊ตฌ์„ฑ ์š”์†Œ ์—ญํ• 
StateGraph() ์ „์ฒด ์—์ด์ „ํŠธ ํ๋ฆ„ ์ •์˜
add_node() ๊ฐ๊ฐ์˜ ํ•จ์ˆ˜(๋…ธ๋“œ)๋ฅผ FSM์— ์ถ”๊ฐ€
add_edge() ๋‹ค์Œ ์ƒํƒœ๋กœ์˜ ์ „์ด ์ •์˜
add_conditional_edges() ์ƒํƒœ ์กฐ๊ฑด์— ๋”ฐ๋ผ ๋ถ„๊ธฐ
invoke() ๊ทธ๋ž˜ํ”„ ์‹คํ–‰ (state ์ž…๋ ฅ ๊ฐ€๋Šฅ)

โœ… ์‚ฌ์šฉ ์žฅ์ 

  • ๋‹จ๊ณ„๋ณ„ ์ „์ด/๋ถ„๊ธฐ/์žฌ์‹œ๋„ ๋กœ์ง์ด ๋ช…ํ™•ํ•˜๊ฒŒ ๋ณด์ž„
  • ์—์ด์ „ํŠธ์˜ ๊ฐ ํ–‰๋™ ์ƒํƒœ๊ฐ€ ์ถ”์  ๊ฐ€๋Šฅ
  • ์ถ”ํ›„์— Tool ์‚ฌ์šฉ ์ˆ˜ ์ฆ๊ฐ€ ์‹œ ํ™•์žฅ์„ฑ ์šฐ์ˆ˜
๋ฐ˜์‘ํ˜•