Erstellen von RAG-Anwendungen mit Azure-Datenbank für PostgreSQL und Python

Abgeschlossen

Da die Datenbank nun mit Einbettungen und Vektorindizes bereit ist, ist es an der Zeit, den Abruf in eine funktionierende Anwendung umzuwandeln. Das Ziel ist einfach: Nehmen Sie eine Benutzerfrage, rufen Sie die relevantesten Blöcke aus PostgreSQL ab und generieren Sie eine Antwort, die in diesen Blöcken mithilfe von Azure OpenAI über LangChain geerdet wird.

In dieser Lektion lernen Sie die Grundlagen der Erstellung einer RAG-Anwendung in Python kennen. Die Anwendung stellt eine Verbindung mit der Datenbank her, führt eine Ähnlichkeitssuche in der Datenbank aus und übergibt die Suchergebnisse an ein Modell mit klaren Anweisungen, um Halluzinationen zu vermeiden. Da das Modell basierend auf den Datenbankdaten einen klar definierten Kontext empfängt, kann es genauere und relevantere Antworten generieren, die in diesem Kontext basieren.

Retrieval-Augmented Generation mit PostgreSQL und LangChain

Erinnern Sie sich an unsere vorherigen Einheiten: Der RAG Flow ist ein Set von Schritten, das Abruf und Generierung kombiniert. Folgende Schritte werden hier ausgeführt:

  1. Der Benutzer stellt eine Frage.
  2. Die Datenbank berechnet eine Einbettung für die Frage und verwendet den Vektorindex, um die nächstgelegenen Übereinstimmungen in der Datenbank zu finden (obere Blöcke).
  3. Die oberen Blöcke werden mit einer Eingabeaufforderung an das Modell übergeben, die lautet: "Antworte nur aus diesem Kontext heraus. Wenn du es nicht weißt, sag es."
  4. Das Modell gibt eine antwort auf natürliche Sprache basierend auf dem bereitgestellten Kontext zurück.

LangChain bietet ein Framework zum Erstellen von Anwendungen mit Sprachmodellen. Es vereinfacht das Herstellen einer Verbindung mit verschiedenen Datenquellen, das Verwalten von Eingabeaufforderungen und das Behandeln von Antworten. Durch die Kombination von Azure Database for PostgreSQL mit Python und LangChain können Sie eine leistungsstarke RAG-Anwendung erstellen, die relevante Informationen abruft und genaue Antworten generiert. Während sich Ihre RAG-Anwendung weiterentwickeln kann, leiten diese Kernprinzipien ihre Entwicklung.

In dieser Anwendung verwenden Sie den LangChain AzureChatOpenAI-Wrapper , um mit dem Azure OpenAI-Dienst zu interagieren.

Für die folgenden Python-Beispiele verwenden Sie die folgende Tabellenstruktur:

CREATE TABLE company_policies (
    id SERIAL PRIMARY KEY,
    title TEXT,
    policy_text TEXT,
    embedding VECTOR(1536)
);

In dieser Tabelle wird davon ausgegangen, dass in der embedding Spalte ein Vektorindex erstellt wird, um effiziente Ähnlichkeitssuchen zu ermöglichen.

Sehen wir uns die Python-Codeausschnitte für jeden Schritt an. Die meisten RAG-Anwendungen folgen einer ähnlichen Struktur.

Herstellen der Verbindung mit der Datenbank

Halten Sie Verbindungen kurzlebig und sicher. In diesem Beispiel befinden sich geheime Schlüssel in Umgebungsvariablen und werden an die Verbindung übergeben.

import os, psycopg2
from contextlib import contextmanager

@contextmanager
def get_conn():
    conn = psycopg2.connect(
        host=os.getenv("PGHOST"),
        user=os.getenv("PGUSER"),
        password=os.getenv("PGPASSWORD"),
        dbname=os.getenv("PGDATABASE"),
        connect_timeout=10
    )
    try:
        yield conn
    finally:
        conn.close()

Dieses Skript richtet einen Kontext-Manager für Datenbankverbindungen ein, um sicherzustellen, dass sie nach der Verwendung ordnungsgemäß geschlossen werden. Zeit, zum tatsächlichen Abruf überzugehen.

Relevante Teile abrufen

Da der Benutzer eine Frage stellt, benötigen Sie eine Funktion, um die Datenbank mit dieser Frage abzufragen. Diese Funktion sollte die relevanten Blöcke basierend auf der Frage zurückgeben. In diesem Beispiel wird davon ausgegangen, dass der Vektorindex mithilfe des Kosinusabstands bewertet wird, sodass Sie die entsprechende Operatorklasse (<=>) für die Abfrage verwenden.

def retrieve_chunks(question, top_k=5):
    sql = """
    WITH q AS (
        SELECT azure_openai.create_embeddings(%s, %s)::vector AS qvec
    )
    SELECT id, title, policy_text
    FROM company_policies, q
    ORDER BY embedding <=> q.qvec
    LIMIT %s;
    """
    params = (os.getenv("OPENAI_EMBED_DEPLOYMENT"), question, top_k)
    with get_conn() as conn, conn.cursor() as cur:
        cur.execute(sql, params)
        rows = cur.fetchall()
    return [{"id": r[0], "title": r[1], "text": r[2]} for r in rows]

