본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 11장: 프레임워크 선택 기준과 마이그레이션 전략
2026년 2월 22일·AI / ML·

11장: 프레임워크 선택 기준과 마이그레이션 전략

5대 프레임워크 종합 비교, 의사결정 트리, 하이브리드 아키텍처, 마이그레이션 가이드, 프레임워크 독립적 설계 원칙을 다룹니다.

20분1,073자9개 섹션
orchestrationai-frameworkaillm
공유
ai-orchestration11 / 11
1234567891011
이전10장: 에러 처리, 폴백, 관측 가능성

이 장에서 배우는 것

  • 5대 프레임워크의 종합 비교표
  • 프로젝트 요구사항에 따른 의사결정 트리
  • 하이브리드 아키텍처(Hybrid Architecture) 패턴
  • 프레임워크 간 마이그레이션 가이드
  • 프레임워크 독립적 설계 원칙

시리즈를 마치며

10개의 장에 걸쳐 AI 오케스트레이션의 주요 프레임워크와 핵심 패턴을 살펴보았습니다. 이 마지막 장에서는 그 모든 내용을 종합하여, 실제 프로젝트에서 어떤 결정을 내려야 하는지 구체적인 가이드를 제시합니다.

기억해야 할 핵심은 이것입니다. 완벽한 프레임워크는 없고, 프로젝트에 적합한 프레임워크가 있을 뿐입니다.


종합 비교표

기본 특성

항목LangChainLangGraphLlamaIndexSemantic KernelHaystack
핵심 강점생태계, 통합에이전트 워크플로우데이터/RAG엔터프라이즈최소 오버헤드
오버헤드~10ms~14ms~6ms-~5.9ms
토큰 효율중간중간~1.60k중간~1.57k
언어 지원Python, JSPython, JSPythonC#, Python, JavaPython
학습 곡선중간높음중간중간낮음

기능별 비교

기능LangChainLangGraphLlamaIndexSemantic KernelHaystack
체이닝LCEL 파이프그래프 엣지Workflows함수 체이닝Pipeline
라우팅RunnableBranch조건부 엣지이벤트 분기플래너ConditionalRouter
메모리MessageHistory체크포인팅ChatMemoryBufferSemanticMemoryChatMessageMemory
스트리밍astream/eventsastream_eventsresponse_gen콜백콜백
에러 처리with_fallbacks조건부 엣지try-except필터커스텀 컴포넌트
관측성OpenTelemetry + LangSmithOpenTelemetry + LangSmith콜백 핸들러OpenTelemetry + App InsightsOpenTelemetry
듀러블 상태제한적SQLite/PostgreSQL제한적제한적제한적
휴먼인더루프제한적interrupt()제한적에이전트 그룹제한적

생태계 비교

항목LangChainLangGraphLlamaIndexSemantic KernelHaystack
GitHub Stars매우 높음높음높음27,500+중간
서드파티 통합매우 풍부LangChain 생태계LlamaHubAzure 생태계중간
학습 자료매우 풍부풍부풍부풍부중간
기업 지원LangChain Inc.LangChain Inc.LlamaIndex Inc.Microsoftdeepset

의사결정 트리

프로젝트에 적합한 프레임워크를 선택하기 위한 의사결정 흐름입니다.

상세 의사결정 기준

1단계: 팀의 기술 스택 확인

  • C# 또는 Java 팀: Semantic Kernel이 사실상 유일한 선택입니다.
  • Python 팀: 모든 프레임워크가 가능합니다. 다음 단계로 진행하세요.

2단계: 주요 사용 사례 파악

  • 데이터 연결 + RAG 중심: LlamaIndex가 가장 적합합니다.
  • 복잡한 에이전트 워크플로우: LangGraph를 권장합니다.
  • 검색/NLP 파이프라인: Haystack이 최적입니다.
  • 범용 체이닝: LangChain이 가장 넓은 커버리지를 제공합니다.

