Condividi tramite


Esercitazione: Valutare la qualità della risposta con la memorizzazione nella cache e la creazione di report

In questa esercitazione si crea un'app MSTest per valutare la risposta della chat di un modello OpenAI. L'app di test usa le librerie Microsoft.Extensions.AI.Evaluation per eseguire le valutazioni, memorizzare nella cache le risposte del modello e creare report. L'esercitazione utilizza valutatori predefiniti e personalizzati. Gli analizzatori di qualità predefiniti (dal pacchetto Microsoft.Extensions.AI.Evaluation.Quality) usano un LLM per eseguire valutazioni; l'analizzatore personalizzato non usa l'intelligenza artificiale.

Prerequisiti

Configurare il servizio di intelligenza artificiale

Per effettuare il provisioning di un servizio e un modello OpenAI di Azure usando il portale di Azure, completare la procedura descritta nell'articolo creare e distribuire una risorsa del servizio OpenAI di Azure. Nel passaggio "Distribuisci un modello" selezionare il modello di gpt-4o.

Creare l'app di test

Completare i passaggi seguenti per creare un progetto MSTest che si connette al modello di gpt-4o intelligenza artificiale.

  1. In una finestra del terminale passare alla directory in cui si vuole creare l'app e creare una nuova app MSTest con il dotnet new comando :

    dotnet new mstest -o TestAIWithReporting
    
  2. Passare alla directory TestAIWithReporting e aggiungere i pacchetti necessari all'app:

    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. Esegui i seguenti comandi per aggiungere segreti dell'applicazione per il nome del modello, l'endpoint e l'ID cliente di 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>
    

    A seconda dell'ambiente, l'ID tenant potrebbe non essere necessario. In tal caso, rimuovi l'ID tenant dal codice che crea un'istanza del componente DefaultAzureCredential.

  4. Aprire la nuova app nell'editor preferito.

Aggiungere il codice dell'app di test

  1. Rinominare il file Test1.cs in MyTests.cs, quindi aprire il file e rinominare la classe in MyTests. Eliminare il metodo vuoto TestMethod1 .

  2. Aggiungere le direttive necessarie using all'inizio del 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. Aggiungere la TestContext proprietà alla classe .

    // The value of the TestContext property is populated by MSTest.
    public TestContext? TestContext { get; set; }
    
  4. Aggiungere il GetAzureOpenAIChatConfiguration metodo , che crea l'oggetto IChatClient usato dall'analizzatore per comunicare con il modello.

    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. Configurare la funzionalità di creazione di 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);
    

    Nome scenario

    Il nome dello scenario è impostato sul nome completo del metodo di test corrente. Tuttavia, è possibile impostarlo su qualsiasi stringa di propria scelta quando si chiama CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken). Ecco alcune considerazioni per la scelta di un nome di scenario:

    • Quando si usa l'archiviazione basata su disco, il nome dello scenario viene usato come nome della cartella in cui vengono archiviati i risultati di valutazione corrispondenti. È quindi consigliabile mantenere il nome ragionevolmente breve ed evitare eventuali caratteri non consentiti nei nomi di file e directory.
    • Per impostazione predefinita, il report di valutazione generato suddivide i nomi degli scenari in . modo che i risultati possano essere visualizzati in una visualizzazione gerarchica con raggruppamento, annidamento e aggregazione appropriati. Ciò è particolarmente utile nei casi in cui il nome dello scenario è impostato sul nome completo del metodo di test corrispondente, poiché consente di raggruppare i risultati per namespace e nomi di classe nella gerarchia. Tuttavia, puoi anche sfruttare questa funzionalità includendo i punti (.) nei nomi dei tuoi scenari personalizzati per creare una gerarchia di report che funzioni meglio per i tuoi scenari.

    Nome della esecuzione

    Il nome dell'esecuzione viene usato per raggruppare i risultati della valutazione che fanno parte della stessa esecuzione di valutazione (o esecuzione di test) quando vengono archiviati i risultati della valutazione. Se non si specifica un nome di esecuzione durante la creazione di , ReportingConfigurationtutte le esecuzioni di valutazione useranno lo stesso nome di esecuzione predefinito di Default. In questo caso, i risultati di un'esecuzione verranno sovrascritti dalla successiva e si perde la possibilità di confrontare i risultati tra diverse esecuzioni.

    In questo esempio viene usato un timestamp come nome di esecuzione. Se nel progetto sono presenti più test, assicurarsi che i risultati vengano raggruppati correttamente usando lo stesso nome di esecuzione in tutte le configurazioni di report usate nei test.

    In uno scenario più reale, è anche possibile condividere lo stesso nome di esecuzione tra i test di valutazione che risiedono in più assembly diversi e eseguiti in processi di test diversi. In questi casi, è possibile usare uno script per aggiornare una variabile di ambiente con un nome di esecuzione appropriato (ad esempio il numero di build corrente assegnato dal sistema CI/CD) prima di eseguire i test. In alternativa, se il sistema di compilazione produce versioni dei file di assembly che aumentano in modo monotonico, è possibile leggere dal AssemblyFileVersionAttribute codice di test e usarlo come nome di esecuzione per confrontare i risultati tra diverse versioni del prodotto.

    Configurazione dei report

    Un ReportingConfiguration identifica:

    Questo test usa una configurazione di reportistica basata su disco.

  6. In un file separato aggiungere la WordCountEvaluator classe , che è un analizzatore personalizzato che implementa 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));
        }
    }
    

    Conta WordCountEvaluator il numero di parole presenti nella risposta. A differenza di alcuni analizzatori, non si basa sull'intelligenza artificiale. Il metodo EvaluateAsync restituisce un EvaluationResult che include un NumericMetric contenente il conteggio delle parole.

    Il EvaluateAsync metodo associa anche un'interpretazione predefinita alla metrica. L'interpretazione predefinita considera la metrica valida (accettabile) se il conteggio delle parole rilevate è compreso tra 6 e 100. In caso contrario, la metrica viene considerata non riuscita. Questa interpretazione predefinita può essere sostituita dal chiamante, se necessario.

  7. Nel MyTests.cs, aggiungere un metodo per raccogliere i valutatori da usare nella valutazione.

    private static IEnumerable<IEvaluator> GetEvaluators()
    {
        IEvaluator relevanceEvaluator = new RelevanceEvaluator();
        IEvaluator coherenceEvaluator = new CoherenceEvaluator();
        IEvaluator wordCountEvaluator = new WordCountEvaluator();
    
        return [relevanceEvaluator, coherenceEvaluator, wordCountEvaluator];
    }
    
  8. Aggiungere un metodo per aggiungere un prompt di ChatMessagesistema, definire le opzioni di chat e chiedere al modello una risposta a una determinata domanda.

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

    Il test in questa esercitazione valuta la risposta dell'LLM a una domanda di astronomia. Poiché ha ReportingConfiguration la memorizzazione nella cache delle risposte abilitata e poiché l'oggetto fornito IChatClient viene sempre recuperato dall'oggetto ScenarioRun creato usando questa configurazione di creazione di report, la risposta LLM per il test viene memorizzata nella cache e riutilizzata. La risposta verrà riutilizzata fino alla scadenza della voce della cache corrispondente (in 14 giorni per impostazione predefinita) o fino a quando non viene modificato qualsiasi parametro di richiesta, ad esempio l'endpoint LLM o la domanda da porre.

  9. Aggiungere un metodo per convalidare la risposta.

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

    Suggerimento

    Le metriche includono ognuna una Reason proprietà che spiega il motivo del punteggio. Il motivo è incluso nel report generato e può essere visualizzato facendo clic sull'icona delle informazioni nella scheda della metrica corrispondente.

  10. Infine, aggiungere il metodo di test stesso.

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

    Questo metodo di test:

    • Crea l'oggetto ScenarioRun. L'uso di await using garantisce che l'oggetto ScenarioRun venga eliminato correttamente e che i risultati di questa valutazione vengano mantenuti correttamente nell'archivio risultati.

    • Ottiene la risposta dell'LLM a una domanda di astronomia specifica. Lo stesso IChatClient che verrà utilizzato per la valutazione viene passato al metodo GetAstronomyConversationAsync per ottenere la memorizzazione nella cache delle risposte per la primaria risposta LLM valutata. Ciò consente inoltre la memorizzazione nella cache delle risposte per i turni LLM usati dagli analizzatori per eseguire le proprie valutazioni internamente. Con la memorizzazione nella cache delle risposte, la risposta LLM viene recuperata:

      • Direttamente dall'endpoint LLM nella prima esecuzione del test corrente o nelle esecuzioni successive se la voce memorizzata nella cache è scaduta (14 giorni, per impostazione predefinita).
      • Dalla cache delle risposte (basata su disco) che è stata configurata in s_defaultReportingConfiguration nelle esecuzioni successive del test.
    • Esegue i valutatori contro la risposta. Analogamente alla risposta LLM, nelle esecuzioni successive, la valutazione viene recuperata dalla cache di risposta (basata su disco) configurata in s_defaultReportingConfiguration.

    • Esegue una convalida di base sul risultato della valutazione.

      Questo passaggio è facoltativo e principalmente a scopo dimostrativo. Nelle valutazioni reali, è possibile non convalidare i singoli risultati perché le risposte e i punteggi di valutazione LLM possono cambiare nel tempo man mano che il prodotto (e i modelli usati) si evolvono. Potresti non voler che i singoli test di valutazione falliscano e blocchino le compilazioni nelle pipeline CI/CD quando ciò accade. Al contrario, potrebbe essere meglio basarsi sul report generato e tenere traccia delle tendenze complessive dei punteggi di valutazione in scenari diversi nel tempo (e fallire solo le singole compilazioni qualora si verifichi un calo significativo dei punteggi di valutazione in più test differenti). Detto questo, c'è una certa sfumatura qui e la scelta di se convalidare singoli risultati o meno può variare a seconda del caso d'uso specifico.

    Quando il metodo termina, l'oggetto scenarioRun viene eliminato e il risultato della valutazione per la valutazione viene archiviato nell'archivio dei risultati (basato su disco) configurato in s_defaultReportingConfiguration.

Eseguire il test/valutazione

Eseguire il test usando il flusso di lavoro di test preferito, ad esempio usando il comando dell'interfaccia a riga di comando dotnet test o tramite Esplora Test.

Generare un report

  1. Installare lo strumento Microsoft.Extensions.AI.Evaluation.Console .NET eseguendo il comando seguente da una finestra del terminale:

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

    Suggerimento

    Potrebbe essere necessario creare prima un file manifesto. Per altre informazioni su tale strumento e sull'installazione di strumenti locali, vedere Strumenti locali.

  2. Generare un report eseguendo il comando seguente:

    dotnet tool run aieval report --path <path\to\your\cache\storage> --output report.html
    
  3. Apri il file report.html. Dovrebbe avere un aspetto simile al seguente.

    Screenshot del report di valutazione che mostra la conversazione e i valori delle metriche.

Passaggi successivi