Bagikan melalui


Tutorial: Mengevaluasi kualitas respons dengan caching dan pelaporan

Dalam tutorial ini, Anda membuat aplikasi MSTest untuk mengevaluasi respons obrolan model OpenAI. Aplikasi pengujian menggunakan pustaka Microsoft.Extensions.AI.Evaluation untuk melakukan evaluasi, menyimpan respons model, dan membuat laporan. Tutorial ini menggunakan evaluator bawaan dan kustom. Evaluator kualitas bawaan (dari paket Microsoft.Extensions.AI.Evaluation.Quality) menggunakan LLM untuk melakukan evaluasi; evaluator kustom tidak menggunakan AI.

Prasyarat

Mengonfigurasi layanan AI

Untuk memprovisikan layanan dan model Azure OpenAI menggunakan portal Azure, selesaikan langkah-langkah dalam artikel Membuat dan menyebarkan sumber daya Azure OpenAI Service. Dalam langkah "Sebarkan model", pilih model gpt-4o.

Membuat aplikasi pengujian

Selesaikan langkah-langkah berikut untuk membuat proyek MSTest yang tersambung ke gpt-4o model AI.

  1. Di jendela terminal, navigasikan ke direktori tempat Anda ingin membuat aplikasi, dan buat aplikasi MSTest baru dengan dotnet new perintah :

    dotnet new mstest -o TestAIWithReporting
    
  2. Navigasi ke direktori TestAIWithReporting, dan tambahkan paket yang diperlukan ke aplikasi Anda:

    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. Jalankan perintah berikut untuk menambahkan rahasia aplikasi untuk titik akhir Azure OpenAI, nama model, dan ID penyewa Anda:

    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>
    

    (Tergantung pada lingkungan Anda, ID penyewa mungkin tidak diperlukan. Dalam hal ini, hapus dari kode yang membuat instans DefaultAzureCredential.)

  4. Buka aplikasi baru di editor pilihan Anda.

Menambahkan kode aplikasi pengujian

  1. Ganti nama file Test1.cs menjadi MyTests.cs, lalu buka file dan ganti nama kelas menjadi MyTests. Hapus metode kosong TestMethod1 .

  2. Tambahkan arahan yang diperlukan using ke bagian atas file.

    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. Tambahkan properti TestContext ke kelas.

    // The value of the TestContext property is populated by MSTest.
    public TestContext? TestContext { get; set; }
    
  4. Tambahkan metode GetAzureOpenAIChatConfiguration, yang membuat IChatClient yang digunakan evaluator untuk berkomunikasi dengan model.

    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. Siapkan fungsionalitas pelaporan.

    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);
    

    Nama skenario

    Nama skenario diatur ke nama yang sepenuhnya memenuhi syarat dari metode pengujian saat ini. Namun, Anda dapat mengaturnya ke string pilihan Anda saat Anda memanggil CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken). Berikut adalah beberapa pertimbangan untuk memilih nama skenario:

    • Saat menggunakan penyimpanan berbasis disk, nama skenario digunakan sebagai nama folder tempat hasil evaluasi yang sesuai disimpan. Jadi ada baiknya untuk menjaga nama cukup pendek dan menghindari karakter apa pun yang tidak diizinkan dalam nama file dan direktori.
    • ** Secara bawaan, laporan evaluasi yang dihasilkan membagi nama skenario pada . sehingga hasilnya dapat ditampilkan dalam tampilan hierarkis dengan pengelompokan, penyusunan bertingkat, dan penggabungan yang sesuai. Ini sangat berguna dalam skenario di mana nama diatur ke nama lengkap yang memenuhi kualifikasi dari metode uji terkait, karena ini memungkinkan hasil dikelompokkan menurut namespace dan nama kelas dalam hierarki. Namun, Anda juga dapat memanfaatkan fitur ini dengan menyertakan titik (.) dalam nama skenario kustom Anda sendiri untuk membuat hierarki pelaporan yang paling sesuai untuk skenario Anda.

    Nama eksekusi

    Nama eksekusi digunakan untuk mengelompokkan hasil evaluasi yang merupakan bagian dari eksekusi evaluasi yang sama (atau uji coba) saat hasil evaluasi disimpan. Jika Anda tidak memberikan nama eksekusi saat membuat ReportingConfiguration, semua eksekusi evaluasi akan menggunakan nama eksekusi default yang sama dari Default. Dalam hal ini, hasil dari satu eksekusi akan ditimpa oleh yang berikutnya dan Anda kehilangan kemampuan untuk membandingkan hasil di berbagai eksekusi.

    Contoh ini menggunakan tanda waktu sebagai nama eksekusi. Jika Anda memiliki lebih dari satu pengujian dalam proyek Anda, pastikan bahwa hasil dikelompokkan dengan benar dengan menggunakan nama eksekusi yang sama di semua konfigurasi pelaporan yang digunakan di seluruh pengujian.

    Dalam skenario dunia yang lebih nyata, Anda mungkin juga ingin berbagi nama eksekusi yang sama di seluruh pengujian evaluasi yang hidup di beberapa rakitan yang berbeda dan yang dijalankan dalam proses pengujian yang berbeda. Dalam kasus seperti itu, Anda dapat menggunakan skrip untuk memperbarui variabel lingkungan dengan nama eksekusi yang sesuai (seperti nomor build saat ini yang ditetapkan oleh sistem CI/CD Anda) sebelum menjalankan pengujian. Atau, jika sistem build Anda menghasilkan versi file rakitan yang meningkat secara monoton, Anda dapat membaca AssemblyFileVersionAttribute dari dalam kode pengujian dan menggunakannya sebagai nama eksekusi untuk membandingkan hasil di berbagai versi produk.

    Konfigurasi pelaporan

    ReportingConfiguration Ini mengidentifikasi:

    Pengujian ini menggunakan konfigurasi pelaporan berbasis disk.

  6. Dalam file terpisah, tambahkan WordCountEvaluator kelas , yang merupakan evaluator kustom yang mengimplementasikan 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 menghitung jumlah kata yang terdapat dalam respons. Tidak seperti beberapa evaluator, itu tidak didasarkan pada AI. Metode EvaluateAsync mengembalikan sebuah EvaluationResult yang menyertakan NumericMetric berisi jumlah kata.

    Metode ini EvaluateAsync juga melampirkan interpretasi default ke metrik. Interpretasi default menganggap metrik menjadi baik (dapat diterima) jika jumlah kata yang terdeteksi adalah antara 6 dan 100. Jika tidak, metrik dianggap gagal. Interpretasi default ini dapat diubah oleh pihak yang memanggil, jika diperlukan.

  7. Kembali ke MyTests.cs, tambahkan metode untuk mengumpulkan evaluator yang akan digunakan dalam evaluasi.

    private static IEnumerable<IEvaluator> GetEvaluators()
    {
        IEvaluator relevanceEvaluator = new RelevanceEvaluator();
        IEvaluator coherenceEvaluator = new CoherenceEvaluator();
        IEvaluator wordCountEvaluator = new WordCountEvaluator();
    
        return [relevanceEvaluator, coherenceEvaluator, wordCountEvaluator];
    }
    
  8. Tambahkan metode untuk menambahkan permintaan ChatMessagesistem , tentukan opsi obrolan, dan minta model untuk respons terhadap pertanyaan tertentu.

    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);
    }
    

    Tes dalam tutorial ini mengevaluasi respons LLM terhadap pertanyaan astronomi. Karena caching respons telah diaktifkan, dan karena IChatClient yang disediakan selalu diambil dari ScenarioRun yang dibuat menggunakan konfigurasi pelaporan ini, respons LLM untuk pengujian dicache dan digunakan kembali. Respons akan digunakan kembali sampai entri cache yang sesuai kedaluwarsa (dalam 14 hari secara default), atau sampai parameter permintaan apa pun, seperti titik akhir LLM atau pertanyaan yang diajukan, diubah.

  9. Tambahkan metode untuk memvalidasi respons.

    /// <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);
    }
    

    Petunjuk / Saran

    Metrik masing-masing menyertakan Reason properti yang menjelaskan alasan skor. Alasannya disertakan dalam laporan yang dihasilkan dan dapat dilihat dengan mengklik ikon informasi pada kartu metrik yang sesuai.

  10. Terakhir, tambahkan metode pengujian itu sendiri.

    [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);
    }
    

    Metode pengujian ini:

    • Membuat ScenarioRun. Penggunaan await using memastikan bahwa ScenarioRun dibuang dengan benar dan bahwa hasil evaluasi ini disimpan secara permanen dengan tepat ke dalam basis data hasil.

    • Mendapatkan respons LLM terhadap pertanyaan astronomi tertentu. Hal yang sama IChatClient yang akan digunakan untuk penilaian diteruskan ke metode GetAstronomyConversationAsync untuk mendapatkan penyimpanan sementara respons untuk respons LLM utama yang sedang dinilai. (Selain itu, ini memungkinkan penyimpanan cache respons untuk giliran LLM yang digunakan evaluator untuk melakukan evaluasi mereka secara internal.) Dengan penyimpanan cache respons, respons LLM diambil baik:

      • Langsung dari titik akhir LLM dalam eksekusi pertama pengujian saat ini, atau dalam eksekusi berikutnya jika entri yang di-cache telah kedaluwarsa (14 hari, secara default).
      • Dari cache respons (berbasis disk) yang dikonfigurasi dalam s_defaultReportingConfiguration pada pengulangan pengujian berikutnya.
    • Menjalankan pengujian evaluator terhadap jawaban. Seperti respons LLM, pada eksekusi berikutnya, evaluasi diambil dari cache respons (berbasis disk) yang dikonfigurasi di s_defaultReportingConfiguration.

    • Menjalankan beberapa validasi dasar pada hasil evaluasi.

      Langkah ini bersifat opsional dan terutama untuk tujuan demonstrasi. Dalam evaluasi dunia nyata, Anda mungkin tidak ingin memvalidasi hasil individual karena respons LLM dan skor evaluasi dapat berubah dari waktu ke waktu saat produk Anda (dan model yang digunakan) berkembang. Anda mungkin tidak ingin pengujian evaluasi individual "gagal" dan memblokir build di alur CI/CD Anda ketika ini terjadi. Sebaliknya, mungkin lebih baik mengandalkan laporan yang dihasilkan dan memantau tren keseluruhan dari skor evaluasi di berbagai skenario sepanjang waktu (dan hanya menggagalkan build individu ketika terdapat penurunan skor evaluasi yang signifikan di berbagai tes yang berbeda). Dengan demikian, ada beberapa nuansa di sini dan pilihan apakah akan memvalidasi hasil individu atau tidak dapat bervariasi tergantung pada kasus penggunaan tertentu.

    Setelah metode selesai, objek scenarioRun dibuang dan hasil dari evaluasi disimpan ke penyimpanan hasil berbasis disk yang dikonfigurasi dalam s_defaultReportingConfiguration.

Menjalankan pengujian/evaluasi

Jalankan pengujian menggunakan alur kerja pengujian pilihan Anda, misalnya, dengan menggunakan perintah dotnet test CLI atau melalui Test Explorer.

Membuat laporan

  1. Instal alat .NET Microsoft.Extensions.AI.Evaluation.Console dengan menjalankan perintah berikut dari jendela terminal:

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

    Petunjuk / Saran

    Anda mungkin perlu membuat file manifes terlebih dahulu. Untuk informasi selengkapnya tentang itu dan menginstal alat lokal, lihat Alat lokal.

  2. Buat laporan dengan menjalankan perintah berikut:

    dotnet tool run aieval report --path <path\to\your\cache\storage> --output report.html
    
  3. Buka file report.html. Ini akan terlihat seperti ini.

    Cuplikan layar laporan evaluasi yang memperlihatkan nilai percakapan dan metrik.

Langkah selanjutnya