Démarrage rapide : Agent IA avec recherche vectorielle dans Azure DocumentDB à l’aide de TypeScript

Créez un agent IA intelligent à l’aide de TypeScript et d’Azure DocumentDB. Ce guide de démarrage rapide illustre une architecture à deux agents qui effectue une recherche sémantique d’hôtel et génère des recommandations personnalisées.

Important

Cet exemple utilise LangChain, une infrastructure populaire pour la création d’applications IA. LangChain fournit des abstractions pour les agents, les outils et les invites, simplifiant ainsi le développement d’agents.

Prerequisites

Vous pouvez utiliser Azure Developer CLI pour créer les ressources Azure requises en exécutant les azd commandes dans l’exemple de référentiel. Pour plus d’informations, consultez Déployer l’infrastructure avec Azure Developer CLI.

Ressources Azure

  • Azure OpenAI dans la ressource Modèles Microsoft Foundry (classique) avec les déploiements de modèles suivants dans Microsoft Azure AI Foundry :

    • gpt-4odéploiement (Agent de synthétiseur) - Recommandé : capacité de 50 000 jetons par minute (TPM)
    • gpt-4o-minidéploiement (Agent planificateur) - Recommandé : capacité de 30 000 jetons par minute (TPM)
    • text-embedding-3-smalldéploiement (embeddings) - Recommandé : capacité de 10 000 jetons par minute (TPM)
    • Quotas de jetons : configurez une capacité de TPM suffisante pour chaque déploiement afin d’éviter toute limitation de débit
      • Consultez Gérer les quotas Azure OpenAI pour la gestion des quotas
      • Si vous rencontrez 429 erreurs, augmentez votre quota TPM ou réduisez la fréquence des demandes
  • Cluster Azure DocumentDB (avec compatibilité MongoDB) avec prise en charge de la recherche vectorielle :

    • Exigences de niveau cluster en fonction de votre algorithme d’index vectoriel préféré :
      • IVF (Index de fichier inversé) : M10 ou version ultérieure (algorithme par défaut)
      • HNSW (Hierarchical Navigable Small World) : M30 ou supérieur (basé sur un graphe)
      • DiskANN : M40 ou version ultérieure (optimisé pour une grande échelle)
    • Configuration du pare-feu : OBLIGATOIRE. Sans configuration de pare-feu appropriée, les tentatives de connexion échouent.
    • Pour l’authentification sans mot de passe, vérifiez que le contrôle d’accès en fonction du rôle (RBAC) est activé.

Outils de développement

Architecture d’application RAG agentique pour Node.js

L’exemple utilise une architecture à deux agents où chaque agent a un rôle spécifique.

Diagramme d’architecture montrant le flux de travail à deux agents avec l’agent planificateur, l’outil de recherche vectorielle et l’agent de synthèse.

Cet exemple utilise l’infrastructure d’agent de LangChain avec le Kit de développement logiciel (SDK) OpenAI. Il tire parti des abstractions d’appel de fonction de LangChain pour l’intégration des outils et suit un flux de travail linéaire entre les agents et l’outil de recherche. L’exécution est sans état, sans historique de conversation, ce qui la rend adaptée aux scénarios de requête et de réponse en un seul tour.

Obtenir l’exemple de code Node.js

  1. Clonez ou téléchargez le dépôt exemples Azure DocumentDB sur votre ordinateur local pour suivre le démarrage rapide.

  2. Accédez au répertoire du projet :

    cd ai/vector-search-agent-typescript
    

Déployer des ressources Azure avec Azure Developer CLI

Utilisez Azure Developer CLI (azd) pour provisionner les ressources Azure OpenAI et DocumentDB requises.

  1. Connectez-vous à Azure :

    azd auth login
    
  2. Provisionnez et déployez l’infrastructure :

    azd up
    
  3. Lorsque vous y êtes invité, sélectionnez votre abonnement et un emplacement (par exemple, swedencentral ou eastus2).

  4. Une fois le déploiement terminé, azd génère les variables d’environnement dont vous avez besoin. Copiez-les dans votre .env fichier (consultez Configurer les variables d’environnement).

Tip

Exécutez azd env get-values à tout moment pour afficher les valeurs d’environnement actuelles.

