AI Agent

LangGraph vs PydanticAI vs CrewAI vs Google ADK — 2026년 에이전트 프레임워크 4파전

cell-devlog 2026. 5. 29. 14:27
반응형

47개 행짜리 비교 스프레드시트를 뒤져봤자 결국 "it depends"로 끝납니다. 실제로 중요한 건 프레임워크가 아니라 여러분 팀이 풀려는 문제입니다. 그 문제부터 정확히 짚고 코드로 비교합니다.


핵심 요약 → LangGraph (25K stars, 34.5M 월간 다운로드): 상태 머신 기반, 최고 통제력, 가장 가파른 학습 곡선 → PydanticAI (16.8K stars): 타입 안전 + DI, 단순 에이전트에 FastAPI 느낌, 멀티에이전트 성숙도 낮음 → CrewAI (47K stars): 역할 기반 가장 빠른 프로토타입, 내장 메모리, 복잡해지면 디버깅 어려움 → Google ADK: A2A·MCP·AG-UI 프로토콜 선도, GCP 네이티브, 커뮤니티 가장 작음 → 월간 검색량: LangGraph 27,100 > CrewAI 14,800 (관심도 기준) → Uber·Klarna·JPMorgan 등 400+ 기업 프로덕션 사용 = LangGraph → "빠른 프로토타입 → 나중에 벽 만남" = CrewAI의 대표적 패턴 → 결론: 단일 정답 없음 — 문제 유형·팀 성향·스택에 따라 명확히 다름


4파전 한눈 요약

# 철학 비교

LangGraph:
  "에이전트 = 상태 머신"
  노드(작업) + 엣지(전환) + 상태(공유 데이터)
  → 완전한 통제, 감사 가능, 복잡 워크플로

PydanticAI:
  "에이전트 = 타입 안전 FastAPI 함수"
  Agent + deps_type + output_type
  → 간결한 코드, 테스트 용이, IDE 지원 최강

CrewAI:
  "에이전트 = 역할이 있는 팀원"
  Agent(역할) + Task(임무) + Crew(팀)
  → 가장 빠른 프로토타입, 자연어 정의

Google ADK:
  "에이전트 = 계층적 트리"
  RootAgent → SubAgents → Tools
  → GCP 네이티브, A2A 프로토콜, 멀티모달

1. LangGraph — 최고의 통제력, 최고의 학습 곡선

LangGraph는 LangChain의 그래프 기반 오케스트레이션 레이어입니다. 에이전트 워크플로를 명시적 노드와 엣지를 가진 상태 머신으로 모델링합니다. Uber, Klarna, LinkedIn, JPMorgan 등 400개 이상 기업이 프로덕션에서 사용하며, Klarna의 AI 어시스턴트는 8,500만 사용자의 지원을 처리하면서 해결 시간을 80% 단축했습니다.

from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
from langgraph.constants import Send
import operator

# ── 상태 정의 ──
class AgentState(TypedDict):
    messages:     list[str]
    results:      Annotated[list[str], operator.add]  # 병렬 누적
    final_report: str
    iteration:    int
    should_retry: bool

# ── 노드 함수들 ──
def analyze_node(state: AgentState) -> AgentState:
    """분석 노드"""
    # Claude/GPT/Gemini 호출
    analysis = call_llm(state["messages"])
    return {"results": [analysis], "iteration": state["iteration"] + 1}

def check_quality(state: AgentState) -> str:
    """조건부 엣지 — 품질 검사 후 다음 노드 결정"""
    if state["iteration"] > 3:
        return "end"
    if needs_revision(state["results"]):
        return "revise"    # → revise_node로
    return "synthesize"    # → synthesize_node로

def synthesize_node(state: AgentState) -> AgentState:
    return {"final_report": merge_results(state["results"])}

# ── 그래프 구성 ──
builder = StateGraph(AgentState)
builder.add_node("analyze",    analyze_node)
builder.add_node("synthesize", synthesize_node)

builder.set_entry_point("analyze")
builder.add_conditional_edges(
    "analyze",
    check_quality,
    {"revise": "analyze", "synthesize": "synthesize", "end": END}
)
builder.add_edge("synthesize", END)

