Agent的评估-01:针对MAF的三种Agent评估方式针对三种IAgentEvaluator的不同实现,介绍了三种评估MAF Agent的方式。在介绍IAgentEvaluator接口的时候,我们提到MAF以此为核心的评估系统。这个系统由IAgentEvaluator、EvalItem和AgentEvaluationResults三个核心类型组成。

1. IAgentEvaluator

MAF的评估系统以IAgentEvaluator为核心。如下面的代码片段所示,每个IAgentEvaluator对象具有一个通过只读属性Name表示的名称,具体的评估工作实现在EvaluateAsync方法中。每一次针对EvaluateAsync方法的调用都会利用evalName参数为本次评估是定一个名称,默认值为Agent Framework Eval。评估所需的所有输入承载与作为输入参数的EvalItem列表上,而评估结果则体现在返回的AgentEvaluationResults对象上。

public interface IAgentEvaluator
{
	string Name { get; }

	Task<AgentEvaluationResults> EvaluateAsync(
		IReadOnlyList<EvalItem> items, 
		string evalName = "Agent Framework Eval", 
		CancellationToken cancellationToken = default);
}

2. EvalItem

EvalItem列表作为IAgentEvaluator对象的输入,承载着所有评估目标Agent所需的所有输入。对于针对AIAgent的一次评估,指的是通过完成一次AIAgent调用,评估返回的响应与理想预期的差距,这项基本的评估工作对应着一个独立的EvalItem对象。

2.1 IConversationSplitter

在正式介绍EvalItem类型之前,我们有必要先来了解如下这个与它有关的IConversationSplitter接口。IConversationSplitter接口代表对话分割器,旨在利用Split方法将指定的一个代表对话历史的ChatMessage列表中分别提取作为查询响应的消息列表。不同的实现采用不同的策略确定这条作为查询和响应的分割线。

public interface IConversationSplitter
{
	(IReadOnlyList<ChatMessage> QueryMessages, IReadOnlyList<ChatMessage> ResponseMessages) Split(IReadOnlyList<ChatMessage> conversation);
}

MAF预定了两个私有的分割器类型,对应的单例对象分别通过ConversationSplitters类型的静态属性LastTurnFull返回。

public static class ConversationSplitters
{
    public static IConversationSplitter LastTurn { get; } = new LastTurnSplitter();
    public static IConversationSplitter Full { get; } = new FullSplitter();

    private sealed class LastTurnSplitter : IConversationSplitter {}
    private sealed class FullSplitter : IConversationSplitter{}
}

LastTurn属性返回的一个LastTurnSplitter对象。顾名思义,LastTurnSplitter关注对话历史的最后一轮对话。由于一轮对话总是以User消息开始,所以如果对话历史存在User消息,那么最后一条User消息之前(包含自身)的消息会作为查询,后面的将作为响应。如果整个对话历史没有User消息,意味着整个列表将作为响应,查询为空。如下的演示程序很好地演示了这样的划分规则。

var systemMessage = new ChatMessage(role: ChatRole.System, content: null);
ChatMessage[] userMessages = [new ChatMessage(role: ChatRole.User, content: null), 
    new ChatMessage(role: ChatRole.User, content: null)];
ChatMessage[] assistantMessages = [new ChatMessage(role: ChatRole.Assistant, content: null), 
    new ChatMessage(role: ChatRole.Assistant, content: null)];

List<ChatMessage> history = [systemMessage, userMessages[0], assistantMessages[0], userMessages[1], assistantMessages[1]];
var (query, response) = ConversationSplitters.LastTurn.Split(history);
Debug.Assert(query.Count == 4);
Debug.Assert(response.Count == 1);

history = [systemMessage, userMessages[0], assistantMessages[0], userMessages[1]];
(query, response) = ConversationSplitters.LastTurn.Split(history);
Debug.Assert(query.Count == 4);
Debug.Assert(response.Count == 0);

history = [ assistantMessages[0], assistantMessages[1]];
(query, response) = ConversationSplitters.LastTurn.Split(history);
Debug.Assert(query.Count == 0);
Debug.Assert(response.Count == 2);

LastTurnSplitter相当于将前面轮次的消息作为少样本提示词,而FullSplitter则将提供的消息列表视为经历多次循环的完整对话历史,所以它会采用截然相反的划分方式:将**第一条User消息之前(含自身)**的部分作为查询,后面的部分作为响应。如下的演示程序体现了这种分割规则。

