# 08. SelfQueryRetriever

## Self-querying

`SelfQueryRetriever` adalah alat pencarian dengan kemampuan untuk menghasilkan dan menyelesaikan pertanyaan dengan sendirinya. Alat ini mengambil pertanyaan bahasa alami yang diberikan oleh pengguna dan membuat pertanyaan terstruktur menggunakan rantai LLM yang membangun pertanyaan, lalu menerapkan pertanyaan terstruktur ini ke penyimpanan data vektor yang mendasarinya (VectorStore) untuk melakukan pencarian.

Proses ini memungkinkan `SelfQueryRetriever` untuk melakukan lebih dari sekadar membandingkan kueri input pengguna secara semantik dengan konten dokumen yang disimpan; SelfQueryRetriever dapat mengekstrak filter pada metadata dokumen dari kueri pengguna dan menjalankan filter ini untuk menemukan dokumen yang relevan. Hal ini memungkinkannya memberikan hasil yang lebih akurat dan relevan untuk kueri pengguna.

**Catatan**

Daftar pengambil kueri mandiri yang didukung oleh LangChain <https://python.langchain.com/docs/integrations/retrievers/self_query>

## Memulai

Kita akan menggunakan penyimpanan vektor `Chroma` untuk demonstrasi kita. Untuk tutorial ini, kami telah membuat satu set kecil dokumen demo dengan ringkasan film.

Catatan: Anda perlu menginstal paket `lark` untuk menggunakan `SelfQueryRetriever`.

```python
%pip install -qU lark chromadb
```

Buatlah penyimpanan vektor yang memungkinkan pencarian kemiripan berdasarkan deskripsi film dan metadata.

Gunakan kelas `Document` untuk membuat dokumen, daftar objek dokumen yang berisi deskripsi singkat tentang film dan metadatanya. Buat penyematan dokumen menggunakan `OpenAIEmbeddings`. Gunakan metode `Chroma.from_documents` untuk membuat penyimpanan vektor Chroma, `vectorstore`, dari `docs` dan `OpenAIEmbeddings`.

```python
from langchain_community.vectorstores import Chroma
from langchain_core.documents import Document
from langchain_openai import OpenAIEmbeddings


docs = [
    Document(
        page_content="A bunch of scientists bring back dinosaurs and mayhem breaks loose",
        metadata={"year": 1993, "rating": 7.7, "genre": "science fiction"},
    ),
    Document(
        page_content="Leo DiCaprio gets lost in a dream within a dream within a dream within a ...",
        metadata={"year": 2010, "director": "Christopher Nolan", "rating": 8.2},
    ),
    Document(
        page_content="A psychologist / detective gets lost in a series of dreams within dreams within dreams and Inception reused the idea",
        metadata={"year": 2006, "director": "Satoshi Kon", "rating": 8.6},
    ),
    Document(
        page_content="A bunch of normal-sized women are supremely wholesome and some men pine after them",
        metadata={"year": 2019, "director": "Greta Gerwig", "rating": 8.3},
    ),
    Document(
        page_content="Toys come alive and have a blast doing so",
        metadata={"year": 1995, "genre": "animated"},
    ),
    Document(
        page_content="Three men walk into the Zone, three men walk out of the Zone",
        metadata={
            "year": 1979,
            "director": "Andrei Tarkovsky",
            "genre": "thriller",
            "rating": 9.9,
        },
    ),
]
vectorstore = Chroma.from_documents(docs, OpenAIEmbeddings())
```

## Membuat SelfQueryRetriever

Sekarang Anda dapat menginstansiasi retriever. Untuk melakukannya, kita perlu menyediakan terlebih dahulu bidang metadata yang didukung oleh dokumen dan deskripsi singkat tentang konten dokumen.

Gunakan kelas `AttributeInfo` untuk mendefinisikan informasi tentang bidang metadata film

* Genre (`genre`): Jenis string, menunjukkan genre film dan dapat mengambil \['fiksi ilmiah', 'komedi', 'drama', 'thriller', 'romansa', 'aksi', 'animasi'] dan memiliki salah satu nilai berikut
* tahun (`year`): Tipe integer, menunjukkan tahun rilis film.
* stradara (`director`): Tipe string, menunjukkan nama sutradara film.
* rating (`rating`): Tipe bilangan real, menunjukkan peringkat film dalam rentang 1-10.

