# LlamaIndex의 구성 요소란 무엇인가요?

1단원에서 나왔던 도움이 되는 집사 에이전트 알프레드를 기억하시나요?
효과적으로 우리를 도우려면, 알프레드는 우리의 요청을 이해하고 **작업을 완료하는 데 도움이 되는 관련 정보를 준비하고, 찾고, 사용**할 수 있어야 합니다.
이것이 LlamaIndex의 구성 요소가 들어오는 곳입니다.

LlamaIndex에는 많은 구성 요소가 있지만, **우리는 특별히 `QueryEngine` 구성 요소에 집중할 것입니다.**
왜냐고요? 에이전트의 검색 증강 생성(RAG) 툴로 사용할 수 있기 때문입니다.

그럼 RAG란 무엇일까요? LLM은 일반적인 지식을 배우기 위해 엄청난 양의 데이터로 훈련됩니다.
하지만 관련 있고 최신 데이터로 훈련되지 않았을 수 있습니다.
RAG는 데이터에서 관련 정보를 찾고 검색하여 LLM에게 제공함으로써 이 문제를 해결합니다.

![RAG](https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit2/llama-index/rag.png)

이제 알프레드가 어떻게 작동하는지 생각해보세요:

1. 알프레드에게 저녁 파티 계획을 도와달라고 요청합니다
2. 알프레드는 캘린더, 식이 선호도, 과거 성공한 메뉴를 확인해야 합니다
3. `QueryEngine`이 알프레드가 이 정보를 찾고 저녁 파티 계획에 사용하도록 도와줍니다

이것이 `QueryEngine`을 LlamaIndex에서 **에이전트 RAG 워크플로우를 구축하기 위한 핵심 구성 요소**로 만듭니다.
알프레드가 도움이 되기 위해 가정 정보를 검색해야 하는 것처럼, 모든 에이전트는 관련 데이터를 찾고 이해할 수 있는 방법이 필요합니다.
`QueryEngine`은 정확히 이 기능을 제공합니다.

이제 구성 요소에 대해 조금 더 깊이 들어가서 **구성 요소를 결합하여 RAG 파이프라인을 만드는** 방법을 살펴보겠습니다.

## 구성 요소를 사용하여 RAG 파이프라인 만들기

이 노트북을 따라가며 Google Colab에서 코드를 실행해볼 수 있습니다.

RAG 내에는 5가지 주요 단계가 있으며, 이는 구축하는 대부분의 더 큰 어플리케이션의 일부가 됩니다:

1. **로딩**: 데이터가 있는 곳(텍스트 파일, PDF, 다른 웹사이트, 데이터베이스, API)에서 워크플로우로 데이터를 가져오는 것을 의미합니다. LlamaHub는 선택할 수 있는 수백 개의 통합을 제공합니다.
2. **인덱싱**: 데이터를 쿼리할 수 있는 데이터 구조를 만드는 것을 의미합니다. LLM의 경우, 이는 거의 항상 벡터 임베딩을 만드는 것을 의미합니다. 이는 데이터 의미의 수치적 표현입니다. 인덱싱은 속성을 기반으로 맥락적으로 관련된 데이터를 정확하게 찾기 쉽게 만드는 수많은 다른 메타데이터 전략을 의미할 수도 있습니다.
3. **저장**: 데이터가 인덱싱되면 다시 인덱싱할 필요가 없도록 인덱스와 다른 메타데이터를 저장하고 싶을 것입니다.
4. **쿼리**: 주어진 인덱싱 전략에 대해 하위 쿼리, 다단계 쿼리, 하이브리드 전략을 포함하여 LLM과 LlamaIndex 데이터 구조를 사용하여 쿼리할 수 있는 많은 방법이 있습니다.
5. **평가**: 모든 흐름에서 중요한 단계는 다른 전략에 비해 얼마나 효과적인지, 또는 변경사항을 만들 때 확인하는 것입니다. 평가는 쿼리에 대한 응답이 얼마나 정확하고, 신뢰할 수 있고, 빠른지에 대한 객관적인 측정을 제공합니다.

다음으로, 구성 요소를 사용하여 이러한 단계들을 재현하는 방법을 살펴보겠습니다.

### 문서 로딩 및 임베딩

앞서 언급했듯이, LlamaIndex는 자체 데이터 위에서 작동할 수 있지만, **데이터에 접근하기 전에 로드해야 합니다.**
LlamaIndex에 데이터를 로드하는 세 가지 주요 방법이 있습니다:

1. `SimpleDirectoryReader`: 로컬 디렉토리에서 다양한 파일 유형을 위한 내장 로더입니다.
2. `LlamaParse`: PDF 파싱을 위한 LlamaIndex의 공식 툴인 LlamaParse로, 관리형 API로 사용 가능합니다.
3. `LlamaHub`: 모든 소스에서 데이터를 수집하기 위한 수백 개의 데이터 로딩 라이브러리 레지스트리입니다.

