Skip to content

Split View: LLM Agent Framework 비교: AutoGen vs CrewAI vs LangGraph 실전 선택 가이드

✨ Learn with Quiz
|

LLM Agent Framework 비교: AutoGen vs CrewAI vs LangGraph 실전 선택 가이드

LLM Agent Framework 비교

들어가며

LLM 기반 에이전트 시스템은 단순한 챗봇을 넘어서, 도구를 사용하고 계획을 세우며 멀티스텝 작업을 자율적으로 수행하는 AI 애플리케이션의 핵심 아키텍처로 자리잡았다. 2024~2025년 동안 에이전트 프레임워크 생태계는 폭발적으로 성장했고, 2026년 현재 프로덕션 수준의 안정성을 갖춘 세 가지 프레임워크가 시장의 주류로 확립되었다.

AutoGen (Microsoft)은 대화 기반 멀티 에이전트 상호작용에 특화되어, 에이전트 간 자연어 대화를 통해 복잡한 작업을 해결하는 패러다임을 제시했다. CrewAI는 역할 기반 에이전트 팀을 직관적인 API로 구성하여 빠른 프로토타이핑과 비즈니스 워크플로우 자동화에 강점을 보인다. LangGraph (LangChain)는 유향 그래프(directed graph)로 에이전트 워크플로우를 모델링하여, 복잡한 상태 관리와 조건부 분기가 필요한 프로덕션 시스템에 최적화되어 있다.

각 프레임워크는 서로 다른 설계 철학과 강점을 가지고 있으며, 프로젝트의 요구사항에 따라 최적의 선택이 달라진다. 이 글에서는 세 프레임워크의 아키텍처를 심층 비교하고, 실전 코드 예제를 통해 각각의 사용법을 익히며, 프로덕션 배포에서 마주치는 실패 사례와 복구 전략까지 포괄적으로 다룬다.

에이전트 프레임워크가 필요한 이유

단일 LLM 호출의 한계

단순한 LLM API 호출은 다음과 같은 한계를 가진다.

  1. 단발성 응답: 한 번의 호출로 복잡한 멀티스텝 작업을 완료할 수 없다. 조사, 분석, 실행, 검증의 연쇄 작업이 필요한 경우 여러 번의 호출을 조율해야 한다.
  2. 도구 사용 제약: LLM이 외부 API, 데이터베이스, 파일 시스템 등과 상호작용하려면 Function Calling 결과를 다시 LLM에 피드백하는 루프가 필요하다.
  3. 상태 관리 부재: 대화 히스토리, 작업 진행 상황, 중간 결과물을 체계적으로 관리하는 메커니즘이 없다.
  4. 오류 복구 부재: API 호출 실패, 환각(hallucination), 무한 루프 등에 대한 시스템 수준의 대응이 불가능하다.

에이전트 프레임워크의 역할

에이전트 프레임워크는 이러한 문제를 다음과 같이 해결한다.

문제프레임워크 해결 방식
멀티스텝 실행계획(planning) -> 실행(execution) -> 관찰(observation) 루프 자동 관리
도구 통합도구 레지스트리, 입출력 스키마 자동 생성, 결과 파싱
상태 관리대화 메모리, 작업 상태, 중간 결과물 영속화
오류 복구재시도 정책, 타임아웃, 폴백 전략, Human-in-the-loop
관찰성실행 트레이싱, 토큰 사용량 추적, 디버깅 도구

아키텍처 비교

AutoGen: 대화 중심 멀티 에이전트

AutoGen의 핵심 설계 철학은 "에이전트 간 대화를 통한 문제 해결"이다. 모든 상호작용이 메시지 기반이며, 에이전트들은 대화 패턴(conversation pattern)에 따라 발언 순서와 내용이 결정된다.

핵심 구성 요소:

  • AssistantAgent: LLM 기반 에이전트로, 시스템 프롬프트와 도구를 가진다
  • UserProxyAgent: 사용자를 대리하는 에이전트로, 코드 실행이나 사용자 입력 전달을 담당한다 (v0.4에서는 별도의 사용자 프록시 없이 AssistantAgent와 팀으로 구성)
  • GroupChat / Teams: 여러 에이전트가 참여하는 그룹 대화 또는 팀 구조
  • ConversationPattern: RoundRobin, Selector, Swarm 등 대화 흐름 패턴

아키텍처 특징:

  • 모든 에이전트 상호작용이 메시지 교환으로 이루어진다
  • 에이전트 간 관계가 대화 패턴으로 정의된다
  • 코드 실행 환경(Docker, 로컬)을 내장으로 제공한다
  • v0.4(AgentChat)에서 비동기(async) 우선 설계를 채택했다

CrewAI: 역할 기반 에이전트 팀

CrewAI는 "각 에이전트에게 명확한 역할(role)과 목표(goal)를 부여하고, 태스크(task)를 할당하여 팀으로 운영한다"는 직관적인 비유를 사용한다. 실제 조직의 팀 운영과 유사한 모델이다.

핵심 구성 요소:

  • Agent: 역할(role), 목표(goal), 배경(backstory), 도구(tools)를 가진 에이전트
  • Task: 에이전트에게 할당되는 구체적 작업 단위
  • Crew: 에이전트들과 태스크를 조합한 실행 단위
  • Process: Sequential(순차), Hierarchical(계층), Parallel(병렬) 실행 방식

아키텍처 특징:

  • LangChain에 의존하지 않는 독립적 구현(자체 엔진)
  • YAML 기반 에이전트/태스크 설정 파일 분리 지원
  • 태스크 간 결과 위임(delegation) 메커니즘 내장
  • 빠른 프로토타이핑에 최적화된 간결한 API

LangGraph: 그래프 기반 상태 머신

LangGraph는 에이전트 워크플로우를 유향 그래프(directed graph) 로 모델링한다. 각 노드는 실행 함수이고, 엣지는 상태에 따른 전이(transition)를 정의한다. 이는 본질적으로 상태 머신(state machine)의 구조와 동일하다.

핵심 구성 요소:

  • StateGraph: 상태를 관리하는 유향 그래프
  • State (TypedDict): 그래프 전체에서 공유되는 타입 안전한 상태
  • Node: 상태를 입력받아 업데이트를 반환하는 함수
  • Edge: 노드 간 전이를 정의 (조건부 엣지 포함)
  • Checkpointer: 상태를 영속화하여 재개 가능한 실행 지원

아키텍처 특징:

  • 명시적인 제어 흐름으로 예측 가능한 실행
  • 타입 안전한 상태 관리 (TypedDict 또는 Pydantic)
  • 내장 Human-in-the-loop 지원
  • LangSmith와의 통합을 통한 뛰어난 관찰성
  • v1.0 도달 (2025 말)로 API 안정화

멀티 에이전트 패턴 비교표

비교 항목AutoGenCrewAILangGraph
설계 철학대화 중심 협업역할 기반 팀그래프 기반 상태 머신
에이전트 정의AssistantAgent + 시스템 프롬프트Agent(role, goal, backstory)Node 함수 + State
오케스트레이션ConversationPattern (RoundRobin, Selector, Swarm)Process (Sequential, Hierarchical, Parallel)조건부 엣지 + 상태 전이
상태 관리대화 히스토리 기반태스크 간 컨텍스트 전달TypedDict/Pydantic 상태 객체
도구 통합Function 기반 도구 등록@tool 데코레이터 / 내장 도구ToolNode + tools_condition
Human-in-the-loopUserProxy 패턴태스크 수준 승인Interrupt + Checkpointer
코드 실행Docker/로컬 내장별도 도구로 구현별도 도구로 구현
메모리대화 기반 단기/장기단기 + 선택적 장기Checkpointer 기반 영속화
관찰성AutoGen Studio UI로깅 기반LangSmith 통합 (추적/재생)
학습 곡선중간낮음높음
프로덕션 성숙도중간 (MS Agent Framework 전환 중)중상높음 (v1.0)
라이선스MITMITMIT
Python 버전3.10+3.10~3.133.9+

각 프레임워크 실전 코드 예제

AutoGen: 리서치 에이전트 팀 구성

AutoGen v0.4 AgentChat API를 사용한 멀티 에이전트 리서치 시스템 예제이다.

import asyncio
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.teams import RoundRobinGroupChat, SelectorGroupChat
from autogen_agentchat.conditions import MaxMessageTermination, TextMentionTermination
from autogen_ext.models.openai import OpenAIChatCompletionClient

# 1. LLM 클라이언트 설정
model_client = OpenAIChatCompletionClient(
    model="gpt-4o",
    api_key="your-api-key",
)

# 2. 도구 정의
async def search_web(query: str) -> str:
    """웹에서 정보를 검색합니다."""
    # 실제로는 Tavily, SerpAPI 등 사용
    return f"'{query}'에 대한 검색 결과: [검색 결과 요약]"

async def analyze_data(data: str) -> str:
    """데이터를 분석하고 인사이트를 추출합니다."""
    return f"분석 결과: {data[:100]}에 대한 심층 분석 완료"

# 3. 에이전트 정의
researcher = AssistantAgent(
    name="Researcher",
    model_client=model_client,
    system_message="""당신은 전문 리서처입니다.
    주어진 주제에 대해 웹 검색을 수행하고 관련 정보를 수집합니다.
    수집한 정보를 구조화하여 Analyst에게 전달합니다.""",
    tools=[search_web],
)

analyst = AssistantAgent(
    name="Analyst",
    model_client=model_client,
    system_message="""당신은 데이터 분석가입니다.
    Researcher가 수집한 정보를 분석하고 핵심 인사이트를 추출합니다.
    분석 결과를 Writer에게 전달합니다.""",
    tools=[analyze_data],
)

writer = AssistantAgent(
    name="Writer",
    model_client=model_client,
    system_message="""당신은 보고서 작성자입니다.
    Analyst의 분석 결과를 바탕으로 최종 보고서를 작성합니다.
    보고서 작성이 완료되면 'TERMINATE'를 출력합니다.""",
)

# 4. 팀 구성 및 실행
termination = MaxMessageTermination(max_messages=15) | TextMentionTermination("TERMINATE")

# SelectorGroupChat: LLM이 다음 발언자를 선택
team = SelectorGroupChat(
    participants=[researcher, analyst, writer],
    model_client=model_client,
    termination_condition=termination,
    selector_prompt="""다음 발언자를 선택하세요.
    정보 수집이 필요하면 Researcher, 분석이 필요하면 Analyst,
    최종 보고서 작성이 필요하면 Writer를 선택합니다.""",
)

async def main():
    result = await team.run(
        task="2026년 AI 에이전트 프레임워크 시장 동향을 조사하고 보고서를 작성하세요."
    )
    for message in result.messages:
        print(f"[{message.source}]: {message.content[:200]}")
    print(f"\n총 메시지 수: {len(result.messages)}")

asyncio.run(main())

CrewAI: 콘텐츠 제작 파이프라인

CrewAI를 사용한 블로그 포스트 자동 생성 파이프라인 예제이다.

from crewai import Agent, Task, Crew, Process
from crewai.tools import tool
from langchain_openai import ChatOpenAI

# 1. LLM 설정
llm = ChatOpenAI(model="gpt-4o", temperature=0.7)

# 2. 커스텀 도구 정의
@tool("Search Tool")
def search_tool(query: str) -> str:
    """주어진 쿼리로 웹 검색을 수행합니다."""
    # 실제로는 Tavily, SerpAPI 등 사용
    return f"'{query}' 검색 결과: [관련 정보 요약]"

@tool("SEO Analyzer")
def seo_analyzer(content: str) -> str:
    """콘텐츠의 SEO 점수를 분석합니다."""
    word_count = len(content.split())
    return f"단어 수: {word_count}, SEO 점수: {'양호' if word_count > 500 else '부족'}"

# 3. 에이전트 정의
research_agent = Agent(
    role="기술 리서처",
    goal="최신 기술 트렌드를 조사하고 정확한 정보를 수집한다",
    backstory="""10년 경력의 기술 저널리스트로, AI와 소프트웨어 엔지니어링
    분야의 최신 동향을 항상 파악하고 있습니다.""",
    tools=[search_tool],
    llm=llm,
    verbose=True,
    allow_delegation=True,  # 다른 에이전트에게 작업 위임 가능
)

writer_agent = Agent(
    role="기술 블로그 작성자",
    goal="독자가 쉽게 이해할 수 있는 깊이 있는 기술 블로그를 작성한다",
    backstory="""기술 블로그 전문 작가로, 복잡한 기술 개념을
    명확하고 실용적인 글로 풀어내는 것에 능숙합니다.""",
    llm=llm,
    verbose=True,
)

editor_agent = Agent(
    role="편집장",
    goal="콘텐츠의 품질, 정확성, SEO 최적화를 검수한다",
    backstory="""기술 미디어 편집장으로, 콘텐츠의 기술적 정확성과
    가독성, SEO 최적화를 동시에 추구합니다.""",
    tools=[seo_analyzer],
    llm=llm,
    verbose=True,
)

# 4. 태스크 정의
research_task = Task(
    description="""'{topic}'에 대한 종합 리서치를 수행합니다.
    - 최신 동향 3가지 이상 수집
    - 핵심 기술 개념 정리
    - 실제 사용 사례 2건 이상 포함""",
    expected_output="구조화된 리서치 보고서 (마크다운 형식)",
    agent=research_agent,
)

writing_task = Task(
    description="""리서치 결과를 바탕으로 기술 블로그 포스트를 작성합니다.
    - 2000자 이상의 본문
    - 코드 예제 2개 이상 포함
    - 실무자 관점의 인사이트 포함""",
    expected_output="완성된 블로그 포스트 (마크다운 형식)",
    agent=writer_agent,
    context=[research_task],  # research_task 결과를 컨텍스트로 받음
)

editing_task = Task(
    description="""작성된 블로그 포스트를 검수합니다.
    - 기술적 정확성 확인
    - 가독성 개선
    - SEO 분석 수행
    - 최종 수정본 출력""",
    expected_output="검수 완료된 최종 블로그 포스트",
    agent=editor_agent,
    context=[writing_task],
)

# 5. Crew 구성 및 실행
crew = Crew(
    agents=[research_agent, writer_agent, editor_agent],
    tasks=[research_task, writing_task, editing_task],
    process=Process.sequential,  # 순차 실행
    verbose=True,
    memory=True,                 # 단기 메모리 활성화
    max_rpm=10,                  # API 호출 속도 제한
)

# 실행
result = crew.kickoff(inputs={"topic": "LLM 에이전트 프레임워크 비교"})
print(f"최종 결과:\n{result}")
print(f"토큰 사용량: {crew.usage_metrics}")

LangGraph: 조건부 분기가 있는 고객 지원 에이전트

LangGraph를 사용한 상태 기반 고객 지원 에이전트 예제이다. 조건부 분기, Human-in-the-loop, 오류 복구를 포함한다.

from typing import Annotated, Literal, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import MemorySaver
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, SystemMessage

# 1. 상태 정의
class SupportState(TypedDict):
    messages: Annotated[list, add_messages]
    category: str           # 문의 카테고리 (billing, technical, general)
    sentiment: str          # 고객 감정 (positive, neutral, negative)
    escalated: bool         # 상위 담당자 에스컬레이션 여부
    resolution: str         # 해결 상태

# 2. 도구 정의
@tool
def lookup_order(order_id: str) -> str:
    """주문 정보를 조회합니다."""
    orders = {
        "ORD-001": {"status": "배송중", "eta": "2026-03-11"},
        "ORD-002": {"status": "배송완료", "delivered": "2026-03-07"},
    }
    if order_id in orders:
        return f"주문 {order_id}: {orders[order_id]}"
    return f"주문 {order_id}를 찾을 수 없습니다."

@tool
def create_ticket(summary: str, priority: str) -> str:
    """고객 지원 티켓을 생성합니다."""
    return f"티켓 생성 완료 - 요약: {summary}, 우선순위: {priority}, 티켓 ID: TKT-{hash(summary) % 10000}"

