코어 6 + 추가 4 · 각 도식마다 개념·컴포넌트·예시·함정·프레임워크 상세 설명 · 2026-09-21
📖 이 문서의 목적
LLM 앱 프레임워크를 고를 때 흔히 마주치는 혼란 — "LangChain vs LlamaIndex", "LangGraph vs AutoGen" 같은 비교는 프레임워크 카탈로그 수준에 머무름. 이 문서는 그보다 한 단계 위에서 "LLM 앱이 풀어야 할 5가지 문제"를 정의하고, 각 문제를 어떤 도식과 도구로 다루는지 보여주는 게 목표.
읽는 법: 처음이라면 1번(멘탈 모델) → 2번(전략 맵) → 3번(파이프라인) 순서로. 이미 LLM 앱을 만들고 있다면 4·5번(런타임/아키텍처)부터.
대상: LLM 앱을 처음 만드는 주니어, 또는 MLOps/DevOps 관점에서 LLM 파이프라인을 검토해야 하는 시니어.
LLM 앱 프레임워크가 풀어야 할 5가지 문제를 한 눈에 보여줘. 멘탈 모델을 먼저 잡고, 나머지 도식이 어디에 위치하는지 기준점을 만들어.
💡 핵심 개념: LLM은 텍스트를 입력받아 텍스트를 출력하는 함수에 불과. 실전 LLM 앱은 그 함수를 둘러싼 5가지 주변 문제(외부 지식, 도구 사용, 프롬프트 관리, 타입 안전성, 에이전트 흐름)를 풀어야 함. 이 5가지가 프레임워크 비교의 진짜 축.
mindmap
root((LLM 앱 프레임워크))
RAG Pipeline
인덱싱
문서 로드
청크 분할
임베딩
검색·생성
벡터 검색
리랭킹
컨텍스트 주입
에이전트 오케스트레이션
ReAct 루프
Tool 호출
State 관리
Human-in-loop
프롬프트 최적화
시그니처
모듈 조립
컴파일러
평가 메트릭
타입 안정성
Pydantic Schema
DI
모킹
시스템 통합
🔧 5가지 축의 의미
RAG Pipeline — LLM이 모르는 외부 지식을 검색해 컨텍스트로 주입. 왜? LLM 학습 cutoff 이후 데이터, 사내 문서, 실시간 DB는 직접 모름.
에이전트 오케스트레이션 — LLM이 스스로 도구를 골라 반복 실행. 왜? 단일 호출로 끝나지 않는 다단계 추론(검색→읽기→코딩→검증).
프롬프트 최적화 — 사람이 손으로 짜는 프롬프트를 컴파일러가 자동 튜닝. 왜? 모델 변경 시 프롬프트 재작성이 부담, few-shot 예시도 데이터로 관리.
타입 안정성 — LLM의 자유 텍스트 출력을 구조화된 객체로 강제. 왜? JSON 파싱 실패가 프로덕션 장애 원인 1위.
📋 구체적 예시: 사내 HR 정책 챗봇을 만든다고 치면 —
RAG: HR 정책 PDF를 인덱싱해서 "연차 며칠?"에 정확히 답하게
Agent: "내 연차 잔여 알려줘" → DB 조회 tool 호출 → 답변
Prompt Opt: "답변 톤"을 컴파일러로 자동 튜닝
Type Safety: tool 결과를 Pydantic 모델로 파싱 (실패 시 graceful)
⚠️ 흔한 함정:
한 축(예: RAG)만 깊게 파고 나머지를 무시 → 나중에 통합 지옥
"LangChain = 다 된다"는 환상 → 범용성은 깊이 희생
5축을 다 처음부터 만들려 함 → RAG → Agent → Type 순서로 단계적 도입이 안전
🛠️ 관련 프레임워크: 각 축의 1등 시민 — LlamaIndex/Haystack(RAG), LangGraph/AutoGen/CrewAI(Agent), DSPy(Prompt Opt), Pydantic AI/Instructor/Semantic Kernel(Type Safety).
2. 전략 맵 — 프레임워크 위치
각 프레임워크를 "단순↔복잡" × "오프라인/데이터↔온라인/추론" 2차원에 배치.
💡 핵심 개념: 프레임워크 선택은 기능 비교가 아니라 내 상황에 맞는 위치 매핑. x축은 "도구 사용 난이도", y축은 "데이터/추론 어느 쪽에 시간 들이는가".
우하(Q4): 고급 데이터 파이프라인 — Haystack 같은 production-grade 검색 시스템.
📋 구체적 예시: "내 팀은 FastAPI 백엔드에 RAG 붙이려 한다" → 좌상(Q2) + 좌하(Q3) 조합 추천: Pydantic AI로 타입 안전한 호출 + LlamaIndex로 인덱싱.
⚠️ 흔한 함정:
"무조건 LangChain" 신봉 → 실제론 80% 경우 LlamaIndex/Pydantic AI가 더 깔끔
프로토타입에 LangGraph 도입 → 그래프 디버깅이 처음엔 너무 복잡
오프라인 데이터 작업 적은 팀이 Haystack 도입 → 도구 과잉
🛠️ 1줄 추천: 데이터 많음 → LlamaIndex. 백엔드 통합 → Pydantic AI. 에이전트 → LangGraph(명시적 그래프) or AutoGen(대화형).
3. 데이터 흐름 — RAG Pipeline
RAG Pipeline의 end-to-end 데이터 흐름. 인덱싱이 벡터 DB를 채우고, 검색·생성이 그걸 읽어 LLM으로 보냄.
💡 핵심 개념: RAG는 한 번에 끝나는 게 아니라 두 개의 시점(오프라인/온라인)으로 나뉨. 인덱싱은 배치로 천천히 돌고, 검색·생성은 사용자 요청마다 실시간. 둘을 잇는 게 벡터 DB.
flowchart LR
subgraph OFF["📥 인덱싱 (오프라인·배치)"]
D[문서]:::off
C[청크]:::off
E[임베딩]:::off
D --> C --> E
end
VDB[("🗄️ 벡터 DB")]:::store
subgraph ON["🔍 검색·생성 (온라인·추론)"]
Q[질의]:::on
R[검색·리랭킹]:::on
G[컨텍스트 주입]:::on
Q --> R --> G
end
LLM{{"🧠 LLM"}}:::llm
A["📨 응답"]:::resp
E ==>|"bulk upsert"| VDB
VDB ==>|"top-k retrieve"| R
G --> LLM --> A
classDef off fill:#fef3c7,stroke:#f59e0b,color:#000
classDef on fill:#dbeafe,stroke:#3b82f6,color:#000
classDef store fill:#e5e7eb,stroke:#6b7280,color:#000
classDef llm fill:#f3e8ff,stroke:#a855f7,color:#000
classDef resp fill:#dcfce7,stroke:#10b981,color:#000
🔧 단계별 책임
Loader — PDF·HTML·DB 등 다양한 소스에서 텍스트 추출 (Unstructured.io, PyPDF)
Splitter — 긴 문서를 의미 단위로 분할 (512~1024 토큰이 일반적)
Embedder — 텍스트 → 벡터 (OpenAI text-embedding-3, Cohere embed-v3, BGE-M3)
Vector DB — 벡터 저장·검색 (Pinecone, Weaviate, Qdrant, pgvector)
Retriever — query와 유사한 top-k chunk 검색 (cosine/dot product)
Reranker — 검색 결과 재정렬로 정밀도↑ (Cohere rerank, BGE reranker)
Context Injector — chunk를 prompt 템플릿에 끼워 넣기
📋 구체적 예시 (한국어 사내 문서 QA):
Notion 페이지 1000개를 Loader로 추출 (PDF, HTML)
Splitter로 페이지당 평균 800 토큰 청크
임베딩 모델은 text-embedding-3-small (저렴) 또는 text-embedding-3-large (정밀)
Pinecone 서버리스에 upsert
사용자 "연차 정책" 검색 → top-10 chunk → rerank → top-3
Prompt: "다음 문서 기반으로 답해. 문서: {chunk}. 질문: {query}"
⚠️ 흔한 함정:
청크 사이즈 잘못 — 너무 크면 검색 정밀도↓, 너무 작으면 맥락 손실. 도메인 PDF는 보통 512 토큰이 안전.
임베딩 모델 변경 시 재임베딩 필요 — 모델 바꾸면 모든 벡터 무효. 마이그레이션 계획 필수.
벡터 DB 메타데이터 누락 — chunk에 source/timestamp/author 저장 안 하면 필터링·디버깅 힘듦.
Reranker 생략 — top-10을 그냥 LLM에 넣으면 노이즈 많음. rerank로 top-3~5 압축 권장.
🛠️ 관련 프레임워크:LlamaIndex(전체 파이프라인), Haystack(production DAG), LangChain(범용), Unstructured.io(Loader 전담).
4. 시간 순서 — 런타임 호출
사용자 질문이 들어와서 응답이 나가는 런타임 호출 시퀀스.
💡 핵심 개념: 데이터 흐름(3번)이 "데이터 어디로 가나"라면, 이 도식은 "누가 언제 누구를 부르나". API 서버·Retriever·LLM은 각각 다른 프로세스/스레드일 수 있어 호출 순서·latency 파악이 중요.
sequenceDiagram
actor User as 👤 사용자
participant API as 🖥️ API
participant R as 🔍 Retriever
participant VDB as 🗄️ Vector DB
participant L as 🧠 LLM
User->>API: 질문 입력
activate API
API->>R: 검색 요청 (질의)
activate R
R->>R: 질의 임베딩
R->>VDB: top-k 검색
activate VDB
VDB-->>R: 관련 chunk들
deactivate VDB
R-->>API: 리랭킹된 chunk들
deactivate R
API->>L: 컨텍스트 + 질문
activate L
L-->>API: 응답 생성
deactivate L
API-->>User: 최종 응답
deactivate API
🔧 단계별 latency (typical)
질의 임베딩 — 50~200ms (OpenAI API)
Vector DB 검색 — 5~50ms (Pinecone p95)
Rerank — 100~300ms (Cohere rerank)
LLM 응답 — 500~3000ms (GPT-4 streaming 기준 첫 토큰까지)
총합 — streaming 안 쓰면 1~4초. UX 임계점: 1초 이내면 "빠르다", 3초 넘으면 "느리다".
📋 구체적 예시 (토스 FAQ 챗봇):
사용자 "적금 해지 어떻게 해?" 입력
API 서버(FastAPI)가 인증·rate-limit 체크 → Retriever 호출
Retriever가 질의 임베딩 + Qdrant 검색 (top-10)
Cohere rerank로 top-3 압축
Prompt 조립: "고객 상담사처럼 답해. 매뉴얼: {top3}. 질문: {q}"
GPT-4o 응답 (streaming, 첫 토큰 800ms 후)
SSE로 사용자에게 토큰 단위 전송
⚠️ 흔한 함정:
전체 흐름 동기 호출 — LLM 응답 기다리는 동안 thread 점유. 해결: streaming + 백프레셔
에러 시 부분 실패 처리 — 검색 실패해도 LLM만 호출? 또는 전체 fail? 정책 필요
타임아웃 미설정 — LLM 30초 걸리면 사용자 떠남. 10초 타임아웃 + graceful 메시지
rate limit 무시 — 임베딩/LLM API는 분당 호출 제한. 큐잉 필수
🛠️ 도식화 도구:sequenceDiagram(mermaid)이 이 패턴의 표준. PlantUML도 가능. 코드 추적은 OpenTelemetry(분산 trace).
5. 내부 구조 — 레이어드 아키텍처
시스템을 계층으로 분리. 어디에 어떤 컴포넌트가 사는 보여줌.
💡 핵심 개념: 좋은 아키텍처는 관심사 분리(SoC). "UI 코드"와 "DB 쿼리"가 한 함수에 섞이면 안 됨. LLM 앱에선 특히 프롬프트 조립을 도메인 로직에서 분리하는 게 핵심.
flowchart TB
subgraph CL["🖥️ 클라이언트 레이어"]
U[👤 사용자]
end
subgraph AL["🌐 API 레이어"]
A["FastAPI 서버"]
end
subgraph RL["📦 RAG 레이어"]
RP["RAG Pipeline"]
RT["Retriever"]
RD["Indexer"]
end
subgraph LL["🧠 LLM 레이어"]
LP["LLM Provider"]
end
subgraph SL["💾 스토리지 레이어"]
VDB[("Vector DB")]
MDB[("Metadata DB")]
end
subgraph XL["🔁 횡단 관심사"]
AG["🤖 Agent"]
PO["✨ Prompt Opt"]
TS["🛡️ Type Safety"]
end
U <--> A
A --> RP
RP --> RD
RP --> RT
RD --> VDB
RT <--> VDB
RT --> A
A --> LP
LP --> A
RD <--> MDB
AG -.도구.-> LP
PO -.최적화.-> LP
TS -.검증.-> A
RetrievedChunk.score — 거리 점수. 낮을수록 유사(cosine distance).
Prompt.context — 시스템 메시지에 들어갈 chunk 배열. 토큰 한도 주의.
📋 Pydantic 구현 예시:
from pydantic import BaseModel, Field
from datetime import datetime
from typing import Literal
class Document(BaseModel):
id: str
title: str
source: str # 'notion://page/123' or 's3://bucket/key'
created_at: datetime
metadata: dict = Field(default_factory=dict)
class Chunk(BaseModel):
id: str
doc_id: str
text: str
position: int
token_count: int
class Embedding(BaseModel):
chunk_id: str
model: str # 'text-embedding-3-small'
vector: list[float]
dimensions: int
⚠️ 흔한 함정:
Float[] 그대로 DB 저장 — 메모리 폭발. pickle/jsonb로 압축 저장 권장
dimensions 안 저장 — 나중에 모델 바꿨는데 옛날 벡터 섞이면 검색 망가짐
Chunk.id 안 unique — 같은 doc에서 같은 chunk 두 번 생성되면 벡터 중복. hash(doc_id + position + text)
🛠️ 구현: Python Pydantic, TypeScript Zod, Go struct + validator. LangChain은 자체 Document 클래스 제공하지만 커스텀 권장.
8. DB 스키마 — ER 다이어그램 추가
PostgreSQL 같은 RDB에 저장되는 테이블 관계. 벡터는 VECTOR 타입 (pgvector) 또는 별도 Vector DB 참조.
💡 핵심 개념: 벡터는 두 가지 저장 옵션. (1) Postgres + pgvector 확장 — 한 DB로 통합, transactional 보장. (2) Pinecone/Qdrant 같은 전용 Vector DB — 성능/스케일 유리, sync 부담. 트레이드오프 있음.
erDiagram
DOCUMENT ||--o{ CHUNK : contains
CHUNK ||--|| EMBEDDING : has
EMBEDDING }o--|| VECTOR_INDEX : stored_in
USER ||--o{ QUERY : submits
QUERY ||--|| RESPONSE : produces
RESPONSE }o--o{ CHUNK : cites
DOCUMENT {
string id PK
string title
string source_uri
datetime created_at
jsonb metadata
}
CHUNK {
string id PK
string doc_id FK
int position
text content
int token_count
}
EMBEDDING {
string chunk_id PK,FK
string model
int dimensions
vector vector
}
VECTOR_INDEX {
string name PK
string embedding_model
int dimensions
string distance_metric
}
USER {
string id PK
string email
}
QUERY {
string id PK
string user_id FK
text question
datetime created_at
}
RESPONSE {
string id PK
string query_id FK
text answer
jsonb metadata
datetime generated_at
}
🔧 인덱스 전략
CHUNK(doc_id, position) — 문서 내 순서 조회
EMBEDDING — vector 컬럼에 HNSW 인덱스 (pgvector) CREATE INDEX ... USING hnsw (vector vector_cosine_ops)
QUERY(user_id, created_at DESC) — 사용자별 최근 질문
RESPONSE(query_id) — 1:1 매핑, 빠른 lookup
📋 pgvector 통합 예시:
-- pgvector 활성화
CREATE EXTENSION IF NOT EXISTS vector;
-- 테이블
CREATE TABLE chunk (
id UUID PRIMARY KEY,
doc_id UUID REFERENCES document(id),
position INT,
content TEXT,
token_count INT
);
CREATE TABLE embedding (
chunk_id UUID PRIMARY KEY REFERENCES chunk(id),
model VARCHAR(64),
dimensions INT,
vector VECTOR(1536) -- OpenAI text-embedding-3-small
);
-- HNSW 인덱스 (코사인 유사도)
CREATE INDEX embedding_vector_idx
ON embedding USING hnsw (vector vector_cosine_ops);
-- 검색
SELECT c.content, e.vector <=> $1 AS distance
FROM embedding e JOIN chunk c ON c.id = e.chunk_id
ORDER BY distance
LIMIT 5;
⚠️ 흔한 함정:
인덱스 없이 검색 — 100만 row 풀스캔. HNSW/IVFFlat 필수
dimensions 안 맞추기 — 1536 모델 쓰면서 column을 VECTOR(768)로 만들면 insert 실패
metadata jsonb 안 쓰기 — source/author/timestamp 다 별도 컬럼 → 폭발. jsonb로 묶기
cascade 삭제 안 설정 — document 지우면 chunk orphan. ON DELETE CASCADE
💡 핵심 개념: 시스템 아키텍처가 아무리 좋아도 사용자가 답을 못 얻으면 실패. "답 정확도"만 보지 말고 "답까지 걸리는 시간", "출처 표시", "재질문 흐름" 같은 UX 지표도 봐야 함.
journey
title RAG 시스템 사용자 여정
section 문제 인식
정보 부족 인식: 3: User
시스템 접속: 5: User
section 검색 시도
질문 입력: 5: User, System
벡터 검색 실행: 4: System
관련 문서 추출: 4: System
section 답변 생성
컨텍스트 구성: 3: System
LLM 응답 생성: 5: System
출처 인용 표시: 4: System
section 검증
답변 확인: 4: User
후속 질문: 3: User
만족/피드백: 4: User
🔧 단계별 UX metric
첫 토큰 latency — 첫 글자까지 1초 이내 권장 (LLM streaming)
답변 정확도 — 사용자 평가 4점 이상 비율 목표
출처 신뢰도 — 인용된 chunk의 relevance 평균 점수
재질문 비율 — 첫 답변에 만족 못한 비율. 낮을수록 좋음
Hallucination rate — 답변 중 근거 없는 문장 비율
📋 만족 여정 vs 좌절 여정 비교:
시나리오
만족 여정
좌절 여정
질문 입력
자동완성, 예시 질문
빈 입력란, "뭘 물어봐야 할지" 모름
응답
2초 내 streaming + 출처
10초 대기 + 출처 없음
후속
"더 자세히", "예시 보여줘" 버튼
새 질문 처음부터 다시
피드백
👍/👎 1클릭 + 코멘트
피드백 없음, 개선 불가
⚠️ 흔한 함정:
"답 정확도"만 KPI로 — 빠르지만 틀린 답이면 사용자 이탈. latency도 KPI
출처 표시 안 함 — hallucination 검증 불가. "이 답의 근거: [chunk N]" 표시
재질문 흐름 없음 — "다음 중 어떤 게 의도?" 같은 disambiguation 질문 UX 안 넣음
피드백 수집 안 함 — 개선 사이클 막힘. 👍/👎 + 로그 연결 필수
🛠️ UX 도구:Streamlit/Gradio(프로토타입), Next.js + Vercel AI SDK(프로덕션), LangSmith(trace/feedback 수집).
10. 구현 로드맵 — Gantt 추가
LLM 앱을 처음부터 만들어갈 때의 단계별 일정 (예시). 기초 → RAG → 에이전트 → 프로덕션화 순서.
💡 핵심 개념: LLM 앱 학습은 단계적이 가장 빠름. 처음부터 멀티에이전트 만들려 하면 디버깅 지옥. "Hello LLM → RAG → 평가 → Agent → 프로덕션화" 순서가 검증된 학습 경로.
gantt
title LLM 앱 프레임워크 학습·구현 로드맵 (예시)
dateFormat YYYY-MM-DD
axisFormat %m/%d
section 기초 이해
LLM API 이해 :a1, 2026-09-21, 7d
프롬프트 작성 :a2, after a1, 5d
section RAG Pipeline
인덱싱 파이프라인 :b1, after a2, 7d
검색·생성 파이프라인 :b2, after b1, 7d
평가 메트릭 설계 :b3, after b2, 5d
section 에이전트
LangGraph 기초 :c1, after b3, 7d
멀티에이전트 :c2, after c1, 7d
section 프로덕션화
타입 안정성 적용 :d1, after c2, 5d
모니터링·로깅 :d2, after d1, 5d
🔧 단계별 산출물 + 검증 방법
a1 LLM API 이해 — OpenAI/Anthropic API 직접 호출. 검증: 간단 Q&A 챗봇 동작