Руководство по созданию агентного веб-приложения в Службе приложений Azure с помощью LangGraph или Службы агента Foundry (Node.js)

В этом руководстве показано, как добавить агентическую возможность в существующее приложение, управляемое данными, Express.js CRUD. Это делается с помощью двух различных подходов: LangGraph и Агентской службы Foundry.

Если веб-приложение уже имеет полезные функции, такие как покупки, бронирование отелей или управление данными, это относительно просто добавить функции агента в веб-приложение, упаковав эти функции в подключаемый модуль (для LangGraph) или как конечную точку OpenAPI (для службы агента Foundry). В этом руководстве вы начнёте с простого приложения для списков to-do. К концу вы сможете создавать, обновлять и управлять задачами с агентом в приложении службы приложений.

Служба агента LangGraph и Foundry позволяют создавать агентские веб-приложения с возможностями на основе ИИ. LangGraph похож на семантический ядро Майкрософт и является пакетом SDK, но семантический ядро в настоящее время не поддерживает JavaScript. В следующей таблице показаны некоторые рекомендации и компромиссы.

Consideration LangGraph Служба агента Foundry
Performance Быстро (выполняется локально) Медленнее (управляемая, удаленная служба)
Development Полный код, максимальный контроль Низкий код, быстрая интеграция
Testing Тесты вручную и модульные тесты в коде Встроенная площадка для быстрого тестирования
Scalability App-managed Под управлением Azure, с автоматическим масштабированием
Ограничительные меры безопасности Требуется настраиваемая реализация Встроенная безопасность содержимого и модерация
Идентичность Требуется настраиваемая реализация Встроенный идентификатор агента и проверка подлинности
Предприятие Требуется настраиваемая интеграция Встроенное развертывание Microsoft 365/Teams и вызовы интегрированных средств Microsoft 365.

В этом руководстве вы узнаете, как:

  • Преобразуйте существующие функции приложения в подключаемый модуль для LangGraph.
  • Добавьте подключаемый модуль в агент LangGraph и используйте его в веб-приложении.
  • Преобразуйте существующую функциональность приложения в конечную точку OpenAPI для Foundry Agent Service.
  • Вызвать агента Foundry в веб-приложении.
  • Назначьте необходимые разрешения для подключения к системе с управляемыми удостоверениями.

Prerequisites

Открытие примера с помощью codespaces

Проще всего приступить к работе с помощью GitHub Codespaces, который предоставляет полную среду разработки со всеми необходимыми средствами, предварительно установленными.

  1. Перейдите в репозиторий GitHub по адресу https://github.com/Azure-Samples/app-service-agentic-langgraph-foundry-node.

  2. Нажмите кнопку "Код" , перейдите на вкладку "Пространства кода" и выберите "Создать пространство кода" на главном.

  3. Подождите несколько минут, пока пространство кода инициализируется. Когда вы будете готовы, вы увидите полностью настроенную среду разработки в браузере.

  4. Запустите приложение локально:

    npm install
    npm run build
    npm start
    
  5. Когда вы видите Ваше приложение, доступное на порту 3000, выберите "Открыть в браузере" и добавьте несколько заданий.

    Агенты не полностью настроены, поэтому они еще не работают. Вы настроите их позже.

Проверка кода агента

Оба подхода используют один и тот же шаблон реализации, где агент инициализируется при запуске приложения и отвечает на пользовательские сообщения с помощью запросов POST.

LangGraphTaskAgent инициализируется в конструкторе в src/agents/LangGraphTaskAgent.ts. Код инициализации выполняет следующие действия:

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

При обработке сообщений пользователей агент вызывается с помощью invoke() вместе с пользовательским сообщением и конфигурацией сеанса для обеспечения непрерывности беседы.

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

Развертывание примера приложения

