Carpe diem

NLP 관련 석사 과정 재학 중 (2022.03 ~ )

AI

Llama Index 살펴보기

Cori 2024. 3. 11. 12:07

 

해당 포스트는 해외블로그 Medium에 게시된 포스트 및 Llama 공식 문서 내용을 정리 한 것이다. 


Llama Index 왜 쓰는데 ?  Llama Index 사용 이유 !  

여러 LLM (Large Language Model)들은 위키피디아, 소스 코드 등과 같은 대량의 공개 데이터에 대해 사전 훈련되어 있다.

하지만, 이런 LLM들은 보통 사용자의 데이터나 해당 문제에 특화된 데이터로는 훈련되어 있지 않다.

 

본인의 데이터로 LLM을 세밀하게 조정할 수도 있지만, 파라미터 수가 기본 Billion (B) 이상인 요즘 언어 모델을 새로 

학습하는 것은 상당한 비용이 소모된다. 비용적인 문제로 인해, LLM이 최근 정보를 학습하도록 업데이트하는 것은 쉽지 않으며,

이러한 문제를 해결하기 위해 여러 방법론이 제안되었다.

 

이 중 하나가 RAG (Retriever Augmented Generation) 방법론인데, Llama Index는 RAG를 위한 여러 기능을 제공한다. 

RAG Architecture

 

* Llama Index와 Langchain 개념이 서로 헷갈릴 수 있는데, 둘 다 LLM을 보다 잘 활용하기 위해 등장한 도구라 볼 수 있다. 

차이점을 들자면 Llama Index는 RAG 구현 (특히 Retriever)에 초점을 두고 있고, LangChain은 LLM과 외부 API 연동에

보다 초점을 두고 있다고 보면 된다. 

Llama Index 제공 기능 

Llama Index는 다음과 같은 기능을 제공한다. 

 

  # 1. API, PDF, SQL등을 포함한 여러 소스에서 데이터를 쉽게 가져올 수 있도록 Connector를 제공한다.  

  # 2. 데이터 인덱스를 통해 LLM이 손쉽고 효율적으로 사용자 요구 사항을 처리할 수 있다. 

  # 3. Engine을 통해 내 데이터에 언어적 접근을 가능하게 해 준다. 

    - Query Engine: 강력한 Retrieve를 통해 Knowledge-Augmented된 데이터 추출   

    - Chat Engine: 보유하고 있는 데이터들과 멀티 메시지 상호작용 가능한 대화형 인터페이스 제공  

  # 4. Langchain, Flask, Docker를 비롯한 여러 API와 상호작용할 수 있다. 

Llama Index 관련 용어

Llama Index를 사용하기 전에 알아두면 좋은 용어들은 다음과 같다. 

 

1. Document

2. Node

3. Index 

4. Query 

5. Router 

6. Agent 

 

이제부터 차례대로 살펴보자.

 

1. Document 

Document는 PDF, API 혹은 데이터베이스에서 검색한 데이터와 같은 모든 데이터 소스를 일컫는 컨테이너다.

수동으로 구축할 수도 있고, Llama Index에서 제공하는 'Data Loader'를 통해 자동으로 생성할 수도 있다. 

Document는 우리가 사용하고자 할 텍스트 데이터 이외에도 metadata, relationships 같은 정보를 저장한다. 

2. Node

텍스트나 이미지 등 여러 형태로 구성된 Document를 Chunk size 단위로 나눈 것을 Node라 한다. 

Document와 유사하게, 이들은 다른 node 간의 relationship, metadata 정보를 가진다.

 

* 같은 Document로부터 나뉘어진 Node들은 같은 메타데이터를 가짐 ! 

 

노드는 크게 3가지 방법 (Document Parsing, Self Define, Index 활용)으로 얻을 수 있다. 

# Case 1. Document Parsing 

from llama_index.text_splitter import SentenceSplitter 
from llama_index.node_parser import SentenceWindowNodeParser 

splitter = SentenceSplitter(
    chunk_size=512, 
    chunk_overlap=20,   # number of characters to overlap between two chunks for preserving the semantic context in subsequent chunks 
)
nodes = splitter.get_nodes_from_documents(documents)

node_parser = SentenceWindowNodeParser.from_defaults(
    window_size=3,   # how many sentences on either side to capture
    window_metadata_key='window',   # the metadata key that holds the window of surrounding sentnces
    original_text_metadata_key='original_sentence',   # the metadatakey that holds the original sentnce
)
window_nodes = node_parser.get_nodes_from_documents(documents)

 

# Case 2. Self Define 

from llama_index.schema import TextNode, NodeRelationship, RelatedNodeInfo

node1 = TextNode(text='<text_chunk>', id_='<node_id>')
node2 = TextNode(text='<text_chunk>', id_='<node_id>')

node1.relationships[NodeRelationship.NEXT] = RelatedNodeInfo(node_id=node2.node_id)
node2.relationships[NodeRelationship.PREVIOUS] = RelatedNodeInfo(node_id=node1.node_id)

nodes = [node1, node2]

 

# Case 3. Using Index 

from llama_index.retrievers import VectorIndexRetriever

index = VectorStoreIndex.from_documents(documents)

 

3. Index 

Document로 구성된 데이터 구조로, LLM에 의해 쿼리하는 것이 가능하도록 설계되었다. 

Index를 통해, 우리의 미숙한 쿼리 전략이 보완될 수 있으며 대표적으로 VectorStore Index와 Summary Index가 있다. 

 

1) VectorStore Index 

입력으로 받은 Document들을 Node로 분리한 후, 모든 Node에 대해 벡터 임베딩을 생성한다. 이를 통해 우리는

LLM에 쿼리를 던질 수 있다. VectorStore Index는 사용자 LLM의 API를 활용하여 텍스트를 모두 임베딩으로 변환한다.

from llama_index import VectorStoreIndex

doc_index = VectorStoreIndex.from_documents(documents)
node_index = VectorStoreIndex(nodes)

 

2) Summary Index 

Document의 텍스트를 요약하려는 쿼리에 가장 적합한 간단한 형태의 인덱스이다.

모든 문서를 단순 저장하고, 쿼리 엔진에 모두 반환합니다.

 

4. Query

LLM을 호출하는 Prompt로, 응답을 얻기 위한 질문이나 요약을 위한 요청 혹은 복잡한 요구 사항이 될 수 있다. 

모든 쿼리의 기본은 QueryEngine으로부터 시작된다. 

query_engine = index.as_query_engine()
response = query_engine.query(
    "Write an email to the user given their background information."
)

 

Querying은 크게 3가지 단계 (Retrieve, Post-process, Response Synthesis)로 구성된다. 

 

Step 1. Retrieve

- Index로부터 사용자 쿼리와 가장 관련 있는 Document를 찾아서 반환한다. 

- retriever가 관련 정보를 추출하는데 가장 많이 사용하는 방법은 'top-k' semantic retrieval 방식이다. 

from llama_index.retrievers import VectorIndexRetriever

index = VectorStoreIndex.from_documents(documents)

# configure retriever
retriever = VectorIndexRetriever(
    index=index,
    similarity_top_k=10,
)

 

Step 2. Postprocessing

- 앞선 Retrieve 단계에서 추출된 노드들을 재순위화, 변형, 필터링한다. 

- 선택적인 단계이며, 특정 키워드가 첨부된 메타데이터를 필요로 하는 경우 주로 사용한다. 

 

Step 3. Response synthesis

- 사용자 쿼리, 가장 유사한 데이터 그리고 사용자 프롬프트가 합쳐져 (RAG) LLM 모델에 전달되고, 응답을 반환하다. 

from llama_index import get_response_synthesizer 

response_synthesizer = get_response_synthesizer()

 

5. Routers

지식 기반으로, 관련 Context를 추출하기 위해 어떤 Retriever가 사용될 지 결정한다. 

RouterRetriever 클래스는 쿼리를 실행하기 위해 하나 혹은 하나 이상의 retriever 후보군을 선택한다. 

Router는 쿼리와 각 후보군의 metadata에 기반해 최적의 옵션을 제공하기 위해 selector를 사용한다. 

from llama_index.query_engine.router_query_engine import RouterQueryEngine
from llama_index.selectors.pydantic_selectors import PydanticSingleSelector
from llama_index.tools.query_engine import QueryEngineTool

list_tool = QueryEngineTool.from_defaults(
    query_engine=list_query_engine,
    description="Useful for summarization questions related to the data source",
)

vector_tool = QueryEngineTool.from_defaults(
    query_engine=vector_query_engine,
    description="Useful for retrieving specific context related to the data source",
)

query_engine = RouterQueryEngine(
    selector=PydanticSingleSelector.from_defaults(),
    query_engine_tools=[
        list_tool,
        vector_tool,
    ],
)
query_engine.query("<query>")

 

6. Agent

자동 추론 및 의사 결정 엔진으로, 시용자 입력을 받아들이고 해당 쿼리를 실행하기 위해 내부적으로 결정을 내린다. 

Agent가 제공하는 주요 기능은 다음과 같다.

 

# Func 1. 복잡한 질문을 세부적으로 분할 

# Func 2. 사용할 외부 도구 선택 + 도구를 호출하기 위한 매개변수 도출 

# Func 3. 일련의 태스크 집합 설계

# Func 4. 이전에 완료된 태스크를 메모리 모듈에 저장 

 

Llama Index 내의 많은 요소들은 agentic하다 할 수 있으며, 데이터에 대한 특정 사용 사례를 돕기 위해 자동으로 의사결정한다. 

 

Ref. 

https://docs.llamaindex.ai/en/stable/