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

教程:第 3 部分 - 使用提示流 SDK 评估和部署自定义聊天应用程序

在本教程中,你将使用提示流 SDK(和其他库)来评估和部署在本教程系列第 2 部分中构建的聊天应用。 在第 3 部分中,你将学习如何:

  • 评估聊天应用回复的质量
  • 将聊天应用部署到 Azure
  • 验证部署

本教程是由三个部分构成的教程的第三部分。

先决条件

  • 完成本教程系列的第 2 部分才能构建聊天应用程序。

  • 你必须具有在 Azure 订阅中添加角色分配所需的权限。 仅允许由特定 Azure 资源的所有者授予权限(通过角色分配)。 在本教程的后面,你可能需要向你的 Azure 订阅所有者(可能是你的 IT 管理员)寻求有关终结点访问的帮助。

评估聊天应用回复的质量

现在,你已经知道聊天应用对查询做出了良好的响应,包括聊天记录,现在是时候根据几个不同的指标和更多数据来评估它的表现了。

将提示流评估器与评估数据集和 get_chat_response() 目标函数配合使用,然后评估评估结果。

运行评估后,即可对逻辑进行改进,例如完善系统提示,并观察聊天应用响应的更改和改进方式。

设置评估模型

选择要使用的评估模型。 这可以与用于构建应用的聊天模型相同。 如果需要评估其他模型,则需要部署对应模型,或予以指定(前提是该模型已存在)。 例如,你可能正在使用 gpt-35-turbo 来补全聊天,但想要使用 gpt-4 进行评估,因为它的性能可能更好。

.env 文件中添加评估模型名称:

AZURE_OPENAI_EVALUATION_DEPLOYMENT=<your evaluation model deployment name>

创建评估数据集

使用以下评估数据集,其中包含众多示例问题和预期答案。

  1. rag-tutorial 文件夹中创建名为 eval_dataset.jsonl 的文件。 请参阅应用程序代码结构,获取参考资料。

  2. 将此数据集粘贴到文件中:

    {"chat_input": "Which tent is the most waterproof?", "truth": "The Alpine Explorer Tent has the highest rainfly waterproof rating at 3000m"}
    {"chat_input": "Which camping table holds the most weight?", "truth": "The Adventure Dining Table has a higher weight capacity than all of the other camping tables mentioned"}
    {"chat_input": "How much do the TrailWalker Hiking Shoes cost? ", "truth": "The Trailewalker Hiking Shoes are priced at $110"}
    {"chat_input": "What is the proper care for trailwalker hiking shoes? ", "truth": "After each use, remove any dirt or debris by brushing or wiping the shoes with a damp cloth."}
    {"chat_input": "What brand is for TrailMaster tent? ", "truth": "OutdoorLiving"}
    {"chat_input": "How do I carry the TrailMaster tent around? ", "truth": " Carry bag included for convenient storage and transportation"}
    {"chat_input": "What is the floor area for Floor Area? ", "truth": "80 square feet"}
    {"chat_input": "What is the material for TrailBlaze Hiking Pants?", "truth": "Made of high-quality nylon fabric"}
    {"chat_input": "What color does TrailBlaze Hiking Pants come in?", "truth": "Khaki"}
    {"chat_input": "Can the warrenty for TrailBlaze pants be transfered? ", "truth": "The warranty is non-transferable and applies only to the original purchaser of the TrailBlaze Hiking Pants. It is valid only when the product is purchased from an authorized retailer."}
    {"chat_input": "How long are the TrailBlaze pants under warrenty for? ", "truth": " The TrailBlaze Hiking Pants are backed by a 1-year limited warranty from the date of purchase."}
    {"chat_input": "What is the material for PowerBurner Camping Stove? ", "truth": "Stainless Steel"}
    {"chat_input": "Is France in Europe?", "truth": "Sorry, I can only queries related to outdoor/camping gear and equipment"}
    

使用提示流评估器进行评估

现在定义一个评估脚本,该脚本将:

  • 从提示流 evals 包中导入 evaluate 函数和评估器。
  • 上传示例 .jsonl 数据集。
  • 围绕聊天应用逻辑生成一个目标函数包装器。
  • 运行评估,该评估采用目标函数,并将评估数据集与聊天应用的响应合并。
  • 生成一组 GPT 辅助指标(相关性、真实性和连贯性)来评估聊天应用响应的质量。
  • 在本地输出结果,并将结果记录到云项目中。

通过脚本,可以在本地查看结果,方法是将结果输出到命令行和 json 文件。

该脚本还会将评估结果记录到云项目中,以便你在用户界面中比较评估运行。

  1. rag-tutorial 文件夹中创建名为 evaluate.py 的文件。

  2. 添加以下代码。 更新 dataset_pathevaluation_name 以适应你的用例。

    import json
    import os
    
    # set environment variables before importing any other code
    from dotenv import load_dotenv
    
    load_dotenv()
    
    import pandas as pd
    
    from promptflow.core import AzureOpenAIModelConfiguration
    from promptflow.evals.evaluate import evaluate
    from promptflow.evals.evaluators import (
        RelevanceEvaluator,
        GroundednessEvaluator,
        CoherenceEvaluator,
    )
    
    # Helper methods
    def load_jsonl(path):
        with open(path, "r") as f:
            return [json.loads(line) for line in f.readlines()]
    
    
    def copilot_wrapper(*, chat_input, **kwargs):
        from copilot_flow.copilot import get_chat_response
    
        result = get_chat_response(chat_input)
    
        parsedResult = {"answer": str(result["reply"]), "context": str(result["context"])}
        return parsedResult
    
    
    def run_evaluation(eval_name, dataset_path):
    
        model_config = AzureOpenAIModelConfiguration(
            azure_deployment=os.getenv("AZURE_OPENAI_EVALUATION_DEPLOYMENT"),
            api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
            azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
        )
    
        # Initializing Evaluators
        relevance_eval = RelevanceEvaluator(model_config)
        groundedness_eval = GroundednessEvaluator(model_config)
        coherence_eval = CoherenceEvaluator(model_config)
    
        output_path = "./eval_results.jsonl"
    
        result = evaluate(
            target=copilot_wrapper,
            evaluation_name=eval_name,
            data=dataset_path,
            evaluators={
                "relevance": relevance_eval,
                "groundedness": groundedness_eval,
                "coherence": coherence_eval,
            },
            evaluator_config={
                "relevance": {"question": "${data.chat_input}"},
                "coherence": {"question": "${data.chat_input}"},
            },
            # to log evaluation to the cloud AI Studio project
            azure_ai_project={
                "subscription_id": os.getenv("AZURE_SUBSCRIPTION_ID"),
                "resource_group_name": os.getenv("AZURE_RESOURCE_GROUP"),
                "project_name": os.getenv("AZUREAI_PROJECT_NAME"),
            },
        )
    
        tabular_result = pd.DataFrame(result.get("rows"))
        tabular_result.to_json(output_path, orient="records", lines=True)
    
        return result, tabular_result
    
    
    if __name__ == "__main__":
        eval_name = "tutorial-eval"
        dataset_path = "./eval_dataset.jsonl"
    
        result, tabular_result = run_evaluation(
            eval_name=eval_name, dataset_path=dataset_path
        )
    
        from pprint import pprint
    
        pprint("-----Summarized Metrics-----")
        pprint(result["metrics"])
        pprint("-----Tabular Result-----")
        pprint(tabular_result)
        pprint(f"View evaluation results in AI Studio: {result['studio_url']}")
    

最后的主要函数允许你在本地查看评估结果,并提供指向 AI Studio 中的评估结果的链接。

运行评估脚本

  1. 在控制台中,使用 Azure CLI 登录到 Azure 帐户:

    az login
    
  2. 安装所需的包:

    pip install promptflow-evals
    pip install promptflow-azure
    
  3. 现在运行评估脚本:

    python evaluate.py
    

有关使用提示流 SDK 进行评估的更多信息,请参阅使用提示流 SDK 进行评估

解释评估输出

在控制台输出中,你可以看到每个问题的答案以及以表格形式呈现的汇总指标。 (输出会存在不同的列。)

'-----Summarized Metrics-----'
{'coherence.gpt_coherence': 4.3076923076923075,
 'groundedness.gpt_groundedness': 4.384615384615385,
 'relevance.gpt_relevance': 4.384615384615385}

'-----Tabular Result-----'
                                             question  ... gpt_coherence
0                  Which tent is the most waterproof?  ...             5
1          Which camping table holds the most weight?  ...             5
2       How much does TrailWalker Hiking Shoes cost?   ...             5
3   What is the proper care for trailwalker hiking...  ...             5
4                What brand is the TrailMaster tent?   ...             1
5        How do I carry the TrailMaster tent around?   ...             5
6             What is the floor area for Floor Area?   ...             3
7    What is the material for TrailBlaze Hiking Pants  ...             5
8     What color do the TrailBlaze Hiking Pants come   ...             5
9   Can the warranty for TrailBlaze pants be trans...  ...             3
10  How long are the TrailBlaze pants under warren...  ...             5
11  What is the material for PowerBurner Camping S...  ...             5
12                               Is France in Europe?  ...             1

