Megosztás:


Oktatóanyag: Válaszminőség kiértékelése gyorsítótárazással és jelentéskészítéssel

Ebben az oktatóanyagban egy MSTest-alkalmazást hoz létre egy OpenAI-modell csevegési válaszának kiértékeléséhez. A tesztalkalmazás a Microsoft.Extensions.AI.Evaluation kódtárakat használja az értékelések végrehajtásához, a modellválaszok gyorsítótárazásához és jelentések létrehozásához. Az oktatóanyag beépített és egyéni kiértékelőket is használ. A beépített minőségi kiértékelők (a Microsoft.Extensions.AI.Evaluation.Quality csomagból) LLM-et használnak az értékelések elvégzéséhez; az egyéni kiértékelő nem használ AI-t.

Előfeltételek

Az AI-szolgáltatás konfigurálása

Ha Azure OpenAI-szolgáltatást és modellt szeretne kiépíteni az Azure Portalon, végezze el az Azure OpenAI-szolgáltatás erőforrásának létrehozása és üzembe helyezése cikkben leírt lépéseket. A "Modell üzembe helyezése" lépésben válassza ki a gpt-4o modellt.

A tesztalkalmazás létrehozása

Hajtsa végre az alábbi lépéseket az AI-modellhez gpt-4o csatlakozó MSTest-projekt létrehozásához.

  1. Egy terminálablakban lépjen arra a könyvtárra, ahol létre szeretné hozni az alkalmazást, és hozzon létre egy új MSTest-alkalmazást a dotnet new következő paranccsal:

    dotnet new mstest -o TestAIWithReporting
    
  2. Lépjen a TestAIWithReporting könyvtárra, és adja hozzá a szükséges csomagokat az alkalmazáshoz:

    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. Futtassa a következő parancsokat alkalmazás titkos kulcsainak hozzáadásához az Azure OpenAI-végponthoz, a modellnévhez és a bérlőazonosítóhoz:

    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>
    

    (A környezettől függően előfordulhat, hogy a bérlőazonosítóra nincs szükség. Ebben az esetben távolítsa el a DefaultAzureCredential példányt a jelölő kódból.)

  4. Nyissa meg az új alkalmazást a választott szerkesztőben.

