Code 评估器

更新时间:
复制 MD 格式

当质量标准可以被代码明确表达时,把评判逻辑交给一段代码比交给大模型更划算。Code 评估器是 AgentLoop 评估器 家族中的确定性成员:与依赖大模型推理的 Agent 评估器、LLM 评估器不同,它执行用户编写的 Python 或 JavaScript 代码,在沙箱中逐条打分。同一份数据反复评估,结果完全一致,不受模型温度、版本升级或 Prompt 措辞的影响;它也没有推理开销,不消耗模型调用,适合承担回归防线中那些“对错分明”的检查项。

典型使用场景

结构化输出校验是最常见的用途,例如要求 Agent 返回合法 JSON 且必须包含指定字段,缺字段即判 0 分。数值类答案的比对同样适合,如金额、数量、计算结果,可以在代码中设定容差区间,而不必让模型去“感觉”是否接近。此外还有以下场景:

  • 敏感词与禁用表述的黑名单扫描。

  • 输出长度与格式规范检查,如是否包含 Markdown 表格、是否以指定前缀开头。

  • 工具调用序列核对,即是否按预期顺序调用了必需工具。

  • 引用完整性检查,即回答中的引用编号是否都能在检索片段中找到。

相对地,依赖大模型推理的主观判断不适合用 Code 评估器,应选择 Agent 评估器或 LLM 评估器。

支持地域

Code 评估器当前支持以下地域:

地域名称

Region ID

华东 1(杭州)

cn-hangzhou

华东 2(上海)

cn-shanghai

华北 2(北京)

cn-beijing

华南 1(深圳)

cn-shenzhen

中国香港

cn-hongkong

新加坡

ap-southeast-1

创建 Code 评估器

使用 Code 评估器的完整流程为:创建评估器 → 编写评估代码 → 创建评估任务并配置字段映射 → 预览测试 → 提交运行并查看结果。前两步分别对应本节与“编写评估代码”,后续步骤见“在评估任务中使用”与“预览测试、运行与结果查看”。开始操作前,请确认 AgentLoop 控制台所在地域属于支持地域

进入 AgentLoop 控制台,在左侧导航栏单击评估,切换到顶部的评估器页签,单击右上角创建评估器

第一步填写基础信息。评估器名称是全局唯一标识,必须以英文字母开头,仅可包含英文字母、数字、下划线和短横线,创建后不可修改;评估器类型选择 Code。显示名称、描述、标签均为选填,其中标签用于在评估器列表中按维度检索,建议为 Code 评估器统一打上便于筛选的标签。

配置项

是否必填

说明

评估器名称

必填

全局唯一标识,用于 API 调用与系统索引,创建后不可修改

评估器类型

必填

选择 Code

显示名称

选填

面向用户展示的友好名称,不填则展示评估器名称

描述

选填

说明该评估器的判定口径,便于团队复用

标签

选填

输入后按回车添加,支持按标签检索

单击创建评估器后进入编辑页,页面包含基础信息评估代码两个页签。基础信息中评估器名称与类型为只读,其余字段可继续修改;核心配置在评估代码页签中完成。

编写评估代码

评估代码页签右上角可切换 Python 与 JavaScript,两者互斥,切换后编辑区文件名会相应变为 code.pycode.js。单击重置为模板可载入官方默认模板,作为起点。代码的唯一约定是:定义一个名为 evaluate 的函数,接收数据字段作为入参,返回一个包含 scoreexplanation 的对象。

Python

Python 默认模板如下。

def evaluate(input, output, context=None):
    # Evaluate one record and return score and explanation.
    return {
        "score": 0,
        "explanation": "Describe why this score was assigned",
    }

JavaScript

JavaScript 默认模板如下。

function evaluate(input, output, context) {
  // Evaluate one record and return score and explanation.
  return {
    score: 0,
    explanation: 'Describe why this score was assigned',
  }
}
module.exports = evaluate
重要

末尾的 module.exports 是必需的,缺失会导致平台无法加载该函数。

返回值契约

evaluate 函数每次处理一条数据项,返回结果必须包含以下两个字段。

字段

类型

说明

score

数值

该条数据的得分。取值区间由代码自行决定,建议在同一评估器内保持稳定,便于跨版本对比

explanation

字符串

打分理由,会展示在评估结果的评估理由中,是排查误判的主要依据

评估结果页会同时展示原始分数、分数范围与归一化分数,因此建议 score 使用固定区间(例如二值制的 0 与 1,或百分制的 0 到 100),避免同一评估器在不同数据上返回不同量纲的分值。explanation 应写明触发该分数的具体原因,例如缺失了哪个字段、命中了哪个敏感词,而不是笼统的“不符合要求”。

变量映射规则

