Tutorial: Compilación de una aplicación web agente en Azure App Service con LangGraph o Foundry Agent Service (Node.js)

En este tutorial se muestra cómo agregar funcionalidad agente a una aplicación CRUD Express.js controlada por datos existente. Para ello, se usan dos enfoques diferentes: LangGraph y Foundry Agent Service.

Si la aplicación web ya tiene características útiles, como compras, reservas de hoteles o administración de datos, es relativamente sencillo agregar funcionalidad de agente a la aplicación web ajustando esas funcionalidades en un complemento (para LangGraph) o como punto de conexión de OpenAPI (para el servicio de agente de Foundry). En este tutorial, empezará con una aplicación de lista de tareas pendientes sencilla. Al final, podrá crear, actualizar y administrar tareas con un agente en una aplicación de App Service.

LangGraph y Foundry Agent Service le permiten crear aplicaciones web agente con funcionalidades controladas por IA. LangGraph es similar al kernel semántico de Microsoft y es un SDK, pero el kernel semántico no admite JavaScript actualmente. En la tabla siguiente se muestran algunas de las consideraciones y desventajas:

Consideration LangGraph Servicio de agente de fundición
Performance Rápido (se ejecuta localmente) Más lento (administrado, servicio remoto)
Development Código completo, control máximo Poco código, integración rápida
Testing Pruebas manuales y unitarias en el código Parque infantil integrado para pruebas rápidas
Scalability App-managed Administrado por Azure, escalado automático
Barreras de protección de seguridad Implementación personalizada necesaria Seguridad y moderación de contenido integrada
identidad Implementación personalizada necesaria ID de agente integrado y autenticación
Enterprise Integración personalizada necesaria Implementación integrada de Microsoft 365/Teams y llamadas a herramientas integradas de Microsoft 365.

En la aplicación desplegada, la autenticación de App Service requiere iniciar sesión con Microsoft Entra tanto para la interfaz del navegador como para las APIs. LangGraph se ejecuta dentro del servicio de aplicaciones y llama directamente al servicio de tareas. Foundry Agent Service se ejecuta remotamente y llama a la API de tareas protegidas a través de su herramienta OpenAPI.

En este tutorial, aprenderá a:

  • Convierta la funcionalidad de la aplicación existente en un complemento para LangGraph.
  • Agregue el complemento a un agente de LangGraph y úselo en una aplicación web.
  • Convierta la funcionalidad de la aplicación existente en un punto de conexión de OpenAPI para foundry Agent Service.
  • Contacte con un agente de Foundry en una aplicación web.
  • Asigne los permisos necesarios para la conectividad de identidad administrada.
  • Protege una aplicación web de Servicio de Aplicaciones y sus APIs con Microsoft Entra ID.
  • Configura una herramienta OpenAPI de Foundry para llamar a APIs de servicios de aplicaciones protegidas con identidad gestionada.

Prerequisites

Abra el ejemplo con Codespaces.

La manera más fácil de empezar es usar GitHub Codespaces, que proporciona un entorno de desarrollo completo con todas las herramientas necesarias preinstaladas.

  1. Vaya al repositorio de GitHub en https://github.com/Azure-Samples/app-service-agentic-langgraph-foundry-node.

  2. Seleccione el botón Code (Código ), seleccione la pestaña Codespaces (Espacios de código ) y seleccione Create codespace on main (Crear espacio de código en main).

  3. Espere unos instantes para que codespace se inicialice. Cuando esté listo, verá un entorno de desarrollo totalmente configurado en el explorador.

  4. Ejecute la aplicación localmente:

    npm install
    npm run build
    npm start
    
  5. Cuando vea La aplicación que se ejecuta en el puerto 3000 está disponible, seleccione Abrir en el explorador y agregue algunas tareas.

    Los agentes no están completamente configurados, por lo que aún no funcionan. Los configurará más adelante.

Revisión del código del agente

Ambos enfoques usan el mismo patrón de implementación, donde el agente se inicializa en el inicio de la aplicación y responde a los mensajes de usuario mediante solicitudes POST.

LangGraphTaskAgent se inicializa en el constructor en src/agents/LangGraphTaskAgent.ts. El código de inicialización hace lo siguiente:

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

La muestra desplegada está protegida por autenticación por App Service y utiliza un hilo LangGraph seleccionado por el servidor. Cuando procesas mensajes de usuario, el agente invoca invoke() con el mensaje del usuario y el ID del hilo gestionado por el servidor:

private readonly conversationThreadId = 'authenticated-conversation';

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

Implementación de la aplicación de ejemplo

El repositorio de ejemplo contiene una plantilla de CLI de Azure Developer (AZD), que crea una aplicación de Servicio de Aplicaciones y despliega tu aplicación de ejemplo. La plantilla permite una identidad gestionada asignada por el sistema para las llamadas de IA de Azure salientes y configura la autenticación de servicios de aplicaciones con Microsoft Entra ID. Para más información sobre la configuración de autenticación subyacente, consulte Endpoints OpenAPI seguros para el Servicio de Agentes de Foundry.

  1. En el terminal, inicia sesión en Azure usando la CLI de Azure Developer:

    azd auth login
    

    Siga las instrucciones para completar el proceso de autenticación.

  2. Despliega la aplicación de Azure App Service usando la plantilla AZD:

    azd up
    
  3. Cuando se le solicite, proporcione las siguientes respuestas:

    Question Answer
    Escriba un nuevo nombre de entorno: Escriba un nombre único.
    Seleccione una suscripción de Azure para usar: Seleccione la suscripción.
    Elija un grupo de recursos para usar: Seleccione Crear un grupo de recursos nuevo.
    Seleccione una ubicación para crear el grupo de recursos en: Seleccione Centro de Suecia.
    Escriba un nombre para el nuevo grupo de recursos: Escriba Entrar.
  4. En la salida de AZD, busque la dirección URL de la aplicación. También copia el valor de audiencia de identidad gestionada de Foundry OpenAPI para usarlo más adelante. El resultado tiene este aspecto:

     Deploying services (azd deploy)
    
       (✓) Done: Deploying service web
       - Endpoint: <URL>
    
     Foundry OpenAPI managed identity audience:
     api://<generated-client-id>
     
  5. Abre el punto de conexión de App Service desde la salida de AZD.

  6. Cuando Microsoft te lo pida, inicia sesión usando una cuenta en el tenant de despliegue y verifica que la lista de tareas se carga.

  7. En el mismo navegador autenticado, abre el esquema OpenAPI autogenerado en https://<app-name>.azurewebsites.net/api/schema.

  8. Copia o guarda el esquema OpenAPI generado. Se utiliza en el panel de Foundry Agent Service.

    Note

    La autenticación por App Service devuelve una redirección HTTP 302 para solicitudes de navegador no autenticadas. Este ejemplo contiene tanto una interfaz de navegador como APIs, por lo que la redirección proporciona una experiencia de inicio de sesión utilizable. Las aplicaciones solo API suelen usar HTTP 401 en su lugar.

    Ahora tienes una app de App Service autenticada. Su identidad gestionada asignada por el sistema se utiliza para las llamadas salientes a Foundry. Una identidad gestionada asignada por el usuario proporciona credenciales sin secreto para la autenticación de servicios de aplicaciones.

Creación y configuración del recurso de Microsoft Foundry

  1. En el portal de Foundry, crea un proyecto.

  2. Implemente un modelo de su elección (consulte Inicio rápido de Microsoft Foundry: Creación de recursos).

  3. En la parte superior del área de juegos de modelos, copie el nombre del modelo.

  4. En la página principal, copia el punto de conexión de Azure OpenAI para usarlo más tarde.

Asignación de permisos necesarios

  1. En el portal de Foundry, selecciona Gestionar en el menú superior.

  2. En Detalles del proyecto, selecciona el recurso primario de tu proyecto y luego selecciona Abrir en Azure Portal.

    Desde el portal de Azure, puedes asignar acceso basado en roles para el recurso.

  3. Añada el siguiente rol tanto a la identidad administrada de la aplicación de App Service como al usuario que utilice con az login:

    Recurso de destino Rol necesario Necesario para
    Fundición Usuario de OpenAI de Cognitive Services El servicio de finalización del chat en Microsoft Agent Framework.

    Para obtener instrucciones, consulte Asignación de roles de Azure mediante Azure Portal.

