Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
In diesem Lernprogramm erstellen Sie eine MSTest-App, um die Chatantwort eines OpenAI-Modells auszuwerten. Die Test-App verwendet die Microsoft.Extensions.AI.Evaluation-Bibliotheken , um die Auswertungen durchzuführen, die Modellantworten zwischenzuspeichern und Berichte zu erstellen. Das Lernprogramm verwendet integrierte und benutzerdefinierte Bewerter. Die integrierten Qualitätsbewerter (aus dem Microsoft.Extensions.AI.Evaluation.Quality-Paket) verwenden ein LLM, um Auswertungen durchzuführen; der benutzerdefinierte Evaluator verwendet keine KI.
Voraussetzungen
- .NET 8 oder eine höhere Version
- Visual Studio Code (wahlweise)
Konfigurieren des KI-Diensts
Um eine Azure OpenAI Service-Instanz und ein Azure-Modell mithilfe des Azure-Portals bereitzustellen, führen Sie die Schritte im Artikel zum Erstellen und Bereitstellen einer Azure OpenAI Service-Ressource aus. Wählen Sie im Schritt "Modell bereitstellen" das gpt-4o Modell aus.
Erstellen der Test-App
Führen Sie die folgenden Schritte aus, um ein MSTest-Projekt zu erstellen, das eine Verbindung mit dem gpt-4o KI-Modell herstellt.
Navigieren Sie in einem Terminalfenster zu dem Verzeichnis, in dem Sie Ihre App erstellen möchten, und erstellen Sie eine neue MSTest-App mit dem
dotnet newBefehl:dotnet new mstest -o TestAIWithReportingNavigieren Sie zum Verzeichnis
TestAIWithReporting, und fügen Sie Ihrer App die erforderlichen Pakete hinzu: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.UserSecretsFühren Sie die folgenden Befehle aus, um App-Geheimnisse für Ihren Azure OpenAI-Endpunkt, den Modellnamen und die Mandanten-ID hinzuzufügen:
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>(Abhängig von Ihrer Umgebung könnte die Mandanten-ID möglicherweise nicht erforderlich sein. In diesem Fall ist sie aus dem Code, der das DefaultAzureCredential instanziiert, zu entfernen.)
Öffnen Sie die neue App in Ihrem Editor der Wahl.
Hinzufügen des Test-App-Codes
Benennen Sie die Datei Test1.cs in MyTests.cs um, öffnen Sie die Datei und benennen Sie die Klasse in
MyTestsum. Löschen Sie die leereTestMethod1Methode.Fügen Sie die erforderlichen
usingDirektiven am Anfang der Datei hinzu.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;Fügen Sie der Klasse die TestContext Eigenschaft hinzu.
// The value of the TestContext property is populated by MSTest. public TestContext? TestContext { get; set; }Fügen Sie die
GetAzureOpenAIChatConfigurationMethode hinzu, die die IChatClient erstellt, mit der der Evaluator mit dem Modell kommuniziert.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); }Richten Sie die Berichterstellungsfunktion ein.
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);Szenarioname
Der Szenarioname wird auf den vollqualifizierten Namen der aktuellen Testmethode festgelegt. Sie können sie jedoch auf eine beliebige Zeichenfolge Ihrer Wahl festlegen, wenn Sie aufrufen CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken). Im Folgenden finden Sie einige Überlegungen zur Auswahl eines Szenarionamens:
- Bei Verwendung des datenträgerbasierten Speichers wird der Szenarioname als Name des Ordners verwendet, unter dem die entsprechenden Auswertungsergebnisse gespeichert werden. Daher empfiehlt es sich, den Namen vernünftig kurz zu halten und alle Zeichen zu vermeiden, die in Datei- und Verzeichnisnamen nicht zulässig sind.
- Standardmäßig teilt der generierte Auswertungsbericht Szenarionamen auf
., sodass die Ergebnisse in einer hierarchischen Ansicht mit entsprechender Gruppierung, Schachtelung und Aggregation angezeigt werden können. Dies ist besonders nützlich in Fällen, in denen der Szenarioname auf den vollqualifizierten Namen der entsprechenden Testmethode festgelegt ist, da die Ergebnisse nach Namespaces und Klassennamen in der Hierarchie gruppiert werden können. Sie können diese Funktion jedoch auch nutzen, indem Sie Punkte (.) in Ihre benutzerdefinierten Szenarionamen einschließen, um eine Berichtshierarchie zu erstellen, die für Ihre Szenarien optimal geeignet ist.
Ausführungsname
Der Ausführungsname wird verwendet, um Auswertungsergebnisse zu gruppieren, die Teil derselben Auswertungsausführung (oder Testausführung) sind, wenn die Auswertungsergebnisse gespeichert werden. Wenn Sie beim Erstellen einer ReportingConfigurationAusführung keinen Ausführungsnamen angeben, verwenden alle Auswertungsläufe denselben Standardausführungsnamen von
Default. In diesem Fall werden Ergebnisse aus einem Durchlauf vom nächsten überschrieben, und Sie verlieren die Möglichkeit, die Ergebnisse über verschiedene Durchläufe hinweg zu vergleichen.In diesem Beispiel wird ein Zeitstempel als Ausführungsname verwendet. Wenn Sie mehr als einen Test in Ihrem Projekt haben, stellen Sie sicher, dass die Ergebnisse ordnungsgemäß gruppiert werden, indem Sie denselben Ausführungsnamen in allen Berichtskonfigurationen verwenden, die für alle Tests verwendet werden.
In einem realen Szenario möchten Sie möglicherweise auch denselben Ausführungsnamen für Auswertungstests freigeben, die sich in mehreren verschiedenen Assemblys befinden und in verschiedenen Testprozessen ausgeführt werden. In solchen Fällen können Sie ein Skript verwenden, um eine Umgebungsvariable mit einem geeigneten Ausführungsnamen (z. B. die aktuelle Buildnummer, die Ihrem CI/CD-System zugewiesen wurde) zu aktualisieren, bevor Sie die Tests ausführen. Oder wenn Ihr Buildsystem monoton zunehmende Assemblydateiversionen erzeugt, können Sie den AssemblyFileVersionAttribute Code aus dem Testcode lesen und diesen als Ausführungsnamen verwenden, um Ergebnisse in verschiedenen Produktversionen zu vergleichen.
Berichterstellungskonfiguration
Ein ReportingConfiguration identifiziert:
- Die Gruppe von Evaluatoren, die für jedes ScenarioRun aufgerufen werden sollen, das durch das Aufrufen von CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken) erstellt wird.
- Der LLM-Endpunkt, den die Evaluatoren verwenden sollten (siehe ReportingConfiguration.ChatConfiguration).
- Wie und wo die Ergebnisse der Szenariodurchläufe gespeichert werden sollen.
- LLM-Antworten, die sich auf Szenarioabläufe beziehen, sollten zwischengespeichert werden.
- Der Ausführungsname, der beim Melden von Ergebnissen für die Szenarioläufe verwendet werden soll.
Dieser Test verwendet eine datenträgerbasierte Berichterstellungskonfiguration.
Fügen Sie in einer separaten Datei die Klasse
WordCountEvaluatorhinzu, die einen benutzerdefinierten Evaluator darstellt und IEvaluator implementiert.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)); } }Die
WordCountEvaluatorzählt die Anzahl der Wörter in der Antwort. Im Gegensatz zu einigen Bewertungen basiert sie nicht auf KI. Die MethodeEvaluateAsyncgibt ein EvaluationResult zurück, das ein NumericMetric enthält, welches die Wortanzahl beinhaltet.Die
EvaluateAsyncMethode fügt auch eine Standardinterpretation an die Metrik an. Die Standardinterpretation betrachtet die Metrik als gut (akzeptabel), wenn die erkannte Wortanzahl zwischen 6 und 100 liegt. Andernfalls wird die Metrik als fehlgeschlagen betrachtet. Diese Standardinterpretation kann bei Bedarf vom Aufrufer überschrieben werden.MyTests.csFügen Sie wieder eine Methode hinzu, um die in der Auswertung zu verwendenden Evaluatoren zu sammeln.private static IEnumerable<IEvaluator> GetEvaluators() { IEvaluator relevanceEvaluator = new RelevanceEvaluator(); IEvaluator coherenceEvaluator = new CoherenceEvaluator(); IEvaluator wordCountEvaluator = new WordCountEvaluator(); return [relevanceEvaluator, coherenceEvaluator, wordCountEvaluator]; }Fügen Sie eine Methode hinzu, um eine Systemaufforderung ChatMessagehinzuzufügen, die Chatoptionen zu definieren und das Modell zur Antwort auf eine bestimmte Frage zu stellen.
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); }Der Test in diesem Tutorial bewertet, wie ein Large Language Model (LLM) auf eine Astronomiefrage antwortet. Da das Response-Caching bei der ReportingConfiguration aktiviert ist und das bereitgestellte IChatClient immer aus dem ScenarioRun abgerufen wird, die mit dieser Berichterstellungskonfiguration erstellt wurde, wird die LLM-Antwort für den Test zwischengespeichert und wiederverwendet. Die Antwort wird wiederverwendet, bis der entsprechende Cacheeintrag abläuft (standardmäßig in 14 Tagen), oder bis ein Anforderungsparameter, z. B. der LLM-Endpunkt oder die Frage, die gestellt wird, geändert wird.
Fügen Sie eine Methode zum Überprüfen der Antwort hinzu.
/// <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); }Tipp
Die Metriken enthalten jeweils eine
ReasonEigenschaft, die die Begründung für die Bewertung erläutert. Der Grund ist im generierten Bericht enthalten und kann durch Klicken auf das Informationssymbol auf der Karte der entsprechenden Metrik angezeigt werden.Fügen Sie schließlich die Testmethode selbst hinzu.
[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); }Diese Testmethode:
Erstellt das ScenarioRun. Die Verwendung von
await usingstellt sicher, dass dasScenarioRunordnungsgemäß entsorgt wird und dass die Ergebnisse dieser Auswertung korrekt und persistent im Ergebnisspeicher vorliegen.Ruft die Antwort des LLM auf eine bestimmte Astronomiefrage ab. Dasselbe IChatClient , das für die Auswertung verwendet wird, wird an die
GetAstronomyConversationAsyncMethode übergeben, um das Zwischenspeichern der Antwort für die primäre LLM-Antwort zu erhalten, die ausgewertet wird. Darüber hinaus ermöglicht das Zwischenspeichern von Antworten für die LLM-Durchläufe den Evaluatoren, ihre Auswertungen intern durchzuführen. Bei der Zwischenspeicherung wird die LLM-Antwort entweder abgerufen:- Direkt vom LLM-Endpunkt in der ersten Ausführung des aktuellen Tests oder in nachfolgenden Ausführungen, wenn der zwischengespeicherte Eintrag abgelaufen ist (standardmäßig 14 Tage).
- Aus dem (datenträgerbasierten) Antwortcache, der in
s_defaultReportingConfigurationin nachfolgenden Läufen des Tests konfiguriert wurde.
Führt die Evaluatoren gegen die Antwort aus. Wie die LLM-Antwort wird die Auswertung bei nachfolgenden Ausführungen aus dem (datenträgerbasierten) Antwortcache abgerufen, der in
s_defaultReportingConfigurationkonfiguriert wurde.Führt eine grundlegende Überprüfung des Auswertungsergebnisses aus.
Dieser Schritt ist optional und hauptsächlich zu Demonstrationszwecken. In realen Auswertungen möchten Sie möglicherweise nicht einzelne Ergebnisse überprüfen, da sich die LLM-Antworten und Bewertungsergebnisse im Laufe der Zeit ändern können, während sich Ihr Produkt (und die verwendeten Modelle) weiterentwickeln. Möglicherweise möchten Sie nicht, dass einzelne Auswertungstests fehlschlagen und Builds in Ihren CI/CD-Pipelines blockieren, wenn dies geschieht. Stattdessen ist es möglicherweise besser, sich auf den generierten Bericht zu verlassen und die allgemeinen Trends für Bewertungsergebnisse in verschiedenen Szenarien im Laufe der Zeit nachzuverfolgen (und nur einzelne Builds fehlschlagen, wenn es einen signifikanten Rückgang der Bewertungsergebnisse in mehreren verschiedenen Tests gibt). Das heißt, es gibt hier einige Nuancen und die Wahl, ob einzelne Ergebnisse überprüft werden sollen oder nicht je nach spezifischem Anwendungsfall variieren können.
Wenn die Methode zurückkehrt, wird das
scenarioRun-Objekt verworfen, und das Auswertungsergebnis wird im datenträgerbasierten Ergebnisspeicher gespeichert, der ins_defaultReportingConfigurationkonfiguriert ist.
Ausführen des Tests/der Auswertung
Führen Sie den Test mithilfe Ihres bevorzugten Testworkflows aus, z. B. mithilfe des CLI-Befehls dotnet test oder über Test-Explorer.
Generieren eines Berichts
Installieren Sie das Microsoft.Extensions.AI.Evaluation.Console .NET-Tool, indem Sie den folgenden Befehl in einem Terminalfenster ausführen:
dotnet tool install --local Microsoft.Extensions.AI.Evaluation.ConsoleTipp
Möglicherweise müssen Sie zuerst eine Manifestdatei erstellen. Weitere Informationen dazu und die Installation lokaler Tools finden Sie unter "Lokale Tools".
Generieren Sie einen Bericht, indem Sie den folgenden Befehl ausführen:
dotnet tool run aieval report --path <path\to\your\cache\storage> --output report.htmlÖffnen Sie die Datei
report.html. Es sollte ungefähr so aussehen.
Nächste Schritte
- Navigieren Sie zu dem Verzeichnis, in dem die Testergebnisse gespeichert sind (sofern Sie den Speicherort nicht geändert haben, als Sie ReportingConfiguration erstellt haben). Beachten Sie im
resultsUnterverzeichnis, dass für jede Testausführung ein Ordner mit einem Zeitstempel (ExecutionName) vorhanden ist. In jedem dieser Ordner handelt es sich um einen Ordner für jeden Szenarionamen – in diesem Fall nur die einzelne Testmethode im Projekt. Dieser Ordner enthält eine JSON-Datei mit allen Daten, einschließlich nachrichten, Antwort und Auswertungsergebnis. - Erweitern Sie die Auswertung. Hier sind ein paar Ideen:
- Fügen Sie einen zusätzlichen benutzerdefinierten Bewerter hinzu, z. B. einen Evaluator, der KI verwendet, um das In der Antwort verwendete Messsystem zu bestimmen .
- Fügen Sie eine weitere Testmethode hinzu, z. B. eine Methode, die mehrere Antworten von der LLM auswertet. Da jede Antwort unterschiedlich sein kann, ist es ratsam, mindestens einige Antworten auf eine Frage zu testen und auszuwerten. In diesem Fall geben Sie bei jedem Aufruf CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken)einen Iterationsnamen an.