[Agent的评估-07]整合MEAI针对自然语言处理相关的评估器

Agent的评估-05:MEAI用来评估LLM响应质量的9种评估器介绍了定义在NuGet包Microsoft.Extensions.AI.Evaluation.Quality中与质量相关指标(包括完整性、一致性、相关性和等效性等)的各种评估器。除此之外,在NuGet包Microsoft.Extensions.AI.Evaluation.NLP还包含两个与自然语言处理(NLP)相关的评估器,这篇文章就来聊聊它们都用来评估怎样的指标,以及如何利用Agent的评估-06:如何改进MAF针对MEAI评估系统的适配方案?中介绍的方案使用它们。

1. F1Evaluator

对机器学习稍微了解的人都应该知道精确率召回率F1这三个基本的指标。当这三个指标被用于语言模型,我们可以使用它们来评估指定的一段文本与标准文本之间的匹配程序。两者之间的所谓匹配体现为Token(文本经过分词后生成的Token)序列的重合度。

  • 精确率:重合的Token数/模型回答 (Response) 的Token数,用来衡量模型回答的内容里,有多少是正确的,有没有胡思乱想;
  • 召回率:重合的Token数/标准答案 (Ground Truth) 的Token数,衡量标准答案里的关键信息,模型答出了多少;
  • F1:精确率和召回率的调和平均,综合评估回答的完整性与准确性,具体的计算公式为:

2 × Precision × Recall Precision + Recall 2 \times \frac{\text{Precision} \times \text{Recall}}{\text{Precision} + \text{Recall}} 2×Precision+RecallPrecision×Recall

F1Evaluator是微软官方扩展库Microsoft.Extensions.AI.Evaluation.NLP中的一个核心评估器。它通过计算字词重合度(F1分数),在**完全不依赖外部大模型裁判(LLM-as-a-Judge)**的前提下,直接根据指定的评估基准(Ground Truth)在本地计算F1分数。如下面的代码所示,F1Evaluator对应的评估指标名称为F1

csharp 复制代码
public sealed class F1Evaluator : IEvaluator
{
    public static string F1MetricName => "F1";
    public IReadOnlyCollection<string> EvaluationMetricNames { get; } = [F1MetricName];

    public ValueTask<EvaluationResult> EvaluateAsync(
        IEnumerable<ChatMessage> messages,
        ChatResponse modelResponse,
        ChatConfiguration? chatConfiguration = null,
        IEnumerable<EvaluationContext>? additionalContext = null,
        CancellationToken cancellationToken = default);
}

在评估的时候需要利用additionalContext参数表示的评估上下文提供作为评估基准的文本。对应的评估上下文类型为具有如下定义的F1EvaluatorContextGroundTruth属性就是作为评估基准的文本。

csharp 复制代码
public sealed class F1EvaluatorContext : EvaluationContext
{
	public static string GroundTruthContextName => "Ground Truth (F1)";
	public string GroundTruth { get; }
	public F1EvaluatorContext(string groundTruth)
		: base(GroundTruthContextName, groundTruth)
	=>GroundTruth = groundTruth;
}

由于F1Evaluator针对F1EvaluatorContext上下文的依赖,所以采用MAF默认的评估API是无法使用此评估器的,所以只能使用我在Agent的评估-06:如何改进MAF针对MEAI评估系统的适配方案?提供的评估方案。在如下的演示程序中,我使用模型gpt-5.4-mini创建了一个Agent,然后我们利用F1Evaluator来评估它的翻译能力。

在调用我们自定义的EvaluateAsync扩展方法是,我们利用evalContextAccessor参数指定的委托提供了作为评估上下文的BLEUEvaluatorContext对象,其GroundTruth属性是调用模型DeepSeek-V4-Pro模型生成的。换句话说,我们实际上在使用模型DeepSeek-V4-Pro提供的译文作为基准来评估Agent。

csharp 复制代码
using Azure.AI.Projects;
using Azure.Identity;
using DotNetEnv;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.AI.Evaluation;
using Microsoft.Extensions.AI.Evaluation.NLP;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
Env.Load();

var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY")!;
var projectUrl = Environment.GetEnvironmentVariable("PROJECT_URL")!;


var groundTruthGenerator = new AIProjectClient(new Uri(projectUrl), new AzureCliCredential())
    .ProjectOpenAIClient
    .GetProjectResponsesClient()
    .AsIChatClient("DeepSeek-V4-Pro");

var agent = new AIProjectClient(new Uri(projectUrl), new AzureCliCredential())
    .ProjectOpenAIClient
    .GetProjectResponsesClient()
    .AsIChatClient("gpt-5.4-mini")
    .AsAIAgent();

var query = """
    将下面这句话翻译成英文:

    道生一,一生二,二生三,三生万物。

    只返回译文。
    """;

var results = await agent.EvaluateAsync(
    queries: [query],
    evaluator: new F1Evaluator(),
    evalContextAccessor: evalItem => GetEvaluationContextsAsync(evalItem, groundTruthGenerator));

var serializerOptions = new JsonSerializerOptions
{
    WriteIndented = true,
    Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
serializerOptions.Converters.Add(new JsonStringEnumConverter());
Console.WriteLine(JsonSerializer.Serialize(results, serializerOptions));

static async ValueTask<IEnumerable<EvaluationContext>> GetEvaluationContextsAsync(
    EvalItem evalItem, 
    IChatClient chatClient)
{
    var response = await chatClient.GetResponseAsync(evalItem.Query);
    return [new F1EvaluatorContext(response.Text)];
}

输出:

json 复制代码
{
  "ProviderName": "F1Evaluator",
  "ReportUrl": null,
  "EvalId": null,
  "RunId": null,
  "Status": null,
  "Error": null,
  "Items": [
    {
      "Metrics": {
        "F1": {
          "$type": "numeric",
          "Value": 0.8846153846153846,
          "Name": "F1",
          "Reason": null,
          "Interpretation": {
            "Rating": "Exceptional",
            "Failed": false,
            "Reason": null
          },
          "Context": {
            "Ground Truth (F1)": {
              "Name": "Ground Truth (F1)",
              "Contents": [
                {
                  "$type": "text",
                  "Text": "The Tao gives birth to One. One gives birth to Two. Two gives birth to Three. Three gives birth to all things.",
                  "Annotations": null,
                  "AdditionalProperties": null
                }
              ]
            }
          },
          "Diagnostics": null,
          "Metadata": {
            "built-in-eval": "True",
            "eval-duration-ms": "3.53"
          }
        }
      }
    }
  ],
  "InputItems": [
    {
      "Query": "将下面这句话翻译成英文:\r\n\r\n道生一,一生二,二生三,三生万物。\r\n\r\n只返回译文。",
      "Response": "The Tao gives birth to one; one gives birth to two; two gives birth to three; three gives birth to all things.",
      "Conversation": [
        {
          "AuthorName": null,
          "CreatedAt": null,
          "Role": "user",
          "Contents": [
            {
              "$type": "text",
              "Text": "将下面这句话翻译成英文:\r\n\r\n道生一,一生二,二生三,三生万物。\r\n\r\n只返回译文。",
              "Annotations": null,
              "AdditionalProperties": null
            }
          ],
          "MessageId": null,
          "AdditionalProperties": null
        },
        {
          "AuthorName": null,
          "CreatedAt": "2026-07-31T05:20:29+00:00",
          "Role": "assistant",
          "Contents": [
            {
              "$type": "text",
              "Text": "The Tao gives birth to one; one gives birth to two; two gives birth to three; three gives birth to all things.",
              "Annotations": null,
              "AdditionalProperties": null
            }
          ],
          "MessageId": "msg_0ee6210290b7a87d006a6c309df070819388a77e5f657a1554",
          "AdditionalProperties": null
        }
      ],
      "HasImageContent": false,
      "Tools": null,
      "Context": null,
      "ExpectedOutput": null,
      "ExpectedToolCalls": null,
      "RawResponse": {
        "Messages": [
          {
            "AuthorName": null,
            "CreatedAt": "2026-07-31T05:20:29+00:00",
            "Role": "assistant",
            "Contents": [
              {
                "$type": "text",
                "Text": "The Tao gives birth to one; one gives birth to two; two gives birth to three; three gives birth to all things.",
                "Annotations": null,
                "AdditionalProperties": null
              }
            ],
            "MessageId": "msg_0ee6210290b7a87d006a6c309df070819388a77e5f657a1554",
            "AdditionalProperties": null
          }
        ],
        "ResponseId": null,
        "ConversationId": null,
        "ModelId": null,
        "CreatedAt": null,
        "FinishReason": null,
        "Usage": null,
        "ContinuationToken": null,
        "AdditionalProperties": null
      },
      "Splitter": null
    }
  ],
  "SubResults": null,
  "PerEvaluator": null,
  "DetailedItems": null,
  "Passed": 1,
  "Failed": 0,
  "Total": 1,
  "AllPassed": true
}

从输出结果可以看出,F1得分0.8846153846153846,算是一个不错的分数,所以评估通过。从InputItems提供的对话历史和提供的评估上下文可以看出Agent生成的译文与作为基准的译文分别如下。除了大小写(One/one,Two/two和Three/three),译文完全一致。

  • Agent生成的译文(gpt-5.4-mini):The Tao gives birth to one; one gives birth to two; two gives birth to three; three gives birth to all things.
  • 作为基准的译文(DeepSeek-V4-Pro):The Tao gives birth to One. One gives birth to Two. Two gives birth to Three. Three gives birth to all things.

2. BLEUEvaluator

F1Evaluator一样,BLEUEvaluator也是为了评估语言模型生成的模型与提供的基准文本的差异,只是这里F1分数换成了BLEU指标。BLEU(Bilingual Evaluation Understudy,双语评估辅助工具)是机器翻译和文本生成领域最经典、最主流的自动评测指标。它最初由IBM在2002年提出,其核心哲学非常纯粹:机器翻译的结果与人类专业翻译的结果越接近,其翻译质量就越高。

BLEU采用被称为N-gram词组匹配模式。它通过计算模型生成的文本(Hypothesis/译文)与人类提供的标准答案(References/参考译文)之间的词语重合度来打分的。它使用连续的词组片段(称为 n-gram)来进行多粒度的比对:

  • 1-gram:检查单个词的重合率。这主要用来评估词汇准确度(模型有没有选对词);
  • 2-gram到4-gram:检查连续的2个、3个或4个词的片段是否匹配。这主要用来评估句子的流畅度和语序(调整后的句子结构是否符合人类语言习惯)。

与F1一样,BLEU的输出范围在0.0到1.0之间(通常在学术界或报告中会乘以100,表示为0到100%):

BLEU 分数区间 工业界与学术界的实际解读
< 10 几乎没有参考价值;完全语无伦次、不可读的翻译。
20 - 29 勉强可用;大体意思能看懂,但存在大量的语法和用词错误。
30 - 40 良好 / 达到商用标准;句意清晰,完全可用于日常或商务交流。
50 以上 极其优秀;文本高度流畅,已经非常逼近人类专家的翻译水平。

BLEUEvaluator定义如下,可以看出它输出的指标名称为BLEU。作为评估基准的文本通过作为评估上下文的BLEUEvaluatorContext的References来提供。

csharp 复制代码
public sealed class BLEUEvaluator : IEvaluator
{
    public static string BLEUMetricName => "BLEU";
    public IReadOnlyCollection<string> EvaluationMetricNames { get; } = [BLEUMetricName];
    public ValueTask<EvaluationResult> EvaluateAsync(
        IEnumerable<ChatMessage> messages,
        ChatResponse modelResponse,
        ChatConfiguration? chatConfiguration = null,
        IEnumerable<EvaluationContext>? additionalContext = null,
        CancellationToken cancellationToken = default)
}

public sealed class BLEUEvaluatorContext : EvaluationContext
{
	public static string ReferencesContextName => "References (BLEU)";
	public IReadOnlyList<string> References { get; }

	public BLEUEvaluatorContext(IEnumerable<string> references)
		: this(references.ToArray())
	{}

	public BLEUEvaluatorContext(params string[] references)
		: base(ReferencesContextName, ((IEnumerable<AIContent>)references.Select((string c) => new TextContent(c))).ToArray())
	=>References = references;
}

我们将上面翻译评估例子中使用的F1Evaluator替换成BLEUEvaluator。如代码所示,除了替换了评估器,我们还利用EvaluateAsyncevalContextAccessor参数提供了对应的作为评估上下文的BLEUEvaluatorContext对象。但是这里我们不再利用LLM生成译文,而是直接指定为一段固定的译文。

csharp 复制代码
using Azure.AI.Projects;
using Azure.Identity;
using DotNetEnv;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.AI.Evaluation.NLP;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
Env.Load();

var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY")!;
var projectUrl = Environment.GetEnvironmentVariable("PROJECT_URL")!;

var agent = new AIProjectClient(new Uri(projectUrl), new AzureCliCredential())
    .ProjectOpenAIClient
    .GetProjectResponsesClient()
    .AsIChatClient("gpt-5.4-mini")
    .AsAIAgent();

var query = """
    将下面这句话翻译成英文:

    道生一,一生二,二生三,三生万物。

    只返回译文。
    """;
var reference = "The Tao produces One; The One produces Two; The Two produces Three; The Three produces everything.";
var results = await agent.EvaluateAsync(
    queries: [query],
    evaluator: new BLEUEvaluator(),
    evalContextAccessor: evalItem => [new BLEUEvaluatorContext([reference])]);

var serializerOptions = new JsonSerializerOptions
{
    WriteIndented = true,
    Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
serializerOptions.Converters.Add(new JsonStringEnumConverter());
Console.WriteLine(JsonSerializer.Serialize(results, serializerOptions));

输出:

json 复制代码
{
  "ProviderName": "BLEUEvaluator",
  "ReportUrl": null,
  "EvalId": null,
  "RunId": null,
  "Status": null,
  "Error": null,
  "Items": [
    {
      "Metrics": {
        "BLEU": {
          "$type": "numeric",
          "Value": 0.0378574628856688,
          "Name": "BLEU",
          "Reason": null,
          "Interpretation": {
            "Rating": "Unacceptable",
            "Failed": true,
            "Reason": "BLEU is less than 0.5."
          },
          "Context": {
            "References (BLEU)": {
              "Name": "References (BLEU)",
              "Contents": [
                {
                  "$type": "text",
                  "Text": "The Tao produces One; The One produces Two; The Two produces Three; The Three produces everything.",
                  "Annotations": null,
                  "AdditionalProperties": null
                }
              ]
            }
          },
          "Diagnostics": null,
          "Metadata": {
            "built-in-eval": "True",
            "eval-duration-ms": "9.93"
          }
        }
      }
    }
  ],
  "InputItems": [
    {
      "Query": "将下面这句话翻译成英文:\r\n\r\n道生一,一生二,二生三,三生万物。\r\n\r\n只返回译文。",
      "Response": "The Dao gives birth to the One, the One gives birth to the Two, the Two gives birth to the Three, and the Three gives birth to all things.",
      "Conversation": [
        {
          "AuthorName": null,
          "CreatedAt": null,
          "Role": "user",
          "Contents": [
            {
              "$type": "text",
              "Text": "将下面这句话翻译成英文:\r\n\r\n道生一,一生二,二生三,三生万物。\r\n\r\n只返回译文。",
              "Annotations": null,
              "AdditionalProperties": null
            }
          ],
          "MessageId": null,
          "AdditionalProperties": null
        },
        {
          "AuthorName": null,
          "CreatedAt": "2026-07-31T05:46:48+00:00",
          "Role": "assistant",
          "Contents": [
            {
              "$type": "text",
              "Text": "The Dao gives birth to the One, the One gives birth to the Two, the Two gives birth to the Three, and the Three gives birth to all things.",
              "Annotations": null,
              "AdditionalProperties": null
            }
          ],
          "MessageId": "msg_0358d1107ecae507006a6c36c8c8b08196994c0a572c883ba6",
          "AdditionalProperties": null
        }
      ],
      "HasImageContent": false,
      "Tools": null,
      "Context": null,
      "ExpectedOutput": null,
      "ExpectedToolCalls": null,
      "RawResponse": {
        "Messages": [
          {
            "AuthorName": null,
            "CreatedAt": "2026-07-31T05:46:48+00:00",
            "Role": "assistant",
            "Contents": [
              {
                "$type": "text",
                "Text": "The Dao gives birth to the One, the One gives birth to the Two, the Two gives birth to the Three, and the Three gives birth to all things.",
                "Annotations": null,
                "AdditionalProperties": null
              }
            ],
            "MessageId": "msg_0358d1107ecae507006a6c36c8c8b08196994c0a572c883ba6",
            "AdditionalProperties": null
          }
        ],
        "ResponseId": null,
        "ConversationId": null,
        "ModelId": null,
        "CreatedAt": null,
        "FinishReason": null,
        "Usage": null,
        "ContinuationToken": null,
        "AdditionalProperties": null
      },
      "Splitter": null
    }
  ],
  "SubResults": null,
  "PerEvaluator": null,
  "DetailedItems": null,
  "Passed": 0,
  "Failed": 1,
  "Total": 1,
  "AllPassed": false
}

从输入可以看出,虽然Agent提供的翻译比我提供的翻译更好,但是评估是与提供的基准作比较,所以BLEU分数只有0.0378574628856688(<0.5),评估没有通过。

相关推荐
CZW1 小时前
RAG系统核心解析:文档向量化与检索的实践探索
agent
code_371491 小时前
Agent 设计及实现 demo
agent
dozenyaoyida2 小时前
AI与大模型新闻日报 | 2026-08-01
人工智能·ai·大模型·新闻
枫昕柚2 小时前
ai--------实验
ai
lincats3 小时前
Caveman vs Ponytail:AI 编程圈"懒人哲学"两大门派正面交锋
ai·ai agent·vibe coding·claude code
孪生质数-3 小时前
AI Agent 工程实践(一):大模型 API 接入示范
网络·人工智能·ai·chatgpt·github·claude·claudecode
极客密码3 小时前
DeepSeek V4-Flash 正式版发布:Agent 基准、价格与 Codex 接入保姆级教程
agent·ai编程·deepseek
音符犹如代码3 小时前
DeepSeek V4 Flash正式版发布
ai·ai编程·deep learning
fthux3 小时前
装闭 RenoPit 源码解析(13):生成AI装修闭坑PDF报告
人工智能·ai·pdf·开源·github