3단계: 운영 요구사항 검토

  • Azure 기반: Semantic Kernel의 네이티브 통합이 강점입니다.
  • 관측성 중요: LangSmith(LangChain/LangGraph)가 가장 성숙합니다.
  • 지연 시간 민감: Haystack(5.9ms) 또는 LlamaIndex(6ms)를 고려하세요.

4단계: 팀의 학습 역량 고려

  • 빠른 프로토타이핑: LangChain(풍부한 학습 자료)
  • 명시적 제어 선호: Haystack(낮은 추상화)
  • 엔터프라이즈 경험: Semantic Kernel(익숙한 플러그인 패턴)
Tip

프레임워크를 선택한 후에도 해당 프레임워크에 완전히 종속되지 않도록 설계하는 것이 중요합니다. 이 장의 마지막 섹션에서 프레임워크 독립적 설계 원칙을 다룹니다.


하이브리드 아키텍처

하나의 프레임워크만 사용할 필요는 없습니다. 각 프레임워크의 강점을 조합하는 하이브리드 아키텍처가 실전에서 자주 사용됩니다.

LlamaIndex + LangGraph

가장 검증된 하이브리드 패턴입니다. LlamaIndex가 데이터 레이어를, LangGraph가 오케스트레이션 레이어를 담당합니다.

hybrid_architecture.py
python
from llama_index.core import VectorStoreIndex
from langgraph.graph import StateGraph, START, END
from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages
 
# --- LlamaIndex: 데이터 레이어 ---
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine(similarity_top_k=5)
 
# --- LangGraph: 오케스트레이션 레이어 ---
class AgentState(TypedDict):
    messages: Annotated[list, add_messages]
    context: str
    needs_research: bool
 
def research_node(state: AgentState) -> dict:
    """LlamaIndex를 사용한 연구 노드"""
    query = state["messages"][-1].content
    response = query_engine.query(query)
    return {
        "context": str(response),
        "needs_research": False,
    }
 
def respond_node(state: AgentState) -> dict:
    """컨텍스트를 활용한 응답 생성"""
    context = state.get("context", "")
    messages = state["messages"]
    response = model.invoke(
        f"컨텍스트: {context}\n\n"
        f"질문: {messages[-1].content}"
    )
    return {"messages": [response]}
 
def route(state: AgentState) -> str:
    if state.get("needs_research", True):
        return "research"
    return "respond"
 
# 그래프 구성
graph = StateGraph(AgentState)
graph.add_node("research", research_node)
graph.add_node("respond", respond_node)
graph.add_conditional_edges(START, route)
graph.add_edge("research", "respond")
graph.add_edge("respond", END)
 
app = graph.compile(checkpointer=checkpointer)

Haystack + LangGraph

지연 시간에 민감한 검색 파이프라인에 에이전트 기능을 추가할 때 적합합니다.

haystack_langgraph_hybrid.py
python
from haystack import Pipeline
from haystack.components.retrievers.in_memory import InMemoryBM25Retriever
 
# Haystack: 고성능 검색 파이프라인
search_pipeline = Pipeline()
search_pipeline.add_component("retriever", bm25_retriever)
search_pipeline.add_component("reranker", reranker)
search_pipeline.connect("retriever", "reranker")
 
# LangGraph: 에이전트 오케스트레이션에서 Haystack 파이프라인 호출
def search_node(state: AgentState) -> dict:
    result = search_pipeline.run({
        "retriever": {"query": state["messages"][-1].content}
    })
    docs = result["reranker"]["documents"]
    context = "\n".join(d.content for d in docs[:3])
    return {"context": context}
Info

하이브리드 아키텍처를 사용할 때는 프레임워크 간 경계를 명확하게 정의해야 합니다. 각 프레임워크의 역할이 겹치면 복잡성만 증가하고 장점은 사라집니다. "데이터 레이어는 LlamaIndex, 오케스트레이션은 LangGraph"처럼 역할을 분명히 분리하세요.


마이그레이션 가이드

기존 프레임워크에서 다른 프레임워크로 전환해야 하는 상황은 자주 발생합니다.

마이그레이션 원칙

  1. 점진적 전환: 빅뱅 마이그레이션은 위험합니다. 기능 단위로 점진적으로 전환하세요.
  2. 어댑터 패턴: 구 프레임워크와 신 프레임워크를 어댑터로 연결하여 공존 기간을 확보합니다.
  3. 테스트 우선: 마이그레이션 전 기존 기능의 통합 테스트를 작성하고, 전환 후 동일한 테스트를 통과하는지 검증합니다.

LangChain에서 LangGraph로

가장 흔한 마이그레이션 경로입니다. 단순 체인에서 에이전트 워크플로우로 진화할 때 발생합니다.

migration_langchain_to_langgraph.py
python
# Before: LangChain LCEL
chain = (
    RunnableParallel(context=retriever, question=RunnablePassthrough())
    | prompt
    | model
    | parser
)
 
# After: LangGraph (동일 기능 + 에이전트 확장 가능)
from langgraph.graph import StateGraph, START, END
 
class RAGState(TypedDict):
    question: str
    context: str
    response: str
 
def retrieve(state: RAGState) -> dict:
    docs = retriever.invoke(state["question"])
    return {"context": format_docs(docs)}
 
def generate(state: RAGState) -> dict:
    response = chain.invoke({
        "context": state["context"],
        "question": state["question"],
    })
    return {"response": response}
 
graph = StateGraph(RAGState)
graph.add_node("retrieve", retrieve)
graph.add_node("generate", generate)
graph.add_edge(START, "retrieve")
graph.add_edge("retrieve", "generate")
graph.add_edge("generate", END)
 
# 기존 LCEL 체인을 노드 내부에서 재사용

어댑터 패턴

adapter_pattern.py
python
from abc import ABC, abstractmethod
 
class LLMServicePort(ABC):
    """프레임워크 독립적 LLM 서비스 인터페이스"""
 
    @abstractmethod
    async def generate(self, prompt: str, **kwargs) -> str:
        pass
 
    @abstractmethod
    async def generate_stream(self, prompt: str, **kwargs):
        pass
 
    @abstractmethod
    async def embed(self, text: str) -> list[float]:
        pass
 
class LangChainAdapter(LLMServicePort):
    """LangChain 어댑터"""
 
    def __init__(self, model, embeddings):
        self.model = model
        self.embeddings = embeddings
 
    async def generate(self, prompt: str, **kwargs) -> str:
        response = await self.model.ainvoke(prompt)
        return response.content
 
    async def generate_stream(self, prompt: str, **kwargs):
        async for chunk in self.model.astream(prompt):
            if chunk.content:
                yield chunk.content
 
    async def embed(self, text: str) -> list[float]:
        return await self.embeddings.aembed_query(text)
 
class HaystackAdapter(LLMServicePort):
    """Haystack 어댑터"""
 
    def __init__(self, generator, embedder):
        self.generator = generator
        self.embedder = embedder
 
    async def generate(self, prompt: str, **kwargs) -> str:
        result = self.generator.run(prompt=prompt)
        return result["replies"][0]
 
    async def generate_stream(self, prompt: str, **kwargs):
        # Haystack 스트리밍 구현
        pass
 
    async def embed(self, text: str) -> list[float]:
        result = self.embedder.run(text=text)
        return result["embedding"]

프레임워크 독립적 설계 원칙

프레임워크에 종속되지 않는 설계를 위한 5가지 핵심 원칙입니다.

1. 포트와 어댑터 패턴

비즈니스 로직을 프레임워크와 분리합니다.

ports_and_adapters.py
python
# 포트 (인터페이스)
class RetrievalPort(ABC):
    @abstractmethod
    async def retrieve(self, query: str, k: int = 5) -> list[Document]:
        pass
 
class GenerationPort(ABC):
    @abstractmethod
    async def generate(self, prompt: str, context: str) -> str:
        pass
 
# 비즈니스 로직 (프레임워크 독립)
class RAGService:
    def __init__(
        self,
        retriever: RetrievalPort,
        generator: GenerationPort,
    ):
        self.retriever = retriever
        self.generator = generator
 
    async def answer(self, question: str) -> str:
        docs = await self.retriever.retrieve(question)
        context = "\n".join(d.content for d in docs)
        return await self.generator.generate(
            prompt=question,
            context=context,
        )
 
# 어댑터 (프레임워크별 구현)
class LlamaIndexRetriever(RetrievalPort):
    def __init__(self, index):
        self.index = index
 
    async def retrieve(self, query: str, k: int = 5) -> list[Document]:
        nodes = await self.index.aretrieve(query)
        return [Document(content=n.text) for n in nodes[:k]]
 
class LangChainGenerator(GenerationPort):
    def __init__(self, chain):
        self.chain = chain
 
    async def generate(self, prompt: str, context: str) -> str:
        return await self.chain.ainvoke({
            "question": prompt,
            "context": context,
        })

2. 설정 주도 아키텍처

프레임워크 선택을 설정으로 관리합니다.

config_driven.py
python
from pydantic_settings import BaseSettings
 
class OrchestratorConfig(BaseSettings):
    framework: str = "langchain"  # langchain | langgraph | llamaindex | haystack
    model_provider: str = "openai"
    model_name: str = "gpt-4o"
    retriever_type: str = "vector"
    vector_store: str = "chroma"
 
    class Config:
        env_prefix = "AI_"
 
def create_service(config: OrchestratorConfig) -> RAGService:
    """설정에 따라 적절한 구현체 생성"""
    retriever = create_retriever(config)
    generator = create_generator(config)
    return RAGService(retriever=retriever, generator=generator)

3. 표준 데이터 모델

프레임워크 간 데이터 교환을 위한 표준 모델을 정의합니다.

standard_models.py
python
from pydantic import BaseModel
from datetime import datetime
 
class Document(BaseModel):
    """표준 문서 모델"""
    content: str
    metadata: dict = {}
    score: float = 0.0
 
class ChatMessage(BaseModel):
    """표준 메시지 모델"""
    role: str  # "user" | "assistant" | "system"
    content: str
    timestamp: datetime = datetime.now()
    metadata: dict = {}
 
class GenerationResult(BaseModel):
    """표준 생성 결과 모델"""
    content: str
    model: str
    input_tokens: int = 0
    output_tokens: int = 0
    latency_ms: float = 0.0
    metadata: dict = {}

4. 테스트 인터페이스 분리

프레임워크 의존성을 테스트에서 격리합니다.

testing.py
python
import pytest
 
class MockRetriever(RetrievalPort):
    async def retrieve(self, query: str, k: int = 5) -> list[Document]:
        return [Document(content="테스트 컨텍스트", score=0.9)]
 
class MockGenerator(GenerationPort):
    async def generate(self, prompt: str, context: str) -> str:
        return f"응답: {prompt} (컨텍스트 활용)"
 
@pytest.mark.asyncio
async def test_rag_service():
    """프레임워크 독립적 테스트"""
    service = RAGService(
        retriever=MockRetriever(),
        generator=MockGenerator(),
    )
    result = await service.answer("테스트 질문")
    assert "테스트 질문" in result

5. 점진적 도입

기존 시스템에 프레임워크를 점진적으로 도입합니다.

gradual_adoption.py
python
class GradualMigrationService:
    """기능 플래그 기반 점진적 마이그레이션"""
 
    def __init__(self, old_service, new_service, feature_flags):
        self.old = old_service
        self.new = new_service
        self.flags = feature_flags
 
    async def answer(self, question: str) -> str:
        if self.flags.is_enabled("use_new_rag"):
            try:
                return await self.new.answer(question)
            except Exception:
                # 새 서비스 실패 시 기존 서비스로 폴백
                return await self.old.answer(question)
        return await self.old.answer(question)
Warning

프레임워크 독립적 설계는 초기 개발 비용을 증가시킵니다. 프로토타입이나 단기 프로젝트에서는 프레임워크에 직접 의존하는 것이 더 효율적일 수 있습니다. 장기 프로덕션 서비스에서만 이 수준의 추상화를 적용하세요.


2026년 이후 전망

AI 오케스트레이션 생태계는 계속 진화하고 있습니다.

  • 통합 추세: 프레임워크 간 상호 운용성이 개선되고 있으며, Agent Client Protocol 같은 표준이 등장하고 있습니다.
  • 경량화: DSPy(~3.53ms 오버헤드) 같은 극도로 경량한 접근 방식이 주목받고 있습니다.
  • 에이전트 중심: 단순 체이닝에서 자율적 에이전트 시스템으로 중심이 이동하고 있습니다.
  • 관측성 통합: OpenTelemetry가 사실상의 표준이 되고 있으며, 모든 프레임워크가 네이티브 지원을 확대하고 있습니다.

어떤 프레임워크를 선택하든, 핵심은 프레임워크가 아니라 문제를 해결하는 능력입니다. 프레임워크는 도구일 뿐이며, 좋은 AI 애플리케이션은 올바른 아키텍처 결정, 효과적인 프롬프트 설계, 그리고 견고한 프로덕션 운영에서 나옵니다.


핵심 요약

  • 프레임워크 선택은 사용 사례, 팀 역량, 운영 환경에 따라 결정해야 합니다.
  • LangChain은 생태계, LangGraph는 에이전트, LlamaIndex는 데이터, Semantic Kernel은 엔터프라이즈, Haystack은 경량성에서 각각 강점을 가집니다.
  • 하이브리드 아키텍처(특히 LlamaIndex + LangGraph)는 실전에서 검증된 조합입니다.
  • 마이그레이션은 어댑터 패턴과 점진적 전환으로 위험을 최소화합니다.
  • 포트와 어댑터 패턴, 표준 데이터 모델, 설정 주도 아키텍처로 프레임워크 독립성을 확보합니다.
  • 프레임워크는 도구일 뿐, 중요한 것은 문제 해결 능력입니다.

이것으로 "AI 오케스트레이션 프레임워크 실전" 시리즈를 마칩니다. 11개의 장에 걸쳐 오케스트레이션의 필요성부터 프레임워크 심층 분석, 공통 패턴, 프로덕션 운영, 그리고 아키텍처 설계 원칙까지 폭넓게 다루었습니다. 이 시리즈가 여러분의 AI 애플리케이션 개발에 실질적인 도움이 되기를 바랍니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#orchestration#ai-framework#ai#llm

관련 글

AI / ML

10장: 에러 처리, 폴백, 관측 가능성

재시도 전략, 서킷 브레이커, OpenTelemetry 통합, 비용 추적, 프로덕션 모니터링까지 프로덕션 안정성 패턴을 다룹니다.

2026년 2월 20일·15분
AI / ML

9장: 스트리밍과 실시간 처리 패턴

SSE/WebSocket, 토큰/이벤트 스트리밍, 구조화된 출력 스트리밍을 각 프레임워크별로 비교하고 프론트엔드 통합을 다룹니다.

2026년 2월 18일·13분
AI / ML

8장: 메모리 관리와 상태 유지

대화 메모리, 장기 메모리, 벡터 메모리, 구조화된 상태를 각 프레임워크별로 비교하고 프로덕션 메모리 전략을 정리합니다.

2026년 2월 16일·16분
이전 글10장: 에러 처리, 폴백, 관측 가능성

댓글

목차

약 20분 남음
  • 이 장에서 배우는 것
  • 시리즈를 마치며
  • 종합 비교표
    • 기본 특성
    • 기능별 비교
    • 생태계 비교
  • 의사결정 트리
    • 상세 의사결정 기준
  • 하이브리드 아키텍처
    • LlamaIndex + LangGraph
    • Haystack + LangGraph
  • 마이그레이션 가이드
    • 마이그레이션 원칙
    • LangChain에서 LangGraph로
    • 어댑터 패턴
  • 프레임워크 독립적 설계 원칙
    • 1. 포트와 어댑터 패턴
    • 2. 설정 주도 아키텍처
    • 3. 표준 데이터 모델
    • 4. 테스트 인터페이스 분리
    • 5. 점진적 도입
  • 2026년 이후 전망
  • 핵심 요약