```python
from langchain.chains.query_constructor.base import AttributeInfo
from langchain.retrievers.self_query.base import SelfQueryRetriever
from langchain_openai import ChatOpenAI

metadata_field_info = [
    AttributeInfo(
        name="genre",
        description="The genre of the movie. One of ['science fiction', 'comedy', 'drama', 'thriller', 'romance', 'action', 'animated']",
        type="string",
    ),
    AttributeInfo(
        name="year",
        description="The year the movie was released",
        type="integer",
    ),
    AttributeInfo(
        name="director",
        description="The name of the movie director",
        type="string",
    ),
    AttributeInfo(
        name="rating", description="A 1-10 rating for the movie", type="float"
    ),
]
```

Tetapkan deskripsi ringkasan singkat tentang film ke variabel document\_content\_description.

```python
# Penjelasan singkat tentang isi dokumen
document_content_description = "Brief summary of a movie"
```

Gunakan metode `SelfQueryRetriever.from_llm()` untuk membuat objek retriever.

* `llm`: model bahasa
* `vectorstore`: Penyimpanan vektor
* `document_content_description`: Deskripsi konten dokumen
* `metadata_field_info`: Informasi bidang metadata

```python
# Tentukan LLM
llm = ChatOpenAI(model="gpt-4-turbo-preview", temperature=0)

# Buat SelfQueryRetriever
retriever = SelfQueryRetriever.from_llm(
    llm,
    vectorstore,
    document_content_description,
    metadata_field_info,
)
```

## Pengujian

Sekarang kita dapat mencoba `retriever` yang telah kita buat!

Panggil metode `invoke` objek `retriever` untuk melakukan pencarian yang difilter.

* Kami mengoper “Saya ingin menonton film dengan rating lebih tinggi dari 8,5” sebagai permintaan pencarian kami, menentukan bahwa kami ingin mencari film dengan rating 8,5 atau lebih tinggi.

```python
# Tentukan hanya filter yang Anda inginkan untuk melihat film dengan peringkat 8,5 atau lebih tinggi.
retriever.invoke("I want to watch a movie rated higher than 8.5")
```

{% code overflow="wrap" %}

```
[Document(metadata={'director': 'Andrei Tarkovsky', 'genre': 'thriller', 'rating': 9.9, 'year': 1979}, page_content='Three men walk into the Zone, three men walk out of the Zone'), Document(metadata={'director': 'Satoshi Kon', 'rating': 8.6, 'year': 2006}, page_content='A psychologist / detective gets lost in a series of dreams within dreams within dreams and Inception reused the idea')]
```

{% endcode %}

Dalam kueri ini, Anda dapat melihat bahwa kami telah menggunakan f`ilter komposit` untuk menentukan kriteria pencarian kami.

Filter komposit: peringkat di atas 8,5, fiksi ilmiah

```python
# Tentukan filter gabungan untuk mencari film fiksi ilmiah dengan peringkat 8,5 atau lebih tinggi.
retriever.invoke("What's a highly rated (above 8.5) science fiction film?")
```

```
[]
```

Kueri ini juga menggunakan filter gabungan untuk menyaring hasil pencarian.

Filter gabungan: 1990 hingga 2005, pilih film tentang gedung pertunjukan, pilih film animasi

```python
# Kueri yang menyebabkan kesalahan
retriever.invoke(
    # Cari film tentang mainan yang dibuat setelah tahun 1990 tetapi sebelum tahun 2005, tentukan filter gabungan dengan kueri bahwa film animasi lebih disukai.
    "What's a movie after 1990 but before 2005 that's all about toys, and preferably is animated"
)
```

{% code overflow="wrap" %}

```
ValueError: Expected operand value to be an int or a float for operator $gt, got 1990
```

{% endcode %}

**Catatan**: Solusi untuk error ini ada di-akhir bagian ini

## Memfilter berdasarkan faktor K

`k` adalah jumlah dokumen yang akan diambil.

Anda juga dapat menggunakan `SelfQueryRetriever` untuk menentukan `k`. Hal ini dapat dilakukan dengan mengoper `enable_limit = True` ke konstruktor.

Buat objek retriever menggunakan kelas `SelfQueryRetriever`.