该脚本会将完整的评估结果写入到 ./eval_results.jsonl。 控制台中有一个链接,用于查看 Azure AI Studio 项目中的评估结果。

注意

你可能会看到一个 ERROR:asyncio:Unclosed client session - 可以安全地忽略此项,并且不会影响评估结果。

在 AI Studio 中查看评估结果

评估运行完成后,请点击链接在 Azure AI Studio 中的评估页面上查看评估结果。

显示 Azure AI Studio 中的评估概述的屏幕截图。

还可以查看各个行并查看每行的指标分数,并查看检索到的完整上下文/文档。 这些指标有助于解释和调试评估结果。

显示 Azure AI Studio 中的评估结果行的屏幕截图。

有关 AI Studio 中的评估结果的详细信息,请参阅 如何在 AI Studio 中查看评估结果

验证聊天应用的行为符合预期后,即可部署应用程序。

将聊天应用部署到 Azure

现在,让我们继续将此聊天应用部署到托管的终结点,以便外部应用程序或网站使用。

部署脚本能够实现以下操作:

  • 创建托管联机终结点
  • 将流定义为模型
  • 将流程部署到该终结点上的托管环境,并确保该终结点具备环境变量
  • 将所有流量路由到该部署
  • 输出用于在 Azure AI Studio 中查看和测试部署的链接

部署定义了一个生成上下文 (Dockerfile),该上下文依赖于流文件夹中指定的 requirement.txt,并且还将我们的环境变量设置为部署的环境,因此我们可以确信我们的聊天应用在生产环境中的运行与在本地的运行相同。

部署的生成上下文 (Dockerfile)

部署的环境需要生成上下文,因此让我们为部署的环境定义 Dockerfile。 部署脚本基于此 Dockerfile 创建环境。 在 copilot_flow 文件夹中创建此 Dockerfile

FROM mcr.microsoft.com/azureml/promptflow/promptflow-runtime:latest
COPY ./requirements.txt .
RUN pip install -r requirements.txt

将聊天应用部署到托管终结点

若要将应用程序部署到 Azure 中的托管终结点,请创建联机终结点,然后在该终结点中创建部署,然后将所有流量路由到该部署。

在创建部署过程中,系统会将 copilot_flow 文件夹打包为模型,并生成云环境。 使用 Microsoft Entra ID 身份验证进行设置终结点。 可以在代码中或终结点详细信息页上的 Azure AI Studio 中更新所需的身份验证模式。

重要

将应用程序部署到 Azure 中的托管终结点会根据所选实例类型关联计算成本。 请确保了解关联的成本,并具有指定的实例类型的配额。 详细了解联机终结点

rag-tutorial 文件夹中创建文件 deploy.py。 添加以下代码:

import os
from dotenv import load_dotenv

load_dotenv()

from azure.ai.ml import MLClient
from azure.identity import DefaultAzureCredential
from azure.ai.ml.entities import (
    ManagedOnlineEndpoint,
    ManagedOnlineDeployment,
    Model,
    Environment,
    BuildContext,
)

client = MLClient(
    DefaultAzureCredential(),
    os.getenv("AZURE_SUBSCRIPTION_ID"),
    os.getenv("AZURE_RESOURCE_GROUP"),
    os.getenv("AZUREAI_PROJECT_NAME"),
)
endpoint_name = "tutorial-endpoint"
deployment_name = "tutorial-deployment"

endpoint = ManagedOnlineEndpoint(
    name=endpoint_name,
    properties={
        "enforce_access_to_default_secret_stores": "enabled"  # for secret injection support
    },
    auth_mode="aad_token",  # using aad auth instead of key-based auth
)

# Get the directory of the current script
script_dir = os.path.dirname(os.path.abspath(__file__))

# Define the path to the directory, appending the script directory to the relative path
copilot_path = os.path.join(script_dir, "copilot_flow")
deployment = ManagedOnlineDeployment(
    name=deployment_name,
    endpoint_name=endpoint_name,
    model=Model(
        name="copilot_flow_model",
        path=copilot_path,  # path to promptflow folder
        properties=[  # this enables the chat interface in the endpoint test tab
            ["azureml.promptflow.source_flow_id", "basic-chat"],
            ["azureml.promptflow.mode", "chat"],
            ["azureml.promptflow.chat_input", "chat_input"],
            ["azureml.promptflow.chat_output", "reply"],
        ],
    ),
    environment=Environment(
        build=BuildContext(
            path=copilot_path,
        ),
        inference_config={
            "liveness_route": {
                "path": "/health",
                "port": 8080,
            },
            "readiness_route": {
                "path": "/health",
                "port": 8080,
            },
            "scoring_route": {
                "path": "/score",
                "port": 8080,
            },
        },
    ),
    instance_type="Standard_DS3_v2",
    instance_count=1,
    environment_variables={
        "PRT_CONFIG_OVERRIDE": f"deployment.subscription_id={client.subscription_id},deployment.resource_group={client.resource_group_name},deployment.workspace_name={client.workspace_name},deployment.endpoint_name={endpoint_name},deployment.deployment_name={deployment_name}",
        "AZURE_OPENAI_ENDPOINT": os.getenv("AZURE_OPENAI_ENDPOINT"),
        "AZURE_SEARCH_ENDPOINT": os.getenv("AZURE_SEARCH_ENDPOINT"),
        "AZURE_OPENAI_API_VERSION": os.getenv("AZURE_OPENAI_API_VERSION"),
        "AZURE_OPENAI_CHAT_DEPLOYMENT": os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT"),
        "AZURE_OPENAI_EVALUATION_DEPLOYMENT": os.getenv(
            "AZURE_OPENAI_EVALUATION_DEPLOYMENT"
        ),
        "AZURE_OPENAI_EMBEDDING_DEPLOYMENT": os.getenv(
            "AZURE_OPENAI_EMBEDDING_DEPLOYMENT"
        ),
        "AZUREAI_SEARCH_INDEX_NAME": os.getenv("AZUREAI_SEARCH_INDEX_NAME"),
    },
)

# 1. create endpoint
created_endpoint = client.begin_create_or_update(
    endpoint
).result()  # result() means we wait on this to complete

# 2. create deployment
created_deployment = client.begin_create_or_update(deployment).result()

# 3. update endpoint traffic for the deployment
endpoint.traffic = {deployment_name: 100}  # 100% of traffic
client.begin_create_or_update(endpoint).result()

重要

Azure 区域租户中的终结点和部署名称必须唯一。 如果收到终结点或部署名称已存在的错误,请尝试其他名称。

输出部署详细信息

将以下行添加到部署脚本的末尾,以便在本地查看评估结果,并获取指向工作室的链接:

def get_ai_studio_url_for_deploy(
    client: MLClient, endpoint_name: str, deployment_name
) -> str:
    studio_base_url = "https://ai.azure.com"
    deployment_url = f"{studio_base_url}/projectdeployments/realtime/{endpoint_name}/{deployment_name}/detail?wsid=/subscriptions/{client.subscription_id}/resourceGroups/{client.resource_group_name}/providers/Microsoft.MachineLearningServices/workspaces/{client.workspace_name}&deploymentName={deployment_name}"

    return deployment_url


print("\n ~~~Deployment details~~~")
print(f"Your online endpoint name is: {endpoint_name}")
print(f"Your deployment name is: {deployment_name}")

print("\n ~~~Test in the Azure AI Studio~~~")
print("\n Follow this link to your deployment in the Azure AI Studio:")
print(
    get_ai_studio_url_for_deploy(
        client=client, endpoint_name=endpoint_name, deployment_name=deployment_name
    )
)

现在使用以下命令运行该脚本:

python deploy.py

注意

部署可能需要 10 分钟才能完成。 建议按照下一步操作,在等待时分配对终结点的访问权限。

部署完成后,你会获得指向 Azure AI Studio 部署页的链接,可在其中测试部署。

验证部署

建议在 Azure AI Studio 中测试应用程序。 如果想要在本地测试已部署的终结点,可以使用一些自定义代码进行调用。

记下需要执行后续步骤的终结点名称。

Azure OpenAI 资源的终结点访问

你可能需要向你的 Azure 订阅所有者(可能是你的 IT 管理员)寻求有关此部分的帮助。

在等待应用程序部署时,你或管理员可以为端点分配基于角色的访问权限。 这些角色允许应用程序在部署环境中无需密钥即可运行,就像在本地一样。

以前,你为帐户提供了一个特定角色,以便能够使用 Microsoft Entra ID 身份验证访问资源。 现在,请分配相同“认知服务 OpenAI 用户”角色的终结点。

注意

这些步骤类似于在快速入门中为用户标识分配角色以使用 Azure OpenAI 服务的方法。

