用 Azure Database for PostgreSQL 和 Python 建立 RAG 應用程式
現在資料庫已經準備好嵌入和向量索引,是時候將檢索變成一個可運作的應用程式了。 目標很簡單:從用戶提問,從 PostgreSQL 中擷取最相關的區塊,並透過 Azure OpenAI 透過 LangChain 生成基於這些區塊的答案。
在這個單元中,你會學習用 Python 建立 RAG 應用程式的基礎。 應用程式連接資料庫,執行相似度搜尋,並將搜尋結果傳給模型,並附有明確指示以避免產生幻覺。 由於模型會根據資料庫資料獲得明確的上下文,因此能根據該上下文產生更準確且相關的答案。
使用 PostgreSQL 和 LangChain 的檢索增強生成技术
記得我們之前單元中提到的 RAG 流程是一組結合檢索與生成的步驟。 讓我們將其拆解:
- 使用者提出問題。
- 資料庫會為問題計算嵌入,並利用向量索引尋找資料庫中最接近的匹配(頂尖區塊)。
- 頂端區塊會傳給模型,並提示:「 只能從此上下文回答。不知道就說吧。」
- 模型會根據所提供的情境回傳自然語言的答案。
LangChain 提供了一個用語言模型來建構應用程式的框架。 它簡化了連接各種資料來源、管理提示及處理回應的流程。 結合 Azure Database for PostgreSQL 與 Python 和 LangChain,你可以打造強大的 RAG 應用程式,擷取相關資訊並產生準確回應。 雖然您的RAG應用可能會演變,但這些核心原則將引導其開發。
在這個應用程式中,你會使用 LangChain AzureChatOpenAI 包裝器來與 Azure OpenAI 服務互動。
以下 Python 範例中,你使用以下表格結構:
CREATE TABLE company_policies (
id SERIAL PRIMARY KEY,
title TEXT,
policy_text TEXT,
embedding VECTOR(1536)
);
在這個表格中,你假設在欄位上建立 embedding 向量索引,以便有效率地進行相似度搜尋。
讓我們來複習每個步驟的 Python 程式碼片段。 大多數RAG申請遵循類似的結構。
連結至資料庫
保持連結短暫且安全。 在此範例中,秘密存在環境變數中並傳遞給連線。
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()
這個腳本會為資料庫連線建立情境管理器,確保使用後連結會被正確關閉。 該進入實際資料檢索階段了。
取回相關區塊
既然使用者在問問題,你需要一個函式來查詢資料庫並提出該問題。 該函式應該會根據問題回傳相關區塊。 這個範例假設向量索引是以 餘弦距離排序,因此查詢時會使用相應的運算子類別(<=>)。
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]
因此,這個函式會根據使用者的問題從資料庫中取得相關區塊。 這些區塊接著用來在後續步驟產生具情境感知的答案。
用 LangChain 產生答案
現在你有一個函式可以取得相關區塊,你需要用這些區塊來產生答案。 模型應僅使用檢索的上下文並引用政策標題。 是時候建立答案產生函數了。
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
備註
使用 AzureChatOpenAI:
- 將訊息傳遞為包含(角色, 內容)元組的清單(或訊息物件)。
- 透過環境變數和建構器提供 Azure 設定:
AZURE_OPENAI_API_KEYAZURE_OPENAI_ENDPOINTazure_deploymentapi_version。 - 請保留
temperature=0以顯示事實性的答案。 較大的數值能提升創意,但可能會降低準確度。
此函式是 RAG 應用程式的核心,負責與語言模型的互動。 注意檢索區塊的格式化與包含在上下文中的呈現方式。 此外,系統提示音設計確保模型符合所提供的情境,並減少可能錯誤的 AI 生成回應。 最後,語言模型會處理訊息,並利用 LangChain調用 方法產生回應。
invoke該方法以格式化訊息被呼叫,模型的回應以自然語言文字回傳。
把它綁在一起
你最不需要的是一個簡單的函式來執行完整流程:
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?"))
在這個例子中,你從資料庫中取得相關區塊,並用它們來產生一個情境感知的答案。 這個函式呼叫展示了從問題到答案產生的完整流程。 首先它會呼叫 retrieve_chunks 函式來取得相關的上下文(基本上是從資料庫回傳的列)。 這個函式接著將該上下文傳遞給 generate_answer 與語言模型互動的函數,以自然語言回應的方式產生最終答案(利用列作為上下文的一部分)。 完整流程確保答案基於檢索資料,提供更準確且可靠的回應。
重點摘要
實務上的 RAG 應用程式會接收使用者問題,建立 SQL 嵌入,使用向量索引擷取資料庫中最接近的段落,並只將該上下文交給模型。 為了降低幻覺,模型也會被指示僅在提供的內容範圍內作答,並在缺少資訊時坦承說明。 保持連線短暫,參數化查詢,並將秘密傳遞給環境變數。 對於事實性回答,請使用低溫度設定,並加入輕量化引用 (標題或 ID),以便回應可追溯。
你現在應該已經很清楚如何用 Azure Database for PostgreSQL 和 Python 建立檢索增強生成(RAG)應用程式。 雖然你的RAG應用在實際情境中可能更為複雜,但核心原則依然相同。