Esercitazione: Creare un'app Web agentic nel servizio app di Azure con LangGraph o il servizio agente Foundry (Node.js)

Questa esercitazione illustra come aggiungere funzionalità agentiche a un'applicazione CRUD basata sui dati esistente Express.js. A tale scopo, si usano due approcci diversi: LangGraph e Foundry Agent Service.

Se l'applicazione Web ha già funzionalità utili, ad esempio acquisti, prenotazioni di hotel o gestione dei dati, è relativamente semplice aggiungere funzionalità dell'agente all'applicazione Web eseguendo il wrapping di tali funzionalità in un plug-in (per LangGraph) o come endpoint OpenAPI (per il servizio agente Foundry). In questo tutorial inizi con una semplice app lista di cose da fare. Al termine, sarai in grado di creare, aggiornare e gestire attività con un agente in un'app di Servizio App.

Sia LangGraph che il servizio agente Foundry consentono di creare applicazioni Web agentic con funzionalità basate su intelligenza artificiale. LangGraph è simile a Microsoft Kernel semantico ed è un SDK, ma il kernel semantico attualmente non supporta JavaScript. La tabella seguente illustra alcune considerazioni e compromessi:

Consideration LangGraph Servizio Agente Fonderia
Performance Veloce (funziona localmente) Più lento (gestito, servizio remoto)
Development Codice completo, controllo massimo Basso codice, integrazione rapida
Testing Test manuali/unit test nel codice Playground predefinito per i test rapidi
Scalability Gestito dall'app Gestito da Azure, con scalabilità automatica
Protezioni di sicurezza Implementazione personalizzata richiesta Sicurezza e moderazione dei contenuti predefiniti
Identità Implementazione personalizzata richiesta ID e autenticazione predefiniti dell'agente
Enterprise Integrazione personalizzata richiesta Distribuzione predefinita di Microsoft 365/Teams e chiamate agli strumenti integrati di Microsoft 365.

Nell'app distribuita, l'autenticazione tramite App Service richiede il login Microsoft Entra sia per l'interfaccia del browser che per le API. LangGraph viene eseguito in App Service e chiama direttamente il servizio attività. Foundry Agent Service funziona da remoto e chiama l'API protetta tramite il suo strumento OpenAPI.

In questa esercitazione si apprenderà come:

  • Convertire le funzionalità dell'app esistenti in un plug-in per LangGraph.
  • Aggiungere il plug-in a un agente LangGraph e usarlo in un'app Web.
  • Convertire le funzionalità dell'app esistenti in un endpoint OpenAPI per il servizio agente Foundry.
  • Chiamare un agente Foundry in un'applicazione web.
  • Assegnare le autorizzazioni necessarie per la connettività dell'identità gestita.
  • Proteggi un'app web di App Service e le sue API con Microsoft Entra ID.
  • Configura uno strumento OpenAPI di Foundry per chiamare API di App Service protette con identità gestita.

Prerequisites

Aprire l'esempio con Codespaces

Il modo più semplice per iniziare consiste nell'usare GitHub Codespaces, che offre un ambiente di sviluppo completo con tutti gli strumenti necessari preinstallati.

  1. Passare al repository GitHub all'indirizzo https://github.com/Azure-Samples/app-service-agentic-langgraph-foundry-node.

  2. Selezionare il pulsante Codice , selezionare la scheda Codespaces e selezionare Crea spazio di codice nel main.

  3. Attendere alcuni istanti per inizializzare Codespace. Quando si è pronti, nel browser verrà visualizzato un ambiente di sviluppo completamente configurato.

  4. Eseguire l'applicazione in locale:

    npm install
    npm run build
    npm start
    
  5. Quando viene visualizzato L'applicazione in esecuzione sulla porta 3000 è disponibile, selezionare Apri nel browser e aggiungere alcune attività.

    Gli agenti non sono completamente configurati per cui non funzionano ancora. Verranno configurati in un secondo momento.

Esaminare il codice dell'agente

Entrambi gli approcci usano lo stesso modello di implementazione, in cui l'agente viene inizializzato all'avvio dell'applicazione e risponde ai messaggi utente da richieste POST.

