通过


开始使用 Azure OpenAI 安全模块

本文介绍如何创建和使用 Azure OpenAI 安全构建基块示例。 目的是演示如何使用基于角色的访问控制(RBAC)来预配 Azure OpenAI 帐户,实现无密钥(Microsoft Entra ID)身份验证到 Azure OpenAI。 此聊天应用示例还包括预配 Azure OpenAI 资源以及使用 Azure 开发人员 CLI 将应用部署到Azure 容器应用所需的所有基础结构和配置。

通过遵循本文的说明,你将:

  • 在Azure 容器应用上部署安全聊天应用。
  • 使用托管标识进行 Azure OpenAI 访问。
  • 使用 OpenAI 库与 Azure OpenAI 大型语言模型(LLM)聊天。

完成此文章后,可以使用自定义代码和数据开始修改新项目。

注意

本文使用一个或多个 AI 应用模板作为本文中的示例和指南的基础。 AI 应用模板为你提供了维护良好、易于部署的参考实现,可帮助确保 AI 应用有一个高质量的起点。

体系结构概述

下图显示了聊天应用的简单体系结构: 显示从客户端到后端应用的体系结构的示意图。

聊天应用作为Azure容器应用运行。 应用使用 Microsoft Entra ID 的托管标识与 Azure OpenAI 进行身份验证,而不是使用 API key。 聊天应用使用 Azure OpenAI 生成对用户消息的响应。

应用程序体系结构依赖于以下服务和组件:

  • Azure OpenAI表示我们向其发送用户的查询的 AI 提供程序。
  • Azure 容器应用是托管应用程序的容器环境。
  • 托管标识 帮助我们确保卓越的安全性,并使您作为开发人员不再需要安全管理机密。
  • Bicep文件用于预配Azure资源,包括 Azure OpenAI、Azure 容器应用、Azure 容器注册表、Azure Log Analytics 和 RBAC 角色。
  • 使用 包和 openai 生成对用户消息的响应的 Python Quart 应用。
  • 基本的 HTML/JavaScript 前端,通过 ReadableStream 使用 JSON Lines 从后端流式传输响应。
  • 使用 Azure.AI.OpenAI NuGet 包的 Blazor Web 应用程序来生成对用户消息的响应。
  • 使用 OpenAI npm 包生成对用户消息的响应的 TypeScript Web 应用。

成本

为了尽量降低此示例中的定价,大多数资源都使用基本定价层或消耗定价层。 根据需要根据预期使用情况更改层级别。 若要停止产生费用,在完成本文后删除资源。

详细了解示例存储库中的 成本

详细了解示例存储库中的费用

详细了解示例仓库中的 成本

先决条件

开发容器 环境提供了完成本文所需的所有依赖项。 可以在 GitHub Codespaces(在浏览器中)或使用Visual Studio Code在本地运行开发容器。

若要使用本文,需要满足以下先决条件:

开放的开发环境

按照以下说明部署预配置开发环境,其中包含完成本文所需的所有依赖项。

GitHub Codespaces 运行一个由 GitHub 托管的开发容器,使用 Visual Studio Code for the Web 作为用户界面。 对于最直接的开发环境,请使用 GitHub Codespaces,以便预先安装正确的开发人员工具和依赖项来完成本文。

重要

所有GitHub帐户每月最多可以使用 Codespaces 60 小时,其中包含 2 个核心实例。 有关详细信息,请参阅 GitHub Codespaces 每月提供的存储和核心小时数

使用以下步骤在 main GitHub 存储库的 Azure-Samples/openai-chat-app-quickstart 分支上创建新的 GitHub Codespace。

  1. 右键单击以下按钮,然后选择在新窗口中打开链接。 此操作允许你拥有可供查看的开发环境和文档。

  2. “创建代码空间”页上,查看并选择“创建新代码空间”

    新建代码空间之前确认屏幕的截图。

  3. 等待 Codespace 启动。 此启动过程会花费几分钟时间。

  4. 在屏幕底部的终端中使用Azure开发人员 CLI 登录到Azure。

    azd auth login
    
  5. 从终端复制代码,然后将其粘贴到浏览器中。 按照说明使用Azure帐户进行身份验证。

本文中的剩余任务需要在此开发容器的上下文中完成。

