共用方式為


教程:使用快取和報告評估回應品質

在本教學課程中,您會建立 MSTest 應用程式來評估 OpenAI 模型的聊天回應。 測試應用程式會使用 Microsoft.Extensions.AI.Evaluation 程式庫來執行評估、快取模型回應,以及建立報表。 本教學課程會同時使用內建和自訂評估器。 內建品質評估工具 (來自 Microsoft.Extensions.AI.Evaluation.Quality 套件) 會使用 LLM 來執行評估;自訂評估器不使用 AI。

先決條件

設定 AI 服務

若要使用 Azure 入口網站 布建 Azure OpenAI 服務和模型,請完成建立及部署 Azure OpenAI 服務資源一文中的步驟。 在 [部署模型] 步驟中,選取 gpt-4o 模型。

建立測試應用程式

完成下列步驟,以建立連線至 gpt-4o AI 模型的 MSTest 專案。

  1. 在終端機視窗中,流覽至您要建立應用程式的目錄,然後使用命令 dotnet new 建立新的 MSTest 應用程式:

    dotnet new mstest -o TestAIWithReporting
    
  2. 瀏覽至 TestAIWithReporting 目錄,並將必要的套件新增至您的應用程式:

    dotnet add package Azure.AI.OpenAI
    dotnet add package Azure.Identity
    dotnet add package Microsoft.Extensions.AI.Abstractions
    dotnet add package Microsoft.Extensions.AI.Evaluation
    dotnet add package Microsoft.Extensions.AI.Evaluation.Quality
    dotnet add package Microsoft.Extensions.AI.Evaluation.Reporting
    dotnet add package Microsoft.Extensions.AI.OpenAI --prerelease
    dotnet add package Microsoft.Extensions.Configuration
    dotnet add package Microsoft.Extensions.Configuration.UserSecrets
    
  3. 執行下列命令,為您的 Azure OpenAI 端點、模型名稱和租使用者識別元新增 應用程式秘密

    dotnet user-secrets init
    dotnet user-secrets set AZURE_OPENAI_ENDPOINT <your-Azure-OpenAI-endpoint>
    dotnet user-secrets set AZURE_OPENAI_GPT_NAME gpt-4o
    dotnet user-secrets set AZURE_TENANT_ID <your-tenant-ID>
    

    (視您的環境而定,可能不需要租用戶識別碼。在此情況下,請將其從具現化 的 DefaultAzureCredential程式碼中移除。

  4. 在您選擇的編輯器中開啟新的應用程式。

新增測試應用程式程式碼

  1. Test1.cs 檔案重新命名為 MyTests.cs,然後開啟檔案並將類別重新命名為 MyTests。 刪除空白 TestMethod1 方法。

  2. 將必要的 using 指示詞新增至檔案頂端。

    using Azure.AI.OpenAI;
    using Azure.Identity;
    using Microsoft.Extensions.AI.Evaluation;
    using Microsoft.Extensions.AI;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.AI.Evaluation.Reporting.Storage;
    using Microsoft.Extensions.AI.Evaluation.Reporting;
    using Microsoft.Extensions.AI.Evaluation.Quality;
    
  3. 將屬性新增至 TestContext 類別。

    // The value of the TestContext property is populated by MSTest.
    public TestContext? TestContext { get; set; }
    
  4. 新增GetAzureOpenAIChatConfiguration方法,以建立評估者用來與模型通訊的IChatClient

    private static ChatConfiguration GetAzureOpenAIChatConfiguration()
    {
        IConfigurationRoot config = new ConfigurationBuilder().AddUserSecrets<MyTests>().Build();
    
        string endpoint = config["AZURE_OPENAI_ENDPOINT"];
        string model = config["AZURE_OPENAI_GPT_NAME"];
        string tenantId = config["AZURE_TENANT_ID"];
    
        // Get an instance of Microsoft.Extensions.AI's <see cref="IChatClient"/>
        // interface for the selected LLM endpoint.
        AzureOpenAIClient azureClient =
            new(
                new Uri(endpoint),
                new DefaultAzureCredential(new DefaultAzureCredentialOptions() { TenantId = tenantId }));
        IChatClient client = azureClient.GetChatClient(deploymentName: model).AsIChatClient();
    
        // Create an instance of <see cref="ChatConfiguration"/>
        // to communicate with the LLM.
        return new ChatConfiguration(client);
    }
    
  5. 設定報告功能。

    private string ScenarioName => $"{TestContext!.FullyQualifiedTestClassName}.{TestContext.TestName}";
    
    private static string ExecutionName => $"{DateTime.Now:yyyyMMddTHHmmss}";
    
    private static readonly ReportingConfiguration s_defaultReportingConfiguration =
        DiskBasedReportingConfiguration.Create(
            storageRootPath: "C:\\TestReports",
            evaluators: GetEvaluators(),
            chatConfiguration: GetAzureOpenAIChatConfiguration(),
            enableResponseCaching: true,
            executionName: ExecutionName);
    

    案例名稱

    案例名稱會設定為目前測試方法的完整名稱。 但是,您可以在呼叫 CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken)時將其設定為您選擇的任何字串。 以下是選擇案例名稱的一些考量:

    • 使用磁碟型儲存體時,案例名稱會作為儲存對應評估結果的資料夾名稱。 因此,最好保持名稱合理簡短,並避免檔案和目錄名稱中不允許使用任何字元。
    • 依預設,產生的評估報告會在 . 處分割案例名稱,以便結果可以在具適當分組、巢狀和彙總的階層檢視中顯示。 這在案例名稱設定為對應測試方法的完整名稱的情況下特別有用,因為它允許依階層中的命名空間和類別名稱將結果分組。 不過,您也可以利用此功能,在您自己的自訂案例名稱中包含句點 (.),以建立最適合您案例的報告階層。

    執行名稱

    執行名稱可用來在儲存評估結果時,將屬於相同評估執行 (或測試執行) 一部分的評估結果分組。 如果您在建立 ReportingConfiguration時未提供執行名稱,則所有評估執行都會使用相同的預設執行名稱 Default。 在此情況下,一次執行的結果將會被下一個執行覆寫,而且您無法比較不同執行之間的結果。

    此範例使用時間戳記作為執行名稱。 如果您的專案中有多個測試,請確定在測試中使用的所有報告組態中使用相同的執行名稱,以正確分組結果。

    在更實際的案例中,您可能也想要在位於多個不同元件中且在不同測試程式中執行的評估測試之間共用相同的執行名稱。 在這種情況下,您可以在執行測試之前使用指令碼使用適當的執行名稱(例如CI/CD系統指派的當前組建編號)更新環境變數。 或者,如果您的建置系統產生單調增加的組件檔案版本,您可以從測試程式碼中讀取AssemblyFileVersionAttribute,並使用該名稱作為執行名稱,以比較不同產品版本之間的結果。

    報告設定

    A ReportingConfiguration 識別了以下內容:

    此測試使用磁碟型報告組態。

  6. 在個別檔案中,新增 WordCountEvaluator 類別,這是實作 IEvaluator的自訂評估器。

    using System.Text.RegularExpressions;
    using Microsoft.Extensions.AI;
    using Microsoft.Extensions.AI.Evaluation;
    
    namespace TestAIWithReporting;
    
    public class WordCountEvaluator : IEvaluator
    {
        public const string WordCountMetricName = "Words";
    
        public IReadOnlyCollection<string> EvaluationMetricNames => [WordCountMetricName];
    
        /// <summary>
        /// Counts the number of words in the supplied string.
        /// </summary>
        private static int CountWords(string? input)
        {
            if (string.IsNullOrWhiteSpace(input))
            {
                return 0;
            }
    
            MatchCollection matches = Regex.Matches(input, @"\b\w+\b");
            return matches.Count;
        }
    
        /// <summary>
        /// Provides a default interpretation for the supplied <paramref name="metric"/>.
        /// </summary>
        private static void Interpret(NumericMetric metric)
        {
            if (metric.Value is null)
            {
                metric.Interpretation =
                    new EvaluationMetricInterpretation(
                        EvaluationRating.Unknown,
                        failed: true,
                        reason: "Failed to calculate word count for the response.");
            }
            else
            {
                if (metric.Value <= 100 && metric.Value > 5)
                    metric.Interpretation = new EvaluationMetricInterpretation(
                        EvaluationRating.Good,
                        reason: "The response was between 6 and 100 words.");
                else
                    metric.Interpretation = new EvaluationMetricInterpretation(
                        EvaluationRating.Unacceptable,
                        failed: true,
                        reason: "The response was either too short or greater than 100 words.");
            }
        }
    
        public ValueTask<EvaluationResult> EvaluateAsync(
            IEnumerable<ChatMessage> messages,
            ChatResponse modelResponse,
            ChatConfiguration? chatConfiguration = null,
            IEnumerable<EvaluationContext>? additionalContext = null,
            CancellationToken cancellationToken = default)
        {
            // Count the number of words in the supplied <see cref="modelResponse"/>.
            int wordCount = CountWords(modelResponse.Text);
    
            string reason =
                $"This {WordCountMetricName} metric has a value of {wordCount} because " +
                $"the evaluated model response contained {wordCount} words.";
    
            // Create a <see cref="NumericMetric"/> with value set to the word count.
            // Include a reason that explains the score.
            var metric = new NumericMetric(WordCountMetricName, value: wordCount, reason);
    
            // Attach a default <see cref="EvaluationMetricInterpretation"/> for the metric.
            Interpret(metric);
    
            return new ValueTask<EvaluationResult>(new EvaluationResult(metric));
        }
    }
    

    計算 WordCountEvaluator 回應中出現的字數。 與某些評估者不同,它不是基於人工智慧。 此 EvaluateAsync 方法會傳回一個包含 NumericMetricEvaluationResultNumericMetric 包含字數統計。

    EvaluateAsync 方法也會將預設解譯附加至度量。 如果偵測到的字數在 6 到 100 之間,則預設解譯會將度量視為良好 (可接受)。 否則,計量會被視為失敗。 如果需要,呼叫者可以覆寫此預設解譯。

  7. 返回 MyTests.cs,新增一個方法來收集評估器以用於評估。

    private static IEnumerable<IEvaluator> GetEvaluators()
    {
        IEvaluator relevanceEvaluator = new RelevanceEvaluator();
        IEvaluator coherenceEvaluator = new CoherenceEvaluator();
        IEvaluator wordCountEvaluator = new WordCountEvaluator();
    
        return [relevanceEvaluator, coherenceEvaluator, wordCountEvaluator];
    }
    
  8. 新增方法以新增系統提示 ChatMessage、定義 聊天選項,並要求模型回應給定問題。

    private static async Task<(IList<ChatMessage> Messages, ChatResponse ModelResponse)> GetAstronomyConversationAsync(
        IChatClient chatClient,
        string astronomyQuestion)
    {
        const string SystemPrompt =
            """
            You're an AI assistant that can answer questions related to astronomy.
            Keep your responses concise and under 100 words.
            Use the imperial measurement system for all measurements in your response.
            """;
    
        IList<ChatMessage> messages =
            [
                new ChatMessage(ChatRole.System, SystemPrompt),
                new ChatMessage(ChatRole.User, astronomyQuestion)
            ];
    
        var chatOptions =
            new ChatOptions
            {
                Temperature = 0.0f,
                ResponseFormat = ChatResponseFormat.Text
            };
    
        ChatResponse response = await chatClient.GetResponseAsync(messages, chatOptions);
        return (messages, response);
    }
    

    本教學課程中的測試會評估 LLM 對天文學問題的回應。 由於 ReportingConfiguration 已啟用回應快取,並且所提供的 IChatClient 總是從此報告配置建立的 ScenarioRun 中擷取,因此測試中 LLM 的回應會被快取並重複使用。 回應將重複使用,直到對應的快取項目到期 (預設為 14 天),或直到任何請求參數 (例如 LLM 端點或所詢問的問題) 變更為止。

  9. 新增驗證回應的方法。

    /// <summary>
    /// Runs basic validation on the supplied <see cref="EvaluationResult"/>.
    /// </summary>
    private static void Validate(EvaluationResult result)
    {
        // Retrieve the score for relevance from the <see cref="EvaluationResult"/>.
        NumericMetric relevance =
            result.Get<NumericMetric>(RelevanceEvaluator.RelevanceMetricName);
        Assert.IsFalse(relevance.Interpretation!.Failed, relevance.Reason);
        Assert.IsTrue(relevance.Interpretation.Rating is EvaluationRating.Good or EvaluationRating.Exceptional);
    
        // Retrieve the score for coherence from the <see cref="EvaluationResult"/>.
        NumericMetric coherence =
            result.Get<NumericMetric>(CoherenceEvaluator.CoherenceMetricName);
        Assert.IsFalse(coherence.Interpretation!.Failed, coherence.Reason);
        Assert.IsTrue(coherence.Interpretation.Rating is EvaluationRating.Good or EvaluationRating.Exceptional);
    
        // Retrieve the word count from the <see cref="EvaluationResult"/>.
        NumericMetric wordCount = result.Get<NumericMetric>(WordCountEvaluator.WordCountMetricName);
        Assert.IsFalse(wordCount.Interpretation!.Failed, wordCount.Reason);
        Assert.IsTrue(wordCount.Interpretation.Rating is EvaluationRating.Good or EvaluationRating.Exceptional);
        Assert.IsFalse(wordCount.ContainsDiagnostics());
        Assert.IsTrue(wordCount.Value > 5 && wordCount.Value <= 100);
    }
    

    小提示

    每個指標都包含一個 Reason 屬性,用於解釋分數的原因。 原因包含在 產生的報告 中,並可按一下對應量度卡片上的資訊圖示來檢視。

  10. 最後,新增 測試方法 本身。

    [TestMethod]
    public async Task SampleAndEvaluateResponse()
    {
        // Create a <see cref="ScenarioRun"/> with the scenario name
        // set to the fully qualified name of the current test method.
        await using ScenarioRun scenarioRun =
            await s_defaultReportingConfiguration.CreateScenarioRunAsync(
                ScenarioName,
                additionalTags: ["Moon"]);
    
        // Use the <see cref="IChatClient"/> that's included in the
        // <see cref="ScenarioRun.ChatConfiguration"/> to get the LLM response.
        (IList<ChatMessage> messages, ChatResponse modelResponse) = await GetAstronomyConversationAsync(
            chatClient: scenarioRun.ChatConfiguration!.ChatClient,
            astronomyQuestion: "How far is the Moon from the Earth at its closest and furthest points?");
    
        // Run the evaluators configured in <see cref="s_defaultReportingConfiguration"/> against the response.
        EvaluationResult result = await scenarioRun.EvaluateAsync(messages, modelResponse);
    
        // Run some basic validation on the evaluation result.
        Validate(result);
    }
    

    該測試方法:

    • 建立 ScenarioRun. 使用 await using 可確保 ScenarioRun 正確處置,而評估的結果能正確保存至結果存放區。

    • 取得 LLM 對特定天文學問題的回應。 用於評估的相同 IChatClient 會被傳遞至 GetAstronomyConversationAsync 方法,以取得正在評估的主要 LLM 回應的 回應快取。 此外,這還啟用專門用於評估者在內部執行評估過程中的 LLM 回合的回應快取。使用回應快取時,LLM 回應的擷取方式包括:

      • 在目前測試的第一次執行中,直接從 LLM 端點獲取,或如果快取條目已過期(預設為 14 天),則在後續執行中獲取。
      • 後續測試執行中,從配置於 s_defaultReportingConfiguration 的(磁碟型)回應快取中取得結果。
    • 針對回應運行評估器。 如同 LLM 回應,在後續執行中,評估會從 s_defaultReportingConfiguration 設定的(磁碟型)回應快取中擷取。

    • 對評估結果執行一些基本驗證。

      此步驟是可選的,主要用於演示目的。 在實際評估中,您可能不想驗證個別結果,因為 LLM 回應和評估分數可能會隨著產品 (和所使用的模型) 的發展而隨時間而變化。 發生這種情況時,您可能不希望個別評估測試「失敗」並封鎖 CI/CD 管線中的建置。 相反地,最好依賴產生的報表,並追蹤一段時間內不同案例中評估分數的整體趨勢 (而且只有在多個不同測試的評估分數大幅下降時,個別組建才會失敗) 。 也就是說,這裡有一些細微差別,是否驗證個別結果的選擇可能會因特定用例而異。

    當方法返回時,scenarioRun 物件會被釋放,並將評估結果儲存到在 s_defaultReportingConfiguration 中設定的(磁碟型)結果存放區。

執行測試/評估

使用您慣用的測試工作流程來執行測試,例如,使用 CLI 命令 dotnet test 或透過 Test Explorer

產生報表

  1. 從終端機視窗執行下列命令,安裝 Microsoft.Extensions.AI.Evaluation.Console .NET 工具:

    dotnet tool install --local Microsoft.Extensions.AI.Evaluation.Console
    

    小提示

    您可能需要先建立資訊清單檔案。 如需相關資訊及安裝本機工具,請參閱 本機工具

  2. 執行下列命令來產生報告:

    dotnet tool run aieval report --path <path\to\your\cache\storage> --output report.html
    
  3. 開啟 report.html 檔案。 它應該看起來像這樣。

    顯示交談和計量值的評估報告螢幕擷取畫面。

後續步驟