* `document_content_description`: Deskripsi konten dokumen
* `metadata_field_info`: Informasi bidang metadata
* `enable_limit`: Apakah akan membatasi hasil pencarian

(Metode 1) Dalam kasus berikut, `search_kwargs = {“k”: 2}` untuk secara eksplisit menentukan bahwa 2 hasil pencarian harus dikembalikan.

```python
retriever = SelfQueryRetriever.from_llm(
    llm,  # Menentukan model bahasa (Language Model).
    vectorstore,  # Menentukan penyimpanan vektor (Vector Store).
    document_content_description,  # Menentukan deskripsi konten dokumen.
    metadata_field_info,  # Menentukan informasi bidang metadata.
    enable_limit=True,  # Mengaktifkan fitur pembatasan hasil pencarian.
    search_kwargs={"k": 2},  # Menetapkan nilai k menjadi 2 untuk membatasi hasil pencarian menjadi 2.
)

# Mengajukan pertanyaan tentang dua film yang berkaitan dengan dinosaurus.
retriever.invoke("Apa saja film tentang dinosaurus?")
```

{% code overflow="wrap" %}

```
[Document(metadata={'genre': 'science fiction', 'rating': 7.7, 'year': 1993}, page_content='A bunch of scientists bring back dinosaurs and mayhem breaks loose'), Document(metadata={'genre': 'animated', 'year': 1995}, page_content='Toys come alive and have a blast doing so')]
```

{% endcode %}

Namun, Anda dapat menggunakan angka seperti `dua`, `tiga`, dst. dalam kueri Anda untuk membatasi hasil pencarian tanpa secara eksplisit menentukan `search_kwargs` dalam kode Anda.

```python
retriever = SelfQueryRetriever.from_llm(
    llm,  # Menentukan model bahasa (Language Model).
    vectorstore,  # Menentukan penyimpanan vektor (Vector Store).
    document_content_description,  # Menentukan deskripsi konten dokumen.
    metadata_field_info,  # Menentukan informasi bidang metadata.
    enable_limit=True,  # Mengaktifkan fitur pembatasan hasil pencarian.
)

# Menanyakan tentang dua film yang berkaitan dengan dinosaurus.
retriever.invoke("Apa saja dua film tentang dinosaurus?")
```

{% code overflow="wrap" %}

```
[Document(metadata={'genre': 'science fiction', 'rating': 7.7, 'year': 1993}, page_content='A bunch of scientists bring back dinosaurs and mayhem breaks loose'), Document(metadata={'genre': 'animated', 'year': 1995}, page_content='Toys come alive and have a blast doing so')]
```

{% endcode %}

Mari kita modifikasi sedikit kueri untuk mengembalikan hanya satu.

```python
# menanyakan satu film tentang dinosaurus
retriever.invoke("What are one movies about dinosaurs")
```

{% code overflow="wrap" %}

```
[Document(metadata={'genre': 'science fiction', 'rating': 7.7, 'year': 1993}, page_content='A bunch of scientists bring back dinosaurs and mayhem breaks loose')]
```

{% endcode %}

## Mengkonfigurasi chain menggunakan LCEL

Untuk melihat apa yang terjadi di balik layar dan memiliki lebih banyak kontrol khusus, kita dapat mengkonfigurasi ulang retriever dari awal.

Pertama, kita perlu membuat query-construction chain.

Chain ini akan mengambil kueri pengguna dan membuat objek StructuredQuery yang menangkap filter yang Anda tentukan.

#### Membuat pembuat kueri terstruktur (query\_constructor)

Gunakan fungsi `get_query_constructor_prompt` untuk mendapatkan prompt konstruktor kueri.

* Fungsi ini menerima `document_content_description` dan `metadata_field_info` sebagai argumen.

Gunakan metode `StructuredQueryOutputParser.from_components()` untuk menginisialisasi pemilah keluaran kueri terstruktur.

* Buatlah `query_constructor` dengan menghubungkan prompt generator(`prompt`), model bahasa (`LLM`), dan `output parser` dalam sebuah pipeline.
* Pipeline ini akan membuat query berdasarkan prompt, memprosesnya melalui model bahasa, dan kemudian mengubahnya menjadi format terstruktur menggunakan output parser.

