[LLM 개발기초] 4. LangChain 활용 #1


이번 챕터부터는 LangChain 을 다양하게 활용하는 예제들을 실행해 보겠습니다.

LangChain 은 AI로부터 답을 얻기까지 여러 단계로 구분하고, 각 단계별로 도구를 만들어 서로 연결할 수 있도록 해주는 라이브러리입니다. 따라서 단계별로 다양한 도구들을 불러와서 이들을 조합하면 내가 원하는 방향으로 AI 를 튜닝할 수 있습니다.

가장 먼저 실험해 볼 도구는 프롬프트(Prompt)입니다. 앞선 챕터에서 예제로 체험한 것 처럼, AI 에게 던지는 메시지를 변형해서 내가 의도한대로 대답을 얻거나 AI 와의 상호작용을 더욱 효율적으로 만드는데 사용됩니다.


Prompt 실습 1: 프롬프트와 LCEL 을 이용한 기본 예제

LangChain 에는 LCEL(LangChain Expression Language) 기능이 탑재되어 있습니다. 그래서 앞서 얘기한 도구들을 만들면 아래 같은 표현식으로 서로 연결할 수 있습니다.

chain = prompt | llm | output_parser

이렇게 연결된 chain 을 이용해서 LLM 모델을 실행할 수 있습니다. 이 과정에서 도구들을 연결하고 동작을 최적화하며, 디버깅을 위한 준비 등을 알아서 해줍니다. 이번 실습에서는 LCEL chain 을 이용합니다. [04_01_prompt_test_01.py]

from langchain_community.llms import Ollama
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

# 1. 프롬프트 템플릿 생성
prompt = ChatPromptTemplate.from_template(
    "당신은 유능한 기상학자입니다. 다음 질문에 답해주세요. <질문>: {question}"
)

# 2. Ollama 모델 초기화
llm = Ollama(model="llama3.1")

# 3. 스트림 출력 파서 생성
class CustomStreamOutputParser(StrOutputParser):
    def parse(self, text):
        return text

output_parser = CustomStreamOutputParser()

# 4. chain 연결 (LCEL)
chain = prompt | llm | output_parser

# 5. chain 실행 및 결과 출력
for chunk in chain.stream({"question": "크기에 따른 태풍의 분류 방법을 알려주세요."}):
    print(chunk, end="", flush=True)

코드를 단계별로 살펴보면

  1. ChatPromptTemplate 을 이용하여 프롬프트를 생성합니다.
    • 프롬프트를 생성하는 가장 단순한 방법을 사용하였습니다.
    • 프롬프트에 LLM 에 전달할 요구사항을 명시하면 됩니다.
    • 내가 할 질문은 5번 chain 실행 과정에서 입력하게 됩니다.
  2. 로컬 PC에 설치된 ollama 와 llama3.1 모델을 사용합니다.
  3. 출력 파서(OutputParser)를 이용하면 출력 결과를 원하는대로 수정할 수 있습니다.
    • 예제에서는 StrOutputParser 를 이용해서 CustomStreamOutputParser 를 만들었습니다.
    • StrOutputParser 는 출력을 문자열로 받는 가장 기본적인 OutputParser 입니다.
    • JSON, XML, CSV 등 다양한 출력 포맷을 사용하고 싶은 경우 아래 내용을 참고하세요
    • 실습 코드에서는 파서를 만들지만, 내부에서 아무일도 하지 않습니다.
  4. LCEL 을 이용해서 chain 을 생성합니다.
    • LangChain 은 도구들을 연결해서 실행할 준비를 합니다.
  5. chain 을 실행하고 결과를 stream 형식으로 출력합니다.
    • 이때 prompt 에 필요한 질문을 함께 전달합니다.

실행해보면 아래처럼 결과가 출력될 것입니다.

$ python -u "c:\___Workspace\Llama\test\llama_test\LLM_test\04_01_prompt_test_01.py"

기상학적으로, 태풍은 크기에 따라 다섯 가지 분류가 있습니다.

1.  **소형태풍**: 이들은 10분간 평균 풍속이 약 33m/s에서 39m/s 사이인 태풍입니다.
2.  **중형태풍**: 이들은 10분간 평균 풍속이 약 40m/s에서 49m/s 사이인 태풍입니다.
3.  **중대형태풍**: 이들은 10분간 평균 풍속이 약 50m/s에서 59m/s 사이인 태풍입니다.
4.  **대형태풍**: 이들은 10분간 평균 풍속이 약 60m/s에서 69m/s 사이인 태풍입니다.
5.  **매우대형태풍**: 이들은 10분간 평균 풍속이 70m/s 이상인 태풍입니다.

