본문 바로가기
NLP

reading LangChain docs (1)

by 볼록티 2024. 1. 16.
728x90
반응형

 

 

 LangChain은 LLM 모델을 쉽게 개발할 수 있게 도와주는 프레임워크 입니다.

 저같은 경우는 회사서 이런저런 상황에 맞는 적절한 챗봇을 구축하는 일을 하게 되었습니다. 주로 OpenAI의 ChatGPT API를 토대로 파인튜닝한 해서 사용자의 입력에 대한 적절한 결과물을 서비스 하게 될 것 같은데요(파운데이션 모델로 1등하는 일은 이제 넘사벽이 된 것만 같습니다. 있는걸 잘 활용하자! 의 자세로 사는 중입니다.).

 

 그리하여 LangChain과 좀 더 친해지려고 공부 겸 실습을 좀 해보기 위해 기록을 하게 되었습니다. 공부도 하고 기여도 하고! 

 

 기본적으로 랭체인을 문서를 보고 설치했다면 랭체인에서 체인을 구성하는 언어인 LCEL을 알아야 합니다.

 

LCEL(LangChain Expression Language): 랭체인 표현 언어로 직역하는데, 말 그대로 랭체인을 사용하기 위해서 알아야하는 표현 방식입니다. 해당 언어의 이점을 알아보면 다음과 같습니다.

 1. Streaming support: 기술적으로 빠르게 토큰을 읽고 아웃풋까지 낼 수 있도록 도와줍니다.

 2. Async support: 프로토타입과 실제 프로덕트 환경에서도 동일한 코드를 사용할 수 있습니다.

 3. Optimized parallel execution: 검색을 활용하는 부분도 있는데(ex. RAG) 이 때 병렬로 실행할 수 있습니다.

 4. Retries and fallbacks: 재시도 또는 대체하는 작업을 수행할 수 있습니다.

 5. Access intermediate results: 복잡한 체인의 경우에 최종 출력 값이 나오기 전에 중간 결과를 확인할 수 있습니다.

 6. Input and output schemas: 출력은 Pydentic 또는 JSON 스키마를 제공해서 입출력 검증에 사용가능합니다. 특히 Lang Serve에 중요합니다.

 7. Seamless LangSmith tracing integration: LangSmith에 자동으로 기록되어 체인 과정에 일어나는 일을 추적할 수 있습니다.

 8. Seamless LangServe deployment integration: LangServe로 쉽게 배포가 가능합니다.

 

 LCEL 을 활용해서 LLM 기반 체인을 구성해서 작업을 단순화하고 최적화하는 겁니다. 실제 서비스에까지 활용이 가능하니 아주 좋은 프레임워크인 것 같습니다.

 

 

---

 먼저 Basic example이 등장하는데요. prompt -> model -> output parser 순서대로 진행합니다.

 

 아래의 코드는 모듈을 위 과정을 나타내는데요. ChatPromptTemplate.from_template 함수를 사용해 인자에 prompt를 설정합니다. 이 때 {topic}은 가장 아래의 chain.invoke({"topic": "ice cream"})에서 입력한 "ice cream"을 받습니다. 

 

 ChatOpenAI 함수에서 model을 설정합니다. 이 과정을 실행하려면 openai API키를 환경변수로 설정해두어야 합니다. 

 StrOutputParser()로 output parser를 선언해줍니다.

 

 마지막으로 chain을 앞서 만든 prompt, model, output_parser로 만들어 줍니다.

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

prompt = ChatPromptTemplate.from_template("{주제}에 대한 재미난 짧은 유머를 들려줘.")
model = ChatOpenAI(model="gpt-4")
output_parser = StrOutputParser()

chain = prompt | model | output_parser

chain.invoke({"주제": "컴퓨터"})


$ '왜 컴퓨터는 항상 차가워?\n- 윈도우(window)가 계속 열려있어서요.'

 

 

 

 

기본예제)

---

1. prompt

 아래의 코드는 프롬프트 템플릿의 베이스 버전을 의미합니다. '주제' 같은 변수들을 받고, 이러한 변수를 받아서 LLM이 이해할 수 있는 형식의 프롬프트로 변환합니다. 그리고 LLM이나 Chatmodel에게 전달하게 됩니다.

prompt_value = prompt.invoke({"주제": "컴퓨터"})
prompt_value

