[基于AgentEvals的自动化评估-04]全面优化面向LangGraph的轨迹评估[下篇]

为了彻底解决AgentEvals针对LagnGraph轨迹评估无法解决同一Superstep内多个节点并发执行的问题,我通过定义一个全新的graph_trajectory_matchgraph_trajectory_match_async函数提供一种更加灵活的评估方案。上篇提供了针对这种方案的编程体验,本篇介绍这这两个函数涉及的轨迹评估究竟是如何实现的。

1. 针对Agent执行轨迹的表达

我们先来回顾一下定义在AgentEvals中的如下这个GraphTrajectory类型,它用来表示作为工作流的Agent的执行轨迹。由于它表示的并非针对Agent的某一次单一调用的执行轨迹,而是站在Thread的角度,将同于Thread中的多次调用轨迹合并在一起 ,所以它的输入、执行结果和执行步骤都是一个列表,而且每个列表的长度是相同的。问题就出在steps字段使用节点名称列表来表示每次Agent调用的执行轨迹,无法确定执行的节点于Superstep之间的关系。

python 复制代码
class GraphTrajectory(TypedDict):
    inputs: Optional[list[dict]]
    results: list[dict]
    steps: list[list[str]]

比如"foo","bar","baz"具有如下几种执行场景:

  • 三个节点在三个不同的Superstep中执行;
  • "foo"和"bar","baz"在两个相邻Superstep中执行;
  • "foo","bar"和"baz"在两个相邻Superstep中执行。

为此我定义了如下这个类似的GraphRunTrajectory,它仅仅表示针对单一Agent调用的执行轨迹。所以输入和执行结果都是一个字典,steps字段则是一个两层列表,第一个对应Superstep,第二个对应具体某个Superstep中执行的节点。

python 复制代码
class GraphRunTrajectory(TypedDict):
    inputs: dict|None
    results: dict|None
    steps: list[list[str]]

针对前面列举的三种执行场景,steps字段可以通过如下的两层列表来表示:

  • [["foo"],["bar"],["baz"]]
  • [["foo"],["bar","baz"]]
  • [["foo","bar"],["baz"]]

2. 将匹配模式纳入评估基准

由于同一个Superstep涉及多个节点的并发执行,由此引入了四种针对单步的节点匹配模式:

  • Exact: 虽然LangGraph无法控制并发节点的执行顺序,但是如果并发节点针对具有依赖关系,也可以通过编程的手段来实现。这种情况下,相当于采用AgentEvals默认的评估模式;
  • Unordered:这是默认的匹配模式,将节点列表视为无序集合(set),元素一样即可;
  • Subset: 将节点列表视为无序集合(set),但要求执行的节点在指定的节点内。如果某个节点具有m个下游节点,只要执行n个节点(n<=m)就可以了,可以采用此模式;
  • Superset:将节点列表视为无序集合(set),但要求执行的节点包含指定的节点。如果某个节点具有m个下游节点,严格要求执行指定的一个或者多个下游节点,可以采用此模式。

我们将匹配模式应用到作为评估基准的GraphRunReferenceTrajectory类型上。GraphRunReferenceTrajectory是针对GraphTrajectory的评估基准,它们之间的不同之处在于其steps字段的定义,这里的集合元素不仅仅可以是表示节点名称列表的list[str]对象,还可以是一个ReferenceStep对象。

python 复制代码
class GraphRunTrajectory(TypedDict):
    inputs: dict|None
    results: dict|None
    steps: list[list[str]]

class ReferenceStep(NamedTuple):
    steps: list[str]
    match_mode: GraphTrajectoryMatchModel = "unordered"

class GraphRunReferenceTrajectory(TypedDict):
    inputs: dict|None
    results: dict|None
    steps: list[ReferenceStep|list[str]]  

ReferenceStep表示针对一个Superstep的评估轨迹基准,steps字段表示节点列表,match_mode表示评估时采用的匹配规则。如果直接使用list[str]对象,意味着使用默认的Unordered匹配模式。

3. 同步轨迹评估

同步版本的轨迹评估实现在如下这个graph_trajectory_match函数中,它具有如下三个参数:

  • outputs : 待评估的执行轨迹,是一个GraphRunTrajectory对象的列表;
  • reference_outputs :执行轨迹评估基准,是一个GraphRunReferenceTrajectory对象的列表;
  • eval_results :是否在评估轨迹的时候验证执行结果是否相同,默认为False

针对单次Agent调用的轨迹评估实现在_scorer函数中,逻辑其实很简单:先验证表示轨迹的列表长度是否相同,在遍历针对每一步的节点列表,根据对应评估基准指定的匹配模式进行对比就可以了。如果开启了eval_results开关,则先比较执行结果是否一致。