더 복잡한 데이터 소스를 위해 LlamaHub 로더와 LlamaParse 파서에 익숙해지세요.

**데이터를 로드하는 가장 간단한 방법은 `SimpleDirectoryReader`를 사용하는 것입니다.**
이 다재다능한 구성 요소는 폴더에서 다양한 파일 유형을 로드하고 LlamaIndex가 작업할 수 있는 `Document` 객체로 변환할 수 있습니다.
`SimpleDirectoryReader`를 사용하여 폴더에서 데이터를 로드하는 방법을 살펴보겠습니다.

```python
from llama_index.core import SimpleDirectoryReader

reader = SimpleDirectoryReader(input_dir="path/to/directory")
documents = reader.load_data()
```

문서를 로드한 후, `Node` 객체라는 더 작은 조각들로 나누어야 합니다.
`Node`는 AI가 작업하기 더 쉬운 원본 문서의 텍스트 청크일 뿐이며, 여전히 원본 `Document` 객체에 대한 참조를 가지고 있습니다.

`IngestionPipeline`은 두 가지 주요 변환을 통해 이러한 노드를 만드는 데 도움이 됩니다.
1. `SentenceSplitter`는 자연스러운 문장 경계에서 문서를 분할하여 관리 가능한 청크로 나눕니다.
2. `HuggingFaceEmbedding`은 각 청크를 수치적 임베딩으로 변환합니다 - AI가 효율적으로 처리할 수 있는 방식으로 의미적 의미를 포착하는 벡터 표현입니다.

이 과정은 검색과 분석에 더 유용한 방식으로 문서를 구성하는 데 도움이 됩니다.

```python
from llama_index.core import Document
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.ingestion import IngestionPipeline

# 변환과 함께 파이프라인 생성
pipeline = IngestionPipeline(
    transformations=[
        SentenceSplitter(chunk_overlap=0),
        HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5"),
    ]
)

nodes = await pipeline.arun(documents=[Document.example()])
```

### 문서 저장 및 인덱싱

`Node` 객체를 만든 후, 검색 가능하게 만들기 위해 인덱싱해야 하지만, 그 전에 데이터를 저장할 곳이 필요합니다.

수집 파이프라인을 사용하고 있으므로, 파이프라인에 벡터 스토어를 직접 연결하여 채울 수 있습니다.
이 경우, 문서를 저장하기 위해 `Chroma`를 사용할 것입니다.

ChromaDB 설치

LlamaHub 섹션에서 소개된 대로, 다음 명령어로 ChromaDB 벡터 스토어를 설치할 수 있습니다:

```bash
pip install llama-index-vector-stores-chroma
```

```python
import chromadb
from llama_index.vector_stores.chroma import ChromaVectorStore

db = chromadb.PersistentClient(path="./alfred_chroma_db")
chroma_collection = db.get_or_create_collection("alfred")
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)

pipeline = IngestionPipeline(
    transformations=[
        SentenceSplitter(chunk_size=25, chunk_overlap=0),
        HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5"),
    ],
    vector_store=vector_store,
)
```

다양한 벡터 스토어에 대한 개요는 LlamaIndex 문서에서 찾을 수 있습니다.

이것이 벡터 임베딩이 들어오는 곳입니다 - 쿼리와 노드를 같은 벡터 공간에 임베딩함으로써 관련 매치를 찾을 수 있습니다.
`VectorStoreIndex`는 일관성을 보장하기 위해 수집 중에 사용한 것과 같은 임베딩 모델을 사용하여 이를 처리합니다.

벡터 스토어와 임베딩에서 이 인덱스를 만드는 방법을 살펴보겠습니다:

```python
from llama_index.core import VectorStoreIndex
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5")
index = VectorStoreIndex.from_vector_store(vector_store, embed_model=embed_model)
```

모든 정보는 `ChromaVectorStore` 객체와 전달된 디렉토리 경로 내에서 자동으로 지속됩니다.

훌륭합니다! 이제 인덱스를 쉽게 저장하고 로드할 수 있으니, 다양한 방법으로 쿼리하는 방법을 탐구해보겠습니다.

### 프롬프트와 LLM으로 VectorStoreIndex 쿼리하기

인덱스를 쿼리하기 전에, 쿼리 인터페이스로 변환해야 합니다. 가장 일반적인 변환 옵션은 다음과 같습니다:

- `as_retriever`: 기본 문서 검색용으로, 유사도 점수가 있는 `NodeWithScore` 객체 리스트를 반환합니다
- `as_query_engine`: 단일 질문-답변 상호작용용으로, 작성된 응답을 반환합니다
- `as_chat_engine`: 여러 메시지에 걸쳐 메모리를 유지하는 대화형 상호작용용으로, 채팅 기록과 인덱싱된 맥락을 사용하여 작성된 응답을 반환합니다

