Post

Langchain Basic

랭체인 기본에 대한 간략한 내용을 정리한 포스트입니다.

Langchain Basic

LLM(Large Language Model)를 사용해서 텍스트를 생성할 수 있다.

먼저, 관련된 패키지를 설치하고 API 키를 가져와야하며 이에 대한 코드는 아래와 같다.

1
2
3
4
5
6
7
## 패키지 설치
%pip install -q langchain-ollama langchain-openai python-dotenv

## 환경 변수 설정 - .env 파일에서 API 키와 같은 환경 변수들을 로드
from dotenv import load_dotenv

load_dotenv()

텍스트를 생성하는 방법은 두 가지가 있다.

  1. Ollama를 이용한 로컬 LLM 사용

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
     from langchain_ollama import ChatOllama
    
     # Ollama를 이용한 로컬 LLM 설정
     llm = ChatOllama(model="llama3.2:1b")
    
     # 간단한 질문으로 테스트
     llm.invoke("What is the capital of France?")
    
     """
     AIMessage(content='The capital of France is Paris.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 7, 'prompt_tokens': 14, 'total_tokens': 21, ... )
     """
    
  2. Open AI GPT(API 키 이용) 모델 사용

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
     from langchain_openai import ChatOpenAI
    
     # OpenAI GPT 모델 설정
     llm = ChatOpenAI(model="gpt-4o-mini")
    
     llm.invoke("What is the capital of France?")  # OPENAI_API_KEY
    
     """
     AIMessage(content='The capital of France is Paris.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 7, 'prompt_tokens': 14, 'total_tokens': 21, ... )
     """
    

여기서 LangChain을 사용하는 이유는 LLM 기반 애플리케이션을 더 쉽게, 효율적으로 개발할 수 있다. 이는 더욱 복잡한 태스크를 수행해야할 때 더욱 효율적으로 개발이 가능하다.

PromptTemplate


Prompt는 LLM을 호출할 때 사용하는 명령어이다.

  • ex. “What is the capital of France?”

프롬프트를 구성할 때, PromptTemplate를 사용하면 변수가 포함된 템플릿을 만들 수 있다.

PromptTemplate 구성

  • input에 정해진 type이 존재함
    • PromptValue : Prompt Template를 통해 만들 수 있음
    • BaseMessages: HumanMessage가 상속받는 것으로 이런 클래스가 대표적으로 4가지가 있음
      • System(LLM APP의 목적, Persona)
      • HumanMessage(사용자 message)
      • AIMessage(LLM)
      • ToolMessage(도구, agent 만들 때 사용하며 invoke할 때 생성)
1
2
3
4
5
6
7
8
9
10
11
12
13
from langchain_ollama import ChatOllama
from langhchain_core.prompts import ChatPromptTemplate

prompt_template = PromptTemplate(
    template="What is the capital of {country}?",
    input_variables=["country"],
)

prompt = prompt_template.invoke({"country": "France"})

llm = ChatOllama(model="llama3.2:1b")

llm.invoke(prompt)

메세지 기반 프롬프트

시스템 메시지, 사용자 메시지, AI 메시지를 조합하여 대화 형식의 프롬프트를 만들 수 있다.

1
2
3
4
5
6
7
8
9
10
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage

# 대화 형식의 메시지 리스트 생성
message_list = [
    SystemMessage(content="You are a helpful assistant!")
    HumanMessage(content="What is the capital of France?"),
    AIMessage(content="The capital of France is Paris"),
    HumanMessage(content="What is the capital of Germany?")
]
llm.invoke(message_list)

위의 코드에서는 AIMessage를 넣어 AI가 답변 형식에 대한 예시를 추가하였으며, 이 방식은 마치 LLM과 대화를 나눈 것처럼 속이는 것으로 개발자가 원하는 방식대로 답하도록 만든다.

이렇게 예시(shot)을 추가하면 AI 수행 능력이 굉장히 향상된다.

ChatPromptTemplate

또 다른 방법으로는 ChatPromptTemplate 을 사용한 프롬프트 작성 방식이 있다.

이 방식은 나중에 나올 LCEL에 적용할 수 있어 확장성 측면에서 훨씬 유리하다는 장점이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from langchain_core.prompts import ChatPromptTemplate

chat_prompt_template = ChatPromptTemplate.from_messages([
    ('system', "You are a helpful assistant!"),     # SystemMessage
    ('human', "What is the capital of {country}?")  # HumanMessage
])

chat_prompt = chat_prompt_template.invoke({"country": "France"})

# 생성된 메시지 확인
chat_prompt.messages

# LLM에 프롬프트 전달
llm.invoke(chat_prompt)    # AIMessage 출력

Output Parser


LangChain을 사용하면 LLM의 출력 형식을 제어할 수 있다.

문자열 출력 파서

대표적으로 StrOutputParser 가 있다. 이 함수를 사용하면 LLM의 출력을 단순 문자열로 변환할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser

# 명시적인 지시사항이 포함된 프롬프트 템플릿 정의
prompt_template = PromptTemplate(
    template="What is the capital of {country}? Return the name of city only",
    input_variables=["country"],
)

# 템플릿에 변수 값을 대입
prompt = prompt_template.invoke({"country": "France"})

# LLM에 프롬프트 전달
ai_message = llm.invoke(prompt)

# 문자열 출력 파서를 사용하여 응답을 단순 문자열로 변환
output_parser = StrOutputParser()

answer = output_parser.invoke(llm.invoke(prompt_template.invoke({'country': 'France'})))

# AI 메시지의 content 속성이 타입 확인
type(ai_message.content)    # str

# 파싱된 응답 확인
print(answer)   # Paris

StrOutputParser는 string 타입으로 반환해주기 데이터 전송할 때 매우 편리하다.

구조화된 출력

Pydantic 모델을 사용하여 LLM의 출력을 구조화된 형식으로 받을 수 있다.

1
2
3
4
5
6
7
8
9
10
11
from pydantic import BaseModel, Field

# 국가 정보를 담을 Pydantic 모델 정의
class CountryDetail(BaseModel):
    capital: str = Field(description="The capital of the country")
    population: int = Field(description="The population of the country")
    language: str = Field(description="The language of the country")
    currency: str = Field(description="The currency of the country")

# LLM에 구조화된 출력 형식 지정
structued_llm = llm.with_structured_output(CountryDetail)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from langchain_core.output_parsers import JsonOutputParser

# JSON 형식으로 응답을 요청하는 프롬프트 템플릿
country_detail_prompt = PromptTemplate(
    template="""Give following information about {country}:
    - Capital
    - Population
    - Language
    - Currency

    return it in JSON format. and return the JSON dictionry only 
    """,
    input_variables=["country"],
)

country_detail_prompt.invoke({"country": "France"})

# 구조화된 LLM으로 응답 받기
json_ai_message = structued_llm.invoke(country_detail_prompt.invoke("country": "France"))

# 구조화된 응답 확인
(json_ai_message)

# Pydantic 모델의 특정 필드 접근
json_ai_message.model_dump()['capital']    # 'Paris'

문자열처럼 JsonOutputParser 함수를 통해 Json을 받을 수 있지만 위의 방식이 훨씬 안전하다.

Runnable


LangChain Expression Language(LCEL)를 사용하여 여러 컴포넌트를 연결하고 복잡한 체인을 구성할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from langchain_ollama import ChatOllama

llm = ChatOllama(model="llama3.2:1b")

from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser

prompt_template = PromptTemplate(
    template="What is the capital of {country}? Return the name of city only",
    input_variables=["country"],
)

# 문자열 출력 파서 설정
output_parser = StrOutputParser()

# 프롬프트 템플릿 -> LLM -> 출력 파서를 연결하는 체인 생성
capital_chain = prompt_template | llm | output_parser

# 생성된 체인 실행
capital_chain.invoke({"country": "France"})

이렇게 chain을 구현할 수 있는 이유는 바로 llm, StrOutputParser, prompt_template 같은 모든 주요 구성 요소들이 LangChain Expression Language (LCEL)의 기본 단위인 Runnable 인터페이스를 구현하기 때문이다.

여러 단계의 추론이 필요한 더 복잡한 체인을 구성해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 국가를 추측하는 프롬프트 템플릿 정의
country_prompt = PromptTemplate(
    template="""Guess the name of the country in the {continent} based on the following information: {information}
    Return the name of the country only
    """,
    input_variables=["information", "continent"],
)

# 국가 추측 체인 생성
country_chain = country_prompt | llm | output_parser

from langchain_core.runnables import RunnablePassthrough

# RunnablePassthrough를 사용하여 입력을 다음 단계로 전달하는 복합 체인 구성
final_chain = {"information": RunnablePassthrough(), "continent": RunnablePassthrough()} | {"country": country_chain} | capital_chain

# 복합 체인 실행
# 정보와 대륙을 입력하면, 해당 국가를 추측하고 그 국가의 수도를 반환
final_chain.invoke({"information": "This country is very famous for its wine in Europe", "continent": "Europe"})    # Madrid

💡 프롬프트를 쪼개서 작성해야하는 이유
길게 작성하게 되면 하라고 훈련받았는데 하지 말라는 조건을 넣게 된다. 이렇게 출력하지 말라는 조건을 넣으면 잘 작동을 안하기 때문에 safety 프롬프트를 넣어서 Yes/no 를 거치고 이 결과가 통과(Yes)면 다음 logic을 거치도록 해야한다.

즉, 하나의 프롬프트가 아니라 여러 프롬프트를 거쳐서 원하는 결과를 얻는 것이 작성하는 것이 바람직하다.

This post is licensed under CC BY 4.0 by the author.