```python
from langchain.chains.query_constructor.base import (
    StructuredQueryOutputParser,
    get_query_constructor_prompt,
)

# Ambil prompt generator query menggunakan deskripsi konten dokumen dan informasi bidang metadata.
prompt = get_query_constructor_prompt(
    document_content_description,
    metadata_field_info,
)

# Buat parser output query terstruktur dari komponen.
output_parser = StructuredQueryOutputParser.from_components()

# Hubungkan prompt, model bahasa, dan parser output untuk membuat query generator.
query_constructor = prompt | llm | output_parser
```

Mari gunakan metode `prompt.format()` untuk mengoper string `“pertanyaan tiruan”` ke parameter kueri dan mencetak hasilnya untuk melihat apa yang dikatakan oleh prompt.

```python
 mencetak hasil pemformatan string prompt dengan parameter kueri yang disetel ke “pertanyaan tiruan”.
print(prompt.format(query="dummy question"))
```

Tujuan Anda adalah menyusun kueri pengguna agar sesuai dengan skema permintaan yang disediakan di bawah ini.

<< Skema Permintaan Terstruktur >> Saat merespons, gunakan cuplikan kode penurunan harga dengan objek JSON yang diformat dalam skema berikut ini:

```
{
    "query": string \ text string to compare to document contents,
    "filter": string \ logical condition statement for filtering documents
}
```

String *query* harus hanya berisi teks yang diharapkan sesuai dengan isi dokumen. Kondisi apa pun dalam filter tidak boleh disebutkan dalam kueri.

Pernyataan kondisi logis terdiri dari satu atau lebih pernyataan perbandingan dan operasi logis.

Sebuah pernyataan perbandingan mengambil bentuk: `comp(attr, val)`:

* `comp` (eq | ne | gt | gte | lt | lte | contain | like | in | nin): pembanding
* `attr` (string): nama atribut yang akan digunakan sebagai pembanding
* `val` (string): adalah nilai perbandingan

Pernyataan operasi logika berbentuk `op(pernyataan1, pernyataan2, ...)`:

* `op` (dan | atau | tidak): operator logika
* `pernyataan1`, `pernyataan2`, ... (pernyataan perbandingan atau pernyataan operasi logika): satu atau lebih pernyataan untuk menerapkan operasi

&#x20;

**Perlu dipastikan:**

* Pastikan Anda hanya menggunakan pembanding dan operator logika yang tercantum di atas dan tidak menggunakan yang lainnya.
* Pastikan bahwa filter hanya mengacu pada atribut yang ada di sumber data.
* Pastikan bahwa filter hanya menggunakan nama atribut dengan nama fungsinya jika ada fungsi yang diterapkan pada atribut tersebut.
* Pastikan filter hanya menggunakan format `YYYY-MM-DD` ketika menangani nilai tipe data tanggal.
* Pastikan bahwa filter memperhitungkan deskripsi atribut dan hanya membuat perbandingan yang layak berdasarkan jenis data yang disimpan.
* Pastikan bahwa filter hanya digunakan sesuai kebutuhan. Jika tidak ada filter yang harus diterapkan, kembalikan “NO\_FILTER” untuk nilai filter.

**Contoh 1:**

Data:

```
{
    "content": "Lyrics of a song",
    "attributes": {
        "artist": {
            "type": "string",
            "description": "Name of the song artist"
        },
        "length": {
            "type": "integer",
            "description": "Length of the song in seconds"
        },
        "genre": {
            "type": "string",
            "description": "The song genre, one of "pop", "rock" or "rap""
        }
    }
}
```

User query:

```
What are songs by Taylor Swift or Katy Perry about teenage romance under 3 minutes long in the dance pop genre
```

Structured Request:

```
{
    "query": "teenager love",
    "filter": "and(or(eq(\"artist\", \"Taylor Swift\"), eq(\"artist\", \"Katy Perry\")), lt(\"length\", 180), eq(\"genre\", \"pop\"))"
}
```

**Contoh 2:**

Data:

```
{
    "content": "Lyrics of a song",
    "attributes": {
        "artist": {
            "type": "string",
            "description": "Name of the song artist"
        },
        "length": {
            "type": "integer",
            "description": "Length of the song in seconds"
        },
        "genre": {
            "type": "string",
            "description": "The song genre, one of "pop", "rock" or "rap""
        }
    }
}
```

