Créer des applications RAG avec Azure Database pour PostgreSQL et Python
Maintenant que la base de données est prête avec des incorporations et des index vectoriels, il est temps de transformer la récupération en application opérationnelle. L’objectif est simple : prenez une question utilisateur, récupérez les blocs les plus pertinents à partir de PostgreSQL et générez une réponse ancrée dans ces blocs à l’aide d’Azure OpenAI via LangChain.
Dans cette unité, vous allez apprendre les bases de la création d’une application RAG en Python. L’application se connecte à la base de données, exécute une recherche de similarité sur la base de données et transmet les résultats de la recherche à un modèle avec des instructions claires pour éviter les hallucinations. Étant donné que le modèle reçoit un contexte bien défini en fonction des données de base de données, il peut générer des réponses plus précises et pertinentes basées sur ce contexte.
Génération augmentée par la récupération avec PostgreSQL et LangChain
Rappelez-vous de nos unités précédentes, le flux RAG est un ensemble d’étapes qui combine la récupération et la génération. Nous allons la décomposer :
- L’utilisateur pose une question.
- La base de données calcule une incorporation pour la question et utilise l’index vectoriel pour rechercher les correspondances les plus proches dans la base de données (blocs principaux).
- Les blocs principaux sont passés au modèle avec une invite indiquant : « Ne répondez qu'à partir de ce contexte. Si vous ne savez pas, dites-le.
- Le modèle retourne une réponse en langage naturel en fonction du contexte fourni.
LangChain fournit une infrastructure pour la création d’applications avec des modèles de langage. Il simplifie le processus de connexion à différentes sources de données, la gestion des invites et la gestion des réponses. En combinant Azure Database pour PostgreSQL avec Python et LangChain, vous pouvez créer une application RAG puissante qui récupère des informations pertinentes et génère des réponses précises. Bien que votre application RAG puisse évoluer, ces principes fondamentaux guident son développement.
Dans cette application, vous utilisez le wrapper LangChain AzureChatOpenAI pour interagir avec le service Azure OpenAI .
Pour les exemples Python suivants, vous utilisez la structure de tableau suivante :
CREATE TABLE company_policies (
id SERIAL PRIMARY KEY,
title TEXT,
policy_text TEXT,
embedding VECTOR(1536)
);
Dans ce tableau, vous supposez qu’un index vectoriel est créé sur la embedding colonne pour permettre des recherches de similarité efficaces.
Examinons les extraits de code Python pour chaque étape. La plupart des applications RAG suivent une structure similaire.
Se connecter à la base de données
Conservez les connexions de courte durée et sécurisées. Dans cet exemple, les secrets se trouvent dans des variables d’environnement et sont passés à la connexion.
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()
Ce script configure un gestionnaire de contexte pour les connexions de base de données, ce qui garantit qu’ils sont correctement fermés après l’utilisation. Il est temps de passer à la récupération proprement dite.
Récupérer les blocs appropriés
Étant donné que l’utilisateur pose une question, vous avez besoin d’une fonction pour interroger la base de données avec cette question. Cette fonction doit retourner les blocs pertinents en fonction de la question. Cet exemple suppose que l’index vectoriel est classé à l’aide de la distance de cosinus, de sorte que vous utilisez la classe d’opérateur (<=>) respective pour l’interrogation.
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]
Ainsi, cette fonction récupère les blocs pertinents de la base de données en fonction de la question de l’utilisateur. Ces blocs sont ensuite utilisés pour générer une réponse prenant en charge le contexte à l’étape suivante.
Générer une réponse avec LangChain
Maintenant que vous avez une fonction pour récupérer des blocs pertinents, vous devez générer une réponse à l’aide de ces blocs. Le modèle ne doit utiliser que le contexte récupéré et citer les titres de stratégie. Temps de création de la fonction de génération de réponses.
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
Note
Pour utiliser AzureChatOpenAI :
- Transmettez des messages sous la forme d’une liste de tuples (rôle, contenu) (ou d’objets de message).
- Fournissez des paramètres Azure via les vars et le constructeur env :
AZURE_OPENAI_API_KEY,AZURE_OPENAI_ENDPOINTetazure_deployment,api_version. - Conservez
temperature=0les réponses factuelles. Les valeurs plus importantes augmentent la créativité, mais peuvent réduire la précision.
Cette fonction est le cœur de l’application RAG, qui gère l’interaction avec le modèle de langage. Notez comment les blocs récupérés sont mis en forme et inclus dans le contexte. En outre, l’invite système est conçue pour s'assurer que le modèle adhère au contexte fourni, ce qui réduit le nombre de réponses générées par l’IA susceptibles d’être incorrectes. Enfin, les messages sont traités par le modèle de langage pour générer une réponse à l’aide de la méthode d’appel LangChain. La invoke méthode est appelée avec les messages mis en forme et la réponse du modèle est retournée en tant que texte en langage naturel.
Reliez le tout
La dernière chose dont vous avez besoin est une fonction simple pour exécuter le flux complet :
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?"))
Dans cet exemple, vous récupérez les blocs pertinents de la base de données et les utilisez pour générer une réponse contextuelle. Cet appel de fonction illustre le flux complet de la génération de questions à réponses. Tout d’abord, il appelle la retrieve_chunks fonction pour obtenir le contexte approprié (essentiellement les lignes retournées par la base de données). Cette fonction transmet ensuite ce contexte à la generate_answer fonction qui interagit avec le modèle de langage pour produire la réponse finale (à l’aide des lignes dans le cadre du contexte) en tant que réponse en langage naturel. Le flux complet garantit que la réponse est ancrée dans les données récupérées, fournissant une réponse plus précise et fiable.
Points clés à prendre
Une application RAG pratique prend une question utilisateur, crée une incorporation dans SQL, utilise un index vectoriel pour extraire les passages les plus proches de la base de données, et ne remet que ce contexte au modèle. Pour réduire l’hallucination, le modèle est également invité à rester dans le contexte fourni et à admettre quand des informations sont manquantes. Conservez les connexions de courte durée, paramétrez les requêtes et transmettez des secrets via des variables d’environnement. Utilisez une faible température pour les réponses factuelles et incluez des citations légères (titres ou ID) afin que les réponses soient traceables.
Vous devez maintenant avoir une bonne compréhension de la création d’une application RAG (Récupération augmentée) à l’aide d’Azure Database pour PostgreSQL et Python. Bien que votre application RAG soit beaucoup plus complexe dans un scénario réel, les principes fondamentaux restent les mêmes.