var systemMessage = new ChatMessage(role: ChatRole.System, content: null);
ChatMessage[] userMessages = [new ChatMessage(role: ChatRole.User, content: null), new ChatMessage(role: ChatRole.User, content: null)];
ChatMessage[] assistantMessages = [new ChatMessage(role: ChatRole.Assistant, content: null), new ChatMessage(role: ChatRole.Assistant, content: null)];

List<ChatMessage> history = [systemMessage, userMessages[0], assistantMessages[0], userMessages[1], assistantMessages[1]];
var (query, response) = ConversationSplitters.Full.Split(history);
Debug.Assert(query.Count == 2);
Debug.Assert(response.Count == 3);

history = [systemMessage, userMessages[0], assistantMessages[0], userMessages[1]];
(query, response) = ConversationSplitters.Full.Split(history);
Debug.Assert(query.Count == 2);
Debug.Assert(response.Count == 2);

history = [ assistantMessages[0], assistantMessages[1]];
(query, response) = ConversationSplitters.Full.Split(history);
Debug.Assert(query.Count == 0);
Debug.Assert(response.Count == 2);

2.2 EvalItem的两种构建方式

EvalItem利用定义的两个构造函数提供了两种构建方式。其中是直接指定查询和响应文本以及对话历史,另一种则是提供对话历史和作为分割器的IConversationSplitter对象,后者是可选的,默认传入的是一个LastTurnSplitter对象。

public sealed class EvalItem
{
	public string Query { get; }
	public string Response { get; }
	public IReadOnlyList<ChatMessage> Conversation { get; }
	public bool HasImageContent { get; }
	public IReadOnlyList<AITool>? Tools { get; set; }
	public string? Context { get; set; }
	public string? ExpectedOutput { get; set; }
	public IReadOnlyList<ExpectedToolCall>? ExpectedToolCalls { get; set; }
	public ChatResponse? RawResponse { get; set; }
	public IConversationSplitter? Splitter { get; set; }

    public EvalItem(string query, string response, IReadOnlyList<ChatMessage> conversation);
	public EvalItem(IReadOnlyList<ChatMessage> conversation, IConversationSplitter? splitter = null);
}

public record ExpectedToolCall(string Name, IReadOnlyDictionary<string, object>? Arguments = null);

各属性说明如下:

  • Query: 作为用户输入的查询文本:
    • 第一种构造方式:直接由query参数指定;
    • 第二种构造方式:如果经过分割后的查询存在,那么查询消息列表最后一条User消息的文本将作为此属性,否则返回一个空字符串。
  • Response:作为输出的响应文本:
    • 第一种构造函数:通过response参数指定;
    • 第二种构造函数:分割后的响应消息中的所有Assistant消息的文本利用空格进行拼接。
  • Conversation:由两个构造函数的conversation参数指定;
  • HasImageContent:对话历史中的消息中,是否存在这样一条消息,它的Contents列表中存在MIME类型为imageDataContent或者UriContent
  • Context:落地上下文(Grounding context),即在评估或推理时,提供给模型的真实世界参考信息,用于约束、校准、验证Agent的回答是否基于正确的事实或业务规则;
  • ExpectedOutput: 期望的输出文本;
  • ExpectedToolCalls:希望的执行的工具调用;
  • RawResponseIChatClient对象调用LLM的原始响应;
  • Splitter: 通过第二个构造函数的splitter参数指定的IConversationSplitter对象。

1.3 EvalItem的初始化

对于如下五个用来评估指定AIAgentEvaluateAsync方法重载来说,如果利用参数responses显式指定了作为Agent响应的AgentResponse列表,将针对每个AgentResponse对象创建创建一个EvalItem。否则将按照numRepetitions参数指定的重复初始,基于queries指定的每个查询文本调用指定的AIAgent,然后根据返回的AgentResponse创建一个EvalItem,此时创建EvalItem的数量为numRepetitions与queries数量的乘积

public static async Task<AgentEvaluationResults> EvaluateAsync(
    this AIAgent agent,
    IEnumerable<string> queries,
    IAgentEvaluator evaluator,
    string evalName = DefaultEvalName,
    IEnumerable<string>? expectedOutput = null,
    IEnumerable<IEnumerable<ExpectedToolCall>>? expectedToolCalls = null,
    IConversationSplitter? splitter = null,
    int numRepetitions = 1,
    CancellationToken cancellationToken = default);

public static async Task<AgentEvaluationResults> EvaluateAsync(
    this AIAgent agent,
    IEnumerable<string> queries,
    IEvaluator evaluator,
    ChatConfiguration chatConfiguration,
    string evalName = DefaultEvalName,
    IEnumerable<string>? expectedOutput = null,
    IEnumerable<IEnumerable<ExpectedToolCall>>? expectedToolCalls = null,
    IConversationSplitter? splitter = null,
    int numRepetitions = 1,
    CancellationToken cancellationToken = default);

