Compartir a través de


Tutorial: Evaluación de la calidad de respuesta con almacenamiento en caché e informes

En este tutorial, creará una aplicación MSTest para evaluar la respuesta de chat de un modelo de OpenAI. La aplicación de prueba usa las bibliotecas Microsoft.Extensions.AI.Evaluation para realizar las evaluaciones, almacenar en caché las respuestas del modelo y crear informes. En el tutorial se usan evaluadores integrados y personalizados. Los evaluadores de calidad integrados (del paquete Microsoft.Extensions.AI.Evaluation.Quality) usan un LLM para realizar evaluaciones; el evaluador personalizado no usa ia.

Prerrequisitos

Configuración del servicio de IA

Para aprovisionar un servicio y un modelo de Azure OpenAI mediante Azure Portal, complete los pasos descritos en el artículo Creación e implementación de un recurso de Azure OpenAI Service. En el paso "Implementar un modelo", seleccione el modelo gpt-4o.

Creación de la aplicación de prueba

Complete los pasos siguientes para crear un proyecto de MSTest que se conecte al modelo de gpt-4o IA.

  1. En una ventana de terminal, vaya al directorio donde desea crear la aplicación y cree una aplicación MSTest con el dotnet new comando :

    dotnet new mstest -o TestAIWithReporting
    
  2. Vaya al directorio TestAIWithReporting y agregue los paquetes necesarios a la aplicación:

    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. Ejecute los siguientes comandos para agregar secretos de aplicación para el punto de conexión de Azure OpenAI, el nombre del modelo y el ID de inquilino:

    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>
    

    (Dependiendo de su entorno, es posible que no se necesite el identificador de inquilino. En ese caso, quítelo del código que crea una instancia de DefaultAzureCredential.

  4. Abra la nueva aplicación en el editor que prefiera.

Adición del código de la aplicación de prueba

  1. Cambie el nombre del archivo Test1.cs a MyTests.cs y, a continuación, abra el archivo y cambie el nombre de la clase a MyTests. Elimine el método vacío TestMethod1 .

  2. Agregue las directivas necesarias using a la parte superior del archivo.

    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. Agregue la TestContext propiedad a la clase .

    // The value of the TestContext property is populated by MSTest.
    public TestContext? TestContext { get; set; }
    
  4. Agregue el GetAzureOpenAIChatConfiguration método , que crea el IChatClient que usa el evaluador para comunicarse con el modelo.

    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. Configure la funcionalidad de informes.

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

    Nombre del escenario

    El nombre del escenario se establece en el nombre completo del método de prueba actual. Sin embargo, puede establecerlo en cualquier cadena que desee cuando llame a CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken). Estas son algunas consideraciones para elegir un nombre de escenario:

    • Al usar el almacenamiento basado en disco, el nombre del escenario se usa como nombre de la carpeta en la que se almacenan los resultados de evaluación correspondientes. Por lo tanto, es una buena idea mantener el nombre razonablemente corto y evitar los caracteres que no se permiten en los nombres de archivo y directorio.
    • De forma predeterminada, el informe de evaluación generado divide los nombres de escenario en . de manera que los resultados se puedan mostrar en una vista jerárquica con la agrupación, el anidamiento y la agregación adecuados. Esto es especialmente útil en los casos en los que el nombre del escenario se establece en el nombre completo del método de prueba correspondiente, ya que permite agrupar los resultados por espacios de nombres y nombres de clase en la jerarquía. Sin embargo, también puede aprovechar esta característica incluyendo puntos (.) en sus propios nombres de escenarios personalizados para crear una jerarquía de informes que sea más efectiva para sus escenarios.

    Nombre de ejecución

    El nombre de ejecución se usa para agrupar los resultados de evaluación que forman parte de la misma ejecución de evaluación (o ejecución de prueba) cuando se almacenan los resultados de la evaluación. Si no proporciona un nombre de ejecución al crear , ReportingConfigurationtodas las ejecuciones de evaluación usarán el mismo nombre de ejecución predeterminado de Default. En este caso, los resultados de una ejecución se sobrescribirán a continuación y perderá la capacidad de comparar los resultados en distintas ejecuciones.

    En este ejemplo se usa una marca de tiempo como nombre de ejecución. Si tiene más de una prueba en el proyecto, asegúrese de que los resultados se agrupan correctamente mediante el mismo nombre de ejecución en todas las configuraciones de informes usadas en las pruebas.

    En un escenario más real, también puede compartir el mismo nombre de ejecución entre las pruebas de evaluación que residen en varios ensamblados diferentes y que se ejecutan en diferentes procesos de prueba. En tales casos, podría usar un script para actualizar una variable de entorno con un nombre de ejecución adecuado (como el número de compilación actual asignado por el sistema de CI/CD) antes de ejecutar las pruebas. O bien, si el sistema de compilación genera versiones de archivo de ensamblado que aumentan de forma monotónica, puede leer el elemento AssemblyFileVersionAttribute desde dentro del código de prueba y usarlo como nombre de ejecución para comparar los resultados en distintas versiones del producto.

    Configuración de informes

    Un ReportingConfiguration identifica:

    Esta prueba usa una configuración de informes basada en disco.

  6. En un archivo independiente, agregue la WordCountEvaluator clase , que es un evaluador personalizado que 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));
        }
    }
    

    WordCountEvaluator Cuenta el número de palabras presentes en la respuesta. A diferencia de algunos evaluadores, no se basa en la inteligencia artificial. El método EvaluateAsync devuelve un EvaluationResult que incluye un NumericMetric con el recuento de palabras.

    El EvaluateAsync método también asocia una interpretación predeterminada a la métrica. La interpretación predeterminada considera que la métrica es buena (aceptable) si el recuento de palabras detectado está comprendido entre 6 y 100. De lo contrario, la métrica se considera errónea. Esta interpretación predeterminada se puede invalidar por el autor de la llamada, si es necesario.

  7. De nuevo en MyTests.cs, agregue un método para recopilar los evaluadores que se van a usar en la evaluación.

    private static IEnumerable<IEvaluator> GetEvaluators()
    {
        IEvaluator relevanceEvaluator = new RelevanceEvaluator();
        IEvaluator coherenceEvaluator = new CoherenceEvaluator();
        IEvaluator wordCountEvaluator = new WordCountEvaluator();
    
        return [relevanceEvaluator, coherenceEvaluator, wordCountEvaluator];
    }
    
  8. Agregue un método para agregar un símbolo del sistema ChatMessage, defina las opciones de chat y pida al modelo una respuesta a una pregunta determinada.

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

    La prueba de este tutorial evalúa la respuesta de LLM a una pregunta de astronomía. Puesto que el ReportingConfiguration tiene habilitado el almacenamiento en caché de respuestas, y dado que el IChatClient proporcionado siempre se obtiene de la ScenarioRun configurada utilizando esta configuración de informes, la respuesta LLM para la prueba se almacena en caché y se reutiliza. La respuesta se volverá a usar hasta que expire la entrada de caché correspondiente (en 14 días de forma predeterminada) o hasta que se cambie cualquier parámetro de solicitud, como el punto de conexión llm o la pregunta que se realiza.

  9. Agregue un método para validar la respuesta.

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

    Sugerencia

    Las métricas incluyen una Reason propiedad que explica el razonamiento de la puntuación. El motivo se incluye en el informe generado y se puede ver haciendo clic en el icono de información de la tarjeta de la métrica correspondiente.

  10. Por último, agregue el propio método de prueba .

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

    Este método de prueba:

    • Crea el objeto ScenarioRun. El uso de await using garantiza que ScenarioRun se elimine correctamente y que los resultados de esta evaluación se almacenen en el almacén de resultados.

    • Obtiene la respuesta del LLM a una pregunta de astronomía específica. Lo mismo IChatClient que se usará para la evaluación se pasa al GetAstronomyConversationAsync método para obtener la caché de la respuesta para la respuesta LLM principal que está siendo evaluada. Además, esto permite la caché de respuestas para los turnos de LLM que los evaluadores usan para realizar sus evaluaciones internamente. En caso de caché de respuestas, la respuesta del LLM se captura ya sea mediante:

      • Directamente desde el punto de conexión LLM en la primera ejecución de la prueba actual o en ejecuciones posteriores si la entrada almacenada en caché ha expirado (14 días, de forma predeterminada).
      • Desde la caché de respuestas (basada en disco) que se configuró en s_defaultReportingConfiguration en ejecuciones posteriores de la prueba.
    • Ejecuta los evaluadores contra la respuesta. Al igual que la respuesta LLM, en ejecuciones posteriores, la evaluación se captura de la caché de respuestas (basada en disco) que se configuró en s_defaultReportingConfiguration.

    • Ejecuta alguna validación básica en el resultado de la evaluación.

      Este paso es opcional y principalmente para fines de demostración. En las evaluaciones reales, es posible que no desee validar resultados individuales, ya que las respuestas de LLM y las puntuaciones de evaluación pueden cambiar a lo largo del tiempo a medida que evoluciona el producto (y los modelos usados). Es posible que no desee que las pruebas de evaluación individuales "produzcan errores" y bloqueen las compilaciones en las canalizaciones de CI/CD cuando esto sucede. En su lugar, podría ser mejor confiar en el informe generado y realizar un seguimiento de las tendencias generales de las puntuaciones de evaluación en diferentes escenarios a lo largo del tiempo (y solo fallar las compilaciones individuales cuando hay una caída significativa en las puntuaciones de evaluación en múltiples pruebas diferentes). Dicho esto, hay algunos matices aquí y la elección de si validar resultados individuales o no puede variar en función del caso de uso específico.

    Cuando el método finaliza, el objeto scenarioRun se elimina y el resultado de la evaluación se almacena en el almacén de resultados en disco configurado en s_defaultReportingConfiguration.

