Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W tym samouczku utworzysz aplikację MSTest, aby ocenić odpowiedź na czat modelu OpenAI. Aplikacja testowa używa bibliotek Microsoft.Extensions.AI.Evaluation do przeprowadzania ocen, buforowania odpowiedzi modelu i tworzenia raportów. W tym samouczku są używane zarówno wbudowane, jak i niestandardowe ewaluatory. Wbudowane ewaluatory jakości (z pakietu Microsoft.Extensions.AI.Evaluation.Quality) używają programu LLM do przeprowadzania ocen; niestandardowy ewaluator nie używa sztucznej inteligencji.
Wymagania wstępne
- .NET 8 lub nowsza wersja
- Visual Studio Code (opcjonalnie)
Konfigurowanie usługi sztucznej inteligencji
Aby aprowizować usługę Azure OpenAI i model przy użyciu witryny Azure Portal, wykonaj kroki opisane w artykule Tworzenie i wdrażanie zasobu usługi Azure OpenAI Service. W kroku "Wdrażanie modelu" wybierz gpt-4o model.
Tworzenie aplikacji testowej
Wykonaj poniższe kroki, aby utworzyć projekt MSTest łączący się z modelem gpt-4o AI.
W oknie terminalu przejdź do katalogu, w którym chcesz utworzyć aplikację, i utwórz nową aplikację MSTest za
dotnet newpomocą polecenia :dotnet new mstest -o TestAIWithReportingPrzejdź do katalogu
TestAIWithReportingi dodaj niezbędne pakiety do aplikacji: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.UserSecretsUruchom następujące polecenia, aby dodać sekrety aplikacji dla punktu końcowego usługi Azure OpenAI, nazwy modelu i identyfikatora dzierżawy:
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>(W zależności od środowiska, identyfikator dzierżawy może nie być potrzebny. W takim przypadku usuń go z kodu, który tworzy instancję DefaultAzureCredential.)
Otwórz nową aplikację w wybranym edytorze.
Dodawanie kodu aplikacji testowej
Zmień nazwę pliku Test1.cs na MyTests.cs, a następnie otwórz plik i zmień nazwę klasy na
MyTests. Usuń pustąTestMethod1metodę.Dodaj niezbędne
usingdyrektywy na początku pliku.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;TestContext Dodaj właściwość do klasy.
// The value of the TestContext property is populated by MSTest. public TestContext? TestContext { get; set; }Dodaj metodę
GetAzureOpenAIChatConfiguration, która tworzy IChatClient element używany przez ewaluatora do komunikowania się z 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); }Konfigurowanie funkcji raportowania.
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);Nazwa scenariusza
Nazwa scenariusza jest ustawiona na w pełni kwalifikowaną nazwę bieżącej metody testowej. Można jednak ustawić go na dowolny wybrany ciąg podczas wywoływania metody CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken). Poniżej przedstawiono kilka zagadnień dotyczących wybierania nazwy scenariusza:
- W przypadku korzystania z magazynu opartego na dyskach nazwa scenariusza jest używana jako nazwa folderu, w którym są przechowywane odpowiednie wyniki oceny. Warto więc nadać nazwie rozsądną długość i unikać znaków, które nie są dozwolone w nazwach plików i katalogów.
- Domyślnie wygenerowany raport oceny dzieli nazwy scenariuszy na znaku
., tak aby wyniki mogły być wyświetlane w widoku hierarchicznym z odpowiednim grupowaniem, zagnieżdżaniem i agregacją. Jest to szczególnie przydatne w przypadkach, gdy nazwa scenariusza jest ustawiona na w pełni kwalifikowaną nazwę odpowiedniej metody testowej, ponieważ umożliwia grupowanie wyników według przestrzeni nazw i nazw klas w hierarchii. Można jednak również skorzystać z tej funkcji, uwzględniając kropki (.) we własnych niestandardowych nazwach scenariuszy, aby utworzyć hierarchię raportowania, która najlepiej sprawdza się w przypadku Twoich scenariuszy.
Nazwa wykonania
Nazwa wykonania służy do grupowania wyników oceny, które są częścią tego samego przebiegu oceny (lub przebiegu testu), gdy są przechowywane wyniki oceny. Jeśli nie podasz nazwy wykonania podczas tworzenia ReportingConfiguration, wszystkie przebiegi ewaluacyjne będą używać tej samej domyślnej nazwy wykonania
Default. W takim przypadku wyniki z jednego przebiegu zostaną zastąpione przez następne i utracisz możliwość porównywania wyników między różnymi przebiegami.W tym przykładzie użyto znacznika czasu jako nazwy wykonania. Jeśli w projekcie istnieje więcej niż jeden test, upewnij się, że wyniki są prawidłowo pogrupowane przy użyciu tej samej nazwy wykonania we wszystkich konfiguracjach raportowania używanych w testach.
W bardziej rzeczywistym scenariuszu możesz również udostępnić tę samą nazwę wykonania w testach ewaluacyjnych, które są rozlokowane w wielu różnych zestawach i uruchamiane w różnych procesach testowych. W takich przypadkach możesz użyć skryptu, aby zaktualizować zmienną środowiskową, dodając odpowiednią nazwę wykonania (na przykład aktualny numer kompilacji przydzielony przez system CI/CD) przed uruchomieniem testów. Jeśli system kompilacji generuje monotonicznie rosnące wersje plików zestawu, możesz odczytać element AssemblyFileVersionAttribute z kodu testowego i użyć go jako nazwy wykonywania, aby porównać wyniki między różnymi wersjami produktów.
Konfiguracja raportowania
A ReportingConfiguration identyfikuje:
- Zestaw ewaluatorów, które należy wywołać dla każdego ScenarioRun, utworzonego przez wywołanie metody CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken).
- Punkt końcowy LLM, którego powinni używać ewaluatorzy (zobacz ReportingConfiguration.ChatConfiguration).
- Sposób i miejsce przechowywania wyników uruchomień scenariuszy.
- Sposób buforowania odpowiedzi LLM związanych z realizacją scenariuszy.
- Nazwa wykonania, która powinna być używana podczas raportowania wyników uruchomienia scenariuszy.
W tym teście jest używana konfiguracja raportowania oparta na dyskach.
W osobnym pliku dodaj klasę
WordCountEvaluator, która jest niestandardowym ewaluatorem, który implementuje IEvaluator element.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)); } }WordCountEvaluatorliczy liczbę wyrazów znajdujących się w odpowiedzi. W przeciwieństwie do niektórych ewaluatorów, nie jest on oparty na sztucznej inteligencji. MetodaEvaluateAsynczwraca element EvaluationResult, który obejmuje NumericMetric zawierający liczbę wyrazów.Metoda
EvaluateAsyncdołącza również domyślną interpretację do metryki. Domyślna interpretacja uważa, że metryka jest dobra (akceptowalna), jeśli wykryta liczba słów wynosi od 6 do 100. W przeciwnym razie metryka jest uważana za nieudaną. Ta domyślna interpretacja może zostać zastąpiona przez obiekt wywołujący w razie potrzeby.Wróć do
MyTests.cs, dodaj metodę, aby zebrać ewaluatorów do wykorzystania w ewaluacji.private static IEnumerable<IEvaluator> GetEvaluators() { IEvaluator relevanceEvaluator = new RelevanceEvaluator(); IEvaluator coherenceEvaluator = new CoherenceEvaluator(); IEvaluator wordCountEvaluator = new WordCountEvaluator(); return [relevanceEvaluator, coherenceEvaluator, wordCountEvaluator]; }Dodaj metodę, aby dodać monit ChatMessagesystemowy , zdefiniować opcje czatu i poprosić model o odpowiedź na podane pytanie.
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 w tym samouczku ocenia odpowiedź LLM na pytanie astronomiczne. Ponieważ ReportingConfiguration ma włączone buforowanie odpowiedzi, a dostarczone IChatClient jest zawsze pobierane z konfiguracji raportowania utworzonej za pomocą tego ScenarioRun, odpowiedź LLM dla testu jest buforowana i ponownie wykorzystywana. Odpowiedź zostanie ponownie użyta do momentu wygaśnięcia odpowiedniego wpisu pamięci podręcznej (domyślnie w ciągu 14 dni) lub do momentu zmiany dowolnego parametru żądania, takiego jak punkt końcowy LLM lub pytanie, które jest zadawane.
Dodaj metodę w celu zweryfikowania odpowiedzi.
/// <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); }Wskazówka
Metryki zawierają
Reasonwłaściwość, która wyjaśnia przyczynę wyniku. Przyczyna jest uwzględniona w wygenerowanym raporcie i można je wyświetlić, klikając ikonę informacji na karcie odpowiedniej metryki.Na koniec dodaj samą metodę testową .
[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); }Ta metoda testowa:
Tworzy element ScenarioRun. Użycie
await usinggwarantuje, żeScenarioRunelement jest poprawnie usunięty i że wyniki tej oceny są poprawnie utrwalane w magazynie wyników.Pobiera odpowiedź LLM na konkretne pytanie astronomiczne. Te same IChatClient, które będzie wykorzystywane do oceny, jest przekazywane do metody
GetAstronomyConversationAsync, aby uzyskać buforowanie odpowiedzi dla głównej ocenianej odpowiedzi LLM. (Ponadto umożliwia to buforowanie odpowiedzi dla kolejek LLM, które ewaluatorzy wykorzystują do przeprowadzania ocen wewnętrznie). W przypadku buforowania odpowiedzi odpowiedź LLM jest pobierana:- Bezpośrednio z punktu końcowego LLM w pierwszym przebiegu bieżącego testu lub w kolejnych uruchomieniach, jeśli wpis w pamięci podręcznej wygasł (domyślnie 14 dni).
- Z pamięci podręcznej odpowiedzi opartej na dysku, która została skonfigurowana w
s_defaultReportingConfigurationpodczas kolejnych przebiegów testu.
Uruchamia ewaluatorów przed odpowiedzią. Podobnie jak w przypadku odpowiedzi LLM, w kolejnych uruchomieniach ocena jest pobierana z pamięci podręcznej odpowiedzi (opartej na dysku), która została skonfigurowana w programie
s_defaultReportingConfiguration.Uruchamia podstawową walidację wyniku oceny.
Ten krok jest opcjonalny i przeznaczony głównie do celów demonstracyjnych. W rzeczywistych ocenach możesz nie chcieć zweryfikować poszczególnych wyników, ponieważ odpowiedzi i oceny LLM mogą ulec zmianie w miarę rozwoju produktu (i używanych modeli). Możesz nie chcieć, aby indywidualne testy ewaluacyjne „zawiodły” i blokowały kompilacje w twoich potokach CI/CD, gdy tak się stanie. Zamiast tego lepiej jest polegać na wygenerowanym raporcie i śledzić ogólne trendy wyników oceny w czasie w różnych scenariuszach (i należy rozważyć wstrzymanie pojedynczych kompilacji tylko wtedy, gdy nastąpi znaczny spadek wyników oceny w wielu różnych testach). Oznacza to, że istnieje tu pewne niuanse i wybór, czy należy zweryfikować poszczególne wyniki, czy nie może się różnić w zależności od konkretnego przypadku użycia.
Gdy metoda zostanie zakończona,
scenarioRunobiekt zostanie usunięty, a wynik oceny zapisany w (opartym na dysku) magazynie wyników skonfigurowanym ws_defaultReportingConfiguration.
Uruchamianie testu/oceny
Uruchom test, korzystając z preferowanego przepływu pracy testu, na przykład za pomocą polecenia wiersza polecenia dotnet test lub za pomocą Eksploratora Testów.
Generowanie raportu
Zainstaluj narzędzie Microsoft.Extensions.AI.Evaluation.Console .NET, uruchamiając następujące polecenie w oknie terminalu:
dotnet tool install --local Microsoft.Extensions.AI.Evaluation.ConsoleWskazówka
Może być konieczne najpierw utworzenie pliku manifestu. Aby uzyskać więcej informacji na temat tego i instalowania narzędzi lokalnych, zobacz Narzędzia lokalne.
Wygeneruj raport, uruchamiając następujące polecenie:
dotnet tool run aieval report --path <path\to\your\cache\storage> --output report.htmlOtwórz plik
report.html. Powinno to wyglądać mniej więcej tak.
Dalsze kroki
- Przejdź do katalogu, w którym są przechowywane wyniki testu (czyli
C:\TestReports, chyba że lokalizacja została zmodyfikowana podczas tworzenia pliku ReportingConfiguration). W podkataloguresultszwróć uwagę, że istnieje folder dla każdego przebiegu testu o nazwie z sygnaturą czasową (ExecutionName). Wewnątrz każdego z tych folderów znajduje się folder dla każdej nazwy scenariusza — w tym przypadku tylko jedna metoda testowa w projekcie. Ten folder zawiera plik JSON zawierający wszystkie dane, w tym komunikaty, odpowiedź i wynik oceny. - Rozwiń ocenę. Oto kilka pomysłów:
- Dodaj dodatkowy ewaluator niestandardowy, taki jak ewaluator, który używa sztucznej inteligencji do określenia systemu pomiaru używanego w odpowiedzi.
- Dodaj kolejną metodę testową, na przykład metodę, która ocenia wiele odpowiedzi z usługi LLM. Ponieważ każda odpowiedź może być inna, dobrze jest próbkować i oceniać co najmniej kilka odpowiedzi na pytanie. W takim przypadku należy określić nazwę iteracji za każdym razem, gdy wywołasz metodę CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken).