Sdílet prostřednictvím


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 k provádění vyhodnocení, ukládání odpovědí modelu do mezipaměti a vytváření sestav. Kurz používá integrované i vlastní vyhodnocovače. Integrované vyhodnocovače kvality (z balíčku Microsoft.Extensions.AI.Evaluation.Quality) používají k vyhodnocení LLM; vlastní vyhodnocovací nástroj nepoužívá AI.

Požadavky

Konfigurace služby AI

Pokud chcete zřídit službu a model Azure OpenAI pomocí webu Azure Portal, proveďte kroky v článku Vytvoření a nasazení prostředku služby Azure OpenAI. V kroku Nasazení modelu vyberte gpt-4o model.

Vytvoření testovací aplikace

Provedením následujících kroků vytvořte projekt MSTest, který se připojí k gpt-4o 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 --prerelease
    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 aplikací pro koncový bod Azure OpenAI, název modelu a ID tenanta.

    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>
    

    (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 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. 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 podle vašeho výběru při volání CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken). Tady je několik důležitých aspektů pro výběr názvu scénáře:

    • 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í vygenerovaná sestava vyhodnocení rozdělí názvy . scénářů tak, aby se výsledky mohly zobrazit v hierarchickém zobrazení s odpovídajícími seskupeními, vnořením a agregací. To je zvlášť užitečné v případech, kdy je název scénáře nastaven na plně kvalifikovaný název odpovídající metody testu, protože umožňuje výsledky seskupit podle jmenných prostorů a názvů tříd ve struktuře hierarchie. 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 hodnotící spuštění používat stejný výchozí název Default spuštění. V takovém 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 obsahující NumericMetric, který obsahuje počet 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. Tuto výchozí interpretaci může volající 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á povoleno ukládání odpovědí do mezipaměti a protože je zadané IChatClient vždy načítáno z ScenarioRun, která je vytvořena pomocí této konfigurační zprávy, odpověď LLM pro test je uložena do mezipaměti a znovu použita. Odpověď se bude opakovaně používat, 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ý je kladen.

  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. Použití await using zajišťuje, že ScenarioRun je správně likvidován a že výsledky tohoto vyhodnocení jsou správně uloženy v úložišti výsledků.

    • Získá odpověď LLM na konkrétní astronomickou otázku. IChatClient Stejný prvek, který se použije k vyhodnocení, je předán metodě GetAstronomyConversationAsync, aby byla zajištěna mezipaměť odpovědí pro primární odpověď LLM, která je hodnocena. (Kromě toho to umožňuje ukládání odpovědí do mezipaměti pro jednotlivé úseky LLM, které evaluátoři používají k provádění jejich interních vyhodnocení.) Při ukládání odpovědí do mezipaměti se načítají odpovědi LLM:

      • 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), která byla nakonfigurována 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, která byla nakonfigurována 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ěřovat jednotlivé výsledky, protože výsledky LLM a hodnocení se můžou v průběhu času měnit při vývoji vašeho produktu (a použitých modelů). V kanálech CI/CD nechcete, aby jednotlivé testy vyhodnocení selhaly a blokovaly sestavení. 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 z okna terminálu:

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

    Návod

    Možná budete muset napřed vytvořit soubor manifestu. Další informace o tom a instalaci místních nástrojů naleznete v tématu Místní nástroje.

  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. Mělo by to vypadat nějak takto.

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

Další kroky