ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • LangChain 과 RAG
    LLM 2024. 9. 13. 09:16

     

    LangChain이란?

     

     

    LangChain은 대규모 언어 모델(LLM)을 활용한 애플리케이션 개발을 위한 프레임워크로, 

    AI 모델의 능력을 극대화하고, 개발 과정을 간소화하여 강력하고 유연한 AI 애플리케이션을 만들 수 있게 해준다.

     

     

    📌 AI 어플리케이션 프레임워크 랭체인 : python 웹 프레임워크 장고

     

     

    LangChain 의 장점

     

    1. 모듈성: LangChain은 다양한 컴포넌트를 제공하여 개발자가 필요에 따라 조합하고 커스터마이즈 가능
      1. LLM Loader VectorStore
      2. OpenAI Anthropic Google Local
    2. 통합성: 다양한 AI 모델, 데이터 소스, 그리고 외부 도구들과의 쉬운 통합을 지원
    3. 효율성: 복잡한 AI 로직을 간단한 체인으로 구현할 수 있어 개발 시간과 비용을 절감
    4. 확장성: 소규모 프로젝트부터 대규모 엔터프라이즈 솔루션까지 다양한 규모의 애플리케이션 개발에 적합

     

    위와 같은 장점들 덕분에 LangChain 은 LLM 어플리케이션 개발 케이스 중 하나인 RAG 를 구현하는데에도 특화되어있다.

     

    등장 이후 굉장히 빠르게 성장해서 django 의 github 스타 수를 넘어섰다👍

     

     

     

    https://github.com/Neulhan/llm-session

     

    GitHub - Neulhan/llm-session

    Contribute to Neulhan/llm-session development by creating an account on GitHub.

    github.com

     

     


     

    그럼 직접 랭체인을 설치해보자👌

     

    랭체인 설치

    pip install langchain
    pip install langchain-openai

     

     

    OpenAI key 등록하기

    매번 코드에 OpenAI Key 를 등록하는게 귀찮으니까

     

    ▼ mac

    echo $SHELL

     

    /bin/zsh 이면 → zsh     /bin/bash → bash

     

     

    mac > zsh

    vi ~/.zshrc
    # 파일 하단에 입력
    export OPENAI_API_KEY='sk-proj-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

     

    터미널 껐다가 켜기 (vscode 나 pycharm 은 터미널 삭제하고 새로 만들기)

     

     

    mac > bash

    vi ~/.bashrc
    # 파일 하단에 입력
    export OPENAI_API_KEY='sk-proj-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

     

    터미널 껐다가 켜기 (vscode 나 pycharm 은 터미널 삭제하고 새로 만들기)

     

     

    windows

    • Windows 검색에서 "시스템 환경 변수 편집"을 검색하여 실행
    • "환경 변수" 버튼을 클릭
    • "사용자 변수" 또는 "시스템 변수" 섹션에서 "새로 만들기" 를 클릭
    • 변수 이름을 OPENAI_API_KEY로, 변수 값을 해당 키로 설정

     

     

    만약, 이걸 안 하면 매번 이렇게 api key 를 써줘야 한다🫠

    llm = ChatOpenAI(model="gpt-4o-mini", api_key="sk-proj-xxxxxxxxxxxxxxxx")
    llm = ChatOpenAI(model="gpt-4o-mini")

     


     

    번역기 튜토리얼1

     

     

     

    from langchain_openai import ChatOpenAI
    from langchain_core.messages import HumanMessage, SystemMessage
    
    # LLM 모델, OpenAI 외에 다른 모델도 유사하게 불러와서 쓰면 된다.
    llm = ChatOpenAI(model="gpt-4o")
    
    # messages. LLM 에 들어갈 메세지를 넣어준다.
    # SystemMessage 는 대화 바깥의 메세지. 시스템에게 명령을 입력해두는 것
    # HumanMessage 는 대화에서 인간 측의 메세지
    messages = [
        SystemMessage(content="다음을 영어에서 한국어로 번역하세요."),
        HumanMessage(content="hi!"),
    ]
    
    # LLM 에 message 를 invoke 하면 결과를 받아볼 수 있다.
    result = llm.invoke(messages)
    
    
    print(result.content)

     

     

    번역기 튜토리얼2

    from langchain_openai import ChatOpenAI
    from langchain_core.messages import HumanMessage, SystemMessage
    from langchain_core.output_parsers import StrOutputParser
    
    
    llm = ChatOpenAI(model="gpt-4o")
    
    messages = [
        SystemMessage(content="Translate the following from English into Korean"),
        HumanMessage(content="hi!"),
    ]
    
    # StrOutputParser 는 결과물에서 Str(문자열)만 뽑아준다 
    parser = StrOutputParser()
    
    
    result = llm.invoke(messages)
    print(result) # content='안녕!' additional_kwargs={'refusal': None} response_metadata......
    
    result = parser.invoke(result)
    print(result) # 안녕!

     

     

    번역기 튜토리얼3

     

    from langchain_openai import ChatOpenAI
    from langchain_core.messages import HumanMessage, SystemMessage
    from langchain_core.output_parsers import StrOutputParser
    
    parser = StrOutputParser()
    model = ChatOpenAI(model="gpt-4o")
    messages = [
        SystemMessage(content="Translate the following from English into Korean"),
        HumanMessage(content="hi!"),
    ]
    
    # invoke 를 연쇄적으로 호출하는 경우 LECL(랭체인 표현식 언어) 로 chaining 을 한 다음
    chain = model | parser
    
    # 한 번의 invoke 로 처리할 수 있다. 
    result = chain.invoke(messages)
    
    # 이렇게 invoke 가 가능한 객체를 랭체인에서는 Runnable 이라고 부른다.
    print(result)

     

     

    번역기 튜토리얼4

    from langchain_core.prompts import ChatPromptTemplate
    
    prompt_template = ChatPromptTemplate.from_messages(
        [("system", "Translate the following into {language}:"), 
        ("user", "{text}")]
    )
    
    # Prompt 에 자리(language, text) 만들어두고 invoke 하면 해당 자리에 들어간다
    result = prompt_template.invoke({"language": "Korean", "text": "hi"})
    
    # invoke 가 되기 때문에 Prompt 도 하나의 Runnable 로 취급되는걸 알 수 있다.
    print(result)

     

     

    번역기 튜토리얼5

    from langchain_core.prompts import ChatPromptTemplate
    from langchain_openai import ChatOpenAI
    from langchain_core.messages import HumanMessage, SystemMessage
    from langchain_core.output_parsers import StrOutputParser
    
    prompt = ChatPromptTemplate.from_messages(
        [("system", "Translate the following into {language}:"), ("user", "{text}")]
    )
    llm = ChatOpenAI(model="gpt-4o")
    parser = StrOutputParser()
    
    # prompt, llm, parser 세 개의 Runnable 을 체이닝해서 체인으로 만든 상태
    chain = prompt | llm | parser
    
    # 입력({"language": "Korean", "text": "Hi!"})이 prompt 로, 
    # prompt 결과물을 입력으로 써서 LLM 을 호출, 
    # LLM 결과물을 입력으로 써서 parser 호출
    result = chain.invoke({"language": "Korean", "text": "Hi!"})
    
    # result 에는 최종적으로 parser 의 결과물이 나온다.
    print(result)

     


     

    RAG 퀵스타트

     

    랭체인의 기본적인 동작에 익숙해졌다면 RAG 도 한 번 시작해보자👌

     

    RAG가 뭔데???🤔

     

    LLM 은 자기가 모르는 건 대답하지 못한다.

    그래서 답이 있을만한 문서를 검색해서 같이 주면서 물어봐야 대답하는데, 그걸 RAG(검색증강생성)라고 한다.

     

    RAG를 하기 위해서는 기본 랭체인 패키지 외에도 langchain_community 와 langchain_chroma 를 설치해줘야한다.

    pip install langchain_community langchain_chroma

     

    RAG 튜토리얼1

    Load 와 Split 을 진행해본다.

    from langchain_community.document_loaders import WebBaseLoader
    from langchain_text_splitters import RecursiveCharacterTextSplitter
    
    
    # 랭체인에는 이미 만들어져있는 여러가지 Loader 들이 존재한다. 
    # (PDFLoader, GitLoader, GoogleDriveLoader, NotionDBLoader 등등)
    # 그 중에서 WebBaseLoader 는 웹에서 정보를 Load 해오는 Loader
    loader = WebBaseLoader(
        web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
    )
    
    # loader 는 .load() 라는 일관된 인터페이스를 가지고 있다.
    docs = loader.load()
    
    print(docs)
    
    # 다양한 TextSplitter 중 RecursiveCharacterTextSplitter 는
    # "\n\n" > "\n" > " " > "" 순서로 텍스트 분할을 시도하는 Splitter
    # 아래는 1,000 글자 단위로 split 하되, 200자 까지 겹치는걸 허용한다는 옵션
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
    splits = text_splitter.split_documents(docs)
    
    print()
    print(splits)

     

     

     

    RAG 튜토리얼2

    from langchain_openai import ChatOpenAI
    from langchain_chroma import Chroma
    from langchain_community.document_loaders import WebBaseLoader
    from langchain_openai import OpenAIEmbeddings
    from langchain_text_splitters import RecursiveCharacterTextSplitter
    
    
    llm = ChatOpenAI(model="gpt-4o-mini")
    
    loader = WebBaseLoader(
        web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
    )
    docs = loader.load()
    
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
    splits = text_splitter.split_documents(docs)
    
    # split 된 문서들을 ChromaDB 에 embedding 해서 넣어주는 과정
    # ChromaDB 는 대표적인 오픈소스 벡터 데이터베이스 중 하나
    # 어떤 임베딩모델을 사용할지, 어떤 문서 조각을 저장할지를 정해준다.
    # OpenAIEmbeddings 에 아무 인자도 주지 않으면 "text-embedding-ada-002" 모델이 사용된다
    vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())
    
    # 임베딩을 저장한 DB 를 리트리버로 만들기. 
    retriever = vectorstore.as_retriever()
    
    # 리트리버는 invoke 호출 가능한 Runnable 이다. 체이닝이 가능하다는 뜻
    docs = retriever.invoke("What is Hallucination?")
    
    
    for doc in docs:
        print(f"{doc=}", end="\n\n")

     

    RAG 튜토리얼3

    from langchain_openai import ChatOpenAI
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_chroma import Chroma
    from langchain_community.document_loaders import WebBaseLoader
    from langchain_openai import OpenAIEmbeddings
    from langchain_text_splitters import RecursiveCharacterTextSplitter
    
    
    llm = ChatOpenAI(model="gpt-4o-mini")
    
    loader = WebBaseLoader(
        web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
    )
    docs = loader.load()
    
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
    splits = text_splitter.split_documents(docs)
    
    vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())
    
    # Retrieve and generate using the relevant snippets of the blog.
    retriever = vectorstore.as_retriever()
    
    
    # 대표적인 RAG 프롬프트 템플릿을 한국어로 번역한 프롬프트
    # 영어 버전으로 사용하는게 LLM 입장에서는 알아듣기 편하나, 성능 낮은 모델의 경우 답변이 영어로 나올 수 있음.
    prompt = ChatPromptTemplate.from_template("""
    당신은 질문 답변 작업의 보조자입니다.
    검색된 다음 문맥을 사용하여 질문에 답하세요.
    답을 모른다면 모른다고 말하세요.
    답변은 최대 세 문장으로 간결하게 작성하세요.
    
    Question: %질문들어갈자리%
    
    Context: {context}
    
    Answer: """)
    
    # 딕셔너리 형태로 체이닝을 시켜도 Dictionary 의 Value 를 invoke 해준다. 
    chain = {"context": retriever,} | prompt
    
    result = chain.invoke("What is Hallucination?")
    
    print(result)

     

     

    영어버전 RAG 프롬프트

     

    https://smith.langchain.com/hub/rlm/rag-prompt

    You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.
    Question: {question} 
    Context: {context} 
    Answer:

     

     

    RAG 튜토리얼4

    from langchain_openai import ChatOpenAI
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_chroma import Chroma
    from langchain_community.document_loaders import WebBaseLoader
    from langchain_openai import OpenAIEmbeddings
    from langchain_text_splitters import RecursiveCharacterTextSplitter
    
    
    llm = ChatOpenAI(model="gpt-4o-mini")
    
    loader = WebBaseLoader(
        web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
    )
    docs = loader.load()
    
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
    splits = text_splitter.split_documents(docs)
    
    vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())
    
    retriever = vectorstore.as_retriever()
    
    
    prompt = ChatPromptTemplate.from_template("""
    당신은 질문 답변 작업의 보조자입니다.
    검색된 다음 문맥을 사용하여 질문에 답하세요.
    답을 모른다면 모른다고 말하세요.
    답변은 최대 세 문장으로 간결하게 작성하세요.
    
    Question: %질문들어갈자리%
    
    Context: {context}
    
    Answer: """)
    
    
    def format_docs(docs):
        return "\n\n".join(doc.page_content for doc in docs)
    
    # invoke 가 안 되는 일반 함수도 아래처럼 체이닝이 가능하다. 이를 RunnableLambda 라고 부른다
    chain = {"context": retriever | format_docs} | prompt
    
    result = chain.invoke("What is Hallucination?")
    
    
    print(result)

     

     

    RAG 튜토리얼5

    from langchain_openai import ChatOpenAI
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_chroma import Chroma
    from langchain_community.document_loaders import WebBaseLoader
    from langchain_core.output_parsers import StrOutputParser
    from langchain_core.runnables import RunnablePassthrough
    from langchain_openai import OpenAIEmbeddings
    from langchain_text_splitters import RecursiveCharacterTextSplitter
    
    
    llm = ChatOpenAI(model="gpt-4o-mini")
    
    
    loader = WebBaseLoader(
        web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
    )
    docs = loader.load()
    
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
    splits = text_splitter.split_documents(docs)
    vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())
    
    retriever = vectorstore.as_retriever()
    prompt = ChatPromptTemplate.from_messages([("user", """
    당신은 질문 답변 작업의 보조자입니다. 검색된 다음 문맥을 사용하여 질문에 답하세요. 답을 모른다면 모른다고 말하세요. 답변은 최대 세 문장으로 간결하게 작성하세요.
    
    Question: {question}
    
    Context: {context}
    
    Answer:
    """)])
    
    
    
    def format_docs(docs):
        return "\n\n".join(doc.page_content for doc in docs)
    
    
    # RunnablePassthrough 는 앞서 들어온 값을 그대로 자기 자리에 담는다
    # 이 경우는 "Task decomposition 에 대해서 설명해줘" 라는 입력이 retriever 에도 들어가고, question 에도 들어가는 셈
    rag_chain = (
        {"context": retriever | format_docs, "question": RunnablePassthrough()}
        | prompt
        | llm
        | StrOutputParser()
    )
    
    result = rag_chain.invoke("Task decomposition 에 대해서 설명해줘")
    
    
    print(result)

     


     

    Vector Store : Pinecone

     

    FAISS vs Chroma vs Pinecone

     

    RAG 튜토리얼에서 살펴본 ChromaDB 는 대표적인 오픈소스 벡터데이터베이스로, 튜토리얼에서는 인-메모리 데이터베이스로서 사용되었다. 인-메모리 라는 뜻은 저장된 데이터가 파일시스템에 영구적으로 저장되는 것이 아니라, 메모리 공간에 저장되기 때문에 프로그램 실행이 종료되면서 데이터도 같이 소실 되는 형태다.

     

    튜토리얼에서는 테스트였기 때문에 간단하게 해본거고, 실제 프로덕션에서 사용하려면 벡터 데이터베이스도 파일시스템에 영구적으로 저장될 필요가 있다. 배포까지 고려하면 가장 간단한 방법은 Pinecone이다.

     

     

    회원가입하고 첫 화면

     

     

     

     Create Index 를 눌러주자 (index 는 RDBMS 로 치면 Table 과 비슷한 존재)

     

     

     

     index 이름을 입력하고 Configuration 에서 오른쪽 Setup by model 을 클릭.

    이후 나온 창에서 내가 사용할 임베딩 모델을 골라주면 알아서 Dimensions 와 Metric 을 추천해준다.

    아래에서는 serverless, aws, us-east-1 을 선택해주자 (무료 플랜으로 사용가능한 유일한 옵션🫠)

     

     

     

     

     내 index 가 생성이 되었다. 아래 HOST 에 써있는 주소로 배포도 다 된 상태👍

     

     

     왼쪽 메뉴에서 API Keys 를 눌러서 내 API KEY 를 확인, 복사해둔다. Pinecone 을 사용하기 위해.

     


     

    Pinecone RAG 튜토리얼

     

    📌 환경변수에 API 등록 → PINECONE_API_KEY 위에서 OPENAI_API_KEY 등록 했던거랑 동일하게 진행

     

    pip install -qU langchain-pinecone
    from langchain_openai import ChatOpenAI
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_community.document_loaders import WebBaseLoader
    from langchain_core.output_parsers import StrOutputParser
    from langchain_core.runnables import RunnablePassthrough
    from langchain_openai import OpenAIEmbeddings
    from langchain_text_splitters import RecursiveCharacterTextSplitter
    # import PineconeVectorStore 
    from langchain_pinecone import PineconeVectorStore
    
    
    llm = ChatOpenAI(model="gpt-4o-mini")
    
    loader = WebBaseLoader(
        web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
    )
    docs = loader.load()
    
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
    splits = text_splitter.split_documents(docs)
    
    # 내가 만든 index 의 이름, 사용할 임베딩 모델, 복사해온 api key 를 넣어준다
    # 환경변수에 PINECONE_API_KEY 등록했으면 api_key 는 생략해도 됨
    vectorstore = PineconeVectorStore(
        index_name="my-first-index",
        embedding=OpenAIEmbeddings(),
        pinecone_api_key="e8f49d78-96ee-4231-bb75-b541dbff0d54",
    )
    
    # Pinecone 에 문서를 추가하는 과정, 이 코드가 실행되고 난 뒤 Pinecone 관리 페이지를 다시 들어가보면
    # Documents 가 생성된 것을 확인할 수 있다
    vectorstore.add_documents(documents=splits)
    
    
    # 이후 Retriever 로의 활용 방법은 동일
    retriever = vectorstore.as_retriever()
    prompt = ChatPromptTemplate.from_messages([("user", """
    당신은 질문 답변 작업의 보조자입니다. 검색된 다음 문맥을 사용하여 질문에 답하세요. 답을 모른다면 모른다고 말하세요. 답변은 최대 세 문장으로 간결하게 작성하세요.
    
    Question: {question}
    
    Context: {context}
    
    Answer:
    """)])
    
    
    def format_docs(docs):
        return "\n\n".join(doc.page_content for doc in docs)
    
    
    rag_chain = (
        {"context": retriever | format_docs, "question": RunnablePassthrough()}
        | prompt
        | llm
        | StrOutputParser()
    )
    
    result = rag_chain.invoke("Task decomposition 에 대해서 설명해줘")
    
    
    print(result)

     

     

    LangChain 은 모듈화가 잘 되어있기 때문에, OpenAI 쓰다가 Anthropic 쓰려면 LLM 쪽 코드 몇줄만 바꾸면 그대로 동작한다.

    ChromaDB 쓰다가 Pinecone 쓰려면 vector store 쪽 코드 몇줄만 바꾸면 앞 뒤 코드는 바꿀 필요가 없다.

    WebBaseLoader 쓰다가 PDFLoader 쓰려면 Loader 쪽 코드 몇 줄만 바꾸면 OK

    한 번 코드를 구성해두면 조금씩 바꿔서 쓰기에 편하다👍

     

    위 튜토리얼들은 Langchain 이 할 수 있는 일들의 일부이고..

    FunctionCalling 과 LangGraph 를 통해 LLM Chain 을 더 길게 늘어뜨리고, 다양한 기능을 수행하고, 중간 결과값에 따라서 분기되게끔 해볼 수 있다고 한다..🫠

     

    검색해가면서 원하는 기능을 구현하기 위해서 코드를 이렇게 저렇게 작성해보면 좋을 것 같다.

     

     

    anything LLM도 다운받아 사용해봤는데 재밌었다. 

     

     

    ▼ 여기서 다운로드 받으면 된다. 사용법 쉬움👌

    https://anythingllm.com/download

     

    Download AnythingLLM for Desktop

    Download the ultimate "all in one" chatbot that allows you to use any LLM, embedder, and vector database all in a single application that runs on your desktop. 100% privately.

    anythingllm.com

     

     

     

    'LLM' 카테고리의 다른 글

    프롬프트 엔지니어링  (3) 2024.09.27
Designed by Tistory.