Azure Cosmos DB for NoSQL 통합
Azure Cosmos DB for NoSQL 통합
LangChain JavaScript로 Azure Cosmos DB for NoSQL 벡터 스토어와 통합해요.
Azure Cosmos DB for NoSQL은 유연한 스키마와 JSON 네이티브 지원으로 항목을 쿼리할 수 있게 해줘요. 이제 벡터 인덱싱과 검색을 제공해요. 이 기능은 고차원 벡터를 처리하도록 설계되어 어떤 규모에서도 효율적이고 정확한 벡터 검색을 가능하게 해요. 이제 데이터와 함께 문서에 직접 벡터를 저장할 수 있어요. 데이터베이스의 각 문서는 전통적인 스키마 없는 데이터뿐만 아니라 문서의 다른 속성으로 고차원 벡터도 포함할 수 있어요.
Azure Cosmos DB for NoSQL의 벡터 검색 기능 활용 방법은 이 페이지에서 배울 수 있어요. Azure 계정이 없다면 무료 계정을 만들면 시작할 수 있어요.
설정 (Setup)
먼저 @langchain/azure-cosmosdb 패키지를 설치해야 해요:
npm install @langchain/azure-cosmosdb @langchain/core
실행 중인 Azure Cosmos DB for NoSQL 인스턴스도 필요해요. 이 가이드를 따라 Azure Portal에서 무료 버전을 비용 없이 배포할 수 있어요.
인스턴스가 실행되면 연결 문자열이 있는지 확인하세요. Azure Portal의 인스턴스 "Settings / Keys" 섹션에서 찾을 수 있어요. 그런 다음 다음 환경 변수를 설정해야 해요:
# Use connection string to authenticate
AZURE_COSMOSDB_NOSQL_CONNECTION_STRING=
# Use managed identity to authenticate
AZURE_COSMOSDB_NOSQL_ENDPOINT=
Azure Managed Identity 사용 (Using Azure Managed identity)
Azure Managed Identity를 사용한다면 자격 증명을 이렇게 구성할 수 있어요:
import { AzureCosmosDBNoSQLVectorStore } from "@langchain/azure-cosmosdb";
import { OpenAIEmbeddings } from "@langchain/openai";
// Create Azure Cosmos DB vector store
const store = new AzureCosmosDBNoSQLVectorStore(new OpenAIEmbeddings(), {
// Or use environment variable AZURE_COSMOSDB_NOSQL_ENDPOINT
endpoint: "https://my-cosmosdb.documents.azure.com:443/",
// Database and container must already exist
databaseName: "my-database",
containerName: "my-container",
});
필터 사용 시 보안 고려 사항 (Security considerations when using filters)
원시 사용자 입력을 WHERE ${userFilter} 같은 SQL 유사 절에 연결하는 것은 SQL 인젝션 공격의 치명적인 위험을 초래해 의도하지 않은 데이터를 노출하거나 시스템 무결성을 손상시킬 수 있어요. 이를 완화하려면 항상 Azure Cosmos DB의 파라미터화된 쿼리 메커니즘을 사용해 @param 자리표시자를 전달하고, 쿼리 로직을 사용자 제공 입력에서 깔끔하게 분리하세요.
안전하지 않은 코드의 예:
import { AzureCosmosDBNoSQLVectorStore } from "@langchain/azure-cosmosdb";
const store = new AzureCosmosDBNoSQLVectorStore(embeddings, {});
// Unsafe: user-controlled input injected into the query
const userId = req.query.userId; // e.g. "123' OR 1=1"
const unsafeQuerySpec = {
query: `SELECT * FROM c WHERE c.metadata.userId = '${userId}'`,
};
await store.delete({ filter: unsafeQuerySpec });
공격자가 123 OR 1=1을 제공하면 쿼리는 SELECT * FROM c WHERE c.metadata.userId = '123' OR 1=1이 되어 조건이 항상 참이 되게 강제해 의도한 필터를 우회하고 모든 문서를 삭제하게 돼요.
이 인젝션 위험을 막으려면 @userId 같은 자리표시자를 정의하고 Cosmos DB가 사용자 입력을 별도의 파라미터로 바인딩해, 아래와 같이 엄격히 데이터로 처리되고 실행 가능한 쿼리 로직으로는 처리되지 않게 하세요.
import { SqlQuerySpec } from "@azure/cosmos";
const safeQuerySpec: SqlQuerySpec = {
query: "SELECT * FROM c WHERE c.metadata.userId = @userId",
parameters: [{ name: "@userId", value: userId }],
};
await store.delete({ filter: safeQuerySpec });
이제 공격자가 123 OR 1=1을 입력하면 입력은 일치시킬 문자 리터럴 값으로 처리되며, 쿼리 구조의 일부로는 처리되지 않아요.
더 많은 사용 예와 모범 사례는 Azure Cosmos DB for NoSQL의 파라미터화된 쿼리 공식 문서를 참고하세요.
사용 예제 (Usage example)
아래는 파일의 문서를 Azure Cosmos DB for NoSQL에 인덱싱하고, 벡터 검색 쿼리를 실행한 뒤, 마지막으로 검색된 문서를 기반으로 자연어로 질문에 답하는 체인을 사용하는 예제예요.
import { AzureCosmosDBNoSQLVectorStore } from "@langchain/azure-cosmosdb";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { ChatOpenAI, OpenAIEmbeddings } from "@langchain/openai";
import { createStuffDocumentsChain } from "@langchain/classic/chains/combine_documents";
import { createRetrievalChain } from "@langchain/classic/chains/retrieval";
import { TextLoader } from "@langchain/classic/document_loaders/fs/text";
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
// Load documents from file
const loader = new TextLoader("./state_of_the_union.txt");
const rawDocuments = await loader.load();
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 0,
});
const documents = await splitter.splitDocuments(rawDocuments);
// Create Azure Cosmos DB vector store
const store = await AzureCosmosDBNoSQLVectorStore.fromDocuments(
documents,
new OpenAIEmbeddings(),
{
databaseName: "langchain",
containerName: "documents",
}
);
// Performs a similarity search
const resultDocuments = await store.similaritySearch(
"What did the president say about Ketanji Brown Jackson?"
);
console.log("Similarity search results:");
console.log(resultDocuments[0].pageContent);
// Use the store as part of a chain
const model = new ChatOpenAI({ model: "gpt-3.5-turbo-1106" });
const questionAnsweringPrompt = ChatPromptTemplate.fromMessages([
[
"system",
"Answer the user's questions based on the below context:\n\n{context}",
],
["human", "{input}"],
]);
const combineDocsChain = await createStuffDocumentsChain({
llm: model,
prompt: questionAnsweringPrompt,
});
const chain = await createRetrievalChain({
retriever: store.asRetriever(),
combineDocsChain,
});
const res = await chain.invoke({
input: "What is the president's top priority regarding prices?",
});
console.log("Chain response:");
console.log(res.answer);
// Clean up
await store.delete();
고급 검색 옵션 (Advanced search options)
모든 검색 유형은 필터 옵션의 searchType 파라미터를 사용해 통합된 similaritySearch와 similaritySearchWithScore 메서드를 통해 접근해요. 검색 유형은 AzureCosmosDBNoSQLSearchType의 상수로 사용 가능해요:
사용 가능한 검색 유형:
AzureCosmosDBNoSQLSearchType.Vector(기본): 표준 벡터 유사도 검색AzureCosmosDBNoSQLSearchType.VectorScoreThreshold: 최소 점수 필터가 있는 벡터 검색AzureCosmosDBNoSQLSearchType.FullTextSearch: FullTextContains를 사용한 전체 텍스트 검색 (프리뷰)AzureCosmosDBNoSQLSearchType.FullTextRanking: BM25 랭킹을 사용한 전체 텍스트 검색 (프리뷰)AzureCosmosDBNoSQLSearchType.Hybrid: RRF를 사용한 하이브리드 벡터 + 전체 텍스트 검색 (프리뷰)AzureCosmosDBNoSQLSearchType.HybridScoreThreshold: 점수 임계값이 있는 하이브리드 검색 (프리뷰)
스토어를 만들 때 defaultSearchType 구성 옵션으로 기본 검색 유형을 설정할 수도 있어요, 그래서 매 쿼리에 지정하지 않아도 돼요:
import {
AzureCosmosDBNoSQLVectorStore,
AzureCosmosDBNoSQLSearchType,
} from "@langchain/azure-cosmosdb";
import { OpenAIEmbeddings } from "@langchain/openai";
const store = new AzureCosmosDBNoSQLVectorStore(new OpenAIEmbeddings(), {
databaseName: "langchain",
containerName: "documents",
defaultSearchType: AzureCosmosDBNoSQLSearchType.VectorScoreThreshold,
});
점수 임계값이 있는 벡터 검색 (Vector search with score threshold)
최소 유사도 점수에 따라 결과를 필터링:
import {
AzureCosmosDBNoSQLVectorStore,
AzureCosmosDBNoSQLSearchType,
} from "@langchain/azure-cosmosdb";
import { OpenAIEmbeddings } from "@langchain/openai";
const store = new AzureCosmosDBNoSQLVectorStore(new OpenAIEmbeddings(), {
databaseName: "langchain",
containerName: "documents",
});
// Only return results with similarity score >= 0.8
const results = await store.similaritySearchWithScore(
"What is the capital of France?",
10,
{
searchType: AzureCosmosDBNoSQLSearchType.VectorScoreThreshold,
threshold: 0.8,
}
);
for (const [doc, score] of results) {
console.log(`Score: ${score}, Content: ${doc.pageContent}`);
}
최대 한계 관련성(MMR) 검색 (Maximal Marginal Relevance search)
MMR 검색은 결과의 관련성과 다양성의 균형을 맞춰요:
import { AzureCosmosDBNoSQLVectorStore } from "@langchain/azure-cosmosdb";
import { OpenAIEmbeddings } from "@langchain/openai";
const store = new AzureCosmosDBNoSQLVectorStore(new OpenAIEmbeddings(), {
databaseName: "langchain",
containerName: "documents",
});
const results = await store.maxMarginalRelevanceSearch("machine learning", {
k: 5, // Number of results to return
fetchK: 20, // Number of candidates to consider
lambda: 0.5, // 0 = max diversity, 1 = max relevance
});
전체 텍스트·하이브리드 검색 (프리뷰) (Full-text and hybrid search)
전체 텍스트 또는 하이브리드 검색을 사용하려면 스토어를 만들 때 활성화하세요:
import {
AzureCosmosDBNoSQLVectorStore,
AzureCosmosDBNoSQLSearchType,
} from "@langchain/azure-cosmosdb";
import { OpenAIEmbeddings } from "@langchain/openai";
const store = new AzureCosmosDBNoSQLVectorStore(new OpenAIEmbeddings(), {
databaseName: "langchain",
containerName: "documents",
fullTextSearchEnabled: true,
fullTextPolicy: {
defaultLanguage: "en-US",
fullTextPaths: [{ path: "/text", language: "en-US" }],
},
indexingPolicy: {
indexingMode: "consistent",
automatic: true,
includedPaths: [{ path: "/*" }],
excludedPaths: [{ path: "/_etag/?" }],
vectorIndexes: [{ path: "/vector", type: "quantizedFlat" }],
fullTextIndexes: [{ path: "/text" }],
},
});
전체 텍스트 검색 (Full-text search)
// Full-text search using FullTextContains in the filter clause
const fullTextResults = await store.similaritySearch("", 10, {
searchType: AzureCosmosDBNoSQLSearchType.FullTextSearch,
filterClause: "WHERE FullTextContains(c.text, 'artificial intelligence')",
});
전체 텍스트 랭킹 (Full-text ranking)
// Full-text ranking with BM25 scoring
const rankingResults = await store.similaritySearch("", 10, {
searchType: AzureCosmosDBNoSQLSearchType.FullTextRanking,
fullTextRankFilter: [
{ searchField: "text", searchText: "artificial intelligence" },
],
});
하이브리드 검색 (Hybrid search)
하이브리드 검색은 Reciprocal Rank Fusion(RRF)을 사용해 벡터 유사도를 전체 텍스트 검색과 결합해요:
// Hybrid search combining vector and full-text results
const hybridResults = await store.similaritySearchWithScore(
"machine learning",
10,
{
searchType: AzureCosmosDBNoSQLSearchType.Hybrid,
fullTextRankFilter: [
{ searchField: "text", searchText: "machine learning" },
],
}
);
// Hybrid search with score threshold
const filteredResults = await store.similaritySearchWithScore(
"machine learning",
10,
{
searchType: AzureCosmosDBNoSQLSearchType.HybridScoreThreshold,
fullTextRankFilter: [
{ searchField: "text", searchText: "machine learning" },
],
threshold: 0.5,
}
);
유틸리티 메서드 (Utility methods)
문서 삭제 (Delete documents)
// Delete specific documents by ID
await store.delete({ ids: ["document-id-123"] });
// Delete documents matching a filter
await store.delete({
filter: {
query: "SELECT * FROM c WHERE c.metadata.category = @category",
parameters: [{ name: "@category", value: "old" }],
},
});
// Delete all documents
await store.delete();
기본 컨테이너 접근 (Access the underlying container)
// Get direct access to the Cosmos DB container for advanced operations
const container = store.getContainer();
const { resources } = await container.items
.query("SELECT * FROM c WHERE c.metadata.category = 'tech'")
.fetchAll();
관련 (Related)
- 벡터 스토어 개념 가이드
- 벡터 스토어 how-to 가이드
출처: 문서
본문
AzureCosmosDBNoSQLVectorStore는 Azure Cosmos DB for NoSQL 서버리스 벡터 검색용 통합이에요. @langchain/azure-cosmosdb 패키지에서 가져와 endpoint/연결 문자열 + databaseName·containerName으로 구성해요. 벡터 검색(Vector), 점수 임계값 검색, MMR 검색, 전체 텍스트·하이브리드 검색(프리뷰)을 searchType으로 지원해요. 필터에 사용자 입력을 넣을 때는 SQL 인젝션을 막기 위해 반드시 파라미터화된 쿼리(@param)를 사용해야 해요.