태풍의 크기는 바람의 속도와 관련되어 있습니다. 태풍은 바람의 속도가 증가할수록 더 큰 크기를 가진다.

$



Prompt 실습 2: 대화내용을 기억하는 연속 채팅

Langchain의 ChatPromptTemplate은 대화형 시스템에서 각 메시지의 역할(시스템, 사용자, 봇)을 명확하게 구분하고, 이전 대화 내용을 기억하여 더욱 자연스러운 대화를 이어나갈 수 있도록 돕습니다. 그리고 대화 내용을 기록하는 역할도 해줍니다.

이번 실습에서는 ChatPromptTemplate을 이용해서 대화 내용을 기억하면서 대화를 이어나갈 수 있도록 해보겠습니다. [04_02_advanced_prompt.py]

from langchain_community.llms import Ollama
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser

# 1. 대화내용 저장을 위한 ChatPromptTemplate 설정
prompt = ChatPromptTemplate.from_messages([
    ("system", "당신은 유능한 기상학자입니다. 답변은 200자 이내로 하세요."),
    MessagesPlaceholder("chat_history"), # 1-1. 프롬프트에 대화 기록용 chat_history 추가
    ("user", "{question}")
])

# 2. Ollama 모델 초기화
llm = Ollama(model="llama3.1")

# 3. 스트림 출력 파서 생성
class CustomStreamOutputParser(StrOutputParser):
    def parse(self, text):
        return text

output_parser = CustomStreamOutputParser()

# 4. chain 연결 (LCEL)
chain = prompt | llm | output_parser

# 5. 채팅 기록 초기화
chat_history = []

# 6. chain 실행 및 결과 출력을 반복
while True:
    # 6-1. 사용자의 입력을 기다림
    user_input = input("\n\n당신: ")
    if user_input == "끝":
        break

    # 6-2. 체인을 실행하고 결과를 stream 형태로 출력
    result = ""
    for chunk in chain.stream({"question": user_input, "chat_history": chat_history}):
        print(chunk, end="", flush=True)
        result += chunk

    # 6-3. 채팅 기록 업데이트
    chat_history.append(("user", user_input))
    chat_history.append(("assistant", result))

코드를 단계별로 살펴보면

  1. ChatPromptTemplate 을 이용해서 프롬프트를 만듭니다. 이때 대화내용을 저장하기 위한 chat_history 설정을 해줍니다.
    • 1-1. MessagesPlaceholder 를 이용해서 대화 기록용 chat_history 를 추가합니다.
  2. 로컬 PC에 설치된 ollama 와 llama3.1 모델을 사용합니다.
  3. 출력 파서(OutputParser)를 설정합니다.
    • 이번 예제에서는 특별히 하는 일이 없습니다.
  4. LCEL 을 이용해서 chain 을 생성합니다.
  5. 대화내용을 저장할 chat_history 배열을 만듭니다.
  6. chain 을 실행합니다.
    • 6-1. 먼저 사용자의 입력을 받습니다.
    • 6-2. 사용자의 입력과 저장된 대화내용을 이용해서 chain 을 실행합니다.
    • 6-3. 대답이 모두 출력되면 chat_history 에 질문과 대답을 넣어줍니다.

코드를 실행해보면 앞선 예제와는 달리 대화를 계속 이어나갈 수 있습니다. (종료: Ctrl + C)

$ python -u "c:\___Workspace\Llama\test\llama_test\LLM_test\04_02_advanced_prompt.py"

당신: 1년 동안 전 세계에서 밠생하는 태풍의 평균 갯수는?
밠생하는 태풍의 평균 갯수에 대해, 저는 그 정보를 제공하겠습니다.

전 세계에서 1년 동안 밠생하는 태풍의 평균 갯수는 약 80개입니다. 이 숫자는 전 세계의 태풍 발생 지역을 감안해 정의되었으며, 아시아와 대서양의 태풍 활동이 가장 활발한 지역입니다.

당신: 앞선 질문에서 지역을 아시아로 한정하면 대답은 어떻게 바뀌지?
아시아의 경우 1년 동안 밠생하는 태풍의 평균 갯수는 약 54개 정도입니다. 이 숫자도 전 세계의 태풍 발생 지역 중 아시아를 포함한 지역에서 
발생하는 태풍의 총 수를 감안해 정의되었습니다.

당신:

대화를 자세히 보면 1번째 질문을 기억하고 있음을 알 수 있습니다. 2번째 질문에서 태풍에 대한 언급없이 지역만 변경해서 대답해 달라고 했는데 태풍의 갯수에 대한 대답을 해줍니다.



Prompt 실습 3: 출력되는 내용을 수정