@tool
def refund_order(order_id: str, reason: str) -> str:
    """주문 환불을 처리합니다. (승인 필요)"""
    return f"환불 요청 접수: {order_id}, 사유: {reason}. 관리자 승인 대기중."

tools = [lookup_order, create_ticket, refund_order]
llm = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)

# 3. 노드 함수 정의
def classify_intent(state: SupportState) -> dict:
    """고객 문의를 분류합니다."""
    classifier_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
    last_message = state["messages"][-1].content

    response = classifier_llm.invoke([
        SystemMessage(content="""고객 문의를 분류하세요.
        카테고리: billing, technical, general
        감정: positive, neutral, negative
        JSON 형식으로 응답: {"category": "...", "sentiment": "..."}"""),
        HumanMessage(content=last_message)
    ])

    import json
    try:
        result = json.loads(response.content)
    except json.JSONDecodeError:
        result = {"category": "general", "sentiment": "neutral"}

    return {
        "category": result.get("category", "general"),
        "sentiment": result.get("sentiment", "neutral"),
    }

def agent_respond(state: SupportState) -> dict:
    """에이전트가 응답합니다."""
    system_prompt = f"""당신은 고객 지원 에이전트입니다.
    현재 문의 카테고리: {state.get('category', 'unknown')}
    고객 감정: {state.get('sentiment', 'unknown')}
    에스컬레이션 여부: {state.get('escalated', False)}

    고객에게 친절하고 정확하게 응답하세요.
    필요한 경우 도구를 사용하여 정보를 조회하거나 티켓을 생성하세요."""

    messages = [SystemMessage(content=system_prompt)] + state["messages"]
    response = llm.invoke(messages)
    return {"messages": [response]}

def check_escalation(state: SupportState) -> dict:
    """에스컬레이션 필요 여부를 판단합니다."""
    if state.get("sentiment") == "negative" and state.get("category") == "billing":
        return {"escalated": True}
    return {"escalated": False}

def human_escalation(state: SupportState) -> dict:
    """상위 담당자에게 에스컬레이션합니다."""
    return {
        "messages": [SystemMessage(content="[시스템] 이 문의는 상위 담당자에게 전달되었습니다. 잠시 기다려주세요.")],
        "resolution": "escalated_to_human",
    }

# 4. 라우팅 함수
def route_after_classification(state: SupportState) -> Literal["check_escalation", "agent_respond"]:
    """분류 결과에 따라 라우팅합니다."""
    if state.get("sentiment") == "negative":
        return "check_escalation"
    return "agent_respond"

def route_after_escalation_check(state: SupportState) -> Literal["human_escalation", "agent_respond"]:
    """에스컬레이션 판단 결과에 따라 라우팅합니다."""
    if state.get("escalated"):
        return "human_escalation"
    return "agent_respond"

# 5. 그래프 구성
workflow = StateGraph(SupportState)

# 노드 추가
workflow.add_node("classify_intent", classify_intent)
workflow.add_node("check_escalation", check_escalation)
workflow.add_node("agent_respond", agent_respond)
workflow.add_node("tools", ToolNode(tools))
workflow.add_node("human_escalation", human_escalation)

# 엣지 정의
workflow.add_edge(START, "classify_intent")
workflow.add_conditional_edges("classify_intent", route_after_classification)
workflow.add_conditional_edges("check_escalation", route_after_escalation_check)
workflow.add_conditional_edges("agent_respond", tools_condition, {"tools": "tools", END: END})
workflow.add_edge("tools", "agent_respond")
workflow.add_edge("human_escalation", END)

# 체크포인터 (상태 영속화)
checkpointer = MemorySaver()
app = workflow.compile(checkpointer=checkpointer)

# 6. 실행
config = {"configurable": {"thread_id": "customer-123"}}
result = app.invoke(
    {"messages": [HumanMessage(content="주문 ORD-001 배송이 너무 늦어요. 환불해주세요.")]},
    config=config,
)

for msg in result["messages"]:
    print(f"[{msg.__class__.__name__}]: {msg.content[:200]}")
print(f"카테고리: {result['category']}, 감정: {result['sentiment']}")

도구 통합 비교

각 프레임워크에서 외부 도구를 통합하는 방식을 비교한다.

도구 정의 패턴 비교

항목AutoGenCrewAILangGraph
정의 방식Python 함수 + type hint@tool 데코레이터 또는 BaseTool 상속@tool 데코레이터 (LangChain)
스키마 자동 생성함수 시그니처 기반docstring + type hintdocstring + type hint
비동기 지원async 함수 네이티브제한적async 지원
에러 처리try/except 직접 구현ToolException 클래스ToolException + 폴백
내장 도구코드 실행, 웹 검색SerperDev, 파일 읽기/쓰기, 코드 인터프리터웹 검색, Retriever, Python REPL

주요 차이점

AutoGen은 Python 함수를 직접 도구로 등록하는 가장 간결한 방식을 사용한다. 함수의 type hint와 docstring으로부터 자동으로 JSON 스키마가 생성되며, async 함수를 네이티브로 지원한다. 코드 실행 환경을 기본 제공하여 에이전트가 생성한 코드를 Docker 컨테이너 또는 로컬 환경에서 즉시 실행할 수 있다는 점이 독특한 강점이다.

CrewAI@tool 데코레이터로 도구를 정의하거나, BaseTool 클래스를 상속하여 더 복잡한 도구를 구현할 수 있다. 내장 도구 라이브러리가 풍부하며, 에이전트가 다른 에이전트에게 작업을 위임하는 것 자체도 일종의 도구로 동작한다.

LangGraph는 LangChain의 도구 생태계를 그대로 활용한다. ToolNodetools_condition을 사용하여 도구 호출과 결과 처리를 그래프의 노드로 명시적으로 모델링한다. 이를 통해 도구 호출 전후에 커스텀 로직(로깅, 검증, 변환)을 삽입하기 용이하다.

메모리와 상태 관리

단기 메모리 (대화 컨텍스트)

프레임워크단기 메모리 방식윈도우 관리토큰 제한 처리
AutoGen메시지 리스트 자동 관리수동 또는 summarize 패턴대화 요약 에이전트 활용
CrewAI태스크 간 컨텍스트 자동 전달자동 (설정 가능)자동 요약
LangGraphState의 messages 필드add_messages reducerTrimmer/Summarizer 노드

장기 메모리 (세션 간 영속화)

AutoGen은 대화 히스토리를 파일이나 데이터베이스에 저장하는 커스텀 구현이 필요하다. AutoGen Studio UI를 통해 세션 관리 기능을 제공하지만, 프로그래밍 수준의 장기 메모리 API는 제한적이다.

CrewAImemory=True 설정으로 단기 메모리를 활성화하고, 선택적으로 ChromaDB 등의 벡터 스토어를 사용한 장기 메모리를 설정할 수 있다. 에이전트가 과거 작업에서 학습한 패턴을 자동으로 활용하는 메커니즘이 내장되어 있다.

LangGraphCheckpointer 인터페이스를 통해 가장 체계적인 상태 영속화를 제공한다. MemorySaver(인메모리), SqliteSaver, PostgresSaver 등 다양한 백엔드를 지원하며, thread_id 기반으로 대화를 재개하고, 특정 시점으로 되돌리는 것이 가능하다. 이는 프로덕션 환경에서 매우 중요한 기능이다.

프로덕션 배포 고려사항

배포 아키텍처 비교

항목AutoGenCrewAILangGraph
서빙 방식FastAPI 직접 구현FastAPI/Flask 직접 구현LangServe / LangGraph Cloud
스케일링수동 구현수동 구현LangGraph Platform (관리형)
상태 영속화커스텀 구현내장 (제한적)Checkpointer (PostgreSQL 등)
동시성asyncio 기반스레드/프로세스 기반asyncio + Checkpointer 기반
모니터링커스텀 로깅커스텀 로깅LangSmith (트레이싱, 평가)
비용 추적콜백 기반usage_metrics 내장LangSmith 토큰 추적

프로덕션 체크리스트

  1. 레이트 리밋 관리: 각 에이전트의 LLM 호출 빈도를 제어한다. CrewAI의 max_rpm, LangGraph의 커스텀 미들웨어, AutoGen의 콜백을 활용한다.
  2. 타임아웃 설정: 에이전트가 무한 루프에 빠지는 것을 방지한다. 최대 반복 횟수와 전체 실행 시간 제한을 반드시 설정한다.
  3. 비용 상한선: 토큰 사용량을 실시간 모니터링하고, 예산 초과 시 실행을 중단하는 안전장치를 구현한다.
  4. 오류 격리: 하나의 에이전트 실패가 전체 시스템을 중단시키지 않도록 서킷 브레이커 패턴을 적용한다.
  5. 감사 로깅: 에이전트의 모든 의사결정과 도구 호출을 추적 가능한 형태로 기록한다.

실패 사례와 복구 전략

사례 1: 에이전트 무한 루프

증상: 에이전트가 같은 도구를 반복 호출하거나, 두 에이전트가 끝없이 대화를 주고받는다.

원인: 종료 조건이 모호하거나, 에이전트의 프롬프트가 명확한 완료 기준을 제시하지 않는 경우 발생한다.

프레임워크별 복구:

  • AutoGen: MaxMessageTermination(max_messages=20)으로 최대 메시지 수 제한. TextMentionTermination("TERMINATE")과 조합하여 이중 안전장치 구성.
  • CrewAI: max_iter 파라미터로 에이전트의 최대 반복 횟수 제한. Crew(max_rpm=10)으로 API 호출 속도도 제한.
  • LangGraph: recursion_limit 파라미터로 그래프 순회 최대 횟수 제한. 상태에 step_count를 추가하고 조건부 엣지에서 확인.

사례 2: 도구 호출 실패와 환각

증상: 에이전트가 존재하지 않는 도구를 호출하거나, 도구에 잘못된 인자를 전달한다.

원인: LLM이 도구의 스키마를 정확히 이해하지 못하거나, 컨텍스트 윈도우가 부족하여 도구 정의를 "잊는" 경우 발생한다.

복구 전략:

  • 도구의 docstring과 파라미터 설명을 최대한 명확하고 상세하게 작성한다.
  • 도구 호출 결과에 대한 검증 레이어를 추가한다. 예상 형식과 다른 결과가 반환되면 재시도하거나 폴백 로직을 실행한다.
  • LangGraph에서는 도구 노드 다음에 검증 노드를 추가하여 결과의 유효성을 확인할 수 있다.

사례 3: 컨텍스트 윈도우 초과

증상: 긴 대화나 대량의 도구 결과로 인해 토큰 제한에 도달하여 에이전트가 핵심 정보를 잃는다.

원인: 멀티 에이전트 시스템에서는 메시지가 기하급수적으로 누적되어 컨텍스트 윈도우를 빠르게 소진한다.

복구 전략:

  • 대화 히스토리를 주기적으로 요약하여 압축한다.
  • 도구 결과를 요약하거나 핵심 정보만 추출하여 컨텍스트에 포함한다.
  • LangGraph에서는 trim_messages 유틸리티를 사용하여 최근 N개 메시지만 유지한다.

사례 4: 에이전트 간 태스크 위임 실패

증상: CrewAI에서 에이전트가 다른 에이전트에게 작업을 위임했지만, 위임받은 에이전트가 맥락을 이해하지 못하고 엉뚱한 결과를 반환한다.

원인: 위임 시 충분한 컨텍스트가 전달되지 않거나, 위임 대상 에이전트의 역할과 맞지 않는 작업이 전달된 경우이다.

복구 전략:

  • 에이전트의 allow_delegation=False로 불필요한 위임을 방지한다.
  • 태스크 설명에 구체적인 입출력 형식을 명시한다.
  • context 파라미터를 통해 선행 태스크의 결과를 명시적으로 전달한다.

사례 5: API 장애 시 전체 시스템 중단

증상: LLM API 장애 또는 도구 API 장애로 인해 전체 에이전트 시스템이 중단된다.

원인: 단일 실패 지점에 대한 폴백 메커니즘이 없는 경우이다.

복구 전략:

# LangGraph에서의 오류 복구 패턴 예시
from tenacity import retry, stop_after_attempt, wait_exponential
from langchain_core.messages import AIMessage

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, max=10))
def resilient_agent_node(state: SupportState) -> dict:
    """재시도 로직이 포함된 에이전트 노드."""
    try:
        response = llm.invoke(state["messages"])
        return {"messages": [response]}
    except Exception as e:
        # 폴백: 미리 정의된 응답 반환
        fallback_response = AIMessage(
            content="죄송합니다. 일시적인 시스템 오류가 발생했습니다. "
                    "잠시 후 다시 시도해주세요. 긴급한 경우 고객센터 "
                    "1588-0000으로 연락해주세요."
        )
        return {
            "messages": [fallback_response],
            "resolution": f"error_fallback: {str(e)}",
        }

선택 가이드: 의사결정 트리

프로젝트의 요구사항에 따른 프레임워크 선택 기준을 정리한다.

CrewAI를 선택해야 하는 경우

  • 빠른 프로토타이핑이 가장 중요한 경우. 수 시간 내에 멀티 에이전트 시스템을 구축하고 시연해야 할 때.
  • 역할 기반 워크플로우가 자연스러운 경우. "리서처 -> 분석가 -> 작성자" 같은 선형적 파이프라인.
  • 비기술 이해관계자에게 에이전트 정의를 보여줘야 하는 경우. role/goal/backstory 형식이 가장 직관적이다.
  • 프로덕션 수준의 상태 관리나 복잡한 조건부 로직이 크게 필요하지 않은 경우.

AutoGen을 선택해야 하는 경우

  • 에이전트 간 자유로운 대화가 핵심인 경우. 토론, 합의, 브레인스토밍 같은 대화 패턴.
  • 코드 생성 및 실행이 주요 기능인 경우. 내장 코드 실행 환경이 큰 장점.
  • Microsoft 생태계와의 통합이 필요한 경우. Azure OpenAI, Microsoft Agent Framework과의 호환성.
  • .NET 환경에서도 에이전트를 실행해야 하는 경우. AutoGen은 .NET 버전도 제공한다.

LangGraph를 선택해야 하는 경우

  • 복잡한 조건부 로직과 분기가 필요한 경우. 상태에 따라 실행 경로가 동적으로 변경되는 워크플로우.
  • 프로덕션 수준의 안정성이 필수인 경우. 상태 영속화, 재개 가능한 실행, 감사 로그.
  • Human-in-the-loop가 핵심 요구사항인 경우. 특정 단계에서 사람의 승인을 받은 뒤 진행.
  • **관찰성(Observability)**이 중요한 경우. LangSmith를 통한 상세한 트레이싱과 디버깅.
  • 장기 실행 워크플로우를 지원해야 하는 경우. 수 시간에서 수 일에 걸친 비동기 작업 관리.

의사결정 요약 표

우선 고려사항1순위 추천2순위 추천비고
프로토타이핑 속도CrewAIAutoGenLangGraph는 초기 셋업 비용이 높다
프로덕션 안정성LangGraphCrewAIAutoGen은 MS Agent Framework 전환 과도기
대화형 에이전트AutoGenLangGraphCrewAI는 대화보다 태스크 중심
복잡한 워크플로우LangGraphAutoGen조건부 분기, 병렬 실행, 상태 머신
코드 실행AutoGenLangGraphAutoGen의 코드 실행 환경이 가장 성숙
관찰성/디버깅LangGraphAutoGenLangSmith 통합이 압도적 장점
학습 곡선CrewAIAutoGenLangGraph는 그래프 개념 이해 필요
커뮤니티/생태계LangGraphCrewAILangChain 생태계의 규모가 가장 큼

운영 시 주의사항

비용 관리

멀티 에이전트 시스템은 단일 LLM 호출 대비 토큰 소비가 5~20배 증가할 수 있다. 에이전트 수가 늘어나고 대화가 길어질수록 비용이 기하급수적으로 증가한다. 반드시 에이전트별, 태스크별 토큰 사용량을 추적하고 예산 상한선을 설정해야 한다. 가능하면 분류기 같은 간단한 작업에는 GPT-4o-mini 같은 소형 모델을, 핵심 추론에만 대형 모델을 사용하는 모델 라우팅 전략을 적용한다.