public static async Task<IReadOnlyList<AgentEvaluationResults>> EvaluateAsync(
    this AIAgent agent,
    IEnumerable<string> queries,
    IEnumerable<IAgentEvaluator> evaluators,
    string evalName = DefaultEvalName,
    IEnumerable<string>? expectedOutput = null,
    IEnumerable<IEnumerable<ExpectedToolCall>>? expectedToolCalls = null,
    IConversationSplitter? splitter = null,
    int numRepetitions = 1,
    CancellationToken cancellationToken = default);

public static async Task<AgentEvaluationResults> EvaluateAsync(
    this AIAgent agent,
    IEnumerable<AgentResponse> responses,
    IEnumerable<string> queries,
    IAgentEvaluator evaluator,
    string evalName = DefaultEvalName,
    IEnumerable<string>? expectedOutput = null,
    IEnumerable<IEnumerable<ExpectedToolCall>>? expectedToolCalls = null,
    CancellationToken cancellationToken = default);

public static async Task<AgentEvaluationResults> EvaluateAsync(
    this AIAgent agent,
    IEnumerable<AgentResponse> responses,
    IEnumerable<string> queries,
    IEvaluator evaluator,
    ChatConfiguration chatConfiguration,
    string evalName = DefaultEvalName,
    IEnumerable<string>? expectedOutput = null,
    IEnumerable<IEnumerable<ExpectedToolCall>>? expectedToolCalls = null,
    CancellationToken cancellationToken = default);

3. AgentEvaluationResults

IAgentEvaluatorEvaluateAsync方法执行后会评估结果封装成一个AgentEvaluationResults对象,所以针对AIAgent上述这些EvaluateAsync扩展方法来说,如果指定的是单一的IAgentEvaluator对象,则返回单一的AgentEvaluationResults。如果指定的是一个IAgentEvaluator列表,则返回一个数据对等的AgentEvaluationResults列表。AgentEvaluationResults类型定义如下。

public sealed class AgentEvaluationResults
{
	public string ProviderName { get; }
	public Uri? ReportUrl { get; set; }
	public string? EvalId { get; set; }
	public string? RunId { get; set; }
	public string? Status { get; set; }
	public string? Error { get; set; }
	public IReadOnlyList<EvaluationResult> Items {get; }
	public IReadOnlyList<EvalItem>? InputItems { get; }
	public IReadOnlyDictionary<string, AgentEvaluationResults>? SubResults { get; set; }
	public IReadOnlyDictionary<string, PerEvaluatorResult>? PerEvaluator { get; set; }
	public IReadOnlyList<EvalItemResult>? DetailedItems { get; set; }
	public int Passed {get; }
	public int Failed {get; }
	public int Total {get; }
	public bool AllPassed {get; }
}

public record PerEvaluatorResult(int Passed, int Failed);

各属性说明如下:

  • ProviderName: 提供的评估提供者,一般会直接使用IAgentEvaluator的名称;
  • ReportUrl:Foundry评估报告的URL,用于查看详细结果;
  • EvalId:Foundry评估任务的唯一ID;
  • RunId:Foundry评估运行的唯一ID;
  • Status:评估运行状态,例如completedfailedcanceledtimeout
  • Error: 当评估失败时的错误信息;
  • Items:每个评估项的结果列表,对应每个EvalItemEvaluationResult
  • InputItems: 原始的评估输入项,用于审计和溯源,与Items按位置一一对应。
  • SubResults:工作流评估中每个子Agent的结果,形成嵌套结构;
  • PerEvaluator:每个评估器的通过/失败统计(Foundry专用);
  • DetailedItems: 基于具体EvalItem的详细评估结果,包括分数、错误信息、token使用等;
  • PassedFailedTotal:通过/失败/总的评估项数量;
  • AllPassed:是否所有评估项都通过。

3.1 EvalItemResult

EvalItemResult是Foundry评估系统中针对一个EvalItem的评估任务中的描述,同时也保安相关的评估指标。它记录某个输出项的评估状态,并包含每个评估指标的评分结果,用于判断该项是否通过或失败。它还保存错误代码、错误信息、输入/输出文本回显,以及token使用情况,便于调试和审计。通过IsPassedIsFailedIsError等属性,可以快速判断该项的整体评估结果,是Foundry细粒度评估的核心数据结构。