이번에는 output parser 를 더욱 업그레이드해서 사용해 보겠습니다. 실습 코드는 태풍에 대한 내용을 출력할 때 “태풍” 단어를 이모지로 바꿔서 출력합니다. [04_03_custom_output_parser.py]

  • 이전 실습에 있던 CustomStreamOutputParser 의 parse() 함수를 수정해서 사용하면 연산이 느린 PC에서 1글자 단위로 입력이 들어와서 단어 단위의 처리를 못하는 문제가 있습니다.
  • 그래서 RunnableGenerator 를 사용하는 방식으로 바꿔서 구현했습니다.
    • 이 방법 대신 6-2 코드에서 출력을 수정해도 됩니다.
from typing import Iterable
from langchain_community.llms import Ollama
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnableGenerator
from langchain_core.messages import AIMessageChunk

# 1. 대화내용 저장을 위한 ChatPromptTemplate 설정
prompt = ChatPromptTemplate.from_messages([
    ("system", "당신은 유능한 기상학자입니다. 답변은 200자 이내로 하세요."),
    MessagesPlaceholder("chat_history"), # 1-1. 프롬프트에 대화 기록용 chat_history 추가
    ("user", "{question}")
])

# 2. Ollama 모델 초기화
llm = Ollama(model="llama3.1")

# 3. 스트림 출력 파서 생성 🌪️
def replace_word_with_emoji(text: str) -> str: # 문자열에서 태풍을 이모지로 바꿔주는 함수
    return text.replace("태풍", "🌪️")

def streaming_parse(chunks: Iterable[AIMessageChunk]) -> Iterable[str]: # AI 출력을 받아서 처리하는 함수
    buffer = ""
    for chunk in chunks: # 속도가 느린 컴퓨터에서 실행하는 경우 단어가 완성될 때까지 모아서 처리
        buffer += chunk
        while " " in buffer:
            word, buffer = buffer.split(" ", 1)
            yield replace_word_with_emoji(word) + " "
    if buffer:
        yield replace_word_with_emoji(buffer)

streaming_parser = RunnableGenerator(streaming_parse)

# 4. chain 연결 (LCEL)
chain = prompt | llm | streaming_parser

# 5. 채팅 기록 초기화
chat_history = []

# 6. chain 실행 및 결과 출력을 반복
while True:
    # 6-1. 사용자의 입력을 기다림
    user_input = input("\n\n당신: ")
    if user_input == "끝":
        break

    # 6-2. 체인을 실행하고 결과를 stream 형태로 출력
    result = ""
    for chunk in chain.stream({"question": user_input, "chat_history": chat_history}):
        print(chunk, end="", flush=True)
        result += chunk

    # 6-3. 채팅 기록 업데이트
    chat_history.append(("user", user_input))
    chat_history.append(("assistant", result))

이전 실습과 다른 부분은 동일하므로 변경된 3번 부분만 설명합니다.

  • 3. LLM stream 이 뱉어내는 단어들을 streaming_parse() 함수에서 처리합니다.
    • stream 에서 나오는 단어들을 buffer 에 모아둡니다.
    • buffer 에 공백문자가 있으면 공백을 기준으로 단어를 추출합니다.
    • 추출된 단어에서 “태풍” 문자가 검색되면 이모지로 바꿔줍니다.

이 코드를 실행해보면 아래와 같이 이모지가 “태풍” 문자를 대체합니다.

$ python -u "c:\___Workspace\Llama\test\llama_test\LLM_test\04_03_custom_output_parser.py"


당신: 전 세계에서 1년 동안 밠생하는 태풍의 평균 갯수는?
전 세계적으로 1년 동안 발생하는 🌪️의  평균 갯수는 약 80개 정도입니다. 이 중 약 60%가 태평양에 발생하고, 나머지 갯수는 인도양과 북서태 평양 지역에서 발생합니다.

당신: 태풍의 종류에 대해서 알려줘
기상학적으로 🌪️은  다음 세 가지 유형으로 분류할 수 있습니다.

1.  **트레치**: 가장 강력한 종류입니다. 강풍(약 200km/h 이상)과-heavy rainfall이 특징입니다.
2.  **슈퍼🌪️**:  전 세계에서 발생하는 가장 강력한 🌪️입니다.  풍속은 250 km/h 이상이며, 거대한 물리적 피해를 남길 수 있습니다.
3.  **서스포트**: 가장 느린 종류입니다. 풍속은 약 100km/h 미만으로, 비교적 가벼운 피해를 낼 수 있습니다.

이러한 유형은 기상학적으로 분류되며, 각 지역에서 발생하는 🌪️의  특징과 영향을 고려하여 분류됩니다.

당신:



참고자료


You may also like...

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.