테마
체인 설계와 실전 패턴
실전에서 체인을 설계하는 방법, RunnablePassthrough, Safety Chain, 비용 최적화를 배웁니다
학습 목표
- 나라→음식→레시피 체인을 단계별로 구성한다
- RunnablePassthrough로 입력을 전달하는 방법을 익힌다
- Safety Chain 분기 패턴을 이해한다
- 토큰 비용 구조와 최적화 전략을 배운다
1. 실전 예제: 나라 → 음식 → 레시피
간단한 질문이라면 하나의 프롬프트로 끝낼 수 있습니다. "한국 대표 음식의 레시피 알려줘"라고 하면 되죠. 하지만 체인을 단계별로 나누는 것이 훨씬 좋은 습관입니다.
1.1 Food Chain 구현
python
from langchain_ollama import ChatOllama
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
llm = ChatOllama(model="llama3.2:1b")
parser = StrOutputParser()
# Food Chain: 나라 → 대표 음식
food_prompt = PromptTemplate(
template="What is the most famous food in {country}? Please return the food name only.",
input_variables=["country"]
)
food_chain = food_prompt | llm | parser
result = food_chain.invoke({"country": "South Korea"})
print(result) # "Bibimbap"1.2 Recipe Chain 구현
python
from langchain_core.prompts import ChatPromptTemplate
# Recipe Chain: 음식 → 레시피
recipe_prompt = ChatPromptTemplate.from_messages([
("system", "You are a chef who provides concise recipes."),
("human", "Give me a simple recipe for {food}. Recipe steps only, no introduction.")
])
recipe_chain = recipe_prompt | llm | parser
result = recipe_chain.invoke({"food": "Bibimbap"})
print(result) # 비빔밥 레시피 단계1.3 체인 연결
python
# 최종 체인: 나라 → 음식 → 레시피
final_chain = (
{"food": food_chain} # food_chain 결과를 "food" 키로
| recipe_chain
)
result = final_chain.invoke({"country": "France"})
print(result) # 크루아상 레시피 등2. RunnablePassthrough
2.1 입력을 그대로 전달하기
RunnablePassthrough는 이전 단계의 결과를 그대로 다음 단계에 전달하는 Runnable입니다.
2.2 단일 키 입력
입력 키가 하나일 때, RunnablePassthrough를 사용하면 딕셔너리를 생략할 수 있습니다:
python
from langchain_core.runnables import RunnablePassthrough
country_prompt = PromptTemplate(
template="Which country is most famous for {information}?",
input_variables=["information"]
)
# RunnablePassthrough로 입력을 information에 자동 매핑
chain = (
{"information": RunnablePassthrough()}
| country_prompt
| llm
| parser
)
# 딕셔너리 대신 문자열로 호출 가능
result = chain.invoke("wine")
print(result) # "France"2.3 다중 키 입력
입력 키가 여러 개일 때는 RunnablePassthrough를 쓸 수 없고, 명시적으로 키를 지정해야 합니다:
python
country_prompt = PromptTemplate(
template="Which country in {continent} is most famous for {information}?",
input_variables=["information", "continent"]
)
# 다중 키는 명시적으로 매핑
chain = (
{
"information": lambda x: x["information"],
"continent": lambda x: x["continent"]
}
| country_prompt
| llm
| parser
)
result = chain.invoke({"information": "wine", "continent": "Europe"})
print(result) # "France"3. Safety Chain 패턴
3.1 왜 Safety를 별도 체인으로 분리하는가?
LLM은 "하지 말라"는 지시를 잘 따르지 못합니다. 평생 "시키는 걸 하라"고 훈련받았는데, "하지 마"라고 하니까 잘 안 되는 것입니다.
3.2 구현 아이디어
Pydantic 모델로 yes/no 이진 판정을 만들고, 결과에 따라 분기합니다:
python
from pydantic import BaseModel, Field
class SafetyCheck(BaseModel):
is_safe: bool = Field(description="질문이 안전하면 true")
# Safety Chain (mini 모델로 비용 절감)
safety_llm = ChatOpenAI(model="gpt-4o-mini")
safety_chain = safety_llm.with_structured_output(SafetyCheck)
# Main Chain (대형 모델로 고품질 답변)
main_llm = ChatOpenAI(model="gpt-4o")
main_chain = main_prompt | main_llm | parser3.3 Safety Chain의 장점
| 항목 | 프롬프트에 Safety 규칙 포함 | Safety Chain 분리 |
|---|---|---|
| 동작 | "이런 질문에 답하지 마" 규칙 추가 | 별도 체인으로 판별 |
| 정확도 | 낮음 (LLM이 금지를 잘 안 따름) | 높음 (이진 판정에 집중) |
| 비용 | 모든 요청에 대형 모델 사용 | 검증은 mini, 생성은 대형 |
| 유지보수 | 프롬프트가 복잡해짐 | 체인이 깔끔하게 분리됨 |
4. 토큰 비용 최적화
4.1 토큰 비용 구조
4.2 실전 비용 절감 팁
1. 프롬프트로 출력 제한
python
# 나쁜 예 - 장황한 응답
prompt = "Tell me about France"
# → 수도, 인구, 문화, 역사... 장문의 응답 (토큰 소모)
# 좋은 예 - 핵심만 요청
prompt = "What is the capital of France? Return the city name only."
# → "Paris" (최소 토큰)2. 줄바꿈 최소화
python
# 줄바꿈도 토큰 1개
template = """
What is the most famous food in {country}?
Please return the food name only.
"""
# 비용 절감 버전 (줄바꿈 없이)
template = "What is the most famous food in {country}? Please return the food name only."3. 정중한 표현 사용
논문에 따르면 please를 포함하면 답변 품질이 향상됩니다. 한국어 프롬프트에서는 존댓말을 사용하는 것이 유사한 효과를 줍니다.
5. 체인 분리가 중요한 이유 - 정리
체인을 잘게 나누면:
- 디버깅이 쉬움 (어느 단계에서 문제가 생겼는지 바로 확인)
- Safety Chain을 중간에 삽입 가능
- 중간 결과를 캐싱하거나 다른 체인에서 재사용 가능
- 모델 최적화 - 간단한 판별은 mini, 복잡한 생성은 대형 모델
핵심 정리
- 체인을 단계별로 분리하는 것이 디버깅, 안전성, 비용 면에서 유리
- RunnablePassthrough: 단일 키 입력을 자동 매핑. 다중 키는 명시적 지정 필요
- Safety Chain: "하지 마" 프롬프트 대신 별도 체인으로 yes/no 판별 → 정확도 향상
- 비용 절감: 출력 토큰이 4배 비쌈, 프롬프트로 최소 응답 유도, 줄바꿈 최소화
- 정중한 표현(please, 존댓말)이 답변 품질에 도움