TypeScript 및 Azure DocumentDB를 사용하여 지능형 AI 에이전트를 빌드합니다. 이 빠른 시작에서는 의미 체계 호텔 검색을 수행하고 개인 설정된 권장 사항을 생성하는 2개 에이전트 아키텍처를 보여 줍니다.
Important
이 샘플에서는 AI 애플리케이션을 빌드하기 위한 인기 있는 프레임워크인 LangChain을 사용합니다. LangChain은 에이전트 개발을 간소화하는 에이전트, 도구 및 프롬프트에 대한 추상화 기능을 제공합니다.
필수 조건
Azure 개발자 CLI를 사용하여 샘플 리포지토리에서 명령을 실행 azd 하여 필요한 Azure 리소스를 만들 수 있습니다. 자세한 내용은 Azure Developer CLI를 사용하여 인프라 배포를 참조하세요.
Azure 리소스
Microsoft Azure AI Foundry에서 다음 모델 배포를 사용하는 Microsoft Foundry 모델 리소스(클래식)의 Azure OpenAI:
-
gpt-4o배포 (신시사이저 에이전트) - 권장: 분당 50,000개의 토큰 처리 용량 (TPM) -
gpt-4o-mini배포(Planner 에이전트) - 권장: 분당 토큰 30,000개(TPM) 용량 -
text-embedding-3-small배포(Embeddings) - 권장: 분당 토큰 10,000개(TPM) 용량 -
토큰 할당량: 속도 제한을 방지하기 위해 각 배포에 대해 충분한 TPM 구성
- Azure OpenAI 할당량 관리를 참조하세요.
- 429 오류가 발생하는 경우 TPM 할당량을 늘리거나 요청 빈도를 줄입니다.
-
벡터 검색 지원을 사용하는 Azure DocumentDB(MongoDB 호환성 포함) 클러스터:
- 기본 설정 벡터 인덱스 알고리즘을 기반으로 하는 클러스터 계층 요구 사항:
- IVF(반전된 파일 인덱스): M10 이상(기본 알고리즘)
- HNSW(계층적 탐색 가능한 Small World): M30 이상(그래프 기반)
- DiskANN: M40 이상(대규모에 최적화됨)
-
방화벽 구성: 필수입니다. 적절한 방화벽 구성이 없으면 연결 시도가 실패합니다.
- 클러스터의 방화벽 규칙에 클라이언트 IP 주소를 추가합니다. 자세한 내용은 IP 주소에서 액세스 권한 부여를 참조하세요.
- 암호 없는 인증의 경우 RBAC(역할 기반 액세스 제어)가 사용하도록 설정되어 있는지 확인합니다.
- 기본 설정 벡터 인덱스 알고리즘을 기반으로 하는 클러스터 계층 요구 사항:
개발 도구
- 리소스 프로비저닝을 위한 Azure 개발자 CLI
- Node.js LTS
- TypeScript 5.0 이상
- 인증을 위한 Azure CLI
- 데이터베이스 관리를 위한 DocumentDB 확장이 있는 Visual Studio Code(선택 사항)
Node.js 대한 에이전트 RAG 애플리케이션 아키텍처
샘플은 각 에이전트에 특정 역할이 있는 2개 에이전트 아키텍처를 사용합니다.
이 샘플에서는 OpenAI SDK와 함께 LangChain의 에이전트 프레임워크를 사용합니다. 도구 통합을 위해 LangChain의 함수 호출 추상화 기능을 활용하고 에이전트와 검색 도구 간의 선형 워크플로를 따릅니다. 실행은 대화 이력 없이 상태 비저장 방식으로 이루어지므로 단일 턴 쿼리 및 응답 시나리오에 적합합니다.
Node.js 샘플 코드 가져오기
리포지토리 Azure DocumentDB 샘플을 복제하거나 로컬 컴퓨터에 다운로드하여 빠른 시작을 따릅니다.
프로젝트 디렉터리로 이동합니다.
cd ai/vector-search-agent-typescript
Azure Developer CLI를 사용하여 Azure 리소스 배포
Azure 개발자 CLI(azd)를 사용하여 필요한 Azure OpenAI 및 DocumentDB 리소스를 프로비전합니다.
Azure에 로그인:
azd auth login인프라 프로비전 및 배포:
azd up메시지가 표시되면 구독 및 위치(예:
swedencentral또는eastus2)를 선택합니다.배포가
azd완료되면 필요한 환경 변수를 출력합니다..env파일에 복사하세요 ( 환경 변수 구성을 참조).
Tip
언제든지 실행 azd env get-values 하여 현재 환경 값을 봅니다.
환경 변수 구성
Azure 리소스를 수동으로 만들거나 사용자 고유의 기존 리소스를 사용하려는 경우 애플리케이션이 Azure OpenAI 및 Azure DocumentDB에 연결하도록 환경 변수를 구성해야 합니다.
azd up를 사용한 경우, 필요한 환경 변수는 azd 환경에서 자동으로 설정되며 azd env get-values를 통해 액세스할 수 있으므로 이 단계를 건너뛸 수 있습니다.
.env 프로젝트 루트에 파일을 만들어 환경 변수를 구성합니다. 리포지토리에서 파일의 .env.sample 복사본을 만들 수 있습니다.
.env 파일을 편집하고 다음 플레이스홀더 값을 바꿉니다.
이 퀵스타트에서는 두 에이전트 아키텍처(Planner + synthesizer)와 세 가지 모델 배포(채팅 모델 2개 + 임베딩)를 사용합니다. 환경 변수는 각 모델 배포에 대해 구성됩니다.
-
AZURE_OPENAI_PLANNER_MODEL: gpt-4o-mini 모델 이름 -
AZURE_OPENAI_SYNTH_MODEL: gpt-4o 모델 이름 -
AZURE_OPENAI_EMBEDDING_MODEL: 텍스트 임베딩-3-소형 모델 이름
Azure ID를 사용한 암호 없는 인증(권장) 또는 기존 연결 문자열 및 API 키의 두 가지 인증 방법 중에서 선택할 수 있습니다.
옵션 1: 암호 없는 인증
Azure OpenAI 및 Azure DocumentDB 모두에서 암호 없는 인증을 사용합니다. 설정 USE_PASSWORDLESS=true, AZURE_OPENAI_ENDPOINT, 및 AZURE_DOCUMENTDB_CLUSTER.
# Enable passwordless authentication
USE_PASSWORDLESS=true
# Azure OpenAI Configuration (passwordless)
AZURE_OPENAI_ENDPOINT=your-openai-endpoint
AZURE_OPENAI_PLANNER_MODEL=gpt-4o-mini
AZURE_OPENAI_PLANNER_API_VERSION=2024-08-01-preview
AZURE_OPENAI_SYNTH_MODEL=gpt-4o
AZURE_OPENAI_SYNTH_API_VERSION=2024-08-01-preview
AZURE_OPENAI_EMBEDDING_MODEL=text-embedding-3-small
AZURE_OPENAI_EMBEDDING_API_VERSION=2023-05-15
# Azure DocumentDB (passwordless)
AZURE_DOCUMENTDB_CLUSTER=your-mongo-cluster-name
AZURE_DOCUMENTDB_DATABASENAME=Hotels
AZURE_DOCUMENTDB_COLLECTION=hotel_data
# Data Configuration
DATA_FILE_WITHOUT_VECTORS=../data/Hotels.json
# Vector Index Configuration
VECTOR_INDEX_ALGORITHM=vector-ivf
EMBEDDING_DIMENSIONS=1536
암호 없는 인증을 위한 필수 구성 요소:
Azure에 로그인되어 있는지 확인합니다.
az loginID에 다음 역할을 부여합니다.
-
Cognitive Services OpenAI UserAzure OpenAI 리소스에서 - Azure DocumentDB 리소스의
DocumentDB Account Contributor및Cosmos DB Account Reader Role
역할 할당에 대한 자세한 내용은 Azure Portal을 사용하여 Azure 역할 할당을 참조하세요.
-
암호 없는 인증의 작동 방식
애플리케이션은 USE_PASSWORDLESS=true일 때 Azure Identity SDK에서 OAuth DefaultAzureCredential 토큰을 얻기 위해 사용합니다. Azure DocumentDB 연결의 경우 액세스 토큰을 MongoDB 드라이버에 직접 전달하는 OIDC 토큰 콜백을 사용합니다. 즉, 구성 파일에 암호 또는 연결 문자열이 저장되지 않습니다.
인증 흐름:
-
DefaultAzureCredential는 사용 가능한 자격 증명(Azure CLI, 관리 ID, 환경 변수)을 순서대로 검사합니다. - Azure OpenAI의 경우 토큰이 LangChain
AzureChatOpenAI및AzureOpenAIEmbeddings클라이언트에 자동으로 전달됩니다. - Azure DocumentDB의 경우 토큰 콜백 함수는 액세스 토큰을 가져오고 인증 메커니즘을 통해 MongoDB 클라이언트에
MONGODB-OIDC제공합니다.
import { AzureOpenAIEmbeddings, AzureChatOpenAI } from "@langchain/openai";
import { MongoClient, OIDCCallbackParams } from 'mongodb';
import { AccessToken, DefaultAzureCredential, TokenCredential, getBearerTokenProvider } from '@azure/identity';
/*
This file contains utility functions to create Azure OpenAI clients for embeddings, planning, and synthesis.
It supports two modes of authentication:
1. API Key based authentication using AZURE_OPENAI_API_KEY and AZURE_OPENAI_ENDPOINTenvironment variables.
2. Passwordless authentication using DefaultAzureCredential from Azure Identity library.
*/
// Azure Identity configuration
const OPENAI_SCOPE = 'https://cognitiveservices.azure.com/.default';
const DOCUMENT_DB_SCOPE = 'https://ossrdbms-aad.database.windows.net/.default';
// Azure identity credential (used for passwordless auth)
const CREDENTIAL = new DefaultAzureCredential();
function requireEnvVars(names: string[]) {
const missing = names.filter((name) => {
const value = process.env[name];
return !value || value.trim().length === 0;
});
if (missing.length > 0) {
throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
}
}
// Token callback for MongoDB OIDC authentication
async function azureIdentityTokenCallback(
params: OIDCCallbackParams,
credential: TokenCredential
): Promise<{ accessToken: string; expiresInSeconds: number }> {
const tokenResponse: AccessToken | null = await credential.getToken([DOCUMENT_DB_SCOPE]);
return {
accessToken: tokenResponse?.token || '',
expiresInSeconds: (tokenResponse?.expiresOnTimestamp || 0) - Math.floor(Date.now() / 1000)
};
옵션 2: 연결 문자열 및 API 키 인증
키 기반 인증을 사용하려면 USE_PASSWORDLESS=false (또는 생략)하며, AZURE_OPENAI_API_KEY 파일에서 AZURE_DOCUMENTDB_CONNECTION_STRING 값과 .env 값을 제공합니다.
# Disable passwordless authentication
USE_PASSWORDLESS=false
# Azure OpenAI Configuration (API key)
AZURE_OPENAI_ENDPOINT=your-openai-endpoint
AZURE_OPENAI_API_KEY=your-azure-openai-api-key
AZURE_OPENAI_PLANNER_MODEL=gpt-4o-mini
AZURE_OPENAI_PLANNER_API_VERSION=2024-08-01-preview
AZURE_OPENAI_SYNTH_MODEL=gpt-4o
AZURE_OPENAI_SYNTH_API_VERSION=2024-08-01-preview
AZURE_OPENAI_EMBEDDING_MODEL=text-embedding-3-small
AZURE_OPENAI_EMBEDDING_API_VERSION=2023-05-15
# Azure DocumentDB (connection string)
AZURE_DOCUMENTDB_CLUSTER=your-mongo-cluster-name
AZURE_DOCUMENTDB_CONNECTION_STRING=mongodb+srv://username:password@cluster.mongocluster.cosmos.azure.com/
AZURE_DOCUMENTDB_DATABASENAME=Hotels
AZURE_DOCUMENTDB_COLLECTION=hotel_data
# Data Configuration
DATA_FILE_WITHOUT_VECTORS=../data/Hotels.json
# Vector Index Configuration
VECTOR_INDEX_ALGORITHM=vector-ivf
EMBEDDING_DIMENSIONS=1536
프로젝트 구조
프로젝트는 표준 Node.js/TypeScript 프로젝트 레이아웃을 따릅니다. 디렉터리 구조는 다음 구조와 같아야 합니다.
vector-search-agent-typescript/
├── src/
│ ├── agent.ts # Main agent application
│ ├── upload-documents.ts # Data upload utility
│ ├── cleanup.ts # Database cleanup utility
│ ├── vector-store.ts # Vector store and tool implementation
│ ├── utils/
│ │ ├── clients.ts # Azure OpenAI and DocumentDB client setup
│ │ ├── prompts.ts # System prompts and tool definitions
│ │ ├── types.ts # TypeScript type definitions
│ │ └── mongo.ts # MongoDB utility functions
│ └── scripts/ # Additional utility scripts
├── .env # Environment variable configuration
├── package.json # npm dependencies and scripts
└── tsconfig.json # TypeScript configuration
에이전트 RAG 애플리케이션에 대한 Node.js 코드 살펴보기
이 섹션에서는 AI 에이전트 워크플로의 핵심 구성 요소를 안내합니다. 에이전트가 요청을 처리하는 방법, 도구가 AI를 데이터베이스에 연결하는 방법 및 프롬프트가 AI의 동작을 안내하는 방법을 강조합니다.
Node.js 에이전트 RAG 애플리케이션
이 파일은 src/agent.ts AI 기반 호텔 추천 시스템을 오케스트레이션합니다.
애플리케이션은 다음 두 가지 Azure 서비스를 사용합니다.
- 쿼리를 이해하고 권장 사항을 생성하는 AI 모델을 사용하는 Azure OpenAI
- 호텔 데이터를 저장하고 벡터 유사성 검색을 수행하는 Azure DocumentDB
Node.js 에이전트 및 도구 구성 요소
세 가지 구성 요소가 함께 작동하여 호텔 검색 요청을 처리합니다.
- Planner 에이전트 - 요청을 해석하고 검색 방법을 결정합니다.
- 벡터 검색 도구 - 플래너 에이전트가 설명하는 것과 비슷한 호텔을 찾습니다.
- 신시사이저 에이전트 - 검색 결과에 따라 유용한 권장 사항을 작성합니다.
에이전틱 RAG 애플리케이션 워크플로
애플리케이션은 다음 두 단계로 호텔 검색 요청을 처리합니다.
- 계획: 워크플로는 플래너 에이전트를 호출하여 사용자의 쿼리(예: "실행 중인 트레일 근처 호텔")를 분석하고 데이터베이스에서 일치하는 호텔을 검색합니다.
- 합성: 워크플로는 검색 결과를 검토하고 요청과 가장 일치하는 호텔을 설명하는 맞춤형 권장 사항을 작성하는 신시사이저 에이전트를 호출합니다.
// Authentication
const clients = process.env.USE_PASSWORDLESS === 'true' || process.env.USE_PASSWORDLESS === '1' ? createClientsPasswordless() : createClients();
const { embeddingClient, plannerClient, synthClient, dbConfig } = clients;
console.log(`DEBUG mode is ${process.env.DEBUG === 'true' ? 'ON' : 'OFF'}`);
console.log(`DEBUG_CALLBACKS length: ${DEBUG_CALLBACKS.length}`);
// Get vector store (get docs, create embeddings, insert docs)
const store = await getExistingStore(
embeddingClient,
dbConfig);
const query = process.env.QUERY || "quintessential lodging near running trails, eateries, retail";
const nearestNeighbors = parseInt(process.env.NEAREST_NEIGHBORS || '5', 10);
//Run planner agent
const hotelContext = await runPlannerAgent(plannerClient, embeddingClient, query, store, nearestNeighbors);
if (process.env.DEBUG === 'true') console.log(hotelContext);
//Run synth agent
const finalAnswer = await runSynthesizerAgent(synthClient, query, hotelContext);
// Get final recommendation (data + AI)
console.log('\n--- FINAL ANSWER ---');
console.log(finalAnswer);
계획 및 합성을 위한 Node.js 에이전트
원본 파일은 src/agent.ts 호텔 검색 요청을 처리하기 위해 함께 작동하는 플래너 및 신시사이저 에이전트를 구현합니다.
Planner 에이전트
플래너 에이전트는 호텔 검색 방법을 결정하는 의사 결정자 입니다.
Planner 에이전트는 사용자의 자연어 쿼리를 수신하고 사용할 수 있는 도구와 함께 LangChain의 에이전트 프레임워크를 사용하여 AI 모델로 보냅니다. AI는 벡터 검색 도구를 호출하기로 결정하고 검색 매개 변수를 제공합니다. LangChain은 도구 실행을 자동으로 처리하고 일치하는 호텔을 반환합니다. AI는 검색 논리를 하드코딩하는 대신 사용자가 원하는 것을 해석하고 검색 방법을 선택하여 시스템이 다양한 유형의 쿼리에 유연하게 사용할 수 있도록 합니다.
async function runPlannerAgent(
plannerClient: any,
embeddingClient: any,
userQuery: string,
store: AzureDocumentDBVectorStore,
nearestNeighbors = 5
): Promise<string> {
console.log('\n--- PLANNER ---');
const userMessage = `Use the "${TOOL_NAME}" tool with nearestNeighbors=${nearestNeighbors} and query="${userQuery}". Do not answer directly; call the tool.`;
const contextSchema = z.object({
store: z.any(),
embeddingClient: z.any()
});
const agent = createAgent({
model: plannerClient,
systemPrompt: PLANNER_SYSTEM_PROMPT,
tools: [getHotelsToMatchSearchQuery],
contextSchema,
});
const agentResult = await agent.invoke(
{ messages: [{ role: 'user', content: userMessage }] },
// @ts-ignore
{ context: { store, embeddingClient }, callbacks: DEBUG_CALLBACKS }
);
const plannerMessages = agentResult.messages || [];
const searchResultsAsText = extractPlannerToolOutput(plannerMessages);
return searchResultsAsText;
}
신시사이저 에이전트
신시사이저 에이전트는 유용한 권장 사항을 만드는 작성자입니다.
신시사이저 에이전트는 호텔 검색 결과와 함께 원래 사용자 쿼리를 받습니다. 권장 사항을 작성하기 위한 지침과 함께 모든 것을 AI 모델로 보냅니다. 호텔을 비교하고 최상의 옵션을 설명하는 자연어 응답을 반환합니다. 이 방법은 원시 검색 결과가 사용자에게 친숙하지 않기 때문에 중요합니다. 신시사이저는 데이터베이스 레코드를 특정 호텔이 사용자의 요구와 일치하는 이유를 설명하는 대화형 권장 사항으로 변환합니다.
async function runSynthesizerAgent(synthClient: any, userQuery: string, hotelContext: string): Promise<string> {
console.log('\n--- SYNTHESIZER ---');
let conciseContext = hotelContext;
console.log(`Context size is ${conciseContext.length} characters`);
const agent = createAgent({
model: synthClient,
systemPrompt: SYNTHESIZER_SYSTEM_PROMPT,
});
const agentResult = await agent.invoke({
messages: [{
role: 'user',
content: createSynthesizerUserPrompt(userQuery, conciseContext)
}]
});
const synthMessages = agentResult.messages;
const finalAnswer = synthMessages[synthMessages.length - 1].content;
console.log(`Output: ${finalAnswer.length} characters of final recommendation`);
return finalAnswer as string;
}
벡터 저장소 검색을 위한 에이전트 도구
원본 파일은 src/vector-store.ts Planner 에이전트가 사용하는 벡터 검색 도구를 정의합니다.
도구 파일은 AI 에이전트가 호텔을 찾는 데 사용할 수 있는 검색 도구를 정의합니다. 이 도구는 에이전트가 데이터베이스에 연결하는 방법입니다. AI는 데이터베이스를 직접 검색하지 않습니다. 이 도구는 검색 도구를 사용하도록 요청하고 도구는 실제 검색을 실행합니다.
도구 정의로서의 Node.js 함수
LangChain의 tool 함수는 일반 TypeScript 함수에서 도구를 만듭니다. 도구 정의에는 이름, 설명 및 스키마가 포함됩니다(유효성 검사에 Zod 사용). 이 정의를 통해 AI는 도구가 존재하고 이를 올바르게 사용하는 방법을 알 수 있습니다.
export const getHotelsToMatchSearchQuery = tool(
async ({ query, nearestNeighbors }, config): Promise<string> => {
try {
const store = config.context.store as AzureDocumentDBVectorStore;
const embeddingClient = config.context.embeddingClient as AzureOpenAIEmbeddings;
// Create query embedding and perform search
const queryVector = await embeddingClient.embedQuery(query);
const results = await store.similaritySearchVectorWithScore(queryVector, nearestNeighbors);
console.log(`Found ${results.length} documents from vector store`);
// Format results for synthesizer
const formatted = results.map(([doc, score]) => {
const md = doc.metadata as Partial<HotelForVectorStore>;
console.log(`Hotel: ${md.HotelName ?? 'N/A'}, Score: ${score}`);
return formatHotelForSynthesizer(md, score);
}).join('\n\n');
return formatted;
} catch (error) {
console.error('Error in getHotelsToMatchSearchQuery tool:', error);
return 'Error occurred while searching for hotels.';
}
},
{
name: TOOL_NAME,
description: TOOL_DESCRIPTION,
schema: z.object({
query: z.string(),
nearestNeighbors: z.number().optional().default(5),
}),
}
);
Azure DocumentDB 벡터 검색을 사용하여 Node.js 도구 실행
AI가 도구를 호출하면 함수 본문이 실행됩니다. Azure OpenAI의 포함 모델을 사용하여 텍스트 쿼리를 숫자 벡터로 변환하여 포함을 생성합니다. 그런 다음 벡터를 Azure DocumentDB로 전송하여 데이터베이스를 검색합니다. 그러면 유사한 설명을 의미하는 유사한 벡터가 있는 호텔을 찾습니다. 마지막으로, 데이터베이스 레코드를 신시사이저 에이전트가 이해할 수 있는 읽기 가능한 텍스트로 변환하여 결과의 서식을 지정합니다.
구현은 Azure DocumentDB와의 원활한 통합을 위해 LangChain을 AzureDocumentDBVectorStore 활용합니다.
이 패턴을 사용하는 이유는 무엇인가요?
에이전트에서 도구를 분리하면 유연성을 확보할 수 있습니다. AI는 검색 시기와 검색 대상을 결정하고 도구는 검색 방법을 처리합니다. 에이전트 논리를 변경하지 않고 도구를 더 추가할 수 있습니다.
AI 동작을 안내하는 에이전트 프롬프트
원본 파일에는 src/utils/prompts.ts 에이전트에 대한 시스템 프롬프트 및 도구 정의가 포함되어 있습니다.
프롬프트 파일은 플래너 및 신시사이저 에이전트 모두에 대한 AI 모델에 지정된 지침과 컨텍스트를 정의합니다. 이러한 프롬프트는 AI의 동작을 안내하고 워크플로에서 해당 역할을 이해하도록 합니다.
AI 응답의 품질은 명확한 지침에 따라 크게 달라집니다. 이러한 프롬프트는 경계를 설정하고, 출력 형식을 정의하며, AI를 사용자의 의사 결정 목표에 집중합니다. 이러한 프롬프트를 사용자 지정하여 코드를 수정하지 않고 에이전트의 동작 방식을 변경할 수 있습니다.
export const PLANNER_SYSTEM_PROMPT = `You are a hotel search planner. Transform the user's request into a clear, detailed search query for a vector database.
CRITICAL REQUIREMENT: You MUST ALWAYS call the "${TOOL_NAME}" tool. This is MANDATORY for every request.
Use a tool call with:
- query (string)
- nearestNeighbors (number 1-20)
QUERY REFINEMENT RULES:
- If vague (e.g., "nice hotel"), add specific attributes: "hotel with high ratings and good amenities"
- If minimal (e.g., "cheap"), expand: "budget hotel with good value"
- Preserve specific details from user (location, amenities, business/leisure)
- Keep natural language - this is for semantic search
- Don't just echo the input - improve it for better search results
- nearestNeighbors: Use 3-5 for specific requests, 10-15 for broader requests, max 20
EXAMPLES:
User: "cheap hotel" → {"tool": "${TOOL_NAME}", "args": {"query": "budget-friendly hotel with good value and affordable rates", "nearestNeighbors": 10}}
User: "hotel near downtown with parking" → {"tool": "${TOOL_NAME}", "args": {"query": "hotel near downtown with good parking and wifi", "nearestNeighbors": 5}}
User: "nice place to stay" → {"tool": "${TOOL_NAME}", "args": {"query": "hotel with high ratings, good reviews, and quality amenities", "nearestNeighbors": 10}}
Do not answer the user directly. Always call the tool.`;
// ============================================================================
// Synthesizer Prompts
// ============================================================================
export const SYNTHESIZER_SYSTEM_PROMPT = `You are an expert hotel recommendation assistant using vector search results.
Only use the TOP 3 results provided. Do not request additional searches or call other tools.
GOAL: Provide a concise comparative recommendation to help the user choose between the top 3 options.
REQUIREMENTS:
- Compare only the top 3 results across the most important attributes: rating, score, location, price-level (if available), and key tags (parking, wifi, pool).
- Identify the main tradeoffs in one short sentence per tradeoff.
- Give a single clear recommendation with one short justification sentence.
- Provide up to two alternative picks (one sentence each) explaining when they are preferable.
FORMAT CONSTRAINTS:
- Plain text only (no markdown).
- Keep the entire response under 220 words.
- Use simple bullets (•) or numbered lists and short sentences (preferably <25 words per sentence).
- Preserve hotel names exactly as provided in the tool summary.
Do not add extra commentary, marketing language, or follow-up questions. If information is missing and necessary to choose, state it in one sentence and still provide the best recommendation based on available data.`;
Node.js 사용하여 Azure DocumentDB에 데이터 준비 및 업로드
샘플은 JSON 파일의 호텔 데이터를 사용합니다. 리포지토리에는 다음 두 가지 버전이 포함됩니다.
-
Hotels.json- 벡터 포함이 없는 호텔 데이터(이 샘플에서 사용) -
Hotels_Vector.json- 미리 계산된 임베딩이 있는 호텔 데이터(다른 예제에서 사용됨)
업로드 작동 방식
스크립트는 upload-documents.ts 다음 세 단계를 수행합니다.
-
데이터 로드 —
Hotels.json파일에서 호텔 레코드를 읽습니다. -
임베딩 생성 - 각 호텔에 대해 스크립트는
Description필드를 Azure OpenAItext-embedding-3-small모델로 보내 1536차원 벡터 임베딩을 생성합니다. 이렇게 하면 텍스트 설명을 의미 체계 의미를 캡처하는 숫자 표현으로 변환합니다. - 삽입 및 인덱스 - 스크립트는 Azure DocumentDB 컬렉션에 문서(포함)를 삽입하고 구성된 알고리즘(IVF, HNSW 또는 DiskANN)을 사용하여 벡터 인덱스를 만듭니다.
import { createClientsPasswordless, createClients } from './utils/clients.js';
import { getStore } from './vector-store.js';
/**
* Upload documents to Azure DocumentDB MongoDB Vector Store
*/
async function uploadDocuments() {
try {
console.log('Starting document upload...\n');
// Get clients based on authentication mode
const usePasswordless = process.env.USE_PASSWORDLESS === 'true' || process.env.USE_PASSWORDLESS === '1';
console.log(`Authentication mode: ${usePasswordless ? 'Passwordless (Azure AD)' : 'API Key'}`);
console.log('\nEnvironment variables check:');
console.log(` DATA_FILE_WITHOUT_VECTORS: ${process.env.DATA_FILE_WITHOUT_VECTORS}`);
console.log(` AZURE_DOCUMENTDB_DATABASENAME: ${process.env.AZURE_DOCUMENTDB_DATABASENAME}`);
console.log(` AZURE_DOCUMENTDB_COLLECTION: ${process.env.AZURE_DOCUMENTDB_COLLECTION}`);
console.log(` AZURE_DOCUMENTDB_CLUSTER: ${process.env.AZURE_DOCUMENTDB_CLUSTER}`);
console.log(` AZURE_OPENAI_EMBEDDING_MODEL: ${process.env.AZURE_OPENAI_EMBEDDING_MODEL}`);
const requiredEnvVars = [
'DATA_FILE_WITHOUT_VECTORS',
'AZURE_DOCUMENTDB_DATABASENAME',
'AZURE_DOCUMENTDB_COLLECTION',
'AZURE_DOCUMENTDB_CLUSTER',
'AZURE_OPENAI_EMBEDDING_MODEL',
];
const missingEnvVars = requiredEnvVars.filter((name) => {
const value = process.env[name];
return !value || value.trim().length === 0;
});
if (missingEnvVars.length > 0) {
throw new Error(`Missing required environment variables: ${missingEnvVars.join(', ')}`);
}
const clients = usePasswordless ? createClientsPasswordless() : createClients();
const { embeddingClient, dbConfig } = clients;
console.log('\ndbConfig properties:');
console.log(` instance: ${dbConfig.instance}`);
console.log(` databaseName: ${dbConfig.databaseName}`);
console.log(` collectionName: ${dbConfig.collectionName}`);
// Check for data file path
const dataFilePath = process.env.DATA_FILE_WITHOUT_VECTORS!;
console.log(`\nReading data from: ${dataFilePath}`);
console.log(`Database: ${dbConfig.databaseName}`);
console.log(`Collection: ${dbConfig.collectionName}`);
console.log(`Vector algorithm: ${process.env.VECTOR_INDEX_ALGORITHM || 'vector-ivf'}\n`);
// Upload documents using existing getStore function
const startTime = Date.now();
const store = await getStore(dataFilePath, embeddingClient, dbConfig);
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
console.log(`\n✓ Upload completed in ${duration} seconds`);
// Close connection
await store.close();
console.log('✓ Connection closed');
// Force exit to ensure process terminates (Azure credential timers may still be active)
process.exit(0);
} catch (error: any) {
console.error('\n✗ Upload failed:', error?.message || error);
console.error('\nFull error:', error);
process.exit(1);
}
}
// Run the upload
uploadDocuments();
벡터 인덱스 만들기
벡터 인덱스는 빠른 유사성 검색을 가능하게 합니다. 인덱스를 만들 때 Azure DocumentDB는 모든 문서를 검사하지 않고도 "이 설명과 비슷한 호텔 찾기"와 같은 쿼리를 효율적으로 응답할 수 있도록 포함 벡터를 구성합니다.
선택하는 인덱스 형식은 성능에 영향을 줍니다.
| 알고리즘 | 클러스터 계층 | 적합한 대상 |
|---|---|---|
| IVF (주) | M10+ | 중소형 데이터 세트, 저렴한 비용 |
| HNSW | M30+ | 높은 재현율, 빠른 쿼리 |
| DiskANN | M40+ | 대규모 데이터 세트, 수십억 개 이상의 벡터 |
import {
AzureDocumentDBVectorStore,
AzureDocumentDBSimilarityType,
AzureDocumentDBConfig
} from "@langchain/azure-cosmosdb";
import type { AzureOpenAIEmbeddings } from "@langchain/openai";
import { readFileSync } from 'fs';
import { Document } from '@langchain/core/documents';
import { HotelsData, Hotel } from './utils/types.js';
import { TOOL_NAME, TOOL_DESCRIPTION } from './utils/prompts.js';
import { z } from 'zod';
import { tool } from "langchain";
import { MongoClient } from 'mongodb';
import { BaseMessage } from "@langchain/core/messages";
type HotelForVectorStore = Omit<Hotel, 'Description_fr' | 'Location' | 'Rooms'>;
// Helper function for similarity type
function getSimilarityType(similarity: string) {
switch (similarity.toUpperCase()) {
case 'COS': return AzureDocumentDBSimilarityType.COS;
case 'L2': return AzureDocumentDBSimilarityType.L2;
case 'IP': return AzureDocumentDBSimilarityType.IP;
default: return AzureDocumentDBSimilarityType.COS;
}
}
// Consolidated vector index configuration
function getVectorIndexOptions() {
const algorithm = process.env.VECTOR_INDEX_ALGORITHM || 'vector-ivf';
const dimensions = parseInt(process.env.EMBEDDING_DIMENSIONS || '1536');
const similarity = getSimilarityType(process.env.VECTOR_SIMILARITY || 'COS');
const baseOptions = { dimensions, similarity };
switch (algorithm) {
case 'vector-hnsw':
return {
kind: 'vector-hnsw' as const,
m: parseInt(process.env.HNSW_M || '16'),
efConstruction: parseInt(process.env.HNSW_EF_CONSTRUCTION || '64'),
...baseOptions
};
case 'vector-diskann':
return {
kind: 'vector-diskann' as const,
...baseOptions
};
case 'vector-ivf':
default:
Node.js 사용하여 에이전트 RAG 애플리케이션 실행
종속성을 설치합니다.
npm install에이전트를 실행하기 전에 임베딩을 포함하여 호텔 데이터를 업로드하십시오.
upload-documents.ts명령은 JSON 파일에서 호텔을 로드하고,text-embedding-3-small를 사용하여 각 호텔에 대한 임베딩을 생성하고, Azure DocumentDB에 문서를 삽입하며, 벡터 인덱스를 만듭니다.npm run upload명령을 사용하여 호텔 추천 에이전트를 실행합니다
agent.ts. 에이전트는 Planner 에이전트, 벡터 검색 및 신시사이저 에이전트를 호출합니다. 출력에는 유사성 점수와 권장 사항이 있는 신시사이저 에이전트의 비교 분석이 포함됩니다.npm startDEBUG mode is OFF DEBUG_CALLBACKS length: 0 Connected to existing vector store: Hotels.hotel_data --- PLANNER --- Found 5 documents from vector store Hotel: Nordick's Valley Motel, Score: 0.49866509437561035 Hotel: White Mountain Lodge & Suites, Score: 0.48731985688209534 Hotel: Trails End Motel, Score: 0.47985398769378662 Hotel: Country Comfort Inn, Score: 0.47431993484497070 Hotel: Lakefront Captain Inn, Score: 0.45787304639816284 --- SYNTHESIZER --- Context size is 3233 characters Output: 812 characters of final recommendation --- FINAL ANSWER --- 1. COMPARISON SUMMARY: • Nordick's Valley Motel has the highest rating (4.5) and offers free parking, air conditioning, and continental breakfast. It is located in Washington D.C., near historic attractions and trails. • White Mountain Lodge & Suites is a resort with unique amenities like a pool, restaurant, and meditation gardens, but has the lowest rating (2.4). It is located in Denver, surrounded by forest trails. • Trails End Motel is budget-friendly with a moderate rating (3.2), free parking, free wifi, and a restaurant. It is close to downtown Scottsdale and eateries. Key tradeoffs: - Nordick's Valley Motel excels in rating and proximity to historic attractions but lacks a pool or free wifi. - White Mountain Lodge & Suites offers resort-style amenities and forest trails but has the lowest rating. - Trails End Motel balances affordability and essential amenities but has fewer unique features compared to the others. 2. BEST OVERALL: Nordick's Valley Motel is the best choice for its high rating, proximity to trails and attractions, and free parking. 3. ALTERNATIVE PICKS: • Choose White Mountain Lodge & Suites if you prioritize resort amenities and forest trails over rating. • Choose Trails End Motel if affordability and proximity to downtown Scottsdale are your main concerns.
Visual Studio Code에서 데이터 보기 및 관리
Visual Studio Code에서 DocumentDB 확장을 선택하여 Azure DocumentDB 계정에 연결합니다.
Hotels 데이터베이스에서 데이터 및 인덱스를 봅니다.
자원을 정리하세요
리소스를 프로비전하는 데 사용한 azd up 경우 다음을 사용하여 모든 Azure 리소스를 제거할 수 있습니다.
azd down
리소스를 수동으로 만들고 모든 리소스를 제거하려는 경우 추가 비용을 방지하기 위해 리소스 그룹을 삭제합니다.
리소스를 다시 사용하려면 정리 명령을 사용하여 완료되면 테스트 데이터베이스를 삭제합니다. 다음 명령을 실행합니다.
npm run cleanup