graph = builder.compile(
    checkpointer=...,          # 상태 영속성 (PostgreSQL, Redis)
    interrupt_before=["synthesize"]  # HITL: synthesize 전 인간 승인
)

# ── 실행 ──
config = {"configurable": {"thread_id": "session-123"}}
result = graph.invoke(
    {"messages": ["분석해줘"], "results": [], "iteration": 0, "should_retry": False},
    config=config
)

# ── LangGraph가 빛나는 이유 ──
# 1. 체크포인터: 서버 재시작해도 정확히 이전 상태에서 재개
# 2. interrupt_before: 특정 노드 전 인간 승인 강제
# 3. 조건부 엣지: 복잡한 분기 로직 명시적 표현
# 4. LangSmith: 모든 노드 실행 트레이스 자동 저장
# LangGraph 선택 기준

✅ 선택해야 할 때:
  - 규제 환경 (금융, 의료): 모든 결정 감사 추적 필수
  - 복잡한 조건부 워크플로: if-else 분기가 많은 에이전트
  - Human-in-the-Loop: 특정 단계에서 인간 승인 필요
  - 장시간 실행 에이전트: 체크포인터로 중간 상태 저장
  - 이미 LangChain/LangSmith 사용 중

❌ 피해야 할 때:
  - 빠른 프로토타입 (학습 곡선 가파름)
  - 단순 Q&A 에이전트 (오버킬)
  - TypeScript 팀이지만 Python LangGraph 쓰려는 경우
    (JS 버전은 Python 대비 기능 부족)

2. PydanticAI — 타입 안전, 단순함, 테스트 가능성

from dataclasses import dataclass
from pydantic import BaseModel, Field
from pydantic_ai import Agent, RunContext
import asyncpg

# ── 의존성 정의 ──
@dataclass
class Deps:
    db: asyncpg.Pool
    user_id: int

# ── 출력 타입 ──
class AnalysisResult(BaseModel):
    summary:     str
    risk_level:  str = Field(pattern="^(low|medium|high)$")
    action_items: list[str]
    confidence:  float = Field(ge=0.0, le=1.0)

# ── 에이전트 정의 ──
agent = Agent(
    "anthropic:claude-sonnet-4-6",
    deps_type=Deps,
    output_type=AnalysisResult,
    system_prompt="당신은 데이터 분석 전문가입니다.",
)

@agent.instructions
async def inject_user_context(ctx: RunContext[Deps]) -> str:
    profile = await ctx.deps.db.fetchrow(
        "SELECT * FROM users WHERE id = $1", ctx.deps.user_id
    )
    return f"분석 대상 사용자: {profile['name']}, 등급: {profile['tier']}"

@agent.tool
async def fetch_data(ctx: RunContext[Deps], days: int = 30) -> list[dict]:
    """최근 데이터를 가져옵니다. Args: days: 조회 기간(일)"""
    rows = await ctx.deps.db.fetch(
        "SELECT * FROM events WHERE user_id=$1 AND date > NOW()-$2*'1 day'::interval",
        ctx.deps.user_id, days
    )
    return [dict(r) for r in rows]

# ── 실행 ──
async def run():
    async with asyncpg.create_pool(DSN) as pool:
        result = await agent.run(
            "이 사용자의 최근 활동을 분석해줘",
            deps=Deps(db=pool, user_id=42)
        )
        print(result.output.risk_level)   # "low" | "medium" | "high" 타입 보장


# ── PydanticAI가 빛나는 이유 ──
# 1. output_type 검증 실패 시 자동 재시도 (LLM에게 피드백)
# 2. deps_type 타입 불일치 → IDE가 작성 시점에 에러 표시
# 3. TestModel로 LLM 없이 에이전트 로직 테스트
# 4. 툴 파라미터도 Pydantic 검증 → 잘못된 인자 자동 거부
# PydanticAI 선택 기준

✅ 선택해야 할 때:
  - FastAPI 쓰는 팀 (동일 패턴, 빠른 온보딩)
  - 타입 안전이 최우선 (프로덕션 코드 품질)
  - 단순~중간 복잡도 에이전트
  - CI/CD에서 LLM API 없이 테스트 필요

