08. Selfqueryretriever

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 (opens in a new tab)

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.

Terminal
%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")
Output
[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')]

Dalam kueri ini, Anda dapat melihat bahwa kami telah menggunakan filter 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"
)
ValueError: Expected operand value to be an int or a float for operator $gt, got 1990

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?")
[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')]

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?")
[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')]

Mari kita modifikasi sedikit kueri untuk mengembalikan hanya satu.

Python
# menanyakan satu film tentang dinosaurus
retriever.invoke("What are one movies about dinosaurs")
[Document(metadata={'genre': 'science fiction', 'rating': 7.7, 'year': 1993}, page_content='A bunch of scientists bring back dinosaurs and mayhem breaks loose')]

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.

** 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

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.

Output
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"
    }
)
Output
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 (opens in a new tab).

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"
)
Output
[Document(metadata={'genre': 'animated', 'year': 1995}, page_content='Toys come alive and have a blast doing so')]

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