User query:

```
What are songs that were not published on Spotify
```

Structured Request:

```
{
    "query": "",
    "filter": "NO_FILTER"
}
```

Contoh 3:

Data:

```
{
  "content": "Brief summary of a movie",
  "attributes": {
    "genre": {
      "description": "The genre of the movie. One of ['science fiction', 'comedy', 'drama', 'thriller', 'romance', 'action', 'animated']",
      "type": "string"
    },
    "year": {
      "description": "The year the movie was released",
      "type": "integer"
    },
    "director": {
      "description": "The name of the movie director",
      "type": "string"
    },
    "rating": {
      "description": "A 1-10 rating for the movie",
      "type": "float"
    }
  }
}
```

User query:

```
dummy question
```

Structured question:

```
```

Panggil metode `query_constructor.invoke()` untuk melakukan pemrosesan untuk kueri yang diberikan.

```notebook-python
query_constructor.invoke(
    {
        # Panggil generator kueri untuk menghasilkan kueri untuk pertanyaan yang diberikan.
        "query": "What are some sci-fi movies from the 90's directed by Luc Besson about taxi drivers"
    }
)
```

```
query='taxi drivers' filter=Operation(operator=<Operator.AND: 'and'>, arguments=[Comparison(comparator=<Comparator.EQ: 'eq'>, attribute='genre', value='science fiction'), Comparison(comparator=<Comparator.GTE: 'gte'>, attribute='year', value=1990), Comparison(comparator=<Comparator.LT: 'lt'>, attribute='year', value=2000), Comparison(comparator=<Comparator.EQ: 'eq'>, attribute='director', value='Luc Besson')]) limit=None
```

Elemen kunci dari pengambil kueri mandiri adalah konstruktor kueri. Untuk membuat sistem pencarian yang hebat, Anda harus memastikan konstruktor kueri berfungsi dengan baik.

Hal ini memerlukan **penyesuaian pada petunjuk, contoh di dalam petunjuk, deskripsi properti, dll.**

\[Catatan].

Untuk contoh yang menunjukkan proses penyempurnaan konstruktor kueri untuk data inventaris hotel, lihat <https://github.com/langchain-ai/langchain/blob/master/cookbook/self_query_hotel_search.ipynb>.

## Mengubah structured query menggunakan Structured Query Translator

Elemen penting berikutnya adalah penerjemah kueri terstruktur, yang bertanggung jawab untuk mengonversi objek `StructuredQuery` umum menjadi filter metadata yang sesuai dengan sintaks penyimpanan vektor yang Anda gunakan.

Kami mengimplementasikan pencari yang menggunakan `SelfQueryRetriever` untuk menghasilkan jawaban atas pertanyaan.

* Gunakan `query_constructor` untuk membuat pertanyaan.
* Gunakan `vectorstore` untuk mengakses penyimpanan vektor.
* Gunakan `ChromaTranslator` untuk menerjemahkan kueri terstruktur agar sesuai dengan penyimpanan vektor Chroma.

```python
from langchain.retrievers.self_query.chroma import ChromaTranslator

retriever = SelfQueryRetriever(
    query_constructor=query_constructor, # Query generator yang dibuat sebelumnya
    vectorstore=vectorstore, # Tentukan penyimpanan vektor
    structured_query_translator=ChromaTranslator(), # Penerjemah query terstruktur
)
```

Hasilkan jawaban untuk pertanyaan yang diberikan menggunakan metode `retriever.invoke().`

```python
retriever.invoke(
    # Cari film tentang mainan yang dibuat setelah tahun 1990 tetapi sebelum tahun 2005, dengan preferensi pada film animasi.
    "What's a movie after 1990 but before 2005 that's all about toys, and preferably is animated"
)
```

{% code overflow="wrap" %}

```
[Document(metadata={'genre': 'animated', 'year': 1995}, page_content='Toys come alive and have a blast doing so')]
```

{% endcode %}

Anda dapat menggunakan generator kueri terstruktur + konverter kueri ini untuk memfilter dan mengambil data tanpa kesalahan.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://altero.gitbook.io/langchain-book-by-altero/ch10-retriever/08.-selfqueryretriever.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
