Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Neste tutorial, você cria um aplicativo MSTest para avaliar a resposta de bate-papo de um modelo OpenAI. O aplicativo de teste usa as bibliotecas Microsoft.Extensions.AI.Evaluation para executar as avaliações, armazenar em cache as respostas do modelo e criar relatórios. O tutorial usa avaliadores internos e personalizados. Os avaliadores de qualidade internos (do pacote Microsoft.Extensions.AI.Evaluation.Quality) usam um LLM para realizar avaliações; o avaliador personalizado não usa IA.
Pré-requisitos
- .NET 8 ou uma versão posterior
- Visual Studio Code (opcional)
Configurar o serviço de IA
Para provisionar um serviço e um modelo do Azure OpenAI usando o portal do Azure, conclua as etapas no artigo Criar e implantar um recurso do Serviço OpenAI do Azure. Na etapa "Implantar um modelo", selecione o modelo gpt-4o.
Criar o aplicativo de teste
Conclua as etapas a seguir para criar um projeto MSTest que se conecta ao modelo gpt-4o de IA.
Em uma janela de terminal, navegue até o diretório onde você deseja criar seu aplicativo e crie um novo aplicativo MSTest com o
dotnet newcomando:dotnet new mstest -o TestAIWithReportingNavegue até o diretório
TestAIWithReportinge adicione os pacotes necessários ao seu aplicativo: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.UserSecretsExecute os seguintes comandos para adicionar segredos de aplicativo ao seu endpoint do Azure OpenAI, nome do modelo e ID do locatário.
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>(Dependendo do seu ambiente, o ID do locatário pode não ser necessário. Nesse caso, remova-o do código que instancia o DefaultAzureCredential.)
Abra o novo aplicativo no editor de sua escolha.
Adicionar o código do aplicativo de teste
Renomeie o arquivo Test1.cs para MyTests.cs e, em seguida, abra o arquivo e renomeie a classe para
MyTests. Exclua o método vazioTestMethod1.Adicione as diretivas necessárias
usingà parte superior do arquivo.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;Adicione a propriedade TestContext à classe.
// The value of the TestContext property is populated by MSTest. public TestContext? TestContext { get; set; }Adicione o
GetAzureOpenAIChatConfigurationmétodo, que cria o IChatClient que o avaliador usa para se comunicar com o 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); }Configure a funcionalidade de relatório.
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 do cenário
O nome do cenário é definido como o nome totalmente qualificado do método de teste atual. No entanto, você pode defini-lo para qualquer cadeia de caracteres de sua escolha quando ligar para CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken). Aqui estão algumas considerações para escolher um nome de cenário:
- Ao usar o armazenamento baseado em disco, o nome do cenário é usado como o nome da pasta na qual os resultados da avaliação correspondente são armazenados. Portanto, é uma boa ideia manter o nome razoavelmente curto e evitar caracteres que não são permitidos em nomes de arquivos e diretórios.
- Por padrão, o relatório de avaliação gerado divide os nomes dos cenários em
., de forma que os resultados possam ser exibidos em uma exibição hierárquica com agrupamento, aninhamento e agregação apropriados. Isso é especialmente útil nos casos em que o nome do cenário é definido como o nome totalmente qualificado do método de teste correspondente, pois permite que os resultados sejam agrupados por namespaces e nomes de classe na hierarquia. No entanto, você também pode aproveitar esse recurso incluindo pontos (.) em seus próprios nomes de cenário personalizados para criar uma hierarquia de relatórios que funcione melhor para seus cenários.
Nome da execução
O nome de execução é usado para agrupar os resultados da avaliação que fazem parte da mesma execução de avaliação (ou execução de teste) quando os resultados da avaliação são armazenados. Se você não fornecer um nome de execução ao criar um ReportingConfiguration, todas as execuções de avaliação usarão o mesmo nome de execução padrão do
Default. Nesse caso, os resultados de uma execução serão substituídos pela próxima e você perderá a capacidade de comparar resultados em diferentes execuções.Este exemplo usa um timestamp como nome de execução. Se você tiver mais de um teste em seu projeto, certifique-se de que os resultados sejam agrupados corretamente usando o mesmo nome de execução em todas as configurações de relatório usadas nos testes.
Em um cenário mais real, você também pode querer compartilhar o mesmo nome de execução entre testes de avaliação que vivem em vários assemblies diferentes e que são executados em processos de teste diferentes. Nesses casos, você pode usar um script para atualizar uma variável de ambiente com um nome de execução apropriado (como o número de compilação atual atribuído pelo seu sistema CI/CD) antes de executar os testes. Ou, se o seu sistema de compilação produzir versões de ficheiros de assemblagem monotonicamente crescentes, poderá ler o AssemblyFileVersionAttribute dentro do código de teste e usá-lo como nome de execução para comparar os resultados entre diferentes versões do produto.
Configuração de relatórios
A ReportingConfiguration identifica:
- O conjunto de avaliadores que devem ser invocados para cada ScenarioRun criado ao chamar CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken).
- O endpoint LLM que os avaliadores devem utilizar (ver ReportingConfiguration.ChatConfiguration).
- Como e onde os resultados das execuções do cenário devem ser armazenados.
- A forma como as respostas LLM relacionadas ao cenário são executadas deve ser armazenada em cache.
- O nome de execução que deve ser usado quando relatar resultados para as execuções de cenários.
Este teste usa uma configuração de relatório baseada em disco.
Em um arquivo separado, adicione classe
WordCountEvaluator, que é um avaliador 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)); } }A
WordCountEvaluatorconta o número de palavras presentes na resposta. Ao contrário de alguns avaliadores, não se baseia em IA. OEvaluateAsyncmétodo retorna um EvaluationResult que inclui um NumericMetric que contém a contagem de palavras.O
EvaluateAsyncmétodo também anexa uma interpretação padrão à métrica. A interpretação padrão considera a métrica boa (aceitável) se a contagem de palavras detetada estiver entre 6 e 100. Caso contrário, a métrica é considerada falha. Essa interpretação padrão pode ser substituída pelo chamador, se necessário.De volta ao
MyTests.cs, adicione um método para reunir os avaliadores para usar na avaliação.private static IEnumerable<IEvaluator> GetEvaluators() { IEvaluator relevanceEvaluator = new RelevanceEvaluator(); IEvaluator coherenceEvaluator = new CoherenceEvaluator(); IEvaluator wordCountEvaluator = new WordCountEvaluator(); return [relevanceEvaluator, coherenceEvaluator, wordCountEvaluator]; }Adicione um método para adicionar um prompt ChatMessagedo sistema, defina as opções de bate-papo e peça ao modelo uma resposta a uma determinada pergunta.
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); }O teste neste tutorial avalia a resposta do LLM a uma pergunta de astronomia. Como o cache de resposta do ReportingConfiguration está ativado, e visto que o IChatClient fornecido é sempre obtido a partir do ScenarioRun criado utilizando esta configuração de relatório, a resposta LLM para o teste é armazenada em cache e reutilizada. A resposta será reutilizada até que a entrada de cache correspondente expire (em 14 dias por padrão), ou até que qualquer parâmetro de solicitação, como o ponto de extremidade LLM ou a pergunta que está sendo feita, seja alterado.
Adicione um método para validar a resposta.
/// <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); }Sugestão
Cada métrica inclui uma propriedade
Reasonque explica o raciocínio da pontuação. O motivo está incluído no relatório gerado e pode ser visualizado clicando no ícone de informações no cartão da métrica correspondente.Finalmente, adicione o método de teste em si.
[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 ensaio:
Cria o ScenarioRun. O uso de
await usinggarante que oScenarioRuné corretamente eliminado e que os resultados desta avaliação são corretamente persistidos no armazenamento de resultados.Obtém a resposta do LLM a uma pergunta específica de astronomia. O mesmo
que será usado para avaliação é passado para o método a fim de obter cache de resposta para a resposta primária do LLM que está sendo avaliada. (Além disso, isto permite o cache de respostas para as interações do LLM que os avaliadores utilizam para realizar as suas avaliações internamente.) Com o cache de resposta, a resposta do LLM é obtida de uma das seguintes formas: - Diretamente do ponto de extremidade do LLM na primeira execução do teste atual ou em execuções subsequentes, caso a entrada em cache tenha expirado (14 dias, por padrão).
- A partir do cache de resposta (baseado em disco) que foi configurado em
s_defaultReportingConfigurationdurante execuções posteriores do teste.
Executa os módulos de avaliação contra a resposta. Como a resposta LLM, em execuções subsequentes, a avaliação é obtida do cache de resposta (baseado em disco) que foi configurado no
s_defaultReportingConfiguration.Executa alguma validação básica no resultado da avaliação.
Esta etapa é opcional e principalmente para fins de demonstração. Em avaliações do mundo real, talvez você não queira validar resultados individuais, pois as respostas do LLM e as pontuações de avaliação podem mudar ao longo do tempo à medida que seu produto (e os modelos usados) evoluem. Talvez você não queira que os testes de avaliação individuais "falhem" e bloqueiem compilações em seus pipelines de CI/CD quando isso acontecer. Em vez disso, pode ser melhor confiar no relatório gerado e acompanhar as tendências gerais para pontuações de avaliação em diferentes cenários ao longo do tempo (e só falhar em compilações individuais quando há uma queda significativa nas pontuações de avaliação em vários testes diferentes). Dito isto, há algumas nuances aqui e a escolha de validar ou não resultados individuais pode variar dependendo do caso de uso específico.
Quando o método retorna, o objeto é descartado
scenarioRune o resultado da avaliação é armazenado no armazenamento de resultados (baseado em disco) configurado ems_defaultReportingConfiguration.
Executar o teste/avaliação
Execute o teste usando seu fluxo de trabalho de teste preferido, por exemplo, usando o comando dotnet test CLI ou através do Test Explorer.
Gerar um relatório
Instale a ferramenta Microsoft.Extensions.AI.Evaluation.Console .NET executando o seguinte comando a partir de uma janela do terminal:
dotnet tool install --local Microsoft.Extensions.AI.Evaluation.ConsoleSugestão
Talvez seja necessário criar um arquivo de manifesto primeiro. Para obter mais informações sobre isso e instalar ferramentas locais, consulte Ferramentas locais.
Gere um relatório executando o seguinte comando:
dotnet tool run aieval report --path <path\to\your\cache\storage> --output report.htmlAbra o ficheiro
report.html. Deveria ser algo assim.
Próximos passos
- Navegue até o diretório onde os resultados do teste são armazenados (que é
C:\TestReports, a menos que você modificou o local quando criou o ReportingConfiguration). No subdiretórioresults, observe que há uma pasta para cada execução de teste nomeada com um carimbo de data/horaExecutionName. Dentro de cada uma dessas pastas há uma pasta para cada nome de cenário — neste caso, apenas o único método de teste no projeto. Essa pasta contém um arquivo JSON com todos os dados, incluindo as mensagens, a resposta e o resultado da avaliação. - Expanda a avaliação. Aqui estão algumas ideias:
- Adicione um avaliador personalizado adicional, como um avaliador que usa IA para determinar o sistema de medição usado na resposta.
- Adicione outro método de teste, por exemplo, um método que avalia várias respostas do LLM. Como cada resposta pode ser diferente, é bom amostrar e avaliar pelo menos algumas respostas a uma pergunta. Nesse caso, você especifica um nome de iteração cada vez que chama CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken).