[인프런] 입문자를 위한 LangChain 기초
강의 정보 :
github 저장소 : https://github.com/tsdata/langchain-study
wikidocs : https://wikidocs.net/book/14473
1. 학습 준비
ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate, from_template, StrOutputParser를 실무 수준에서 활용 가능
---
## 전체 구성
```
I. 기초 개념
├─ 1. 채팅 모델의 메시지 시스템
├─ 2. 메시지의 역할 (role)
└─ 3. 템플릿 (Template)의 개념
II. 프롬프트 템플릿 (Prompt Template)
├─ 1. PromptTemplate (일반 텍스트)
├─ 2. ChatPromptTemplate (채팅 모델용)
├─ 3. MessagePromptTemplate 계층구조
└─ 4. 메시지 템플릿들
III. 메시지 템플릿 상세
├─ 1. SystemMessagePromptTemplate
├─ 2. HumanMessagePromptTemplate
├─ 3. AIMessagePromptTemplate
└─ 4. 동적 메시지 삽입 (MessagesPlaceholder)
IV. from_template 메서드
├─ 1. 작동 원리
├─ 2. 사용 패턴
├─ 3. 변수 자동 감지
└─ 4. 다른 생성 방식과의 비교
V. 출력 파싱 (Output Parsing)
├─ 1. StrOutputParser
├─ 2. 다른 파서들
├─ 3. LCEL 체인에서의 사용
└─ 4. 에러 처리
VI. 실전 프로젝트
├─ 1. 기초 예제
├─ 2. 중급 예제
├─ 3. 고급 예제
└─ 4. 모범 사례
VII. 문제 해결
├─ 1. 일반적인 실수
├─ 2. 디버깅 기법
├─ 3. 성능 최적화
└─ 4. 체크리스트
```
---
# I. 기초 개념
## 1. 채팅 모델의 메시지 시스템
### 1.1 일반 LLM vs 채팅 모델
LangChain이 복잡한 프롬프트 시스템을 제공하는 이유를 이해하려면, 두 가지 LLM 타입의 차이를 먼저 알아야 합니다.
**일반 LLM (Text Completion)**
```
입력: "당신은 번역가입니다. 이 문장을 한국어로 번역하세요: I love programming."
출력: "나는 프로그래밍을 사랑합니다."
```
- 단순 텍스트 → 텍스트 변환
- 역할 정의가 모호함
- 입력 형식이 자유로움
**채팅 모델 (Chat Completion)**
메시지 리스트:
[
SystemMessage("당신은 전문 번역가입니다."),
HumanMessage("이 문장을 한국어로 번역하세요: I love programming.")
]
↓
ChatOpenAI
↓
AIMessage("나는 프로그래밍을 사랑합니다.")
- 메시지 객체들의 리스트 입력
- 각 메시지는 **역할(role)**을 가짐
- 대화형 상호작용에 최적화
- 더 정확한 응답 생성
### 1.2 왜 구조화된 메시지가 중요한가?
| 특성 | 일반 텍스트 | 구조화된 메시지 |
|------|-----------|--------------|
| 역할 명확성 | 낮음 | 높음 |
| 응답 일관성 | 낮음 | 높음 |
| 대화 기록 관리 | 어려움 | 쉬움 |
| 멀티턴 대화 | 복잡함 | 자연스러움 |
| 메모리 통합 | 수동 | 자동 |
**핵심**: 채팅 모델에 메시지를 보낼 때, LLM이 "누가 말하는지", "어떤 맥락인지"를 명확히 이해하도록 구조화해야 합니다.
---
## 2. 메시지의 역할 (Role)
### 2.1 4가지 기본 역할
**SystemMessage** - 모델의 지시사항
from langchain.schema import SystemMessage
msg = SystemMessage(content="당신은 Python 전문가입니다. 기술적이고 정확하게 답변하세요.")
- 모델의 **성격, 규칙, 지시사항** 정의
- 대화 맨 처음 **한 번만** 포함
- 시스템 메시지는 모든 응답에 영향을 줌
**HumanMessage** - 사용자의 질문/요청
from langchain.schema import HumanMessage
msg = HumanMessage(content="Python에서 async/await은 어떻게 작동하나요?")
- 사용자(인간)이 말하는 부분
- 매 턴마다 새로운 입력으로 변함
- 실제 사용자 요청 포함
**AIMessage** - 모델의 이전 응답
from langchain.schema import AIMessage
msg = AIMessage(content="async/await는 비동기 프로그래밍을 위한 문법입니다...")
- 모델이 생성한 이전 응답
- 대화 기록으로 사용
- 멀티턴 대화에서 문맥 제공
**ToolMessage** - 도구 실행 결과
from langchain.schema import ToolMessage
msg = ToolMessage(
content="검색 결과: Python 3.11은 2024년 5월에 출시됨",
tool_call_id="search_001"
)
- 도구/함수 실행 결과
- 에이전트 시스템에서 사용
- 모델이 사용한 도구의 응답
### 2.2 메시지 흐름 예시
**고객 서비스 챗봇**
시간 흐름 →
[시스템 설정]
SystemMessage: "당신은 친절한 고객 서비스 담당자입니다. 한국어로 응답하세요."
[첫 번째 턴]
HumanMessage: "배송된 제품이 손상되었어요."
↓
[AI 응답 생성]
AIMessage: "죄송합니다. 새 제품으로 교환해드리겠습니다."
[두 번째 턴]
HumanMessage: "언제 도착할까요?"
ChatHistory: [
HumanMessage("배송된 제품이 손상되었어요."),
AIMessage("죄송합니다. 새 제품으로 교환해드리겠습니다."),
HumanMessage("언제 도착할까요?")
]
↓
[AI가 문맥을 이해하고 응답]
AIMessage: "3-5일 이내에 배송됩니다."
---
## 3. 템플릿 (Template) 개념
### 3.1 왜 템플릿이 필요한가?
**문제 상황:**
```python
# ❌ 반복되는 코드
msg1 = HumanMessage(content="Python에서 비동기 프로그래밍은 어떻게 하나요?")
msg2 = HumanMessage(content="JavaScript에서 비동기 프로그래밍은 어떻게 하나요?")
msg3 = HumanMessage(content="Go에서 비동기 프로그래밍은 어떻게 하나요?")
# ... 계속 반복
# 패턴: "{language}에서 비동기 프로그래밍은 어떻게 하나요?"
```
**해결책: 템플릿 사용**
```python
# 한 번의 정의로 재사용
template = "사용자 질문: {language}에서 비동기 프로그래밍은 어떻게 하나요?"
# 변수를 바꿔가며 메시지 생성
msg1 = create_message(template, language="Python")
msg2 = create_message(template, language="JavaScript")
msg3 = create_message(template, language="Go")
```
### 3.2 LangChain 템플릿의 층계
```
┌─────────────────────────────────────────────┐
│ 고수준 인터페이스 │
│ ChatPromptTemplate.from_messages() │
└────────────────────┬────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 메시지 템플릿들 (MessagePromptTemplate) │
│ ├─ SystemMessagePromptTemplate │
│ ├─ HumanMessagePromptTemplate │
│ ├─ AIMessagePromptTemplate │
│ └─ ToolMessagePromptTemplate │
└────────────────────┬────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 기본 템플릿 │
│ PromptTemplate (변수 감지, 포맷팅) │
└────────────────────┬────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 문자열 패턴 매칭 │
│ Python str.format() 문법 │
│ "문자 {variable} 문자" → {variable} 감지 │
└─────────────────────────────────────────────┘
```
---
# II. 프롬프트 템플릿 (Prompt Template)
## 1. PromptTemplate (일반 텍스트)
### 1.1 정의
- **일반 LLM 또는 텍스트 생성 모델**을 위한 템플릿
- 변수가 포함된 문자열
- 포맷팅 후 일반 문자열 반환
### 1.2 생성 방식
**방식 1: from_template() 사용 (권장)**
```python
from langchain_core.prompts import PromptTemplate
# 템플릿 문자열에서 변수 자동 감지
prompt = PromptTemplate.from_template(
"'{topic}'에 대해 {sentences}문장으로 설명해주세요."
)
print(prompt.input_variables) # ['topic', 'sentences']
# 포맷팅
formatted = prompt.format(topic="블랙홀", sentences="3")
print(formatted)
# 출력: '블랙홀'에 대해 3문장으로 설명해주세요.
```
**방식 2: 직접 생성**
```python
from langchain_core.prompts import PromptTemplate
# 명시적으로 변수 지정
prompt = PromptTemplate(
input_variables=["topic", "sentences"],
template="'{topic}'에 대해 {sentences}문장으로 설명해주세요."
)
```
### 1.3 주요 메서드
```python
prompt = PromptTemplate.from_template(
"'{topic}'을 {language}로 설명해주세요."
)
# 1. 변수 확인
print(prompt.input_variables) # ['topic', 'language']
# 2. 포맷팅 (문자열 반환)
text = prompt.format(topic="API", language="초보자용으로")
print(type(text)) # <class 'str'>
# 3. 부분 포맷팅 (일부 변수만)
partial = prompt.partial(language="초보자용으로")
text2 = partial.format(topic="데이터베이스")
```
---
## 2. ChatPromptTemplate (채팅 모델용)
### 2.1 정의
- **채팅 모델(ChatGPT, Claude 등)**을 위한 메시지 기반 템플릿
- 여러 메시지 역할을 조합
- 메시지 객체 리스트 반환
### 2.2 작동 원리
```python
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 {role}입니다."),
("human", "{question}")
])
# 내부 구조
# prompt.messages = [
# SystemMessagePromptTemplate(...),
# HumanMessagePromptTemplate(...)
# ]
# 포맷팅
messages = prompt.format_messages(
role="미슐랭 저지",
question="서울의 최고 식당은?"
)
# 결과: [SystemMessage, HumanMessage] 객체 리스트
```
### 2.3 생성 방식 비교
| 방식 | 사용 시기 | 코드 |
|------|---------|------|
| **from_template()** | 간단한 프롬프트 | `ChatPromptTemplate.from_template("안녕 {name}")` |
| **from_messages()** | 여러 메시지 역할 필요 | `ChatPromptTemplate.from_messages([...])` |
| **from_role_strings()** | 커스텀 역할 | `ChatPromptTemplate.from_role_strings(...)` |
---
## 3. MessagePromptTemplate 계층구조
### 3.1 클래스 관계
```
┌─────────────────────────────────┐
│ Runnable │
│ (LCEL 인터페이스) │
└─────────────┬───────────────────┘
↑
┌─────────────────────────────────┐
│ BasePromptTemplate │
│ (기본 템플릿 기능) │
└─────────────┬───────────────────┘
↑
┌─────────────────────────────────┐
│ StringPromptTemplate │
│ (문자열 기반 템플릿) │
└─────────────┬───────────────────┘
↑
┌─────────────────────────────────┐
│ PromptTemplate │
│ (일반 텍스트 템플릿) │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ BaseMessagePromptTemplate │
│ (메시지 템플릿 기본) │
└─────────────┬───────────────────┘
↑
┌─────────┴─────────┬─────────────┐
↓ ↓ ↓
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ System... │ │ Human... │ │ AI... │
│ Template │ │ Template │ │ Template │
└─────────────┘ └─────────────┘ └─────────────┘
```
---
## 4. 메시지 템플릿들
### 4.1 종류별 정리
| 템플릿 | 용도 | 반환 | 사용 빈도 |
|--------|------|------|---------|
| **SystemMessagePromptTemplate** | 시스템 지시사항 | SystemMessage | 높음 |
| **HumanMessagePromptTemplate** | 사용자 입력 | HumanMessage | 높음 |
| **AIMessagePromptTemplate** | 이전 AI 응답 | AIMessage | 중간 |
| **ChatMessagePromptTemplate** | 커스텀 역할 | ChatMessage | 낮음 |
| **ToolMessagePromptTemplate** | 도구 결과 | ToolMessage | 낮음 |
---
# III. 메시지 템플릿 상세
## 1. SystemMessagePromptTemplate
### 1.1 정의 및 목적
- 모델의 **성격, 역할, 행동 규칙** 정의
- 대화 맨 처음에 **한 번만** 설정
- 모든 응답에 영향을 미치는 지시사항
### 1.2 생성 방식
**from_template() 사용**
```python
from langchain_core.prompts import SystemMessagePromptTemplate
template = SystemMessagePromptTemplate.from_template(
"당신은 {language} 전문가입니다. {tone} 톤으로 답변하세요."
)
message = template.format(
language="Python",
tone="친절하고 상세한"
)
print(message)
# SystemMessage(content='당신은 Python 전문가입니다. 친절하고 상세한 톤으로 답변하세요.')
```
### 1.3 실전 예제
**예제 1: 고객 서비스**
```python
from langchain_core.prompts import SystemMessagePromptTemplate
system = SystemMessagePromptTemplate.from_template("""
당신은 {company}의 고객 서비스 담당자입니다.
성격:
- 친절하고 전문적
- {language}로 응답
- 최대 {max_sentences}문장으로 답변
규칙:
1. 항상 감사로 시작
2. 구체적 솔루션 제시
3. 추가 도움 여부 확인
""")
message = system.format(
company="TechStore",
language="한국어",
max_sentences="3"
)
```
**예제 2: 코드 리뷰어**
```python
system = SystemMessagePromptTemplate.from_template("""
당신은 {years}년 경력의 {language} 엔지니어입니다.
평가 기준:
- 코드 품질: {quality_focus}
- 성능: {performance_focus}
- 보안: {security_focus}
응답 형식: 장점 → 개선점 → 제안
""")
message = system.format(
years="10",
language="Python",
quality_focus="가독성",
performance_focus="최적화",
security_focus="SQL 인젝션 방지"
)
```
### 1.4 변수 관리
```python
system = SystemMessagePromptTemplate.from_template(
"당신은 {role}입니다. {context}를 고려하여 답변하세요."
)
# 변수 확인
print(system.input_variables) # ['role', 'context']
# 부분 포맷팅
partial = system.partial(role="미슐랭 저지")
message = partial.format(context="한식")
```
---
## 2. HumanMessagePromptTemplate
### 2.1 정의 및 목적
- 사용자(인간)의 **질문, 요청** 정의
- 매 턴마다 **다른 입력**을 받음
- 동적 사용자 입력 처리
### 2.2 생성 방식
**from_template() 사용**
```python
from langchain_core.prompts import HumanMessagePromptTemplate
template = HumanMessagePromptTemplate.from_template(
"다음 텍스트를 {target_language}로 번역해주세요:\n{text}"
)
message = template.format(
target_language="일본어",
text="안녕하세요, 만나서 반갑습니다!"
)
print(message)
# HumanMessage(content='다음 텍스트를 일본어로 번역해주세요:\n안녕하세요, 만나서 반갑습니다!')
```
### 2.3 실전 예제
**예제 1: 코드 분석**
```python
human = HumanMessagePromptTemplate.from_template("""
프로그래밍 언어: {language}
코드:
```
{code}
```
질문: {question}
요청: {requirements}
""")
message = human.format(
language="Python",
code="def add(a, b):\n return a + b",
question="이 함수의 시간 복잡도는?",
requirements="Big O 표기법으로 설명"
)
```
**예제 2: Q&A**
```python
human = HumanMessagePromptTemplate.from_template("""
주제: {topic}
난이도: {level}
질문: {question}
""")
message = human.format(
topic="머신러닝",
level="초급",
question="신경망이란?"
)
```
### 2.4 SystemMessagePromptTemplate과의 관계
```python
# 둘 다 필요!
system = SystemMessagePromptTemplate.from_template(
"당신은 전문가입니다."
)
human = HumanMessagePromptTemplate.from_template(
"'{topic}'에 대해 설명해줘"
)
# 일부만 사용하면 불완전한 대화가 됨
```
---
## 3. AIMessagePromptTemplate
### 3.1 정의
- 모델의 **이전 응답** 포함
- 멀티턴 대화에서 **문맥 제공**
- 대화 기록 재현
### 3.2 사용 예시
```python
from langchain_core.prompts import AIMessagePromptTemplate
ai = AIMessagePromptTemplate.from_template(
"나는 {previous_response}라고 답했습니다."
)
message = ai.format(
previous_response="async/await은 비동기 프로그래밍을 위한 문법입니다"
)
```
### 3.3 대화 흐름에서의 역할
```
[대화 구성]
SystemMessage: "당신은 전문가입니다."
HumanMessage: "첫 질문"
AIMessage: "첫 답변"
↓ (시간 경과)
HumanMessage: "후속 질문"
↓
AIMessagePromptTemplate으로 이전 답변 재현
↓
모델이 문맥을 이해한 후 답변
```
---
## 4. 동적 메시지 삽입 (MessagesPlaceholder)
### 4.1 개념
- **변수 리스트를 메시지로 삽입**
- 대화 기록을 동적으로 추가
- 멀티턴 대화에 필수
### 4.2 문제 상황
```python
# 문제: 대화 기록이 계속 늘어남
messages = [
SystemMessage("당신은 어시스턴트입니다."),
HumanMessage("질문 1"),
AIMessage("답변 1"),
HumanMessage("질문 2"),
AIMessage("답변 2"),
# ... 계속 늘어남
]
```
**해결책:**
```python
# MessagesPlaceholder 사용
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 어시스턴트입니다."),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{current_question}")
])
# 사용
chat_history = [
HumanMessage("질문 1"),
AIMessage("답변 1"),
# ... 동적으로 추가됨
]
messages = prompt.format_messages(
chat_history=chat_history,
current_question="새로운 질문"
)
```
### 4.3 실전 예제
```python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.schema import HumanMessage, AIMessage
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 한국 역사 전문가입니다."),
MessagesPlaceholder(variable_name="conversation"),
("human", "{user_question}")
])
# 대화 기록 준비
conversation = [
HumanMessage(content="삼국시대는 언제입니까?"),
AIMessage(content="삼국시대는 기원후 57년부터 668년까지입니다."),
HumanMessage(content="신라가 이룬 성과는?"),
AIMessage(content="신라는 당과 연합하여 삼국통일을 이루었습니다.")
]
# 포맷팅
messages = prompt.format_messages(
conversation=conversation,
user_question="그 당시 문화 발전은?"
)
# 결과: System → 대화 기록 → 현재 질문 순서
```
---
# IV. from_template 메서드
## 1. 작동 원리
### 1.1 개념
- Python `str.format()` 문법 활용
- `{}` 안의 변수명 자동 감지
- 가장 간단한 템플릿 생성 방식
### 1.2 변수 감지 메커니즘
```python
import re
template = "'{topic}'에 대해 {language}로 설명해줘"
# 내부 동작 (단순화)
variables = re.findall(r'\{(\w+)\}', template)
print(variables) # ['topic', 'language']
# 결과
# input_variables = ['topic', 'language']
# template = "'{topic}'에 대해 {language}로 설명해줘"
```
### 1.3 복수 변수 처리
```python
from langchain_core.prompts import PromptTemplate
# 변수 여러 개
template = PromptTemplate.from_template(
"{greeting}, {name}! 오늘은 {day}인데 {mood}신가요?"
)
print(template.input_variables) # ['greeting', 'name', 'day', 'mood']
# 중복 변수
template2 = PromptTemplate.from_template(
"{name}님, {name}은 좋은 개발자네요."
)
print(template2.input_variables) # ['name'] - 한 번만 포함
```
---
## 2. 사용 패턴
### 2.1 PromptTemplate.from_template()
```python
from langchain_core.prompts import PromptTemplate
# 패턴: 문자열 템플릿 직접 전달
prompt = PromptTemplate.from_template(
"다음 {content_type}을 {language}로 정리해줘"
)
text = prompt.format(content_type="블로그 글", language="한국어")
```
### 2.2 SystemMessagePromptTemplate.from_template()
```python
from langchain_core.prompts import SystemMessagePromptTemplate
system = SystemMessagePromptTemplate.from_template(
"당신은 {role}입니다. {tone} 톤으로 답변하세요."
)
message = system.format(
role="마케팅 전문가",
tone="친근하고 활발한"
)
```
### 2.3 HumanMessagePromptTemplate.from_template()
```python
from langchain_core.prompts import HumanMessagePromptTemplate
human = HumanMessagePromptTemplate.from_template(
"'{title}'의 주요 내용을 {format}으로 요약해줘"
)
message = human.format(
title="Python 동시성 프로그래밍",
format="3줄 요약"
)
```
### 2.4 ChatPromptTemplate.from_template()
```python
from langchain_core.prompts import ChatPromptTemplate
# 단순 인터페이스 (내부적으로 human message로 변환)
prompt = ChatPromptTemplate.from_template(
"다음 {subject}에 대해 설명해줘"
)
# 내부 구조:
# ChatPromptTemplate.from_messages([
# ("human", "다음 {subject}에 대해 설명해줘")
# ])
messages = prompt.format_messages(subject="FastAPI")
```
---
## 3. from_template() vs 다른 생성 방식
### 3.1 비교표
| 메서드 | 입력 | 용도 | 코드 길이 |
|--------|------|------|---------|
| **from_template()** | 문자열 | 빠른 프로토타입 | 최소 |
| **from_messages()** | 튜플/객체 리스트 | 복잡한 구조 | 중간 |
| **__init__()** | 명시적 파라미터 | 세밀한 제어 | 최대 |
### 3.2 코드 비교
```python
from langchain_core.prompts import PromptTemplate
# from_template() - 최간단
prompt1 = PromptTemplate.from_template(
"'{topic}'에 대해 설명해줘"
)
# __init__() 직접 사용
prompt2 = PromptTemplate(
input_variables=["topic"],
template="'{topic}'에 대해 설명해줘"
)
# from_template() + partial
base = PromptTemplate.from_template(
"'{topic}'에 대해 {language}로 설명해줘"
)
partial = base.partial(language="초보자용")
```
---
## 4. 주의사항
### 4.1 변수명 규칙
```python
# 올바른 변수명
"{topic}" # 문자
"{max_count}" # 언더스코어
"{class_type}" # 언더스코어
# 잘못된 변수명
"{topic-name}" # 하이픈 불가
"{123}" # 숫자로 시작 불가
"{topic name}" # 공백 불가
```
### 4.2 이스케이프 처리
```python
# 템플릿에서 {} 사용하려면
template = "배열 {array}를 JSON 형식으로: {{\"key\": \"{array}\"}}"
# 이중 중괄호 {{ }}로 이스케이프
```
---
# V. 출력 파싱 (Output Parsing)
## 1. StrOutputParser
### 1.1 정의
- LLM의 응답을 **순수 문자열로 변환**
- `AIMessage` 객체에서 `content` 추출
- 가장 간단한 파서
### 1.2 필요성
```python
from langchain_openai import ChatOpenAI
from langchain.schema import AIMessage
model = ChatOpenAI(model="gpt-4o-mini")
response = model.invoke("안녕하세요")
print(response)
# AIMessage(
# content='안녕하세요, 무엇을 도와드릴까요?',
# response_metadata={...},
# ...
# )
# 문제: 순수 텍스트만 필요한데 객체가 반환됨
print(type(response)) # <class 'langchain_core.messages.ai.AIMessage'>
```
**해결책:**
```python
from langchain_core.output_parsers import StrOutputParser
parser = StrOutputParser()
text = parser.invoke(response)
print(text) # "안녕하세요, 무엇을 도와드릴까요?"
print(type(text)) # <class 'str'>
```
### 1.3 기본 사용법
```python
from langchain_core.output_parsers import StrOutputParser
# 파서 생성
parser = StrOutputParser()
# AIMessage 파싱
from langchain.schema import AIMessage
response = AIMessage(content="블랙홀은...")
text = parser.invoke(response)
# 또는
text = parser.parse(response)
```
---
## 2. LCEL 체인에서의 StrOutputParser
### 2.1 개념: LCEL (LangChain Expression Language)
```
입력 → 프롬프트 → 모델 → 파서 → 출력
[prompt] [model] [parser]
```
파이프 연산자 `|`로 연결:
```python
chain = prompt | model | parser
```
### 2.2 완전한 예제
```python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
# 프롬프트 정의
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 {expertise} 전문가입니다."),
("human", "{question}")
])
# 모델 선택
model = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)
# 파서 선택
parser = StrOutputParser()
# 체인 구성
chain = prompt | model | parser
# 실행
result = chain.invoke({
"expertise": "천체물리학",
"question": "블랙홀이란?"
})
print(result) # 순수 문자열
print(type(result)) # <class 'str'>
```
### 2.3 파이프 흐름 상세
```python
# 내부 동작
result = chain.invoke({"expertise": "천체물리학", "question": "블랙홀이란?"})
# 1단계: prompt.invoke()
# 입력: {"expertise": "천체물리학", "question": "블랙홀이란?"}
# 출력: ChatPromptValue(messages=[...])
# 2단계: model.invoke()
# 입력: [SystemMessage(...), HumanMessage(...)]
# 출력: AIMessage(content="블랙홀은...")
# 3단계: parser.invoke()
# 입력: AIMessage(content="블랙홀은...")
# 출력: "블랙홀은..."
```
---
## 3. 다른 파서들
### 3.1 파서 종류별 비교
| 파서 | 반환 타입 | 사용 사례 | 복잡도 |
|------|---------|---------|--------|
| **StrOutputParser** | `str` | 텍스트 응답 | 낮음 |
| **JsonOutputParser** | `dict` | JSON 데이터 | 중간 |
| **PydanticOutputParser** | Python 객체 | 구조화 데이터 (타입 검증) | 높음 |
| **CommaSeparatedListOutputParser** | `list` | 목록/태그 | 낮음 |
| **XMLOutputParser** | `dict` | XML 데이터 | 중간 |
### 3.2 JsonOutputParser 예제
```python
from langchain_core.output_parsers import JsonOutputParser
from pydantic import BaseModel, Field
# 스키마 정의
class Restaurant(BaseModel):
name: str = Field(description="식당 이름")
rating: int = Field(description="별점 1-5")
# 파서 생성
parser = JsonOutputParser(pydantic_object=Restaurant)
# 체인 구성
prompt = ChatPromptTemplate.from_template(
"서울 미슐랭 식당을 JSON 형식으로 반환해줘:\n{format_instructions}"
)
chain = prompt | model | parser
result = chain.invoke({
"format_instructions": parser.get_format_instructions()
})
# 결과: {"name": "가온", "rating": 3}
print(type(result)) # <class 'dict'>
```
### 3.3 PydanticOutputParser 예제
```python
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
# 스키마 정의 (타입 검증 포함)
class Task(BaseModel):
title: str = Field(description="작업 제목")
priority: int = Field(description="1-5 사이의 우선순위")
due_date: str = Field(description="YYYY-MM-DD 형식")
# 파서 생성
parser = PydanticOutputParser(pydantic_object=Task)
# 형식 지시사항 자동 생성
format_instructions = parser.get_format_instructions()
# 체인
prompt = ChatPromptTemplate.from_template(
"다음 작업을 JSON으로 파싱해줘:\n{task_text}\n{format_instructions}"
)
chain = prompt | model | parser
result = chain.invoke({
"task_text": "12월 25일까지 문서 완성 (높은 우선순위)",
"format_instructions": format_instructions
})
# 결과: Task 객체
print(result.title) # "문서 완성"
print(result.due_date) # "2025-12-25"
print(type(result.priority)) # <class 'int'>
```
---
## 4. 에러 처리
### 4.1 OutputParserException
```python
from langchain_core.output_parsers import (
OutputParserException,
JsonOutputParser
)
parser = JsonOutputParser()
try:
result = parser.parse("{'invalid': json}")
except OutputParserException as e:
print(f"파싱 에러: {e}")
# 에러 처리 로직
```
### 4.2 재시도 로직
```python
from langchain_core.output_parsers import PydanticOutputParser
parser = PydanticOutputParser(pydantic_object=Task)
# with_retry() 사용
chain = (
prompt
| model.with_retry(
stop_after_attempt=3,
wait_random_min=1,
wait_random_max=3
)
| parser
)
```
---
# VI. 실전 프로젝트
## 1. 기초 예제: 간단한 번역기
```python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
# 프롬프트
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 전문 번역가입니다."),
("human", "{source_language}을 {target_language}로 번역:\n{text}")
])
# 모델
model = ChatOpenAI(model="gpt-4o-mini")
# 파서
parser = StrOutputParser()
# 체인
chain = prompt | model | parser
# 실행
result = chain.invoke({
"source_language": "English",
"target_language": "한국어",
"text": "Hello, how are you?"
})
print(result)
# 출력: "안녕하세요, 어떻게 지내세요?"
```
---
## 2. 중급 예제: 고객 서비스 챗봇
```python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
from langchain.schema import HumanMessage, AIMessage
# 프롬프트 (대화 기록 포함)
prompt = ChatPromptTemplate.from_messages([
("system", """당신은 {company} 고객 서비스 담당자입니다.
성격:
- 친절하고 전문적
- {language}로 응답
- 최대 {max_sentences}문장으로 답변
규칙:
1. 감사로 시작
2. 구체적 솔루션 제시
3. 추가 도움 여부 확인"""),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{current_inquiry}")
])
# 모델
model = ChatOpenAI(model="gpt-4o-mini")
# 체인
chain = prompt | model | StrOutputParser()
# 첫 턴
response1 = chain.invoke({
"company": "TechStore",
"language": "한국어",
"max_sentences": "3",
"chat_history": [],
"current_inquiry": "배송된 제품이 손상되었어요."
})
print("고객: 배송된 제품이 손상되었어요.")
print(f"챗봇: {response1}\n")
# 두 번째 턴 (대화 기록 포함)
chat_history = [
HumanMessage(content="배송된 제품이 손상되었어요."),
AIMessage(content=response1)
]
response2 = chain.invoke({
"company": "TechStore",
"language": "한국어",
"max_sentences": "3",
"chat_history": chat_history,
"current_inquiry": "환불은 가능한가요?"
})
print("고객: 환불은 가능한가요?")
print(f"챗봇: {response2}")
```
---
## 3. 고급 예제: 구조화된 데이터 추출
```python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import PydanticOutputParser
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
# 데이터 모델 정의
class UserInfo(BaseModel):
name: str = Field(description="사용자 이름")
email: str = Field(description="이메일 주소")
age: int = Field(description="나이")
interests: list[str] = Field(description="관심사 목록")
# 파서 생성
parser = PydanticOutputParser(pydantic_object=UserInfo)
# 프롬프트 (형식 지시사항 포함)
format_instructions = parser.get_format_instructions()
prompt = ChatPromptTemplate.from_template(
"""다음 텍스트에서 사용자 정보를 추출하세요:
텍스트: {text}
{format_instructions}"""
)
# 모델
model = ChatOpenAI(model="gpt-4o-mini")
# 체인
chain = prompt | model | parser
# 실행
result = chain.invoke({
"text": "내 이름은 김철수이고 이메일은 kim@example.com입니다. 29살이고 Python, 머신러닝, 영화에 관심 있어요.",
"format_instructions": format_instructions
})
print(result.name) # "김철수"
print(result.email) # "kim@example.com"
print(result.interests) # ["Python", "머신러닝", "영화"]
print(type(result)) # <class '__main__.UserInfo'>
```
---
## 4. 모범 사례
### 4.1 변수 관리
```python
# 좋은 예: 변수 메타데이터와 함께
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 {expertise_level} {role}입니다."),
("human", "요청: {user_request}\n추가 정보: {context}")
])
# 변수 확인
print(prompt.input_variables)
# ['expertise_level', 'role', 'user_request', 'context']
# 나쁜 예: 변수명이 모호함
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 {a} {b}입니다."),
("human", "{c}")
])
```
### 4.2 에러 처리
```python
from langchain_core.output_parsers import OutputParserException
try:
result = chain.invoke(...)
except OutputParserException as e:
print(f"출력 파싱 에러: {e}")
# 폴백 로직
result = fallback_result
except Exception as e:
print(f"예상 외 에러: {e}")
# 로깅 및 재시도
```
### 4.3 성능 최적화
```python
# 캐싱 활용
from langchain.caches import InMemoryCache
from langchain.globals import set_llm_cache
set_llm_cache(InMemoryCache())
# 동일한 입력에 대해 캐시 사용
result1 = chain.invoke({"topic": "Python"})
result2 = chain.invoke({"topic": "Python"}) # 캐시에서 반환
```
---
# VII. 문제 해결
## 1. 일반적인 실수
### 1.1 변수 누락
```python
# 실수
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 {role}입니다."),
("human", "{question}")
])
# 변수를 하나만 전달
messages = prompt.format_messages(role="전문가")
# KeyError: 'question'
# 해결책: 모든 변수 전달
messages = prompt.format_messages(
role="전문가",
question="안녕?"
)
```
### 1.2 파서 선택 오류
```python
# 실수: 구조화된 데이터에 StrOutputParser
response = chain.invoke(...)
# "{"name": "Kim", "age": 30}" ← 문자열
print(type(response)) # <class 'str'>
# 이후 접근 불가
# response.name # AttributeError
# 해결책: 적절한 파서 사용
from langchain_core.output_parsers import JsonOutputParser
```
### 1.3 메시지 구조 오류
```python
# 실수: from_template()으로 여러 메시지 정의
prompt = ChatPromptTemplate.from_template(
"system: 당신은 전문가입니다.\nhuman: {question}"
)
# ← 이는 단일 human 메시지로 취급됨
# 해결책: from_messages() 사용
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 전문가입니다."),
("human", "{question}")
])
```
---
## 2. 디버깅 기법
### 2.1 변수 확인
```python
# 프롬프트의 변수 확인
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 {role}입니다."),
("human", "{question}")
])
print("변수:", prompt.input_variables)
# ['role', 'question']
print("메시지 수:", len(prompt.messages))
# 2
print("메시지 타입:")
for msg in prompt.messages:
print(f" - {type(msg).__name__}")
```
### 2.2 포맷팅 결과 확인
```python
# 포맷팅 전 확인
messages = prompt.format_messages(
role="Python 전문가",
question="async/await이란?"
)
for i, msg in enumerate(messages):
print(f"\n메시지 {i}:")
print(f" 역할: {msg.type}")
print(f" 내용: {msg.content}")
```
### 2.3 체인 단계별 검증
```python
# 각 단계 검증
prompt = ChatPromptTemplate.from_template("...")
model = ChatOpenAI(model="gpt-4o-mini")
parser = StrOutputParser()
# 1단계: 프롬프트
prompt_result = prompt.invoke({"topic": "Python"})
print("프롬프트 결과:", type(prompt_result))
# 2단계: 모델
model_result = model.invoke(prompt_result)
print("모델 결과:", type(model_result))
# 3단계: 파서
parser_result = parser.invoke(model_result)
print("파서 결과:", type(parser_result))
```
---
## 3. 성능 최적화
### 3.1 토큰 사용량 모니터링
```python
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o-mini")
response = chain.invoke({...})
# 토큰 사용량 확인
if hasattr(response, 'response_metadata'):
usage = response.response_metadata.get('token_usage', {})
print(f"입력 토큰: {usage.get('prompt_tokens')}")
print(f"출력 토큰: {usage.get('completion_tokens')}")
```
### 3.2 프롬프트 단순화
```python
# 복잡한 프롬프트
system = """당신은 매우 유능한 프로그래밍 전문가입니다.
당신은 Python, JavaScript, Java, C++ 등 모든 언어에 능통합니다.
당신은 항상 명확하고 정확한 답변을 제공합니다.
... (200줄 이상)
"""
# 간결한 프롬프트
system = "당신은 {language} 전문가입니다. 명확하고 정확하게 답변하세요."
```
### 3.3 배치 처리
```python
# 여러 요청을 배치로 처리
inputs = [
{"topic": "Python"},
{"topic": "JavaScript"},
{"topic": "Go"}
]
# batch_invoke 사용
results = chain.batch(inputs)
# 또는 async 버전
import asyncio
async def batch_async():
results = await asyncio.gather(*[
chain.ainvoke(input_dict) for input_dict in inputs
])
return results
```
---
## 4. 체크리스트
### 4.1 프롬프트 설계
```
☐ 필요한 메시지 역할 파악 (system, human, ai)
☐ 동적 변수 식별 및 명명
☐ 변수명이 명확한지 확인
☐ 템플릿 문법 검증 ({variable} 형식)
☐ 메시지 순서 확인
☐ 변수 개수와 전달 파라미터 일치 확인
```
### 4.2 체인 구성
```
☐ ChatPromptTemplate 또는 PromptTemplate 선택
☐ 모델 선택 (ChatOpenAI, Anthropic 등)
☐ 출력 파서 선택 (StrOutputParser, JsonOutputParser 등)
☐ LCEL로 체인 구성 (prompt | model | parser)
☐ invoke() 파라미터 검증
☐ 출력 타입 확인
```
### 4.3 테스트 및 배포
```
☐ 샘플 입력으로 테스트
☐ 모든 변수 조합 테스트
☐ 에러 케이스 테스트
☐ 출력 형식 검증
☐ 토큰 사용량 모니터링
☐ 성능 측정
☐ 프롬프트 버전 관리
```
---
# VIII. 요약 및 빠른 참조
## 명령어 빠른 참조
```python
# 프롬프트 생성
from langchain_core.prompts import (
PromptTemplate,
ChatPromptTemplate,
SystemMessagePromptTemplate,
HumanMessagePromptTemplate,
MessagesPlaceholder
)
# 출력 파싱
from langchain_core.output_parsers import (
StrOutputParser,
JsonOutputParser,
PydanticOutputParser,
CommaSeparatedListOutputParser
)
# 모델
from langchain_openai import ChatOpenAI
# 메시지
from langchain.schema import (
SystemMessage,
HumanMessage,
AIMessage,
ToolMessage
)
# 체인 구성
chain = prompt | model | parser
# 실행
result = chain.invoke({...})
```
## 의사결정 플로우
```
프롬프트 설계 시작
↓
메시지 역할 몇 개?
├─ 1개 → ChatPromptTemplate.from_template()
└─ 2개+ → ChatPromptTemplate.from_messages()
↓
동적 대화 기록 필요?
├─ 예 → MessagesPlaceholder 추가
└─ 아니오 → 정적 메시지만 사용
↓
출력 형식?
├─ 텍스트 → StrOutputParser
├─ JSON → JsonOutputParser
└─ 구조화 데이터 → PydanticOutputParser
↓
체인 구성: prompt | model | parser
↓
invoke({변수들})로 실행
```
**학습을 마친 후:**
- LangChain 공식 문서: https://python.langchain.com/
- 샘플 프로젝트 구현 시작
- 실제 프로덕션 시스템에 통합
- 프롬프트 버전 관리 및 A/B 테스팅 실시
댓글