public sealed class EvalItemResult
{
	public string ItemId { get; }
	public string Status { get; }
	public IReadOnlyList<EvalScoreResult> Scores { get; }
	public string? ErrorCode { get; set; }
	public string? ErrorMessage { get; set; }
	public string? ResponseId { get; set; }
	public string? InputText { get; set; }
	public string? OutputText { get; set; }
	public IReadOnlyDictionary<string, int>? TokenUsage { get; set; }
	public bool IsError{ get; }
	public bool IsPassed { get; }
	public bool IsFailed { get; }
}
public record EvalScoreResult(string Name, double Score, bool? Passed = null);

各个属性说明如下:

  • ItemId:该评估项在Foundry evaluation API中的ID;
  • Status:该项的评估状态,例如"pass", “fail”, "error"和"errored"等;
  • Scores:每个评估指标的得分列表;
  • ErrorCode:当该项评估发生错误时的错误代码;
  • ErrorMessage:当该项评估发生错误时的错误信息;
  • ResponseId:评估API返回的Response ID;
  • InputText:评估API回显的输入文本;
  • OutputText:评估API回显的输出文本;
  • TokenUsage:评估过程中使用的token数量;
  • IsError:如果状态为errorerrored,则该项处于错误状态;
  • IsPassed:如果所有指标的Passed == true,则该项整体通过;
  • IsFailed:如果任一指标的Passed == false,则该项整体失败。

3.2 EvaluationResult & EvaluationMetric

EvalItemResult一样,EvaluationResult也是针对一个具体的EvalItem,但它只承载最终的评估结果,具体体现在通过其Metrics属性返回的评估指标。具体的指标通过EvaluationMetric类型标识,每个EvaluationMetric对象具有一个确定的名称,这个名称就是Metrics字典的Key

public sealed class EvaluationResult
{
	public IDictionary<string, EvaluationMetric> Metrics { get; set; }
}

public class EvaluationMetric
{
	public string Name { get; set; }
	public string? Reason { get; set; }
	public EvaluationMetricInterpretation? Interpretation { get; set; }
	public IDictionary<string, EvaluationContext>? Context { get; set; }
	public IList<EvaluationDiagnostic>? Diagnostics { get; set; }
	public IDictionary<string, string>? Metadata { get; set; }
}

EvaluationMetric各属性说明如下:

  • Name:评估指标的名称,比如"task_completion"、“tool_accuracy”、"safety"和"coherence_score"等;
  • Reason: 可选的解释性文本,用于说明为什么得到这个结果,例如:“模型没有按照要求调用工具”,“输出包含敏感内容”和“评分偏低,因为回答不完整”等;
  • Interpretation: 用于表达该指标的好坏/通过/失败等语义,它让指标不仅有值,还能表达是否符合预期
  • Context: 用于记录生成当前指标参考的上下文,例如Ground truth,任务描述,模型输出,工具调用信息等;
  • Diagnostics: 用于记录更细粒度的诊断信息,例如哪一段文本不符合要求,哪个工具调用参数错误,哪个安全规则被触发等;
  • Metadata: 任意字符串键值对形式的额外元数据。

如下这个泛型的EvaluationMetric<T>类型派生于EvaluationMetric,并在基类基础上定义了作为指标值的Value属性,泛型参数T作为它的类型。BooleanMetricNumericMetricStringMetric继承自EvaluationMetric<T>,分别采用布尔值、数组和字符串来标识指标的值。

public class EvaluationMetric<T> : EvaluationMetric
{
	public T? Value { get; set; }
}

public sealed class BooleanMetric : EvaluationMetric<bool?>;
public sealed class NumericMetric : EvaluationMetric<double?>;
public sealed class StringMetric : EvaluationMetric<string>;

2.3 EvaluationMetricInterpretation

EvaluationMetric利用Interpretation返回的EvaluationMetricInterpretation对象对指标作进一步解释。用于告诉评估系统:这个指标到底是好还是坏、是否失败、为什么这样判断。它不是指标本身,而是指标的语义层解释

public sealed class EvaluationMetricInterpretation
{
	public EvaluationRating Rating { get; set; }
	public bool Failed { get; set; }
	public string? Reason { get; set; }
}

public enum EvaluationRating
{
	Unknown,
	Inconclusive,
	Unacceptable,
	Poor,
	Average,
	Good,
	Exceptional
}

三个属性说明如下:

  • RatingEvaluationRating是一个枚举,用来表达结果的质量;
  • Failed:用来表达这个指标是否被视为失败;
  • Reason:使用文字对RatingFailed的赋值原因作进一步解释。

2.4 EvaluationDiagnostic