보안 고려사항

에이전트가 도구를 통해 외부 시스템에 접근할 때 다음을 주의한다.

  • 최소 권한 원칙: 에이전트에게 필요한 최소한의 API 권한만 부여한다. 읽기 전용 작업에 쓰기 권한을 주지 않는다.
  • 입력 검증: 에이전트가 도구에 전달하는 인자를 검증한다. SQL Injection이나 Command Injection 공격 벡터가 될 수 있다.
  • 출력 필터링: 에이전트의 응답에 민감 정보(PII, 자격증명)가 포함되지 않도록 필터링 레이어를 추가한다.
  • 샌드박싱: 코드 실행 도구는 반드시 격리된 환경(Docker 컨테이너)에서 실행한다.

테스트 전략

에이전트 시스템의 테스트는 전통적인 소프트웨어 테스트보다 복잡하다. LLM의 비결정적 출력 때문에 동일한 입력에 대해 다른 결과가 나올 수 있다.

  • 단위 테스트: 각 도구 함수를 독립적으로 테스트한다. 에이전트 없이 도구의 입출력을 검증한다.
  • 통합 테스트: 모의(mock) LLM 응답을 사용하여 에이전트의 도구 호출 패턴을 검증한다.
  • 평가(Eval): LLM-as-Judge 패턴을 사용하여 에이전트의 최종 출력 품질을 자동 평가한다. LangSmith의 평가 기능이나 별도의 평가 파이프라인을 구축한다.
  • 부하 테스트: 동시 사용자 수와 요청 빈도에 따른 시스템 성능을 측정한다. 특히 LLM API의 레이트 리밋에 주의한다.

버전 관리와 마이그레이션

세 프레임워크 모두 빠르게 발전하고 있어 API 변경이 빈번하다. 특히 AutoGen은 v0.2에서 v0.4(AgentChat)로의 대규모 아키텍처 변경이 있었고, 향후 Microsoft Agent Framework로의 전환이 예고되어 있다. 프레임워크 버전을 고정하고(pinning), 주요 업데이트 시 별도의 브랜치에서 마이그레이션 테스트를 수행하는 것이 안전하다.

마치며

LLM 에이전트 프레임워크의 선택은 은탄환이 없다. CrewAI는 "빠르게 만들어서 보여주는" 시나리오에서 빛을 발하고, AutoGen은 에이전트 간 풍부한 대화 상호작용이 핵심인 경우에 적합하며, LangGraph는 프로덕션 수준의 안정성과 제어가 필요한 복잡한 워크플로우에 최적이다.

실무에서 권장하는 접근은 다음과 같다. 먼저 CrewAI로 빠르게 프로토타입을 구축하여 에이전트 시스템의 가치를 검증한다. 검증이 완료되면 프로덕션 요구사항(상태 관리, 재개 가능성, 관찰성, Human-in-the-loop)을 기준으로 LangGraph로 재구현하는 것을 고려한다. 대화 중심의 에이전트가 핵심이라면 AutoGen을 선택하되, Microsoft Agent Framework로의 전환 계획을 함께 수립한다.

어떤 프레임워크를 선택하든, 에이전트 시스템의 성공은 프레임워크 자체보다는 프롬프트 엔지니어링의 품질, 도구 설계의 견고함, 그리고 실패 모드에 대한 철저한 대비에 달려 있다는 점을 기억해야 한다.

참고자료

Comparing LLM Agent Frameworks: AutoGen vs CrewAI vs LangGraph — A Practical Selection Guide

Comparing LLM Agent Frameworks

Introduction

LLM-based agent systems have evolved well beyond simple chatbots, establishing themselves as a core architecture for AI applications that use tools, formulate plans, and autonomously carry out multi-step tasks. The agent framework ecosystem grew explosively during 2024–2025, and as of 2026, three frameworks with production-grade stability have emerged as the market mainstream.

AutoGen (Microsoft) specializes in conversation-driven multi-agent interaction, presenting a paradigm in which agents solve complex tasks through natural language dialogue with one another. CrewAI excels at rapid prototyping and business workflow automation by composing role-based agent teams through an intuitive API. LangGraph (LangChain) models agent workflows as directed graphs and is optimized for production systems that require sophisticated state management and conditional branching.

Each framework embodies a distinct design philosophy and set of strengths, and the optimal choice varies depending on the project's requirements. In this article, we provide an in-depth architectural comparison of all three frameworks, walk through practical code examples for each, and comprehensively cover failure scenarios and recovery strategies encountered in production deployments.

Why You Need an Agent Framework

Limitations of a Single LLM Call

Simple LLM API calls have the following limitations:

  1. One-shot responses: A single call cannot complete complex multi-step tasks. Chains of research, analysis, execution, and verification require orchestrating multiple calls.
  2. Tool use constraints: For an LLM to interact with external APIs, databases, and file systems, a loop that feeds Function Calling results back to the LLM is needed.
  3. No state management: There is no built-in mechanism to systematically manage conversation history, task progress, or intermediate artifacts.
  4. No error recovery: System-level handling of API failures, hallucinations, and infinite loops is impossible.

The Role of an Agent Framework

Agent frameworks address these issues as follows:

ProblemFramework Solution
Multi-step executionAutomatic management of the planning -> execution -> observation loop
Tool integrationTool registry, automatic I/O schema generation, result parsing
State managementConversation memory, task state, intermediate artifact persistence
Error recoveryRetry policies, timeouts, fallback strategies, Human-in-the-loop
ObservabilityExecution tracing, token usage tracking, debugging tools

Architecture Comparison

AutoGen: Conversation-Centric Multi-Agent

AutoGen's core design philosophy is "problem-solving through inter-agent conversation." All interactions are message-based, and the speaking order and content of agents are determined by conversation patterns.

Key Components:

  • AssistantAgent: An LLM-based agent equipped with a system prompt and tools
  • UserProxyAgent: An agent that acts as a proxy for the user, handling code execution or forwarding user input (in v0.4, teams are composed with AssistantAgents without a separate user proxy)
  • GroupChat / Teams: Group conversations or team structures involving multiple agents
  • ConversationPattern: Conversation flow patterns such as RoundRobin, Selector, and Swarm

Architecture Characteristics:

  • All agent interactions take place through message exchange
  • Inter-agent relationships are defined by conversation patterns
  • Built-in code execution environments (Docker, local) are provided
  • v0.4 (AgentChat) adopts an async-first design

CrewAI: Role-Based Agent Teams

CrewAI uses the intuitive metaphor of "assigning each agent a clear role and goal, allocating tasks, and operating them as a team." It mirrors how real organizations run their teams.

Key Components:

  • Agent: An agent with a role, goal, backstory, and tools
  • Task: A specific unit of work assigned to an agent
  • Crew: An execution unit combining agents and tasks
  • Process: Execution modes — Sequential, Hierarchical, or Parallel

Architecture Characteristics:

  • Independent implementation not dependent on LangChain (its own engine)
  • Supports YAML-based agent/task configuration file separation
  • Built-in task delegation mechanism between tasks
  • Concise API optimized for rapid prototyping

LangGraph: Graph-Based State Machine

LangGraph models agent workflows as directed graphs. Each node is an execution function, and edges define transitions based on state. This is essentially the same structure as a state machine.

Key Components:

  • StateGraph: A directed graph that manages state
  • State (TypedDict): Type-safe state shared across the entire graph
  • Node: A function that receives state as input and returns updates
  • Edge: Defines transitions between nodes (including conditional edges)
  • Checkpointer: Supports resumable execution by persisting state

Architecture Characteristics:

  • Predictable execution through explicit control flow
  • Type-safe state management (TypedDict or Pydantic)
  • Built-in Human-in-the-loop support
  • Excellent observability through LangSmith integration
  • API stabilized with v1.0 release (late 2025)

Multi-Agent Pattern Comparison Table

