Skip to content

체인 설계와 실전 패턴

실전에서 체인을 설계하는 방법, RunnablePassthrough, Safety Chain, 비용 최적화를 배웁니다

학습 목표

  1. 나라→음식→레시피 체인을 단계별로 구성한다
  2. RunnablePassthrough로 입력을 전달하는 방법을 익힌다
  3. Safety Chain 분기 패턴을 이해한다
  4. 토큰 비용 구조와 최적화 전략을 배운다

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 | parser

3.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, 존댓말)이 답변 품질에 도움