himga.eval — API 设计与使用说明#
版本:基于当前实现(2026-04-22) 模块路径:
src/himga/eval/
目录#
1. 概述#
himga.eval 驱动完整评测循环,输出与 MAGMA Table 1(LoCoMo)和 Table 2(LongMemEval)对齐的分类指标。
三层结构
层 |
文件 |
职责 |
|---|---|---|
Runner |
|
驱动预测循环,收集 |
Judge |
|
LLM-as-a-Judge 评分,支持多种评分模式与结果缓存 |
Metrics |
|
12 项指标聚合,按 |
三层故意分离:runner 跑完后,judge 和 metrics 可离线反复调用,重跑指标不需要重新跑模型。
2. 模块结构#
src/himga/eval/
├── __init__.py # 导出 EvalResult, run_eval, compute_metrics
├── runner.py # 评测主循环
├── judge.py # LLM-as-a-Judge 打分
└── metrics.py # 12 项指标聚合
公共导入路径
from himga.eval import EvalResult, run_eval, compute_metrics
from himga.eval.judge import judge_answer, batch_judge, is_unanswerable
from himga.eval.metrics import token_f1, ALL_METRICS
3. 安装#
核心安装(包含 exact_match、F1、ROUGE、BLEU、METEOR):
pip install himga
# 或
uv add himga
完整指标(额外包含 BERTScore、Sentence-BERT):
pip install "himga[eval]"
# 或
uv add "himga[eval]"
GPU 加速(BERTScore / SBERT 自动检测 CUDA,无需代码改动):
# 先安装 CUDA 版 PyTorch,再安装 eval extras
pip install torch --index-url https://download.pytorch.org/whl/cu118
pip install "himga[eval]"
开发环境(uv sync --dev)默认安装所有依赖,包含完整 eval extras。
4. 类型参考#
EvalResult#
@dataclass
class EvalResult:
sample_id: str
question_id: str
question_type: QuestionType
question: str
ground_truth: str
prediction: str
单个 QA pair 的预测记录,是 runner → judge → metrics 流水线的数据载体。
字段 |
类型 |
说明 |
|---|---|---|
|
|
来自 |
|
|
来自 |
|
|
问题类型,决定 judge 模式并用于 |
|
|
问题文本,传给 judge prompt |
|
|
标准答案 |
|
|
Agent 生成的答案 |
5. 函数参考#
5.1 run_eval(runner.py)#
def run_eval(
dataset: list[Sample],
agent: BaseAgent,
*,
show_progress: bool = True,
) -> list[EvalResult]
驱动预测循环,每个 Sample 前 reset memory,然后依次回答所有 QA pair。
注意:run_eval 不调用 judge,不计算指标,只收集预测结果。
5.2 is_unanswerable(judge.py)#
def is_unanswerable(text: str) -> bool
规则匹配:判断 text 是否表达了”无法回答”的意思。用于 LoCoMo ADVERSARIAL 类型问题的 judge,无需 LLM 调用。
匹配 "", "n/a", "unanswerable" 等精确值,以及 "not mentioned", "cannot answer", "no information" 等 14 个短语模式。
5.3 judge_answer(judge.py)#
def judge_answer(
question: str,
ground_truth: str,
prediction: str,
*,
llm: BaseLLMClient | None = None,
mode: str = "continuous",
) -> float
判断 prediction 是否正确,返回 [0.0, 1.0] 的分数。
mode 参数
模式 |
返回值 |
适用类型 |
说明 |
|---|---|---|---|
|
|
LoCoMo SINGLE_HOP / MULTI_HOP / TEMPORAL / OPEN_DOMAIN |
LLM 返回 JSON |
|
|
LoCoMo ADVERSARIAL |
规则匹配,不调用 LLM; |
|
|
LongMemEval TEMPORAL_REASONING |
允许 off-by-one 日期误差 |
|
|
LongMemEval KNOWLEDGE_UPDATE |
包含最新答案即正确,即使同时包含旧信息 |
|
|
LongMemEval SINGLE_SESSION_PREFERENCE |
按 rubric 判断,不要求完全覆盖 |
|
|
LongMemEval SINGLE_SESSION_USER / ASSISTANT / MULTI_SESSION |
通用二元判断 |
注意:mode="adversarial" 时 llm 可为 None;其他模式要求传入 llm,否则抛出 ValueError。
5.4 batch_judge(judge.py)#
def batch_judge(
results: list[EvalResult],
*,
llm: BaseLLMClient,
mode: str = "auto",
cache_path: Path | None = None,
) -> list[float]
批量 judge,支持结果缓存到本地 JSON 文件。
mode="auto"(推荐):根据每条结果的 question_type 自动选择对应的 judge 模式,无需手动指定。映射关系:
QuestionType |
自动选择的 mode |
|---|---|
SINGLE_HOP / MULTI_HOP / TEMPORAL / OPEN_DOMAIN |
|
ADVERSARIAL |
|
TEMPORAL_REASONING |
|
KNOWLEDGE_UPDATE |
|
SINGLE_SESSION_PREFERENCE |
|
SINGLE_SESSION_USER / ASSISTANT / MULTI_SESSION |
|
缓存行为:若 cache_path 指向已存在的文件且包含所有 question_id,直接从缓存读取,不调用 LLM;否则补充计算缺失项并更新文件。
5.5 compute_metrics(metrics.py)#
def compute_metrics(
results: list[EvalResult],
judge_scores: list[float],
*,
metrics: tuple[str, ...] | list[str] | None = None,
) -> dict
将预测结果和 judge 分数聚合为多维指标。
metrics 参数:控制计算哪些指标。None(默认)计算全部 12 项。传入子集可跳过重量级模型:
# 跳过 BERTScore / SBERT,快速运行
out = compute_metrics(results, scores, metrics=("judge_score", "f1", "rouge1"))
返回结构
{
"overall": {
"judge_score": float,
"exact_match": float,
"f1": float,
"rouge1": float,
"rouge2": float,
"rougeL": float,
"bleu1": float,
"bleu2": float,
"bleu4": float,
"meteor": float,
"bert_f1": float, # 需要 himga[eval]
"sbert_similarity": float, # 需要 himga[eval]
},
"by_type": {
"single_hop": {
"judge_score": float, ..., "count": int
},
# 仅包含 results 中实际出现的 QuestionType
}
}
空输入:results=[] 时返回全零 overall 和空 by_type,不抛异常。
6. 指标一览#
指标 |
依赖 |
范围 |
说明 |
|---|---|---|---|
|
LLM(外部) |
[0.0, 1.0] |
LLM-as-a-Judge 评分 |
|
无 |
{0.0, 1.0} |
大小写不敏感完全匹配 |
|
无 |
[0.0, 1.0] |
Token-level F1(SQuAD 风格,标点替换为空格) |
|
|
[0.0, 1.0] |
ROUGE-1 F1 |
|
|
[0.0, 1.0] |
ROUGE-2 F1 |
|
|
[0.0, 1.0] |
ROUGE-L F1 |
|
|
[0.0, 1.0] |
BLEU-1,SmoothingFunction.method1 |
|
|
[0.0, 1.0] |
BLEU-2 |
|
|
[0.0, 1.0] |
BLEU-4 |
|
|
[0.0, 1.0] |
METEOR |
|
|
[0.0, 1.0] |
BERTScore F1,模型 |
|
|
[-1.0, 1.0] |
Sentence-BERT 余弦相似度,模型 |
bert_f1 和 sbert_similarity 未安装 eval extras 时调用会抛出 ImportError,并提示安装命令。
7. 评测流水线#
load_dataset("locomo")
│
▼
list[Sample]
│
▼ run_eval(dataset, agent)
│ ├── agent.memory.reset() ← 每个 sample 前
│ ├── agent.ingest_sample(sample)
│ └── agent.answer(qa.question) ← 每个 QA pair
│
▼
list[EvalResult]
│
├─────────────────────────────────────────────┐
▼ ▼
batch_judge(results, llm, cache_path) compute_metrics(results, [0.0]*n)
(mode="auto",按 question_type 路由) (离线立即计算 F1/ROUGE/BLEU/METEOR)
│
▼
list[float] (judge_scores)
│
▼
compute_metrics(results, judge_scores)
│
▼
{"overall": {...}, "by_type": {...}}
8. 使用示例#
完整评测流水线(LoCoMo)#
from pathlib import Path
from himga.data import load_dataset
from himga.agent import BaseAgent
from himga.memory import NullMemory
from himga.llm import AnthropicClient
from himga.eval import run_eval, compute_metrics
from himga.eval.judge import batch_judge
agent_llm = AnthropicClient(model="claude-sonnet-4-6")
judge_llm = AnthropicClient(model="claude-haiku-4-5-20251001")
cache = Path("outputs/locomo_judge_cache.json")
samples = load_dataset("locomo")
agent = BaseAgent(memory=NullMemory(), llm=agent_llm)
results = run_eval(samples, agent=agent, show_progress=True)
judge_scores = batch_judge(results, llm=judge_llm, cache_path=cache) # mode="auto"
metrics = compute_metrics(results, judge_scores)
print(f"Overall judge={metrics['overall']['judge_score']:.3f} "
f"f1={metrics['overall']['f1']:.3f} "
f"rouge1={metrics['overall']['rouge1']:.3f}")
快速指标(跳过重量级模型)#
# 不需要 himga[eval],无 LLM 调用
fast_metrics = ("exact_match", "f1", "rouge1", "rouge2", "rougeL", "bleu1", "meteor")
metrics = compute_metrics(results, [0.0] * len(results), metrics=fast_metrics)
print(f"F1={metrics['overall']['f1']:.3f} ROUGE-1={metrics['overall']['rouge1']:.3f}")
judge 缓存节省 API 费用#
cache = Path("outputs/lme_judge_cache.json")
# 第一次:调用 LLM,结果写入缓存
scores1 = batch_judge(results, llm=judge_llm, cache_path=cache)
# 第二次(如修改了聚合逻辑):从缓存读取,不调用 LLM
scores2 = batch_judge(results, llm=judge_llm, cache_path=cache)
离线重算指标#
import json
from dataclasses import asdict
from himga.data.schema import QuestionType
from himga.eval import EvalResult
# 保存
with open("outputs/results.json", "w") as f:
json.dump([asdict(r) for r in results], f, indent=2)
# 重建
with open("outputs/results.json") as f:
raw = json.load(f)
results = [
EvalResult(**{**r, "question_type": QuestionType(r["question_type"])})
for r in raw
]
scores = batch_judge(results, llm=judge_llm, cache_path=Path("outputs/cache.json"))
metrics = compute_metrics(results, scores)
9. 指标对齐说明#
Judge Score#
数据集 |
问题类型 |
Judge 模式 |
说明 |
|---|---|---|---|
LoCoMo |
SINGLE_HOP / MULTI_HOP / TEMPORAL / OPEN_DOMAIN |
|
0.0–1.0 连续评分,对齐 MAGMA rubric |
LoCoMo |
ADVERSARIAL |
|
规则匹配,无 LLM 调用 |
LongMemEval |
TEMPORAL_REASONING |
|
允许 ±1 日期误差 |
LongMemEval |
KNOWLEDGE_UPDATE |
|
最新答案优先 |
LongMemEval |
SINGLE_SESSION_PREFERENCE |
|
Rubric 部分匹配 |
LongMemEval |
其余类型 |
|
通用二元判断 |
与 MAGMA Table 1 / Table 2 的对应关系#
MAGMA 列名 |
HiMGA 对应字段 |
|---|---|
Score (Judge) |
|
Score (F1) |
|
各行(问题类型) |
|
10. 设计决策说明#
为什么 judge 支持多模式而非统一二元?#
LoCoMo 和 LongMemEval 的评测协议不同:LoCoMo 用 0–1 连续分衡量语义相似度,LongMemEval 用二元 yes/no 并针对不同题型有专用 prompt(如允许 off-by-one 的 temporal_reasoning、优先最新答案的 knowledge_update)。统一模式会降低对齐精度,mode="auto" 让调用方无需关心细节。
为什么 bert_f1 和 sbert_similarity 是可选 extra?#
两者分别需要 ~1.3 GB 和 ~80 MB 的预训练模型,下载时间长、不适合所有环境。将其设为 [eval] extras 使核心安装保持轻量。未安装时调用会立即抛出带安装指引的 ImportError,而非静默返回 0.0(避免误解结果)。
为什么 compute_metrics 有 metrics 参数?#
开发和测试阶段通常不需要 BERTScore / SBERT,但默认加载会拖慢测试速度。metrics 参数允许按需计算,@pytest.mark.slow 测试才触发重量级模型。
为什么 runner 不内嵌 judge?#
Judge 有网络延迟、API 费用、结果可缓存复用。分离后修改指标聚合逻辑不需要重跑 judge;重跑 runner 也不需要重新缓存 judge 结果。
为什么 judge 缓存以 question_id 为键?#
question_id 在同一数据集内唯一,新增样本时可增量更新缓存,不需要失效全部旧缓存。