Configuración de variables de conexión en la aplicación de ejemplo

  1. Abra .env. Con los valores que copió anteriormente desde el portal de Foundry, configure las siguientes variables:

    Variable Description
    AZURE_OPENAI_ENDPOINT Azure OpenAI endpoint (copiado de la página principal del portal Foundry).
    AZURE_OPENAI_DEPLOYMENT_NAME Nombre del modelo en la implementación (copiado del entorno de pruebas de modelos en el nuevo portal de Foundry).

    Note

    Para simplificar el tutorial, usará estas variables en .env en lugar de sobrescribirlas con la configuración de la aplicación en App Service.

    Note

    Para simplificar el tutorial, usará estas variables en .env en lugar de sobrescribirlas con la configuración de la aplicación en App Service.

    Los valores en .env configuran la conexión saliente de la app a Foundry. AZURE_AI_FOUNDRY_ACCOUNT_CLIENT_IDconfigura la conexión OpenAPI de entrada independiente de Foundry a App Service y se almacena en el entorno AZD.

La autenticación de App Service se ejecuta en Azure, no en el proceso local Express, por lo que el flujo de trabajo de pruebas local permanece sin cambios.

  1. Inicie sesión en Azure con la CLI de Azure:

    az login
    

    Esto permite que la biblioteca cliente de Identidad de Azure en el código de ejemplo reciba un token de autenticación para el usuario que ha iniciado sesión. Recuerde que ha agregado el rol necesario para este usuario anteriormente.

  2. Ejecute la aplicación localmente:

    npm run build
    npm start
    
  3. Cuando vea La aplicación que se ejecuta en el puerto 3000 está disponible, seleccione Abrir en el explorador.

  4. Valida ambos pivotes por separado:

    • LangGraph:Selecciona LangGraph Agent y pídele al agente que cree una tarea. LangGraph invoca la herramienta de tareas en proceso.
    • Servicio Foundry Agent: Selecciona Foundry Agent y pídele al agente que cree una tarea. El agente remoto de Foundry invoca el punto de conexión /api/tasks implementado y protegido con identidad gestionada.

    La tarea que crea el agente de Foundry aparece en la instancia de App Service desplegada, no en la base de datos local en memoria. La herramienta OpenAPI de Foundry siempre utiliza la URL del servidor incrustada en el esquema OpenAPI.

  5. De nuevo en el espacio de código de GitHub, implemente los cambios de la aplicación.

    azd up
    
  6. Navega hasta la aplicación desplegada, inicia sesión y prueba ambos pivotes. Crea y lista tareas con el Agente LangGraph, y luego crea y lista tareas con el Agente Foundry. Comprueba que ambos pivotes actualicen la lista de tareas.

Preguntas más frecuentes

¿Cómo puedo añadir generación aumentada por recuperación (RAG) al agente de Foundry?

Esta guía se aplica a la ruta Foundry Agent Service en este tutorial. No cambia las implementaciones de LangGraph, Kernel semántico o Microsoft Agent Framework que se muestran en la otra pestaña.

Crea o selecciona una base de conocimientos de Foundry IQ y luego conecta esa base con el agente de Foundry Agent Service. La conexión se expone al agente como una herramienta de conocimiento MCP administrada.

El código de App Service sigue invocando al mismo agente por su nombre a través de su cliente Foundry existente y agent_reference. La aplicación web no necesita una integración directa con Búsqueda de Azure AI ni un cliente MCP propio. Si la interfaz muestra las fuentes, procesa las anotaciones de citas devueltas por el agente.

¿Qué identidad gestionada utiliza cada conexión?

Direction identidad
App Service llama a Foundry Identidad asignada por el sistema de App Service
Llamadas a la herramienta OpenAPI de Foundry /api/tasks Identidad asignada por el sistema al recurso padre de Foundry

El endpoint del proyecto selecciona el proyecto y el agente. No determina la identidad que utiliza la herramienta OpenAPI alojada.

Limpieza de recursos

Cuando haya terminado con la aplicación, puede eliminar los recursos de App Service para evitar incurrir en costos adicionales:

azd down --purge

Luego, elimina el recurso de Foundry si lo creaste por separado.

Más recursos