본문 바로가기
Data Analysis/AI

LLM langchain - OutputParser and LCEL

by Hagrid 2025. 1. 19.
반응형

LangChain에서 OutputParser와 LCEL(Yogis)는 주로 언어 모델을 처리하고 결과를 더 쉽게 다룰 수 있게 돕는 도구들입니다. 아주 쉽게 설명하자면, 이들은 우리가 원하는 형태로 출력이나 결과를 처리하고 정리하는 역할을 합니다.

OutputParser

OutputParser는 언어 모델이 생성한 출력 데이터를 우리가 원하는 형태로 바꾸는 도구입니다. 예를 들어, 모델이 생성한 텍스트가 너무 길거나 복잡하면, 우리가 필요한 정보만 뽑아서 쓸 수 있게 도와줍니다.

 

우리가 "오늘 날씨 어때?"라고 물어봤을 때, 모델이 이렇게 대답할 수 있습니다:

오늘은 맑고 기온은 22도입니다. 바람은 약하게 불고 있습니다.
from langchain.output_parsers import OutputParser

# 날씨 응답을 기온만 추출하는 Parser 예시
class WeatherParser(OutputParser):
    def parse(self, output: str):
        lines = output.split("\n")
        for line in lines:
            if "기온" in line:
                return line.split("기온은")[1].strip()

# 모델의 출력
output = "오늘은 맑고 기온은 22도입니다. 바람은 약하게 불고 있습니다."

# OutputParser를 사용하여 기온만 추출
parser = WeatherParser()
temperature = parser.parse(output)
print(temperature)  # 결과: 22도

이럴 때, 우리가 날씨와 기온만 뽑고 싶다면, OutputParser를 이용해 필요 없는 부분을 제거할 수 있습니다.

LCEL Yogis

LCEL은 LangChain에서 Logical Chain Execution Layer의 약자로, 작업을 여러 단계로 나누고 이 단계를 순차적으로 실행하는 구조를 제공합니다. 쉽게 말하면, 여러 작은 작업 단위(요기, Yogi)를 연결하여 문제를 해결하는 체계라고 생각하면 됩니다.

 이 도구는 "Yogis"라고 불리는데, 이는 단순히 단계별로 작업을 처리하는 작은 작업 단위들을 의미합니다. 여러 단계의 작업을 모아서 하나의 큰 문제를 해결하는 방식입니다.

 

  • LCEL의 역할
    • 복잡한 문제를 작고 간단한 단계로 나눠서 각각 처리한 뒤, 그 결과를 순차적으로 연결합니다.
    • 마치 공장 생산 라인처럼 하나의 작업이 끝나면 다음 작업으로 결과를 넘기고, 최종적으로 원하는 결과를 얻는 방식입니다.
  • LCEL에서 Yogi란?
    • Yogi(요기)는 작은 작업 단위를 의미합니다. LCEL에서 각 Yogi는 특정 작업만 담당합니다.
    • 예를 들어:
      • 첫 번째 Yogi: 질문을 분석
      • 두 번째 Yogi: 모델에게 질문을 보냄
      • 세 번째 Yogi: 답변을 정리

 

LCEL은 기본적으로 여러 작업들을 순차적으로 실행하는 데 사용되며, "Yogi"들은 각 작업을 어떻게 분리하고 조합할지를 결정합니다.

from langchain.yogis import LCEL

# 여러 단계로 날씨를 처리하는 예시
class WeatherYogi(LCEL):
    def process(self, output: str):
        temperature = self.extract_temperature(output)
        weather_condition = self.extract_condition(output)
        return f"오늘의 날씨는 {weather_condition}이고, 기온은 {temperature}입니다."

    def extract_temperature(self, output: str):
        return "22도"  # 예시로 단순화

    def extract_condition(self, output: str):
        return "맑음"  # 예시로 단순화

# 모델의 출력
output = "오늘은 맑고 기온은 22도입니다. 바람은 약하게 불고 있습니다."

# LCEL Yogis를 사용하여 여러 단계를 처리
yogi = WeatherYogi()
result = yogi.process(output)
print(result)  # 결과: 오늘의 날씨는 맑음이고, 기온은 22도입니다.