若要向你自己授予你正在使用的 Azure AI 服务资源的访问权限,请执行以下操作:

  1. 在 AI Studio 中,转到你的项目,然后从左侧窗格中选择“设置”。

  2. 在“已连接资源”部分中,选择类型为“AIServices”的连接名称。

    项目设置页的屏幕截图,其中突出显示了如何选择已连接的 AI 服务资源以打开它。

    注意

    如果未看到“AIServices”连接,请改用“Azure OpenAI”连接。

  3. 在资源详细信息页上,选择“资源”标题下的链接,在 Azure 门户中打开 AI 服务资源。

    显示如何在 Azure 门户中打开资源的 AI 服务连接详细信息的屏幕截图。

  4. 在 Azure 门户的左侧页面中,选择“访问控制(IAM)”>“+ 添加”>“添加角色分配”。

  5. 搜索“认知服务 OpenAI 用户”角色,然后选择它。 然后,选择“下一步”。

    用于选择认知服务 OpenAI 用户角色的页面屏幕截图。

  6. 选择“托管标识” 。 然后选择“选择成员”。

  7. 在打开的“选择成员”窗格中,为托管标识选择“机器学习在线终结点”,然后搜索终结点名称。 选择终结点,然后选择“选择”

    显示联机终结点的成员选择的屏幕截图。

  8. 继续执行向导,然后选择“查看 + 分配”以添加角色分配。

注意

可能需要几分钟时间才能完成访问传播。 如果在下一步测试时出现未授权错误,请在几分钟后重试。

Azure AI 搜索资源的终结点访问

你可能需要向你的 Azure 订阅所有者(可能是你的 IT 管理员)寻求有关此部分的帮助。

与为 Azure AI 搜索服务分配搜索索引数据参与者角色的方式类似,你需要为终结点分配相同的角色。

  1. 在 Azure AI Studio 中,选择“设置”并导航到连接的 Azure AI 搜索服务。

  2. 选择链接以打开资源的摘要。 选择摘要页上的链接,在 Azure 门户中打开资源。

  3. 在 Azure 门户的左侧页面中,选择“访问控制(IAM)”>“+ 添加”>“添加角色分配”。

    屏幕截图显示了搜索资源的访问控制。

  4. 搜索搜索索引数据参与者角色,然后选择该角色。 然后,选择“下一步”。

  5. 选择“托管标识” 。 然后选择“选择成员”。

  6. 在打开的“选择成员”窗格中,为托管标识选择“机器学习在线终结点”,然后搜索终结点名称。 选择终结点,然后选择“选择”

    显示选择终结点的屏幕截图。

  7. 继续执行向导,然后选择“查看 + 分配”以添加角色分配。

注意

可能需要几分钟时间才能完成访问传播。 如果在下一步测试时出现未授权错误,请在几分钟后重试。

在 AI Studio 中测试部署

部署完成后,即可获得指向部署的便捷链接。 如果不使用该链接,请导航到项目中的“部署”选项卡并选择新部署。

显示 Azure AI Studio 中的部署概述的屏幕截图。

选择“测试”选项卡,然后尝试在聊天界面中提问。

例如,键入“毅行者登山鞋是否防水?”然后按 enter 键。

显示 Azure AI Studio 中的部署响应的屏幕截图。

看到返回的响应即表明部署已经过验证。

如果收到错误,请选择“日志”选项卡以获取更多详细信息。

注意

如果收到未经授权的错误,则可能尚未应用终结点访问权限。 请过几分钟重试。

在本地调用已部署的聊天应用

如果想要在本地验证部署,可以通过 Python 脚本进行调用。

定义一个脚本,该脚本将:

  • 为评分 URL 构造格式标准的请求。
  • 发布请求并处理响应。

使用以下代码在 rag-tutorial 文件夹中创建一个 invoke-local.py 文件。 修改 queryendpoint_name(按需修改其他参数)以适应用例。

import os
from dotenv import load_dotenv

load_dotenv()

import requests
from azure.ai.ml import MLClient
from azure.identity import DefaultAzureCredential

query = "Are the trailwalker shoes waterproof?"
endpoint_name = "tutorial-endpoint"

client = MLClient(
    DefaultAzureCredential(),
    os.getenv("AZURE_SUBSCRIPTION_ID"),
    os.getenv("AZURE_RESOURCE_GROUP"),
    os.getenv("AZUREAI_PROJECT_NAME"),
)


scoring_url = client.online_endpoints.get(endpoint_name).scoring_uri

headers = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {client._credential.get_token('https://ml.azure.com').token}",
    "Accept": "application/json",
}

response = requests.post(
    scoring_url,
    headers=headers,
    json={"chat_input": query},
)
(print(response.json()["reply"]))

应该会在控制台中看到聊天应用对查询的回复。

注意

如果收到未经授权的错误,则可能尚未应用终结点访问权限。 请过几分钟重试。

清理资源

为了避免产生不必要的 Azure 成本,如果不再需要在本快速入门中创建的资源,应该将其删除。 若要管理资源,可以使用 Azure 门户