使用以下步骤在 main GitHub 存储库的 Azure-Samples/openai-chat-app-quickstart-dotnet 分支上创建新的 GitHub Codespace。

  1. 右键单击以下按钮,然后选择在新窗口中打开链接。 此操作允许你拥有可供查看的开发环境和文档。

  2. “创建代码空间”页上,查看并选择“创建代码空间”

    新建代码空间之前确认屏幕的截图。

  3. 等待 Codespace 启动。 此启动过程会花费几分钟时间。

  4. 在屏幕底部的终端中使用Azure开发人员 CLI 登录到Azure。

    azd auth login
    
  5. 从终端复制代码,然后将其粘贴到浏览器中。 按照说明使用Azure帐户进行身份验证。

本文中的剩余任务需要在此开发容器的上下文中完成。

使用以下步骤在 main GitHub 存储库的 Azure-Samples/openai-chat-app-quickstart-javascript 分支上创建新的 GitHub Codespace。

  1. 右键单击以下按钮,然后选择在新窗口中打开链接。 此操作允许你拥有可供查看的开发环境和文档。

Open in GitHub Codespaces

  1. “创建代码空间”页上,查看并选择“创建新代码空间”

    新建代码空间之前确认屏幕的截图。

  2. 等待 Codespace 启动。 此启动过程会花费几分钟时间。

  3. 在屏幕底部的终端中使用Azure开发人员 CLI 登录到Azure。

    azd auth login
    
  4. 从终端复制代码,然后将其粘贴到浏览器中。 按照说明使用Azure帐户进行身份验证。

本文中的剩余任务需要在此开发容器的上下文中完成。

部署和运行

示例存储库包含聊天应用Azure部署的所有代码和配置文件。 以下步骤将引导完成示例聊天应用Azure部署过程。

将聊天应用部署到Azure

重要

Microsoft Azure在本部分中创建的资源会立即产生成本。 即使在命令完全执行之前中断命令,这些资源也会产生成本。

  1. 运行以下用于Azure资源预配和源代码部署的Azure开发人员 CLI 命令:

    azd up
    
  2. 使用下表回答提示:

    提示 答案
    环境名称 保持简短和小写。 添加名称或别名。 例如,secure-chat。 它用作资源组名称的一部分。
    订阅 选择要用于创建资源的订阅。
    位置(用于托管) 从列表中选择附近的位置。
    OpenAI 模型的位置 从列表中选择附近的位置。 如果可以使用与第一个位置相同的位置,请选择该位置。
  3. 等待应用部署完成。 部署通常需要 5 到 10 分钟才能完成。

使用聊天应用向大型语言模型提问

  1. 终端在成功部署应用程序后显示 URL。

  2. 选择标记为 Deploying service web 的 URL 在浏览器中打开聊天应用程序。

    浏览器中聊天应用的屏幕截图,其中显示了有关聊天输入的多个建议以及用于输入问题的聊天文本框。

  3. 在浏览器中,输入一个问题,例如“为什么托管标识比密钥更好?”。

  4. 答案来自 Azure OpenAI,并显示结果。

浏览示例代码

虽然 OpenAI 和 Azure OpenAI 服务 依赖于 common Python 客户端库,但使用 Azure OpenAI 终结点时需要小代码更改。 让我们看看此示例如何使用 Microsoft Entra ID 配置无密钥身份验证,并与 Azure OpenAI 通信。

使用托管标识配置身份验证

在此示例中, src/quartapp/chat.py 该文件首先配置无密钥身份验证。

以下代码片段使用 azure.identity.aio 模块创建异步Microsoft Entra身份验证流。

以下代码片段使用AZURE_CLIENT_IDazd环境变量创建能够通过用户分配的托管标识进行身份验证的 ManagedIdentityCredential 实例。

user_assigned_managed_identity_credential = ManagedIdentityCredential(client_id=os.getenv("AZURE_CLIENT_ID")) 

注意

部署应用时,会预配 azd 资源环境变量 azd

以下代码片段使用 AZURE_TENANT_IDazd 资源环境变量创建能够与当前Microsoft Entra租户进行身份验证的 AzureDeveloperCliCredential 实例。

azure_dev_cli_credential = AzureDeveloperCliCredential(tenant_id=os.getenv("AZURE_TENANT_ID"), process_timeout=60)  

Azure标识客户端库提供 credentials — 实现 Azure Core 库的 TokenCredential 协议的公共类。 凭据表示从Microsoft Entra ID获取访问令牌的不同身份验证流。 这些凭证可以链接在一起,形成要尝试的身份验证机制的有序序列。

以下代码片段使用 ChainedTokenCredentialManagedIdentityCredentialAzureDeveloperCliCredential 创建:

  • ManagedIdentityCredential用于Azure Functions、Azure 应用服务和Azure 容器应用。 通过将client_id传递给ManagedIdentityCredential,以支持用户分配的托管标识。
  • AzureDeveloperCliCredential 用于本地开发。 它之前是根据要使用的 Microsoft Entra 租户设置的。