EvaluationMetric利用Diagnostics是新返回的一组EvaluationDiagnostic对象输出一些诊断信息。每个EvaluationDiagnostic对象包含由Message属性提供的文字描述,和通过Severity属性体现的诊断等级。

public sealed class EvaluationDiagnostic
{
	public EvaluationDiagnosticSeverity Severity { get; set; }
	public string Message { get; set; }
}
public enum EvaluationDiagnosticSeverity
{
	Informational,
	Warning,
	Error
}

2.4 EvaluationContext

EvaluationMetricContext返回一个IDictionary<string, EvaluationContext>类型的字典。EvaluationContext是为评估提供的额外上下文输入容器,用于携带评估所需但不在对话历史中的信息(如ground truth、规则、工具规范、参考答案等)。它是一个抽象基类,要求派生类必须把所有上下文内容以AIContent的形式放入Contents,因为序列化与报告系统只依赖Contents。每个EvaluationContext一个确切的名称,这个名称就是上述字典的Key。

public abstract class EvaluationContext
{
	public string Name { get; set; }
	public IList<AIContent> Contents { get; set; }
}

public sealed class CompletenessEvaluatorContext : EvaluationContext
{
	public static string GroundTruthContextName => "Ground Truth (Completeness)";
	public string GroundTruth { get; }
}

public sealed class EquivalenceEvaluatorContext : EvaluationContext
{
	public static string GroundTruthContextName => "Ground Truth (Equivalence)";
	public string GroundTruth { get; }
}

public sealed class GroundednessEvaluatorContext : EvaluationContext
{
	public static string GroundingContextName => "Grounding Context (Groundedness)";
	public string GroundingContext { get; }
}

public sealed class IntentResolutionEvaluatorContext : EvaluationContext
{
	public static string ToolDefinitionsContextName => "Tool Definitions (Intent Resolution)";
	public IReadOnlyList<AITool> ToolDefinitions { get; }
}

public sealed class RetrievalEvaluatorContext : EvaluationContext
{
	public static string RetrievedContextChunksContextName => "Retrieved Context Chunks (Retrieval)";
	public IReadOnlyList<string> RetrievedContextChunks { get; }
}

public sealed class ToolCallAccuracyEvaluatorContext : EvaluationContext
{
	public static string ToolDefinitionsContextName => "Tool Definitions (Tool Call Accuracy)";
	public IReadOnlyList<AITool> ToolDefinitions { get; }
}

MEAI为EvaluationContext定义了如下这几个派生类:

ReferenceContext —— 提供正确答案(Ground Truth)

用于参考型评估(reference-based evaluation),它通常包含:

  • 正确答案文本
  • 多个参考答案(可选)
  • 关键事实列表

评估器用它来:

  • 比较模型输出与正确答案
  • 做语义相似度
  • 判断是否答对
RubricContext —— 提供评分标准(Rubric)

用于规则型评估(rule-based evaluation),包含:

  • 必须满足的规则
  • 必须覆盖的要点
  • 写作规范
  • 评分维度说明

评估器用它来:

  • 检查模型是否遵守规则
  • 判断是否覆盖关键点
  • 给出 Good/Bad/Failed等解释
TaskSpecContext —— 提供任务要求(Task Specification)

用于任务完成度评估(task completion evaluation),它包含:

  • 输出格式要求(如 JSON schema)
  • 必须包含的字段
  • 输出风格要求
  • 任务目标描述

评估器用它来:

  • 判断模型是否完成任务
  • 检查格式是否正确
  • 检查字段是否齐全
ToolSchemaContext —— 提供工具调用规范(Tool Schema)

用于工具调用准确性评估(tool-call accuracy evaluation),它包含:

  • 工具名称
  • 参数类型
  • 必填参数
  • 参数约束(如枚举、范围)

评估器用它来:

  • 判断模型是否调用正确工具
  • 参数是否正确
  • 是否遗漏必填字段
SafetyRulesContext —— 提供安全规则(Safety Rules)

用于安全评估(safety evaluation)包含:

  • 不得出现的内容(暴力、仇恨、色情等)
  • 敏感主题列表
  • 风险等级定义

评估器用它来:

  • 检查模型是否违反安全规则
  • 标记风险等级
  • 给出失败原因
RagContext —— 提供检索文档(RAG Passages)

用于RAG相关评估(retrieval-based evaluation), 它包含:

  • 检索到的文档片段
  • 文档来源(URL、ID)
  • 置信度分数(可选)

评估器用它来:

  • 判断模型是否正确引用检索内容
  • 是否幻觉(hallucination)
  • 是否遗漏关键事实
Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