(2/3) LLM Basic
🦜 LangChain 입문 시리즈 (전체 3편)
- LLM 을 위한 init setting — 환경 셋업
- LLM Basic — LangChain 기본 사용법 ← 지금 글
- Fine Tuning for Data — LLM 파인튜닝 데이터 처리
load_dotenv
load_dotenv 함수는 .env 파일을 읽어와서 환경변수로 사용합니다.
.env 는 KEY=VALUE 형태로 환경변수를 적어두는 파일이에요. 예를 들면 아래처럼 씁니다.
OPENAI_API_KEY=sk-...
# API KEY를 환경변수로 관리하기 위한 설정 파일
from dotenv import load_dotenv
# import os
# API KEY 정보로드
load_dotenv()
# print(f"[API KEY]\n{os.environ['OPENAI_API_KEY'][:-15]}" + "*" * 15)
True
인메모리로 비용 줄이기
InMemoryCache를 사용하면 메모리에 결과를 저장해서 같은 질문에 대해 비용을 아낄 수 있어요.
from langchain.cache import InMemoryCache
from langchain.globals import set_llm_cache
# 인메모리 캐시를 사용합니다.
set_llm_cache(InMemoryCache())
SQLiteCache를 사용하면 DB에 결과를 저장해서 같은 질문에 대해 비용을 아낄 수 있어요.
from langchain_community.cache import SQLiteCache
from langchain_core.globals import set_llm_cache
import os
# 캐시 디렉토리를 생성합니다.
if not os.path.exists("cache"):
os.makedirs("cache")
# SQLiteCache를 사용합니다.
set_llm_cache(SQLiteCache(database_path="cache/llm_cache.db"))
Langsmith
Langsmith는 LangChain 사용 현황을 알려줍니다. 너무 편해요.
원래는 langsmith를 import 한 다음 client, Trace 함수를 사용해서 설정해야 합니다.
그런데 테디노트님께서 만든 라이브러리로 편리하게 사용할 수 있어요.
# LangSmith 추적을 설정합니다. https://smith.langchain.com
# !pip install -qU langchain-teddynote
from langchain_teddynote import logging
# 프로젝트 이름을 입력합니다.
logging.langsmith("My-Book-01")
LangSmith 추적을 시작합니다.
[프로젝트명]
My-Book-01
LangChain 기본
langchain은 PromptTemplate를 사용하여 체인을 생성해서 사용하는 것을 기본으로 알고 있으면 좋습니다.
invoke 또는 predict를 사용하면 됩니다. batch를 사용하면 여러 개도 넣어서 처리할 수 있습니다.
아래 구조가 기본 구조이니 참고하세요.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
# ChatOpenAI 모델을 인스턴스화합니다.
model = ChatOpenAI(temperature=0)
# 주어진 토픽에 대한 농담을 요청하는 프롬프트 템플릿을 생성합니다.
prompt = PromptTemplate.from_template("{topic} 에 대하여 3문장으로 설명해줘.")
# 프롬프트와 모델을 연결하여 대화 체인을 생성합니다.
chain = prompt | model | StrOutputParser()
topic = """
존 윅은 은퇴한 전설적인 암살자로, 아내의 죽음 후 남겨진 강아지와 평화롭게 지내고 있었습니다. 그러나 러시아 마피아 보스의 아들 이오세프가 그의 차를 훔치고 강아지를 죽이자 존은 복수를 결심합니다. 존은 다시 암살자의 세계로 돌아가 이오세프와 그를 보호하려는 마피아 조직과 싸웁니다. 마피아들은 존의 전설적인 실력을 두려워하며 그를 제거하려 하지만 실패합니다. 결국 존은 이오세프와 마피아 조직을 처단하며 복수를 완성합니다.
"""
chain.invoke(topic)
'존 윅은 은퇴한 암살자로, 아내의 죽음 후 복수를 결심하게 된다. 이오세프와 마피아 조직과의 전투에서 전설적인 실력을 발휘하며 복수를 완성한다. 결국 존은 이오세프와 마피아 조직을 처단하고 자신의 정의를 성취한다.'
사용된 토큰 수와 금액 등을 알고 싶으면 get_openai_callback 를 활용하면 됩니다.
from langchain.callbacks import get_openai_callback
with get_openai_callback() as cb:
result = chain.invoke(topic)
print(cb)
print(f"총 사용된 토큰수: \t\t{cb.total_tokens}")
print(f"프롬프트에 사용된 토큰수: \t{cb.prompt_tokens}")
print(f"답변에 사용된 토큰수: \t{cb.completion_tokens}")
print(f"호출에 청구된 금액(USD): \t${cb.total_cost}")
Tokens Used: 408
Prompt Tokens: 259
Completion Tokens: 149
Successful Requests: 1
Total Cost (USD): $0.000353
총 사용된 토큰수: 408
프롬프트에 사용된 토큰수: 259
답변에 사용된 토큰수: 149
호출에 청구된 금액(USD): $0.000353
PromptTemplate를 사용하는 방법으로 아래의 방법이 좀 더 편할 수 있습니다.
먼저 template를 만들고 PromptTemplate를 사용하여 변수 처리할 수 있어 가시성이 좋습니다.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
# ChatOpenAI 모델을 인스턴스화합니다.
model = ChatOpenAI(temperature=0)
# template 정의
template = "{topic} 에 대하여 3문장으로 설명해줘."
# PromptTemplate 객체를 활용하여 prompt_template 생성
prompt = PromptTemplate(
template=template,
input_variables=["topic"],
)
chain = prompt | model | StrOutputParser()
chain.invoke({'topic': '스타벅스'})
'스타벅스는 세계적으로 유명한 커피 전문 브랜드로, 다양한 음료와 디저트를 판매한다. 고객들은 편안한 분위기에서 커피를 즐기며 휴식을 취할 수 있다. 매장 내부는 모던하고 아늑한 인테리어로 꾸며져 있어 많은 사람들이 방문한다.'
만들어진 prompt도 출력해서 확인할 수 있어요.
print(prompt)
print(prompt.input_variables)
print(prompt.template)
input_variables=['topic'] template='{topic} 에 대하여 3문장으로 설명해줘.'
['topic']
{topic} 에 대하여 3문장으로 설명해줘.
# chain.stream 메서드를 사용하여 '멀티모달' 토픽에 대한 스트림을 생성하고 반복합니다.
for token in chain.stream({"topic": "스타벅스"}):
# 스트림에서 받은 데이터의 내용을 출력합니다. 줄바꿈 없이 이어서 출력하고, 버퍼를 즉시 비웁니다.
print(token, end="", flush=True)
스타벅스는 세계적으로 유명한 커피 전문 브랜드이며, 다양한 음료와 디저트를 판매한다. 고객들은 편안한 분위기에서 커피를 즐기며 휴식을 취할 수 있다. 매장 내부는 현대적이고 세련된 디자인으로 꾸며져 있어 많은 사람들이 방문한다.
# 주어진 토픽 리스트를 batch 처리하는 함수 호출
chain.batch([{"topic": "스타벅스"}, {"topic": "존윅"}])
['스타벅스는 세계적으로 유명한 커피 전문 브랜드로, 다양한 음료와 디저트를 판매한다. 고객들은 편안한 분위기에서 커피를 즐기며 휴식을 취할 수 있다. 매장 내부는 모던하고 아늑한 인테리어로 꾸며져 있어 많은 사람들이 방문한다.',
'존윅은 전직 킬러가 복수를 위해 돌아온 액션 영화 시리즈이다. 주인공 존윅은 뛰어난 전투 기술과 냉정한 심리로 적들을 상대하는 모습이 인상적이다. 시리즈는 고난과 역경을 극복하며 자신의 목표를 향해 달려가는 모험을 그린다.']
partial_variables
partial_variables로 고정 변수를 설정할 수 있어요.
고정 변수도 나중에 바꿀 수 있어요. prompt.partial() 로 갈아끼우면 됩니다.
# template 정의
template = "{country1}과 {country2}의 수도는 각각 어디인가요?"
# PromptTemplate 객체를 활용하여 prompt_template 생성
prompt = PromptTemplate(
template=template,
input_variables=["country1"],
partial_variables={
"country2": "미국" # dictionary 형태로 partial_variables를 전달
},
)
prompt
PromptTemplate(input_variables=['country1'], partial_variables={'country2': '미국'}, template='{country1}과 {country2}의 수도는 각각 어디인가요?')
prompt.format(country1="대한민국")
'대한민국과 미국의 수도는 각각 어디인가요?'
prompt_partial = prompt.partial(country2="캐나다")
prompt_partial.format(country1="대한민국")
'대한민국과 캐나다의 수도는 각각 어디인가요?'
RunnableParallel
Runnables는 언어 모델 작업을 모듈화하는 데 쓸 수 있어요.
RunnableParallel를 사용하면 여러 Runnable을 병렬로 처리하거나 연쇄적으로 실행할 수 있습니다.
from langchain_core.runnables import RunnableParallel
# RunnableParallel 인스턴스를 생성합니다. 이 인스턴스는 여러 Runnable 인스턴스를 병렬로 실행할 수 있습니다.
runnable = RunnableParallel(
# RunnablePassthrough 인스턴스를 'passed' 키워드 인자로 전달합니다. 이는 입력된 데이터를 그대로 통과시키는 역할을 합니다.
passed=RunnablePassthrough(),
# 'extra' 키워드 인자로 RunnablePassthrough.assign을 사용하여, 'mult' 람다 함수를 할당합니다. 이 함수는 입력된 딕셔너리의 'num' 키에 해당하는 값을 3배로 증가시킵니다.
extra=RunnablePassthrough.assign(mult=lambda x: x["num"] * 3),
# 'modified' 키워드 인자로 람다 함수를 전달합니다. 이 함수는 입력된 딕셔너리의 'num' 키에 해당하는 값에 1을 더합니다.
modified=lambda x: x["num"] + 1,
)
# runnable 인스턴스에 {'num': 1} 딕셔너리를 입력으로 전달하여 invoke 메소드를 호출합니다.
runnable.invoke({"num": 1})
chain1 = (
{"country": RunnablePassthrough()}
| PromptTemplate.from_template("{country} 의 수도는?")
| ChatOpenAI()
)
chain2 = (
{"country": RunnablePassthrough()}
| PromptTemplate.from_template("{country} 의 면적은?")
| ChatOpenAI()
)
combined_chain = RunnableParallel(capital=chain1, area=chain2)
combined_chain.invoke("대한민국")
itemgetter
itemgetter를 사용하면 Chain에서 dict의 Key를 가지고 Value를 활용하여 코드를 전개할 수 있습니다.
from operator import itemgetter
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableLambda
from langchain_openai import ChatOpenAI
# 문장의 길이를 반환하는 함수입니다.
def length_function(text):
return len(text)
# 두 문장의 길이를 곱한 값을 반환하는 함수입니다.
def _multiple_length_function(text1, text2):
return len(text1) * len(text2)
# _multiple_length_function 함수를 사용하여 두 문장의 길이를 곱한 값을 반환하는 함수입니다.
def multiple_length_function(_dict):
return _multiple_length_function(_dict["text1"], _dict["text2"])
prompt = ChatPromptTemplate.from_template("{a} + {b} 는 무엇인가요?")
model = ChatOpenAI()
chain1 = prompt | model
chain = (
{
"a": itemgetter("word1") | RunnableLambda(length_function),
"b": {"text1": itemgetter("word1"), "text2": itemgetter("word2")}
| RunnableLambda(multiple_length_function),
}
| prompt
| model
)
파일에서 프롬프트 읽어오기
load_prompt 를 통해 yaml 파일에서 프롬프트를 읽어 올 수 있어요.
from langchain_core.prompts import load_prompt
prompt = load_prompt("prompts/fruit_color.yaml", encoding="utf-8")
prompt
또한 아래처럼 system에 role을 부여하는 용도로도 사용할 수 있습니다.
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
llm = ChatOpenAI()
chat_template = ChatPromptTemplate.from_messages(
[
# role, message
("system", "당신은 친절한 AI 어시스턴트입니다. 당신의 이름은 {name} 입니다."),
("human", "반가워요!"),
("ai", "안녕하세요! 무엇을 도와드릴까요?"),
("human", "{user_input}"),
]
)
# 챗 message 를 생성합니다.
messages = chat_template.format_messages(
name="테디", user_input="당신의 이름은 무엇입니까?"
)
messages
chain = chat_template | llm
chain.invoke({"name": "Teddy", "user_input": "당신의 이름은 무엇입니까?"}).content
MessagesPlaceholder 를 사용하면 아웃풋을 가공할 수 있어요.
format으로 기본 규칙을 정하고, 이후 invoke에서 들어오는 conversation을 처리합니다.
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI
llm = ChatOpenAI()
chat_prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"당신은 요약 전문 AI 어시스턴트입니다. 당신의 임무는 주요 키워드로 대화를 요약하는 것입니다.",
),
MessagesPlaceholder(variable_name="conversation"),
("human", "지금까지의 대화를 {word_count} 단어로 요약합니다."),
]
)
chat_prompt
formatted_chat_prompt = chat_prompt.format(
word_count=5,
conversation=[
("human", "안녕하세요! 저는 오늘 새로 입사한 테디 입니다. 만나서 반갑습니다."),
("ai", "반가워요! 앞으로 잘 부탁 드립니다."),
],
)
print(formatted_chat_prompt)
# chain 생성
chain = chat_prompt | llm | StrOutputParser()
System: 당신은 요약 전문 AI 어시스턴트입니다. 당신의 임무는 주요 키워드로 대화를 요약하는 것입니다.
Human: 안녕하세요! 저는 오늘 새로 입사한 테디 입니다. 만나서 반갑습니다.
AI: 반가워요! 앞으로 잘 부탁 드립니다.
Human: 지금까지의 대화를 5 단어로 요약합니다.
# conversation 을 다양한 대화로 확장
conversation = [
("human", "안녕하세요! 저는 오늘 새로 입사한 테디입니다. 앞으로 잘 부탁드립니다."),
("ai", "반갑습니다, 테디님! 새로운 팀원으로 합류해 주셔서 기쁩니다. 어떻게 도와드릴까요?"),
("human", "네, 우선 회사 시스템과 프로세스에 대해 궁금한 점이 많아요. 특히 데이터 처리 파이프라인에 대해 알고 싶습니다."),
("ai", "회사의 데이터 파이프라인은 주로 GCP 기반으로 구축되어 있습니다. 일단 빅쿼리로 데이터가 수집되고, 파이프라인은 스파크를 사용하여 처리됩니다."),
("human", "아, 그럼 ETL 작업도 빅쿼리에서 다 처리하는 건가요?"),
("ai", "네, 대부분의 ETL 작업은 빅쿼리에서 진행되며, 복잡한 작업은 스파크 클러스터를 통해 처리됩니다."),
("human", "좋네요! 혹시 데이터 모니터링은 어떻게 이루어지나요? 실시간 알림 같은 시스템이 있나요?"),
("ai", "네, 실시간 모니터링은 스택드라이버와 연동되어 있으며, 중요한 이벤트가 발생할 때는 이메일이나 슬랙 알림을 받으실 수 있습니다."),
("human", "슬랙 알림도 지원되나요? 그럼 실시간 협업도 쉽게 가능하겠네요."),
("ai", "맞습니다. 실시간으로 팀원들과의 협업이 가능하며, 알림을 통해 즉각적인 대응이 가능합니다."),
("human", "감사합니다! 그러면 제가 지금부터 시스템에 적응하는 데 많은 도움이 될 것 같습니다."),
("ai", "천천히 익숙해지시면 됩니다. 궁금한 점이 생기면 언제든지 문의 주세요!")
]
# chain 실행
result = chain.invoke(
{
"word_count": 3,
"conversation": conversation,
}
)
print(result)
새로운 팀원, 데이터 파이프라인, 실시간 협업
← 이전 글: (1/3) LLM 을 위한 init setting — 환경 셋업 | 다음 글 →: (3/3) Fine Tuning for Data — LLM 파인튜닝 데이터 처리