Configurer les variables d’environnement

Si vous avez créé vos ressources Azure manuellement ou souhaitez utiliser vos propres ressources existantes, vous devez configurer des variables d’environnement pour que l’application se connecte à Azure OpenAI et à Azure DocumentDB. Si vous avez utilisé azd up, vous pouvez ignorer cette étape, car les variables d’environnement nécessaires sont automatiquement définies dans l’environnement azd et sont accessibles avec azd env get-values.

Créez un .env fichier à la racine de votre projet pour configurer des variables d’environnement. Vous pouvez créer une copie du .env.sample fichier à partir du référentiel.

Modifiez le fichier .env et remplacez ces valeurs d’espace réservé :

Ce guide de démarrage rapide utilise une architecture à deux agents (planificateur + synthétiseur) avec trois déploiements de modèles (deux modèles de conversation + incorporations). Les variables d’environnement sont configurées pour chaque déploiement de modèle.

  • AZURE_OPENAI_PLANNER_MODEL: Nom de votre modèle gpt-4o-mini
  • AZURE_OPENAI_SYNTH_MODEL: Nom de votre modèle gpt-4o
  • AZURE_OPENAI_EMBEDDING_MODEL: Nom de votre modèle text-embedding-3-small

Vous pouvez choisir entre deux méthodes d’authentification : l’authentification sans mot de passe à l’aide d’Azure Identity (recommandé) ou de la chaîne de connexion traditionnelle et de la clé API.

Option 1 : Authentification sans mot de passe

Utilisez l’authentification sans mot de passe avec Azure OpenAI et Azure DocumentDB. Définir USE_PASSWORDLESS=true, AZURE_OPENAI_ENDPOINTet 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

Conditions préalables pour l’authentification sans mot de passe :

  • Vérifiez que vous êtes connecté à Azure : az login

  • Accordez à votre identité les rôles suivants :

    • Cognitive Services OpenAI User sur la ressource Azure OpenAI
    • DocumentDB Account Contributor et Cosmos DB Account Reader Role sur la ressource Azure DocumentDB

    Pour plus d’informations sur l’attribution de rôles, consultez Affecter des rôles Azure à l’aide du portail Azure.

Fonctionnement de l’authentification sans mot de passe

Quand USE_PASSWORDLESS=true, l’application utilise DefaultAzureCredential à partir du Kit de développement logiciel (SDK) Azure Identity pour obtenir un jeton OAuth. Pour les connexions Azure DocumentDB, elle utilise un rappel de jeton OIDC qui transmet le jeton d’accès directement au pilote MongoDB. Cela signifie qu’aucun mot de passe ou chaîne de connexion n’est stocké dans des fichiers de configuration.

Flux d’authentification :

  1. DefaultAzureCredential recherche les informations d’identification disponibles (Azure CLI, identité managée, variables d’environnement) dans l’ordre.
  2. Pour Azure OpenAI, le jeton est transmis automatiquement au LangChain AzureChatOpenAI et AzureOpenAIEmbeddings aux clients.
  3. Pour Azure DocumentDB, une fonction de rappel de jeton récupère un jeton d’accès et la fournit au client MongoDB via le MONGODB-OIDC mécanisme d’authentification.
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)
  };

Option 2 : Chaîne de connexion et authentification par clé API

Utilisez l’authentification basée sur des clés en définissant USE_PASSWORDLESS=false (ou omettez-la) et en fournissant AZURE_OPENAI_API_KEY et AZURE_DOCUMENTDB_CONNECTION_STRING valeurs dans votre .env fichier.

# 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

Structure du projet

Le projet suit une disposition de projet Node.js/TypeScript standard. Votre structure de répertoire doit ressembler à la structure suivante :

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

Explorer le code Node.js pour l’application RAG agentique

Cette section décrit les principaux composants du flux de travail de l’agent IA. Il met en évidence la façon dont les agents traitent les demandes, comment les outils connectent l’IA à la base de données et comment les invites guident le comportement de l’IA.

application Node.js Agentic RAG

Le src/agent.ts fichier orchestre un système de recommandation d’hôtel alimenté par l’IA.