에이전트와 같은 상호작용에 더 일반적이므로 쿼리 엔진에 집중하겠습니다.
응답에 사용할 LLM도 쿼리 엔진에 전달합니다.

```python
from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI

llm = HuggingFaceInferenceAPI(model_name="Qwen/Qwen2.5-Coder-32B-Instruct")
query_engine = index.as_query_engine(
    llm=llm,
    response_mode="tree_summarize",
)
query_engine.query("인생의 의미는 무엇인가요?")
# 인생의 의미는 42입니다
```

### 응답 처리

내부적으로, 쿼리 엔진은 질문에 답하기 위해 LLM만 사용하는 것이 아니라 응답을 처리하기 위한 전략으로 `ResponseSynthesizer`도 사용합니다.
다시 한번, 이것은 완전히 사용자 정의 가능하지만 즉시 잘 작동하는 세 가지 주요 전략이 있습니다:

- `refine`: 검색된 각 텍스트 청크를 순차적으로 거쳐 답변을 생성하고 개선합니다. 이는 Node/검색된 청크당 별도의 LLM 호출을 만듭니다.
- `compact` (기본값): 개선과 유사하지만 미리 청크를 연결하여 더 적은 LLM 호출을 만듭니다.
- `tree_summarize`: 검색된 각 텍스트 청크를 거쳐 답변의 트리 구조를 만들어 상세한 답변을 생성합니다.

저수준 구성 API로 쿼리 워크플로우를 세밀하게 제어하세요. 이 API는 쿼리 프로세스의 모든 단계를 사용자 정의하고 미세 조정하여 정확한 요구사항에 맞출 수 있게 해주며, 워크플로우와도 잘 맞습니다.

언어 모델이 항상 예측 가능한 방식으로 수행되지 않을 수 있으므로, 우리가 얻는 답변이 항상 정확하다고 확신할 수 없습니다. **답변의 품질을 평가**함으로써 이를 처리할 수 있습니다.

### 평가 및 관찰 가능성

LlamaIndex는 **응답 품질을 평가하기 위한 내장 평가 도구**를 제공합니다.
이러한 평가자는 LLM을 활용하여 다양한 차원에서 응답을 분석합니다.
사용 가능한 세 가지 주요 평가자를 살펴보겠습니다:

- `FaithfulnessEvaluator`: 답변이 맥락에 의해 지원되는지 확인하여 답변의 신뢰성을 평가합니다.
- `AnswerRelevancyEvaluator`: 답변이 질문과 관련이 있는지 확인하여 답변의 관련성을 평가합니다.
- `CorrectnessEvaluator`: 답변이 정확한지 확인하여 답변의 정확성을 평가합니다.

에이전트 관찰 가능성과 평가에 대해 더 배우고 싶나요? 보너스 단원 2로 여정을 계속하세요.

```python
from llama_index.core.evaluation import FaithfulnessEvaluator

query_engine = # 이전 섹션에서
llm = # 이전 섹션에서

# 인덱스 쿼리
evaluator = FaithfulnessEvaluator(llm=llm)
response = query_engine.query(
    "미국 혁명에서 뉴욕시에서 어떤 전투가 일어났나요?"
)
eval_result = evaluator.evaluate_response(response=response)
eval_result.passing
```

직접적인 평가 없이도 **관찰 가능성을 통해 시스템이 어떻게 수행되고 있는지에 대한 통찰을 얻을 수 있습니다.**
이는 더 복잡한 워크플로우를 구축하고 각 구성 요소가 어떻게 수행되고 있는지 이해하고 싶을 때 특히 유용합니다.

LlamaTrace 설치

LlamaHub 섹션에서 소개된 대로, 다음 명령어로 Arize Phoenix의 LlamaTrace 콜백을 설치할 수 있습니다:

```bash
pip install -U llama-index-callbacks-arize-phoenix
```

또한 LlamaTrace API 키를 `PHOENIX_API_KEY` 환경 변수로 설정해야 합니다. 다음을 통해 얻을 수 있습니다:
- [LlamaTrace](https://llamatrace.com/login)에서 계정 생성
- 계정 설정에서 API 키 생성
- 아래 코드에서 추적을 활성화하기 위해 API 키 사용

```python
import llama_index
import os

PHOENIX_API_KEY = ""
os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"api_key={PHOENIX_API_KEY}"
llama_index.core.set_global_handler(
    "arize_phoenix",
    endpoint="https://llamatrace.com/v1/traces"
)
```

구성 요소와 사용 방법에 대해 더 배우고 싶나요? 구성 요소 가이드 또는 RAG 가이드로 여정을 계속하세요.

구성 요소를 사용하여 `QueryEngine`을 만드는 방법을 보았습니다. 이제 **`QueryEngine`을 에이전트의 툴로 사용하는** 방법을 살펴보겠습니다!