Comparison ItemAutoGenCrewAILangGraph
Design PhilosophyConversation-centric collaborationRole-based teamsGraph-based state machine
Agent DefinitionAssistantAgent + system promptAgent(role, goal, backstory)Node function + State
OrchestrationConversationPattern (RoundRobin, Selector, Swarm)Process (Sequential, Hierarchical, Parallel)Conditional edges + state transitions
State ManagementConversation history-basedContext passing between tasksTypedDict/Pydantic state object
Tool IntegrationFunction-based tool registration@tool decorator / built-in toolsToolNode + tools_condition
Human-in-the-LoopUserProxy patternTask-level approvalInterrupt + Checkpointer
Code ExecutionBuilt-in Docker/localImplemented via separate toolsImplemented via separate tools
MemoryConversation-based short/long-termShort-term + optional long-termCheckpointer-based persistence
ObservabilityAutoGen Studio UILogging-basedLangSmith integration (tracing/replay)
Learning CurveMediumLowHigh
Production MaturityMedium (transitioning to MS Agent Framework)Medium-HighHigh (v1.0)
LicenseMITMITMIT
Python Version3.10+3.10–3.133.9+

Practical Code Examples for Each Framework

AutoGen: Building a Research Agent Team

An example of a multi-agent research system using the AutoGen v0.4 AgentChat API.

import asyncio
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.teams import RoundRobinGroupChat, SelectorGroupChat
from autogen_agentchat.conditions import MaxMessageTermination, TextMentionTermination
from autogen_ext.models.openai import OpenAIChatCompletionClient

# 1. LLM client configuration
model_client = OpenAIChatCompletionClient(
    model="gpt-4o",
    api_key="your-api-key",
)

# 2. Tool definitions
async def search_web(query: str) -> str:
    """Searches the web for information."""
    # In practice, use Tavily, SerpAPI, etc.
    return f"Search results for '{query}': [Search result summary]"

async def analyze_data(data: str) -> str:
    """Analyzes data and extracts insights."""
    return f"Analysis result: In-depth analysis of {data[:100]} completed"

# 3. Agent definitions
researcher = AssistantAgent(
    name="Researcher",
    model_client=model_client,
    system_message="""You are a professional researcher.
    Perform web searches on the given topic and collect relevant information.
    Structure the collected information and pass it to the Analyst.""",
    tools=[search_web],
)

analyst = AssistantAgent(
    name="Analyst",
    model_client=model_client,
    system_message="""You are a data analyst.
    Analyze the information collected by the Researcher and extract key insights.
    Pass the analysis results to the Writer.""",
    tools=[analyze_data],
)

writer = AssistantAgent(
    name="Writer",
    model_client=model_client,
    system_message="""You are a report writer.
    Write the final report based on the Analyst's analysis results.
    Output 'TERMINATE' when the report is complete.""",
)

# 4. Team composition and execution
termination = MaxMessageTermination(max_messages=15) | TextMentionTermination("TERMINATE")

# SelectorGroupChat: LLM selects the next speaker
team = SelectorGroupChat(
    participants=[researcher, analyst, writer],
    model_client=model_client,
    termination_condition=termination,
    selector_prompt="""Select the next speaker.
    Choose Researcher if information gathering is needed, Analyst if analysis is needed,
    and Writer if the final report needs to be written.""",
)

async def main():
    result = await team.run(
        task="Research the 2026 AI agent framework market trends and write a report."
    )
    for message in result.messages:
        print(f"[{message.source}]: {message.content[:200]}")
    print(f"\nTotal messages: {len(result.messages)}")

asyncio.run(main())

CrewAI: Content Creation Pipeline

An example of an automated blog post generation pipeline using CrewAI.

from crewai import Agent, Task, Crew, Process
from crewai.tools import tool
from langchain_openai import ChatOpenAI

# 1. LLM configuration
llm = ChatOpenAI(model="gpt-4o", temperature=0.7)

# 2. Custom tool definitions
@tool("Search Tool")
def search_tool(query: str) -> str:
    """Performs a web search with the given query."""
    # In practice, use Tavily, SerpAPI, etc.
    return f"Search results for '{query}': [Relevant information summary]"

@tool("SEO Analyzer")
def seo_analyzer(content: str) -> str:
    """Analyzes the SEO score of the content."""
    word_count = len(content.split())
    return f"Word count: {word_count}, SEO score: {'Good' if word_count > 500 else 'Insufficient'}"

# 3. Agent definitions
research_agent = Agent(
    role="Technical Researcher",
    goal="Research the latest technology trends and collect accurate information",
    backstory="""A technology journalist with 10 years of experience who stays
    on top of the latest trends in AI and software engineering.""",
    tools=[search_tool],
    llm=llm,
    verbose=True,
    allow_delegation=True,  # Can delegate work to other agents
)

writer_agent = Agent(
    role="Technical Blog Writer",
    goal="Write in-depth technical blog posts that readers can easily understand",
    backstory="""A professional technical blog writer skilled at breaking down
    complex technical concepts into clear, practical prose.""",
    llm=llm,
    verbose=True,
)

editor_agent = Agent(
    role="Editor-in-Chief",
    goal="Review content quality, accuracy, and SEO optimization",
    backstory="""An editor-in-chief at a technology media outlet who pursues
    technical accuracy, readability, and SEO optimization simultaneously.""",
    tools=[seo_analyzer],
    llm=llm,
    verbose=True,
)

# 4. Task definitions
research_task = Task(
    description="""Conduct comprehensive research on '{topic}'.
    - Collect at least 3 recent trends
    - Organize key technical concepts
    - Include at least 2 real-world use cases""",
    expected_output="A structured research report (Markdown format)",
    agent=research_agent,
)

writing_task = Task(
    description="""Write a technical blog post based on the research results.
    - At least 2000 characters of body text
    - Include at least 2 code examples
    - Include insights from a practitioner's perspective""",
    expected_output="A completed blog post (Markdown format)",
    agent=writer_agent,
    context=[research_task],  # Receives research_task results as context
)

editing_task = Task(
    description="""Review the written blog post.
    - Verify technical accuracy
    - Improve readability
    - Perform SEO analysis
    - Output the final revised version""",
    expected_output="The final reviewed blog post",
    agent=editor_agent,
    context=[writing_task],
)

# 5. Crew composition and execution
crew = Crew(
    agents=[research_agent, writer_agent, editor_agent],
    tasks=[research_task, writing_task, editing_task],
    process=Process.sequential,  # Sequential execution
    verbose=True,
    memory=True,                 # Enable short-term memory
    max_rpm=10,                  # API call rate limit
)

# Execution
result = crew.kickoff(inputs={"topic": "LLM Agent Framework Comparison"})
print(f"Final result:\n{result}")
print(f"Token usage: {crew.usage_metrics}")

LangGraph: Customer Support Agent with Conditional Branching

An example of a state-based customer support agent using LangGraph, featuring conditional branching, Human-in-the-loop, and error recovery.

from typing import Annotated, Literal, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import MemorySaver
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, SystemMessage

# 1. State definition
class SupportState(TypedDict):
    messages: Annotated[list, add_messages]
    category: str           # Inquiry category (billing, technical, general)
    sentiment: str          # Customer sentiment (positive, neutral, negative)
    escalated: bool         # Whether escalated to a senior agent
    resolution: str         # Resolution status

# 2. Tool definitions
@tool
def lookup_order(order_id: str) -> str:
    """Looks up order information."""
    orders = {
        "ORD-001": {"status": "In transit", "eta": "2026-03-11"},
        "ORD-002": {"status": "Delivered", "delivered": "2026-03-07"},
    }
    if order_id in orders:
        return f"Order {order_id}: {orders[order_id]}"
    return f"Order {order_id} not found."

@tool
def create_ticket(summary: str, priority: str) -> str:
    """Creates a customer support ticket."""
    return f"Ticket created - Summary: {summary}, Priority: {priority}, Ticket ID: TKT-{hash(summary) % 10000}"

@tool
def refund_order(order_id: str, reason: str) -> str:
    """Processes an order refund. (Requires approval)"""
    return f"Refund request received: {order_id}, Reason: {reason}. Awaiting manager approval."

tools = [lookup_order, create_ticket, refund_order]
llm = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)

# 3. Node function definitions
def classify_intent(state: SupportState) -> dict:
    """Classifies the customer inquiry."""
    classifier_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
    last_message = state["messages"][-1].content

    response = classifier_llm.invoke([
        SystemMessage(content="""Classify the customer inquiry.
        Category: billing, technical, general
        Sentiment: positive, neutral, negative
        Respond in JSON format: {"category": "...", "sentiment": "..."}"""),
        HumanMessage(content=last_message)
    ])

    import json
    try:
        result = json.loads(response.content)
    except json.JSONDecodeError:
        result = {"category": "general", "sentiment": "neutral"}

    return {
        "category": result.get("category", "general"),
        "sentiment": result.get("sentiment", "neutral"),
    }

def agent_respond(state: SupportState) -> dict:
    """The agent generates a response."""
    system_prompt = f"""You are a customer support agent.
    Current inquiry category: {state.get('category', 'unknown')}
    Customer sentiment: {state.get('sentiment', 'unknown')}
    Escalation status: {state.get('escalated', False)}

    Respond to the customer in a friendly and accurate manner.
    Use tools to look up information or create tickets as needed."""

    messages = [SystemMessage(content=system_prompt)] + state["messages"]
    response = llm.invoke(messages)
    return {"messages": [response]}

def check_escalation(state: SupportState) -> dict:
    """Determines whether escalation is needed."""
    if state.get("sentiment") == "negative" and state.get("category") == "billing":
        return {"escalated": True}
    return {"escalated": False}

def human_escalation(state: SupportState) -> dict:
    """Escalates to a senior agent."""
    return {
        "messages": [SystemMessage(content="[System] This inquiry has been forwarded to a senior agent. Please wait a moment.")],
        "resolution": "escalated_to_human",
    }

# 4. Routing functions
def route_after_classification(state: SupportState) -> Literal["check_escalation", "agent_respond"]:
    """Routes based on classification results."""
    if state.get("sentiment") == "negative":
        return "check_escalation"
    return "agent_respond"

def route_after_escalation_check(state: SupportState) -> Literal["human_escalation", "agent_respond"]:
    """Routes based on escalation check results."""
    if state.get("escalated"):
        return "human_escalation"
    return "agent_respond"

# 5. Graph construction
workflow = StateGraph(SupportState)

# Add nodes
workflow.add_node("classify_intent", classify_intent)
workflow.add_node("check_escalation", check_escalation)
workflow.add_node("agent_respond", agent_respond)
workflow.add_node("tools", ToolNode(tools))
workflow.add_node("human_escalation", human_escalation)

# Define edges
workflow.add_edge(START, "classify_intent")
workflow.add_conditional_edges("classify_intent", route_after_classification)
workflow.add_conditional_edges("check_escalation", route_after_escalation_check)
workflow.add_conditional_edges("agent_respond", tools_condition, {"tools": "tools", END: END})
workflow.add_edge("tools", "agent_respond")
workflow.add_edge("human_escalation", END)

# Checkpointer (state persistence)
checkpointer = MemorySaver()
app = workflow.compile(checkpointer=checkpointer)

# 6. Execution
config = {"configurable": {"thread_id": "customer-123"}}
result = app.invoke(
    {"messages": [HumanMessage(content="My order ORD-001 is taking too long to ship. I want a refund.")]},
    config=config,
)

for msg in result["messages"]:
    print(f"[{msg.__class__.__name__}]: {msg.content[:200]}")
print(f"Category: {result['category']}, Sentiment: {result['sentiment']}")

Tool Integration Comparison

This section compares how each framework integrates external tools.

Tool Definition Pattern Comparison

ItemAutoGenCrewAILangGraph
Definition MethodPython function + type hint@tool decorator or BaseTool inheritance@tool decorator (LangChain)
Auto Schema GenerationBased on function signaturedocstring + type hintdocstring + type hint
Async SupportNative async functionsLimitedAsync supported
Error HandlingManual try/exceptToolException classToolException + fallback
Built-in ToolsCode execution, web searchSerperDev, file read/write, code interpreterWeb search, Retriever, Python REPL

Key Differences

AutoGen uses the most concise approach by registering Python functions directly as tools. JSON schemas are automatically generated from the function's type hints and docstrings, and async functions are natively supported. Its unique strength is providing a built-in code execution environment, allowing agents to immediately run generated code in Docker containers or the local environment.

CrewAI lets you define tools with the @tool decorator or implement more complex tools by subclassing BaseTool. It has a rich built-in tool library, and an agent delegating work to another agent itself functions as a kind of tool.

LangGraph leverages the LangChain tool ecosystem as-is. It uses ToolNode and tools_condition to explicitly model tool invocations and result handling as graph nodes. This makes it easy to insert custom logic (logging, validation, transformation) before and after tool calls.

Memory and State Management

Short-Term Memory (Conversation Context)

FrameworkShort-Term Memory ApproachWindow ManagementToken Limit Handling
AutoGenAutomatic message list managementManual or summarize patternConversation summary agent
CrewAIAutomatic context passing between tasksAutomatic (configurable)Auto-summarization
LangGraphState's messages fieldadd_messages reducerTrimmer/Summarizer node

Long-Term Memory (Cross-Session Persistence)

AutoGen requires custom implementation to store conversation history in files or databases. While AutoGen Studio UI provides session management features, the programmatic long-term memory API is limited.

CrewAI activates short-term memory with memory=True and can optionally configure long-term memory using vector stores such as ChromaDB. It has a built-in mechanism that automatically leverages patterns learned from past tasks.

LangGraph provides the most systematic state persistence through the Checkpointer interface. It supports various backends including MemorySaver (in-memory), SqliteSaver, and PostgresSaver, and enables resuming conversations by thread_id and rolling back to specific points in time. This is an extremely important capability in production environments.

Production Deployment Considerations

Deployment Architecture Comparison

ItemAutoGenCrewAILangGraph
Serving MethodBuild with FastAPIBuild with FastAPI/FlaskLangServe / LangGraph Cloud
ScalingManual implementationManual implementationLangGraph Platform (managed)
State PersistenceCustom implementationBuilt-in (limited)Checkpointer (PostgreSQL, etc.)
Concurrencyasyncio-basedThread/process-basedasyncio + Checkpointer-based
MonitoringCustom loggingCustom loggingLangSmith (tracing, evaluation)
Cost TrackingCallback-basedBuilt-in usage_metricsLangSmith token tracking

Production Checklist

  1. Rate limit management: Control the LLM call frequency of each agent. Leverage CrewAI's max_rpm, LangGraph's custom middleware, and AutoGen's callbacks.
  2. Timeout configuration: Prevent agents from falling into infinite loops. Always set maximum iteration counts and total execution time limits.
  3. Cost ceilings: Monitor token usage in real-time and implement safeguards that halt execution when the budget is exceeded.
  4. Error isolation: Apply the circuit breaker pattern so that a single agent failure does not bring down the entire system.
  5. Audit logging: Record all agent decisions and tool calls in a traceable format.

Failure Cases and Recovery Strategies

Case 1: Agent Infinite Loop

Symptoms: An agent repeatedly calls the same tool, or two agents engage in an endless back-and-forth conversation.

Root Cause: This occurs when the termination condition is ambiguous or the agent's prompt does not provide clear completion criteria.

Framework-Specific Recovery:

  • AutoGen: Limit the maximum number of messages with MaxMessageTermination(max_messages=20). Combine with TextMentionTermination("TERMINATE") for a dual safety net.
  • CrewAI: Limit the agent's maximum iterations with the max_iter parameter. Also limit API call rate with Crew(max_rpm=10).
  • LangGraph: Limit the maximum number of graph traversals with the recursion_limit parameter. Add a step_count to the state and check it in conditional edges.

Case 2: Tool Call Failures and Hallucinations

Symptoms: An agent calls a tool that does not exist or passes incorrect arguments to a tool.

Root Cause: This happens when the LLM does not accurately understand the tool's schema, or when the context window is insufficient, causing the LLM to "forget" the tool definitions.

Recovery Strategies:

  • Write tool docstrings and parameter descriptions as clearly and in as much detail as possible.
  • Add a validation layer for tool call results. If results are returned in an unexpected format, retry or execute fallback logic.
  • In LangGraph, you can add a validation node after the tool node to verify result validity.

Case 3: Context Window Overflow

Symptoms: Long conversations or large volumes of tool results cause the token limit to be reached, and the agent loses critical information.

Root Cause: In multi-agent systems, messages accumulate exponentially, rapidly exhausting the context window.

Recovery Strategies:

  • Periodically summarize and compress the conversation history.
  • Summarize tool results or extract only key information before including them in the context.
  • In LangGraph, use the trim_messages utility to retain only the most recent N messages.

Case 4: Failed Task Delegation Between Agents

Symptoms: In CrewAI, an agent delegates work to another agent, but the delegated agent fails to understand the context and returns irrelevant results.

Root Cause: This occurs when insufficient context is passed during delegation, or when the delegated task does not match the target agent's role.

Recovery Strategies:

  • Prevent unnecessary delegation by setting allow_delegation=False on agents.
  • Specify concrete input/output formats in the task description.
  • Explicitly pass the results of preceding tasks through the context parameter.

Case 5: Full System Outage Due to API Failure

Symptoms: An LLM API outage or tool API failure causes the entire agent system to go down.

Root Cause: There is no fallback mechanism for a single point of failure.

Recovery Strategies:

# Example error recovery pattern in LangGraph
from tenacity import retry, stop_after_attempt, wait_exponential
from langchain_core.messages import AIMessage

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, max=10))
def resilient_agent_node(state: SupportState) -> dict:
    """An agent node with built-in retry logic."""
    try:
        response = llm.invoke(state["messages"])
        return {"messages": [response]}
    except Exception as e:
        # Fallback: return a predefined response
        fallback_response = AIMessage(
            content="We apologize for the inconvenience. A temporary system error has occurred. "
                    "Please try again shortly. For urgent matters, please contact "
                    "our customer service at 1-800-000-0000."
        )
        return {
            "messages": [fallback_response],
            "resolution": f"error_fallback: {str(e)}",
        }

Selection Guide: Decision Tree

This section provides framework selection criteria based on project requirements.

When to Choose CrewAI

  • When rapid prototyping is the top priority. When you need to build and demo a multi-agent system within hours.
  • When role-based workflows feel natural. Linear pipelines like "Researcher -> Analyst -> Writer."
  • When you need to present agent definitions to non-technical stakeholders. The role/goal/backstory format is the most intuitive.
  • When production-grade state management or complex conditional logic is not a major requirement.

When to Choose AutoGen

  • When free-flowing conversation between agents is central. Conversation patterns like debates, consensus-building, and brainstorming.
  • When code generation and execution is a primary feature. The built-in code execution environment is a major advantage.
  • When integration with the Microsoft ecosystem is needed. Compatibility with Azure OpenAI and the Microsoft Agent Framework.
  • When agents also need to run in a .NET environment. AutoGen offers a .NET version as well.

When to Choose LangGraph

  • When complex conditional logic and branching are required. Workflows where execution paths change dynamically based on state.
  • When production-grade reliability is essential. State persistence, resumable execution, and audit logs.
  • When Human-in-the-loop is a core requirement. Proceeding only after obtaining human approval at specific steps.
  • When observability is important. Detailed tracing and debugging through LangSmith.
  • When long-running workflows must be supported. Managing asynchronous tasks spanning hours to days.

Decision Summary Table

Priority Consideration1st Choice2nd ChoiceNotes
Prototyping speedCrewAIAutoGenLangGraph has a high initial setup cost
Production reliabilityLangGraphCrewAIAutoGen is in a transitional period to MS Agent Framework
Conversational agentsAutoGenLangGraphCrewAI is task-centric rather than conversation-centric
Complex workflowsLangGraphAutoGenConditional branching, parallel execution, state machines
Code executionAutoGenLangGraphAutoGen's code execution environment is the most mature
Observability/DebuggingLangGraphAutoGenLangSmith integration is an overwhelming advantage
Learning curveCrewAIAutoGenLangGraph requires understanding graph concepts
Community/EcosystemLangGraphCrewAIThe LangChain ecosystem is the largest in scale

Operational Considerations

Cost Management

Multi-agent systems can consume 5 to 20 times more tokens compared to single LLM calls. Costs grow exponentially as the number of agents increases and conversations become longer. You must track token usage per agent and per task, and set budget ceilings. Where possible, apply a model routing strategy that uses smaller models like GPT-4o-mini for simple tasks such as classifiers, and reserves larger models for core reasoning only.

Security Considerations

Exercise caution when agents access external systems through tools:

  • Principle of least privilege: Grant agents only the minimum API permissions they need. Do not give write permissions for read-only operations.
  • Input validation: Validate the arguments agents pass to tools. These can become attack vectors for SQL injection or command injection.
  • Output filtering: Add a filtering layer to ensure agent responses do not contain sensitive information (PII, credentials).
  • Sandboxing: Code execution tools must always run in isolated environments (Docker containers).

Testing Strategy

Testing agent systems is more complex than traditional software testing. Due to the non-deterministic output of LLMs, the same input can produce different results.

  • Unit tests: Test each tool function independently. Validate tool input/output without involving agents.
  • Integration tests: Use mock LLM responses to verify agent tool-calling patterns.
  • Evaluation (Eval): Use the LLM-as-Judge pattern to automatically evaluate the quality of agent final outputs. Build evaluation pipelines using LangSmith's evaluation features or a custom setup.
  • Load tests: Measure system performance under varying concurrent user counts and request frequencies. Pay special attention to LLM API rate limits.

Version Management and Migration

All three frameworks are evolving rapidly, and API changes are frequent. AutoGen in particular underwent a major architectural overhaul from v0.2 to v0.4 (AgentChat), and a future transition to the Microsoft Agent Framework has been announced. It is safest to pin framework versions and perform migration testing in a separate branch for major updates.

Conclusion

There is no silver bullet when choosing an LLM agent framework. CrewAI shines in "build it fast and show it" scenarios, AutoGen is well-suited when rich conversational interactions between agents are central, and LangGraph is optimal for complex workflows requiring production-grade reliability and control.

The recommended approach in practice is as follows: First, quickly build a prototype with CrewAI to validate the value of the agent system. Once validated, consider reimplementing with LangGraph based on production requirements (state management, resumability, observability, Human-in-the-loop). If conversational agents are central, choose AutoGen, but formulate a transition plan to the Microsoft Agent Framework alongside it.

Regardless of which framework you choose, remember that the success of an agent system depends less on the framework itself and more on the quality of prompt engineering, the robustness of tool design, and thorough preparation for failure modes.

References

Quiz

Q1: What is the main topic covered in "Comparing LLM Agent Frameworks: AutoGen vs CrewAI vs LangGraph — A Practical Selection Guide"?

A comprehensive comparison guide for three LLM agent frameworks (AutoGen, CrewAI, LangGraph). Covers architecture and design philosophy, multi-agent orchestration patterns, tool integration, memory management, production deployment strategies, and practical selection criteria — a...

Q2: Why You Need an Agent Framework? Limitations of a Single LLM Call Simple LLM API calls have the following limitations: One-shot responses: A single call cannot complete complex multi-step tasks. Chains of research, analysis, execution, and verification require orchestrating multiple calls.

Q3: Describe the Architecture Comparison. AutoGen: Conversation-Centric Multi-Agent AutoGen's core design philosophy is "problem-solving through inter-agent conversation." All interactions are message-based, and the speaking order and content of agents are determined by conversation patterns.

Q4: What are the key aspects of Practical Code Examples for Each Framework? AutoGen: Building a Research Agent Team An example of a multi-agent research system using the AutoGen v0.4 AgentChat API. CrewAI: Content Creation Pipeline An example of an automated blog post generation pipeline using CrewAI.

Q5: How does Tool Integration Comparison work? This section compares how each framework integrates external tools. Tool Definition Pattern Comparison Key Differences AutoGen uses the most concise approach by registering Python functions directly as tools.