编辑区下方的变量映射表格由平台自动解析 evaluate 函数的入参生成,无需手工声明。规则是:没有默认值的参数为必填变量,有默认值的参数为可选变量。

以 Python 模板为例,inputoutput 因无默认值被标记为必填context 因带有默认值 None 被标记为可选。JavaScript 中不存在默认值语法时,三个参数都会被判定为必填,若希望某个变量可选,可写作 function evaluate(input, output, context = null)

参数名不限于 input(必有)、output(必有)、context,返回参数 score(必有)与 explanation(必有),其他可自定义。声明后,这些变量会出现在创建评估任务时的字段映射中,逐一指定其从哪个数据字段取值即可。还可以在变量映射表中为每个变量填写描述,说明该变量期望接收什么内容。配置完成后单击右上角保存

常见检查场景的示例代码集中在代码示例中,涵盖格式校验、数值容差比对、工具调用核对与扣分制质检。

在评估任务中使用

保存评估器后,需要通过评估任务把它作用到实际数据上。在评估模块的评估任务页签单击新建任务,流程分为两步。

第一步数据配置填写任务名称与描述,选择数据来源。数据来源支持链路、Agent 轨迹、日志与数据集四种。选择链路时还需指定评估粒度(单轮对话或多轮对话),可选择是否包含前轮输入和输出,并可通过 Agent 智能体下拉框与数据过滤表达式缩小评估范围。

第二步选择评估器是关键。页面上方的预览数据区域会展示一条真实样本的全部可用字段,单击换一条可切换样本,便于确认数据形态。左侧评估器列表切换到自定义页签,勾选刚创建的 Code 评估器;右侧字段映射区域会列出该评估器的全部变量,逐个选择对应的数据字段。以链路数据源为例,可映射的字段包括以下几类。

字段

内容

trace.input

用户的原始提问

trace.output

Agent 的最终回答

trace.tool_context

工具调用的参数、返回结果及上下文

trace.agent_trajectory

Agent 内部执行链路

trace.rag_context

RAG 检索上下文

trace.rag_retrieval_queries

RAG 检索的查询语句

trace.rag_retrieval_documents

RAG 检索到的文档片段

trace.rag_llm_output

RAG 链路中模型的输出

traceId

链路唯一标识

必填变量全部映射完成后,评估器卡片上会出现已就绪标记。

预览测试、运行与结果查看

正式运行前建议先做一次预览测试。单击页面底部的运行测试,右侧抽屉会展示当前样本的字段取值,再次单击抽屉内的运行测试即可对已选中的评估器批量试跑。执行完成后,评估结果区域会显示每个评估器的状态、得分与评估理由。这一步能在消耗正式额度之前暴露大多数问题,例如字段映射选错、代码在真实数据形态上抛异常、或 score 与预期口径不一致。

确认无误后单击创建并运行提交任务。任务详情页包含概览运行记录评估结果三个页签。运行记录展示每次执行的总数、完成率、成功与失败条数、耗时与平均分;评估结果页提供明细视图与数据项视图,明细视图逐条列出数据项内容、分数、分数范围、归一化分数、状态、评估器、评估耗时与生成时间,并支持按状态、评估器、得分范围与时间窗口过滤。Code 评估器的单条执行耗时通常在十余秒量级,任务内多条数据并发执行,总时长不随数据条数线性累加,百条规模的任务整体耗时约一分钟。

代码示例

以下示例覆盖四类常见检查项,可直接复制到评估代码编辑区,按需调整后使用。

格式校验

要求 Agent 输出为合法 JSON,且必须包含 titleitems 两个字段,items 至少有一项。

def evaluate(input, output, context=None):
    import json

    text = output if isinstance(output, str) else json.dumps(output, ensure_ascii=False)

    try:
        data = json.loads(text)
    except Exception as e:
        return {"score": 0, "explanation": "输出不是合法 JSON:%s" % e}

    if not isinstance(data, dict):
        return {"score": 0, "explanation": "输出 JSON 顶层不是对象"}

    missing = [k for k in ("title", "items") if k not in data]
    if missing:
        return {"score": 0, "explanation": "缺失必需字段:%s" % ", ".join(missing)}

    if not isinstance(data["items"], list) or len(data["items"]) == 0:
        return {"score": 0, "explanation": "items 不是数组或为空数组"}

    return {"score": 1, "explanation": "JSON 合法且包含 title 与 %d 条 items" % len(data["items"])}

数值容差比对

比对 Agent 给出的数值答案与标准答案,允许千分之一的相对误差。expected 是自定义变量,在创建评估任务时映射到数据集中的标准答案列。

