你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

教程:使用 LangGraph 或 Foundry 代理服务在 Azure 应用服务中生成代理 Web 应用(Node.js)

本教程演示如何将代理功能添加到现有的数据驱动 Express.js CRUD 应用程序。 它使用两种不同的方法执行此作:LangGraph 和 Foundry 代理服务。

如果 Web 应用程序已具有有用的功能(如购物、酒店预订或数据管理),则通过将这些功能包装在插件(for LangGraph)或 OpenAPI 终结点(对于 Foundry 代理服务)中来向 Web 应用程序添加代理功能相对简单。 本教程从简单的 to-do 列表应用开始。 最后,你将能够使用应用服务应用中的代理创建、更新和管理任务。

LangGraph 和 Foundry Agent Service 能够让您使用 AI 驱动的能力构建具备自主功能的 Web 应用程序。 LangGraph 类似于Microsoft语义内核,并且是 SDK,但语义内核目前不支持 JavaScript。 下表显示了一些注意事项和权衡:

Consideration LangGraph Foundry 代理服务
Performance 快速(在本地运行) 速度较慢(托管的远程服务)
Development 完整代码,最大控制 低代码,快速集成
Testing 代码中的手动/单元测试 用于快速测试的内置沙盒
Scalability 应用托管 Azure 托管,自动缩放

本教程中,您将学习如何:

  • 将现有应用功能转换为 LangGraph 的插件。
  • 将插件添加到 LangGraph 代理并在 Web 应用中使用它。
  • 将现有应用功能转换为 OpenAPI 终结点以用于 Foundry 代理服务。
  • 在 Web 应用中调用 Microsoft Foundry 代理。
  • 分配建立托管标识连接所需的权限。

Prerequisites

使用 Codespaces 打开示例

最简单的入门方法是使用 GitHub Codespaces,它提供一个完整的开发环境,并预装了所有必需的工具。

  1. 请导航至位于https://github.com/Azure-Samples/app-service-agentic-langgraph-foundry-node的 GitHub 存储库。

  2. 选择 “代码 ”按钮,选择 “Codespaces ”选项卡,然后选择 “在 main 上创建代码空间”。

  3. 请稍等片刻,让 Codespace 初始化。 准备就绪后,浏览器中会显示完全配置的开发环境。

  4. 在本地运行应用程序:

    npm install
    npm run build
    npm start
    
  5. 当看到端口 3000 上运行的应用程序可用时,请选择“ 在浏览器中打开 ”并添加一些任务。

    智能体未完全配置,因此尚不起作用。 稍后需对其进行配置。

查看代理代码

这两种方法使用相同的实现模式,即在应用程序启动时初始化代理,并通过 POST 请求响应用户消息。

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

部署示例应用程序

示例存储库包含一个 Azure 开发人员 CLI (AZD) 模板,该模板使用托管标识创建应用服务应用并部署示例应用程序。

  1. 在终端中,使用 Azure 开发人员 CLI 登录到 Azure:

    azd auth login
    

    按照说明完成身份验证过程。

  2. 使用 AZD 模板部署 Azure 应用服务应用:

    azd up
    
  3. 出现提示时,请提供以下答案:

    Question Answer
    输入新的环境名称: 键入唯一名称。
    选择要使用的 Azure 订阅: 选择订阅。
    选择要使用的资源组: 选择“新建资源组”
    选择要在其中创建资源组的位置: 选择任何区域。 实际上,资源将在 美国东部 2 中生成。
    输入新资源组的名称: 键入 Enter。
  4. 在 AZD 输出中,找到应用的 URL 并在浏览器中导航到该 URL。 该 URL 在 AZD 输出中如下所示:

     Deploying services (azd deploy)
    
       (✓) Done: Deploying service web
       - Endpoint: <URL>
     
  5. 选择 OpenAPI 架构 项以在默认 /api/schema 路径中打开自动生成的 OpenAPI 架构。 稍后需要此架构。

  6. 成功部署后,会看到已部署应用程序的 URL。

    现在已有了一个具有系统分配的托管标识的应用服务应用。

