이 자습서에서는 OpenAI 모델의 채팅 응답을 평가하는 MSTest 앱을 만듭니다. 테스트 앱은 Microsoft.Extensions.AI.Evaluation 라이브러리를 사용하여 평가를 수행하고, 모델 응답을 캐시하고, 보고서를 만듭니다. 이 자습서에서는 기본 제공 및 사용자 지정 평가기를 모두 사용합니다. Microsoft.Extensions.AI.Evaluation.Quality 패키지의 기본 제공 품질 평가자는 LLM을 사용하여 평가를 수행합니다. 사용자 지정 평가기는 AI를 사용하지 않습니다.
필수 조건
- .NET 8 이상 버전
- Visual Studio Code (선택 사항)
AI 서비스 구성
Azure Portal을 사용하여 Azure OpenAI 서비스 및 모델을 프로비전하려면 Azure OpenAI 서비스 리소스 만들기 및 배포 문서의 단계를 완료합니다. "모델 배포" 단계에서 gpt-4o 모델을 선택합니다.
테스트 앱 만들기
다음 단계를 완료하여 AI 모델에 연결하는 MSTest 프로젝트를 만듭니다 gpt-4o .
터미널 창에서 앱을 만들 디렉터리로 이동하고 다음 명령을 사용하여 새 MSTest 앱을
dotnet new만듭니다.dotnet new mstest -o TestAIWithReportingTestAIWithReporting디렉터리로 이동하고 필요한 패키지를 앱에 추가합니다.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다음 명령을 실행하여 Azure OpenAI 엔드포인트, 모델 이름 및 테넌트 ID에 대한 앱 비밀 추가합니다.
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>(환경에 따라 테넌트 ID가 필요하지 않을 수 있습니다. 이 경우 DefaultAzureCredential를 인스턴스화하는 코드에서 제거하십시오.)
선택한 편집기에서 새 앱을 엽니다.
테스트 앱 코드 추가
Test1.cs 파일의 이름을 MyTests.cs 이름을 바꾼 다음 파일을 열고 클래스
MyTests이름을 .로 바꿉니다. 빈TestMethod1메서드를 삭제합니다.파일 맨 위에 필요한
using지시문을 추가합니다.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 속성을 추가합니다.
// The value of the TestContext property is populated by MSTest. public TestContext? TestContext { get; set; }GetAzureOpenAIChatConfiguration메서드를 추가합니다. 이 메서드는 평가자가 모델과 통신하는 IChatClient를 생성합니다.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); }보고 기능을 설정합니다.
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);시나리오 이름
시나리오 이름은 현재 테스트 메서드의 정규화된 이름으로 설정됩니다. 그러나 호출 CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken)할 때 원하는 문자열로 설정할 수 있습니다. 시나리오 이름을 선택하기 위한 몇 가지 고려 사항은 다음과 같습니다.
- 디스크 기반 스토리지를 사용하는 경우 시나리오 이름은 해당 평가 결과가 저장되는 폴더의 이름으로 사용됩니다. 따라서 이름을 합리적으로 짧게 유지하고 파일 및 디렉터리 이름에 허용되지 않는 문자를 피하는 것이 좋습니다.
- 기본적으로 생성된 평가 보고서는 시나리오 이름을
.분할하여 적절한 그룹화, 중첩 및 집계가 있는 계층적 뷰에 결과를 표시할 수 있도록 합니다. 이는 시나리오 이름이 해당 테스트 메서드의 정규화된 이름으로 설정된 경우에 특히 유용합니다. 이는 결과를 계층 구조의 네임스페이스 및 클래스 이름으로 그룹화할 수 있기 때문에 유용합니다. 그러나 사용자 지정 시나리오 이름에 마침표(.)를 포함하여 이 기능을 활용하여 시나리오에 가장 적합한 보고 계층 구조를 만들 수도 있습니다.
실행 이름
실행 이름은 평가 결과가 저장될 때 동일한 평가 실행(또는 테스트 실행)의 일부인 평가 결과를 그룹화하는 데 사용됩니다. 만들 ReportingConfiguration때 실행 이름을 제공하지 않으면 모든 평가 실행에서 동일한 기본 실행 이름을
Default사용합니다. 이 경우 한 실행의 결과가 다음에 의해 덮어쓰여지고 여러 실행에서 결과를 비교할 수 없게 됩니다.이 예제에서는 타임스탬프를 실행 이름으로 사용합니다. 프로젝트에 둘 이상의 테스트가 있는 경우 테스트에서 사용되는 모든 보고 구성에서 동일한 실행 이름을 사용하여 결과가 올바르게 그룹화되었는지 확인합니다.
보다 실제적인 시나리오에서는 여러 다른 어셈블리에 있고 다른 테스트 프로세스에서 실행되는 평가 테스트에서 동일한 실행 이름을 공유할 수도 있습니다. 이러한 경우 테스트를 실행하기 전에 스크립트를 사용하여 적절한 실행 이름(예: CI/CD 시스템에서 할당한 현재 빌드 번호)으로 환경 변수를 업데이트할 수 있습니다. 또는 빌드 시스템에서 단조로 증가하는 어셈블리 파일 버전을 생성하는 경우 테스트 코드 내에서 해당 버전을 읽고 AssemblyFileVersionAttribute 실행 이름으로 사용하여 여러 제품 버전에서 결과를 비교할 수 있습니다.
보고 구성
A ReportingConfiguration 는 다음을 식별합니다.
- 호출 CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken)하여 만든 각 ScenarioRun에 대해 호출해야 할 평가자 집합입니다.
- 평가자가 사용해야 하는 LLM 엔드포인트입니다(참조 ReportingConfiguration.ChatConfiguration).
- 시나리오 실행 결과를 어떻게 그리고 어디에 저장해야 하는지.
- 시나리오 실행과 관련된 LLM 응답을 캐시하는 방법
- 시나리오 실행에 대한 결과를 보고할 때 사용해야 하는 실행 이름입니다.
이 테스트는 디스크 기반 보고 구성을 사용합니다.
별도의 파일에서
WordCountEvaluator클래스를 추가하는데, 이는 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에 있는 단어의 수를 계산합니다. 일부 평가자와 달리 AI를 기반으로 하지 않습니다. 메서드는 단어 개수를 포함하는 NumericMetric를 갖춘 EvaluationResult를 반환합니다.또한 이 메서드는
EvaluateAsync메트릭에 기본 해석을 연결합니다. 검색된 단어 수가 6에서 100 사이인 경우 기본 해석은 메트릭이 양호(허용 가능)라고 간주합니다. 그렇지 않으면 메트릭이 실패한 것으로 간주됩니다. 필요한 경우 호출자가 이 기본 해석을 재정의할 수 있습니다.MyTests.cs로 다시 돌아가서 평가에 사용할 평가자를 수집하는 메서드를 추가하세요.private static IEnumerable<IEvaluator> GetEvaluators() { IEvaluator relevanceEvaluator = new RelevanceEvaluator(); IEvaluator coherenceEvaluator = new CoherenceEvaluator(); IEvaluator wordCountEvaluator = new WordCountEvaluator(); return [relevanceEvaluator, coherenceEvaluator, wordCountEvaluator]; }시스템 프롬프트 ChatMessage를 추가하고 , 채팅 옵션을 정의하고, 모델에 지정된 질문에 대한 응답을 요청하는 메서드를 추가합니다.
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); }이 자습서의 테스트는 천문학 질문에 대한 LLM의 응답을 평가합니다. ReportingConfiguration 에 응답 캐싱이 허용되었기 때문에, 그리고 제공된 IChatClient는 항상 이 보고서 구성에서 생성된 ScenarioRun에서 가져오기 때문에, 테스트의 LLM 응답이 캐시되어 재사용됩니다. 해당 캐시 항목이 만료될 때까지(기본적으로 14일) 또는 LLM 엔드포인트 또는 질문과 같은 요청 매개 변수가 변경될 때까지 응답이 다시 사용됩니다.
응답의 유효성을 검사하는 메서드를 추가합니다.
/// <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); }팁 (조언)
메트릭에는 각각 점수의 추론을 설명하는
Reason속성이 포함되어 있습니다. 그 이유는 생성된 보고서에 포함되며 해당 메트릭 카드의 정보 아이콘을 클릭하여 볼 수 있습니다.마지막으로 테스트 메서드 자체를 추가합니다.
[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); }이 테스트 방법은 다음과 같습니다.
ScenarioRun을(를) 만듭니다. 이
await using사용은ScenarioRun이 올바르게 처리되고 평가 결과가 결과 저장소에 정확하게 저장되도록 보장합니다.특정 천문학 질문에 대한 LLM의 응답을 가져옵니다. 평가에 사용되는 동일한 IChatClient는 메서드에 전달되어 평가되는 기본 LLM 응답에 대한 응답 캐싱을 가져오게 됩니다. 또한 이를 통해 평가자가 내부적으로 평가를 수행하는 데 사용하는 LLM 턴에 대한 응답 캐싱이 가능합니다. 응답 캐싱을 사용하면 LLM 응답이 다음 중 하나를 가져옵니다.
- 현재 테스트의 첫 번째 실행에서 LLM 엔드포인트에서 직접 또는 캐시된 항목이 만료된 경우 후속 실행에서(기본적으로 14일)
- 테스트의 이후 실행에서 구성된
s_defaultReportingConfiguration디스크 기반 응답 캐시에서.
응답에 대해 평가 도구를 실행합니다. LLM 응답과 마찬가지로 후속 실행 시 평가는 구성된 (디스크 기반) 응답 캐시에서 가져옵니다
s_defaultReportingConfiguration.평가 결과에 대한 몇 가지 기본 유효성 검사를 실행합니다.
이 단계는 선택 사항이며 주로 데모용입니다. 실제 평가에서는 제품(및 사용된 모델)이 진화함에 따라 LLM 응답 및 평가 점수가 시간이 지남에 따라 변경될 수 있으므로 개별 결과의 유효성을 검사하지 않을 수 있습니다. 당신은 개별 평가 테스트가 "실패"하여 CI/CD 파이프라인에서 빌드를 차단하는 것을 원하지 않을 수도 있습니다. 대신 생성된 보고서에 의존하여 시간이 지남에 따라 다양한 시나리오에서 평가 점수의 전반적인 추세를 추적하는 것이 더 좋을 수 있습니다(그리고 여러 다른 테스트에서 평가 점수가 크게 저하된 경우에만 개별 빌드에 실패함). 즉, 여기에 몇 가지 미묘한 차이가 있으며 개별 결과의 유효성을 검사할지 여부를 선택하는 것은 특정 사용 사례에 따라 달라질 수 있습니다.
메서드가 반환될 때
scenarioRun개체가 삭제되며, 평가 결과는s_defaultReportingConfiguration에 구성된 디스크 기반 결과 저장소에 저장됩니다.
테스트/평가 실행
CLI 명령을 dotnet test 사용하거나 테스트 탐색기를 통해 원하는 테스트 워크플로를 사용하여 테스트를 실행합니다.
보고서 생성
터미널 창에서 다음 명령을 실행하여 Microsoft.Extensions.AI.Evaluation.Console .NET 도구를 설치합니다.
dotnet tool install --local Microsoft.Extensions.AI.Evaluation.Console팁 (조언)
먼저 매니페스트 파일을 만들어야 할 수 있습니다. 해당 도구 및 로컬 도구 설치에 대한 자세한 내용은 로컬 도구를 참조하세요.
다음 명령을 실행하여 보고서를 생성합니다.
dotnet tool run aieval report --path <path\to\your\cache\storage> --output report.htmlreport.html파일을 엽니다. 그것은 다음과 같이 보일 것입니다.
다음 단계
- 테스트 결과가 저장되는 디렉터리(
C:\TestReports만들 때 ReportingConfiguration위치를 수정하지 않는 한)로 이동합니다.results하위 디렉터리에는 타임스탬프(ExecutionName)로 명명된 각 테스트 실행에 대한 폴더가 있습니다. 각 폴더 안에는 각 시나리오 이름에 대한 폴더가 있습니다. 이 경우 프로젝트의 단일 테스트 메서드만 있습니다. 해당 폴더에는 메시지, 응답 및 평가 결과를 포함한 모든 데이터가 포함된 JSON 파일이 포함되어 있습니다. - 평가를 확장합니다. 다음은 몇 가지 아이디어입니다.
- AI를 사용하여 응답에 사용되는 측정 시스템을 결정하는 평가자와 같은 추가 사용자 지정 평가기를 추가합니다.
- LLM에서 여러 응답을 평가하는 메서드 와 같은 다른 테스트 메서드를 추가합니다. 각 응답은 다를 수 있으므로 질문에 대한 몇 가지 응답을 샘플링하고 평가하는 것이 좋습니다. 이 경우 호출 CreateScenarioRunAsync(String, String, IEnumerable<String>, IEnumerable<String>, CancellationToken)할 때마다 반복 이름을 지정합니다.
.NET