Dela via


Använda LangChain med Azure Database for PostgreSQL

Azure Database for PostgreSQL integreras sömlöst med ledande llm-orkestreringspaket (large language model), till exempel LangChain. Med den här integreringen kan utvecklare använda avancerade AI-funktioner i sina program. LangChain kan effektivisera hanteringen och användningen av LLM:er, inbäddningsmodeller och databaser så att generativa AI-program blir enklare att utveckla.

Den här artikeln visar hur du använder den integrerade vektordatabasen i Azure Database for PostgreSQL för att lagra och hantera dokument i samlingar med LangChain. Den visar också hur du skapar index och utför vektorsökningsfrågor med hjälp av närmaste grannalgoritmer som cosinusavstånd, L2-avstånd (Euklidiska avstånd) och inre produkt för att hitta dokument nära frågevektorerna.

Stöd för vektor

Du kan använda Azure Database for PostgreSQL för att effektivt lagra och fråga miljontals vektorinbäddningar i PostgreSQL. Tjänsten kan hjälpa dig att skala dina AI-användningsfall från konceptbevis till produktion. Den erbjuder följande fördelar:

  • Tillhandahåller ett välbekant SQL-gränssnitt för att fråga vektorbäddningar och relationsdata.
  • pgvector Ökar med en snabbare och mer exakt likhetssökning över mer än 100 miljoner vektorer med hjälp av DiskANN-indexeringsalgoritmen.
  • Förenklar åtgärder genom att integrera relationsmetadata, vektorbäddningar och tidsseriedata i en enda databas.
  • Använder kraften i det robusta PostgreSQL-ekosystemet och Azure-molnplattformen för funktioner i företagsklass, inklusive replikering och hög tillgänglighet.

Autentisering

Azure Database for PostgreSQL stöder lösenordsbaserad och Microsoft Entra-autentisering (tidigare Azure Active Directory).

Med Microsoft Entra-autentisering kan du använda Microsoft Entra-ID för att autentisera till din PostgreSQL-server. Microsoft Entra-ID eliminerar behovet av att hantera separata användarnamn och lösenord för dina databasanvändare. Det gör att du kan använda samma säkerhetsmekanismer som du använder för andra Azure-tjänster.

I den här artikeln kan du använda någon av autentiseringsmetoderna.

Inställningar

Azure Database for PostgreSQL använder stöd för LangChain Postgres med öppen källkod för att ansluta till Azure Database for PostgreSQL. Ladda först ned partnerpaketet:

%pip install -qU langchain_postgres
%pip install -qU langchain-openai
%pip install -qU azure-identity

Aktivera pgvector i Azure Database for PostgreSQL

Se Aktivera och använda pgvector i Azure Database for PostgreSQL.

Konfigurera autentiseringsuppgifter

Du måste hämta anslutningsinformationen för Azure Database for PostgreSQL och lägga till dem som miljövariabler.

USE_ENTRA_AUTH Ange flaggan till True om du vill använda Microsoft Entra-autentisering. Om du använder Microsoft Entra-autentisering måste du ange de enda värd- och databasnamnen. Om du använder lösenordsautentisering måste du också ange användarnamnet och lösenordet.

import getpass
import os

USE_ENTRA_AUTH = True

# Supply the connection details for the database
os.environ["DBHOST"] = "<server-name>"
os.environ["DBNAME"] = "<database-name>"
os.environ["SSLMODE"] = "require"

if not USE_ENTRA_AUTH:
    # If you're using a username and password, supply them here
    os.environ["DBUSER"] = "<username>"
    os.environ["DBPASSWORD"] = getpass.getpass("Database Password:")

Konfigurera Azure OpenAI-inbäddningar

os.environ["AZURE_OPENAI_ENDPOINT"] = "<azure-openai-endpoint>"
os.environ["AZURE_OPENAI_API_KEY"] = getpass.getpass("Azure OpenAI API Key:")
AZURE_OPENAI_ENDPOINT = os.environ["AZURE_OPENAI_ENDPOINT"]
AZURE_OPENAI_API_KEY = os.environ["AZURE_OPENAI_API_KEY"]

from langchain_openai import AzureOpenAIEmbeddings

embeddings = AzureOpenAIEmbeddings(
    model="text-embedding-3-small",
    api_key=AZURE_OPENAI_API_KEY,
    azure_endpoint=AZURE_OPENAI_ENDPOINT,
    azure_deployment="text-embedding-3-small",
)

Initialisering

Använd Microsoft Entra-autentisering

Följande avsnitt innehåller funktioner som konfigurerar LangChain för att använda Microsoft Entra-autentisering. Funktionen get_token_and_username hämtar token för Azure Database for PostgreSQL-tjänsten med hjälp av DefaultAzureCredential från azure.identity-biblioteket. Det säkerställer att SQLAlchemy-motorn har en giltig token som du kan skapa nya anslutningar med. Den parsar också token, som är en JSON-webbtoken (JWT), för att extrahera användarnamnet som används för att ansluta till databasen.