Daher ruft diese Funktion relevante Blöcke aus der Datenbank basierend auf der Frage des Benutzers ab. Diese Blöcke werden dann verwendet, um in einem nachfolgenden Schritt eine kontextbezogene Antwort zu generieren.

Generieren einer Antwort mit LangChain

Nachdem Sie nun über eine Funktion zum Abrufen relevanter Blöcke verfügen, müssen Sie mithilfe dieser Blöcke eine Antwort generieren. Das Modell sollte nur den abgerufenen Kontext verwenden und Richtlinientitel zitieren. Zeit zum Erstellen der Antwortgenerierungsfunktion.

from langchain_openai import AzureChatOpenAI

SYSTEM_PROMPT = """
You are a helpful assistant. Answer using ONLY the provided context.
If the answer is not in the context, say you don’t have enough information.
Cite policy titles in square brackets, e.g., [Vacation policy].
"""

def format_context(chunks):
    return "\n\n".join([f"[{c['title']}] {c['text']}" for c in chunks])

def generate_answer(question, chunks):
    llm = AzureChatOpenAI(
        azure_deployment=os.getenv("OPENAI_CHAT_DEPLOYMENT"),
        api_key=os.getenv("AZURE_OPENAI_API_KEY"),
        azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
        api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
        temperature=0
    )
    context = format_context(chunks)
    messages = [
        ("system", SYSTEM_PROMPT),
        ("human", f"Question: {question}\nContext:\n{context}")
    ]
    return llm.invoke(messages).content

Hinweis

So verwenden Sie AzureChatOpenAI:

  • Übergeben Sie Nachrichten als eine Liste von (Rolle, Inhalt) Tupeln (oder Nachrichtenobjekten).
  • Bereitstellen von Azure-Einstellungen über env vars und constructor: AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, azure_deployment und api_version.
  • Belassen Sie temperature=0 für sachliche Antworten. Größere Werte erhöhen kreativität, können aber die Genauigkeit verringern.

Diese Funktion ist der Kern der RAG-Anwendung und behandelt die Interaktion mit dem Sprachmodell. Beachten Sie, wie abgerufene Blöcke formatiert und im Kontext enthalten sind. Darüber hinaus ist die Systemaufforderung so konzipiert, dass das Modell dem bereitgestellten Kontext entspricht, und reduziert KI-generierte Antworten, die falsch sein könnten. Schließlich werden die Nachrichten vom Sprachmodell verarbeitet, um eine Antwort mithilfe der LangChain-Aufrufmethode zu generieren. Die invoke Methode wird mit den formatierten Nachrichten aufgerufen, und die Antwort des Modells wird als Text in natürlicher Sprache zurückgegeben.

Binden Sie es zusammen.

Das Letzte, was Sie benötigen, ist eine einfache Funktion, um den vollständigen Ablauf auszuführen:

def answer_question(question):
    chunks = retrieve_chunks(question)
    if not chunks:
        return "I couldn’t find relevant content in the policy store."
    return generate_answer(question, chunks)

# Quick test
print(answer_question("How many vacation days do employees get?"))

In diesem Beispiel rufen Sie die relevanten Blöcke aus der Datenbank ab und verwenden sie, um eine kontextbezogene Antwort zu generieren. Dieser Funktionsaufruf veranschaulicht den vollständigen Fluss von Frage zur Antwortgenerierung. Zuerst ruft sie die retrieve_chunks Funktion auf, um den relevanten Kontext abzurufen (im Grunde die zeilen, die von der Datenbank zurückgegeben werden). Diese Funktion übergibt dann diesen Kontext an die generate_answer Funktion, die mit dem Sprachmodell interagiert, um die endgültige Antwort (mithilfe der Zeilen als Teil des Kontexts) als natürliche Sprachantwort zu erzeugen. Der vollständige Ablauf stellt sicher, dass die Antwort in den abgerufenen Daten geerdet wird und eine genauere und zuverlässigere Antwort liefert.

Wichtige Erkenntnisse

Eine praktische RAG-Anwendung nimmt eine Benutzerfrage, erstellt eine Einbettung in SQL, verwendet einen Vektorindex, um die nächstgelegenen Passagen in der Datenbank abzurufen, und gibt nur diesen Kontext an das Modell weiter. Um Halluzinationen zu vermeiden, wird das Modell außerdem angewiesen, innerhalb des vorgegebenen Kontexts zu bleiben und zuzugeben, wenn Informationen fehlen. Halten Sie die Verbindungen kurzlebig, parametrisieren Sie Abfragen, und übergeben Sie geheime Schlüssel über Umgebungsvariablen. Verwenden Sie eine niedrige Temperatur für faktenbezogene Antworten und fügen Sie einfache Zitate (Titel oder IDs) ein, damit Antworten nachverfolgbar sind.

Sie sollten nun ein solides Verständnis dafür haben, wie Sie eine RAG-Anwendung (Retrieval Augmented Generation) mit Azure Database für PostgreSQL und Python erstellen. Während Ihre RAG-Anwendung in einem realen Szenario viel komplexer sein könnte, bleiben die Kernprinzipien gleich.