❌ 피해야 할 때:
  - 복잡한 그래프 워크플로 (LangGraph로)
  - 내장 메모리가 필요한 경우 (직접 구현 필요)
  - 비개발자도 에이전트 정의에 참여하는 환경

3. CrewAI — 가장 빠른 프로토타입, 역할 기반

from crewai import Agent, Task, Crew, Process
from crewai.tools import BaseTool

# ── 툴 정의 ──
class WebSearchTool(BaseTool):
    name: str = "web_search"
    description: str = "웹에서 최신 정보를 검색합니다"

    def _run(self, query: str) -> str:
        return search_web(query)   # 실제 검색 로직

# ── 에이전트 정의 (자연어로) ──
researcher = Agent(
    role="수석 리서처",
    goal="주어진 주제에 대한 최신 정보를 수집하고 분석",
    backstory="""당신은 10년 경력의 AI 리서처입니다.
    복잡한 기술 트렌드를 명확하게 분석하는 전문가입니다.""",
    tools=[WebSearchTool()],
    llm="anthropic/claude-sonnet-4-6",
    verbose=True,
    memory=True,    # ← 내장 메모리 (세션 내)
)

writer = Agent(
    role="기술 작가",
    goal="리서처의 결과를 바탕으로 명확한 리포트 작성",
    backstory="기술 문서 전문가. 복잡한 내용을 쉽게 설명합니다.",
    llm="openai/gpt-5.5",    # 다른 모델 사용 가능
)

fact_checker = Agent(
    role="팩트 체커",
    goal="리포트의 모든 주장을 검증",
    backstory="정확성을 최우선으로 하는 검증 전문가입니다.",
    tools=[WebSearchTool()],
    llm="google/gemini-3.5-flash",
)

# ── 태스크 정의 ──
research_task = Task(
    description="2026년 AI 에이전트 프레임워크 트렌드 조사",
    expected_output="주요 프레임워크와 트렌드를 정리한 리서치 노트",
    agent=researcher,
)

writing_task = Task(
    description="리서치 노트를 바탕으로 기술 블로그 포스트 작성",
    expected_output="2000자 이상의 구조화된 기술 블로그 포스트",
    agent=writer,
    context=[research_task],  # research_task 결과를 입력으로 사용
)

fact_check_task = Task(
    description="블로그 포스트의 사실 관계 검증",
    expected_output="검증 완료된 최종 포스트 + 수정 사항 목록",
    agent=fact_checker,
    context=[writing_task],
)

# ── 크루 구성 + 실행 ──
crew = Crew(
    agents=[researcher, writer, fact_checker],
    tasks=[research_task, writing_task, fact_check_task],
    process=Process.sequential,   # sequential | hierarchical
    verbose=True,
    memory=True,                  # 크루 레벨 공유 메모리
    embedder={
        "provider": "openai",
        "config": {"model": "text-embedding-3-small"}
    }
)

result = crew.kickoff(
    inputs={"topic": "LLM 에이전트 프레임워크 2026"}
)
print(result.raw)                 # 최종 결과 텍스트

# ── CrewAI가 빛나는 이유 ──
# 1. 가장 적은 코드로 멀티 에이전트 팀 구성
# 2. 내장 메모리 (short-term, long-term, entity, user)
# 3. backstory + goal = 에이전트 성격 자연어 정의
# 4. hierarchical process: 매니저 에이전트가 자동 위임
# CrewAI 선택 기준

✅ 선택해야 할 때:
  - 스테이크홀더 데모, PoC, 빠른 프로토타입
  - 역할 기반 협업 에이전트 (연구→작성→검증)
  - 비개발자도 에이전트 정의 이해 가능해야 할 때
  - 내장 메모리가 필요한 경우

❌ 피해야 할 때:
  - 복잡해질수록 추상화 레이어 디버깅이 어려움
  - 세밀한 상태 제어 필요 (LangGraph로)
  - 타입 안전이 최우선 (PydanticAI로)
  - "CrewAI 벽": 48K stars지만 복잡 워크플로에서 이탈 많음

4. Google ADK — 프로토콜 선도, GCP 네이티브