L’application utilise deux services Azure :

  • Azure OpenAI qui utilise des modèles IA qui comprennent les requêtes et génèrent des recommandations
  • Azure DocumentDB qui stocke les données de l’hôtel et effectue des recherches de similarité vectorielle

composants de l’agent et de l’outil Node.js

Les trois composants fonctionnent ensemble pour traiter la demande de recherche d’hôtel :

  • Agent planificateur : interprète la demande et décide comment effectuer une recherche
  • Outil de recherche vectorielle - Recherche des hôtels similaires à ce que décrit l’agent planificateur
  • Agent de synthétiseur - Écrit une recommandation utile en fonction des résultats de la recherche

Flux de travail de l'application RAG Agentic

L’application traite une demande de recherche d’hôtel en deux étapes :

  • Planification: Le processus de travail appelle l’agent planificateur, qui analyse la requête de l’utilisateur (comme « hôtels près des sentiers de course ») et recherche dans la base de données les hôtels correspondants.
  • Synthèse: Le flux de travail appelle l’agent de synthétiseur, qui passe en revue les résultats de la recherche et écrit une recommandation personnalisée expliquant quels hôtels correspondent le mieux à la demande.
// 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 Agents pour la planification et la synthèse

Le src/agent.ts fichier source implémente les agents planificateur et synthétiseur qui fonctionnent ensemble pour traiter les demandes de recherche d’hôtel.

Agent planificateur

L’agent planificateur est le décideur qui détermine comment rechercher des hôtels.

L’agent du planificateur reçoit la requête en langage naturel de l’utilisateur et l’envoie à un modèle IA à l’aide de l’infrastructure d’agent de LangChain, ainsi que les outils disponibles qu’il peut utiliser. L’IA décide d’appeler l’outil de recherche vectorielle et fournit des paramètres de recherche. LangChain gère automatiquement l’exécution de l’outil et retourne les hôtels correspondants. Au lieu de coder en dur la logique de recherche, l’IA interprète ce que l’utilisateur souhaite et choisit comment effectuer une recherche, ce qui rend le système flexible pour différents types de requêtes.

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;
}

Agent de synthèse

L’agent de synthèse est l'auteur qui crée des recommandations utiles.

L’agent de synthétiseur reçoit la requête utilisateur d’origine ainsi que les résultats de recherche de l’hôtel. Il envoie tout à un modèle IA avec des instructions pour l’écriture de recommandations. Elle retourne une réponse en langage naturel qui compare les hôtels et explique les meilleures options. Cette approche est importante, car les résultats de recherche bruts ne peuvent pas être utilisés de manière conviviale. Le synthétiseur transforme les enregistrements de base de données en recommandation conversationnelle qui explique pourquoi certains hôtels correspondent aux besoins de l’utilisateur.

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;
}

Le src/vector-store.ts fichier source définit l’outil de recherche vectorielle utilisé par l’agent planificateur.

Le fichier d’outils définit un outil de recherche que l’agent IA peut utiliser pour rechercher des hôtels. Cet outil est la façon dont l’agent se connecte à la base de données. L’IA ne recherche pas directement la base de données. Il demande à utiliser l’outil de recherche et l’outil exécute la recherche réelle.

Node.js Fonction en tant que définition d’outil

La fonction de tool LangChain crée un outil à partir d’une fonction TypeScript standard. La définition de l’outil inclut le nom, la description et le schéma (à l’aide de Zod pour validation). Cette définition permet à l’IA de savoir que l’outil existe et comment l’utiliser correctement.

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

Lorsque l’IA appelle l’outil, le corps de la fonction s’exécute. Il génère une incorporation en convertissant la requête de texte en vecteur numérique à l’aide du modèle d’incorporation d’Azure OpenAI. Ensuite, il recherche la base de données en envoyant le vecteur à Azure DocumentDB, qui recherche des hôtels avec des vecteurs similaires qui signifient des descriptions similaires. Enfin, il met en forme les résultats en convertissant les enregistrements de base de données en texte lisible que l’agent de synthétiseur peut comprendre.

L’implémentation tire parti de AzureDocumentDBVectorStore LangChain pour une intégration transparente à Azure DocumentDB.

Pourquoi utiliser ce modèle ?

