다음을 통해 공유


Azure Database for PostgreSQL에서 LangChain 사용

Azure Database for PostgreSQL은 LangChain과 같은 주요 LLM(대규모 언어 모델) 오케스트레이션 패키지와 원활하게 통합됩니다. 이 통합을 통해 개발자는 애플리케이션에서 고급 AI 기능을 사용할 수 있습니다. LangChain은 생성 AI 애플리케이션을 더 쉽게 개발할 수 있도록 LLM, 포함 모델 및 데이터베이스의 관리 및 사용을 간소화할 수 있습니다.

이 문서에서는 Azure Database for PostgreSQL의 통합 벡터 데이터베이스 를 사용하여 LangChain을 사용하여 컬렉션에 문서를 저장하고 관리하는 방법을 보여 줍니다. 또한 코사인 거리, L2 거리(유클리드 거리) 및 내부 제품과 같은 가장 인접한 알고리즘을 사용하여 인덱스를 만들고 벡터 검색 쿼리를 수행하여 쿼리 벡터에 가까운 문서를 찾는 방법을 보여 줍니다.

벡터 지원

Azure Database for PostgreSQL을 사용하여 PostgreSQL에 수백만 개의 벡터 포함을 효율적으로 저장하고 쿼리할 수 있습니다. 이 서비스는 개념 증명에서 프로덕션으로 AI 사용 사례를 확장하는 데 도움이 될 수 있습니다. 다음과 같은 이점을 제공합니다.

  • 벡터 포함 및 관계형 데이터를 쿼리하기 위한 친숙한 SQL 인터페이스를 제공합니다.
  • pgvectorDiskANN 인덱싱 알고리즘을 사용하여 1억 개 이상의 벡터에서 더 빠르고 정확한 유사성 검색을 향상시킵니다.
  • 관계형 메타데이터, 벡터 포함 및 시계열 데이터를 단일 데이터베이스에 통합하여 작업을 간소화합니다.
  • 복제 및 고가용성을 비롯한 엔터프라이즈급 기능을 위해 강력한 PostgreSQL 에코시스템 및 Azure 클라우드 플랫폼의 기능을 사용합니다.

인증

Azure Database for PostgreSQL은 암호 기반 및 Microsoft Entra (이전의 Azure Active Directory) 인증을 지원합니다.

Microsoft Entra 인증을 사용하면 Microsoft Entra ID를 사용하여 PostgreSQL 서버에 인증할 수 있습니다. Microsoft Entra ID를 사용하면 데이터베이스 사용자에 대해 별도의 사용자 이름과 암호를 관리할 필요가 없습니다. 이를 통해 다른 Azure 서비스에 사용하는 것과 동일한 보안 메커니즘을 사용할 수 있습니다.

이 문서에서는 인증 방법 중 하나를 사용할 수 있습니다.

설치 프로그램

Azure Database for PostgreSQL은 오픈 소스 LangChain Postgres 지원을 사용하여 Azure Database for PostgreSQL에 연결합니다. 먼저 파트너 패키지를 다운로드합니다.

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

Azure Database for PostgreSQL에서 pgvector 사용

Azure Database for PostgreSQL에서 pgvector 사용 및 사용을 참조하세요.

자격 증명 설정

Azure Database for PostgreSQL 연결 세부 정보를 가져와서 환경 변수로 추가해야 합니다.

USE_ENTRA_AUTH 플래그를 True로 설정하여 Microsoft Entra 인증을 사용합니다. Microsoft Entra 인증을 사용하는 경우 호스트 및 데이터베이스 이름만 제공해야 합니다. 암호 인증을 사용하는 경우 사용자 이름 및 암호도 설정해야 합니다.

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

Azure OpenAI 임베딩 설정

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

초기화

Microsoft Entra 인증 사용

다음 섹션에는 Microsoft Entra 인증을 사용하도록 LangChain을 설정하는 함수가 포함되어 있습니다. 이 함수 get_token_and_usernameazure.identity 라이브러리의 DefaultAzureCredential을(를) 사용하여 Azure Database for PostgreSQL 서비스의 토큰을 검색합니다. SQLAlchemy 엔진에 새 연결을 만들 수 있는 유효한 토큰이 있는지 확인합니다. 또한 JWT(JSON 웹 토큰)인 토큰을 구문 분석하여 데이터베이스에 연결하는 데 사용되는 사용자 이름을 추출합니다.

이 함수는 create_postgres_engine 토큰 관리자에서 가져온 토큰에 따라 사용자 이름과 암호를 동적으로 설정하는 SQLAlchemy 엔진을 만듭니다. 이 엔진을 connection로 LangChain 벡터 저장소의 PGVector 매개 변수에 전달할 수 있습니다.

Azure에 로그인

Azure에 로그인하려면 Azure CLI 가 설치되어 있는지 확인합니다. 터미널에서 다음 명령을 실행합니다.

az login

로그인한 후 다음 코드는 토큰을 가져옵니다.

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

암호 인증 사용

Microsoft Entra 인증 get_connection_uri 을 사용하지 않는 경우 환경 변수에서 사용자 이름과 암호를 가져오는 연결 URI를 제공합니다.

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

벡터 저장소 만들기

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

벡터 저장소 관리

벡터 저장소에 항목 추가

ID로 문서를 추가할 때, 해당 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])

벡터 저장소의 항목 업데이트

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

벡터 저장소에서 항목 삭제

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

벡터 저장소에 대한 쿼리

벡터 저장소를 만들고 관련 문서를 추가한 후 체인 또는 에이전트의 벡터 저장소를 쿼리할 수 있습니다.

필터링 지원

벡터 저장소는 문서의 메타데이터 필드에 적용할 수 있는 필터 집합을 지원합니다.

오퍼레이터 의미/범주
$eq 같음(==)
$ne Inequality(!=)
$lt 보다 작음(<)
$lte 작거나 같음(<=)
$gt 보다 큼(>)
$gte 크거나 같음(>=)
$in 특수 사례(in)
$nin 특정 예외(포함 안됨)
$between 특별한 경우 (중간)
$like 텍스트(like)
$ilike 텍스트(대/소문자 구분 없음)
$and 논리적(and)
$or 논리적(또는)

직접 쿼리

다음과 같이 간단한 유사성 검색을 수행할 수 있습니다.

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

여러 필드가 있지만 연산자가 없는 사전을 제공하는 경우 최상위 수준은 논리 AND 필터로 해석됩니다.

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

유사성 검색을 실행하고 해당 점수를 받으려면 다음을 실행할 수 있습니다.

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

벡터 저장소에서 PGVector 실행할 수 있는 검색의 전체 목록은 API 참조를 참조하세요.

리트리버로 변환

체인에서 더 쉽게 사용할 수 있는 리트리버로 벡터 저장소를 변환할 수도 있습니다.

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

현재 제한 사항

  • langchain_postgres 는 Psycopg 3(psycopg3)에서만 작동합니다. 연결 문자열을 postgresql+psycopg2://...에서 postgresql+psycopg://langchain:langchain@...로 업데이트합니다.
  • 포함 저장소 및 컬렉션의 스키마가 사용자 지정 ID에서 올바르게 작동하도록 add_documents 변경되었습니다.
  • 이제 명시적 연결 개체를 전달해야 합니다.
  • 현재 스키마 변경에 대한 쉬운 데이터 마이그레이션을 지원하는 메커니즘은 없습니다 . 벡터 저장소에서 스키마를 변경하려면 테이블을 다시 만들고 문서를 다시 추가해야 합니다.