바꿀 모델을 전제로 설계하라: LangChain으로 LLM 앱 조립하기
한 문장 결론: LangChain은 모델·프롬프트·메모리·검색(RAG) 같은 부품을 표준화해, LLM 교체와 기능 확장을 ‘조립’ 수준으로 낮춘다. 포인트는 체인(LCEL)로 데이터 흐름을 고정해 변경 비용을 줄이는 것이다.
LangChain은 모델·프롬프트·메모리·검색(RAG) 같은 부품을 표준화해, LLM 교체와 기능 확장을 “조립” 수준으로 낮춥니다.
LLM 앱은 왜 금방 복잡해지나
LLM 기능은 “모델 한 번 호출”로 시작하지만, 금방 다음 부품이 붙습니다.
- 프롬프트 템플릿
- 입력 검증과 출력 파싱
- 대화형 UX를 위한 메모리
- 최신/사내 데이터를 위한 검색(Retrieval, RAG)
핵심은 모델을 하나 고르는 일이 아니라, 부품이 늘어나도 변경 비용이 폭증하지 않게 구조를 고정하는 일입니다.
구조를 튼튼하게 만드는 두 가지 원칙
- 교체 가능성
- 특정 LLM 공급자나 SDK에 종속되지 않도록 “경계”와 “인터페이스”를 먼저 고정합니다.
- 조합 가능성
- 프롬프트·메모리·검색·파서 같은 부품을 “흐름”으로 연결해 확장합니다.
배경/문제
LLM 기반 기능은 보통 다음 패턴으로 커집니다.
- 모델 호출만 하던 코드에 프롬프트 템플릿, 유효성 검사, 출력 파싱이 붙습니다.
- 대화형 UX를 위해 메모리가 필요해집니다.
- 최신/사내 데이터가 필요해져 검색(Retrieval)과 벡터 저장소가 붙습니다.
이 흐름을 그때그때의 접착 코드(glue code)로 처리하면, 어느 순간부터 모델 변경이나 기능 추가가 곧 리팩터링 프로젝트가 됩니다.
핵심 개념
1) LangChain이 제공하는 “부품”
LangChain은 LLM 앱 구성 요소를 추상화(abstraction) 로 정리합니다. 복잡한 내부는 숨기고, 필요한 인터페이스만 남겨서 교체와 확장을 쉽게 만듭니다.
아래 다이어그램은 글에서 다루는 핵심 구성 요소와 연결 관계입니다.
기대 결과: 기능이 늘어나도 “어디를 바꾸면 되는지”가 부품 단위로 분리되어, 교체와 확장이 예측 가능해집니다.
2) 체인(Chain)을 “흐름”으로 고정한다
체인(Chain)은 “명령 시퀀스”라기보다, 아래 같은 데이터 흐름을 연결해 둔 파이프라인입니다.
- 프롬프트 → 모델 호출 → (필요 시) 후처리
메시지 기반 프롬프트는 템플릿 변수를 선언하고, 호출 시점에 필요한 입력이 채워지는지 검증하는 데 유리합니다.
해결 접근
접근 1) “모델 SDK”보다 “네트워크 경계”를 먼저 고정한다
프런트엔드에서 가장 먼저 고정할 건 모델 호출 라이브러리가 아니라 요청이 지나가는 경계입니다.
- 브라우저는 우리 서버(Next.js Route Handler) 만 호출합니다.
- 서버가 API 키, 요청 검증, 공급자 변경을 책임집니다.
이 패턴은 키 노출 위험을 줄이고, 모델 교체 시 프런트엔드 변경 폭도 줄입니다.
접근 2) “번역 체인”을 최소 단위로 만든다
가장 작은 실전 예시는, 메시지 기반 프롬프트와 채팅 모델을 조합해 번역을 수행하는 형태입니다.
구현(코드)
아래 코드는 “Next.js 앱에서 안전한 경계를 만들고, 서버에서만 키를 다루는” 형태로 재구성한 예시입니다. 호출 방식(동일 프로세스/별도 백엔드)은 환경/정책에 따라 달라질 수 있습니다.
1) LangChain 번역 체인 (서버 전용, Python)
# translator_chain.py
import os
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are a translator bot. Translate sentences from {source_language} to {target_language}.",
),
("human", "Translate this: {sentence}"),
]
)
llm = ChatOpenAI(
temperature=0.1,
api_key=os.environ["OPENAI_API_KEY"],
)
chain = prompt | llm
def translate(source_language: str, target_language: str, sentence: str) -> str:
res = chain.invoke(
{
"source_language": source_language,
"target_language": target_language,
"sentence": sentence,
}
)
return res.content기대 결과: 프롬프트와 모델 호출이 체인으로 묶여 “번역 기능”이 독립된 부품이 됩니다. 키는 환경 변수로만 주입되어 코드에 남지 않습니다.
2) Next.js Route Handler로 API 경계 만들기
// app/api/translate/route.ts
import { NextResponse } from "next/server";
export async function POST(req: Request) {
const body = await req.json();
const source_language = String(body?.source_language ?? "");
const target_language = String(body?.target_language ?? "");
const sentence = String(body?.sentence ?? "");
if (!source_language || !target_language || !sentence) {
return NextResponse.json(
{ ok: false, error: "invalid_payload" },
{ status: 400 }
);
}
// 여기서부터는 팀 환경에 따라 달라질 수 있습니다.
// - 같은 서버 프로세스에서 Python을 호출하거나
// - 별도 백엔드(예: FastAPI)로 체인을 서비스하고
// - Next.js는 그 서버로 프록시합니다.
return NextResponse.json({ ok: true });
}기대 결과: 브라우저는 /api/translate만 호출하고, 모델 키, 공급자, 호출 방식은 서버에서 통제할 수 있습니다.
3) 클라이언트에서 호출 (Client Component)
"use client";
export async function translate(sentence: string) {
const res = await fetch("/api/translate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
source_language: "en",
target_language: "ko",
sentence,
}),
});
return res.json();
}기대 결과: 브라우저 전용 코드는 Client Component로 고정되고, 서버가 비밀정보와 정책을 관리하는 경계가 명확해집니다.
검증 방법(체크리스트)
흔한 실수/FAQ
Q1. API 키를 프런트엔드에 넣으면 안 되나요?
브라우저에 키가 노출되면 악용될 수 있고, 비용과 데이터 접근 리스크로 이어질 수 있습니다. 키는 서버에서만 다루고, 클라이언트는 Route Handler만 호출하는 구조가 안전합니다.
Q2. RAG을 붙이면 무엇이 달라지나요?
모델이 학습하지 않은 사내 문서나 최신 정보를 사용해야 할 때, 검색 결과를 컨텍스트로 주입해 응답 품질을 끌어올리는 패턴입니다. LangChain에서는 Retriever를 부품으로 연결해 흐름을 구성합니다.
Q3. 프롬프트 주입(Injection) 이슈는 어떻게 보나요?
사용자 입력이 시스템 지시를 덮어쓰도록 유도하는 공격이 있을 수 있습니다. 제품에서는 입력과 출력 검증, 도구 실행 경계를 함께 설계하는 편이 안전합니다.
요약(3~5줄)
LangChain은 LLM 앱을 “모델 호출 코드”가 아니라 “조립 가능한 부품”으로 바라보게 해줍니다.
체인으로 데이터 흐름을 고정하면, 프롬프트·메모리·검색(RAG) 같은 기능을 붙여도 변경 비용이 급격히 커지지 않습니다.
Next.js에서는 Route Handler로 경계를 만들고, 서버에서만 키와 정책을 관리하는 구성이 안정적입니다.
결론
LLM 기능은 모델 자체보다 “변경에 강한 구조”가 더 오래 남습니다.
부품을 표준화하고, 흐름을 체인으로 고정하면, 새로운 요구사항이 들어와도 조립으로 대응할 수 있습니다.