Construir aplicações RAG com Azure Database para PostgreSQL e Python
Agora que a base de dados está pronta com embeddings e índices vetoriais, é altura de transformar a recuperação numa aplicação funcional. O objetivo é simples: pegar numa pergunta do utilizador, recuperar os chunks mais relevantes do PostgreSQL e gerar uma resposta baseada nesses chunks usando Azure OpenAI através do LangChain.
Nesta unidade, aprende o básico para construir uma aplicação RAG em Python. A aplicação liga-se à base de dados, executa uma pesquisa de similaridade na base de dados e encaminha os resultados da pesquisa para um modelo com instruções claras para evitar alucinações. Como o modelo recebe um contexto bem definido com base nos dados da base de dados, pode gerar respostas mais precisas e relevantes fundamentadas nesse contexto.
Geração Aumentada por Recuperação com PostgreSQL e LangChain
Lembre-se das nossas unidades anteriores: o fluxo RAG é um conjunto de etapas que combina recuperação e geração. Vamos por partes:
- O utilizador faz uma pergunta.
- A base de dados calcula um embedding para a pergunta e usa o índice vetorial para encontrar as correspondências mais próximas na base de dados (segmentos superiores).
- Os segmentos superiores são passados ao modelo com um prompt que diz: "Responder apenas a partir deste contexto. Se não sabes, diz."
- O modelo retorna uma resposta em linguagem natural baseada no contexto fornecido.
O LangChain fornece uma estrutura para construir aplicações com modelos de linguagem. Simplifica o processo de ligação a várias fontes de dados, gestão de prompts e tratamento de respostas. Ao combinar o Azure Database para PostgreSQL com Python e LangChain, pode criar uma aplicação RAG poderosa que recupera informações relevantes e gera respostas precisas. Embora a sua aplicação RAG possa evoluir, estes princípios fundamentais orientam o seu desenvolvimento.
Nesta aplicação, utiliza o wrapper LangChain AzureChatOpenAI para interagir com o serviço Azure OpenAI .
Para os seguintes exemplos em Python , utiliza-se a seguinte estrutura de tabelas:
CREATE TABLE company_policies (
id SERIAL PRIMARY KEY,
title TEXT,
policy_text TEXT,
embedding VECTOR(1536)
);
Nesta tabela, assume que é criado um índice vetorial na embedding coluna para permitir pesquisas de similaridade eficientes.
Vamos rever os excertos de código Python para cada etapa. A maioria das aplicações RAG segue uma estrutura semelhante.
Conectar-se ao banco de dados
Mantenha as ligações curtas e seguras. Neste exemplo, os segredos estão nas variáveis do ambiente e são passados para a ligação.
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()
Este script configura um gestor de contexto para ligações à base de dados, garantindo que são devidamente fechadas após a utilização. Chegou a hora de passar para a recolha real de dados.
Recuperar os pedaços relevantes
Como o utilizador está a fazer uma pergunta, precisa de uma função para consultar a base de dados com essa pergunta. Essa função deve devolver os blocos relevantes com base na pergunta. Este exemplo assume que o índice de vetor é ordenado usando a distância cosseno, pelo que se utiliza a respetiva classe de operador (<=>) para a consulta.
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]
Assim, esta função recupera partes relevantes da base de dados com base na pergunta do utilizador. Estes blocos são então usados para gerar uma resposta consciente do contexto numa etapa subsequente.
Gerar uma resposta com LangChain
Agora que tens uma função para recuperar chunks relevantes, precisas de gerar uma resposta usando esses chunks. O modelo deve apenas usar o contexto recuperado e citar os títulos das políticas. Está na hora de criar a função de geração de respostas.
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
Observação
Para usar AzureChatOpenAI:
- Passe mensagens como uma lista de tuplas (função, conteúdo) ou objetos de mensagem.
- Forneça as definições Azure via variáveis de ambiente e construtor:
AZURE_OPENAI_API_KEY,AZURE_OPENAI_ENDPOINT, eazure_deployment,api_version. - Guarda
temperature=0para respostas factuais. Valores maiores aumentam a criatividade, mas podem reduzir a precisão.
Esta função é o núcleo da aplicação RAG, tratando da interação com o modelo de linguagem. Repare como os blocos recuperados são formatados e incluídos no contexto. Além disso, o prompt do sistema foi concebido para garantir que o modelo cumpre o contexto fornecido e reduz as respostas geradas pela IA que possam estar incorretas. Finalmente, as mensagens são processadas pelo modelo de linguagem para gerar uma resposta usando o método de invocação LangChain. O invoke método é chamado com as mensagens formatadas, e a resposta do modelo é devolvida como texto em linguagem natural.
Liga tudo
A última coisa que precisas é de uma função simples para executar o fluxo completo:
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?"))
Neste exemplo, recupera os blocos relevantes da base de dados e usa-os para gerar uma resposta consciente do contexto. Esta chamada de função demonstra o fluxo completo da geração da pergunta para a resposta. Primeiro, chama a retrieve_chunks função para obter o contexto relevante (basicamente as linhas devolvidas da base de dados). Esta função passa então esse contexto para a generate_answer função que interage com o modelo de linguagem para produzir a resposta final (usando as linhas como parte do contexto) como resposta em linguagem natural. O fluxo completo assegura que a resposta está fundamentada nos dados recuperados, proporcionando uma resposta mais precisa e fiável.
Principais conclusões
Uma aplicação RAG prática pega numa pergunta do utilizador, cria um embedding em SQL, usa um índice vetorial para obter as passagens mais próximas da base de dados e entrega apenas esse contexto ao modelo. Para reduzir alucinações, o modelo também é instruído a manter-se dentro do contexto fornecido e admitir quando falta informação. Mantenha as ligações de curta duração; parametrize as consultas; e passe segredos através de variáveis de ambiente. Use uma temperatura baixa para respostas factuais e inclua citações leves (títulos ou IDs) para que as respostas sejam rastreáveis.
Agora deves ter uma compreensão sólida de como construir uma aplicação de Geração Aumentada de Recuperação (RAG) usando o Azure Database para PostgreSQL e Python. Embora a sua aplicação RAG possa ser muito mais complexa num cenário real, os princípios fundamentais mantêm-se os mesmos.