Google ADK는 A2A(Agent-to-Agent) 프로토콜 네이티브 지원으로 LangGraph, CrewAI 등 타 프레임워크로 만든 에이전트와 표준화된 태스크 인터페이스로 통신할 수 있습니다. 50개 이상의 파트너(Salesforce, ServiceNow 포함)가 A2A를 지원합니다.

from google.adk.agents import LlmAgent, SequentialAgent
from google.adk.tools import google_search, code_executor
from google.adk.sessions import InMemorySessionService
from google.adk.runners import Runner
from google import genai

# ── 서브 에이전트 정의 ──
research_agent = LlmAgent(
    name="research_agent",
    model="gemini-3.5-flash",    # Gemini 네이티브
    instruction="""당신은 리서치 전문 에이전트입니다.
    Google 검색으로 최신 정보를 수집하세요.""",
    tools=[google_search],       # Google 서비스 네이티브 통합
    output_key="research_result",
)

code_agent = LlmAgent(
    name="code_agent",
    model="gemini-3.5-flash",
    instruction="리서치 결과를 바탕으로 Python 코드를 작성하고 실행하세요.",
    tools=[code_executor],       # 코드 실행 샌드박스
    output_key="code_result",
)

# ── 오케스트레이터 (계층적 트리) ──
root_agent = SequentialAgent(
    name="root_agent",
    sub_agents=[research_agent, code_agent],
    # ADK는 계층적 트리 구조
    # 루트 → 서브 에이전트들 순차 또는 병렬 실행
)

# ── 세션 + 러너 설정 ──
session_service = InMemorySessionService()
runner = Runner(
    agent=root_agent,
    app_name="my-agent-app",
    session_service=session_service,
)

# ── A2A 프로토콜로 타 프레임워크 에이전트 호출 ──
# ADK 에이전트가 LangGraph로 만든 에이전트를 호출하는 것이 가능
from google.adk.tools.a2a import A2ATool

external_agent_tool = A2ATool(
    agent_url="http://langgraph-agent.internal/a2a",
    # → LangGraph 에이전트를 ADK 툴로 사용
)

hybrid_agent = LlmAgent(
    name="hybrid",
    model="gemini-3.5-flash",
    tools=[google_search, external_agent_tool],  # 다른 프레임워크 에이전트 포함
)

# ── 멀티모달 (ADK 특화) ──
multimodal_agent = LlmAgent(
    name="vision_agent",
    model="gemini-3.5-flash",   # 네이티브 멀티모달
    instruction="이미지를 분석하고 관련 코드를 생성하세요.",
)

# 이미지 + 텍스트 동시 입력
from google.genai import types

response = runner.run(
    session_id="session-1",
    new_message=types.Content(parts=[
        types.Part(text="이 UI 스크린샷을 보고 React 컴포넌트 만들어줘"),
        types.Part(inline_data=types.Blob(
            mime_type="image/png",
            data=screenshot_bytes
        ))
    ])
)

# ── Google ADK가 빛나는 이유 ──
# 1. A2A: 다른 프레임워크 에이전트와 표준 통신
# 2. Gemini 멀티모달 네이티브 (이미지·오디오·영상 입력)
# 3. GCP Vertex AI, Cloud Run 배포 완전 통합
# 4. MCP + A2A + AG-UI 3가지 프로토콜 선도
# Google ADK 선택 기준

✅ 선택해야 할 때:
  - GCP/Vertex AI 올인 팀
  - 멀티모달 에이전트 (이미지·영상·음성 입력)
  - A2A: 여러 프레임워크 에이전트 간 통신 필요
  - Salesforce·ServiceNow 등 A2A 파트너 연동

❌ 피해야 할 때:
  - GCP 외 클라우드 (AWS, Azure 배포 어려움)
  - 커뮤니티 작음 → 디버깅 자료 부족
  - OpenAI 모델 중심 팀

5. 12개 축 전체 비교 매트릭스

