![[基于OpenEvals的自动化评估-12]Agent执行轨迹评估[无LLM参与]](http://pic.xiahunao.cn/yaotu/[基于OpenEvals的自动化评估-12]Agent执行轨迹评估[无LLM参与])
如果您正在构建AgentOpenEvals包含用于评估Agent完整执行轨迹的评估器即Agent在解决任务过程中发出的消息和工具调用序列。由于用户与Agent之间采用基于消息的通信方式所以Agent的执行轨迹通过返回的消息列表来体现。执行轨迹在OpenEvals中以OpenAI风格的消息列表格式呈现。此外也支持LangChain的BaseMessage对象。在进行两个消息的比较时只考虑角色和工具调用会忽略消息的其他内容。1. 创建评估器的工厂函数针对Agent执行轨迹的评估器分别通过如下两个工厂函数创建其中create_trajectory_match_evaluator创建的以同步执行的SimpleEvaluator对象create_async_trajectory_match_evaluator则创建以异步执行的SimpleAsyncEvaluator。defcreate_trajectory_match_evaluator(*,trajectory_match_mode:TrajectoryMatchModestrict,tool_args_match_mode:ToolArgsMatchModeexact,tool_args_match_overrides:Optional[ToolArgsMatchOverrides]None,)-SimpleEvaluatordefcreate_async_trajectory_match_evaluator(*,trajectory_match_mode:TrajectoryMatchModestrict,tool_args_match_mode:ToolArgsMatchModeexact,tool_args_match_overrides:Optional[ToolArgsMatchOverrides]None,)-SimpleAsyncEvaluator由于涉及到工具调用在不同的场景下我们对真正被调用的工具及其顺序具有不同的要求create_trajectory_match_evaluator/create_async_trajectory_match_evaluator为此定义一个名为trajectory_match_mode的参数它具有如下四个选项。在[基于OpenEvals的自动化评估-04基于JSON相似度的评估]中我们介绍OpenEvals基于JSON的评估其中涉及的集合匹配模式也有类似的选项“superset”, “subset”, “same_elements”, “ordered”其实两者的作用差不多。TrajectoryMatchModeLiteral[strict,unordered,subset,superset]四种轨迹匹配模式说明如下strictoutputs和reference_outputs的工具调用列表在内容和顺序上均保持一致unorderedoutputs和reference_outputs的工具调用相同忽略顺序subset:outputs工具调用集合是reference_outputs工具调用集合的子集superset:outputs工具调用集合是reference_outputs工具调用集合的超集。在针对工具调用参数的评估中也具有类似的匹配规则这体现在create_trajectory_match_evaluator/create_async_trajectory_match_evaluator函数的额外两个参数tool_args_match_mode和tool_args_match_overrides上。ToolArgsMatchModeLiteral[exact,ignore,subset,superset]ToolArgsMatchOverridesdict[str,Union[ToolArgsMatchMode,list[str],Callable[[dict,dict],bool]]]tool_args_match_mode参数类型ToolArgsMatchMod具有如下四种匹配模式exactoutputs和reference_outputs提供的参数完全一致这是默认选项ignore忽略参数评估subsetoutputs提供的参数reference_outputs提供参数的子集supersetoutputs提供的参数reference_outputs提供参数的超集。tool_args_match_mode对参数匹配规则进行了整体定义我们可以利用另一个参数tool_args_match_overrides对某些工具的参数匹配逻辑做定制化覆盖。该参数对应的类型是一个字典Key代表工具名称Value具有如下几种形式ToolArgsMatchMode 针对当前工具的参数匹配规则list[str]只考虑匹配该列表执行的参数Callable[[dict, dict], bool]利用指定的函数来确定是否匹配两个参数分别表示outputs和reference_outputs提供的参数。2. 消息列表的转换在进行轨迹匹配的时候评估器会将待评估outputs和作为基准reference_outputs的消息列表转换成基于OpenAI风格的ChatCompletionMessage列表然后对消息进行逐条对比。具体的转换就实现在如下这个_normalize_to_openai_messages_list函数中。def_normalize_to_openai_messages_list(messages:Optional[Union[list[ChatCompletionMessage],list[BaseMessage],ChatCompletionMessage,dict]],)-list[ChatCompletionMessage]classChatCompletionMessage(TypedDict):id:NotRequired[Optional[str]]content:Union[str,list[dict]]role:strtool_calls:NotRequired[Optional[list[dict]]]在如下的演示程序中我们按照LangChain的标准构建了一个包含HumanMessage,AIMessage和ToolMessage三种消息类型的对话历史并调用_normalize_to_openai_messages_list函数将其转换成ChatCompletionMessage列表并序列化成JSON输出来。importjsonfromlangchain_core.messagesimportHumanMessage,AIMessage,ToolMessagefromopenevals.utilsimport_normalize_to_openai_messages_list messages[HumanMessage(今天苏州天气如何),AIMessage(tool_calls[{name:get_weather,args:{city:苏州},id:call-001}]),ToolMessage(content晴气温25度,tool_call_idcall-001),AIMessage(content苏州目前天气晴气温25度)]normalized_messages_normalize_to_openai_messages_list(messagesmessages)print(json.dumps(normalized_messages,indent2,ensure_asciiFalse))输出[{role:user,content:今天苏州天气如何},{role:assistant,tool_calls:[{type:function,id:call-001,function:{name:get_weather,arguments:{\city\: \苏州\}}}],content:},{role:tool,tool_call_id:call-001,content:晴气温25度},{role:assistant,content:苏州目前天气晴气温25度}]3. 评估的流程我们现在简单说说整个评估流程。outputs和reference_outputs参数提供的消息列表被转换成标准的ChatCompletionMessage列表我们姑且将转换后列表简称为outputs消息列表和reference_outputs列表后会执行如下的流程完成评估如果outputs消息列表和reference_outputs列表长度不一致评估失败依次从outputs消息列表和reference_outputs列表提取单条消息实施评估如果角色不一致评估失败如果包含工具调用列表则按照上述的匹配模式进行评估strict两组工具列表的顺序必须一致否则评估失败unorderedoutputs工具调用集合必须与reference_outputs工具调用集合一致忽略顺序否则评估失败subset:outputs工具调用集合是reference_outputs工具调用集合的子集否则评估失败superset:outputs工具调用集合是reference_outputs工具调用集合的超集否则评估失败。在具体对两个工具调用进行比较的时候在默认的情况下需要保证名称和参数列表完全一致。工具调用的ID一般是随机生成不在评估范围之内。如果对参数tool_args_match_mode和tool_args_match_overrides作用相应的设置则采用对应的匹配策略。4. 针对LangChain Agent的轨迹评估在如下的演示程序中我们对创建的Agent实施轨迹评估。我们在调用create_agent函数的时候注册了两个工具函数look_up_location_code用来提取指定城市的位置代码get_weather则根据指定位置代码获取所在地的天气信息。当我们调用这个Agent查询指定城市的天气时正常的执行轨迹应该是LLM在接收到作为原始查询的HumanMessage后利用返回的AIMessage下发针对look_up_location_code工具函数的调用作为参数的city为解析出来的城市名Agent在本地完成工具执行后将城市对应的位置代码以ToolMessage的形式发送给LLMLLM再次以AIMessage的形式下发针对get_weather工具函数的调用参数为得到的位置代码Agent执行工具将天气信息以ToolMessage的形式上传LLMLLM将获得的天气信息整理成最终的答复并以AIMessage的形式返回给Agent。importjson,asynciofromlangchain.agentsimportcreate_agentfromlangchain.toolsimporttoolfromlangchain_openaiimportChatOpenAIfromopenevalsimportcreate_async_trajectory_match_evaluatorfromlangchain_core.messagesimportHumanMessage,AIMessage,ToolMessagefromdotenvimportload_dotenv load_dotenv()tooldeflook_up_location_code(city:str)-str:提取指定城市的位置代码 Args: city: 城市名称 Returns: 指定城市对应的位置代码 returnlocation-123tooldefget_weather(location_code:str)-str:提取指定位置代码所在地的天气 Args: location_code: 位置代码 Returns: 天气信息 return晴气温25度agentcreate_agent(modelChatOpenAI(modelgpt-5.4-mini),tools[look_up_location_code,get_weather])referenced_messages[HumanMessage(...),AIMessage(content,tool_calls[{name:look_up_location_code,args:{city:苏州},id:call-001}]),ToolMessage(contentlocation-123,tool_call_idcall-001),AIMessage(content,tool_calls[{name:get_weather,args:{location_code:location-123},id:call-002}]),ToolMessage(content...,tool_call_idcall-002),AIMessage(content...)]asyncdefmain():resultawaitagent.ainvoke(input{messages:[{role:user,content:今天苏州是晴天吗}]})messagesresult.get(messages)evaluatorcreate_async_trajectory_match_evaluator()resultawaitevaluator(outputsmessages,reference_outputsreferenced_messages)print(json.dumps(result,indent2))asyncio.run(main())由于消息的内容都不在评估范围之内所以在指定作为评估基准的referenced_messages时我们没有指定任何一个消息的内容但需要保证tool_calls中每个工具调用在名称和参数列表的准确性最终会得到如下的评估结果{key:trajectory_strict_match,score:true,comment:null,metadata:null}现在我们对referenced_messages略加改动将第二个AIMessage的工具调用location_code参数的从location-123改成location-999评估将会失败。referenced_messages[HumanMessage(...),AIMessage(content,tool_calls[{name:look_up_location_code,args:{city:苏州},id:call-001}]),ToolMessage(contentlocation-123,tool_call_idcall-001),AIMessage(content,tool_calls[{name:get_weather,args:{location_code:location-999},id:call-002}]),ToolMessage(content...,tool_call_idcall-002),AIMessage(content...)]输出{key:trajectory_strict_match,score:false,comment:null,metadata:null}