python 复制代码
def _scorer(
    *,
    outputs: GraphRunTrajectory,
    reference_outputs: GraphRunReferenceTrajectory,
    eval_results: bool
) -> bool:
    if eval_results and outputs["results"] != reference_outputs["results"]:
        return False

    for out_step, raw_ref_step in zip(outputs["steps"], reference_outputs["steps"]):
        ref_step:ReferenceStep =  raw_ref_step if isinstance(raw_ref_step,ReferenceStep) else ReferenceStep(raw_ref_step, "exact")
        match_mode = ref_step.match_mode
        if match_mode == "exact" and out_step!=ref_step.steps:
            return False
        
        out_step_set = set(out_step)
        ref_step_set = set(ref_step.steps)
        match match_mode:        
            case "unordered":
                if out_step_set != ref_step_set:
                    return False
            case "subset":
                if not out_step_set.issubset(ref_step_set):
                    return False
            
            case "superset":
                if not out_step_set.issuperset(ref_step_set):
                    return False
    return True


def graph_trajectory_match(
    *,
    outputs: list[GraphRunTrajectory],
    reference_outputs: list[GraphRunReferenceTrajectory],
    eval_results: bool = False
) -> EvaluatorResult:
    if outputs is None or reference_outputs is None or len(outputs) != len(reference_outputs):
        raise ValueError("Strict trajectory match requires both outputs and reference_outputs")
    
    matric_name = "graph_trajectory_match" 
    result:EvaluatorResult
    for output, reference in zip(outputs, reference_outputs):
        result =cast(EvaluatorResult, _run_evaluator(
            run_name=matric_name,
            scorer=functools.partial(_scorer,  eval_results = eval_results),
            feedback_key=matric_name,
            outputs=output,
            reference_outputs=reference,
        ))       
        if not cast(bool, result.get("score", False)):
           comment = f"""\
Trajectory not match. 
outputs:{output}
reference_outputs:{reference}
"""
           return { **result, "comment": comment}   # type: ignore  
    return result # type: ignore  

graph_trajectory_match函数会先验证outputsreference_outputs在长度上是否匹配,在从中依次提取出GraphRunTrajectoryGraphRunReferenceTrajectory,通过调用_run_evaluator函数借助_scorer函数实施评估。如果评估没有通过,直接返回生成的EvaluatorResult对象。为了利于Debug,我们会将待评估轨迹和基准轨迹放到comment字段中。

4. 异步轨迹评估

同步版本的轨迹评估实现在如下这个graph_trajectory_match_async函数中,其实现的本质就是利用async_wrapper函数将针对同步函数_scorer的调用转换成异步形式,然后在每次迭代中通过调用_arun_evaluator函数利用async_wrapper函数实施评估。

python 复制代码
async def graph_trajectory_match_async(
    *,
    outputs: list[GraphRunTrajectory],
    reference_outputs: list[GraphRunReferenceTrajectory],
    eval_results: bool = False
) -> EvaluatorResult:    
    async def async_wrapper(**kwargs: Any):
        return _scorer(eval_results=eval_results, **kwargs)

    if outputs is None or reference_outputs is None or len(outputs) != len(reference_outputs) or len(outputs) == 0:
        raise ValueError("Strict trajectory match requires both outputs and reference_outputs")
        
    matric_name = f"graph_trajectory_match" 
    result:EvaluatorResult
    for output, reference in zip(outputs, reference_outputs):
        result =cast(EvaluatorResult, await _arun_evaluator(
            run_name=matric_name,
            scorer=async_wrapper,
            feedback_key=matric_name,
            outputs=output,
            reference_outputs=reference,
        ))       
        if not cast(bool, result.get("score", False)):
            comment = f"""\
Trajectory not match. 
outputs:{output}
reference_outputs:{reference}
"""
            return { **result, "comment": comment}   # type: ignore    
    return result # type: ignore
相关推荐
QCodingDev2 小时前
Spring AI Alibaba ReAct Agent实战:从Tool Calling到Agent,企业AI复杂业务该如何设计?
java·人工智能·agent·ai编程·spring ai
灵析表格2 小时前
灵析表格财务函数深度实用性分析与实操教程
开发语言·ai·json·excel·wps
Json____2 小时前
AI内容创作平台项目源码
人工智能·ai·agent·内容创作·wwwoop.com
Jay-r2 小时前
DeepSeek Harness 极简上手:装好、玩熟、让它自己长新能力
人工智能·windows·ai·github·ai编程·deepseek·harness
冬奇Lab2 小时前
Code Agent 解剖(02):agent 是怎么一轮一轮思考和行动的?
人工智能·llm·agent
小马过河R3 小时前
不只是又一个 Agent 框架:DeepSeek Harness 如何重新定义“可组合”
人工智能·机器学习·系统架构·agent·ai编程·harness
嵌入式学习_force3 小时前
BES2810ZP深度解析
ai·蓝牙·bes2810
lifallen3 小时前
DeepSeek Harness:把 Agent 做成可替换的运行时插件树
人工智能·学习·ai·开源软件·ai编程
刀锋00013 小时前
从0到1手搓生产级 AI Agent:LangGraph 1.2 + LangChain 1.3 保姆级实战(全部代码已跑通)
人工智能·python·langchain·ai agent·langgraph