FRAMEWORK_MATRIX = {
    "항목": ["LangGraph", "PydanticAI", "CrewAI", "Google ADK"],

    # 기본 정보
    "GitHub Stars":  ["25K", "16.8K", "47K", "미공개"],
    "월 다운로드":   ["34.5M", "높음", "높음", "낮음"],
    "언어":         ["Python/JS", "Python", "Python", "Python/JS"],

    # 기술 특성
    "오케스트레이션 모델": [
        "그래프(노드+엣지)",
        "단일/멀티 에이전트",
        "역할 기반 크루",
        "계층적 트리"
    ],
    "타입 안전":     ["중간", "최고", "낮음", "중간"],
    "MCP 지원":     ["어댑터", "네이티브", "어댑터", "네이티브"],
    "A2A 지원":     ["❌", "❌", "❌", "✅ 네이티브"],
    "내장 메모리":   ["체크포인터(상태)", "❌ 직접 구현", "✅ 4가지 타입", "✅ 3가지 타입"],
    "멀티모달":     ["❌", "모델 의존", "모델 의존", "✅ Gemini 네이티브"],

    # 개발 경험
    "학습 곡선":    ["가파름", "FastAPI 팀은 쉬움", "가장 완만", "중간"],
    "프로토타입 속도": ["느림", "중간", "가장 빠름", "중간"],
    "테스트 용이성": ["중간(LangSmith)", "최고(TestModel)", "낮음", "중간"],

    # 프로덕션
    "프로덕션 성숙도": ["가장 성숙", "빠르게 성숙 중", "중간", "초기"],
    "배포 유연성":   ["높음", "높음", "높음", "GCP 최적화"],
    "옵저버빌리티": ["LangSmith 최강", "Logfire/OTel", "제한적", "Cloud Trace"],
    "프로덕션 레퍼런스": ["Uber·Klarna·JPMorgan", "증가 중", "스타트업", "Google 고객"],
}

6. 동일 태스크 코드 비교 — "고객 지원 에이전트"

4개 프레임워크로 동일한 태스크(고객 문의 → DB 조회 → 답변)를 구현하면 코드 차이가 극명하게 드러납니다.

# ── LangGraph ──
from langgraph.graph import StateGraph, END

class State(TypedDict):
    query: str
    customer_data: dict
    response: str

def fetch_customer(state):
    data = db.query(state["query"])
    return {"customer_data": data}

def generate_response(state):
    resp = llm.call(state["query"], state["customer_data"])
    return {"response": resp}

builder = StateGraph(State)
builder.add_node("fetch", fetch_customer)
builder.add_node("respond", generate_response)
builder.set_entry_point("fetch")
builder.add_edge("fetch", "respond")
builder.add_edge("respond", END)
graph = builder.compile()
# → 명시적, 완전한 통제, 코드 많음


# ── PydanticAI ──
@dataclass
class Deps:
    db: Database

class Response(BaseModel):
    answer: str
    confidence: float

agent = Agent("anthropic:claude-sonnet-4-6",
    deps_type=Deps, output_type=Response)

@agent.tool
async def get_customer(ctx: RunContext[Deps], query: str) -> dict:
    """고객 정보를 조회합니다."""
    return await ctx.deps.db.fetch(query)

result = await agent.run("고객 문의 처리", deps=Deps(db=db))
# → 간결, 타입 안전, 중간 코드량


# ── CrewAI ──
support_agent = Agent(
    role="고객 지원 전문가",
    goal="고객 문의를 신속하고 정확하게 해결",
    backstory="5년 경력 고객 지원 전문가입니다.",
    tools=[DBQueryTool()],
    llm="anthropic/claude-sonnet-4-6"
)
task = Task(
    description="다음 고객 문의를 처리하세요",
    agent=support_agent
)
crew = Crew(agents=[support_agent], tasks=[task])
result = crew.kickoff(inputs={"query": "고객 문의 내용"})
# → 가장 적은 코드, 자연어 정의


# ── Google ADK ──
support_agent = LlmAgent(
    name="support_agent",
    model="gemini-3.5-flash",
    instruction="고객 지원 전문가로서 문의를 처리하세요.",
    tools=[db_query_tool],
)
runner = Runner(agent=support_agent,
    session_service=InMemorySessionService())
result = runner.run(session_id="s1", new_message=Content(...))
# → GCP 연동 쉬움, Gemini 최적화

7. 의사결정 트리 — 지금 어떤 걸 선택해야 하나