def evaluate(output, expected, tolerance=0.001):
    import re

    def to_number(v):
        if isinstance(v, (int, float)):
            return float(v)
        m = re.search(r"-?\d+(?:\.\d+)?", str(v).replace(",", ""))
        return float(m.group()) if m else None

    got, want = to_number(output), to_number(expected)

    if want is None:
        return {"score": 0, "explanation": "标准答案中未解析到数值"}
    if got is None:
        return {"score": 0, "explanation": "模型输出中未解析到数值"}

    denominator = abs(want) if want != 0 else 1.0
    diff = abs(got - want) / denominator

    if diff <= tolerance:
        return {"score": 1, "explanation": "数值匹配:期望 %g,实际 %g" % (want, got)}
    return {"score": 0, "explanation": "数值偏差超限:期望 %g,实际 %g,相对误差 %.2f%%" % (want, got, diff * 100)}

注意 tolerance 带有默认值,因此在变量映射中会被标记为可选。若不在任务中映射该变量,代码将使用默认值。

工具调用核对

检查 Agent 是否调用了必需工具。该示例将工具上下文序列化为字符串后,检查每个必需工具名是否在其中出现,context 通常映射到链路数据的 trace.tool_context。需要注意,这种匹配方式不区分工具名的出现位置:如果必需工具名恰好出现在其他工具调用的参数或返回内容中,也会被计入“已调用”,结果可能偏高,评估结论需结合真实数据复核。

def evaluate(output, context=None, required_tools="search,summarize"):
    import json

    expected = [t.strip() for t in str(required_tools).split(",") if t.strip()]
    if not expected:
        return {"score": 1, "explanation": "未声明必需工具,默认通过"}

    raw = context if isinstance(context, str) else json.dumps(context or {}, ensure_ascii=False)

    called = set()
    for name in expected:
        if name in raw:
            called.add(name)

    missing = [t for t in expected if t not in called]
    hit_rate = len(called) / len(expected)

    if not missing:
        return {"score": 1, "explanation": "全部必需工具均已调用:%s" % ", ".join(expected)}
    return {
        "score": round(hit_rate, 2),
        "explanation": "缺少工具调用:%s(命中 %d/%d)" % (", ".join(missing), len(called), len(expected)),
    }

扣分制质检

JavaScript 版本,采用百分制扣分模型,同时检查敏感表述、输出长度与格式要求,一次性给出综合分与扣分明细。

function evaluate(input, output, context = null) {
  const text = typeof output === 'string' ? output : JSON.stringify(output)
  const banned = ['稳赚不赔', '保证收益', '百分之百', '绝对安全']

  let score = 100
  const reasons = []

  const hits = banned.filter((w) => text.includes(w))
  if (hits.length > 0) {
    score -= 40 * hits.length
    reasons.push(`命中禁用表述:${hits.join('、')}`)
  }

  if (text.trim().length < 20) {
    score -= 30
    reasons.push(`回答过短,仅 ${text.trim().length} 字`)
  }

  if (!/风险提示|仅供参考/.test(text)) {
    score -= 20
    reasons.push('缺少风险提示语')
  }

  score = Math.max(0, score)

  return {
    score,
    explanation: reasons.length === 0 ? '通过全部质检项' : reasons.join(';'),
  }
}
module.exports = evaluate

编写建议

编写评估代码时可参考以下建议:

  • 先归一化输出类型output 的实际类型并不总是字符串,链路数据中的 trace.output 可能是列表或字典,因此在做正则或子串匹配之前,务必先做类型归一:Python 中用 json.dumps(output, ensure_ascii=False) if not isinstance(output, str) else output,JavaScript 中用 typeof output === 'string' ? output : JSON.stringify(output)

  • 把异常转化为低分与说明:代码中所有可能抛异常的操作都应被捕获并转化为一个明确的低分与说明,而不是让函数崩溃。JSON 解析失败、类型转换失败、索引越界都是真实数据中的常态,把它们表达为零分加原因,远比一条执行失败记录更有价值。

  • 固定评分口径:同一个评估器在不同数据上返回的分值区间必须一致,否则跨版本对比与看板聚合都会失真。二值判断用 0 和 1,程度判断用固定的百分制或 0 到 1 的连续值,不要混用。explanation 要写得足够具体,把判定依据里的关键证据带上,例如缺失的字段名、命中的敏感词、实际与期望的数值,这条信息是日后回看评估结果时唯一的线索。

  • 仅用标准库并保持纯函数:建议仅依赖语言标准库实现评估逻辑,把代码保持在纯函数的形态:同样的入参得到同样的结果,不访问外部服务、不依赖执行环境的状态。这既是沙箱环境的现实约束,也是让评估结果可复现、可信任的前提。

  • 用可选参数外置阈值:善用可选参数把阈值外置。像容差、必需工具列表、长度下限这类判定阈值,写成带默认值的函数参数后会成为可选变量,既能在任务中按需覆盖,也让同一份代码可以服务多个评估任务,不必为每次调参克隆一个新评估器。