Funktionen create_postgres_engine skapar en SQLAlchemy-motor som dynamiskt anger användarnamn och lösenord baserat på den token som hämtas från tokenhanteraren. Den här motorn kan skickas till connection-parametern för PGVector LangChain-vektorlager.

Logga in på Azure

Om du vill logga in på Azure kontrollerar du att Azure CLI är installerat. Kör följande kommando i terminalen:

az login

När du har loggat in hämtar följande kod token:

import base64
import json
from functools import lru_cache

from azure.identity import DefaultAzureCredential
from sqlalchemy import create_engine, event
from sqlalchemy.engine.url import URL


@lru_cache(maxsize=1)
def get_credential():
    """Memoized function to create the Azure credential, which caches tokens."""
    return DefaultAzureCredential()


def decode_jwt(token):
    """Decode the JWT payload to extract claims."""
    payload = token.split(".")[1]
    padding = "=" * (4 - len(payload) % 4)
    decoded_payload = base64.urlsafe_b64decode(payload + padding)
    return json.loads(decoded_payload)


def get_token_and_username():
    """Fetches a token and returns the username and token."""
    # Fetch a new token and extract the username
    token = get_credential().get_token(
        "https://ossrdbms-aad.database.windows.net/.default"
    )
    claims = decode_jwt(token.token)
    username = claims.get("upn")
    if not username:
        raise ValueError("Could not extract username from token. Have you logged in?")

    return username, token.token


def create_postgres_engine():
    db_url = URL.create(
        drivername="postgresql+psycopg",
        username="",  # This will be replaced dynamically
        password="",  # This will be replaced dynamically
        host=os.environ["DBHOST"],
        port=os.environ.get("DBPORT", 5432),
        database=os.environ["DBNAME"],
    )

    # Create a SQLAlchemy engine
    engine = create_engine(db_url, echo=True)

    # Listen for the connection event to inject dynamic credentials
    @event.listens_for(engine, "do_connect")
    def provide_dynamic_credentials(dialect, conn_rec, cargs, cparams):
        # Fetch the dynamic username and token
        username, token = get_token_and_username()

        # Override the connection parameters
        cparams["user"] = username
        cparams["password"] = token

    return engine

Använda lösenordsautentisering

Om du inte använder Microsoft Entra-autentisering get_connection_uri tillhandahåller du en anslutnings-URI som hämtar användarnamnet och lösenordet från miljövariabler:

import urllib.parse


def get_connection_uri():
    # Read URI parameters from the environment
    dbhost = os.environ["DBHOST"]
    dbname = os.environ["DBNAME"]
    dbuser = urllib.parse.quote(os.environ["DBUSER"])
    password = os.environ["DBPASSWORD"]
    sslmode = os.environ["SSLMODE"]

    # Construct the connection URI
    # Use Psycopg 3!
    db_uri = (
        f"postgresql+psycopg://{dbuser}:{password}@{dbhost}/{dbname}?sslmode={sslmode}"
    )
    return db_uri

Skapa vektorlagringsplatsen

from langchain_core.documents import Document
from langchain_postgres import PGVector
from langchain_postgres.vectorstores import PGVector

collection_name = "my_docs"

# The connection is either a SQLAlchemy engine or a connection URI
connection = create_postgres_engine() if USE_ENTRA_AUTH else get_connection_uri()

vector_store = PGVector(
    embeddings=embeddings,
    collection_name=collection_name,
    connection=connection,
    use_jsonb=True,
)

Hantering av vektorarkivet

Lägga till objekt i vektorlagret

Om du lägger till dokument efter ID skrivs alla befintliga dokument som matchar det ID:t över.

docs = [
    Document(
        page_content="there are cats in the pond",
        metadata={"id": 1, "location": "pond", "topic": "animals"},
    ),
    Document(
        page_content="ducks are also found in the pond",
        metadata={"id": 2, "location": "pond", "topic": "animals"},
    ),
    Document(
        page_content="fresh apples are available at the market",
        metadata={"id": 3, "location": "market", "topic": "food"},
    ),
    Document(
        page_content="the market also sells fresh oranges",
        metadata={"id": 4, "location": "market", "topic": "food"},
    ),
    Document(
        page_content="the new art exhibit is fascinating",
        metadata={"id": 5, "location": "museum", "topic": "art"},
    ),
    Document(
        page_content="a sculpture exhibit is also at the museum",
        metadata={"id": 6, "location": "museum", "topic": "art"},
    ),
    Document(
        page_content="a new coffee shop opened on Main Street",
        metadata={"id": 7, "location": "Main Street", "topic": "food"},
    ),
    Document(
        page_content="the book club meets at the library",
        metadata={"id": 8, "location": "library", "topic": "reading"},
    ),
    Document(
        page_content="the library hosts a weekly story time for kids",
        metadata={"id": 9, "location": "library", "topic": "reading"},
    ),
    Document(
        page_content="a cooking class for beginners is offered at the community center",
        metadata={"id": 10, "location": "community center", "topic": "classes"},
    ),
]