def choose_framework(requirements: dict) -> str:
    """
    팀과 태스크에 맞는 프레임워크 선택
    """

    # GCP 네이티브 또는 멀티모달
    if requirements.get("gcp_native") or requirements.get("multimodal"):
        return "Google ADK"

    # 규제 환경 또는 복잡한 워크플로
    if (requirements.get("regulated_env") or     # 금융, 의료, 법률
        requirements.get("complex_workflow") or   # 많은 조건 분기
        requirements.get("audit_trail") or        # 모든 결정 감사 필요
        requirements.get("hitl_required")):       # 인간 승인 단계
        return "LangGraph"

    # 빠른 프로토타입 또는 데모
    if (requirements.get("fast_prototype") or
        requirements.get("role_based_team") or   # 연구→작성→검증 역할 분담
        requirements.get("built_in_memory")):    # 내장 메모리 필요
        return "CrewAI"

    # 타입 안전 또는 FastAPI 팀
    if (requirements.get("type_safety") or
        requirements.get("fastapi_team") or
        requirements.get("testability")):        # CI/CD 테스트 중요
        return "PydanticAI"

    # 기본값: 팀 규모와 복잡도에 따라
    if requirements.get("team_size", 5) > 20:
        return "LangGraph"   # 큰 팀 = 프로덕션 성숙도 중요
    else:
        return "PydanticAI"  # 작은 팀 = 빠른 개발 + 타입 안전


# 실제 사용 케이스 → 프레임워크 매핑
use_case_map = {
    "고객 지원 챗봇 (단순)":           "PydanticAI",
    "금융 거래 승인 파이프라인":        "LangGraph",
    "리서치→작성→검증 멀티에이전트":    "CrewAI",
    "이미지 분석 에이전트":             "Google ADK",
    "스프린트 내 MVP 데모":             "CrewAI",
    "Klarna 수준 프로덕션 에이전트":    "LangGraph",
    "FastAPI 기반 서비스에 AI 추가":    "PydanticAI",
    "GCP Vertex AI 배포 에이전트":      "Google ADK",
}

8. 2026년 현실 — 팀들이 실제로 선택하는 것

# 실제 채택 패턴 (2026년 Q2 기준)

프로덕션 엔터프라이즈:
  LangGraph + LangSmith
  "체크포인터, 감사 추적, 400개 기업 레퍼런스"
  Uber, Klarna, LinkedIn, JPMorgan 사용

스타트업 PoC/데모:
  CrewAI → 한 달 후 LangGraph 마이그레이션
  "CrewAI 벽": 처음엔 빠른데 복잡해질수록 한계

FastAPI Python 팀:
  PydanticAI 빠르게 증가 중
  "같은 패턴, 타입 안전, 테스트 가능"

GCP 올인 팀:
  Google ADK
  "Gemini + Vertex AI + A2A 패키지 딜"

현실적 조합:
  프로토타입: CrewAI
  ↓ 복잡해지면 마이그레이션
  프로덕션: LangGraph (복잡 워크플로)
           또는 PydanticAI (단순 에이전트)

결론

4파전 한줄 요약

  • LangGraph: 가장 많은 프로덕션 레퍼런스, 복잡한 워크플로의 정답, 학습 비용 감수
  • PydanticAI: 타입 안전의 정석, FastAPI 팀의 자연스러운 선택, 단순~중간 에이전트
  • CrewAI: 역할 기반 프로토타입 최강, 빠른 데모, 복잡해지면 벽 만남
  • Google ADK: A2A 프로토콜 선도, 멀티모달, GCP 올인 팀만

2026년 선택 원칙

  • 프레임워크를 먼저 고르지 말 것 — 풀려는 문제의 복잡도가 먼저
  • "CrewAI로 시작 → 복잡해지면 LangGraph"가 가장 흔한 실전 경로
  • 단일 정답 없음 — 투트랙(프로토타입용 + 프로덕션용) 전략이 현실적

흔한 실수

  • GitHub Stars로 선택 (CrewAI 47K > LangGraph 25K지만 프로덕션은 LangGraph가 앞섬)
  • 복잡 워크플로에 CrewAI 고집 → 6개월 후 마이그레이션 비용
  • 프로덕션 레디 전에 Google ADK 선택 → 커뮤니티 얇아서 디버깅 고통

관련 글

반응형