LLM 오케스트레이션 프레임워크 가이드¶
LLM 애플리케이션 개발을 위한 오케스트레이션 프레임워크 비교 및 선택 가이드. 각 프레임워크의 철학, 아키텍처, 장단점을 분석하고 상황별 최적 선택을 제시한다.
개요¶
LLM 오케스트레이션이란 LLM 호출, 데이터 검색, 도구 사용, 메모리 관리 등을 체계적으로 조합하여 복잡한 AI 애플리케이션을 구축하는 것을 의미한다. 단순한 API 호출을 넘어 프롬프트 관리, 에러 처리, 평가, 관측까지 통합적으로 다룬다.
왜 프레임워크가 필요한가¶
| 직접 구현 시 문제 | 프레임워크가 해결하는 것 |
|---|---|
| 프롬프트 관리 파편화 | 템플릿 시스템, 버전 관리 |
| LLM 호출 에러 처리 반복 | 재시도, fallback, rate limit 관리 |
| RAG 파이프라인 매번 재구현 | 검색-생성 통합 추상화 |
| 에이전트 루프 구현 복잡 | Agent 추상화, 도구 연동 |
| 디버깅 어려움 | 트레이싱, 로깅 내장 |
| 평가 체계 부재 | 평가 프레임워크 통합 |
프레임워크 비교¶
종합 비교표¶
| 프레임워크 | 철학 | 언어 | Stars | 주요 패턴 | 학습 곡선 |
|---|---|---|---|---|---|
| LangChain | 범용 빌딩 블록 | Python, JS | 100k+ | Chain, Agent, Graph | 높음 |
| LlamaIndex | 데이터 프레임워크 | Python, TS | 37k+ | Index, Query Engine | 중간 |
| DSPy | 프로그래밍 > 프롬프팅 | Python | 20k+ | Module, Optimizer | 높음 |
| Semantic Kernel | 엔터프라이즈 AI | C#, Python, Java | 22k+ | Plugin, Planner | 중간 |
| Haystack | 파이프라인 중심 | Python | 18k+ | Pipeline, Component | 중간 |
| CrewAI | 멀티 에이전트 | Python | 25k+ | Agent, Task, Crew | 낮음 |
| AutoGen | 대화형 에이전트 | Python | 35k+ | Agent, GroupChat | 중간 |
상세 비교¶
| 항목 | LangChain | LlamaIndex | DSPy | Semantic Kernel |
|---|---|---|---|---|
| 핵심 강점 | 생태계 크기, 통합 | RAG 최적화, 인덱싱 | 자동 프롬프트 최적화 | 엔터프라이즈, 다언어 |
| 약점 | 추상화 과다, 복잡 | RAG 외 기능 제한적 | 학습 곡선, 문서 부족 | 생태계 작음 |
| RAG 지원 | 좋음 | 최고 | 좋음 (모듈형) | 좋음 |
| Agent 지원 | LangGraph (최고) | 기본 | 기본 | Planner |
| 메모리 | 다양한 옵션 | ChatMemory | 없음 | ChatHistory |
| 스트리밍 | 지원 | 지원 | 제한적 | 지원 |
| Observability | LangSmith 통합 | 기본 콜백 | 없음 | 기본 로깅 |
| 프로덕션 사례 | 많음 | 많음 | 증가 중 | Microsoft 생태계 |
아키텍처 패턴¶
1. Chain 패턴¶
순차적으로 단계를 연결하는 가장 기본적인 패턴.
적합한 경우: 단순 변환, 분류, 요약 등 단일 단계 작업
2. Agent 패턴¶
LLM이 도구를 선택하고 반복적으로 실행하는 자율적 패턴.
┌─────────────────────────────────────────┐
│ Agent Loop │
│ │
│ Input ──▶ [LLM: 판단] ──▶ 완료? │
│ │ │ │
│ │ No │ Yes │
│ ▼ ▼ │
│ [Tool 선택] Output │
│ │ │
│ ▼ │
│ [Tool 실행] │
│ │ │
│ ▼ │
│ [결과 관찰] ───▶ [LLM: 판단] │
│ │
└─────────────────────────────────────────┘
적합한 경우: 도구 사용, 복잡한 질의, 동적 워크플로
3. Graph 패턴 (LangGraph)¶
노드와 엣지로 복잡한 워크플로를 정의하는 패턴.
┌─────────────────────────────────────────────┐
│ Graph Workflow │
│ │
│ [분류기] ──┬──▶ [SQL 생성] ──▶ [실행] │
│ │ │ │
│ ├──▶ [RAG 검색] ──▶ [생성] │
│ │ │ │
│ └──▶ [예측 API] ──────┐│ │
│ ││ │
│ ┌───────────┘│ │
│ ▼ ▼ │
│ [결과 통합] ──▶ [응답] │
│ │
└─────────────────────────────────────────────┘
적합한 경우: 조건 분기, 병렬 처리, Human-in-the-loop, 복잡한 에이전트
4. Pipeline 패턴 (Haystack)¶
재사용 가능한 컴포넌트를 파이프라인으로 조립하는 패턴.
[Converter] → [Splitter] → [Embedder] → [Writer]
│
[Query] → [Embedder] → [Retriever] → [Ranker] → [Generator]
적합한 경우: 모듈화된 ETL + 검색 + 생성 파이프라인
상황별 선택 가이드¶
어떤 앱을 만드는가?
│
├── RAG 앱 (문서 Q&A)
│ ├── 단순 RAG → LlamaIndex
│ ├── 복잡한 인덱싱 전략 → LlamaIndex
│ └── Agent + RAG 결합 → LangGraph + LlamaIndex
│
├── Agent 시스템
│ ├── 단일 에이전트 (도구 사용) → LangGraph
│ ├── 멀티 에이전트 (역할 분담) → CrewAI / AutoGen
│ └── 복잡한 워크플로 (조건, 루프) → LangGraph
│
├── 프롬프트 최적화
│ └── 자동 프롬프트 튜닝 → DSPy
│
├── 엔터프라이즈 통합
│ ├── Microsoft 생태계 (.NET, Azure) → Semantic Kernel
│ └── Java 백엔드 → Semantic Kernel / Spring AI
│
└── 빠른 프로토타입
├── 간단한 체인 → LangChain
└── 에이전트 프로토타입 → CrewAI
선택 매트릭스¶
| 요구사항 | 1순위 | 2순위 | 비고 |
|---|---|---|---|
| RAG 앱 | LlamaIndex | LangChain | LlamaIndex가 인덱싱 전략 풍부 |
| 도구 사용 에이전트 | LangGraph | CrewAI | LangGraph가 제어 정밀도 높음 |
| 멀티 에이전트 | CrewAI | AutoGen | CrewAI가 단순, AutoGen이 유연 |
| 프롬프트 최적화 | DSPy | - | 유일한 전용 프레임워크 |
| 엔터프라이즈 | Semantic Kernel | Haystack | 다언어, 보안, 거버넌스 |
| 최소 오버헤드 | 직접 구현 | DSPy | 프레임워크 없이 가능한지 먼저 검토 |
코드 비교: 동일한 RAG 파이프라인¶
요구사항¶
문서를 검색하고 답변을 생성하는 기본 RAG 파이프라인.
LangChain 구현¶
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
# 1. 인덱싱
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(documents)
vectorstore = Chroma.from_documents(chunks, OpenAIEmbeddings())
retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
# 2. 체인 구성
prompt = ChatPromptTemplate.from_template("""
다음 문서를 참고하여 질문에 답하세요.
문서:
{context}
질문: {question}
""")
chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| ChatOpenAI(model="gpt-4o", temperature=0)
| StrOutputParser()
)
# 3. 실행
answer = chain.invoke("빈집 정비 절차는?")
LlamaIndex 구현¶
from llama_index.core import (
VectorStoreIndex,
SimpleDirectoryReader,
Settings,
)
from llama_index.llms.openai import OpenAI
from llama_index.embeddings.openai import OpenAIEmbedding
# 1. 설정
Settings.llm = OpenAI(model="gpt-4o", temperature=0)
Settings.embed_model = OpenAIEmbedding()
# 2. 인덱싱 + 쿼리 엔진 (한 줄)
documents = SimpleDirectoryReader("./data").load_data()
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine(similarity_top_k=5)
# 3. 실행
response = query_engine.query("빈집 정비 절차는?")
print(response)
DSPy 구현¶
import dspy
# 1. 설정
lm = dspy.LM("openai/gpt-4o", temperature=0)
dspy.configure(lm=lm)
# 2. Retriever 정의
retriever = dspy.ColBERTv2(url="http://colbert-server:8893/api/search")
# 3. RAG 모듈 정의
class RAG(dspy.Module):
def __init__(self, num_passages=5):
self.retrieve = dspy.Retrieve(k=num_passages)
self.generate = dspy.ChainOfThought("context, question -> answer")
def forward(self, question):
context = self.retrieve(question).passages
return self.generate(context=context, question=question)
# 4. 컴파일 (자동 프롬프트 최적화)
from dspy.teleprompt import BootstrapFewShot
trainset = [
dspy.Example(
question="빈집 정비 절차는?",
answer="빈집 정비는 실태조사, 정비계획 수립..."
).with_inputs("question"),
]
optimizer = BootstrapFewShot(metric=dspy.evaluate.answer_exact_match)
compiled_rag = optimizer.compile(RAG(), trainset=trainset)
# 5. 실행
result = compiled_rag("빈집 정비 절차는?")
구현 비교¶
| 항목 | LangChain | LlamaIndex | DSPy |
|---|---|---|---|
| 코드량 | 중간 (~20줄) | 적음 (~10줄) | 많음 (~30줄) |
| 커스터마이징 | 높음 | 중간 | 높음 (자동 최적화) |
| 프롬프트 관리 | 수동 | 자동 (기본) | 자동 (최적화) |
| 디버깅 용이성 | 좋음 (LangSmith) | 보통 | 보통 |
| 프로덕션 전환 | 직접적 | 직접적 | 컴파일 단계 필요 |
성능 비교¶
오버헤드 측정 (동일 RAG 파이프라인)¶
| 항목 | 직접 구현 | LangChain | LlamaIndex | DSPy |
|---|---|---|---|---|
| 콜드 스타트 | 100ms | 350ms | 280ms | 200ms |
| 요청당 오버헤드 | ~0ms | ~15ms | ~10ms | ~5ms |
| 메모리 (idle) | 50MB | 180MB | 150MB | 80MB |
| 의존성 수 | 3-5 | 50+ | 30+ | 10+ |
참고: LLM API 호출 자체가 500ms-3s이므로 프레임워크 오버헤드는 대부분 무시할 수준.
복잡도 비교¶
| 시나리오 | 직접 구현 | LangChain | LlamaIndex |
|---|---|---|---|
| 단순 RAG | 쉬움 | 쉬움 | 매우 쉬움 |
| 멀티모달 RAG | 어려움 | 중간 | 쉬움 |
| 에이전트 + 도구 | 매우 어려움 | 쉬움 (LangGraph) | 어려움 |
| 프롬프트 최적화 | 수동 (노가다) | 수동 | 수동 |
| 프롬프트 자동 최적화 | 불가 | 불가 | 불가 |
DSPy의 경우 프롬프트 자동 최적화가 가능하여, 반복적인 프롬프트 엔지니어링이 필요한 경우 큰 이점이 있다.
프로덕션 고려사항¶
에러 핸들링¶
# LangChain: fallback chain
from langchain_core.runnables import RunnableWithFallbacks
primary = ChatOpenAI(model="gpt-4o")
fallback = ChatOpenAI(model="gpt-4o-mini")
chain_with_fallback = primary.with_fallbacks([fallback])
# 직접 구현: 재시도 + fallback
import tenacity
MODELS = ["gpt-4o", "gpt-4o-mini", "claude-3.5-sonnet"]
@tenacity.retry(
stop=tenacity.stop_after_attempt(3),
wait=tenacity.wait_exponential(min=1, max=10),
retry=tenacity.retry_if_exception_type(
(openai.RateLimitError, openai.APITimeoutError)
),
)
async def call_with_retry(messages, model_index=0):
try:
return await client.chat.completions.create(
model=MODELS[model_index],
messages=messages,
)
except openai.RateLimitError:
if model_index + 1 < len(MODELS):
return await call_with_retry(messages, model_index + 1)
raise
재시도 전략¶
| 에러 타입 | 전략 | 최대 재시도 |
|---|---|---|
| Rate Limit (429) | Exponential backoff | 5회 |
| Timeout | 즉시 재시도 | 3회 |
| Server Error (500) | Exponential backoff | 3회 |
| Invalid Response | 프롬프트 변경 후 재시도 | 2회 |
| Context Length | 입력 축소 후 재시도 | 1회 |
Fallback 전략¶
Primary Model (gpt-4o)
│
├── Rate Limit → Secondary Model (claude-3.5-sonnet)
│
├── Timeout → Same model, 재시도
│
├── Context Too Long → 입력 축소 후 재시도
│
└── 전체 실패 → Cached response 또는 에러 메시지
프로덕션 체크리스트¶
| 항목 | 설명 | 우선순위 |
|---|---|---|
| 재시도 로직 | Exponential backoff + jitter | 필수 |
| Fallback 모델 | 주 모델 장애 시 대체 모델 | 필수 |
| Rate limit 관리 | 토큰 버킷, 큐잉 | 필수 |
| 타임아웃 설정 | 요청별 적절한 timeout | 필수 |
| 입력 검증 | 토큰 수 사전 체크, 유해 입력 필터 | 필수 |
| 출력 검증 | JSON 파싱, 포맷 체크 | 권장 |
| 캐싱 | Semantic cache, exact cache | 권장 |
| 비동기 처리 | async/await, 배치 API | 권장 |
| 모니터링 | 메트릭 수집, 알림 | 필수 |
| 비용 제한 | 일일/월간 비용 cap | 권장 |
프레임워크 없이 가능한 경우¶
모든 프로젝트에 프레임워크가 필요한 것은 아니다.
프레임워크 불필요¶
- 단일 LLM 호출 (분류, 요약 등)
- 고정된 2-3단계 파이프라인
- 팀에 LLM 경험이 풍부
- 최소 의존성 요구
프레임워크 권장¶
- RAG + Agent + 도구 결합
- 멀티 에이전트 시스템
- 빠른 프로토타이핑 후 반복
- 팀에 LLM 경험이 부족
- 평가/모니터링 통합 필요
참고 자료¶
| 자료 | 링크 |
|---|---|
| LangChain Documentation | https://python.langchain.com/docs/ |
| LlamaIndex Documentation | https://docs.llamaindex.ai/ |
| DSPy Documentation | https://dspy.ai/ |
| Semantic Kernel | https://learn.microsoft.com/semantic-kernel/ |
| LangGraph Guide | https://langchain-ai.github.io/langgraph/ |
최종 업데이트: 2026-03-25