Návod: Vyhodnocení kvality odpovědí pomocí ukládání do mezipaměti a sestavování zpráv.

V tomto kurzu vytvoříte aplikaci MSTest, která vyhodnotí odpověď na chat modelu OpenAI. Testovací aplikace používá knihovny Microsoft.Extensions.AI.Evaluation pro provádění vyhodnocování, ukládání odpovědí modelu do mezipaměti a vytváření sestav. Kurz používá integrované i vlastní vyhodnocovače. Integrované hodnotitele kvality (z balíčku Microsoft.Extensions.AI.Evaluation.Quality) používají LLM k vyhodnocení; vlastní hodnotitel nepoužívá AI.

Požadavky

Konfigurace služby AI

Pokud chcete zřídit Azure OpenAI service a model pomocí portálu Azure, proveďte kroky v článku Vytvoření a nasazení prostředku Azure OpenAI Service. V kroku Nasazení modelu vyberte gpt-5 model.

Vytvoření testovací aplikace

Provedením následujících kroků vytvořte projekt MSTest, který se připojuje k modelu AI.

  1. V okně terminálu přejděte do adresáře, do kterého chcete aplikaci vytvořit, a pomocí příkazu vytvořte novou aplikaci dotnet new MSTest:

    dotnet new mstest -o TestAIWithReporting
    
  2. Přejděte do adresáře TestAIWithReporting a přidejte do aplikace potřebné balíčky:

    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. Spuštěním následujících příkazů přidejte tajné kódy app pro koncový bod Azure OpenAI a ID tenanta:

    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>
    

    (V závislosti na vašem prostředí nemusí být ID tenanta potřeba. V takovém případě ho odeberte z kódu, který vytvoří instanci objektu DefaultAzureCredential.)

  4. Otevřete novou aplikaci v libovolném editoru.

Přidání kódu testovací aplikace

  1. Přejmenujte soubor Test1.cs na MyTests.cs a pak soubor otevřete a přejmenujte třídu na MyTests. Odstraňte prázdnou TestMethod1 metodu.

  2. Na začátek souboru přidejte potřebné using direktivy.

    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 Přidejte vlastnost do třídy.

    // The value of the TestContext property is populated by MSTest.
    public TestContext? TestContext { get; set; }
    
  4. Přidejte metodu GetAzureOpenAIChatConfiguration, která vytvoří IChatClient, jež evaluátor používá ke komunikaci s modelem.

    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. Nastavte funkci pro generování reportů.

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

    Název scénáře

    Název scénáře je nastaven na plně kvalifikovaný název aktuální testovací metody. Můžete ho ale nastavit na libovolný řetězec při volání CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken). Při výběru názvu scénáře zvažte tyto faktory:

    • Při použití diskového úložiště se název scénáře použije jako název složky, ve které jsou uloženy odpovídající výsledky vyhodnocení. Proto je vhodné zachovat název přiměřeně krátký a vyhnout se všem znakům, které nejsou povolené v názvech souborů a adresářů.
    • Ve výchozím nastavení je vygenerovaná sestava vyhodnocení nastavena tak, že názvy scénářů jsou rozděleny podle ., aby se výsledky zobrazovaly v hierarchickém zobrazení s logickým seskupováním, vnořením a agregací. Hierarchické zobrazení je zvlášť užitečné, když je název scénáře plně kvalifikovaným názvem odpovídající testovací metody, protože seskupuje výsledky podle oborů názvů a názvů tříd v hierarchii. Tuto funkci ale můžete využít také tak, že do vlastních názvů scénářů zahrnete tečky (.) a vytvoříte tak hierarchii sestav, která nejlépe odpovídá vašim scénářům.

    Název spuštění

    Název spuštění se používá k seskupení výsledků vyhodnocení, které jsou součástí stejného zkušebního běhu (nebo testovacího spuštění) při uložení výsledků vyhodnocení. Pokud při vytváření ReportingConfiguration nezadáte název spuštění, budou všechna spuštění používat stejný výchozí název spuštění Default. V tomto případě se výsledky z jednoho spuštění přepíšou dalším spuštěním a ztratíte možnost porovnat výsledky napříč různými běhy.

    Tento příklad používá časové razítko jako název spuštění. Pokud máte v projektu více než jeden test, ujistěte se, že jsou výsledky seskupené správně pomocí stejného názvu spuštění ve všech konfiguracích vytváření sestav používaných napříč testy.

    Ve skutečném scénáři můžete také chtít sdílet stejný název spuštění napříč testy vyhodnocení, které se nacházejí v několika různých sestaveních a které se spouští v různých testovacích procesech. V takových případech můžete pomocí skriptu aktualizovat proměnnou prostředí odpovídajícím názvem spuštění (například aktuálním číslem sestavení přiřazeným systémem CI/CD) před spuštěním testů. Nebo pokud váš systém sestavení vytváří monotonicky rostoucí verze souboru sestavení, mohli byste číst AssemblyFileVersionAttribute z testovacího kódu a použít ho jako název spuštění k porovnání výsledků v různých verzích produktu.

    Konfigurace sestav

    Identifikuje ReportingConfiguration :

    Tento test používá konfiguraci generování sestav na disku.

  6. Do samostatného souboru přidejte WordCountEvaluator třídu, což je vlastní vyhodnocovací objekt, který implementuje 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));
        }
    }
    

    Spočítá WordCountEvaluator počet slov v odpovědi. Na rozdíl od některých vyhodnocovačů to není založené na umělé inteligenci. Metoda EvaluateAsync vrátí EvaluationResult, který zahrnuje NumericMetric s počtem slov.

    Metoda EvaluateAsync také připojí výchozí interpretaci k metrice. Výchozí interpretace považuje metriku za dobrou (přijatelnou), pokud je zjištěný počet slov mezi 6 a 100. Jinak se metrika považuje za neúspěšnou. Volající může tuto výchozí interpretaci v případě potřeby přepsat.

  7. MyTests.csZpátky přidejte metodu pro shromáždění vyhodnocovačů, které se mají použít v vyhodnocení.

    private static IEnumerable<IEvaluator> GetEvaluators()
    {
        IEvaluator relevanceEvaluator = new RelevanceEvaluator();
        IEvaluator coherenceEvaluator = new CoherenceEvaluator();
        IEvaluator wordCountEvaluator = new WordCountEvaluator();
    
        return [relevanceEvaluator, coherenceEvaluator, wordCountEvaluator];
    }
    
  8. Přidejte metodu pro přidání systémové výzvy ChatMessage, definujte možnosti chatu a požádejte model o odpověď na danou otázku.

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

    Test v tomto kurzu vyhodnotí odpověď LLM na otázku astronomie. Vzhledem k tomu, že ReportingConfiguration má povolené ukládání odpovědí do mezipaměti a IChatClient je vždy načítán z ScenarioRun vytvořené touto konfigurací sestav, LLM odpověď pro test je uložena do mezipaměti a znovu použita. Odpověď se znovu použije, dokud nevyprší platnost odpovídající položky mezipaměti (ve výchozím nastavení 14 dnů) nebo dokud se nezmění jakýkoli parametr požadavku, například koncový bod LLM nebo dotaz, který se žádá.

  9. Přidejte metodu pro ověření odpovědi.

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

    Návod

    Každá metrika obsahuje Reason vlastnost, která vysvětluje odůvodnění skóre. Důvod je součástí vygenerované sestavy a lze jej zobrazit kliknutím na ikonu informace na kartě příslušné metriky.

  10. Nakonec přidejte samotnou testovací metodu .

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

    Tato testovací metoda:

    • Vytvoří ScenarioRun. await using zajišťuje správné odstranění ScenarioRun a správné uchování výsledků vyhodnocení do úložiště výsledků.

    • Získá odpověď LLM na konkrétní astronomickou otázku. Test přenáší stejný IChatClient, který se používá pro vyhodnocování, k metodě GetAstronomyConversationAsync za účelem získání mezipaměti odpovědí pro primární odpověď LLM, která je vyhodnocována. (Předání stejného klienta také umožňuje ukládání odpovědí do mezipaměti pro otočení LLM, které vyhodnocovači používají k internímu hodnocení.) S ukládáním odpovědí do mezipaměti je odpověď LLM načtena buď:

      • Přímo z koncového bodu LLM v prvním spuštění aktuálního testu nebo v následných spuštěních, pokud vypršela platnost položky uložené v mezipaměti (ve výchozím nastavení 14 dnů).
      • Z mezipaměti odpovědí (založené na disku) nakonfigurované v s_defaultReportingConfiguration následných spuštěních testu.
    • Spustí vyhodnocovače proti odpovědi. Podobně jako odpověď LLM, při následných spuštěních se vyhodnocení načítá z mezipaměti odpovědí (založené na disku) nakonfigurované v s_defaultReportingConfiguration.

    • Spustí pro výsledek vyhodnocení několik základních ověření.

      Tento krok je volitelný a hlavně pro demonstrační účely. V reálných hodnoceních možná nebudete chtít ověřit jednotlivé výsledky, protože výsledky LLM a skóre hodnocení se můžou v průběhu času měnit při vývoji vašeho produktu (a použitých modelů). Nemusíte chtít, aby jednotlivé testy v kanálech CI/CD selhaly a blokovaly sestavení, když se výsledky změní. Místo toho může být lepší spoléhat se na vygenerovanou sestavu a sledovat celkové trendy hodnocení skóre v různých scénářích během času (a selhávat jednotlivá sestavení pouze tehdy, pokud dojde k významnému propadu skóre hodnocení v několika různých testech). To znamená, že zde je určitá nuance a volba, zda ověřit jednotlivé výsledky nebo ne, se může lišit v závislosti na konkrétním případu použití.

    Po návratu metody je scenarioRun objekt uvolněn a výsledek hodnocení je uložen do konfigurovaného diskového úložiště výsledků v s_defaultReportingConfiguration.

Spuštění testu/vyhodnocení

Spusťte test pomocí preferovaného testovacího pracovního postupu, například pomocí příkazu dotnet test rozhraní příkazového řádku nebo průzkumníka testů.

Vygenerujte sestavu

  1. Nainstalujte nástroj Microsoft.Extensions.AI.Evaluation.Console .NET spuštěním následujícího příkazu v terminálovém okně:

    dotnet tool install --create-manifest-if-needed Microsoft.Extensions.AI.Evaluation.Console
    
  2. Vygenerujte sestavu spuštěním následujícího příkazu:

    dotnet tool run aieval report --path <path\to\your\cache\storage> --output report.html
    
  3. Otevřete soubor report.html. Sestava vypadá podobně jako na následujícím snímku obrazovky.

    Snímek obrazovky hodnotící zprávy zobrazující konverzaci a hodnoty metrik

Další kroky