![[基于OpenEvals的自动化评估-04]基于JSON相似度的评估[无LLM参与的字符匹配]](http://pic.xiahunao.cn/yaotu/[基于OpenEvals的自动化评估-04]基于JSON相似度的评估[无LLM参与的字符匹配])
由于Agent与LLM交互的数据基本采用基于JSON的序列化方式针对数据结构的描述也基本采用JSON Schema所以很多情况下针对Agent输出的评估体现在基于JSON的相似度比较。OpenEvals专门为此定义了如下两个用于创建基于JSON匹配评估器的工厂函数其中create_json_match_evaluator用于同步调用create_async_json_match_evaluator用于异步调用。这是两个非常重要的函数我们不仅可以用它来评估基于JSON的结构化输出Agent最核心的轨迹评估就是就依赖于它。如代码所示我们在调用这两个方法的时候可以利用judge参数指定连接LLM的客户端所以我们也可以利用LLM实现基于语义的相似度比较。但是这是一个可选的参数如果没有指定默认采用严格的字符匹配。这篇文章主要关注无LLM参数的评估下一篇我们将重点介绍基于LLM-as-a-Judge的评估。defcreate_json_match_evaluator(*,aggregator:Optional[Literal[average,all]]None,list_aggregator:Literal[average,all]all,rubric:Dict[str,str]{},exclude_keys:list[str][],judge:Optional[Union[ModelClient,BaseChatModel,]]None,model:Optional[str]None,use_reasoning:boolTrue,list_match_mode:Literal[superset,subset,same_elements,ordered]same_elements,)-SimpleEvaluatordefcreate_async_json_match_evaluator(*,aggregator:Optional[Literal[average,all]]None,list_aggregator:Literal[average,all]all,rubric:Dict[str,str]{},exclude_keys:list[str][],judge:Optional[Union[ModelClient,BaseChatModel,]]None,model:Optional[str]None,use_reasoning:boolTrue,list_match_mode:Literal[superset,subset,same_elements,ordered]same_elements,)-SimpleAsyncEvaluator1. 针对每个JSON字段得分的聚合接下来我们会通过实例演示的方式介绍create_json_match_evaluator和create_async_json_match_evaluator函数定义的每个参数对应怎样的评估行为。我们下来看看aggregator参数从两个函数的定义可以看出这个参数具有None、all和average三个选项。我们定义了如下的演示程序辅助方法会根据指定的关键字参数调用create_async_json_match_evaluator方法创建SimpleAsyncEvaluator对象并调用此对象对传入的outputs和references实施评估。作为评估结果的EvaluatorResult对象或者EvaluatorResult列表通过辅助函数pretty_print将key和score这两个核心字段打印出来。importasynciofromopenevals.jsonimportcreate_async_json_match_evaluatorfromopenevals.typesimportEvaluatorResultdefcreate(name:str,age:int,**kwargs)-dict:person{name:name,age:age}return{**person,**kwargs}defpretty_print(title:str,results:EvaluatorResult|list[EvaluatorResult]):print(f{title}:)if(isinstance(results,list)):forresultinresults:print(f{result[key]}{result[score]})else:print(f{results[key]}{results[score]})print()asyncdefeval(title:str,*,outputs:dict|list[dict],references:dict|list[dict],**kwargs):evaluatorcreate_async_json_match_evaluator(**kwargs)resultsawaitevaluator(outputsoutputs,reference_outputsreferences)pretty_print(title,results)asyncdefmain():alice1create(Alice,20,birth1981-08-24)alice2create(Alice,30,genderfemale)awaiteval(aggregatorNone,aggregatorNone,outputsalice1,referencesalice2)awaiteval(aggregatorall,aggregatorall,outputsalice1,referencesalice2)awaiteval(aggregatoraverage,aggregatoraverage,outputsalice1,referencesalice2)asyncio.run(main())从数据结构的角度来讲JSON本质上也就是一个字典所以我定义了辅助方法create根据指定的name、age以及其他任务额外字段创建一个字典来实施评估。说在main函数中我们使用{name:Alice, age:20, birth 1981-08-24}和{name:Alice, age:20, gender female}这字典作为待评估的outputs和作为评估基准的reference_outputs。然后分别采用三种不同的aggregator选项调用eval函数并得到如下的输出结果输出aggregatorNone: json_match:age 0 json_match:birthDate 0 json_match:gender 0 json_match:name 1 aggregatorall: json_match:all 0 aggregatoraverage: json_match:average 0.25从输出结果可以看出如果采用默认的aggregatorNone评估器会为JSON的每个字段生成一个EvaluatorResult对象对应的Key为json_match:{fieldName}。对于本例来说outputs和reference_outputs的指定的字典一共涉及name、age、birth和gender四个Key所以默认会生成四个EvaluatorResult对象。对于这四个Key只有name是相同的所以只有它对应的score为1其余三个均为0。有时我们并不需要进行如此细粒度的评估这段JSON生成一个评估结果就可以了这样的结果可以对基于每个JSON字段得分进行聚合得到。aggregator参数的all和average选项就表示两种聚合方式all如果outputs和reference_outputs拥有相同的Key且对应的Value均相同得1分否则得0分。EvaluatorResult中对应的Key为json_match:all。所以它的值为0average对每个Key对应的得分取平均值。EvaluatorResult中对应的Key为json_match:average。所以它的值为0.25。2. 在基于列表评估中针对列表元素得分的聚合作为outputs参数表示的待评估的JSON不仅限于一个字典还可以是一个字典的列表。在就行基于列表的评估时就需要使用到list_aggregator参数。如果说aggregator是站在字典的层次对所有的Key对应score进行聚合那么list_aggregator就是在站在列表的层次对字典对应的score进行聚合。整个待评估数据层次结构可以划分为list-dict-key。list_aggregator只有如下两个选项all如果列表中所有字典都能匹配得1分否则得0分average:对列表中所有字典对应的得分取平均值。我们修改了上面演示程序的main方法使用相同的outputs和reference_outputs采用list_aggregator和aggregator的6种组合调用eval方法并将得到的评估结果打印出来。asyncdefmain():alicecreate(Alice,20)bob1create(Bob,20)bob2create(Bob,30)outputs[alice,bob1]rerferences[alice,bob2]awaiteval(list_aggregatorall; aggregator none,list_aggregatorall,aggregatorNone,outputsoutputs,referencesrerferences)awaiteval(list_aggregatorall; aggregator all,list_aggregatorall,aggregatorall,outputsoutputs,referencesrerferences)awaiteval(list_aggregatorall; aggregator average,list_aggregatorall,aggregatoraverage,outputsoutputs,referencesrerferences)awaiteval(list_aggregatoraverage; aggregator none,list_aggregatoraverage,aggregatorNone,outputsoutputs,referencesrerferences)awaiteval(list_aggregatoraverage; aggregator all,list_aggregatoraverage,aggregatorall,outputsoutputs,referencesrerferences)awaiteval(list_aggregatoraverage; aggregator average,list_aggregatoraverage,aggregatoraverage,outputsoutputs,referencesrerferences)输出list_aggregatorall; aggregator none: json_match:age 0 json_match:name 1 list_aggregatorall; aggregator all: json_match:all 0 list_aggregatorall; aggregator average: json_match:average 0 list_aggregatoraverage; aggregator none: json_match:age 0.5 json_match:name 1.0 list_aggregatoraverage; aggregator all: json_match:all 0.5 list_aggregatoraverage; aggregator average: json_match:average 0.753. 列表的四种匹配模式list_match_mode决定评估器在处理列表结构的outputs和reference_outputs时如何进行元素对齐与评分展开。它不是评分规则而是结构对齐算法。它具有如下四个选项ordered、superset、subset和same_elements。3.1 严格按照顺序逐个匹配强制outputs和reference_outputs严格按照索引提取列表元素进行匹配。如果两个列表长度不一在list_aggregator为all时得0分为average时这些多出部分得元素得0分进而拉低整体平均分。下面演示程序执行了三项评估第一次由于outputs和reference_outputs列表的两个元素顺序颠倒即使list_aggregator和aggregator都设置为average也是0分。第二次在outputs列表额外添加了一个元素导致只有2/3匹配得分0.6666666666666666在将list_aggregator设置为all时得0分。asyncdefmain():alicecreate(Alice,20)bob1create(Bob,30)bob2create(Bob,40)references[alice,bob1]awaiteval(list_aggregatoraverage; aggregator average;[bob1,alice]/[alice, bob1],list_aggregatoraverage,aggregatoraverage,outputs[bob1,alice],referencesreferences,list_match_modeordered)awaiteval(list_aggregatoraverage; aggregator average;[alice, bob1, bob2]/[alice, bob1],list_aggregatoraverage,aggregatoraverage,outputs[alice,bob1,bob2],referencesreferences,list_match_modeordered)awaiteval(list_aggregatorall; aggregator average;[alice, bob1, bob2]/[alice, bob1],list_aggregatorall,aggregatoraverage,outputs[alice,bob1,bob2],referencesreferences,list_match_modeordered)输出list_aggregatoraverage;aggregatoraverage;[bob1,alice]/[alice,bob1]:json_match:average0.0list_aggregatoraverage;aggregatoraverage;[alice,bob1,bob2]/[alice,bob1]:json_match:average0.6666666666666666list_aggregatorall;aggregatoraverage;[alice,bob1,bob2]/[alice,bob1]:json_match:average03.2 遍历outputs(reference_outputs)匹配reference_outputsoutputssuperset模式由reference_outputs列表主导匹配具体匹配匹配流程为遍历reference_outputs列表中得每个元素在outputs列表中找最匹配且未绑定得元素如果list_aggregator为all在reference_outputs列表中的每个元素均与绑定的outputs元素完全匹配情况下得1分否则得0分如果list_aggregator为average对每个绑定的得分求平均。如下的演示程序很好地演示了这种模式的积分规则。asyncdefmain():alicecreate(Alice,20)bob1create(Bob,30)bob2create(Bob,40)references[bob1]awaiteval(list_aggregatoraverage; aggregator average;[bob1, alice]/[bob1],list_aggregatoraverage,aggregatoraverage,outputs[bob1,alice],referencesreferences,list_match_modesuperset)awaiteval(list_aggregatorall; aggregator average;[bob1, alice]/[bob1],list_aggregatorall,aggregatoraverage,outputs[bob1,alice],referencesreferences,list_match_modesuperset)awaiteval(list_aggregatoraverage; aggregator average;[bob2, alice]/[bob1],list_aggregatoraverage,aggregatoraverage,outputs[bob2,alice],referencesreferences,list_match_modesuperset)awaiteval(list_aggregatorall; aggregator average;[bob2, alice]/[bob1],list_aggregatorall,aggregatoraverage,outputs[bob2,alice],referencesreferences,list_match_modesuperset)输出list_aggregatoraverage; aggregator average;[bob1, alice]/[bob1]: json_match:average 1.0 list_aggregatorall; aggregator average;[bob1, alice]/[bob1]: json_match:average 1 list_aggregatoraverage; aggregator average;[bob2, alice]/[bob1]: json_match:average 0.5 list_aggregatorall; aggregator average;[bob2, alice]/[bob1]: json_match:average 0从语义上将superset匹配模式相当于希望outputs列表是reference_outputs列表的超集Superset适合如下的场景reference_outputs列表是权威列表outputs集合可能包含更多元素superset希望每个reference_outputs元素都能找到最接近的outputs元素而subset匹配模式则这样相反它希望outputs列表是reference_outputs列表的子集Subset相当于将两个集合对调过来。对于前面介绍的匹配流程和计分方式将两个集合对调一下就是subset模式的算法。3.3 更加严格的subset模式对于subset模式来说只要不影响outputs列表元素是否属于reference_outputs列表的判定往reference_outputs列表添加任意数量的元素都不会改变最终的评估结果。same_elements则可以看成是subset模式的加强版它按照subset模式相同的方式遍历outputs列表并从reference_outputs列表提取匹配度最高的元素但是会对reference_outputs列表中未匹配的元素施加惩罚。这种模式非常适合要求outputs与reference_outputs列表必须一致的应用场景。这是默认的匹配模式我们在第二节针对list_aggregator的演示即使针对这种模式。