[LangChain] LangChain이란? — LCEL, Prompt Template, Few-shot, Output Parser
by 우와한개발자
1. LangChain과 LangGraph
1) LangChain
(1) 개념
- LangChain은 LLM 기반 애플리케이션을 구축하기 위한 프레임워크이다.
- Prompt, Model, Parser, Retriever, Tool, Memory 등을 연결하여 하나의 파이프라인으로 구성할 수 있다.
- 가장 큰 사용 이유는 LLM 애플리케이션에 필요한 생태계를 통합하기 위해서이다.
(2) 주요 기능
- LLM, Prompt, Memory를 연결하는 체인 구성
- Vector DB와 연동한 RAG 지원
- 외부 API와 함수를 연결하는 Tool 통합
- 다양한 모델, DB, 프레임워크와의 생태계 통합
(3) 기본 파이프라인
Prompt → Model → Output
(4) 주요 구성요소
- Model: LLM 또는 Chat Model을 연결한다.
- Prompt: 모델에 전달할 입력을 설계한다.
- Parser: 모델 출력을 구조화한다.
- Tool: 외부 API 및 함수를 연결한다.
- Retriever: Vector DB에서 관련 문서를 검색한다.
- Agent: 도구 선택 및 작업 자동화를 수행한다.
2) LangGraph
(1) 개념
- LangGraph는 Agent 워크플로우를 그래프 구조로 설계하기 위한 프레임워크이다.
- LangChain보다 복잡한 상태 관리, 조건 분기, 반복 실행, Multi-Agent 구조를 설계하는 데 적합하다.
(2) 그래프 기반 설계
- 노드와 엣지로 Agent 흐름을 명확히 정의한다.
- 각 노드는 하나의 작업 단계를 의미하고, 엣지는 다음 실행 흐름을 의미한다.
(3) 상태 관리
- 공유 상태 객체를 기반으로 복잡한 흐름을 제어한다.
- 이전 단계의 결과를 다음 단계에서 사용할 수 있다.
(4) Multi-Agent 지원
- 여러 Agent 간 협업과 오케스트레이션을 구현할 수 있다.
- 예를 들어 검색 Agent, 분석 Agent, 보고서 작성 Agent를 분리하여 협업하게 만들 수 있다.
(5) 디버깅 용이
- 실행 과정을 시각화하고 단계별로 추적할 수 있다.
- 복잡한 Agent 흐름에서 어느 단계에서 문제가 발생했는지 확인하기 쉽다.
2. LangChain 구현 기초
1) 라이브러리 설치
(1) requirements.txt 예시
langchain
langchain-openai
langchain-community
langchain-core
langchain-chroma
langgraph
chromadb
pypdf
tiktoken
python-dotenv
streamlit
duckduckgo-search
2) 기본 구조
- prompt에 중괄호 {}로 작성한 변수는 invoke 시 Python 딕셔너리 형태로 값을 할당한다.
- prompt | llm은 해당 프롬프트를 LLM에게 전달한다는 의미이다.
- LangChain에서는 | 연산자를 사용하여 Prompt, Model, Parser 등을 연결한다.
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
load_dotenv()
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
prompt = ChatPromptTemplate.from_template(
"""
당신은 AI 전문가입니다.
질문:
{question}
"""
)
chain = prompt | llm
result = chain.invoke(
{"question": "LangChain을 설명해주세요"}
)
print(result.content)
3) LCEL(LangChain Expression Language)
(1) 개념
- LCEL은 LangChain Expression Language의 약자이다.
- LangChain의 파이프라인을 선언적으로 정의할 수 있는 표준 문법이다.
- | 연산자로 구성요소를 연결한다.
(2) 예시
chain = prompt | llm | parser
(3) 특징
- 비동기 실행을 지원한다.
- 스트리밍을 지원한다.
- 배치 처리를 지원한다.
- Spring AI에서 메서드를 .으로 연결해 chaining하는 것과 비슷하게 이해할 수 있다.
3. Prompt Engineering
1) 작성 방법
(1) 구체적이고 간결하게 지시
- 모델이 해야 할 일을 명확하게 작성한다.
- 애매한 표현보다 원하는 역할, 조건, 출력 형식을 구체적으로 제시한다.
(2) 역할 부여
- 모델에게 특정 역할을 부여하면 답변의 방향이 명확해진다.
- 예를 들어 “당신은 Python 전문가입니다.”처럼 작성할 수 있다.
(3) 출력 형식 명시
- JSON, 표, 목록 등 원하는 출력 형식을 명시한다.
- 애플리케이션에서 결과를 활용해야 한다면 구조화된 형식이 중요하다.
(4) Few-shot 예시 제공
- 몇 가지 예시를 제공하면 모델이 원하는 응답 패턴을 더 잘 따른다.
- Zero-shot보다 일관되고 정확한 응답을 얻을 수 있다.
2) Prompt Template
(1) 개념
- 프롬프트를 재사용 가능한 형태로 정의하는 방식이다.
- {변수명}을 사용하여 런타임에 값을 주입할 수 있다.
(2) 특징
- 변수 삽입: {변수명}으로 런타임 값 주입
- 역할 분리: system / human / ai 메시지 구분
- 재사용성: 하나의 템플릿으로 다양한 입력 처리
(3) 코드 예시
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 {role}입니다."),
("human", "{question}")
])
chain = prompt | llm
response = chain.invoke({
"role": "Python 전문가",
"question": "리스트 내포란?"
})
3) Few-shot Prompting
(1) 개념
- Few-shot Prompting은 모델에 몇 가지 예시를 제공하여 원하는 출력 패턴을 학습시키는 기법이다.
- 모델을 실제로 재학습시키는 것은 아니지만, 프롬프트 안의 예시를 참고하여 비슷한 형식으로 답변하게 만든다.
(2) 코드 예시
from langchain_core.prompts import ChatPromptTemplate
examples = [
("서울", "대한민국"),
("도쿄", "일본"),
("파리", "프랑스")
]
messages = [
("system", "도시에 맞는 국가를 답하세요.")
]
for city, country in examples:
messages.append(("human", f"{city}는 어느 나라의 도시인가요?"))
messages.append(("ai", country))
messages.append(("human", "{city}는 어느 나라의 도시인가요?"))
prompt = ChatPromptTemplate.from_messages(messages)
chain = prompt | llm
response = chain.invoke({
"city": "뉴욕"
})
4. Output Parser와 구조화 출력
1) Output Parser
(1) 개념
- Output Parser는 LLM의 원시 텍스트 출력을 애플리케이션이 처리하기 좋은 구조화된 형태로 변환하는 도구이다.
- 단순 문자열이 아니라 JSON, 객체, 리스트 등의 형태로 변환할 수 있다.
(2) 필요한 이유
- LLM의 응답은 기본적으로 자연어 텍스트이다.
- 애플리케이션에서 사용하려면 필드명, 타입, 구조가 일정해야 한다.
- Output Parser를 사용하면 LLM 응답을 코드에서 다루기 쉬운 형태로 변환할 수 있다.
(3) 출력 형태 예시
- 문자열 출력: LLM의 원시 텍스트를 그대로 반환
- JSON 출력: 구조화된 JSON 형식으로 변환
- Pydantic 객체 출력: 타입이 정의된 객체로 변환
2) Pydantic
(1) 개념
- Pydantic은 Python의 데이터 유효성 검증 및 직렬화 라이브러리이다.
- LLM 출력을 타입 안전하고 구조화된 객체로 변환할 때 사용할 수 있다.
(2) Java와 비교
- Java의 DTO나 record 타입처럼, 정해진 필드와 타입을 가진 데이터 객체를 정의하는 방식과 비슷하다.
- 예를 들어 name: str, age: int처럼 필드 타입을 미리 정의하면, LLM의 출력이 해당 구조에 맞는지 검증할 수 있다.
3) PydanticOutputParser
(1) 개념
- PydanticOutputParser는 LLM의 응답을 Pydantic 모델에 맞게 변환한다.
- Parser 객체를 직접 생성하고, format_instructions를 프롬프트에 포함하여 LLM에게 출력 형식을 지시한다.
(2) 주의점
- LLM 응답의 필드명이 Pydantic 모델의 필드명과 일치해야 매핑된다.
- LLM 응답에 필요한 필드가 없거나 필드명이 다르면 파싱 오류가 발생할 수 있다.
- 필드 누락을 허용하고 싶다면 Optional 또는 기본값을 지정할 수 있다.
(3) 코드 예시
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
load_dotenv()
class Answer(BaseModel):
name: str = Field(description="기술 이름")
description: str = Field(description="기술 설명")
use_cases: str = Field(description="사용 사례")
parser = PydanticOutputParser(pydantic_object=Answer)
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
prompt = ChatPromptTemplate.from_template(
"""
당신은 AI 전문가입니다.
다음 기술을 설명해주세요.
질문:
{question}
반드시 아래 형식으로 답변하세요.
{format_instructions}
"""
)
chain = prompt | llm | parser
result = chain.invoke({
"question": "LangChain을 설명해주세요",
"format_instructions": parser.get_format_instructions()
})
print(result)
print(result.name)
print(result.description)
print(result.use_cases)
print(result.model_dump())
4) with_structured_output()
(1) 개념
- with_structured_output()은 LLM에 Pydantic 모델을 직접 연결하여 구조화된 출력을 받는 방식이다.
- PydanticOutputParser처럼 별도의 Parser 객체를 만들지 않아도 된다.
- format_instructions를 프롬프트에 직접 넣지 않아도 된다.
(2) 특징
- 코드가 간단하다.
- 구조화된 응답을 실무에서 빠르게 적용하기 좋다.
- 모델이 지원하는 구조화 출력 방식에 따라 동작한다.
(3) 코드 예시
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Field
load_dotenv()
class Answer(BaseModel):
name: str = Field(description="기술 이름")
description: str = Field(description="기술 설명")
use_cases: str = Field(description="사용 사례")
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
structured_llm = llm.with_structured_output(Answer)
prompt = ChatPromptTemplate.from_template(
"""
당신은 AI 전문가입니다.
다음 기술을 설명해주세요.
질문:
{question}
"""
)
chain = prompt | structured_llm
result = chain.invoke({
"question": "LangChain을 설명해주세요"
})
print(result)
print(result.name)
print(result.description)
print(result.use_cases)
print(result.model_dump())
5) PydanticOutputParser와 with_structured_output() 비교
| 구분 | PydanticOutputParser | with_structured_output() |
| 사용 방식 | Parser 객체를 직접 생성해서 사용 | LLM에 Pydantic 모델을 직접 연결 |
| 구조 지정 위치 | PydanticOutputParser(pydantic_object=Answer) | llm.with_structured_output(Answer) |
| 프롬프트 작성 | format_instructions를 프롬프트에 포함해야 함 | 별도의 format_instructions 작성이 필요 없음 |
| Chain 구성 | prompt | llm |
| 반환 결과 | Pydantic 모델 객체 | Pydantic 모델 객체 |
| 필드 접근 | result.name, result.description처럼 접근 | result.name, result.description처럼 접근 |
| 장점 | Output Parser의 동작 흐름을 이해하기 좋음 | 코드가 간단하고 실무에서 사용하기 편함 |
| 주의점 | LLM이 형식을 지키지 않으면 파싱 오류가 발생할 수 있음 | 모델이 지원하는 구조화 출력 방식에 따라 동작함 |
| 적합한 경우 | Output Parser 개념을 학습하거나 출력 형식을 명시적으로 제어할 때 | 구조화된 응답을 간단하게 받고 싶을 때 |
9. 실행 방식
1) invoke
- 단일 입력에 대해 한 번 실행하고 결과를 반환한다.
- 가장 기본적인 실행 방식이다.
result = chain.invoke({
"question": "LangChain이란?"
})
2) stream
- 모델의 응답을 한 번에 받지 않고 토큰 단위 또는 chunk 단위로 순차적으로 받는다.
- 챗봇에서 답변이 실시간으로 출력되는 것처럼 보이게 할 때 사용한다.
for chunk in chain.stream({
"question": "LangChain을 설명해주세요."
}):
print(chunk.content, end="")
3) batch
- 여러 입력을 한 번에 처리한다.
- 같은 체인을 여러 질문에 반복 적용할 때 사용한다.
results = chain.batch([
{"question": "LangChain이란?"},
{"question": "LangGraph란?"},
{"question": "RAG란?"}
])
10. 응답 품질 평가
1) 평가 기준
(1) 정확성
- 사실 관계와 기술적 내용이 맞는지 평가한다.
- 잘못된 개념 설명이나 허위 정보가 없는지 확인한다.
(2) 완전성
- 사용자가 요청한 내용이 모두 포함되었는지 평가한다.
- 필수 필드, 조건, 형식이 누락되지 않았는지 확인한다.
(3) 일관성
- 반복 실행 시 출력 형식이 일정한지 평가한다.
- JSON, 표, 객체 구조 등이 매번 안정적으로 유지되는지 확인한다.
2) 일반적인 문제 패턴
(1) 필드 누락
- 요청한 필드가 응답에 포함되지 않는 문제이다.
(2) 타입 불일치
- 문자열이어야 하는 값이 숫자로 나오거나, 리스트여야 하는 값이 문자열로 나오는 문제이다.
(3) 중첩 구조 파싱 오류
- JSON이나 객체 안에 또 다른 객체가 들어가는 구조에서 형식이 깨지는 문제이다.
(4) 환각
- 실제로 존재하지 않는 정보를 그럴듯하게 생성하는 문제이다.
'AI > LangChain' 카테고리의 다른 글
| [LangChain] LangGraph로 Agent 워크플로우 구현하기 — State, Node, Edge, Router (0) | 2026.06.23 |
|---|---|
| [LangChain] Tool Calling과 Agent 구현하기 — @tool, ReAct, Memory, LangGraph (0) | 2026.06.23 |
| [LangChain] RAG 검색 품질 높이기 — Hybrid Search, Reranking (0) | 2026.06.22 |
| [LangChain] LangChain으로 RAG 구현하기 — PyPDFLoader, Chroma, Retriever (0) | 2026.06.22 |
| [LangChain] 생성형 AI와 LLM Agent — 등장 배경부터 Agent Engineering까지 (0) | 2026.06.22 |
블로그의 정보
우와한개발자 님의 블로그
우와한개발자