La séparation de l’outil de l’agent offre une flexibilité. L’IA décide du moment et de l’objet de la recherche, tandis que l’outil gère comment effectuer la recherche. Vous pouvez ajouter d’autres outils sans modifier la logique de l’agent.

L’agent invite à guider le comportement de l’IA

Le src/utils/prompts.ts fichier source contient des invites système et des définitions d’outils pour les agents.

Le fichier de commandes définit les instructions et le contexte donnés aux modèles d'IA pour les agents planificateur et synthétiseur. Ces instructions guident le comportement de l’IA et garantissent qu’elle comprend son rôle dans le processus de travail.

La qualité des réponses ia dépend fortement des instructions claires. Ces instructions fixent des limites, précisent le format de sortie et concentrent l’intelligence artificielle sur l’objectif de l’utilisateur de prendre une décision. Vous pouvez personnaliser ces invites pour modifier le comportement des agents sans modifier de code.

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.`;

Préparer et charger des données dans Azure DocumentDB avec Node.js

L’exemple utilise des données d’hôtel à partir d’un fichier JSON. Le référentiel comprend deux versions :

  • Hotels.json - Données d’hôtel sans incorporations vectorielles (utilisées par cet exemple)
  • Hotels_Vector.json - Données d’hôtel avec des incorporations pré-calculées (utilisées par d’autres exemples)

Fonctionnement du chargement

Le upload-documents.ts script effectue trois étapes :

  1. Charger des données : lit les enregistrements d’hôtel à partir du Hotels.json fichier.
  2. Génération d’incorporations : pour chaque hôtel, le script envoie le Description champ au modèle Azure OpenAI text-embedding-3-small pour générer un vecteur 1536 dimensionnel incorporé. Cela convertit la description du texte en représentation numérique qui capture sa signification sémantique.
  3. Insertion et index : le script insère des documents (avec leurs incorporations) dans la collection Azure DocumentDB et crée un index vectoriel à l’aide de l’algorithme configuré (IVF, HNSW ou 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();

Création d'un index vectoriel

L’index vectoriel est ce qui permet une recherche de similarité rapide. Lorsque l’index est créé, Azure DocumentDB organise les vecteurs d’incorporation afin que les requêtes telles que « rechercher des hôtels similaires à cette description » puissent être répondues efficacement sans analyser chaque document.

Le type d’index que vous choisissez affecte les performances :

Algorithm Niveau de cluster Idéal pour
IVF M10+ Jeux de données de petite à moyenne taille, coût inférieur
HNSW M30+ Rappel élevé, requêtes rapides
DiskANN M40+ Jeux de données à grande échelle, plus d'un milliard de vecteurs
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:

Exécutez l’application RAG agentique avec Node.js

  1. Installez des dépendances :

    npm install
    
  2. Avant d’exécuter l’agent, téléversez les données hôtelières avec des embeddings. La upload-documents.ts commande charge les hôtels à partir du fichier JSON, génère des incorporations pour chaque hôtel à l’aide text-embedding-3-smallde , insère des documents dans Azure DocumentDB et crée un index vectoriel.

    npm run upload
    
  3. Exécutez l’agent de recommandation d’hôtel à l’aide de la agent.ts commande. L'agent appelle l'agent planificateur, l'agent de recherche vectorielle et l'agent de synthèse. La sortie comprend des scores de similarité et l'analyse comparative de l'agent-synthétiseur, accompagnée de recommandations.

    npm start
    
    DEBUG 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.
    

Afficher et gérer des données dans Visual Studio Code

  1. Sélectionnez l’extension DocumentDB dans Visual Studio Code pour vous connecter à votre compte Azure DocumentDB.

  2. Affichez les données et les index dans la base de données Hotels.

    Extension Visual Studio Code DocumentDB montrant l’index de recherche vectorielle et les documents d’hôtel.

Nettoyer les ressources

Si vous avez utilisé azd up pour approvisionner des ressources, vous pouvez supprimer toutes les ressources Azure avec :

azd down

Si vous avez créé manuellement les ressources et souhaitez supprimer toutes les ressources, supprimez le groupe de ressources pour éviter les coûts supplémentaires.

Si vous souhaitez réutiliser les ressources, utilisez la commande de nettoyage pour supprimer la base de données de test lorsque vous avez terminé. Exécutez la commande suivante:

npm run cleanup