面向心智理论(Theory-of-Mind)及相关社会认知基准的标准化 QA 评测 + 数据治理框架。
围绕三条流水线,配合一套报告工具,全部用根目录的 python run_*.py 一键驱动,配置即用、无需改代码:
| 流水线 | 入口 | 配置 | 作用 |
|---|---|---|---|
| 评测 | python run_eval.py |
experiment_config.yaml |
对模型在各数据集上跑预测 → 判分 → 出图 |
| 数据质量评估 | python run_filter.py |
filter/config.yaml |
用决策树(pass@k / 可回答性 / shortcut)评估并自动修复数据 |
| 数据合成 | python run_feedback.py |
feedback/config.yaml |
从模型 bad case 诊断薄弱维度 → 合成新训练数据 |
| 报告 | python report/generate_*.py |
report/tables_config.yaml |
把评测结果汇总成 Markdown 表格 / HTML 报告 |
- 安装
- 标准数据格式
- 一、评测流水线
run_eval.py - 二、数据质量评估
run_filter.py - 三、数据合成
run_feedback.py - 仓库结构
- 新增数据集 / 模型
- 常见问题
- 文档索引
pip install -r requirements.txt框架支持任何兼容 OpenAI API 的模型(本地 vLLM、DeepSeek、OpenAI、通义千问等)。
所有数据集在评测前都已归一化为统一 schema,存放在 datasets/<数据集名>/(parquet):
{
"story": "背景故事文本",
"question": "问题文本",
"answer": {
"correct_answers": ["正确答案"],
"wrong_answers": ["错误选项1", "错误选项2"]
},
"meta": {
"id": "可选的样本ID",
"...": "其他分组字段(如 ability / dimension / lang)"
}
}correct_answers永远是字符串列表。wrong_answers为空 → 开放题(模型输出自由文本,由 judge 判分)。wrong_answers非空 → 选择题(模型输出选项字母,规则判分)。- 题型自动判定:无干扰项且唯一正确答案 =
open;多个正确答案 =mcq_multi;否则 =mcq_single。 mcq_grouped(多问合并):一个 prompt 内含多道子问题(如 EmoBench EU 的「情绪 + 原因」两问),由数据集的prepare_samples钩子预先打包,规则判分要求每个子问题各对才算整体对。详见 docs/add_new_dataset.md。- 数据集专属的分组字段放进
meta,可视化和分组指标会自动用到。
复刻原论文 prompt(可选):默认 prompt 由框架按协议统一生成;若某数据集需要忠实复刻原论文的题面排版 / system prompt,可在
tasks/<数据集>/prompt.py提供build_prompt/build_system_prompt/prepare_samples钩子(约定式加载,缺省回退通用实现)。当前 19 个数据集均已接入prompt.py,其中 EmoBench 额外用prepare_samples把 EU 子集合并为mcq_grouped多问。详见 docs/add_new_dataset.md。
📚 数据集总览:19 个数据集各自考察什么、从哪个原始数据集改造而来、以及如何评测,集中整理在 docs/datasets.md。
整体流程:
配置 experiment_config.yaml
↓
python run_eval.py ← 预测 → 判分 → 出图(结果存 results/、图存 figures/)
↓
python report/generate_*.py ← 汇总成 Markdown 表格 / HTML 报告(存 tables/)
框架自动完成:加载数据 →(可选)数据集 prepare_samples 预处理 → 按协议(或数据集自定义 prompt.py)构造 Prompt → 并发调用模型 → 判分(选择题 / grouped 多问走规则判分,开放题走 LLM judge)→ 汇总指标 → 多轮取平均 → 出图。
所有评测配置都在根目录的 experiment_config.yaml:
# ── 被测模型(只填连接相关参数)────────────────────────────
llm:
model_name: MyModel # 自定义名称,同时是 results/ 下的目录名
api_key: not-needed # 本地服务填 not-needed;云端 API 填真实 key(支持 ${ENV_VAR})
api_url: http://0.0.0.0:8000/v1
max_workers: 16 # 并发线程数;云端 API 撞限流就调小(如 3~8)
# ── 评测协议(采样参数 / system prompt / extractor / 投票全部由协议驱动)──
protocol: cot # direct | direct_think | cot | del_tom,详见 docs/protocols.md
# ── 评测阶段与数据集 ──────────────────────────────────────
stage: all # predict(只推理)| metric(只判分)| visualize(只出图)| all
datasets: # run_eval.py 批量评测的数据集列表
- BigToM
- EmoBench
- FanToM
- HiToM
- SocialIQA
- ToMBench
# ── 实验参数 ──────────────────────────────────────────────
max_samples: 0 # 0 = 跑全量;调试时可设 3~30(固定随机种子,可复现)
normalized_datasets_path: datasets # 标准化数据集根目录
results_path: results # 预测/判分结果根目录
figures_path: figures # visualize 阶段出图根目录关键点
model_name决定结果存哪个目录,不同模型必须用不同名称。- 温度 / max_tokens / 是否思考 / 重复次数(= 协议的 n_samples)全部由
protocol决定,llm段只保留连接信息,避免手动设错。各协议的参数与 prompt 详见 docs/protocols.md。- 选择题走规则判分(提取
\boxed{}比对字母),不需要 judge 模型。开放题的判分方式由数据集自己在tasks/<数据集>/config.yaml的open_judge字段选择(f1/llm_simple/rubric),需要 judge 模型时在同一文件里配judge1/judge2,与被测模型解耦。详见 docs/add_new_dataset.md。
# 跑全部数据集(datasets 列表)
python run_eval.py
# 只跑某一个数据集(同样从 experiment_config.yaml 读 protocol/stage)
python tasks/ToMBench/run.py分阶段运行——改 experiment_config.yaml 里的 stage 再运行:
| stage | 作用 |
|---|---|
predict |
只推理,产出 prediction.jsonl(适合先攒预测) |
metric |
对已有预测重新判分(复用最新 exp 目录),产出 metrics.json |
visualize |
按 metrics.json 出图到 figures/<数据集>/<模型>/ |
all |
预测 + 判分 + 出图,端到端 |
# metric / visualize 复用最新实验目录;也可用 --exp-dir 指定已有目录
python run_eval.py --exp-dir 20260516_230618results/
└── {数据集}/
└── {model_name}/
└── exp_{时间戳}/
├── config.json # 本次实验配置(api_key/api_url 已自动脱敏)
├── metrics.json # 汇总指标
└── prediction.jsonl # 每条样本的预测详情
metrics.json 结构:
{
"avg_metrics": {
"accuracy": 0.7340, "correct": 743, "total": 1012,
"by_ability": { "Belief: Content false beliefs": 0.71 },
"per_sample_results": [ { "sample_id": "...", "is_correct": true, "error_reason": null, "prompt_type": "mcq_single" } ]
},
"all_metrics": [ { "accuracy": 0.7321, "correct": 741, "total": 1012, "per_sample_results": [ ... ] } ]
}avg_metrics:多轮平均指标,含各分组准确率(by_*随数据集而异)。all_metrics:每一轮的原始指标 + 逐样本判分结果per_sample_results。
prediction.jsonl 结构(每行一条,只存原始模型输出,判分在 metric 阶段做):
{
"sample_id": "emo_0001",
"sample_index": 0,
"repeat": 0,
"prompt_type": "mcq_single",
"story": "...", "question": "...",
"correct_answers": ["..."], "wrong_answers": ["..."],
"options": {"A": "...", "B": "..."},
"correct_letters": ["B"], "wrong_letters": ["A", "C"],
"shuffle_seed": 123456,
"prompt": "Read the following story...",
"pred": { "content": "...\\boxed{B}", "reasoning": "..." },
"meta": {"ability": "..."},
"protocol": "cot", "extractor": "cot"
}
pred.content是模型的原始文本输出;pred.reasoning是 thinking 模式下剥离出的思考过程。is_correct/error_reason不在这里——它们是判分结果,落在metrics.json的per_sample_results。
评测完成后,可以用 scripts/sync_to_tomresults.py 把最新有效的 exp_* 结果同步到 ToMResults 仓库。ToMResults 的结果目录结构为 results/<model>/<dataset>/,同步时会复制 config.json、metrics.json、prediction.jsonl,并自动重建 leaderboard.json。
本地同步:
python3 scripts/sync_to_tomresults.py \
--tomresults-dir /Users/weihao/Desktop/ToMResults \
--dataset SoMBench \
--model qwen3-8b \
--results-root /Users/weihao/Desktop/ToMEval/results默认情况下,如果目标仓库中同一模型 / 数据集的 run_timestamp 已经相同或更新,脚本会跳过同步;需要强制覆盖时加 --force:
python3 scripts/sync_to_tomresults.py \
--tomresults-dir /Users/weihao/Desktop/ToMResults \
--dataset SoMBench \
--model qwen3-8b \
--results-root /Users/weihao/Desktop/ToMEval/results \
--force如果要通过 PR 更新 ToMResults,可以让脚本创建结果分支、提交并推送:
python3 scripts/sync_to_tomresults.py \
--tomresults-dir /Users/weihao/Desktop/ToMResults \
--dataset SoMBench \
--model qwen3-8b \
--results-root /Users/weihao/Desktop/ToMEval/results \
--git-branch \
--commit \
--push--push 会把结果分支推到 ToMResults 的 origin,之后在 GitHub 上从该分支创建 PR 到 main。不要直接把结果推送到 ToMResults 的 main 分支。
创建真实 PR 前,可以先用 --dry-run-pr 检查即将创建的 PR 标题、正文、head、base 和 repo;该模式只打印信息,不请求 GitHub API:
python3 scripts/sync_to_tomresults.py \
--tomresults-dir /Users/weihao/Desktop/ToMResults \
--dataset SoMBench \
--model qwen3-8b \
--results-root /Users/weihao/Desktop/ToMEval/results \
--force \
--git-branch \
--commit \
--dry-run-pr确认无误后,可以用 --open-pr 在推送结果分支后自动向 ToMResults 创建 PR:
GITHUB_TOKEN=<your_token> python3 scripts/sync_to_tomresults.py \
--tomresults-dir /Users/weihao/Desktop/ToMResults \
--dataset SoMBench \
--model qwen3-8b \
--results-root /Users/weihao/Desktop/ToMEval/results \
--git-branch \
--commit \
--push \
--open-pr--open-pr 必须配合 --push 使用。GitHub token 从环境变量读取,默认变量名是 GITHUB_TOKEN;不要把 token 写进代码、日志或提交记录。PR 默认提交到 TomTraining/ToMResults 的 main 分支,也可用 --repo、--base-branch 和 --github-token-env 覆盖。若没有新的 run_timestamp,脚本默认跳过同步,不会创建无意义 PR;实际使用时建议等新结果产生后再运行,避免对已经在 main 中存在的结果重复开 PR。
visualize 阶段(或 python -m src.visualization)是数据集无关的:它只读 metrics.json 里的 by_* 分组指标,自动出图到 figures/<数据集>/<模型>/:
- 每个
by_*分组 → 柱状图(带样本数);含|的复合键 → 热力图; - 开放题的 rubric 打分 → 平均分柱状图;多 judge → 一致性散点 + Bland-Altman;
- 传入多个
--results→ 额外出多模型对比图 / 雷达图。
# 单模型出图
python -m src.visualization --results results/SoMBench/Qwen3-8B/exp_xxxx --out figures/SoMBench
# 多模型对比(传多个 --results)
python -m src.visualization --results results/SoMBench/A/exp_xxx results/SoMBench/B/exp_yyy --out figures/SoMBenchpython report/generate_dataset_tables.py # 各数据集详细表 → tables/{数据集}/基础指标.md + 其他指标.md
python report/generate_summary.py # 跨数据集汇总表 → tables/SUMMARY.md
python report/generate_html_report.py # 可选:交互式 HTML 报告(带热力图)→ tables/report.html用 report/tables_config.yaml 控制进表的模型/数据集:
results_dir: results
output_dir: tables
exp_suffix: # 不填则取最新一次实验
dataset: # 不填则处理所有数据集
- ToMBench
models: # 不填则处理所有模型;display 是表里显示的名称
- name: Qwen3-8B # 与 experiment_config.yaml 的 model_name 一致
display: Qwen3-8B-Think对比多个模型:用不同
model_name各跑一次评测,结果自动存进不同目录,生成表格时并排展示。
本地 vLLM:
vllm serve /path/to/model --port 8000 # 普通模型
vllm serve /path/to/Qwen3-8B --port 8000 \
--reasoning-parser qwen3 # 支持 thinking 的模型llm:
model_name: Qwen3-8B
api_key: not-needed
api_url: http://0.0.0.0:8000/v1
# 是否走 thinking 由协议决定:direct 关闭、其余开启确认服务正常:curl http://0.0.0.0:8000/v1/models
云端 OpenAI 兼容 API:
| 服务 | api_url | 常用 model_name |
|---|---|---|
| DeepSeek | https://api.deepseek.com/v1 |
deepseek-chat |
| OpenAI | https://api.openai.com/v1 |
gpt-4o |
| 通义千问 | https://dashscope.aliyuncs.com/compatible-mode/v1 |
qwen3-8b / qwen-max |
| 智谱 AI | https://open.bigmodel.cn/api/paas/v4 |
glm-4 |
| 月之暗面 | https://api.moonshot.cn/v1 |
moonshot-v1-8k |
llm:
model_name: deepseek-chat
api_key: ${DEEPSEEK_API_KEY} # 或直接填 key(注意别提交到 git)
api_url: https://api.deepseek.com/v1
max_workers: 8 # 云端并发建议降低,撞限流再调小
protocol: cot
⚠️ 部分云端 API 有约束:dashscope qwen3-8b 的max_tokens上限是 8192,且 thinking 开启时只能流式调用——配 judge 时记得在judge1里写enable_thinking: false和max_tokens: 8192。
更多细节见 docs/add_new_model.md。
filter/ 是数据质量评估 + 自动修复流水线,用决策树逐层筛查训练数据里的低质题(太简单 / 无法回答 / 可走捷径),并迭代修复。
评估链路(逐层缩小检测范围,省 token):
- pass@k:用一个弱模型对每条样本独立预测
pass_k次(默认 3)。全过 →easy(太简单);全错 → 待定;部分过 → 进入下一层。 - 可回答性:用强模型判断 partial / 全错的样本是否自洽、答案是否唯一。判 false →
bad。 - shortcut 三维探测:对 partial 且可回答的样本,分别去掉故事 / 问题 / 选项各试
shortcut_k次,看是否仍能蒙对。命中 →shortcut。 - 决策树打标 + 修复:综合上面结果给标签(
easy/hard/medium/shortcut/bad/unfixable),对easy/bad/shortcut自动修复并迭代(最多max_iter轮,仍修不好记unfixable)。 - finalize:把所有轮次里的
hard + medium合并成最终训练集。
datasets: [BigToM, EmoBench, FanToM, HiToM, SocialIQA, ToMBench]
models:
strong: # 强模型:可回答性判断 + 修复
model_name: deepseek-v4-flash
api_url: https://dashscope.aliyuncs.com/compatible-mode/v1
api_key: <YOUR_API_KEY>
max_tokens: 8192
simple: # 弱模型:pass@k 评估
model_name: qwen3-8b
api_url: https://dashscope.aliyuncs.com/compatible-mode/v1
api_key: <YOUR_API_KEY>
max_tokens: 8192
paths:
input_root: train_datasets # 输入:train_datasets/<数据集>/*.parquet
output_root: filter_output # 输出根目录
pass_k: 3 # pass@k 的 k
shortcut_k: 3 # shortcut 每维试探次数(独立于 pass_k)
# 阶段开关(按需关闭,优雅降级)
passk_enabled: true # false = 跳过难度分桶,全部按 partial 走
answerability_enabled: true # false = 跳过可回答性,假设全部可回答
shortcut_enabled: true # false = 跳过 shortcut,假设全部非 shortcut
repair_enabled: true # false = 只评估打标,不修复 / 不迭代
max_iter: 2 # 修复迭代上限(含第一轮)python run_filter.py # 无命令行参数,全部从 filter/config.yaml 读输出落在 filter_output/:各数据集的逐轮评估明细、final/train_set.parquet(最终训练集),以及顶层 SUMMARY_REPORT.md 汇总报告。详见 filter/README.md。
feedback/ 是数据合成流水线:分析模型的错题,诊断薄弱的认知维度,针对性地合成新训练数据。
四个阶段:
- load:从
results/<数据集>/<模型>/exp_*/prediction.jsonl收集多个模型的 bad case 并集,按维度分层采样。 - diagnose:按能力维度分组错题,让 LLM 总结常见错误模式、认知操作、建议的合成主题,产出诊断报告。
- synth:依据诊断报告调用 LLM 生成新题目(输出与训练数据 parquet 同构)。
- dedupe:用 MinHash LSH 过滤掉与测试集相似的样本(防泄漏)以及内部重复,写出最终 parquet。
stage: all # all / load / diagnose / synth / dedupe
predictions_root: results # bad case 来源(ToMEval 评测结果)
output_path: feedback_output
max_bad_cases: 0 # 每数据集最多收集的 bad case 数(0=不限)
synthesis_model: # 合成用的模型(建议强模型)
model_name: deepseek-v4-flash
api_url: https://dashscope.aliyuncs.com/compatible-mode/v1
api_key: <YOUR_API_KEY>
leakage_guard: # LSH 防泄漏守门员
test_root: datasets
threshold: 0.6 # 与测试集相似度阈值
internal_threshold: 0.85
synthesis_datasets: # 要合成哪些数据集、各合成多少
- name: ToMBench
target_samples: 50
samples_per_report: 5
models:
- name: Qwen3-8B
exp: ~ # ~ = 自动取最新 exp_* 目录python run_feedback.py # 用默认 feedback/config.yaml
python run_feedback.py --config feedback/config.yaml # 指定配置最终合成数据落在 feedback_output/(清洗后的 parquet)+ SUMMARY.md 汇总。可改 stage 单独跑某一阶段。详见 feedback/README.md。
ToMEval/
├── experiment_config.yaml # 评测配置
├── run_eval.py # 评测入口
├── run_filter.py # 数据质量评估入口
├── run_feedback.py # 数据合成入口
├── requirements.txt
├── src/
│ ├── evaluation/ # 评测核心
│ │ ├── pipeline.py # 三阶段编排(predict/metric/visualize)
│ │ ├── data.py # 标准化数据加载
│ │ ├── prediction.py # 预测生成
│ │ ├── protocols.py # 协议:采样参数 / system prompt / extractor
│ │ ├── prompt_loaders.py # 按数据集约定加载 tasks/<DS>/prompt.py 的自定义钩子
│ │ ├── lang.py # 样本语言归一化(meta.lang/language → en/zh)
│ │ ├── voting.py # del_tom 多数投票
│ │ ├── judge.py # 判分分派(MCQ / grouped 规则 + open 委托)
│ │ ├── open_judge.py # 开放题判分模式(f1 / llm_simple / rubric)
│ │ ├── prompts.py / storage.py / paths.py / metrics.py / task_metrics.py / types.py
│ ├── llm/ # OpenAI 兼容客户端(ContentClient / StructureClient)
│ ├── dataloader/ # parquet 数据加载
│ └── visualization/ # 数据集无关出图
├── tasks/<数据集>/ # 各数据集入口:config.yaml / run.py / metrics.py(可选 prompt.py 复刻原论文 prompt)
├── datasets/ # 标准化后的测试数据集(parquet)
├── train_datasets/ # 训练数据集(filter 的输入)
├── filter/ # 数据质量评估流水线(config.yaml / pipeline.py / eval/ / repair/)
├── feedback/ # 数据合成流水线(config.yaml / stage1~4 / prompts.py)
├── report/ # 报告生成(generate_dataset_tables / summary / html_report)
├── results/ # 评测结果
├── figures/ # 可视化输出
├── tables/ # 生成的表格 / 报告
├── docs/ # 详细文档
└── logs/ # 日志
- 新增数据集:归一化数据 → 建
tasks/<数据集>/{config.yaml, run.py, metrics.py}→ 加进experiment_config.yaml的datasets。开放题数据集再在config.yaml配open_judge。详见 docs/add_new_dataset.md。 - 新增模型:改
experiment_config.yaml的llm段即可,无需改代码。详见 docs/add_new_model.md。
Q:运行时报 "Connection refused"?
检查本地 vLLM 是否在跑:curl http://0.0.0.0:8000/v1/models。
Q:云端 API 报 429 限流 / 思考模式报错?
调小 max_workers(如 3);dashscope qwen3 thinking 开启时只支持流式,judge 这类非流式调用要设 enable_thinking: false,且 max_tokens 不超过 8192。
Q:思考(thinking)模式怎么控制?
由协议统一控制:direct 关闭(裸答),direct_think / cot / del_tom 开启;思考过程存在 prediction.jsonl 的 pred.reasoning。详见 docs/protocols.md。
Q:模型不支持结构化输出怎么办?
StructureClient 自动降级:先试原生 parse 模式,失败则改用「Prompt 注入 JSON 格式 + 正则提取」的 create 模式,无需手动干预。
Q:随机抽样(max_samples > 0)每次结果一样吗?
一样,内部使用固定随机种子,相同 max_samples 抽到的样本完全一致。
| 文档 | 内容 |
|---|---|
| docs/protocols.md | 四个评测协议的采样参数、system prompt、extractor、投票、shuffle |
| docs/visualization.md | 可视化模块用法与自动出图行为 |
| docs/add_new_dataset.md | 如何新增数据集(含开放题 open_judge 配置) |
| docs/add_new_model.md | 如何接入新模型、查看结果、生成报告 |
| docs/generate_tables.md / docs/generate_summary.md | 报告表格生成 |
| filter/README.md | 数据质量评估流水线细节 |
| feedback/README.md | 数据合成流水线细节 |