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 comando dotnet new:

    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 del arrendatario. En ese caso, quítelo del código que crea una instancia del 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 luego 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 método GetAzureOpenAIChatConfiguration, 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 crearlo 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 . para que los resultados se puedan ver de forma jerárquica con la agrupación, el anidamiento y la consolidació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 mediante la inclusión de puntos (.) en sus propios nombres de escenario personalizados para crear una jerarquía de informes que funcione mejor en 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 una situación más real, también podría querer compartir el mismo nombre de ejecución en las pruebas de evaluación que residen en varios ensamblados y se ejecutan en distintos 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 que contiene 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 del modelo LLM a una pregunta de astronomía. Dado que ReportingConfiguration tiene habilitado el almacenamiento en caché de respuestas y, dado que el IChatClient proporcionado siempre se obtiene de la ScenarioRun creada mediante esta configuración de informes, la respuesta LLM para la prueba quedará 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. Si se usa await using, se garantiza que se disponga correctamente de ScenarioRun y que los resultados de esta evaluación se almacenen correctamente en el almacén de resultados.

    • Obtiene la respuesta del LLM a una pregunta de astronomía específica. El mismo IChatClient que se empleará en la evaluación se pasa al método GetAstronomyConversationAsync para obtener el almacenamiento en caché de respuestas de la respuesta de LLM principal que se va a evaluar. (Además, esto permite el almacenamiento en caché de respuestas de los cambios del LLM que los evaluadores usan para realizar sus evaluaciones internamente). Con el almacenamiento en caché de respuestas, se registra la respuesta del LLM:

      • 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 en 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 cada una de las pruebas de evaluación "generen errores" y bloqueen las compilaciones en las canalizaciones de CI/CD cuando esto sucede. En vez de eso, 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 se generarán errores en compilaciones concretas si hay las puntuaciones de evaluación caen considerablemente en varias 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 regresa, el objeto scenarioRun se elimina y el resultado de la evaluación se almacena en el almacén de resultados (basado en disco) que está 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 de la CLI dotnet test o mediante Explorador de pruebas.

Generar 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
    
  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. Necesita tener el siguiente aspecto.

    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 usted modificó 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: