Erstellen Sie in 5 Minuten einen kostenlosen Q&A-Bot: Milvus + LangChain

Wie lange dauert es, einen nützlichen, günstigen und genauen Q&A-Bot zu erstellen?

Die Antwort ist 5 Minuten. Nutzen Sie einfach den Open-Source-RAG-Technologie-Stack LangChain und die benutzerfreundliche Vektordatenbank Milvus. Es muss betont werden, dass die Kosten dieses Q&A-Bots sehr niedrig sind, da wir während der Rückruf-, Evaluierungs- und Entwicklungsiterationen keine großen Sprachmodell-APIs aufrufen müssen. Erst im letzten Schritt – bei der Generierung der endgültigen Frage- und Antwortergebnisse – wird die API einmalig aufgerufen.

Wenn Sie mehr über die Technologie hinter dem Q&A-Bot erfahren möchten, können Sie sich den Quellcode auf GitHub ( https://github.com/zilliztech/akcio) ansehen. Der vollständige Code dieses Artikels ist über Bootcamp verfügbar ( https://github.com/milvus-io/bootcamp/blob/master/bootcamp/RAG/readthedocs_zilliz_langchain.ipynb).

Bevor wir offiziell beginnen, werfen wir einen Blick auf RAG. Der Hauptzweck von RAG besteht darin, die Textausgabe durch generative KI zu unterstützen. Mit anderen Worten: RAG nutzt Fakten und individuelle Daten, um LLM-Illusionen zu reduzieren. Insbesondere können wir in RAG zuverlässige und vertrauenswürdige benutzerdefinierte Datentexte wie Produktdokumentationen verwenden und anschließend ähnliche Ergebnisse aus einer Vektordatenbank abrufen. Die genaue Textantwort wird dann als „Kontext“ zusammen mit der „Frage“ in die „Eingabeaufforderung“ eingefügt und in ein LLM wie ChatGPT von OpenAI eingespeist. Letztendlich generiert LLM eine faktenbasierte Chat-Antwort.

Der spezifische Prozess von RAG:

  1. Bereiten Sie vertrauenswürdige benutzerdefinierte Daten und ein Einbettungsmodell vor.

  2. Verwenden Sie Encoder, um die Daten aufzuteilen, Einbettungsvektoren zu generieren und die Daten und Metadaten in der Vektordatenbank zu speichern.

  3. Der Benutzer stellt eine Frage. Verwenden Sie denselben Encoder aus Schritt 1, um das Problem in einen Einbettungsvektor umzuwandeln.

  4. Semantische Suche mithilfe von Vektordatenbanken zum Abrufen von Antworten auf Fragen.

  5. Verwenden Sie den Suchantworttextblock als „Kontext“ und Benutzerfrageergebnisse, um eine Eingabeaufforderung zu bilden. Senden Sie eine Eingabeaufforderung an LLM.

  6. LLM generiert die Antwort.

01. Daten abrufen

Lassen Sie uns zunächst die Werkzeuge vorstellen, die in diesem Konstruktionsprozess verwendet werden:

Milvus ist eine Open-Source-Hochleistungsvektordatenbank, die den Suchprozess für unstrukturierte Daten vereinfacht. Milvus kann umfangreiche Einbettungsvektordaten speichern, indizieren und durchsuchen.

OpenAI entwickelt hauptsächlich KI-Modelle und -Tools und sein bekanntestes Produkt ist GPT.

Das LangChain-Tool und die Wrapper-Bibliothek können Entwicklern dabei helfen, eine Brücke zwischen traditioneller Software und LLM zu schlagen.

Wir verwenden die Produktdokumentationsseite. ReadTheDocs ist eine kostenlose Open-Source-Dokumentationssoftware, die Dokumentation über Sphinx generiert.

Download readthedocs pages locally.
    DOCS_PAGE="https://pymilvus.readthedocs.io/en/latest/"
    wget -r -A.html -P rtdocs --header="Accept-Charset: UTF-8" $DOCS_PAGE

Der obige Code lädt die Dokumentationsseite in einen lokalen Pfad herunter rtdocs. Lesen Sie als Nächstes diese Dokumente in LangChain:

#!pip install langchain
from langchain.document_loaders import ReadTheDocsLoader
loader = ReadTheDocsLoader(
   "rtdocs/pymilvus.readthedocs.io/en/latest/",
   features="html.parser")
docs = loader.load()

02. Verwenden Sie eine HTML-Struktur, um Daten aufzuteilen

Es ist notwendig, die Chunking-Strategie, die Chunk-Größe und die Chunk-Überlappung zu bestimmen. Für dieses Tutorial sieht unsere Konfiguration so aus:

  • Chunking-Strategie = Aufteilung entsprechend der Markdown-Titelstruktur.

  • Kachelgröße = Parameter „Einbettungsmodell“ verwendenMAX_SEQ_LENGTH

  • Überlappung = 10-15 %

  • Funktion =

    Langchain HTMLHeaderTextSplitter teilt Markdown-Dateiheader.

    Langchain RecursiveCharacterTextSplitter teilt langen Text.

from langchain.text_splitter import HTMLHeaderTextSplitter, RecursiveCharacterTextSplitter
Define the headers to split on for the HTMLHeaderTextSplitter
headers_to_split_on = [
    ("h1", "Header 1"),
    ("h2", "Header 2"),]
Create an instance of the HTMLHeaderTextSplitter
html_splitter = HTMLHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
Use the embedding model parameters.
chunk_size = MAX_SEQ_LENGTH - HF_EOS_TOKEN_LENGTH
chunk_overlap = np.round(chunk_size * 0.10, 0)
Create an instance of the RecursiveCharacterTextSplitter
child_splitter = RecursiveCharacterTextSplitter(
    chunk_size = chunk_size,
    chunk_overlap = chunk_overlap,
    length_function = len,)
Split the HTML text using the HTMLHeaderTextSplitter.
html_header_splits = []
for doc in docs:
    splits = html_splitter.split_text(doc.page_content)
    for split in splits:
        # Add the source URL and header values to the metadata
        metadata = {}
        new_text = split.page_content
        for header_name, metadata_header_name in headers_to_split_on:
            header_value = new_text.split("¶ ")[0].strip()
            metadata[header_name] = header_value
            try:
                new_text = new_text.split("¶ ")[1].strip()
            except:
                break
        split.metadata = {
            **metadata,
            "source": doc.metadata["source"]}
        # Add the header to the text
        split.page_content = split.page_content
    html_header_splits.extend(splits)
Split the documents further into smaller, recursive chunks.
chunks = child_splitter.split_documents(html_header_splits)
end_time = time.time()
print(f"chunking time: {end_time - start_time}")
print(f"docs: {len(docs)}, split into: {len(html_header_splits)}")
print(f"split into chunks: {len(chunks)}, type: list of {type(chunks[0])}") 
Inspect a chunk.
print()
print("Looking at a sample chunk...")
print(chunks[1].page_content[:100])
print(chunks[1].metadata)

Dieser Textblock wird durch Dokumentation unterstützt. Darüber hinaus werden Titel und Textblöcke zusammen gespeichert und die Titel können später verwendet werden.

03.Einbettungsvektor generieren

Die neuesten MTEB-Leistungstestergebnisse zeigen, dass das Open-Source-Einbettungs-/Rückrufmodell und OpenAI Embeddings (ada-002) ähnliche Auswirkungen haben. Das kleine Modell mit der höchsten Punktzahl in der Abbildung unten ist bge-large-en-v1.5das Modell, das für diesen Artikel ausgewählt wird.

Das Bild oben zeigt die Rangliste der Einbettungsmodelle, die höchste Rangfolge ist voyage-lite-01-instruct(size 4.2 GB, and third rankbge-base-en-v1.5(size 1.5 GB). OpenAIEmbeddingtext-embeddings-ada-002Platz 22.

Lassen Sie uns nun das Modell initialisieren.

#pip install torch, sentence-transformers
import torch
from sentence_transformers import SentenceTransformer
Initialize torch settings
DEVICE = torch.device('cuda:3' 
   if torch.cuda.is_available() 
   else 'cpu')
Load the encoder model from huggingface model hub.
model_name = "BAAI/bge-base-en-v1.5"
encoder = SentenceTransformer(model_name, device=DEVICE)
Get the model parameters and save for later.
MAX_SEQ_LENGTH = encoder.get_max_seq_length() 
EMBEDDING_LENGTH = encoder.get_sentence_embedding_dimension()

Als nächstes verwenden Sie das Modell, um Einbettungsvektoren zu generieren und alle Daten in ein Wörterbuch zu integrieren.

chunk_list = []
for chunk in chunks:
    # Generate embeddings using encoder from HuggingFace.
    embeddings = torch.tensor(encoder.encode([chunk.page_content]))
    embeddings = F.normalize(embeddings, p=2, dim=1)
    converted_values = list(map(np.float32, embeddings))[0]
    # Assemble embedding vector, original text chunk, metadata.
    chunk_dict = {
        'vector': converted_values,
        'text': chunk.page_content,
        'source': chunk.metadata['source'],
        'h1': chunk.metadata['h1'][:50],
        'h2': chunk.metadata['h1'][:50],}
    chunk_list.append(chunk_dict)

04. Erstellen Sie einen Index in Milvus und fügen Sie Daten ein

Wir speichern die Originaltextblöcke in der Vektordatenbank in der Form , vector, text, source.h1h2

Starten Sie den Milvus-Server und stellen Sie eine Verbindung her. Um einen serverlosen Cluster verwenden zu können, müssen Sie ihn beim Herstellen einer Verbindung angeben ZILLIZ_API_KEY.

#pip install pymilvus
from pymilvus import connections
ENDPOINT=”https://xxxx.api.region.zillizcloud.com:443”
connections.connect(
   uri=ENDPOINT,
   token=TOKEN)

Erstellen Sie die Milvus-Sammlung und benennen Sie sie MilvusDocs. Die Sammlung ähnelt einer Tabelle in einer herkömmlichen Datenbank. Sie verfügt über ein Schema und definiert Felder und Datentypen. Die Vektorabmessungen im Schema sollten mit den Abmessungen der vom Einbettungsmodell generierten Vektoren übereinstimmen. Erstellen Sie in der Zwischenzeit den Index:

from pymilvus import (
   FieldSchema, DataType, 
   CollectionSchema, Collection)
1. Define a minimum expandable schema.
fields = [
   FieldSchema(“pk”, DataType.INT64, is_primary=True, auto_id=True),
   FieldSchema(“vector”, DataType.FLOAT_VECTOR, dim=768),]
schema = CollectionSchema(
   fields,
   enable_dynamic_field=True,)
2. Create the collection.
mc = Collection(“MilvusDocs”, schema)
3. Index the collection.
mc.create_index(
   field_name=”vector”,
   index_params={
       “index_type”: “AUTOINDEX”,
       “metric_type”: “COSINE”,}

Das Einfügen von Daten in Milvus/Zilliz ist schneller als in Pinecone!

Insert data into the Milvus collection.
insert_result = mc.insert(chunk_list)
After final entity is inserted, call flush
to stop growing segments left in memory.
mc.flush() 
print(mc.partitions)

05.Stellen Sie Fragen

Als Nächstes können wir die Leistungsfähigkeit der semantischen Suche nutzen, um Fragen zum Dokument zu beantworten. Die semantische Suche nutzt Techniken des nächsten Nachbarn im Vektorraum, um das am besten passende Dokument zur Beantwortung der Frage des Benutzers zu finden. Das Ziel der semantischen Suche besteht darin, die Bedeutung von Fragen und Dokumenten zu verstehen, nicht nur passende Schlüsselwörter. Während des Abrufvorgangs kann Milvus auch Metadaten nutzen, um das Sucherlebnis zu verbessern ( expr=unter Verwendung boolescher Ausdrücke in Milvus-API-Optionen).

Define a sample question about your data.
QUESTION = "what is the default distance metric used in AUTOINDEX?"
QUERY = [question]
Before conducting a search, load the data into memory.
mc.load()
Embed the question using the same encoder.
embedded_question = torch.tensor(encoder.encode([QUESTION]))
Normalize embeddings to unit length.
embedded_question = F.normalize(embedded_question, p=2, dim=1)
Convert the embeddings to list of list of np.float32.
embedded_question = list(map(np.float32, embedded_question))
Return top k results with AUTOINDEX.
TOP_K = 5
Run semantic vector search using your query and the vector database.
start_time = time.time()
results = mc.search(
    data=embedded_question, 
    anns_field="vector", 
    # No params for AUTOINDEX
    param={},
    # Boolean expression if any
    expr="",
    output_fields=["h1", "h2", "text", "source"], 
    limit=TOP_K,
    consistency_level="Eventually")
elapsed_time = time.time() - start_time
print(f"Milvus search time: {elapsed_time} sec")

Hier sind die Suchergebnisse, wir haben diesen Text in contextdas Feld eingefügt:

for n, hits in enumerate(results):
     print(f"{n}th query result")
     for hit in hits:
         print(hit)
Assemble the context as a stuffed string.
context = ""
for r in results[0]:
    text = r.entity.text
    context += f"{text} "
Also save the context metadata to retrieve along with the answer.
context_metadata = {
    "h1": results[0][0].entity.h1,
    "h2": results[0][0].entity.h2,
    "source": results[0][0].entity.source,}

Das Bild oben zeigt, dass 5 Textblöcke abgerufen wurden. Der erste Textblock enthält die Antwort auf die Frage. Da wir es während des Abrufs verwenden output_fields=, enthalten die vom Abruf zurückgegebenen Ausgabefelder Referenzen und Metadaten.

id: 445766022949255988, distance: 0.708217978477478, entity: {
  'chunk': "...# Optional, default MetricType.L2 } timeout (float) –
           An optional duration of time in seconds to allow for the
           RPC. …",
  'source': 'https://pymilvus.readthedocs.io/en/latest/api.html',
  'h1': 'API reference',
  'h2': 'Client'}

06. Verwenden Sie LLM, um Antworten auf Benutzerfragen basierend auf dem Kontext zu generieren

In diesem Schritt verwenden wir ein kleines generatives KI-Modell (LLM), das über HuggingFace verfügbar ist.

#pip install transformers
from transformers import AutoTokenizer, pipeline
tiny_llm = "deepset/tinyroberta-squad2"
tokenizer = AutoTokenizer.from_pretrained(tiny_llm)
context cannot be empty so just put random text in it.
QA_input = {
    'question': question,
    'context': 'The quick brown fox jumped over the lazy dog'}
nlp = pipeline('question-answering', 
               model=tiny_llm, 
               tokenizer=tokenizer)
result = nlp(QA_input)
print(f"Question: {question}")
print(f"Answer: {result['answer']}")

Die Antwort ist nicht sehr genau. Versuchen wir, die gleiche Frage anhand des erinnerten Textes zu stellen:

QA_input = {
    'question': question,
    'context': context,}
nlp = pipeline('question-answering', 
               model=tiny_llm, 
               tokenizer=tokenizer)
result = nlp(QA_input)
Print the question, answer, grounding sources and citations.
Answer = assemble_grounding_sources(result[‘answer’], context_metadata)
print(f"Question: {question}")
print(answer)

Die Antwort ist viel genauer!

Als Nächstes haben wir versucht, das GPT von OpenAI zu verwenden, und festgestellt, dass die Antwortergebnisse mit denen des Open-Source-Roboters, den wir selbst erstellt haben, identisch waren.

def prepare_response(response):
    return response["choices"][-1]["message"]["content"]
def generate_response(
    llm, 
    temperature=0.0, #0 for reproducible experiments
    grounding_sources=None,
    system_content="", assistant_content="", user_content=""):
    response = openai.ChatCompletion.create(
        model=llm,
        temperature=temperature,
        api_key=openai.api_key,
        messages=[
            {"role": "system", "content": system_content},
            {"role": "assistant", "content": assistant_content},
            {"role": "user", "content": user_content}, ])
        answer = prepare_response(response=response)
    
        # Add the grounding sources and citations.
        answer = assemble_grounding_sources(answer, grounding_sources)
        return answer
Generate response
response = generate_response(
    llm="gpt-3.5-turbo-1106",
    temperature=0.0,
    grounding_sources=context_metadata,
    system_content="Answer the question using the context provided. Be succinct.",
    user_content=f"question: {QUESTION}, context: {context}")
Print the question, answer, grounding sources and citations.
print(f"Question: {QUESTION}")
print(response)

07. Zusammenfassung

In diesem Artikel wird ausführlich gezeigt, wie Sie einen RAG-Chatbot für benutzerdefinierte Dokumente erstellen. Dank LangChain, Milvus und dem Open-Source-LLM können wir problemlos kostenlose Fragen und Antworten zu formulierten Daten implementieren.

Broadcom kündigte die Beendigung des bestehenden VMware-Partnerprogramms Deepin-IDE-Versionsupdate, ein neues Erscheinungsbild, an. WAVE SUMMIT feiert seine 10. Ausgabe. Wen Xinyiyan wird die neueste Enthüllung haben! Zhou Hongyi: Der gebürtige Hongmeng wird auf jeden Fall Erfolg haben. Der komplette Quellcode von GTA 5 wurde öffentlich durchgesickert. Linus: Ich werde den Code an Heiligabend nicht lesen. Ich werde eine neue Version des Java-Tool-Sets Hutool-5.8.24 veröffentlichen nächstes Jahr. Lasst uns gemeinsam über Furion klagen. Kommerzielle Erkundung: Das Boot ist vorbei. Wan Zhongshan, v4.9.1.15 Apple veröffentlicht Open-Source-Multimodal-Großsprachenmodell Ferret Yakult Company bestätigt, dass 95 G-Daten durchgesickert sind
{{o.name}}
{{m.name}}

Supongo que te gusta

Origin my.oschina.net/u/4209276/blog/10324159
Recomendado
Clasificación