우리가 날씨와 온도를 파악한 후, 그것을 바탕으로 오늘의 날씨를 어떻게 활용할지 결정하는 작업을 여러 단계로 나눌 수 있습니다.

LCEL 없이 단순한 처리:

# 단일 작업으로 데이터를 처리
output = chat_model(prompt)
parsed_output = parse_output(output)

 

  • 이 코드에서는 데이터를 한 번에 처리하지만, 과정이 단순하고 복잡한 작업에는 적합하지 않습니다.

LCEL을 활용한 단계적 처리:

LCEL을 사용하면 각 작업 단계를 분리하여 깔끔하게 처리할 수 있습니다.

from langchain.yogis import LCEL

class SimpleYogi(LCEL):
    def process(self, input_data):
        # 1단계: 질문 생성
        question = self.generate_question(input_data)
        
        # 2단계: 모델로 질문 전달
        response = self.get_response_from_model(question)
        
        # 3단계: 응답 정리
        return self.parse_response(response)
    
    def generate_question(self, input_data):
        return f"What are the top 5 {input_data}?"
    
    def get_response_from_model(self, question):
        # 가상으로 ChatOpenAI 모델 호출
        return "pikachu, bulbasaur, charmander, squirtle, caterpie"
    
    def parse_response(self, response):
        return response.split(", ")

# LCEL 기반 처리
yogi = SimpleYogi()
result = yogi.process("pokemons")
print(result)
# 결과: ['pikachu', 'bulbasaur', 'charmander', 'squirtle', 'caterpie']

LCEL과 Yogi의 장점

  1. 복잡한 문제를 간단하게 나누기
    • 여러 단계로 나눠서 작업하면 유지보수가 쉬워지고, 각 단계를 독립적으로 테스트할 수 있습니다.
  2. 재사용성 증가
    • 각 Yogi는 하나의 작업만 담당하기 때문에 다른 프로젝트에서도 재사용이 가능합니다.
  3. 구조화된 데이터 처리
    • 데이터를 단계적으로 정리하고 가공할 수 있어, 복잡한 흐름도 이해하기 쉬워집니다.

코드 설명으로 보는 Langchain BaseOutputParserCommaOutputParser

1. ChatOpenAI 불러오기

 

chat = ChatOpenAI(temperature=0.1)
  • ChatOpenAI는 챗봇 같은 일을 해주는 똑똑한 도우미예요.
  • temperature=0.1은 ‘창의력 정도’를 나타내는데, 숫자가 낮을수록 더 “딱딱한” 답변을, 높을수록 “엉뚱할 수 있는” 답변을 준다고 생각하면 돼요.

2. BaseOutputParser와 CommaOutputParser

class CommaOutputParser(BaseOutputParser):
    def parse(self, text):
        items = text.strip().split(",")
        return list(map(str.strip, items))
 
  • BaseOutputParser는 챗봇이 대답한 결과를 어떻게 ‘정리’할지를 정해주는 역할을 해요.
  • 여기서는 CommaOutputParser를 만들어서, “콤마(,)”로 구분된 문장을 받아 각각 리스트 형태로 나눠주는 일을 하죠.
    • 예: "사과, 바나나, 포도" → ["사과", "바나나", "포도"]

3. ChatPromptTemplate와 메시지

template = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "You are a list generating machine. Everything you are asked will be answered with a comma separated list of max {max_items} in lowercase. Do NOT reply with anything else.",
        ),
        ("human", "{question}"),
    ]
)
 
  • ChatPromptTemplate는 챗봇에게 “이렇게 대답해 줘!” 라고 지시하는 메시지를 만드는 역할을 해요.
  • system 메시지에는 챗봇에게 “너는 리스트만 만들어주는 기계야. 소문자로, 콤마로 구분해서, 최대 {max_items}개까지만 대답해!”라고 강력하게 규칙을 알려주고 있어요.
  • human 메시지에는 우리가 실제로 묻고 싶은 질문이 들어가요. "{question}" 자리에 우리가 궁금한 걸 넣을 수 있죠.

4. 파이프(|)로 체인을 만드는 이유

chain = template | chat | CommaOutputParser()

 

 
  • ' | '기호는 물 한 방울이 계속 흐르듯이, 여러 단계를 순차적으로 연결해줘요.
  1. template: 먼저 시스템과 인간 메시지를 합쳐서 프롬프트(“챗봇에게 줄 최종 질문 문장”)를 만들어요.
  2. chat: 만들어진 프롬프트를 똑똑한 챗봇(ChatOpenAI)에게 보내 답변을 받아요.
  3. CommaOutputParser: 챗봇이 준 답변을 “콤마로 나눈 리스트” 형태로 깔끔하게 정리해요.

5. chain.invoke()로 실제 질문하기

chain.invoke({"max_items": 5, "question": "What are the pokemons?"})

 

 
  • 여기서 max_items와 question은 우리가 아까 템플릿에서 {max_items}와 {question}라고 써둔 자리에 들어갈 값이에요.
  • 결과적으로 챗봇은 “최대 5개의 포켓몬 이름을 소문자로, 콤마로 구분해서 대답”하고, CommaOutputParser가 그것을 리스트 형태로 정리해 돌려주게 돼요.
    • 예: 챗봇이 "pikachu, bulbasaur, charmander, squirtle, caterpie"라고 답하면, 최종 결과는 ["pikachu", "bulbasaur", "charmander", "squirtle", "caterpie"]가 되겠죠.

System 메시지에 “너는 리스트만 만들어주는 기계야…”라고 쓰는 이유

챗봇에게 역할(role) 부여하기

  • 챗봇은 마치 연기자라고 생각해보면 쉬워요. 연기자에게 “너는 의사 역할을 해!”라고 말해주면, 그때부터 의사처럼 대답을 하려고 노력하죠.
  • 마찬가지로 System 메시지에서 “너는 리스트만 만들어주는 기계야!”라고 말해주면, 그때부터 챗봇은 ‘진짜로 리스트만 만들어야 하는구나’라고 인식하고 그 규칙을 따르려고 해요.

대답을 원하는 형태로 ‘강제’하기

  • 우리가 “소문자로만, 콤마로 구분해서, 최대 {max_items}개까지만!”이라고 말해주면 챗봇은 다른 형식으로 대답을 하다가도 “아차, 나는 리스트 기계였지” 하고 지시사항을 다시 인식해요.
  • 이런 System 메시지는 챗봇을 조종하거나 기계처럼 특정 업무에 딱 맞는 답변을 주도록 만드는 강력한 방법이에요.

CommaOutputParser(콤마 파서)는 왜 쓰는 걸까?

 

챗봇 답변을 기계가 쓰기 좋게 ‘후처리’

  • 챗봇 답변은 그냥 사람이 읽기 좋게 된 문자열(예: "사과, 바나나, 포도")일 뿐이에요.
  • 하지만 실제 코드를 짤 때에는 문자열을 쪼개서 리스트로 만드는 게 더 편리할 때가 많아요.
    • 예: ["사과", "바나나", "포도"]
  • CommaOutputParser는 이 “콤마로 구분된 한 줄짜리 텍스트”를 쉽게 리스트로 변환해 주는 도구예요.

사람이 아니라 프로그램에서 쓰기 편해짐

  • 가령 리스트로 바꾼 뒤에, “각 과일 이름을 대문자로 바꿔볼까?” 하면:
     
     
    이런 식으로 Python 코드에서 다루기가 훨씬 간단해요 (아래 참조)
  • 만약 문자열로 그대로 있으면 “사과, 바나나, 포도”에서 “, ”를 기준으로 split해야 하고, trim해야 하고… 매번 귀찮잖아요. 그런 과정을 대신해주는 게 CommaOutputParser예요.
    • 그니까 귀찮은걸 미리 만들어놔서 대신 해준다 이렇게 볼수 있습니다 = 틀정하기 ! 
fruits = ["사과", "바나나", "포도"]
new_fruits = [fruit.upper() for fruit in fruits]

사용처

  • 챗봇이 원래 아무렇게나 답변할 때, 내가 원하는 형식으로 데이터를 정리해서 그 다음 단계(분석, 통계, 저장 등)에 쓰고 싶은 경우.
  • 예를 들어, 채팅으로 “오늘 할 일 3개만 말해줘!” 하고 답을 받은 다음, 그 항목들을 바로 DB에 저장하거나 다른 로직에 넘길 때 리스트 형태가 더 유리할 수 있죠.

 

반응형

댓글