azure_credential = ChainedTokenCredential(
    user_assigned_managed_identity_credential,
    azure_dev_cli_credential
)

提示

凭据的排列顺序非常重要,因为会使用第一个有效的 Microsoft Entra 访问令牌。 有关详细信息,请查看 ChainedTokenCredential 概述 文章。

以下代码片段根据所选 Azure 凭据获取 Azure OpenAI 令牌提供者。 通过使用两个参数调用 azure.identity.aio.get_bearer_token_provider 来获取此值:

  • azure_credential ChainedTokenCredential:前面创建的用于对请求进行身份验证的实例。

  • https://cognitiveservices.azure.com/.default:需要一个或多个持有者令牌范围。 在本例中,Azure Cognitive Services 端点。

token_provider = get_bearer_token_provider(
    azure_credential, "https://cognitiveservices.azure.com/.default"
)

以下行检查所需的AZURE_OPENAI_ENDPOINTAZURE_OPENAI_CHAT_DEPLOYMENT两个环境变量,这些是在azd预配期间设置的azd类型的环境变量。 如果值不存在,则会引发错误。

openai_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
if not openai_endpoint:
    raise ValueError("AZURE_OPENAI_ENDPOINT is required for Azure OpenAI")
if not os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT"):
    raise ValueError("AZURE_OPENAI_CHAT_DEPLOYMENT is required for Azure OpenAI")

此代码片段针对 Azure 的 /openai/v1/ 终结点初始化 OpenAI 客户端,并传递令牌提供程序作为 api_key。 v1 终结点不需要使用api_version

bp.openai_client = AsyncOpenAI(
    base_url=f"{openai_endpoint.rstrip('/')}/openai/v1/",
    api_key=token_provider,
)

以下行设置用于 API 调用的 Azure OpenAI 模型部署名称:

bp.openai_model = os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT")

注意

OpenAI 使用 model 关键字参数指定要使用的模型。 Azure OpenAI 具有 独特的模型部署的概念。 使用 Azure OpenAI 时,model应引用在 Azure OpenAI 模型部署期间选择的部署名称

此函数完成后,客户端已正确配置,并准备好与 Azure OpenAI 服务进行交互。

使用 OpenAI 响应 API 流式处理响应

处理 response_stream 路由中的响应 API 流式处理调用。 前端直接发送 input 格式的响应项目,后端将其转发到 responses.stream()

async def response_stream():
    try:
        async with bp.openai_client.responses.stream(
            model=bp.openai_model,
            input=request_input,
            store=False,
        ) as openai_stream:
            async for event in openai_stream:
                yield json.dumps(event.model_dump(), ensure_ascii=False) + "\n"
    except Exception as e:
        current_app.logger.exception("Responses stream failed")
        yield json.dumps({"error": str(e)}, ensure_ascii=False) + "\n"

探索示例代码

.NET应用程序依赖于 Azure。艾。OpenAI 客户端库与 Azure OpenAI 服务通信,它依赖于 OpenAI 库。 示例应用使用 Microsoft Entra ID 配置无密钥身份验证,以便与 Azure OpenAI 通信。

配置身份验证和服务注册

在此示例中,无密钥身份验证配置在 program.cs 文件中。 以下代码片段使用由AZURE_CLIENT_ID设置的azd环境变量来创建一个能够通过用户分配的托管标识进行身份验证的ManagedIdentityCredential实例。

var userAssignedIdentityCredential = 
    new ManagedIdentityCredential(builder.Configuration.GetValue<string>("AZURE_CLIENT_ID"));

注意

部署应用时,会预配 azd 资源环境变量 azd

以下代码片段使用AZURE_TENANT_ID环境变量集由azd创建一个AzureDeveloperCliCredential实例,该实例能够通过已登录azd的账户进行本地身份验证。

var azureDevCliCredential = new AzureDeveloperCliCredential(
    new AzureDeveloperCliCredentialOptions()
    { 
        TenantId = builder.Configuration.GetValue<string>("AZURE_TENANT_ID") 
    });

Azure标识客户端库提供实现 Azure Core 库的 TokenCredential 协议的凭据类。 凭据表示用于从 Microsoft Entra ID 获取访问令牌的不同身份验证流程。 这些凭据可以使用 ChainedTokenCredential 链接在一起,以形成要尝试的身份验证机制的有序序列。

