Condividi tramite


Uso di LangChain con Database di Azure per PostgreSQL

Database di Azure per PostgreSQL si integra perfettamente con i principali pacchetti di orchestrazione LLM (Large Language Model), ad esempio LangChain, consentendo agli sviluppatori di sfruttare la potenza delle funzionalità avanzate di intelligenza artificiale all'interno delle applicazioni. LangChain può semplificare la gestione e l'uso di VM, modelli di incorporamento e database, semplificando lo sviluppo di applicazioni di intelligenza artificiale generative.

Questa esercitazione illustra come usare il database vettoriale integrato di Database di Azure per PostgreSQL per archiviare e gestire documenti nelle raccolte con LangChain. Viene inoltre illustrato come creare indici ed eseguire query di ricerca vettoriali usando algoritmi vicini approssimativi, ad esempio Distanza coseno, L2 (distanza euclidea) e IP (prodotto interno) per individuare i documenti vicini ai vettori di query.

Supporto vettoriale

Database di Azure per PostgreSQL - Il server flessibile consente di archiviare ed eseguire query in modo efficiente su milioni di incorporamenti vettoriali in PostgreSQL e ridimensionare i casi d'uso di intelligenza artificiale dal modello di verifica alla produzione:

  • Fornisce un'interfaccia SQL familiare per l'esecuzione di query su incorporamenti vettoriali e dati relazionali.
  • Aumenta pgvector con una ricerca di somiglianza più rapida e precisa in più di 100 milioni di vettori usando l'algoritmo di indicizzazione DiskANN.
  • Semplifica le operazioni integrando metadati relazionali, incorporamenti vettoriali e dati di serie temporali in un singolo database.
  • Usa la potenza dell'ecosistema PostgreSQL affidabile e del cloud di Azure per le funzionalità di livello aziendale, tra cui la replica e la disponibilità elevata.

Autenticazione

Database di Azure per PostgreSQL : il server flessibile supporta l'autenticazione basata su password e Microsoft Entra (in precedenza Azure Active Directory). L'autenticazione Entra consente di usare l'identità Entra per eseguire l'autenticazione nel server PostgreSQL. L'ID Entra elimina la necessità di gestire nomi utente e password separati per gli utenti del database e consente di usare gli stessi meccanismi di sicurezza usati per altri servizi di Azure.

Questo notebook è configurato per l'uso di entrambi i metodi di autenticazione. È possibile configurare se usare o meno l'autenticazione Entra in un secondo momento nel notebook.

Configurazione

Azure Database per PostgreSQL utilizza il supporto Postgres open-source di LangChain per connettersi ad Azure Database per PostgreSQL. Scaricare innanzitutto il pacchetto partner:

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

Abilitare pgvector in Database di Azure per PostgreSQL - Server flessibile

Vedere le istruzioni di abilitazione per Database di Azure per PostgreSQL.

Credenziali

Per eseguire questo notebook sono necessari i dettagli della connessione di Database di Azure per PostgreSQL e di aggiungerli come variabili di ambiente.

Impostare il USE_ENTRA_AUTH flag su True se si vuole usare l'autenticazione di Microsoft Entra. Se si usa l'autenticazione Entra, è sufficiente specificare l'host e il nome del database. Se si usa l'autenticazione della password, sarà necessario impostare anche il nome utente e la password.

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 using a username and password, supply them here
    os.environ["DBUSER"] = "<username>"
    os.environ["DBPASSWORD"] = getpass.getpass("Database Password:")

Configurare incorporamenti OpenAI di Azure

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",
)

Inizializzazione

Autenticazione Microsoft Entra

La cella seguente contiene funzioni che configurano LangChain per l'uso dell'autenticazione Entra. Fornisce una funzione get_token_and_username che recupera i token per il servizio Database di Azure per PostgreSQL usando DefaultAzureCredential dalla azure.identity libreria. Garantisce che il motore sqlalchemy abbia un token valido con cui creare nuove connessioni. Analizza anche il token, ovvero un token Web Java (JWT), per estrarre il nome utente usato per connettersi al database.

La funzione create_postgres_engine crea un'istanza di sqlalchemy Engine che imposta dinamicamente il nome utente e la password in base al token recuperato da TokenManager. Questo Engine valore può essere passato al connection parametro di PGVector LangChain VectorStore.

Accesso ad Azure

Per accedere ad Azure, assicurarsi di avere installato l'Azure CLI. È necessario eseguire il comando seguente nel terminale:

az login

Dopo l'accesso, il codice seguente recupera il 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 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

Autenticazione della password

Se non si usa l'autenticazione Entra, fornisce get_connection_uri un URI di connessione che esegue il pull del nome utente e della password dalle variabili di ambiente.

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 connection URI
    # Use psycopg 3!
    db_uri = (
        f"postgresql+psycopg://{dbuser}:{password}@{dbhost}/{dbname}?sslmode={sslmode}"
    )
    return db_uri

Creazione dell'archivio vettoriale

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,
)

Gestire l'archivio vettoriale

Aggiungere elementi all'archivio vettoriale

L'aggiunta di documenti per ID sovrascrive tutti i documenti esistenti che corrispondono a tale ID.

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])

Aggiornare gli elementi nell'archivio vettoriale

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])

Eliminare elementi dall'archivio vettoriale

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

Eseguire un query nell'archivio vettoriale

Dopo che il tuo archivio vettoriale è stato creato e i documenti pertinenti sono stati aggiunti, puoi eseguire una query sull'archivio vettoriale nella tua catena o agente.

Supporto per i filtri

L'archivio vettoriale supporta un set di filtri che possono essere applicati ai campi di metadati dei documenti.

Operatore Significato/Categoria
$eq Uguaglianza (==)
$ne Disuguaglianza (!=)
$lt Minore di (<)
$lte Minore o uguale (<=)
$gt Maggiore di (>)
$gte Maggiore o uguale a (>=)
$in Combinazione di maiuscole/minuscole speciale (in)
$nin Combinazione di maiuscole/minuscole speciale (non in)
$between Combinazione di maiuscole/minuscole speciale (tra)
$like Testo (ad esempio)
$ilike Testo (senza distinzione tra maiuscole e minuscole)
$and Logico (e)
$or Logico (o)

Eseguire una query direttamente

L'esecuzione di una semplice ricerca di somiglianza può essere eseguita nel modo seguente:

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'}]

Se si specifica un dict con più campi, ma nessun operatore, il livello superiore viene interpretato come filtro AND logico

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')]

Se si vuole eseguire una ricerca di somiglianza e ricevere i punteggi corrispondenti, è possibile eseguire:

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'}]

Per un elenco completo delle diverse ricerche che è possibile eseguire in un PGVector archivio vettoriale, vedere le informazioni di riferimento sulle API.

Eseguire una query utilizzando una funzione di recupero

È anche possibile trasformare l'archivio vettoriale in un retriever per semplificare l'utilizzo nelle catene.

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')]

Limitazioni correnti

  • langchain_postgres funziona solo con psycopg3. Aggiornare le stringhe di connessione da postgresql+psycopg2://... a postgresql+psycopg://langchain:langchain@...
  • Lo schema dell'archivio di embedding e della raccolta è stato modificato per consentire a add_documents di funzionare correttamente con gli ID specificati dall'utente.
  • È ora necessario passare un oggetto di connessione esplicito.

Attualmente , non esiste alcun meccanismo che supporta una semplice migrazione dei dati sulle modifiche dello schema. Qualsiasi modifica dello schema nell'archivio vettoriale richiede quindi all'utente di ricreare le tabelle e di leggere i documenti.