04. RAPTOR: Ringkasan Konteks Panjang

Instalasi

pip install -qU langchain umap-learn scikit-learn langchain_community tiktoken langchain-openai langchainhub chromadb langchain-anthropic

RAPTOR: Recursive Abstractive Processing for Tree-Organized Retrieval

Makalah RAPTOR menyajikan pendekatan yang menarik untuk pengindeksan dan pengambilan dokumen.

  • Leafs adalah kumpulan dokumen awal.

  • Leafs di-embedding dan dikelompokkan.

  • Kemudian, klaster tersebut meringkas informasi antara dokumen-dokumen serupa pada tingkat yang lebih tinggi (lebih abstrak).

Proses ini dilakukan secara rekursif, membentuk "pohon" yang mengarah dari dokumen asli (leafs) ke ringkasan yang lebih abstrak.

Ini dapat diterapkan pada berbagai skala; leafs dapat berupa:

  • Potongan teks dalam satu dokumen (seperti yang ditunjukkan dalam makalah)

  • Seluruh dokumen (seperti yang ditunjukkan di bawah ini)

Dengan LLM yang memiliki konteks lebih panjang, ini dapat diterapkan pada seluruh dokumen.

Dokumen

Mari kita terapkan ini pada dokumen LCEL di LangChain.

Dalam hal ini, setiap dokumen adalah halaman web unik dari dokumen LCEL.

Konteksnya bervariasi dari kurang dari 2.000 token hingga lebih dari 10.000 token.

Ini menjelaskan proses mengekstraksi data teks dari dokumen web, menghitung jumlah token dalam teks, dan memvisualisasikannya dalam bentuk histogram.

  • Pustaka tiktoken digunakan untuk menghitung jumlah token dalam sebuah string berdasarkan nama encoding yang diberikan.

  • Kelas RecursiveUrlLoader digunakan untuk memuat dokumen web secara rekursif dari URL yang ditentukan. Dalam proses ini, BeautifulSoup digunakan untuk mengekstrak teks dari dokumen HTML.

  • Dokumen dimuat dari beberapa URL dan semua data teks dikumpulkan ke dalam satu daftar.

  • Untuk setiap teks dokumen, fungsi num_tokens_from_string dipanggil untuk menghitung jumlah token, dan hasilnya disimpan dalam daftar.

  • Matplotlib digunakan untuk memvisualisasikan distribusi jumlah token yang dihitung sebagai histogram. Histogram menunjukkan jumlah token di sumbu x dan frekuensi dokumen dengan jumlah token tersebut di sumbu y.

  • Histogram membantu memahami distribusi data dan memberikan representasi visual dari distribusi panjang data teks

from langchain_community.document_loaders.recursive_url_loader import RecursiveUrlLoader
from bs4 import BeautifulSoup as Soup
import tiktoken
import matplotlib.pyplot as plt


def num_tokens_from_string(string: str, encoding_name: str) -> int:
    # Mengembalikan jumlah token dalam string yang diberikan.
    encoding = tiktoken.get_encoding(encoding_name)
    num_tokens = len(encoding.encode(string))
    return num_tokens


# Memuat dokumen LCEL
url = "https://python.langchain.com/docs/expression_language/"
loader = RecursiveUrlLoader(
    url=url, max_depth=20, extractor=lambda x: Soup(x, "html.parser").text
)
docs = loader.load()

# Memuat dokumen LCEL menggunakan PydanticOutputParser (di luar dokumen LCEL utama)
url = "https://python.langchain.com/docs/how_to/output_parser_structured/"
loader = RecursiveUrlLoader(
    url=url, max_depth=1, extractor=lambda x: Soup(x, "html.parser").text
)
docs_pydantic = loader.load()

# Memuat dokumen LCEL menggunakan Self Query (di luar dokumen LCEL utama)
url = "https://python.langchain.com/docs/how_to/self_query/"
loader = RecursiveUrlLoader(
    url=url, max_depth=1, extractor=lambda x: Soup(x, "html.parser").text
)
docs_sq = loader.load()

# Teks dokumen
docs.extend([*docs_pydantic, *docs_sq])
docs_texts = [d.page_content for d in docs]

# Menghitung jumlah token untuk setiap dokumen
counts = [num_tokens_from_string(d, "cl100k_base") for d in docs_texts]

# Membuat histogram jumlah token
plt.figure(figsize=(10, 6))
plt.hist(counts, bins=30, color="blue", edgecolor="black", alpha=0.7)
plt.title("Jumlah Token dalam Dokumen LCEL")
plt.xlabel("Jumlah Token")
plt.ylabel("Frekuensi")
plt.grid(axis="y", alpha=0.75)

# Menampilkan histogram
plt.show()

Ini menjelaskan proses mengurutkan dan menggabungkan teks dokumen serta menghitung jumlah token:

  1. Urutkan dokumen (docs) berdasarkan kunci "source" di metadata.

  2. Balikkan urutan daftar dokumen yang telah diurutkan.

  3. Gabungkan konten dokumen yang dibalik menggunakan pemisah khusus ("\n\n\n --- \n\n\n").

  4. Hitung jumlah token dari konten yang digabungkan menggunakan fungsi num_tokens_from_string, dan cetak hasilnya. Dalam hal ini, digunakan model "cl100k_base".

# Menggabungkan teks dokumen.
# Urutkan dokumen berdasarkan metadata sumber.
d_sorted = sorted(docs, key=lambda x: x.metadata["source"])
d_reversed = list(reversed(d_sorted))  # Balikkan urutan dokumen yang sudah diurutkan.
concatenated_content = "\n\n\n --- \n\n\n".join(
    [
        # Menggabungkan konten dari dokumen yang diurutkan secara terbalik.
        doc.page_content
        for doc in d_reversed
    ]
)
print(
    "Jumlah token dalam semua konteks: %s"  # Cetak jumlah token dalam semua konteks.
    % num_tokens_from_string(concatenated_content, "cl100k_base")
)
Jumlah token dalam semua konteks: 23675

Proses membagi teks menggunakan RecursiveCharacterTextSplitter dijelaskan sebagai berikut:

  1. Tetapkan variabel chunk_size_tok untuk menentukan ukuran setiap potongan teks menjadi 2000 token.

  2. Gunakan metode from_tiktoken_encoder dari RecursiveCharacterTextSplitter untuk menginisialisasi pembagi teks. Pada langkah ini, atur chunk_size dan chunk_overlap menjadi 0.

  3. Panggil metode split_text dari pembagi teks yang telah diinisialisasi untuk membagi teks yang telah digabungkan dan disimpan di variabel concatenated_content. Hasil dari pembagian ini disimpan dalam variabel texts_split.

# Kode untuk membagi teks
from langchain_text_splitters import RecursiveCharacterTextSplitter

chunk_size_tok = 2000  # Mengatur ukuran potongan dalam token.
# Inisialisasi pemisah teks karakter rekursif. Atur ukuran potongan dan tumpang tindih menggunakan pengode token.
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    chunk_size=chunk_size_tok, chunk_overlap=0
)
texts_split = text_splitter.split_text(
    concatenated_content
)  # Membagi teks yang diberikan.

Model

Anda dapat menguji berbagai model, termasuk seri Claude3 yang baru.

Jangan lupa untuk mengatur API key terkait:

  • OPENAI_API_KEY untuk OpenAI, dan ANTHROPIC_API_KEY jika menggunakan Anthropic.

  • Gunakan ChatOpenAI atau ChatAnthropic + OpenAIEmbeddings untuk mengimplementasikan model chatbot.

  1. Instansiasi OpenAIEmbeddings untuk menginisialisasi fungsionalitas embedding dari OpenAI.

  2. Gunakan ChatOpenAI atau ChatAnthropic, atur temperature menjadi 0, dan inisialisasi model chatbot.

from dotenv import load_dotenv

load_dotenv()
True

Menggunakan Cache Embedding.

from langchain_openai import OpenAIEmbeddings
from langchain.embeddings import CacheBackedEmbeddings
from langchain.storage import LocalFileStore

store = LocalFileStore("./cache/")

# Membuat instance embeddings.
embd = OpenAIEmbeddings(model="text-embedding-3-small", disallowed_special=())

cached_embeddings = CacheBackedEmbeddings.from_bytes_store(
    embd, store, namespace=embd.model
)

Inisialisasi model.

from langchain_anthropic import ChatAnthropic
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.callbacks.base import BaseCallbackHandler


class StreamCallback(BaseCallbackHandler):
    def on_llm_new_token(self, token: str, **kwargs):
        print(token, end="", flush=True)


# Inisialisasi model ChatOpenAI. Model yang digunakan adalah "gpt-4-turbo-preview".
model = ChatOpenAI(
    model="gpt-4-turbo-preview",
    temperature=0,
    streaming=True,
    callbacks=[StreamCallback()],
)

# Inisialisasi model ChatAnthropic. Temperature diatur ke 0, dan model yang digunakan adalah "claude-3-opus-20240229".
# model = ChatAnthropic(temperature=0, model="claude-3-opus-20240229")

Membangun Tree

Pendekatan klastering dalam pembangunan pohon mencakup beberapa ide menarik:

GMM (Gaussian Mixture Model):

  • Memodelkan distribusi titik data di berbagai klaster.

  • Mengevaluasi Bayesian Information Criterion (BIC) model untuk menentukan jumlah klaster yang optimal.

UMAP (Uniform Manifold Approximation and Projection)

  • Mendukung klastering.

  • Mengurangi dimensi dari data berdimensi tinggi.

  • UMAP membantu menekankan pengelompokan alami berdasarkan kesamaan titik data.

Klastering Lokal dan Global

  • Digunakan untuk menganalisis data dalam berbagai skala.

  • Secara efektif menangkap pola-pola halus dan pola yang lebih luas dalam data.

Penetapan Ambang Batas

  • Diterapkan dalam konteks GMM untuk menentukan keanggotaan klaster.

  • Berdasarkan distribusi probabilitas (menetapkan titik data ke ≥ 1 klaster).

Kode untuk GMM dan pengaturan ambang batas berasal dari Sarthi et al., seperti yang disebutkan di dua sumber berikut:

Penghargaan penuh diberikan kepada kedua penulis.

Fungsi global_cluster_embeddings menggunakan UMAP untuk melakukan reduksi dimensi global dari embeddings.

  1. Mengurangi dimensi embeddings yang dimasukkan ke dimensi yang ditentukan (dim) menggunakan UMAP.

  2. n_neighbors menentukan jumlah tetangga yang dipertimbangkan untuk setiap titik; jika tidak disediakan, pengaturan defaultnya adalah akar kuadrat dari jumlah embeddings.

  3. metric menentukan metrik jarak yang akan digunakan oleh UMAP.

  4. Hasilnya, embeddings dikembalikan sebagai array numpy dengan dimensi yang dikurangi sesuai dengan yang ditentukan.

from typing import Dict, List, Optional, Tuple

import numpy as np
import pandas as pd
import umap
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from sklearn.mixture import GaussianMixture

RANDOM_SEED = 42  # Nilai seed yang tetap untuk reprodusibilitas

### --- Menambahkan komentar dan dokumentasi dari kode yang dikutip di atas --- ###


def global_cluster_embeddings(
    embeddings: np.ndarray,
    dim: int,
    n_neighbors: Optional[int] = None,
    metric: str = "cosine",
) -> np.ndarray:
    """
    Melakukan reduksi dimensi global dari embeddings menggunakan UMAP.

    Parameter:
    - embeddings: Input embeddings dalam bentuk array numpy.
    - dim: Dimensi target untuk ruang yang direduksi.
    - n_neighbors: Opsional; jumlah tetangga yang dipertimbangkan untuk setiap titik.
                   Default ke akar kuadrat dari jumlah embeddings jika tidak diberikan.
    - metric: Metrik jarak yang digunakan untuk UMAP.

    Mengembalikan:
    - Array numpy dari embeddings yang direduksi ke dimensi yang ditentukan.
    """
    if n_neighbors is None:
        n_neighbors = int((len(embeddings) - 1) ** 0.5)
    return umap.UMAP(
        n_neighbors=n_neighbors, n_components=dim, metric=metric
    ).fit_transform(embeddings)

Mengimplementasikan fungsi local_cluster_embeddings untuk melakukan reduksi dimensi lokal pada data embedding.

Embedding yang dimasukkan direduksi ke dimensi yang ditentukan (dim) menggunakan UMAP. Selama proses reduksi dimensi, jumlah tetangga (num_neighbors) dan metrik jarak (metric) untuk setiap titik digunakan sebagai parameter. Akhirnya, embedding yang telah direduksi dimensinya dikembalikan sebagai array numpy.

def local_cluster_embeddings(
    embeddings: np.ndarray, dim: int, num_neighbors: int = 10, metric: str = "cosine"
) -> np.ndarray:
    """
    Melakukan reduksi dimensi lokal pada embedding, biasanya digunakan setelah klastering global.

    Parameter:
    - embeddings: Input embedding dalam bentuk array numpy.
    - dim: Jumlah dimensi target untuk ruang yang direduksi.
    - num_neighbors: Jumlah tetangga yang dipertimbangkan untuk setiap titik.
    - metric: Metrik jarak yang digunakan untuk UMAP.

    Mengembalikan:
    - Array numpy dari embedding yang direduksi ke dimensi yang ditentukan.
    """
    return umap.UMAP(
        n_neighbors=num_neighbors, n_components=dim, metric=metric
    ).fit_transform(embeddings)

Fungsi get_optimal_clusters digunakan untuk menentukan jumlah klaster optimal berdasarkan data embedding yang diberikan. Proses ini dilakukan dengan menghitung Bayesian Information Criterion (BIC) menggunakan Gaussian Mixture Model (GMM).

  • Embedding yang dimasukkan disediakan dalam bentuk array numpy.

  • max_clusters menentukan jumlah maksimum klaster yang dipertimbangkan. Nilai default adalah 50.

  • random_state menggunakan nilai tetap untuk reprodusibilitas.

  • Fungsi ini mencoba beberapa jumlah klaster untuk embedding yang dimasukkan dan menghitung nilai BIC untuk setiap jumlah.

  • Jumlah klaster dengan nilai BIC minimum ditentukan sebagai jumlah klaster optimal dan dikembalikan.

  • Fungsi ini dapat digunakan untuk secara otomatis menemukan jumlah klaster yang paling baik menjelaskan data dalam masalah klastering.

def get_optimal_clusters(
    embeddings: np.ndarray, max_clusters: int = 50, random_state: int = RANDOM_SEED
) -> int:
    """
    Menentukan jumlah klaster optimal menggunakan Gaussian Mixture Model (GMM)
    dan Bayesian Information Criterion (BIC).

    Parameter:
    - embeddings: Input embedding dalam bentuk array numpy.
    - max_clusters: Jumlah maksimum klaster yang dipertimbangkan.
    - random_state: Seed untuk reprodusibilitas.

    Mengembalikan:
    - Bilangan bulat yang mewakili jumlah klaster optimal yang ditemukan.
    """
    max_clusters = min(
        max_clusters, len(embeddings)
    )  # Mengatur jumlah maksimum klaster ke nilai yang lebih kecil antara max_clusters dan panjang embedding
    n_clusters = np.arange(1, max_clusters)  # Menghasilkan rentang dari 1 hingga jumlah klaster maksimum
    bics = []  # Daftar untuk menyimpan skor BIC
    for n in n_clusters:  # Mengulangi setiap jumlah klaster
        gm = GaussianMixture(
            n_components=n, random_state=random_state
        )  # Inisialisasi Gaussian Mixture Model
        gm.fit(embeddings)  # Melatih model pada embedding
        bics.append(gm.bic(embeddings))  # Menambahkan skor BIC dari model yang telah dilatih ke dalam daftar
    return n_clusters[np.argmin(bics)]  # Mengembalikan jumlah klaster dengan skor BIC terendah

Fungsi GMM_cluster mengelompokkan embedding menggunakan Gaussian Mixture Model (GMM) berdasarkan ambang batas probabilitas.

  • Embedding yang dimasukkan disediakan dalam bentuk array numpy.

  • threshold adalah ambang batas probabilitas yang digunakan untuk menetapkan embedding ke klaster tertentu.

  • random_state adalah nilai seed yang digunakan untuk reprodusibilitas hasil.

  • Fungsi get_optimal_clusters dipanggil untuk menentukan jumlah klaster yang optimal.

  • Berdasarkan jumlah klaster yang ditentukan, Gaussian Mixture Model diinisialisasi dan dilatih pada embedding yang dimasukkan.

  • Probabilitas penetapan klaster untuk setiap embedding dihitung, dan jika probabilitas melebihi ambang batas yang diberikan, embedding ditetapkan ke klaster.

  • Akhirnya, fungsi ini mengembalikan tuple yang berisi label klaster untuk embedding dan jumlah klaster yang ditentukan.

def GMM_cluster(embeddings: np.ndarray, threshold: float, random_state: int = 0):
    """
    Mengelompokkan embedding menggunakan Gaussian Mixture Model (GMM) berdasarkan ambang batas probabilitas.

    Parameter:
    - embeddings: Input embedding dalam bentuk array numpy.
    - threshold: Ambang batas probabilitas untuk menetapkan embedding ke klaster.
    - random_state: Seed untuk reprodusibilitas.

    Mengembalikan:
    - Tuple yang berisi label klaster dan jumlah klaster yang ditentukan.
    """
    n_clusters = get_optimal_clusters(embeddings)  # Menentukan jumlah klaster yang optimal.
    # Inisialisasi Gaussian Mixture Model.
    gm = GaussianMixture(n_components=n_clusters, random_state=random_state)
    gm.fit(embeddings)  # Melatih model pada embedding.
    probs = gm.predict_proba(
        embeddings
    )  # Memprediksi probabilitas setiap embedding untuk setiap klaster.
    # Memilih klaster dengan probabilitas yang melebihi ambang batas sebagai label.
    labels = [np.where(prob > threshold)[0] for prob in probs]
    return labels, n_clusters  # Mengembalikan label dan jumlah klaster.

Fungsi perform_clustering melakukan reduksi dimensi, klastering global menggunakan Gaussian Mixture Model (GMM), dan klastering lokal di dalam setiap klaster global pada embedding, serta mengembalikan hasil klastering.

  1. Reduksi Dimensi:

    • Fungsi ini melakukan reduksi dimensi pada embedding yang dimasukkan menggunakan UMAP, mengurangi dimensi embedding ke dimensi yang ditentukan (dim).

  2. Klastering Global:

    • Fungsi ini melakukan klastering global pada embedding berdimensi rendah menggunakan GMM. Penetapan klaster ditentukan berdasarkan ambang batas probabilitas yang diberikan (threshold).

  3. Klastering Lokal:

    • Dalam setiap klaster global, dilakukan klastering lokal lebih lanjut. Berdasarkan hasil klastering global, reduksi dimensi dan klastering GMM diterapkan lagi pada embedding di dalam setiap klaster global.

  4. Output Akhir:

    • Fungsi ini menetapkan ID klaster global dan lokal ke semua embedding dan mengembalikan daftar yang berisi ID klaster untuk setiap embedding dalam urutan munculnya. Daftar ini mencakup array ID klaster yang sesuai dengan setiap embedding.

Fungsi ini menggabungkan klastering global dan lokal untuk data berdimensi tinggi, memungkinkan hasil klastering yang lebih rinci dan memberikan analisis yang lebih efektif dari struktur data yang kompleks.

def perform_clustering(
    embeddings: np.ndarray,
    dim: int,
    threshold: float,
) -> List[np.ndarray]:
        """
    Secara berurutan melakukan reduksi dimensi, klastering menggunakan Gaussian Mixture Model (GMM),
    dan klastering lokal dalam setiap klaster global pada embedding.

    Parameter:
    - embeddings: Input embedding dalam bentuk array numpy.
    - dim: Dimensi target untuk reduksi UMAP.
    - threshold: Ambang batas probabilitas untuk menetapkan embedding ke klaster di GMM.

    Mengembalikan:
    - Daftar array numpy yang berisi ID klaster untuk setiap embedding.
    """
    if len(embeddings) <= dim + 1:
        # Hindari klastering jika datanya tidak cukup.
        return [np.array([0]) for _ in range(len(embeddings))]
        return [np.array([0]) for _ in range(len(embeddings))]

    # Reduksi dimensi global
    reduced_embeddings_global = global_cluster_embeddings(embeddings, dim)
    # Klastering global
    global_clusters, n_global_clusters = GMM_cluster(
        reduced_embeddings_global, threshold
    )

    all_local_clusters = [np.array([]) for _ in range (len(embeddings))]
    total_clusters = 0

    # Lakukan klastering lokal dalam setiap klaster global
    for i in range(n_global_clusters):
        # Ekstrak embedding yang termasuk dalam klaster global saat ini
        global_cluster_embeddings_ = embeddings[
            np.array([i in gc for gc in global_clusters])
        ]

        if len(global_cluster_embeddings_) == 0:
            lanjutkan
        if len(global_cluster_embeddings_) <= dim + 1:
            # Tangani klaster kecil dengan penetapan langsung
            local_clusters = [np.array([0]) for _ in global_cluster_embeddings_]
            n_local_clusters = 1
        else:
            # Lakukan reduksi dimensi lokal dan klastering
            reduced_embeddings_local = local_cluster_embeddings(
                global_cluster_embeddings_, dim
            )
            local_clusters, n_local_clusters = GMM_cluster(
                reduced_embeddings_local, threshold
            )

        # Tetapkan ID klaster lokal, menyesuaikan untuk klaster yang sudah diproses
        for j in range(n_local_clusters):
            local_cluster_embeddings_ = global_cluster_embeddings_[
                np.array([j in lc for lc in local_clusters])
            ]
            indices = np.where(
                (embeddings == local_cluster_embeddings_[:, None]).all(-1)
            )[1]
            for idx in indices:
                all_local_clusters[idx] = np.append(
                    all_local_clusters[idx], j + total_clusters
                )

        total_clusters += n_local_clusters

    return all_local_clusters

Mengimplementasikan fungsi embed untuk menghasilkan embedding dari daftar dokumen teks.

  • Menerima daftar dokumen teks (texts) sebagai input.

  • Menggunakan metode embed_documents dari objek embd untuk menghasilkan embedding dari dokumen teks.

  • Mengonversi embedding yang dihasilkan ke dalam format numpy.ndarray dan mengembalikannya.

def embed(texts):
    # Menghasilkan embedding dari daftar dokumen teks.
    #
    # Fungsi ini mengasumsikan adanya objek `embd`, yang memiliki metode `embed_documents` 
    # yang menerima daftar teks dan mengembalikan embedding-nya.
    #
    # Parameter:
    # - texts: List[str], daftar dokumen teks yang akan di-embed.
    #
    # Mengembalikan:
    # - numpy.ndarray: Array embedding untuk dokumen teks yang diberikan.
    text_embeddings = embd.embed_documents(
        texts
    )  # Menghasilkan embedding dari dokumen teks.
    text_embeddings_np = np.array(text_embeddings)  # Mengonversi embedding ke dalam array numpy.
    return text_embeddings_np  # Mengembalikan array numpy yang telah di-embed.

Fungsi embed_cluster_texts menghasilkan embedding dan klasterisasi dari daftar teks, mengembalikan pandas.DataFrame yang berisi teks asli, embedding yang sesuai, dan label klaster yang diberikan.

  1. Menghasilkan embedding untuk daftar teks yang diberikan.

  2. Melakukan klasterisasi berdasarkan embedding yang dihasilkan menggunakan fungsi perform_clustering yang telah didefinisikan sebelumnya.

  3. Menginisialisasi pandas.DataFrame untuk menyimpan hasilnya.

  4. Menyimpan teks asli, daftar embedding, dan label klaster di DataFrame.

  5. Fungsi ini menggabungkan pembuatan embedding dan klasterisasi dalam satu langkah, memudahkan analisis struktural dan pengelompokan data teks.

def embed_cluster_texts(texts):
    """
    Menghasilkan embedding dan klasterisasi dari daftar teks, mengembalikan DataFrame yang berisi teks, embedding-nya, dan label klaster.

    Fungsi ini menggabungkan pembuatan embedding dan klasterisasi dalam satu langkah. Fungsi ini mengasumsikan adanya fungsi `perform_clustering` yang sudah didefinisikan sebelumnya, yang melakukan klasterisasi pada embedding.

    Parameter:
    - texts: List[str], daftar dokumen teks yang akan diproses.

    Mengembalikan:
    - pandas.DataFrame: DataFrame yang berisi teks asli, embedding-nya, dan label klaster yang diberikan.
    """
    text_embeddings_np = embed(texts)  # Menghasilkan embedding
    cluster_labels = perform_clustering(
        text_embeddings_np, 10, 0.1
    )  # Melakukan klasterisasi pada embedding
    df = pd.DataFrame()  # Menginisialisasi DataFrame untuk menyimpan hasil
    df["text"] = texts  # Menyimpan teks asli
    df["embd"] = list(text_embeddings_np)  # Menyimpan embedding sebagai daftar dalam DataFrame
    df["cluster"] = cluster_labels  # Menyimpan label klaster
    return df

Fungsi fmt_txt memformat dokumen teks dari pandas DataFrame menjadi satu string.

  1. Menerima DataFrame sebagai parameter input, di mana DataFrame harus berisi kolom 'text' dengan dokumen teks yang akan diformat.

  2. Semua dokumen teks digabungkan menggunakan pemisah khusus ("--- --- \n --- ---") dan dikembalikan sebagai satu string.

  3. Fungsi ini mengembalikan satu string yang berisi dokumen teks yang telah digabungkan.

def fmt_txt(df: pd.DataFrame) -> str:
    """
    Memformat dokumen teks di dalam DataFrame menjadi satu string.

    Parameter:
    - df: DataFrame yang berisi dokumen teks yang akan diformat dalam kolom 'text'.

    Mengembalikan:
    - Satu string di mana semua dokumen teks digabungkan menggunakan pemisah khusus.
    """
    unique_txt = df["text"].tolist()  # Mengonversi semua teks di kolom 'text' menjadi daftar
    return "--- --- \n --- --- ".join(
        unique_txt
    )  # Menggabungkan dokumen teks dengan pemisah khusus dan mengembalikannya