A tesztalkalmazás kódjának hozzáadása

  1. Nevezze át a Test1.cs fájlt MyTests.cs, majd nyissa meg a fájlt, és nevezze át az osztályt a következőreMyTests: . Törölje az üres TestMethod1 metódust.

  2. Adja hozzá a szükséges using irányelveket a fájl elejéhez.

    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. Adja hozzá a TestContext tulajdonságot az osztályhoz.

    // The value of the TestContext property is populated by MSTest.
    public TestContext? TestContext { get; set; }
    
  4. Adja hozzá a GetAzureOpenAIChatConfiguration metódust, amely létrehozza azt, IChatClient amelyet az értékelő a modellel való kommunikációhoz használ.

    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. A jelentéskészítési funkció beállítása.

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

    Forgatókönyv neve

    A forgatókönyv neve az aktuális tesztmetódus teljes neve. A CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken) hívásakor azonban bármilyen tetszőleges sztringet megadhat. Íme néhány szempont a forgatókönyv nevének kiválasztásához:

    • Lemezalapú tároló használata esetén a forgatókönyv neve annak a mappának a neve, amelyben a megfelelő kiértékelési eredményeket tárolja. Ezért érdemes a nevet viszonylag röviden tartani, és elkerülni a fájl- és könyvtárnevekben nem engedélyezett karaktereket.
    • Alapértelmezés szerint a létrehozott kiértékelési jelentés felosztja a forgatókönyvek nevét . , hogy az eredmények hierarchikus nézetben, megfelelő csoportosítással, beágyazással és összesítéssel jelenjenek meg. Ez különösen akkor hasznos, ha a forgatókönyv neve a megfelelő tesztmetódus teljes neve, mivel lehetővé teszi az eredmények névterek és osztálynevek szerinti csoportosítását a hierarchiában. Ezt a funkciót azonban kihasználhatja úgy is, hogy pontokat (.) a saját egyéni forgatókönyvek neveibe is beiktatva létrehoz egy olyan jelentési hierarchiát, amely a legjobban megfelel a forgatókönyveinek.

    Végrehajtási név

    A végrehajtási név arra szolgál, hogy csoportosítsa a kiértékelési eredményeket, amelyek ugyanahhoz a próbafuttatáshoz (vagy tesztfuttatáshoz) tartoznak a kiértékelési eredmények tárolásakor. Ha nem ad meg végrehajtási nevet a ReportingConfigurationlétrehozáskor, az összes kiértékelési futtatás ugyanazt az alapértelmezett végrehajtási nevet Defaultfogja használni. Ebben az esetben az egyik futtatás eredményeit a következő felülírja, és elveszíti a különböző futtatások eredményeinek összehasonlítási képességét.

    Ez a példa egy időbélyeget használ a végrehajtás neveként. Ha egynél több teszt van a projektben, győződjön meg arról, hogy az eredmények megfelelően vannak csoportosítva ugyanazzal a végrehajtási névvel a tesztek során használt összes jelentéskonfigurációban.

    Egy valósabb forgatókönyvben érdemes lehet ugyanazt a végrehajtási nevet megosztani a különböző szerelvényekben található és különböző tesztfolyamatokban végzett értékelési tesztek között. Ilyen esetekben szkripttel frissíthet egy környezeti változót a megfelelő végrehajtási névvel (például a CI/CD-rendszer által hozzárendelt aktuális buildszámmal), mielőtt futtatná a teszteket. Vagy ha a buildelési rendszer monoton módon növekvő szerelvényfájl-verziókat állít elő, a tesztkódból elolvashatja a AssemblyFileVersionAttribute forrást, és végrehajtási névként használhatja az eredményeket a különböző termékverziók eredményeinek összehasonlításához.

    Jelentéskészítési konfiguráció

    A ReportingConfiguration azonosít:

    Ez a teszt lemezalapú jelentéskészítési konfigurációt használ.

  6. Egy külön fájlban adja hozzá az WordCountEvaluator osztályt, amely egy egyéni kiértékelő, és a(z) IEvaluator-t implementálja.

    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 WordCountEvaluator válaszban szereplő szavak számát számítja ki. Egyes kiértékelőktől eltérően ez nem AI-n alapul. A EvaluateAsync metódus egy EvaluationResult-t ad vissza, ami tartalmazza a szavak számát.

    A EvaluateAsync metódus egy alapértelmezett értelmezést is csatol a metrikához. Az alapértelmezett értelmezés jónak (elfogadhatónak) tekinti a metrikát, ha az észlelt szószám 6 és 100 között van. Ellenkező esetben a metrika sikertelennek minősül. Ezt az alapértelmezett értelmezést a hívó felül tudja bírálni, ha szükséges.

  7. A MyTests.cs helyen adjon hozzá egy metódust, amely összegyűjti a kiértékelőket, hogy felhasználásra kerüljenek az értékelés során.

    private static IEnumerable<IEvaluator> GetEvaluators()
    {
        IEvaluator relevanceEvaluator = new RelevanceEvaluator();
        IEvaluator coherenceEvaluator = new CoherenceEvaluator();
        IEvaluator wordCountEvaluator = new WordCountEvaluator();
    
        return [relevanceEvaluator, coherenceEvaluator, wordCountEvaluator];
    }
    
  8. Adjon hozzá egy metódust egy rendszerkérés ChatMessagehozzáadásához, határozza meg a csevegési beállításokat, és kérjen választ a modelltől egy adott kérdésre.

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

    Az oktatóanyagban szereplő teszt kiértékeli az LLM válaszát egy csillagászati kérdésre. Mivel a ReportingConfiguration válasz gyorsítótárazása engedélyezve van, és mivel a megadott IChatClient adatokat a rendszer mindig ezzel a ScenarioRun jelentéskészítési konfigurációval hozza létre, a rendszer gyorsítótárazza és újra felhasználja a teszt LLM-válaszát. A válasz a megfelelő gyorsítótár-bejegyzés lejáratáig (alapértelmezés szerint 14 napon belül) vagy addig lesz újra felhasználva, amíg bármilyen kérelemparaméter , például az LLM-végpont vagy a feltett kérdés nem módosul.

  9. Adjon hozzá egy metódust a válasz ellenőrzéséhez.

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

    Jótanács

    A metrikák mindegyike tartalmaz egy tulajdonságot Reason , amely elmagyarázza a pontszám indoklását. Ennek oka szerepel a létrehozott jelentésben , és a megfelelő metrika kártyáján az információ ikonra kattintva tekinthető meg.

  10. Végül adja hozzá magát a vizsgálati módszert .

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

    Ez a vizsgálati módszer:

    • Létrehozza a ScenarioRun. A await using használata biztosítja, hogy a ScenarioRun megfelelő módon el legyen távolítva, és hogy a kiértékelés eredményeit helyesen tárolják az eredménytárolóban.

    • Megkapja az LLM válaszát egy adott csillagászati kérdésre. A rendszer ugyanazt IChatClient adja át a metódusnak, amelyet a GetAstronomyConversationAsync kiértékelésre használt elsődleges LLM-válasz válasz gyorsítótárazásának lekéréséhez használ. (Emellett lehetővé teszi az értékelők által a belső értékelések során használt LLM-válaszok gyorsítótárazását.) Ezzel a gyorsítótárazással az LLM-válaszok a következő módokon lesznek lekérve:

      • Közvetlenül az LLM-végpontról az aktuális teszt első futtatásakor, vagy az azt követő futtatások során, ha a gyorsítótárazott bejegyzés lejárt (alapértelmezés szerint 14 nap).
      • A teszt későbbi futtatásaiban s_defaultReportingConfiguration konfigurált (lemezalapú) válaszgyorsítótárból.
    • A kiértékelőket futtatja a válasz ellenőrzéséhez. Az LLM-válaszhoz hasonlóan az azt követő futtatások során a kiértékelést a s_defaultReportingConfiguration-ben konfigurált (lemezalapú) válaszgyorsítótárból kérik le.

    • Elvégez néhány alapvető érvényesítést a kiértékelési eredményen.

      Ez a lépés nem kötelező, és elsősorban bemutató célokra szolgál. A valós értékelésekben előfordulhat, hogy nem szeretné ellenőrizni az egyes eredményeket, mivel az LLM-válaszok és az értékelési pontszámok idővel változhatnak a termék (és a használt modellek) fejlődésével. Előfordulhat, hogy nem szeretné, ha az értékelő tesztek "sikertelenek" lennének, és blokkolnák a build folyamatokat a CI/CD pipeline-okban, amikor ez történik. Ehelyett lehet, hogy célszerűbb lenne a létrehozott jelentésre támaszkodni, és az értékelési pontszámok általános trendjeit nyomon követni különböző forgatókönyvek esetén, hosszabb időszakon át (és csak akkor legyen sikertelen egyes build-ek futtatása, amikor több különböző teszt során jelentős csökkenés tapasztalható az értékelési pontszámokban). Mindazonáltal van némi árnyalat ebben, és az a választás, hogy ellenőrizzük-e az egyes eredményeket vagy sem, a konkrét használati esettől függően változhat.

    Amikor a metódus visszatér, az scenarioRun objektum el lesz helyezve, és a kiértékelési eredmény a (lemezalapú) eredménytárolóban lesz tárolva, amely a következőben s_defaultReportingConfigurationvan konfigurálva: .

A teszt/kiértékelés futtatása

Futtassa a tesztet az előnyben részesített tesztelési munkafolyamattal, például a CLI paranccsal dotnet test vagy a Test Explorer használatával.

Jelentés létrehozása

  1. Telepítse a Microsoft.Extensions.AI.Evaluation.Console .NET eszközt a következő parancs terminálablakból való futtatásával:

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

    Jótanács

    Előfordulhat, hogy először létre kell hoznia egy jegyzékfájlt. Erről és a helyi eszközök telepítéséről további információt a Helyi eszközök című témakörben talál.

  2. Hozzon létre egy jelentést a következő parancs futtatásával:

    dotnet tool run aieval report --path <path\to\your\cache\storage> --output report.html
    
  3. Nyissa meg a(z) report.html fájlt. Valahogy így kell kinéznie.

    A beszélgetés és a metrikaértékeket megjelenítő kiértékelési jelentés képernyőképe.

Következő lépések