Репозиторий с примером содержит шаблон для Azure Developer CLI (AZD), который создает приложение службы приложений Azure с управляемым удостоверением и развертывает образец приложения.

  1. В терминале войдите в Azure с помощью Интерфейса командной строки разработчика Azure:

    azd auth login
    

    Следуйте инструкциям, чтобы завершить процесс проверки подлинности.

  2. Разверните приложение Службы приложений Azure с помощью шаблона AZD:

    azd up
    
  3. При появлении запроса укажите следующие ответы:

    Question Answer
    Введите новое имя среды: Введите уникальное имя.
    Выберите подписку Azure для использования: Выберите подписку.
    Выберите группу ресурсов для использования: Выберите команду Создать группу ресурсов.
    Выберите расположение для создания группы ресурсов в: Выберите Швецию Центральную.
    Введите имя новой группы ресурсов: Нажмите Enter.
  4. В выводе AZD найдите URL вашего приложения и откройте его в браузере. URL-адрес выглядит следующим образом в выходных данных AZD:

     Deploying services (azd deploy)
    
       (✓) Done: Deploying service web
       - Endpoint: <URL>
     
  5. Откройте автоматически созданную схему OpenAPI по пути https://....azurewebsites.net/api/schema. Вам потребуется эта схема позже.

    Теперь у вас есть приложение службы приложений с управляемым удостоверением, назначаемое системой.

Создание и настройка ресурса Microsoft Foundry

  1. На портале Foundry убедитесь, что верхняя кнопка-переключатель New Foundry активирована, и создайте проект.

  2. Разверните модель выбранного варианта (см. краткое руководство по Microsoft Foundry: создание ресурсов).

  3. В верхней части игровой площадки модели скопируйте имя модели.

  4. Самый простой способ получить конечную точку Azure OpenAI по-прежнему с классического портала. Нажмите переключатель New Foundry, затем выберите Azure OpenAI, а потом скопируйте URL-адрес в Azure OpenAI endpoint на будущее.

    Скриншот, на котором показано, как скопировать конечную точку OpenAI и конечную точку проекта Foundry в портале.

Назначьте необходимые разрешения

  1. В верхнем меню нового портала Foundry выберите "Работа", а затем выберите "Администратор". В строке проекта Foundry вы увидите две ссылки. Один из столбцов Name — ресурс проекта Foundry, а в столбце "Родительский ресурс" — ресурс Foundry.

    Снимок экрана, показывающий, как быстро перейти к ресурсу литейного производства или ресурсу проекта литейного производства.

  2. Выберите ресурс Foundry в родительском ресурсе и выберите "Управление этим ресурсом" на портале Azure. На портале Azure можно назначить доступ на основе ролей для ресурса развернутого веб-приложения.

  3. Добавьте следующую роль для управляемой идентификации приложения службы приложений.

    Целевой ресурс Требуемая роль Требуется для
    Литейный завод Пользователь когнитивных сервисов OpenAI Служба завершения чата в Microsoft Agent Framework.

    Инструкции см. в разделе Назначение ролей Azure с помощью портала Azure.

Настройка переменных подключения в примере приложения

  1. Откройте .env. Используя значения, скопированные ранее на портале Foundry, настройте следующие переменные:

    Variable Description
    AZURE_OPENAI_ENDPOINT Конечная точка Azure OpenAI (скопирована с классического портала Foundry).
    AZURE_OPENAI_DEPLOYMENT_NAME Имя модели в развертывании (скопированное с площадки модели в новом портале Foundry).

    Note

    Чтобы упростить руководство, вы будете использовать эти переменные в .env, вместо того чтобы перезаписывать их с настройками приложений в App Service.

    Note

    Чтобы упростить руководство, вы будете использовать эти переменные в .env, вместо того чтобы перезаписывать их с настройками приложений в App Service.

  2. Войдите в Azure с помощью Azure CLI:

    az login
    

    Это позволяет клиентской библиотеке удостоверений Azure в примере кода получать маркер проверки подлинности для пользователя, вошедшего в систему. Помните, что вы добавили необходимую роль для этого пользователя ранее.

  3. Запустите приложение локально:

    npm run build
    npm start
    
  4. Когда вы видите Ваше приложение, запущенное на порту 3000, доступно, выберите Открыть в браузере.

  5. Выберите ссылку агента LangGraph и ссылку агента Foundry , чтобы попробовать интерфейс чата. Если вы получите ответ, приложение успешно подключается к ресурсу Microsoft Foundry.

  6. Вернитесь в пространство кода GitHub, разверните изменения приложения.

    azd up
    
  7. Перейдите к развернутому приложению еще раз и протестируйте агенты чата.

Очистите ресурсы

После завершения работы с приложением можно удалить ресурсы службы приложений, чтобы избежать дополнительных затрат:

azd down --purge

Так как шаблон AZD не включает ресурсы Microsoft Foundry, их необходимо удалить вручную, если вы хотите.

Дополнительные ресурсы