LangGraphTaskAgent viene inizializzato nel costruttore in src/agents/LangGraphTaskAgent.ts. Il codice di inizializzazione esegue le operazioni seguenti:

    constructor(taskService: TaskService) {
        this.taskService = taskService;
        this.memory = new MemorySaver();
        try {
            const endpoint = process.env.AZURE_OPENAI_ENDPOINT;
            const deploymentName = process.env.AZURE_OPENAI_DEPLOYMENT_NAME;

            if (!endpoint || !deploymentName) {
                console.warn('Azure OpenAI configuration missing for LangGraph agent');
                return;
            }
            // Initialize Azure OpenAI client
            const credential = new DefaultAzureCredential();
            const azureADTokenProvider = getBearerTokenProvider(credential, "https://cognitiveservices.azure.com/.default");
            
            this.llm = new AzureChatOpenAI({
                azureOpenAIEndpoint: endpoint,
                azureOpenAIApiDeploymentName: deploymentName,
                azureADTokenProvider: azureADTokenProvider,
                azureOpenAIApiVersion: "2024-10-21"
            });
            // Define tools directly in the array
            const tools = [
                tool(
                    async ({ title, isComplete = false }) => {
                        const task = await this.taskService.addTask(title, isComplete);
                        return `Task created successfully: "${task.title}" (ID: ${task.id})`;
                    },
                    {
                        name: 'createTask',
                        description: 'Create a new task',
                        schema: z.object({
                            title: z.string(),
                            isComplete: z.boolean().optional()
                        }) as any
                    }
                ),
                tool(
                    async () => {
                        const tasks = await this.taskService.getAllTasks();
                        if (tasks.length === 0) {
                            return 'No tasks found.';
                        }
                        return `Found ${tasks.length} tasks:\n` + 
                               tasks.map(t => `- ${t.id}: ${t.title} (${t.isComplete ? 'Complete' : 'Incomplete'})`).join('\n');
                    },
                    {
                        name: 'getTasks',
                        description: 'Get all tasks',
                        schema: z.object({}) as any
                    }
                ),
                tool(
                    async ({ id }) => {
                        const task = await this.taskService.getTaskById(id);
                        if (!task) {
                            return `Task with ID ${id} not found.`;
                        }
                        return `Task ${task.id}: "${task.title}" - Status: ${task.isComplete ? 'Complete' : 'Incomplete'}`;
                    },
                    {
                        name: 'getTask',
                        description: 'Get a specific task by ID',
                        schema: z.object({
                            id: z.number()
                        }) as any
                    }
                ),
                tool(
                    async ({ id, title, isComplete }) => {
                        const updated = await this.taskService.updateTask(id, title, isComplete);
                        if (!updated) {
                            return `Task with ID ${id} not found.`;
                        }
                        return `Task ${id} updated successfully.`;
                    },
                    {
                        name: 'updateTask',
                        description: 'Update an existing task',
                        schema: z.object({
                            id: z.number(),
                            title: z.string().optional(),
                            isComplete: z.boolean().optional()
                        }) as any
                    }
                ),
                tool(
                    async ({ id }) => {
                        const deleted = await this.taskService.deleteTask(id);
                        if (!deleted) {
                            return `Task with ID ${id} not found.`;
                        }
                        return `Task ${id} deleted successfully.`;
                    },
                    {
                        name: 'deleteTask',
                        description: 'Delete a task',
                        schema: z.object({
                            id: z.number()
                        }) as any
                    }
                )
            ];

            // Create the ReAct agent with memory
            this.agent = createReactAgent({
                llm: this.llm,
                tools,
                checkpointSaver: this.memory,
                stateModifier: `You are an AI assistant that manages tasks using CRUD operations.
                
You have access to tools for creating, reading, updating, and deleting tasks.
Always use the appropriate tool for any task management request.
Be helpful and provide clear responses about the actions you take.

If you need more information to complete a request, ask the user for it.`
            });
        } catch (error) {
            console.error('Error initializing LangGraph agent:', error);
        }
    }

Il campione distribuito è protetto dall'autenticazione App Service e utilizza un thread LangGraph selezionato dal server. Quando elabori i messaggi dell'utente, l'agente invoca invoke() il messaggio dell'utente e l'ID thread gestito dal server:

private readonly conversationThreadId = 'authenticated-conversation';

const result = await this.agent.invoke(
    {
        messages: [
            { role: 'user', content: message }
        ]
    },
    {
        configurable: {
            thread_id: this.conversationThreadId
        }
    }
);

Distribuire l'applicazione di esempio

Il repository di esempio contiene un template Azure Developer CLI (AZD), che crea un'app di App Service e distribuisce la tua applicazione di esempio. Il template consente un'identità gestita assegnata al sistema per le chiamate AI di Azure in uscita e configura l'autenticazione degli App Service con Microsoft Entra ID. Per ulteriori informazioni sulla configurazione di autenticazione sottostante, vedi Secure OpenAPI endpoints for Foundry Agent Service.

  1. Nel terminale, accedi ad Azure usando Azure Developer CLI:

    azd auth login
    

    Seguire le istruzioni per completare il processo di autenticazione.

  2. Distribuisci l'app Servizio app di Azure utilizzando il modello AZD:

    azd up
    
  3. Quando richiesto, fornire le risposte seguenti:

    Question Answer
    Immettere un nuovo nome di ambiente: Digitare un nome univoco.
    Selezionare una sottoscrizione di Azure da usare: Selezionare la sottoscrizione.
    Selezionare un gruppo di risorse da usare: Selezionare Crea un nuovo gruppo di risorse.
    Selezionare un percorso in cui creare il gruppo di risorse: Selezionare Svezia centrale.
    Immettere un nome per il nuovo gruppo di risorse: Immettere Invio.
  4. Nell'output AZD trovare l'URL dell'app. Copia anche il valore dell'audience di identità gestita da Foundry OpenAPI per più avanti. L'output è simile al seguente:

     Deploying services (azd deploy)
    
       (✓) Done: Deploying service web
       - Endpoint: <URL>
    
     Foundry OpenAPI managed identity audience:
     api://<generated-client-id>
     
  5. Apri l'endpoint di App Service dall'output di AZD.

  6. Quando Microsoft ti chiede, accedi usando un account nel tenant di distribuzione e verifica che la lista delle attività si carichi.

  7. Nello stesso browser autenticato, apri lo schema OpenAPI autogenerato a https://<app-name>.azurewebsites.net/api/schema.

  8. Copia o salva lo schema OpenAPI generato. Lo usi nel pivot del Foundry Agent Service.

    Note

    L'autenticazione tramite App Service restituisce un reindirizzamento HTTP 302 per le richieste del browser non autenticate. Questo esempio contiene sia un'interfaccia utente del browser che API, quindi il redirect offre un'esperienza di accesso utilizzabile. Le app basate esclusivamente su API usano comunemente invece HTTP 401.

    Ora disponi di un'app di App Service autenticata. La sua identità gestita assegnata al sistema viene utilizzata per le chiamate in uscita di Foundry. Un'identità gestita separata assegnata dall'utente fornisce credenziali senza segreti per l'autenticazione dei servizi applicativi.

Creare e configurare la risorsa Microsoft Foundry

  1. Nel portale Foundry, crea un progetto.

  2. Distribuire un modello preferito (vedere Avvio rapido di Microsoft Foundry: Creare risorse).

  3. Nella parte superiore del playground del modello copiare il nome del modello.

  4. Nella pagina iniziale, copia l'endpoint di Azure OpenAI per usarlo in seguito.

Assegnare le autorizzazioni necessarie

  1. Nel portale Foundry, seleziona Gestisci nel menu superiore.

  2. In Project details, seleziona la risorsa Genitore per il tuo project, poi seleziona Apri in Azure portal.

    Dal portale Azure puoi assegnare l'accesso basato sul ruolo per la risorsa.

  3. Aggiungi il seguente ruolo sia per l'identità gestita dell'app di App Service sia per l'utente che usi con az login:

    Risorsa di destinazione Ruolo obbligatorio Necessario per
    Fonderia Utente di Servizi Cognitivi OpenAI Servizio di completamento della chat in Microsoft Agent Framework.

    Per istruzioni, vedere Assegnare ruoli di Azure usando il portale di Azure.

Configurare le variabili di connessione nell'applicazione di esempio

  1. Aprire il file con estensione env. Usando i valori copiati in precedenza dal portale foundry, configurare le variabili seguenti:

    Variable Description
    AZURE_OPENAI_ENDPOINT Azure OpenAI endpoint (copiato dalla homepage del portale Foundry).
    AZURE_OPENAI_DEPLOYMENT_NAME Nome del modello nella distribuzione (copiato dall'area di sperimentazione del modello nel nuovo portale Foundry).

    Note

    Per semplificare l'esercitazione, queste variabili verranno usate in .env invece di sovrascriverle con le impostazioni dell'app nel servizio app.

    Note

    Per semplificare l'esercitazione, queste variabili verranno usate in .env invece di sovrascriverle con le impostazioni dell'app nel servizio app.

    I valori in .env configurano la connessione in uscita dell'app a Foundry. AZURE_AI_FOUNDRY_ACCOUNT_CLIENT_ID configura la connessione OpenAPI separata in ingresso da Foundry ad App Service e viene memorizzata nell'ambiente AZD.

L'autenticazione tramite App Service funziona in Azure, non nel processo locale Express, quindi il flusso di lavoro di test locale rimane invariato.

  1. Accedere ad Azure con l'interfaccia della riga di comando di Azure:

    az login
    

    Ciò consente alla libreria client di Identità di Azure nel codice di esempio di ricevere un token di autenticazione per l'utente connesso. Tenere presente che è stato aggiunto il ruolo necessario per questo utente in precedenza.

  2. Eseguire l'applicazione in locale:

    npm run build
    npm start
    
  3. Quando viene visualizzato L'applicazione in esecuzione sulla porta 3000 è disponibile, selezionare Apri nel browser.

  4. Valida entrambi i pivot separatamente:

    • LangGraph: Seleziona LangGraph Agent e chiedi all'agente di creare un compito. LangGraph chiama lo strumento di task in processo.
    • Servizio Foundry Agent: Seleziona Foundry Agent, e chiedi all'agente di creare un'attività. L'agente remoto di Foundry invoca l'endpoint /api/tasks distribuito e protetto usando l'identità gestita.

    Il compito creato dall'agente Foundry appare nell'istanza del servizio App distribuito, non nel database locale in memoria. Lo strumento Foundry OpenAPI utilizza sempre l'URL del server incorporato nello schema OpenAPI.

  5. Tornare nello spazio di codice GitHub, distribuire le modifiche dell'app.

    azd up
    
  6. Naviga fino all'applicazione distribuita, accedi e testa entrambi i pivot. Crea e elenca i compiti con Agente LangGraph, poi crea e elenca i compiti con Agente Foundry. Verifica che entrambi i pivot aggiornino la lista delle attività.

Domande frequenti

Come posso aggiungere la generazione aumentata di recupero (RAG) all'agente Foundry?

Queste indicazioni si applicano al percorso Foundry Agent Service in questo tutorial. Non cambia le implementazioni di LangGraph, Kernel semantico o Microsoft Agent Framework mostrate nell'altra scheda.

Crea o seleziona una knowledge base di Foundry IQ, e poi collega la knowledge base all'agente di Foundry Agent Service. La connessione viene esposta all'agente come uno strumento di conoscenza MCP gestito.

Il codice di App Service continua a richiamare lo stesso agente per nome tramite il client Foundry esistente e agent_reference. L'app web non ha bisogno di un'integrazione diretta con Azure AI Search né di un proprio client MCP. Se l'interfaccia mostra le fonti, elabora le annotazioni di citazione restituite dall'agente.

Quale identità gestita utilizza ogni connessione?

Direzione Identità
App Service chiama Foundry Identità assegnata al sistema App Service
Chiamate dello strumento OpenAPI di Foundry /api/tasks Identità assegnata al sistema di risorsa Parent Foundry

L'endpoint del progetto seleziona il progetto e l'agente. Non determina l'identità che lo strumento OpenAPI ospitato utilizza.

Pulire le risorse

Al termine dell'applicazione, è possibile eliminare le risorse del servizio app per evitare di sostenere ulteriori costi:

azd down --purge

Poi, elimina la risorsa Foundry se l'hai creata separatamente.

Altre risorse