Ejecución de la prueba o evaluación

Ejecute la prueba mediante el flujo de trabajo de prueba preferido, por ejemplo, mediante el comando dotnet test de la CLI o mediante el Explorador de pruebas.

Generación de un informe

  1. Instale la herramienta .NET Microsoft.Extensions.AI.Evaluation.Console mediante la ejecución del siguiente comando desde una ventana de terminal:

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

    Sugerencia

    Es posible que tenga que crear primero un archivo de manifiesto. Para obtener más información sobre eso e instalar herramientas locales, consulte Herramientas locales.

  2. Genere un informe mediante la ejecución del comando siguiente:

    dotnet tool run aieval report --path <path\to\your\cache\storage> --output report.html
    
  3. Abra el archivo report.html . Debería parecerse a esto.

    Captura de pantalla del informe de evaluación que muestra los valores de la conversación y las métricas.

Pasos siguientes

  • Vaya al directorio donde se almacenan los resultados de la prueba (que es C:\TestReports, a menos que haya modificado la ubicación al crear el ReportingConfiguration). En el results subdirectorio, observe que hay una carpeta para cada ejecución de prueba denominada con una marca de tiempo (ExecutionName). Dentro de cada una de esas carpetas hay una carpeta para cada nombre de escenario; en este caso, solo el método de prueba único en el proyecto. Esa carpeta contiene un archivo JSON con todos los datos, incluidos los mensajes, la respuesta y el resultado de evaluación.
  • Expanda la evaluación. Estas son algunas ideas: