跳到主要内容

Phoenix evals:给 LLM 输出打分

本章讲什么: phoenix-evals 是一个可独立 pip 安装的子包(packages/phoenix-evals/),不依赖 Phoenix 服务也能用。它提供"评估器(Evaluator)"的统一抽象,以及把任意数据形状对接到评估器的 input_mapping 机制。本章讲它的三块积木:ScoreEvaluatorClassificationEvaluator

3.1 这是什么:打分的统一形状

问题: "这次回答好不好"有无数种判法——有没有幻觉、相不相关、有没有毒性、引用对不对。每种判法输入不同(有的要 question+answer,有的要 documents),输出也想统一(都能存进 Phoenix 当标注)。

思路: 用两个抽象统一一切。

  • Score = 一次评估的结果,统一形状(evaluators.py:159):namescore(数值)、label(类别)、explanation(解释)、metadatakind(human/llm/heuristic/code)、direction(maximize/minimize)。任何评估器都吐 Score,所以下游统一处理。
  • Evaluator = 一个评估器(evaluators.py:303),抽象基类,核心方法是 evaluate(eval_input)List[Score]

最小用法(来自 README):

# 示意,基于 packages/phoenix-evals README + evaluators.py
from phoenix.evals import create_classifier
from phoenix.evals.llm import LLM

llm = LLM(provider="openai", model="gpt-4o")
sentiment = create_classifier(
name="sentiment",
prompt_template="Classify the sentiment: {text}",
llm=llm,
choices=["positive", "negative", "neutral"],
)
result = sentiment.evaluate({"text": "I love this product!"})
print(result[0].label) # "positive"
print(result[0].explanation) # LLM 的理由

3.2 核心机制:input_mapping —— 把任意数据形状"绑"到评估器

要解决的小问题: 一个相关性评估器内部要的是字段 questionanswer。但你的数据里可能叫 queryresponse,或者嵌在 record["input"]["q"] 里。怎么对接而不强迫你改数据?

思路: 评估器声明它需要哪些字段(从 prompt 模板变量或 input_schema 推断);你提供一个 input_mapping 说"我的数据里这个字段对应你要的那个字段",值可以是键名、JSONPath 风格路径,或一个取值函数。

Evaluator.evaluate 的骨架(evaluators.py:419-437):

# src/.../evaluators.py:419-437 (节选)
def evaluate(self, eval_input, input_mapping=None):
input_mapping = input_mapping or self._input_mapping # 1. 用本次或已绑定的映射
required_fields = self._get_required_fields(input_mapping) # 2. 我需要哪些字段
remapped = _remap_and_validate_input( # 3. 按映射抽取并校验
eval_input, required_fields, input_mapping=input_mapping, ...)
# 4. 用 remapped 跑实际评估,产出 List[Score]

remap 与校验的实现转交 utils.remap_eval_input(evaluators.py:39 导入),并用 Pydantic 校验抽出来的字段。

bind:固定映射复用。 如果你要在一批同形状数据上反复用同一映射,可以 bind 一次,之后 evaluate 不必再传:

# src/.../evaluators.py:459-465
def bind(self, input_mapping):
self._input_mapping = input_mapping
def unbind(self):
self._input_mapping = None

直觉: 评估器是个"插座"(规定了几个孔的形状),input_mapping 是"转接头"——你的数据无论原来什么插头,接上转接头就能插进去。bind 就是把转接头焊死。

3.3 核心机制:ClassificationEvaluator —— 一次结构化打分

这是最常用的评估器(evaluators.py:601)。给它一个 prompt 模板和一组 choices,它让 LLM 做选择题并要求结构化输出。

choices 有三种写法,表达力递增:

choices 形式例子产出的 Score
List[str]["positive","negative"]只有 label,score 为 None
Dict[str, number]{"good": 1, "bad": 0}label + 对应 score
Dict[str, (number, desc)]{"good": (1, "...")}label+score,desc 进 prompt 帮 LLM 理解(文档注明不太可靠)

(三种形式的语义见 evaluators.py:615-623 的 docstring。)

为什么要结构化输出: 评估器要的是"label + 可选 explanation",而不是一段自由文本。ClassificationEvaluator 依赖 LLM 的 tool calling / structured output 能力(docstring 在 evaluators.py:606 明确要求),把 LLM 回答约束成一个 schema,这样 label 一定是 choices 之一、explanation 单独成字段——可靠可解析。

便捷工厂 create_classifier(evaluators.py:1133)是它的薄包装,就是把上面几个参数转构造函数,让常见用法一行搞定。

3.4 巧妙之处:评估器自带 OTel 追踪

README 强调"Evaluators are natively instrumented via OpenTelemetry"。代码里,真正被 @trace 装饰的是内部包装方法 _traced_evaluate(同步,evaluators.py:383)和 _async_traced_evaluate(异步,evaluators.py:401);公开的 evaluate(evaluators.py:419)在校验/重映射之后调用 self._traced_evaluate,由它来真正产 span(@trace 来自 from .tracing import trace,evaluators.py:34)。配套一组帮助函数把评估器元数据、remapped 输入、输出 Score 序列化成 span 属性(evaluators.py:91-120)。

这是个漂亮的闭环: 用 Phoenix-evals 打分这件事本身,也会产生 span 发回 Phoenix——于是"打分过程"也变成可观测、可curation的数据。评估器既是 Phoenix 的消费者也是生产者。

3.5 LLM 适配器:不绑死一家

evals 通过 adapter 支持多家模型 SDK,目录 packages/phoenix-evals/src/phoenix/evals/llm/adapters/ 下有 openaianthropicgooglelitellmlangchain 各自的 adapter.py + factories.pyLLM 包装类(llm/wrapper.py)对上层暴露统一接口,底层按 provider 选适配器。所以同一个评估器换个 LLM(provider=...) 就能换裁判模型。

3.6 边界与坑

  • 依赖 LLM 的结构化输出能力。 ClassificationEvaluator 在不支持 tool calling / structured output 的模型上不可靠(docstring 明确提醒)。
  • (score, description) 形式不推荐。 docstring 直说 LLM 不能可靠遵守这种 schema(evaluators.py:620-623)。
  • input_mapping 的取值若用函数,是任意 Python。 与服务端的过滤 DSL 不同,这里在你自己的进程里跑,不是不可信输入,所以没做 AST 沙箱。

3.7 代码地图

主题文件符号
结果对象packages/phoenix-evals/src/phoenix/evals/evaluators.pyScoreScore.to_dict
评估器基类同上EvaluatorEvaluator.evaluate_traced_evaluatebind_get_required_fields
分类评估器同上ClassificationEvaluatorcreate_classifier
input 重映射packages/phoenix-evals/src/phoenix/evals/utils.pyremap_eval_input
自动追踪packages/phoenix-evals/src/phoenix/evals/tracing.pytrace
LLM 适配器packages/phoenix-evals/src/phoenix/evals/llm/LLM(wrapper.py)、各 adapters/*/adapter.py
内置指标packages/phoenix-evals/src/phoenix/evals/metrics/hallucinationfaithfulnesstool_selection