12.6 RAG-Abfragen
Damit unsere Frage zu einer sinnvollen Antwort führt, werden mehrere Schritte hintereinander von der LlamaIndex-Bibliothek ausgeführt:
-
Die Frage in natürlicher Sprache wird in Vektoren umgewandelt. Dazu muss das gleiche Embedding-Modell verwendet werden wie beim Erzeugen des VectorStoreIndex.
-
Es wird nach ähnlichen Vektoren in der Datenbank gesucht.
-
Der Inhalt der gefundenen LlamaIndex-Nodes wird als Context mit der Frage an ein LLM weitergeleitet.
-
Das LLM formuliert aus den Daten eine Antwort in natürlicher Sprache.
Nachdem wir uns bisher mit dem Embedding-Modell beschäftigt haben, kommen wir hier zum ersten Mal mit dem LLM in Kontakt. Wobei, so ganz stimmt das nicht: In Abschnitt 12.4, »Index erzeugen«, verwendete der TitleExtractor ein LLM, um eine Zusammenfassung aus den aufgeteilten Dokumenten zu erzeugen. Welches LLM wird dabei verwendet und wo können wir das konfigurieren?
LlamaIndex nimmt aktuell als Standard-LLM OpenAIs gpt-4o, vorausgesetzt, dass Sie einen gültigen API-Schlüssel in der Umgebungsvariable OPENAI_API_KEY hinterlegt haben. Sie können dieses Verhalten aber sehr einfach ändern und zum Beispiel ein lokales llama3.1 aus Ihrem Ollama-Server verwenden:
from llama_index.core.settings import Settings
from llama_index.llms.ollama import Ollama
...
Settings.llm = Ollama(model="llama3.1")
Oder Sie verwenden das beliebte Claude 3.5 Sonnet:
from llama_index.llms.anthropic import Anthropic
...
Settings.llm = Anthropic(model="claude-3-5-sonnet-20240620")
Beachten Sie den Import von Settings und die folgende Einstellung Settings.llm. LlamaIndex verwendet für alle Zugriffe auf ein LLM nun das eingestellte Modell. Genauso verhält es sich auch mit dem Embedding-Modell. Die Einstellung dafür lautet Settings.embed_model.
Prompt: Welche Tagfalterart wurde im Jahr 2023 in Tirol am häufigsten gefunden?
Um diese Frage zu stellen, benötigen wir noch eine LlamaIndex query_engine, die wir von dem VectorStoreIndex ableiten können:
query_engine = index.as_query_engine()
response = query_engine.query(
"Welche Tagfalterart wurde im Jahr 2023 in Tirol am "
"häufigsten gefunden?")
print(response)
Spannend sind jetzt natürlich die unterschiedlichen Antworten der verschiedenen Modelle. Die oben gestellte Frage bezieht sich auf eine Tabelle auf Seite 41 im Jahresbericht 2023 zum Insektenmonitoring in Österreich. Das PDF-Dokument enthält vielfältige Informationen zu den wissenschaftlichen Ergebnissen, nicht nur zu den Untersuchungen in Tirol. Hier die Antwort von der Kombination aus dem Embedding-Modell bge-m3 und dem LLM Claude 3.5 Sonnet:
Das Große Ochsenauge (Maniola jurtina) wurde im Jahr 2023 in Tirol am häufigsten beobachtet. Mit 430 gezählten Individuen war es die Art mit den meisten Sichtungen in diesem Jahr.
Noch detaillierter werden die Angaben bei der Verwendung des Embedding-Modells von OpenAI:
Das Große Ochsenauge (Maniola jurtina) war die Tagfalterart, die im Jahr 2023 in Tirol am häufigsten beobachtet wurde. Diese Art bleibt weiterhin an der Spitze der Liste mit den meisten gezählten Individuen. Danach folgen in der Häufigkeit der Kleine Kohlweißling (Pieris rapae), der Hauhechelbläuling (Polyommatus icarus) und das Kleine Wiesenvögelchen (Coenonympha pamphilus).
Es ist jedoch wichtig zu erwähnen, dass der Kleine Fuchs (Aglais urticae) die am weitesten verbreitete Art war und an 87 von 100 untersuchten Standorten nachgewiesen wurde.
Wenn Sie selbst schon einmal Daten aus einer Tabelle eines PDF-Dokuments extrahieren wollten, dann wissen Sie sicher, dass man in den meisten Fällen schneller ist, wenn man die Daten neu eingibt. PDF beschreibt, wo etwas auf einer Seite steht, nicht aber, wie es inhaltlich zusammengehört. Umso erstaunlicher ist es, wie gut die Kombination aus Vektor-Suche und LLM funktioniert. Dass Claude darauf hinweist, dass der Kleine Fuchs an den meisten Standorten gefunden wurde, ist bemerkenswert, auch wenn sich die Zahl 87 auf den gesamten Zeitraum zwischen 2018 und 2023 und nicht auf das gefragte Jahr 2023 bezieht.
Sie können das PDF-Dokument von der Projekt-Webseite laden, um sich selbst einen Eindruck von den Ausgangsdaten zu verschaffen: https://viel-falter.at/ergebnisse/veroeffentlichungen. Um Ihnen diese Mühe zu ersparen, haben wir den betreffenden Ausschnitt der Tabelle in Abbildung 12.4 abgedruckt.
Abbildung 12.4 Ausschnitt der Tabelle zum Tagfalter Monitoring in Tirol
Source Code
Nach den ganzen Code-Schnipseln wollen wir Ihnen noch das vollständige Programm zeigen, das wir zum Testen der Modelle verwendet haben. Es enthält Kommandozeilenparameter für die Auswahl des Embedding-Modells und des LLMs, außerdem gibt es noch einen Schalter, um den VectorStoreIndex neu zu erzeugen, und einen Schalter für mehr Logging-Ausgaben. Die vielen import-Anweisungen haben wir nicht abgedruckt, da sie zum Verständnis des Programms nicht notwendig sind.
Beachten Sie, dass beim Anlegen des VectorStoreIndex als Name der Collection der Wert des Schalters args.embed übergeben wird. Ohne eine Angabe dieses Schalters wird der Default-Wert openai verwendet. Dadurch können Sie in der Milvus-Datenbank mehrere Indizes parallel speichern und parallel testen.
Noch ein Hinweis zu den Umgebungsvariablen für OpenAI und Anthropic: Wir haben im aktuellen Verzeichnis eine Datei .env angelegt, die folgenden Inhalt hat:
OPENAI_API_KEY="sk-proj-dG...."
ANTHROPIC_API_KEY='sk-ant-api03-....'
Die Python-Bibliothek dotenv, die Sie mit pip install dotenv installieren können, lädt diese Datei. Vorausgesetzt Sie haben noch Guthaben auf Ihren Accounts, dann können Sie die verschiedenen Modelle von OpenAI oder Anthropic ausprobieren.
# Datei: vf-rag.py
load_dotenv()
parser = argparse.ArgumentParser(
prog="vf-rag",
description="Information about viel-falter.at project")
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("-d", "--debug", action="store_true")
parser.add_argument("-r", "--regenerate", action="store_true")
parser.add_argument(
"-l", "--llm", choices=["ollama", "openai", "claude"],
default="claude")
parser.add_argument(
"-e", "--embed", default="openai",
choices=["m3", "nomic", "mxbai", "base", "openai"])
args = parser.parse_args()
if args.verbose or args.debug:
logging.basicConfig(
stream=sys.stdout,
level=logging.DEBUG if args.debug else logging.INFO)
logging.getLogger().addHandler(logging.StreamHandler(
stream=sys.stdout))
milvus_store = "http://localhost:19530"
dimensions = 1024
if args.embed == "m3":
Settings.embed_model = HuggingFaceEmbedding(
model_name="BAAI/bge-m3",
cache_folder="/home/user/huggingface_cache")
elif args.embed == "base":
Settings.embed_model = HuggingFaceEmbedding(
model_name="BAAI/bge-base-en-v1.5",
cache_folder="/home/user/huggingface_cache")
dimensions = 768
elif args.embed == "nomic":
Settings.embed_model = OllamaEmbedding(
model_name="nomic-embed-text")
dimensions = 512
elif args.embed == "mxbai":
Settings.embed_model = OllamaEmbedding(
model_name="mxbai-embed-large")
else:
Settings.embed_model = OpenAIEmbedding()
dimensions = 1536
if args.llm == "ollama":
Settings.llm = Ollama(model="llama3.1")
elif args.llm == "openai":
Settings.llm = OpenAI(model="gpt-4o-mini")
else:
Settings.llm = Anthropic(
model="claude-3-5-sonnet-20240620")
logging.log(level=logging.INFO, msg=args)
logging.log(level=logging.DEBUG, msg=os.environ)
if args.regenerate:
pdf_documents = SimpleDirectoryReader("pdfs").load_data(
show_progress=True)
webpages = [
"https://viel-falter.at/",
"https://viel-falter.at/monitoring/ziele-vision/",
"https://viel-falter.at/projektpartner/",
"https://viel-falter.at/news/",
"https://viel-falter.at/tagfalter-exkursion/",
]
web_documents = SimpleWebPageReader(
html_to_text=True).load_data(webpages)
documents = pdf_documents + web_documents
vector_store = MilvusVectorStore(
uri=milvus_store, dim=dimensions, overwrite=True,
collection_name=args.embed)
storage_context = StorageContext.from_defaults(
vector_store=vector_store)
index = VectorStoreIndex.from_documents(
documents=documents,
storage_context=storage_context,
show_progress=True)
else:
vector_store = MilvusVectorStore(
uri=milvus_store, dim=dimensions,
collection_name=args.embed)
index = VectorStoreIndex.from_vector_store(
vector_store)
query_engine = index.as_query_engine()
response = query_engine.query(
"Welche Tagfalterart wurde im Jahr 2023 in Tirol am "
"häufigsten gefunden?")
print(response)
Fazit
Wir waren beeindruckt, wie viel sich mithilfe der LlamaIndex-Bibliothek sogar mit den Standard-Einstellungen erreichen lässt. Der gesamte Quelltext des soeben vorgestellten RAG-Skripts umfasst gerade einmal 100 Zeilen Code, wobei wir für unsere Tests noch Kommandozeilen-Parameter für das zu verwendende LLM und das neuerliche Erzeugen des Index eingebaut haben.
Wir wollen Sie hier nicht glauben machen, dass Sie mit so einfachen Tricks eine Applikation erstellen können, die sich im produktiven Umfeld erfolgreich einsetzen lässt. Es gibt mehrere Beispiele, wie ein schnell zusammengestellter Chatbot kein großer Erfolg wurde. Probleme der LLMs wie enthaltene Vorurteile (bias) können nicht ausgeblendet werden. Außerdem sollte die Applikation auch gegen kreative Benutzereingaben abgesichert werden, die versuchen, das LLM zu anderen Zwecken als die von Ihnen gewünschten Abfragen zu verwenden.
Für eine firmeninterne Wissensdatenbank könnte das aber ein Ausgangspunkt für eine erfolgreiche Applikation sein. Es steht außer Frage, dass dabei kontinuierlich Tests laufen sollten, die die Qualität der Antworten nach zuvor festgelegten Regeln überprüfen.