创建和配置 Microsoft Foundry 资源

  1. Foundry 门户中,部署所选的模型(请参阅 快速入门:Microsoft Foundry 入门)。 在此过程中会为你创建一个项目和一个默认代理。

  2. 从左侧菜单中选择“概述”。

  3. 选择 Microsoft Foundry ,并在 Microsoft Foundry 项目终结点中复制 URL。

  4. 选择 Azure OpenAI ,并在 Azure OpenAI 终结点 中复制 URL 供以后使用。

    显示如何在 Foundry 门户中复制 OpenAI 终结点和 Foundry 项目终结点的屏幕截图。

  5. 从左侧菜单中选择 “代理”,然后选择默认代理。

  6. “安装 ”窗格中,复制 代理 ID 以及 部署中的模型名称。

    显示如何在 Foundry 门户中复制代理 ID 和模型部署名称的屏幕截图。

  7. “设置” 窗格中,使用 OpenAPI 规格工具添加操作。 使用从部署的 Web 应用和 匿名 身份验证获取的 OpenAPI 架构。 有关详细步骤,请参阅 如何使用 OpenAPI 规范工具

    应用程序代码已配置为包含服务器的 urloperationId,这两项为代理所需。 有关详细信息,请参阅 如何将 Foundry 代理服务与 OpenAPI 指定工具配合使用:先决条件

  8. 选择“在 Playground 中试用”,并使用提示语如“显示所有任务”来测试 Foundry 代理。

    如果收到有效的响应,代理正在对已部署的 Web 应用上的 OpenAPI 终结点进行工具调用。

分配所需的权限

  1. 在 Foundry 门户的右上角,选择资源的名称,然后选择 资源组 以在 Azure 门户中打开它。

    显示如何在 Azure 门户中快速转到 Foundry 资源的资源组视图的屏幕截图。

  2. 使用下表为应用服务应用的管理标识的两个资源中的每一个添加一个角色:

    目标资源 所需角色 用途:
    Microsoft Foundry 认知服务 OpenAI 用户 LangGraph 中的聊天完成服务。
    Microsoft Foundry 项目 Azure AI 用户 读取并调用 Foundry 代理。

    有关说明,请参阅使用 Azure 门户分配 Azure 角色

在示例应用程序中配置连接变量

  1. 打开 .env。 使用前面从 Foundry 门户复制的值,配置以下变量:

    Variable Description
    AZURE_OPENAI_ENDPOINT Azure OpenAI 终结点(从“概述”页面复制)。 LangGraph 代理需要此物。
    AZURE_OPENAI_DEPLOYMENT_NAME 部署中的模型名称(从“代理配置窗口”复制)。 LangGraph 代理需要此物。
    AZURE_AI_FOUNDRY_PROJECT_ENDPOINT Microsoft Foundry 项目终结点(从“概述”页复制)。 这是 Foundry 智能体服务所需的。
    AZURE_AI_FOUNDRY_AGENT_ID 代理 ID(从“代理设置”窗格复制)。 为了调用现有的 Microsoft Foundry 代理,需要这样做。

    Note

    为了简化本教程,你将在 .env 中使用这些变量,而不是在应用服务中使用应用设置覆盖它们。

  2. 使用 Azure CLI 登录到 Azure:

    az login
    

    这允许示例代码中的 Azure 标识客户端库接收已登录用户的身份验证令牌。 请记住,之前已为此用户添加了所需的角色。

  3. 在本地运行应用程序:

    npm run build
    npm start
    
  4. 当看到端口 3000 上运行的应用程序可用时,请选择 “在浏览器中打开”。

  5. 选择 LangGraph 代理 链接和 Foundry 代理 链接以试用聊天界面。 如果收到响应,应用程序将成功连接到 Microsoft Foundry 资源。

  6. 返回 GitHub codespace,部署应用更改。

    azd up
    
  7. 再次导航到已部署的应用程序并测试聊天代理。

清理资源

完成应用程序后,可以删除应用服务资源,以避免产生进一步的成本:

azd down --purge

由于 AZD 模板不包含 Microsoft Foundry 资源,需要根据需要手动删除它们。

更多资源