Proses ini melibatkan pembuatan embedding dari data teks, pengelompokan (clustering), dan pembuatan ringkasan untuk setiap klaster.

  1. Embedding dihasilkan untuk daftar teks yang diberikan, dan klasterisasi berdasarkan kemiripan dilakukan. Hasilnya adalah sebuah DataFrame bernama df_clusters, yang berisi teks asli, embedding, dan informasi penugasan klaster.

  2. Untuk mempermudah penanganan penugasan klaster, entri DataFrame diperluas. Setiap baris diubah menjadi DataFrame baru yang berisi teks, embedding, dan klaster.

  3. Identifikator klaster unik diekstraksi dari DataFrame yang diperluas, dan teks untuk setiap klaster diformat untuk menghasilkan ringkasan. Ringkasan ini disimpan dalam DataFrame df_summary, yang berisi ringkasan setiap klaster, tingkat detail yang ditentukan, dan identifikator klaster.

  4. Akhirnya, fungsi ini mengembalikan tuple yang berisi dua DataFrame. DataFrame pertama mencakup teks asli, embedding, dan informasi penugasan klaster, sedangkan DataFrame kedua berisi ringkasan untuk setiap klaster, tingkat detail, dan identifikator klaster.

def embed_cluster_summarize_texts(
    texts: List[str], level: int
) -> Tuple[pd.DataFrame, pd.DataFrame]:
    """
    Melakukan embedding, klasterisasi, dan peringkasan untuk daftar teks.
    Fungsi ini pertama-tama menghasilkan embedding untuk teks-teks tersebut, melakukan klasterisasi berdasarkan kemiripan,
    kemudian memperluas penugasan klaster untuk mempermudah pemrosesan, dan merangkum konten dalam setiap klaster.

    Parameter:
    - texts: Daftar dokumen teks yang akan diproses.
    - level: Parameter integer yang menentukan kedalaman atau tingkat detail dari pemrosesan.

    Mengembalikan:
    - Tuple yang berisi dua DataFrame:
      1. DataFrame pertama (`df_clusters`) berisi teks asli, embedding, dan penugasan klaster.
      2. DataFrame kedua (`df_summary`) berisi ringkasan untuk setiap klaster, tingkat detail yang ditentukan, dan identifikator klaster.
    """

    # Membuat embedding dan klasterisasi teks, membuat DataFrame dengan kolom 'text', 'embd', dan 'cluster'.
    df_clusters = embed_cluster_texts(texts)

    # Siapkan untuk memperluas DataFrame untuk mempermudah manipulasi klaster.
    expanded_list = []

    # Memperluas entri DataFrame menjadi pasangan dokumen-klaster untuk mempermudah pemrosesan.
    for index, row in df_clusters.iterrows():
        for cluster in row["cluster"]:
            expanded_list.append(
                {"text": row["text"], "embd": row["embd"], "cluster": cluster}
            )

    # Membuat DataFrame baru dari daftar yang diperluas.
    expanded_df = pd.DataFrame(expanded_list)

    # Mengambil identifikator klaster unik untuk diproses.
    all_clusters = expanded_df["cluster"].unique()

    print(f"--Generated {len(all_clusters)} clusters--")

    # Ringkasan
    template = """Berikut adalah subset dari dokumen bahasa ekspresi LangChain.

    Bahasa ekspresi LangChain menyediakan cara untuk menyusun chain di LangChain.

    Silakan berikan ringkasan terperinci dari dokumen yang diberikan.

    Dokumen:
    {context}
    """
    prompt = ChatPromptTemplate.from_template(template)
    chain = prompt | model | StrOutputParser()

    # Memformat teks dalam setiap klaster untuk peringkasan.
    summaries = []
    for i in all_clusters:
        df_cluster = expanded_df[expanded_df["cluster"] == i]
        formatted_txt = fmt_txt(df_cluster)
        summaries.append(chain.invoke({"context": formatted_txt}))

    # Membuat DataFrame untuk menyimpan ringkasan, klasternya, dan levelnya.
    df_summary = pd.DataFrame(
        {
            "summaries": summaries,
            "level": [level] * len(summaries),
            "cluster": list(all_clusters),
        }
    )

    return df_clusters, df_summary

Fungsi ini mengimplementasikan proses embedding, klasterisasi, dan peringkasan data teks secara rekursif.

  1. Menghasilkan embedding, melakukan klasterisasi, dan merangkum daftar teks yang diberikan, serta menyimpan hasilnya di setiap langkah.

  2. Fungsi ini berjalan hingga mencapai level rekursi maksimum yang ditentukan, atau hingga jumlah klaster unik menjadi 1.

  3. Pada setiap langkah rekursif, hasil klasterisasi dan peringkasan di level saat ini dikembalikan dalam bentuk DataFrame dan disimpan dalam kamus hasil.

  4. Jika level saat ini lebih kecil dari level rekursi maksimum dan jumlah klaster unik lebih besar dari 1, hasil peringkasan di level saat ini digunakan sebagai input teks untuk level berikutnya, dan fungsi ini dipanggil secara rekursif.

  5. Akhirnya, fungsi ini mengembalikan kamus yang berisi DataFrame klaster dan DataFrame ringkasan untuk setiap level.

def recursive_embed_cluster_summarize(
    texts: List[str], level: int = 1, n_levels: int = 3
) -> Dict[int, Tuple[pd.DataFrame, pd.DataFrame]]:
    """
    Secara rekursif menghasilkan embedding, melakukan klasterisasi, dan merangkum teks hingga level yang ditentukan 
    tercapai atau jumlah klaster unik menjadi 1, menyimpan hasil di setiap level.

    Parameter:
    - texts: List[str], daftar teks yang akan diproses.
    - level: int, level rekursi saat ini (dimulai dari 1).
    - n_levels: int, kedalaman maksimum rekursi.

    Mengembalikan:
    - Dict[int, Tuple[pd.DataFrame, pd.DataFrame]], kamus di mana kunci adalah level rekursi,
      dan nilainya adalah tuple yang berisi DataFrame klaster dan DataFrame ringkasan pada level tersebut.
    """
    results = {}  # Kamus untuk menyimpan hasil di setiap level

    # Melakukan embedding, klasterisasi, dan peringkasan pada level saat ini
    df_clusters, df_summary = embed_cluster_summarize_texts(texts, level)

    # Menyimpan hasil dari level saat ini
    results[level] = (df_clusters, df_summary)

    # Menentukan apakah rekursi lebih lanjut mungkin dan berarti
    unique_clusters = df_summary["cluster"].nunique()
    if level < n_levels and unique_clusters > 1:
        # Gunakan ringkasan sebagai teks input untuk level rekursi berikutnya
        new_texts = df_summary["summaries"].tolist()
        next_level_results = recursive_embed_cluster_summarize(
            new_texts, level + 1, n_levels
        )

        # Menggabungkan hasil level berikutnya ke dalam kamus hasil saat ini
        results.update(next_level_results)

    return results
# Jumlah total dokumen
len(docs_texts)
3
# Pembangunan Tree
leaf_texts = docs_texts  # Atur teks dokumen sebagai teks leaf
results = recursive_embed_cluster_summarize(
    leaf_texts, level=1, n_levels=3
)  # Lakukan embedding, klastering, dan summarization secara rekursif untuk mendapatkan hasil
The provided documents from LangChain cover a wide range of topics related to building applications with language models, including how to parse language model (LLM) responses into structured formats, and how to implement "self-querying" retrieval systems. Here's a detailed summary of the key points from each document:

### How to Use Output Parsers to Parse an LLM Response into Structured Format

- **Purpose**: To structure the unstructured text output from language models into a more structured format.
- **Output Parsers**: Classes designed to structure LLM responses. They must implement methods for getting format instructions and parsing the LLM response into a structured format. An optional method, "Parse with prompt," allows for retrying or fixing the output using information from the prompt.
- **PydanticOutputParser**: Demonstrated as the main type of output parser, which uses Pydantic models to define the desired data structure for the LLM's output.
- **Implementation**: The document outlines how to set up a parser, inject instructions into a prompt template, and use a query to prompt a language model to populate the data structure. It also explains how output parsers are part of the LangChain Expression Language (LCEL) and support various invoke and stream methods.
- **Streaming**: While all parsers support streaming interfaces, only certain parsers can stream through partially parsed objects. The document provides examples of using the SimpleJsonOutputParser for streaming partial outputs.

### How to Do "Self-Querying" Retrieval

- **Purpose**: To enhance retrieval systems by allowing them to query themselves using structured queries generated from natural language inputs.
- **Self-Querying Retriever**: A retriever that uses a query-constructing LLM chain to write a structured query and then applies that structured query to its underlying VectorStore. This enables the retriever to use semantic similarity comparisons and extract filters from the user query on the metadata of stored documents.
- **Implementation**: The document provides a step-by-step guide on creating a self-querying retriever using a Chroma vector store and documents containing movie summaries. It explains how to instantiate the retriever by providing metadata field information and a description of the document contents.
- **Testing**: Examples demonstrate how to use the self-querying retriever to fetch documents based on various queries and filters, including specifying the number of documents to fetch (k).
- **Constructing from Scratch with LCEL**: For more custom control, the document outlines how to reconstruct the self-querying retriever from scratch, including creating a query-construction chain and a structured query translator.

These documents provide comprehensive guides for developers looking to leverage LangChain for building applications that interact with language models, offering insights into structuring LLM outputs and enhancing retrieval systems with self-querying capabilities

Dalam makalah ini, collapsed tree retrieval melaporkan kinerja terbaik.

Ini melibatkan perataan struktur tree menjadi satu layer, diikuti dengan menerapkan k-nearest neighbor (kNN) search ke semua node secara bersamaan.

Berikut adalah proses sederhana untuk melakukannya.

Proses membangun penyimpanan yang telah di-vektorisasi dan dapat dicari menggunakan Chroma vector store untuk data teks dijelaskan.

  1. Awalnya, salin data teks yang disimpan di leaf_texts ke dalam variabel all_texts.

  2. Iterasi melalui data results, ekstraksi teks yang diringkas di setiap level, dan tambahkan ke all_texts.

  3. Konversikan nilai-nilai dalam kolom summaries dari DataFrame setiap level menjadi daftar dan ekstrak mereka.

  4. Tambahkan ringkasan yang diekstraksi ke all_texts.

  5. Bangun Chroma vector store menggunakan semua data teks (all_texts).

  6. Panggil Chroma.from_texts untuk membuat vektor dari data teks dan membuat vector store.

  7. Untuk membuat penyimpanan vektor yang dibuat dapat dicari, inisialisasi retriever dengan memanggil metode .as_retriever().

Melalui proses ini, data teks, termasuk ringkasan dari berbagai level, di-vektorisasi dan Chroma vector store yang dapat dicari dibangun berdasarkan hal tersebut.

from langchain_community.vectorstores import FAISS

# Inisialisasi all_texts dengan menyalin leaf_texts.
all_texts = leaf_texts.copy()

# Iterasi melalui hasil untuk mengekstrak ringkasan dari setiap level dan menambahkannya ke all_texts.
for level in sorted(results.keys()):
    # Ekstrak ringkasan dari DataFrame di level saat ini.
    summaries = results[level][1]["summaries"].tolist()
    # Tambahkan ringkasan dari level saat ini ke all_texts.
    all_texts.extend(summaries)

# Sekarang, bangun FAISS vectorstore menggunakan all_texts.
vectorstore = FAISS.from_texts(texts=all_texts, embedding=embd)

Simpan DB secara lokal.

import os

DB_INDEX = "RAPTOR"

# Periksa apakah indeks DB FAISS sudah ada secara lokal. Jika ada, muat, gabungkan dengan vectorstore, lalu simpan.
if os.path.exists(DB_INDEX):
    local_index = FAISS.load_local(DB_INDEX, embd)
    local_index.merge_from(vectorstore)
    local_index.save_local(DB_INDEX)
else:
    vectorstore.save_local(folder_path=DB_INDEX)
# Membuat retriever
retriever = vectorstore.as_retriever()

Mengimplementasikan proses untuk mendefinisikan Retrieval Augmented Generation (RAG) chain dan menangani permintaan untuk contoh kode spesifik.

  1. Mengambil prompt RAG menggunakan hub.pull.

  2. Mendefinisikan fungsi format_docs untuk memformat dokumen. Fungsi ini menggabungkan konten halaman dokumen dan mengembalikannya.

  3. Membangun RAG chain. Chain ini mengambil konteks dari retriever, memformatnya menggunakan fungsi format_docs, dan memproses pertanyaan.

  4. Gunakan RunnablePassthrough() untuk meneruskan pertanyaan sebagaimana adanya.

  5. Chain ini mem-parsing output akhir sebagai string melalui prompt, model, dan StrOutputParser().

  6. Gunakan metode rag_chain.invoke untuk menangani pertanyaan: "How to define a RAG chain? Give me a specific code example."

from langchain import hub
from langchain_core.runnables import RunnablePassthrough

# Membuat prompt
prompt = hub.pull("rlm/rag-prompt")

# Post-processing dokumen
def format_docs(docs):
    # Gabungkan dan kembalikan konten halaman dokumen
    return "\n\n".join(doc.page_content for doc in docs)

# Mendefinisikan RAG chain
rag_chain = (
    # Memformat hasil pencarian dan memproses pertanyaan
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt  # Terapkan prompt
    | model  # Terapkan model
    | StrOutputParser()  # Terapkan string output parser
)
# Menjalankan pertanyaan abstrak
_ = rag_chain.invoke("Jelaskan tema inti dari seluruh dokumen.")
Tema inti dari seluruh dokumen adalah pengembangan aplikasi menggunakan model bahasa, dengan fokus pada tiga aspek utama: transformasi respons model bahasa menjadi format terstruktur, kemampuan "self-querying" untuk pencarian informasi, dan penggunaan bahasa ekspresi LangChain untuk menghubungkan komponen dalam pembuatan aplikasi. Dokumen ini memberikan panduan terperinci bagi pengembang untuk memanfaatkan model bahasa dalam membangun aplikasi yang memerlukan output terstruktur, kemampuan self-querying, dan penggunaan bahasa ekspresif untuk mengintegrasikan berbagai komponen. Ini mencakup implementasi parser output, pembuatan retriever self-querying, dan pengenalan bahasa ekspresi LangChain serta ekosistemnya untuk memfasilitasi pembangunan aplikasi kompleks dengan model bahasa

LangSmith

_ = rag_chain.invoke(
    "Sebutkan dua metode utama yang harus diimplementasikan oleh output parser")
Dua metode utama yang harus diimplementasikan oleh output parser adalah "Get format instructions" dan "Parse". Metode "Get format instructions" mengembalikan sebuah string yang berisi instruksi tentang bagaimana output dari model bahasa harus diformat. Metode "Parse" mengambil sebuah string (yang diasumsikan sebagai respons dari model bahasa) dan menguraikannya menjadi suatu struktur.

LangSmith

_ = rag_chain.invoke(
    "Silakan tulis metode kueri mandiri dan contoh kode")
To create a method for self-querying retrieval and provide an example code, you would use a language model to construct a query based on natural language input, then apply this query to a vector store for document retrieval. The process involves defining metadata for documents, setting up a vector store, and using a language model to generate structured queries.

Example code snippet based on the provided context:

```
from langchain.chroma import Chroma
from langchain_core.documents import Document
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.retrievers.self_query.base import SelfQueryRetriever

# Define documents with metadata
docs = [
    Document(
        page_content="A bunch of scientists bring back dinosaurs and mayhem breaks loose",
        metadata={"year": 1993, "rating": 7.7, "genre": "science fiction"},
    ),
    # Add more documents as needed
]

# Set up the vector store with documents
vectorstore = Chroma.from_documents(docs, OpenAIEmbeddings())

# Define metadata fields for the retriever
metadata_field_info = [
    # Define attributes like genre, year, director, rating
]

# Set up the language model for query construction
llm = ChatOpenAI(temperature=0)

# Instantiate the self-querying retriever
retriever = SelfQueryRetriever.from_llm(
    llm,
    vectorstore,
    "Brief summary of a movie",
    metadata_field_info,
)

# Example query
query_result = retriever.invoke("I want to watch a movie rated higher than 8.5")
This code sets up a self-querying retriever using LangChain, where Chroma is used as the vector store, and ChatOpenAI is the language model for generating structured queries. The SelfQueryRetriever takes natural language input and retrieves documents based on the constructed query and metadata filters.
```

LangSmith

Last updated