Skalierbares Frage-Antwort-System über große Dokumente mit LangChain und Vertex AI PaLM
Published on
![In diesem Artikel wird untersucht, wie man ein skalierbares Frage-Antwort-System für große Dokumente erstellt, indem man das LangChain-Framework mit Googles Vertex AI PaLM API kombiniert.](/_next/image?url=https%3A%2F%2Fraw.githubusercontent.com%2Flynn-mikami%2FImages%2Fmain%2Fquestion-answering-document-retrieval-llm.jpeg%3Fx-oss-process%3Dimage%2Fresize%2Climit_0%2Cm_fill%2Cw_1536%2Ch_1024%2Fquality%2Cq_100&w=3840&q=75)
Einführung in Frage-Antwort-Systeme über große Dokumente mit LLMs
Die Beantwortung von Fragen (QA) ist eine wichtige Aufgabe der natürlichen Sprachverarbeitung, bei der es darum geht, Fragen, die von Menschen in natürlicher Sprache gestellt werden, automatisch zu beantworten. Obwohl große Sprachmodelle (LLMs) wie PaLM beeindruckende QA-Fähigkeiten gezeigt haben, sind sie durch die Menge an Kontext beschränkt, die innerhalb ihrer Token-Grenze Platz findet (in der Regel einige tausend Token). Dies stellt eine Herausforderung für QA über große Dokumente dar, die viele Seiten umfassen können.
In diesem Artikel werden wir untersuchen, wie man ein skalierbares QA-System für große Dokumente aufbaut, indem man das LangChain-Framework mit Googles Vertex AI PaLM API kombiniert. Wir werden verschiedene Methoden behandeln, darunter:
- Stuffing - Den gesamten Text als Kontext einfügen
- Map-Reduce - Dokumente in Abschnitte aufteilen und parallel verarbeiten
- Refine - Eine Antwort iterativ über Dokumentabschnitte verbessern
- Similarity Search - Vektor-Embeddings verwenden, um relevante Abschnitte zu finden
Wir werden die Stärken und Limitierungen jeder Methode vergleichen. Der vollständige Code ist in diesem Colab-Notebook verfügbar.
Vergleichen wir die Metriken für jede Methode anhand unseres 50-seitigen Beispiel-Dokuments:
Methode | Relevante Dokumente | LLM-Anfragen | Gesamtzahl der Tokens | Antwortqualität |
---|---|---|---|---|
Stuffing | 3 Seiten | 1 | 8432 | Gut |
Map-Reduce | 50 Seiten | 51 | 63019 | Okay |
Refine | 50 Seiten | 50 | 71209 | Gut |
Similarity Search | 4 Seiten | 5 | 5194 | Hervorragend |
Der Similarity Search-Ansatz kann eine qualitativ hochwertige Antwort mit 10-mal weniger Seiten, LLM-Anfragen und Tokens im Vergleich zu den Methoden mit dem gesamten Dokument finden. Diese Kluft würde sich auf größeren Datensätzen noch weiter vergrößern.
Schritt 1: Einrichten von LangChain für Frage-Antwort-Systeme über große Dokumente
Installieren Sie zunächst die erforderlichen Abhängigkeiten, einschließlich des Vertex AI SDKs, LangChain und ChromaDB:
!pip install google-cloud-aiplatform langchain==0.0.323 chromadb==0.3.26 pypdf
Importieren Sie die wichtigsten Bibliotheken:
from langchain.document_loaders import PyPDFLoader
from langchain.llms import VertexAI
from langchain.chains.question_answering import load_qa_chain
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.embeddings import VertexAIEmbeddings
Laden Sie das PaLM-Textmodell und das Embeddings-Modell:
vertex_llm_text = VertexAI(model_name="text-bison@001")
vertex_embeddings = VertexAIEmbeddings(model_name="textembedding-gecko@001")
Schritt 2: Laden der Dokumente
In diesem Beispiel verwenden wir ein PDF-Whitepaper zu MLOps. Laden Sie es herunter und laden Sie den Text mit PyPDFLoader:
pdf_url = "https://services.google.com/fh/files/misc/practitioners_guide_to_mlops_whitepaper.pdf"
pdf_loader = PyPDFLoader(pdf_file)
pages = pdf_loader.load_and_split()
Dadurch wird das PDF in Seiten aufgeteilt, die wir als Basisdokumente verwenden können.
Schritt 3: Stuffing der Dokumente
Der einfachste Ansatz besteht darin, den gesamten Dokumententext in das Kontextfenster des LLMs einzufügen. Richten Sie eine Vorlagemodell ein:
prompt_template = """Beantworten Sie die Frage so präzise wie möglich mit dem gegebenen Kontext.
Wenn die Antwort nicht im Kontext enthalten ist, sagen Sie "Antwort nicht im Kontext verfügbar" \n\n
Kontext: \n {context}?\n
Frage: \n {question} \n
Antwort:
"""
prompt = PromptTemplate(
template=prompt_template, input_variables=["context", "question"]
)
Laden Sie eine Stuffing-QA-Kette:
stuff_chain = load_qa_chain(vertex_llm_text, chain_type="stuff", prompt=prompt)
Führen Sie es dann für eine Frage aus:
question = "Was ist Experimentation?"
context = "\n".join(str(p.page_content) for p in pages[:7])
stuff_answer = stuff_chain(
{"input_documents": pages[7:10], "question": question}, return_only_outputs=True
)
Dies funktioniert, ist aber durch die Größe des Kontexts begrenzt, den das Modell verarbeiten kann (einige tausend Token). Wenn das vollständige 50-seitige Dokument eingefügt wird, erreicht dies diese Grenze:
try:
print(stuff_chain(
{"input_documents": pages[7:], "question": question},
return_only_outputs=True))
except Exception as e:
print("The code failed since it won't be able to run inference on such a huge context")
Schritt 4: Map-Reduce
Um größere Dokumente zu skalieren, können wir sie in Abschnitte aufteilen, QA für jeden Abschnitt durchführen und dann die Ergebnisse aggregieren. LangChain bietet eine Map-Reduce-Kette zur Bearbeitung dieses Vorgangs an.
Definieren Sie zunächst getrennte Frage- und Kombinationsvorlagen:
question_prompt_template = """
Beantworten Sie die Frage so präzise wie möglich mit dem gegebenen Kontext.\n\n
Kontext: \n {context} \n
Frage: \n {question} \n
Antwort:
"""
question_prompt = PromptTemplate(
template=question_prompt_template, input_variables=["context", "question"]
)
combine_prompt_template = """Erstellen Sie anhand des extrahierten Inhalts und der Frage eine abschließende Antwort.
Wenn die Antwort nicht im Kontext enthalten ist, sagen Sie "Antwort nicht im Kontext verfügbar. \n\n
Zusammenfassungen: \n {summaries}?\n
Frage: \n {question} \n
Antwort:
"""
combine_prompt = PromptTemplate(
template=combine_prompt_template, input_variables=["summaries", "question"]
)
Laden Sie die Map-Reduce-Kette unter Angabe der Frage- und Kombinationsvorlagen:
map_reduce_chain = load_qa_chain(
vertex_llm_text,
chain_type="map_reduce",
return_intermediate_steps=True,
question_prompt=question_prompt,
combine_prompt=combine_prompt,
)
Führen Sie es auf dem vollständigen Dokumentensatz aus:
map_reduce_outputs = map_reduce_chain({"input_documents": pages, "question": question})
Hier wird QA auf jeder Seite einzeln ausgeführt und die Ergebnisse anschließend in einem abschließenden Schritt kombiniert. Wir können die Zwischenergebnisse überprüfen:
for doc, out in zip(
map_reduce_outputs["input_documents"], map_reduce_outputs["intermediate_steps"]
):
print(f"Seite: {doc.metadata['page']}")
print(f"Antwort: {out}")
Der Map-Reduce-Ansatz skalierbar für große Dokumente und bietet Einblicke, woher die Informationen stammen. Allerdings kann es manchmal zu Informationsverlusten im abschließenden Kombinationsschritt kommen.
Schritt 5: Verfeinern
Der Verfeinerungsansatz zielt darauf ab, Informationsverluste durch iteratives Verfeinern einer Antwort zu minimieren. Es beginnt mit einer initialen Antwort auf dem ersten Abschnitt und verfeinert sie dann mit jedem nachfolgenden Abschnitt.
Definieren Sie eine Verfeinerungsanfrage, die die vorhandene Antwort und den neuen Kontext enthält:
refine_prompt_template = """
Die ursprüngliche Frage lautet: \n {question} \n
Die bereitgestellte Antwort ist: \n {existing_answer}\n
Verfeinern Sie die vorhandene Antwort bei Bedarf mit folgendem Kontext: \n {context_str} \n
Basierend auf dem extrahierten Inhalt und der Fragestellung, erstellen Sie eine endgültige Antwort.
Wenn die Antwort nicht im Kontext enthalten ist, geben Sie "Antwort im Kontext nicht verfügbar" an. \n\n
"""
refine_prompt = PromptTemplate(
input_variables=["question", "existing_answer", "context_str"],
template=refine_prompt_template,
)
Laden Sie eine Verfeinerungskette:
refine_chain = load_qa_chain(
vertex_llm_text,
chain_type="verfeinern",
return_intermediate_steps=True,
question_prompt=initial_question_prompt,
refine_prompt=refine_prompt,
)
Führen Sie es auf dem vollständigen Dokument aus:
refine_outputs = refine_chain({"input_documents": pages, "question": question})
Überprüfen Sie die Zwischenschritte, um zu sehen, wie die Antwort verfeinert wird:
for doc, out in zip(
refine_outputs["input_documents"], refine_outputs["intermediate_steps"]
):
print(f"Seite: {doc.metadata['page']}")
print(f"Antwort: {out}")
Der Verfeinerungsansatz hilft dabei, Informationen über das gesamte Dokument hinweg zu bewahren. Allerdings erfordert er nach wie vor die lineare Verarbeitung des gesamten Dokuments.
Schritt 6: Ähnlichkeitssuche
Für eine verbesserte Effizienz können wir zunächst Embeddings verwenden, um nur die relevantesten Abschnitte für eine bestimmte Frage zu finden. Dadurch entfällt die Notwendigkeit, das gesamte Dokument zu verarbeiten.
Erstellen Sie einen Vektorindex der Dokumentabschnitte mit ChromaDB:
vector_index = Chroma.from_documents(pages, vertex_embeddings).as_retriever()
Rufen Sie die relevantesten Abschnitte für die Frage ab:
docs = vector_index.get_relevant_documents(question)
Führen Sie die Map-Reduce-Kette nur auf diesen relevanten Abschnitten aus:
map_reduce_embeddings_outputs = map_reduce_chain(
{"input_documents": docs, "question": question}
)
print(map_reduce_embeddings_outputs["output_text"])
Auf diese Weise wird eine qualitativ hochwertige Antwort gefunden, ohne dabei den gesamten Text des Dokuments verarbeiten zu müssen. Die Ähnlichkeitssuche bietet das beste Gleichgewicht zwischen Genauigkeit und Effizienz.
Fazit
In diesem Artikel haben wir verschiedene Ansätze zur Beantwortung von Fragen über große Dokumente mithilfe von LangChain und Vertex AI PaLM demonstriert. Während einfaches Hinzufügen für kleine Dokumente funktionieren kann, sind für größere Daten Map-Reduce- und Verfeinerungsansätze erforderlich.
Allerdings ist die effizienteste und effektivste Methode, zuerst die Vektor-Ähnlichkeitssuche zu verwenden, um nur die relevantesten Abschnitte für eine bestimmte Frage zu finden. Dadurch wird die Menge des Textes minimiert, die vom LLM verarbeitet werden muss, während dennoch qualitativ hochwertige Antworten erzeugt werden.
Die Kombination aus Ähnlichkeitssuche, LangChain's QA-Ketten und leistungsstarken LLMs wie PaLM ermöglichen den Aufbau skalierbarer Frage-Antwort-Systeme über große Dokumentsammlungen. Sie können mit dem vollständigen Code in diesem Notebook beginnen.
Möchten Sie die neuesten Nachrichten zu LLM erfahren? Schauen Sie sich die neueste LLM-Rangliste an!