共用方式為


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

在本教學課程中,您會建立 MSTest 應用程式來評估 OpenAI 模型的聊天回應。 測試應用程式使用 Microsoft。擴充功能:AI.Evaluation函式庫,用於執行評估、快取模型回應並建立報告。 本教學課程會同時使用內建和自訂評估器。 內建品質評估器(來自 Microsoft。Extensions.AI.Evaluation.Quality 套件)使用 LLM 進行評估;自訂評估器沒有使用 AI。

先決條件

設定 AI 服務

若要使用 Azure 入口網站來佈建 Azure OpenAI 服務和模型,請完成 建立並部署 Azure OpenAI 服務資源 文章中的步驟。 在 [部署模型] 步驟中,選取 gpt-5 模型。

建立測試應用程式

完成以下步驟,建立一個連接 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
    dotnet add package Microsoft.Extensions.Configuration
    dotnet add package Microsoft.Extensions.Configuration.UserSecrets
    
  3. 執行以下指令,為你的Azure OpenAI 端點和租戶 ID 新增 app secrets

    dotnet user-secrets init
    dotnet user-secrets set AZURE_OPENAI_ENDPOINT <your-Azure-OpenAI-endpoint>
    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 tenantId = config["AZURE_TENANT_ID"];
        string model = "gpt-5";
    
        // 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方法回傳一個EvaluationResult,其中包含NumericMetric來顯示字數。

    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 --create-manifest-if-needed Microsoft.Extensions.AI.Evaluation.Console
    
  2. 執行下列命令來產生報告:

    dotnet tool run aieval report --path <path\to\your\cache\storage> --output report.html
    
  3. 開啟 report.html 檔案。 報告看起來和以下截圖很像。

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

後續步驟