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 密钥。 聊天应用使用 Azure OpenAI 生成对用户消息的响应。

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

  • Microsoft AI 聊天协议提供了跨 AI 解决方案和语言的标准化 API 协定。 聊天应用符合 Microsoft AI 聊天协议,这允许评估应用与任何符合该协议的聊天应用运行。
  • 使用包生成对用户消息的响应的 Python Quartopenai
  • 基本 HTML/JavaScript 前端,它使用 JSON 行通过 ReadableStream 从后端流式传输响应。

成本

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

先决条件

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

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

打开开发环境

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

GitHub Codespaces 运行由 GitHub 托管的开发容器,将 Visual Studio Code 网页版作为用户界面。 对于最简单的开发环境,请使用 GitHub Codespaces,以便预先安装完成本文所需的合适的开发人员工具和依赖项。

重要

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

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

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

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

    新建 codespace 之前的确认屏幕的截图。

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

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

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

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

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

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

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

    新建 codespace 之前的确认屏幕的截图。

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

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

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

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

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

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

在 GitHub Codespaces 中打开

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

    新建 codespace 之前的确认屏幕的截图。

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

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

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

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

部署和运行

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

将聊天应用部署到 Azure

重要

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

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

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

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

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

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

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

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

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

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

浏览示例代码

虽然 OpenAI 和 Azure OpenAI 服务依赖于 常见的 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 标识客户端库提供凭据—实现 Azure Core 库的 TokenCredential 协议的公共类。 凭据表示从 Microsoft Entra ID 获取访问令牌的独特身份验证流。 这些凭证可以链接在一起,形成要尝试的身份验证机制的有序序列。

以下代码片段使用 ChainedTokenCredential a ManagedIdentityCredential 和 a AzureDeveloperCliCredential

  • 用于 ManagedIdentityCredential Azure Functions 和 Azure App 服务。 通过传递给用户分配的托管标识,支持该client_idManagedIdentityCredential标识。
  • 用于 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_credentialChainedTokenCredential:前面创建的用于对请求进行身份验证的实例。

  • https://cognitiveservices.azure.com/.default:需要一个或多个持有者令牌范围。 在这种情况下,Azure 认知服务 终结点。

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

以下行检查在应用部署期间AZURE_OPENAI_ENDPOINT预配的必需AZURE_OPENAI_CHATGPT_DEPLOYMENT和资源azdazd环境变量。 如果值不存在,则会引发错误。

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

此代码片段初始化 Azure OpenAI 客户端、设置api_versionazure_endpointazure_ad_token_providerclient_args) 参数:

bp.openai_client = AsyncAzureOpenAI(
    api_version=os.getenv("AZURE_OPENAI_API_VERSION") or "2024-02-15-preview",
    azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
    azure_ad_token_provider=token_provider,
)  

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

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

注意

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

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

使用 OpenAI 客户端和模型的响应流

处理 response_stream 路由中的聊天完成调用。 以下代码片段演示了如何使用 openai_clientmodel 用法。

async def response_stream():
    # This sends all messages, so API request may exceed token limits
    all_messages = [
        {"role": "system", "content": "You are a helpful assistant."},
    ] + request_messages

    chat_coroutine = bp.openai_client.chat.completions.create(
        # Azure OpenAI takes the deployment name as the model name
        model=bp.openai_model,
        messages=all_messages,
        stream=True,
    )

探索示例代码

.NET 应用程序依赖于 Azure.AI.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环境变量集来创建azdazd 实例。

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

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

以下代码片段注册 AzureOpenAIClient 依赖项注入,并创建使用 ChainedTokenCredential a ManagedIdentityCredential 和 a AzureDeveloperCliCredential

  • 用于 ManagedIdentityCredential Azure Functions 和 Azure App 服务。 使用 AZURE_CLIENT_ID 提供给 ManagedIdentityCredential用户的托管标识来支持用户分配的托管标识。
  • 用于 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 ,以形成要尝试的有序身份验证机制序列。 这样,就可以在生产环境和本地开发环境中部署相同的代码。

此图显示了先尝试托管标识的流中的两个凭据,然后尝试默认的 Azure 凭据。

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

在此示例中,提供了 ./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.tsgetTokenProvider()返回一个回调,该回调提供限定为 Azure 认知服务终结点的持有者令牌。

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 流式传输聊天答案

使用以下 Fastify 路由处理程序 ./src/openai-chat-api.ts 将消息发送到 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()

}

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

其他安全注意事项

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

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

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

清理资源

清理 Azure 资源

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

要删除 Azure 资源并移除源代码,请运行以下 Azure Developer CLI 命令:

azd down --purge

清理 GitHub Codespaces

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

重要

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

  1. 登录到 GitHub Codespaces 仪表板 (https://github.com/codespaces)。

  2. 找到当前正在运行的、源自 Azure-Samples/openai-chat-app-quickstart GitHub 存储库的 Codespaces。

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

  1. 登录到 GitHub Codespaces 仪表板 (https://github.com/codespaces)。

  2. 找到当前正在运行的、源自 Azure-Samples/openai-chat-app-quickstart-dotnet GitHub 存储库的 Codespaces。

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

  1. 登录到 GitHub Codespaces 仪表板 (https://github.com/codespaces)。

  2. 找到当前正在运行的、源自 Azure-Samples/openai-chat-app-quickstart-javascript GitHub 存储库的 Codespaces。

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

获取帮助

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

后续步骤

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

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