以下代码片段将 AzureOpenAIClient 注册到依赖注入中,并使用 ChainedTokenCredentialManagedIdentityCredential 创建 AzureDeveloperCliCredential

  • ManagedIdentityCredential用于Azure Functions、Azure 应用服务和Azure 容器应用。 使用提供给AZURE_CLIENT_IDManagedIdentityCredential来支持用户分配的托管标识。
  • AzureDeveloperCliCredential 用于本地开发。 它以前基于要使用的Microsoft Entra租户进行设置。
builder.Services.AddAzureClients(
    clientBuilder => {
        clientBuilder.AddClient<AzureOpenAIClient, AzureOpenAIClientOptions>((options, _, _)
            => new AzureOpenAIClient(
                new Uri(endpoint),
                new ChainedTokenCredential(
                    userAssignedIdentityCredential, azureDevCliCredential), options));
    });

提示

凭据的顺序非常重要,因为会使用第一个有效的 Microsoft Entra 访问令牌。 有关详细信息,请查看 ChainedTokenCredential 概述 文章。

使用 Azure OpenAI 客户端获取聊天完成

Blazor Web 应用在AzureOpenAIClient组件顶部插入注册的Home.Razor

@inject AzureOpenAIClient azureOpenAIClient

当用户提交表单时,会 AzureOpenAIClient 将其提示发送到 OpenAI 模型以生成完成:

ChatClient chatClient = azureOpenAIClient.GetChatClient("gpt-4o-mini");

messages.Add(new UserChatMessage(model.UserMessage));

ChatCompletion completion = await chatClient.CompleteChatAsync(messages);
    messages.Add(new SystemChatMessage(completion.Content[0].Text));

探索示例代码

虽然 OpenAI 和 Azure OpenAI 服务 依赖于 openai (常见的 JavaScript 客户端库),但使用 Azure OpenAI 终结点时需要小代码更改。 让我们看看此示例如何使用 Microsoft Entra ID 配置无密钥身份验证,并与 Azure OpenAI 通信。

每个环境的无密钥身份验证

Azure标识客户端库提供实现 Azure Core 库的 TokenCredential 协议的凭据类。 凭据表示获取访问令牌的不同身份验证流程,该访问令牌来自 Microsoft Entra ID。 可以使用 ChainedTokenCredential 将这些凭据链接在一起,以形成要尝试的有序身份验证机制序列。 这样,就可以在生产环境和本地开发环境中部署相同的代码。

使用托管标识配置身份验证

在此示例中,./src/azure-authentication.ts提供了多个函数,用于提供Azure OpenAI 的无密钥身份验证。

第一个函数getChainedCredential()返回链中找到的第一个有效Azure凭据。

function getChainedCredential() {

    return new ChainedTokenCredential(
        new ManagedIdentityCredential(process.env.AZURE_CLIENT_ID!), 
        new AzureDeveloperCliCredential({
            tenantId: process.env.AZURE_TENANT_ID! ? process.env.AZURE_TENANT_ID! : undefined
          })
    );
}

提示

凭据的顺序非常重要,因为会使用第一个有效的Microsoft Entra访问令牌。 有关详细信息,请查看 ChainedTokenCredential 概述 文章。

获取 OpenAI 的持有者令牌

./src/azure-authentication.ts中的第二个函数是 getTokenProvider(),它返回一个提供作用域为 Azure 认知服务终结点的 Bearer 令牌的回调。

function getTokenProvider(): () => Promise<string> {
    const credential  = getChainedCredential();
    const scope = "https://cognitiveservices.azure.com/.default";
    return getBearerTokenProvider(credential, scope);
}

前面的代码片段利用getBearerTokenProvider获取凭据和作用域,然后返回提供持票者令牌的回调函数。

创建经过身份验证Azure OpenAI 客户端

./src/azure-authentication.ts 中的第三个函数是 getOpenAiClient(),该函数返回 Azure OpenAI 客户端。

export function getOpenAiClient(): AzureOpenAI | undefined{
    try {

        if (!process.env.AZURE_OPENAI_ENDPOINT) {
            throw new Error("AZURE_OPENAI_ENDPOINT is required for Azure OpenAI");
        }
        if (!process.env.AZURE_OPENAI_CHAT_DEPLOYMENT) {
            throw new Error("AZURE_OPENAI_CHAT_DEPLOYMENT is required for Azure OpenAI");
        }

        const options = { 
            azureADTokenProvider: getTokenProvider(), 
            deployment: process.env.AZURE_OPENAI_CHAT_DEPLOYMENT!, 
            apiVersion: process.env.AZURE_OPENAI_API_VERSION! || "2024-02-15-preview",
            endpoint: process.env.AZURE_OPENAI_ENDPOINT!
        }

        // Create the Asynchronous Azure OpenAI client
        return new AzureOpenAI (options);

    } catch (error) {
        console.error('Error getting Azure OpenAI client: ', error);
    }
}