$ ChatPromptValue(messages=[HumanMessage(content='컴퓨터에 대한 재미난 짧은 유머를 들려줘.')])

 

 

2. Model

 위의 prompt_value는 이제 모델에 입력으로 전달됩니다. 

message = model.invoke(prompt_value)
message

$ AIMessage(content='왜 컴퓨터가 노래를 못할까요?\n\n- 왜냐하면 그들의 Windows가 깨졌기 때문입니다!')

 

 

 아래의 예시는 ChatOpenAI 모듈이 아닌 LLM에 해당하는 OpenAI 모듈에 프롬프트를 날린 결과입니다. 모델을 변경하기가 쉽다는걸 알 수 있습니다.

from langchain_openai.llms import OpenAI

llm = OpenAI(model="gpt-3.5-turbo-instruct")
llm.invoke(prompt_value)

$ '\n\n컴퓨터가 바다로 간 이유는?\n너무 많은 메모리를 차지해서 물이 들어가기 때문이야!'

 

 

3. Ouput parser

 모델의 출력을 파싱하여 제공합니다. prompt, model 단계와 마찬가지로 invoke() 함수로 출력결과를 얻습니다. StrOutputParser()에 아무지정을 하지 않았기 때문에 텍스트만 가져오는 것으로 출력이 마무리 됩니다.

output_parser.invoke(message)
$ '왜 컴퓨터가 노래를 못할까요?\n\n- 왜냐하면 그들의 Windows가 깨졌기 때문입니다!'

 

 

4. Entire Pipeline

 1~3 까지의 과정을 나타낸 그림입니다. 한번 보고 상기하면 좋을 것 같습니다.

 

 

기본예제)

---

 RAG Search Example는 Retrieval-Augmented Generation 를 사용합니다. 프롬프트로 하는 검색에 있어서 검색을 하여 보강해서 결과를 생성하는 방법입니다. 좀 더 깊이 있는 답변을 기대할 수 있고, 일반 모델보다 더 정확하고 상세한 정보를 제공할 수 있는 장점이 있습니다.

 

# Requires:
# pip install langchain docarray tiktoken

from langchain_community.vectorstores import DocArrayInMemorySearch
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_openai.chat_models import ChatOpenAI
from langchain_openai.embeddings import OpenAIEmbeddings

vectorstore = DocArrayInMemorySearch.from_texts(
    ["해리슨은 강남에서 일한다.", "곰은 꿀먹기를 좋아한다."],
    embedding=OpenAIEmbeddings(),
)
retriever = vectorstore.as_retriever()

template = """검색정보를 기반으로 질문에 대답해.:
{검색정보}

질문: {질문}
"""
prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI()
output_parser = StrOutputParser()

setup_and_retrieval = RunnableParallel(
    {"검색정보": retriever, "질문": RunnablePassthrough()}
)
chain = setup_and_retrieval | prompt | model | output_parser

chain.invoke("해리슨은 어디에서 일해?")

$ '답변: 해리슨은 강남에서 일합니다.'

 이슈가 있었는데 chain.invoke()하는 부분에서 발생했었습니다. 입력을 임베딩으로 변환하는 부분까지는 잘 되었는데, 이후에 전달하는 과정에서 인자의 누락이 발생한다고 합니다. 구글링을 통해 pydentic 버전을 명시해 새로 설치하여 해결했습니다.(디버깅링크)

 

 

 

 

 

 

요약)

 1. RunnableParallel 객체를 두 항목으로 생성합니다.

 2. "검색정보"는 검색정보를 포함하고 있고, "질문은" 사용자의 원래 질문을 포함하고 있습니다.

 3. 사용자가 입력한 "질문"과 검색된 문서인 "검색정보"를 프롬프트로 구성하고 PromptValue를 출력합니다.

 4. ChatOpenAI() 모델에 프롬프트를 전달합니다.

 5. 결과를 파싱하여 최종 결과를 얻습니다.

 

728x90
반응형

'NLP' 카테고리의 다른 글

reading LangChain docs (3)  (0) 2024.01.17
reading LangChain docs (2)  (0) 2024.01.17
BertSum 뉴스 추출 요약 모델  (0) 2022.09.15
트위터 데이터 수집 (a.k.a twitterscraper)  (0) 2020.02.05
비정형 데이터 - Doc2Vec  (0) 2019.12.19

댓글