ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Langchain vs Llama-index
    Internship_LLM 2024. 2. 6. 17:20
    728x90

    지금까지 Langchain과 Llama-index에 대해서 간단히 알아보았는데, 그렇다면 두 프레임워크를 적절히 비교해보려고 한다.

    랭체인에서 쿼리 부분이 발전된 것이 라마인덱스인데, 발전은 되었지만 랭체인의 범용성이 아직은 더 우수하다고 본다. 랭체인과 라마인덱스의 비교를 통해 프레임워크를 적절하게 선택해보자.

     

    ※ Langchain vs Llama-index

    - 랭체인과 라마인덱스 모두 언어 모델(GPT) 등의 API를 이용해서 다양한 모델들을 쉽게 사용하고 통합할 수 있지만, 랭체인은 ChatOpenAI() 라이브러리를 사용하고, 라마인덱스는 os에서 API key를 직접 받아온다.

     

    Langchain

    from langchain.chat_models import ChatOpenAI
    
    llm = ChatOpenAI(temperatrue=0.1) # api_key 삽입 가능

     

    Llama-index

    import os
    
    # openAI API key
    os.environ['OPENAI_API_KEY'] = 'YOUR OPENAI API KEY'

     

    간단하게 API key를 삽입하면서 쉽게 각 모델 사용이 가능하다.

     

    전체적인 구성의 차이

    모델 설정 - vector store에 저장 - 쿼리 질문이라는 전체적인 구성은 유사하다.

     

    Langchain

    1. 모델 api 선정

    2. document 불러오기(LocalFileStore)

    3. 파일 load

    4. 사용자가 CharacterTextSplitter를 사용해서 적절히 chunk 단위로 parsing

    5. 임베딩을 통해서 vector store 저장

    6. 템플릿과 chain 방식을 선정해서 chain을 구성

    7. 쿼리 전달

     

    Llama-index

    1. api key

    2. 데이터 문서 다운로드

    3. 데이터 문서 load(SimpleDirectoryReader)

    4. model 선정

    5. chunk 단위로 parsing(꼭 필요한 작업은 아님)

    6. vector store의 vector index 정의(VectorStoreIndex)

    7. QueryEngine 빌드 및 Query 시작

     

    이렇게 langchain의 경우에는 chunk 단위로 적절히 parsing하는 작업을 해야 메모리의 효율성을 보장할 수 있지만, chain과 템플릿을 통해서 사용자가 원하는 방식으로 형성이 가능하다.

    하지만, Llama-index의 경우에는 모델 통합이나 템플릿과 chain보다는 다른 부분에 집중을 하고 있는데, vector store에 chunk 단위의 parsing 작업이 없어도 VectorStoreIndex로 적절히 잘 보관되어 인덱스의 효율성을 보장하고, 쿼리엔진으로 쿼리 자체에 집중하는 모습을 보인다.

     

    이제 그렇다면, vector store의 저장모습을 살펴보자.

     

    langchain

    # CharacterTextSplitter : 사용자가 지정한 문자를 기준으로 문서를 분할한다.
    splitter = CharacterTextSplitter.from_tiktoken_encoder(
        separator='\n',         # 해당 문자 기준으로 문서 분할
        chunk_size=600,         # 분할된 한 문서의 최대 chunk 크기를 지정
        chunk_overlap=100,      # 문서 분할 시 앞뒤 문서의 100자를 중복으로 추가하여 생성. 문맥상 적절하지 않은 부분에서 문서 분할 문제 해결
    )
    # UnstructedFileLoader는 text files, powerpoints, html, pdfs, images 등 여러 가지 형식의 파일 지원에 편리함
    loader = UnstructuredFileLoader("./files/운수 좋은날.txt")
    # load_and_split : 파일 로딩과 동시에 분할 진행. splitter 파라미터로 전달하고, 분할된 문서를 반환한다.
    docs = loader.load_and_split(text_splitter=splitter)
    
    # Embedding : text에 적절한 점수를 의미별로 부여하는 방식이고, 자연어를 vector로 변환하는 작업
    # Embedding 된 문서는 vectorstore에 저장됨.
    # Retriever에서 쿼리와 연관성이 높은 문서들을 vectorstore 로부터 찾아오고, 문서를 LLM에 전달할 프롬프트에 포함시켜서 정확도가 높은 답변을 기대함.
    embeddings = OpenAIEmbeddings()
    
    # Cache Memory를 사용해서 임베딩을 효율적으로 처리함.
    # CacheBackedEmbeddings : Embedding 객체와 캐시가 저장되어 있는 위치를 파라미터로 전달. embedding 객체가 호출될 일이 있으면, cached_embeddings를 사용
    # 이미 캐시되어 있다면 저장된 캐시를 사용, 그렇지 않다면 embedding을 진행하여 캐시를 생성함.
    cached_embeddings = CacheBackedEmbeddings.from_bytes_store(embeddings, cache_dir)
    vectorstore = FAISS.from_documents(docs, cached_embeddings)
    retriver = vectorstore.as_retriever()

     

     

     

    Llama-index

    # node index를 chunk 단위로 parsing
    node_parser = SimpleNodeParser.from_defaults(chunk_size=512)
    nodes = node_parser.get_nodes_from_documents(documnets)
    
    # vectorstore의 vector index를 정의
    vector_index = VectorStoreIndex(nodes)

     

    주석부분을 제외하더라도 차이가 나는 것을 볼 수 있다.

    langchain의 chunk 단위 split, embedding, vectorstore 저장과 Llama-index의 split, vectorstore 저장 모습을 보면 랭체인에서 라마인덱스로 발전하면서 어느정도로 vectorstore에서 인덱스에 힘을 주었는지를 알 수 있다.

     

    langchain의 프롬프트와 체인 기능

    prompt = ChatPromptTemplate.from_messages(
        [
            (
                "system",
                """
                You are a helpful assistant. 
                Answer questions using only the following context. 
                If you don't know the answer just say you don't know, don't make it up:
                \n\n
                {context}",
                """
            ),
            ("human", "{question}"),
        ]
    )
    
    # Stuff : 관련 문서를 모두 prompt에 채워 넣어 전달
    # Map reduce : 각 문서를 요약하고, 요약된 문서를 기반으로 최종 요약본을 만들어냄. 문서 요약에서 속도가 느림
    # Refine : 문서들을 순회하며 중간 답변을 생성, 이것을 반복하면서 답변을 정제함. 양질의 답변을 만들어 낼 수 있고, 속도와 비용면에서 단점이 있지만, 결과물이 뛰어남.
    # Map re-rank : 각 문서에 대해 각 답변을 만들어 내고, 점수를 부여한다. 가장 높은 점수의 답변을 최종 답변으로 설정함.
    
    # Stuff 방식으로 chain을 구성
    chain = (
        {
            "context": retriver,
            "question": RunnablePassthrough(),         # 사용자의 질문이 {question}에 그대로 들어가게 됨.
        }
        | prompt
        | llm
    )

     

    이러한 템플릿 프롬프트와 체인 기능을 통해서 langchain 만으로 쉽게 우리가 원하는 모습을 구현할 수 있게 된다. chain과 chain의 연결도 가능하여 손쉽게 여러가지 구성을 할 수 있다.

     

    Query

    langchain

    # 전달된 쿼리를 retriever에 전달하고, template의 {context}에 넣어준다.
    result = chain.invoke("김첨지는 학생을 어디로 데려다 주었나?")

     

    Llama-index

    # QueryEngine을 빌드하고 Query를 시작
    query_engine = vector_index.as_query_engine()
    response_vector = query_engine.query("What did the author do growing up?")

     

    쿼리 엔진을 이용해서 쿼리를 작동시키는 것이 라마인덱스라면, chain에서 invoke 함수나 predict 함수를 사용해서 retriever에 전달하고, template에서 모습을 보여주는 것이 랭체인이다.

     

    이렇게 랭체인과 라마인덱스를 비교해보았는데, 전체적인 구성차이는 큰 차이가 없다. 하지만, 서비스의 특성상 집중해야 하는 것이 무엇이냐에 따라 적절하게 프레임워크를 선택해야 할 것이다.

    단, 랭체인이 선호되는 이유는 개발자 커뮤니티에서 다양한 예제들이 널려 있다는 것이다. 또한, 프로젝트 초기 단계에서 방향성을 설정하는 데 있어서 유연성과 확장성이 좋아 적절하게 모델을 조작할 수 있다는 것이 엄청난 장점이라고 생각된다.

    728x90

    'Internship_LLM' 카테고리의 다른 글

    LLM 기법(3) - Llama-index  (2) 2024.02.06
    LLM 기법(2) - Langchain  (0) 2024.02.05
    LLM 기법 (1)  (0) 2024.02.01
    LLaMA 논문 리뷰  (2) 2024.01.31
    LLM Parameters  (0) 2024.01.26
Designed by Tistory.