此代码使用选项(包括正确作用域的令牌)来创建 AzureOpenAI 客户端

使用 Azure OpenAI 流式传输聊天答案

./src/openai-chat-api.ts 中使用以下 Fastify 路由处理程序将消息发送到 Azure OpenAI 并流式传输响应。

import { FastifyReply, FastifyRequest } from 'fastify';
import { AzureOpenAI } from "openai";
import { getOpenAiClient } from './azure-authentication.js';
import { ChatCompletionChunk, ChatCompletionMessageParam } from 'openai/resources/chat/completions';

interface ChatRequestBody {
    messages: ChatCompletionMessageParam [];
  }

export async function chatRoute (request: FastifyRequest<{ Body: ChatRequestBody }>, reply: FastifyReply) {

    const requestMessages: ChatCompletionMessageParam[] = request?.body?.messages;
    const openaiClient: AzureOpenAI | undefined = getOpenAiClient();

    if (!openaiClient) {
      throw new Error("Azure OpenAI client is not configured");
    }

    const allMessages = [
      { role: "system", content: "You are a helpful assistant."},
      ...requestMessages
    ] as ChatCompletionMessageParam [];

    const chatCompletionChunks = await openaiClient.chat.completions.create({
      // Azure Open AI takes the deployment name as the model name
      model: process.env.AZURE_OPENAI_CHAT_DEPLOYMENT_MODEL || "gpt-4o-mini",
      messages: allMessages,
      stream: true

    })
    reply.raw.setHeader('Content-Type', 'text/html; charset=utf-8');
    reply.raw.setHeader('Cache-Control', 'no-cache');
    reply.raw.setHeader('Connection', 'keep-alive');
    reply.raw.flushHeaders();

    for await (const chunk of chatCompletionChunks as AsyncIterable<ChatCompletionChunk>) {
      for (const choice of chunk.choices) {
        reply.raw.write(JSON.stringify(choice) + "\n")
      }
    }

    reply.raw.end()

}

该函数获取聊天对话,包括任何以前的消息,并将其发送到 openAI Azure。 由于流区块从 Azure OpenAI 返回,因此会发送到客户端。

其他安全注意事项

本文演示如何使用 ChainedTokenCredential 对 Azure OpenAI 服务进行身份验证。

该示例还有一个 GitHub Action,用于扫描基础结构即代码文件并生成包含任何检测到问题的报表。 为确保自己的存储库中的持续最佳做法,我们建议基于模板创建解决方案的任何人都可以确保启用了 GitHub 机密扫描设置

请考虑其他安全措施,例如:

清理资源

清理 Azure 资源

本文中创建的 Azure 资源将向你的 Azure 订阅计费。 如果你预计将来不需要这些资源,请将其删除,以避免产生更多费用。

若要删除Azure资源并删除源代码,请运行以下Azure开发人员 CLI 命令:

azd down --purge

清理 GitHub Codespaces

删除 GitHub Codespaces 环境可确保可以最大程度地提高帐户获得的每个核心小时免费权利量。

重要

有关 GitHub 帐户权限的更多信息,请参阅 GitHub Codespaces 每月包含的存储和核心小时数

  1. 登录到 GitHub Codespaces 仪表板

  2. 找到由 Azure-Samples/openai-chat-app-quickstart GitHub 存储库提供的正在运行的 Codespaces 实例。

  3. 打开 codespace 的上下文菜单,然后选择“删除”。

  1. 登录到 GitHub Codespaces 仪表板

  2. 找到由 Azure-Samples/openai-chat-app-quickstart-dotnet GitHub 存储库提供的正在运行的 Codespaces 实例。

  3. 打开 codespace 的上下文菜单,然后选择“删除”。

  1. 登录到 GitHub Codespaces 仪表板

  2. 找到由 Azure-Samples/openai-chat-app-quickstart-javascript GitHub 存储库提供的正在运行的 Codespaces 实例。

  3. 打开 codespace 的上下文菜单,然后选择“删除”。

获取帮助

如果未解决问题,请将问题记录到存储库的 Issues

后续步骤

如果未解决问题,请将问题记录到存储库的 Issues

如果未解决问题,请将问题记录到存储库的 Issues