vector_store.add_documents(docs, ids=[doc.metadata["id"] for doc in docs])

Uppdatera objekt i vektorarkivet

docs = [
    Document(
        page_content="Updated - cooking class for beginners is offered at the community center",
        metadata={"id": 10, "location": "community center", "topic": "classes"},
    )
]
vector_store.add_documents(docs, ids=[doc.metadata["id"] for doc in docs])

Ta bort objekt från vektorarkivet

vector_store.delete(ids=["3"])

Frågor till vektorarkivet

När du har skapat vektorarkivet och lagt till relevanta dokument kan du fråga vektorarkivet i kedjan eller agenten.

Stöd för filtrering

Vektorarkivet stöder en uppsättning filter som kan användas mot metadatafälten i dokumenten:

Operatör Betydelse/kategori
$eq Likhet (==)
$ne Ojämlikhet (!=)
$lt Mindre än (<)
$lte Mindre än eller lika med (<=)
$gt Större än (>)
$gte Större än eller lika med (>=)
$in Speciell hantering (i)
$nin Specialfall (inte i)
$between Specialbehandlad (mellan)
$like Text (liknande)
$ilike Text (skiftlägesokänsligt som)
$and Logiskt (och)
$or Logiskt (eller)

Direktfråga

Du kan utföra en enkel likhetssökning på följande sätt:

results = vector_store.similarity_search(
    "kitty", k=10, filter={"id": {"$in": [1, 5, 2, 9]}}
)
for doc in results:
    print(f"* {doc.page_content} [{doc.metadata}]")
    * there are cats in the pond [{'id': 1, 'topic': 'animals', 'location': 'pond'}]
    * ducks are also found in the pond [{'id': 2, 'topic': 'animals', 'location': 'pond'}]
    * the new art exhibit is fascinating [{'id': 5, 'topic': 'art', 'location': 'museum'}]
    * the library hosts a weekly story time for kids [{'id': 9, 'topic': 'reading', 'location': 'library'}]

Om du anger en ordlista med flera fält men inga operatorer tolkas den översta nivån som ett logiskt AND filter:

vector_store.similarity_search(
    "ducks",
    k=10,
    filter={"id": {"$in": [1, 5, 2, 9]}, "location": {"$in": ["pond", "market"]}},
)
[Document(id='2', metadata={'id': 2, 'topic': 'animals', 'location': 'pond'}, page_content='ducks are also found in the pond'),
 Document(id='1', metadata={'id': 1, 'topic': 'animals', 'location': 'pond'}, page_content='there are cats in the pond')]
vector_store.similarity_search(
    "ducks",
    k=10,
    filter={
        "$and": [
            {"id": {"$in": [1, 5, 2, 9]}},
            {"location": {"$in": ["pond", "market"]}},
        ]
    },
)
[Document(id='2', metadata={'id': 2, 'topic': 'animals', 'location': 'pond'}, page_content='ducks are also found in the pond'),
 Document(id='1', metadata={'id': 1, 'topic': 'animals', 'location': 'pond'}, page_content='there are cats in the pond')]

Om du vill köra en likhetssökning och ta emot motsvarande poäng kan du köra:

results = vector_store.similarity_search_with_score(query="cats", k=1)
for doc, score in results:
    print(f"* [SIM={score:3f}] {doc.page_content} [{doc.metadata}]")
* [SIM=0.528338] there are cats in the pond [{'id': 1, 'topic': 'animals', 'location': 'pond'}]

En fullständig lista över sökningar som du kan köra i ett PGVector vektorlager finns i API-referensen.

Omvandling till retriever

Du kan också omvandla vektorlagret till en retriever för enklare användning i dina kedjor:

retriever = vector_store.as_retriever(search_type="mmr", search_kwargs={"k": 1})
retriever.invoke("kitty")
[Document(id='1', metadata={'id': 1, 'topic': 'animals', 'location': 'pond'}, page_content='there are cats in the pond')]

Aktuella begränsningar

  • langchain_postgres fungerar endast med Psycopg 3 (psycopg3). Uppdatera anslutningssträngarna från postgresql+psycopg2://... till postgresql+psycopg://langchain:langchain@....
  • Schemat för inbäddningsarkivet och samlingen har ändrats så att det add_documents fungerar korrekt med användardefinierade ID:er.
  • Du måste överföra ett explicit anslutningsobjekt nu.
  • För närvarande finns det ingen mekanism som stöder enkel datamigrering vid schemaändringar. Alla schemaändringar i vektorarkivet kräver att du återskapar tabellerna och lägger till dokumenten igen.