# CharacterEval 复现核查矩阵（协议 1.0，草案）

- 核查日期：2026-10-28（V06 校验脚本与 V07 判定标准于同日写定）；同日 19:05 UTC 锚定 V07 实测完成，V07a 判定标准随后、于实测前写定；2026-10-29 11:19 UTC 部署方完成 V07a，结果“足以解释”
- 平台协议：`charactereval-1.0 · 2026-10-28`（已实现并部署；奖励模型部署校验 V06 已通过；锚定 V07 结果为“一档以内”；V07a 结果为数值差异足以解释，按预定标准记为原因，正式评测可以开放）
- 实现：协议模块 `worker/src/benchmarks/charactereval/protocol.ts`（官方字符串与 77 个角色资料 repr 由 `tools/experiments/charactereval_reference.py` 从源文件生成），适配器 `index.ts`，引擎新增奖励模型裁判类型（`engine/runner.ts` 的 rewardChunk，`llm/rewards.ts` 走网关的原样透传接口）。
- 核查脚本：`tools/experiments/charactereval_spec_check.py`，2026-10-28 实测 26/26 通过（V01–V04、V08 及生成文件、逐字复制段）。
- 本地端到端：本机 Worker + 模拟接口跑 12 条样本，引擎发出的生成请求 12/12、奖励模型输入 34/34 与官方逻辑逐字节一致，指标均值与维度平均与 compute_score.py 一致；另以故障注入验证 E12。
- 核查方式：三次独立交叉复核 + 一次汇总。三位复核者分别从论文、官方代码、发布物与数字出发，互不知道彼此和汇总者的结论；本表只收三份报告一致、或经汇总者回到源文件复查后确定的结论。复核报告与脚本留存于审计目录（发布审计包时一并公开）。
- 依据：
  - 论文：arXiv:2401.01275 v2 e-print `acl_latex.tex`，SHA-256 `f98299737e75bfb7002e47a76a67161246fbf1286c7be43f8ccd5800b50d6c17`。v1、v2、ACL 正式版三者的表 4、附录 GPT-4 表、相关系数表、表 1 逐格相同（各 240 格），v1→v2 只把 BC-Character-Turbo 改名为 BC-NPC-Turbo。
  - 官方实现：github.com/morecry/CharacterEval，commit `c3d44a6`（2025-05-28）。
  - 发布物：
    - 测试集 `data/test_data.jsonl`（SHA-256 `6d9723a1…dcd8`）；
    - 指标分配 `data/id2metric.jsonl`（`df80ee71…f801`）；
    - 角色资料 `data/character_profiles.json`（`d9b4c9f7…434e`）；
    - 5 个开源 7B 模型的冻结回复 `results/generation_trans_*.jsonl`；
    - 奖励模型 HF `morecry/BaichuanCharRM` 修订 `e953da5`（权重与 `e1b9f1e` 的 LFS 哈希相同；仓库没有固定修订）。

## 依据优先级

每项设定取下列中**第一个**有明确定义的来源：

1. **论文原文**：文字与论文自己的数字矛盾时，取能复现这些数字的读法，并记录实验。
2. **官方实现**（c3d44a6）。
3. **官方发布物**：测试集、指标分配、角色资料、奖励模型检查点。
4. **平台选择**：前三者都没有定义时才允许，必须在本表和报告里写明“平台选择”及理由。

论文没有给出生成提示词、奖励模型输入格式和任何生成参数，这些只能由第 2、3 类确定。

## 核查矩阵

| ID | 设定 | 论文原文依据 | 官方实现 | 平台实现（1.0） | 结论 | 验证 |
|---|---|---|---|---|---|---|
| E01 | 测试集 | 表 1：Test 4,564；第 387 行：样本 = (角色, 上下文, 回复) | `test_data.jsonl`：4,564 条，id 唯一，77 个角色，字段 `id, role, novel_name, context`（无参考回复） | 冻结官方文件及其 SHA-256 | 一致。样本总数论文内部不一致（正文 11,376，表 1 合计 11,375，README 23,020），不影响评测 | V01 |
| E02 | 参与打分的样本 | 第 469 行：每条样本只评一部分指标 | `transform_format.py:17`：只保留在 `id2metric` 中的 id；33 个测试 id 没有任何指标 | 只为 4,531 个有指标的 id 生成回复；33 个 id 不生成 | 平台选择（省费用，不影响分数：官方生成后直接丢弃）。报告写明 n=4,531 / 8,032 对 | V01 |
| E03 | 稀疏指标分配 | 第 469–471 行 | `id2metric.jsonl`：每个 id 1–11 个 `[metric_en, metric_zh]`，共 8,032 对；各指标 308（Flu）至 744（Emp）对 | 逐字使用 `id2metric` | 一致 | V01 |
| E04 | 送入奖励模型的维度名 | 图 2 与第 313–354 行（英文名） | `id2metric` 的 `metric_zh`，如“知识曝光度”“共情度” | 用 `metric_zh` 原文 | 按代码（标注说明 PDF 的叫法不同，不能用） | V02 |
| E05 | 指标方向 | 第 327、649 行 | 标注说明：拒答不该知道的问题得 5 分 | 全部越高越好，KH 不反转 | 一致 | — |
| E06 | 角色资料文本 | 第 170 行：资料来自百度百科；未给格式 | `get_response.py:49-52`、`run_char_rm.py:16`：Python `str(dict)`，含嵌套 list/dict；9 个字符串值含 `'` 时 repr 改用双引号 | 离线用 Python 生成 77 个 repr 字符串，冻结为数据文件；平台不在 TS 中重写 repr | 按代码 | V02（按角色名排序后 `role\trepr\n` 拼接的 SHA-256 = `198d4026…370c2`） |
| E07 | 生成消息结构 | 第 535 行：所有模型用同一提示词，闭源模型“略作调整”；原文未给出 | `get_response.py:10-34, 50-52`：<br>① 没有 system 消息，第一条 user = `{str(profile)}\n现在请你扮演一个角色扮演专家。请你根据上述信息扮演{role}进行对话。\n`；<br>② assistant = `好的！现在我来扮演{role}。`，角色先说话时再接 `我首先发话：` 加首句正文（不带“角色：”前缀）；<br>③ 之后每句角色发言前的对方发言作 user：`{说话人}：{正文}`，角色发言作 assistant：`{role}：{正文}`；<br>④ 最后一句作 query | 逐字照代码构造；消息数组离线生成并冻结，平台核对哈希 | 按代码。“略作调整”内容不明，平台对所有模型不做调整，登记为平台选择 | V03 |
| E08 | 上下文切分 | 未说明 | `make_inputs`：按 `\n` 分行，按第一个全角“：”切出说话人，其余用“：”拼回 | 同代码。数据中 529 条只有一行，174 行有多个全角冒号，3 条末尾带空格（原样保留），id 8158 的说话人名含括号；两个 assert 都不会触发 | 按代码 | V03 |
| E09 | 生成参数 | 未说明（temperature、top_p、max tokens、采样均 0 次命中） | 只有 ChatGLM3 路径，隐含其包装器默认值 T=0.8、top_p=0.8 | 用接入自带的参数（每次评测记录实际值），max_tokens 至少 1024 | 平台选择，与 CharacterBench M09 相同。理由：0.8/0.8 只是 ChatGLM3 包装器的默认值，论文的闭源模型走各家 API | 报告披露 |
| E10 | 推理模型的思考内容 | 未说明 | 无 | 只取最终回复正文 | 平台选择 | — |
| E11 | 回复后处理 | 未说明 | `transform_format.py:15`：`split("\n")[0]`；不 strip，不去“角色：”前缀 | 同代码；冻结回复中 89–98% 带“角色：”前缀，原样送入 | 按代码 | V04 |
| E12 | 失败处理 | 未说明 | `transform_format.py:14`：`None` 或 `"ERROR"` 从所有指标中剔除；空串 `""` 保留并打分 | 服务商内容审核拦截 → 记为 ERROR 并剔除；客户端重试后仍是空回复 → 以空串打分；其他调用失败（网络、5xx、限流）→ 会话失败，可从断点重试，不剔除；剔除数在报告中公开 | 按代码；“哪些失败算 ERROR”属于平台选择（理由：网络类故障是平台侧问题，不应让样本离开分母）。剔除率的上榜门槛尚未设定 | V05（本地故障注入已验证：拦截 2 条剔除，空回复 2 条照常打分） |
| E13 | 奖励模型 | 第 471 行：CharacterRM，底座 Baichuan2-13B-Base | HF `morecry/BaichuanCharRM` e953da5；`score = Linear(5120→1)` 取最后一个位置（末尾 `</s>`）的隐状态，再过 sigmoid | 部署方提供；部署前须做权重哈希核对（同 CharacterJudge） | 一致（ACL 附录写的“Baichuan-13B-Base”不准确） | V06 |
| E14 | 奖励模型输入文本 | 未说明 | `run_char_rm.py:15-18`：`"<RoleInfo>\n\n" + str(profile) + "\n\n<Context>\n\n" + context + "\n\n<Response>\n\n" + model_output + "\n\n<Dimension>\n\n" + metric_zh`；context 是数据集原始多行文本 | 平台拼好完整文本，经网关原样透传接口发送（网关不得再拼接） | 按代码 | V02、V07 |
| E15 | 分词、截断、精度 | 未说明 | `encode(add_special_tokens=False) + [eos]`，不加 BOS；超过 4096 时保留最后 4096 个 token；batch=1；bfloat16（线性头和 sigmoid 也在 bf16 下） | 由部署方保证；用 V07 实测 | 按代码。bf16 下单样本分数是离散的（约 0.0156 一档），换精度会改变分数 | V07 |
| E16 | 分数变换 | 第 471 行：五分制 | `score = sigmoid × 4 + 1` | 同代码 | 按代码。训练损失写的是 `labels/4`，发布的训练标签是 1–5；只有训练时实际用 (label−1)/4，表 4 中约 1.6 的均值才可能出现，所以推理按 ×4+1 | V07 |
| E17 | 指标汇总 | 表 4 每格一个数 | `compute_score.py`：每个指标对所有记录求算术平均，不取整 | 同代码；会话层不取整 | 一致 | V08 |
| E18 | 维度平均 | 表 4、表 5 的 Avg. 列，未写定义 | 无 | 维度 = 该维度各指标均值的等权平均（未取整）；角色一致性 5 项含 KE | 按论文数字：表 4 与附录 GPT-4 表共 90 个 Avg. 值（`charactereval_table_checks.py` 2026-10-28 复算；此前误记为 120 个）与简单平均的差都在 ±0.0007 内，按样本数加权则对不上 | V08 |
| E19 | 总分与排序 | 论文没有总分；第 168 行称角色一致性最关键 | 无 | 不设总分；榜单按角色一致性平均排序（与论文结果榜相同） | 平台选择，沿用现有论文结果榜的做法 | — |
| E20 | 取整 | 表 4 为 3 位小数 | 打印完整浮点 | 只在显示时取 3 位 | 按表格 | V08 |
| E21 | 人格回测（MBTI） | 第 180、362、473 行：54 个角色的 MBTI（personality-database.com），测评用 16personalities.com，“as implemented in” Wang et al. 2023（InCharacter） | 仓库全部提交、HF 页面都没有标签、题目、提示词或代码；作者在 issue #5 承诺发布但没有兑现 | **1.0 不提供**。重建方案另行登记，与论文这一列不可比 | 不可复现。所有准确率都能写成 k/216（54 角色 × 4 字母），论文报的应是逐字母准确率 | — |
| E22 | 奖励模型与人工的相关性（表 2） | 表 2 | 没有发布留出的标注集；`rm_train_data.json` 的 7,228 条全部来自测试集上下文 | 不复现，报告说明原因 | 不可复现（清单 D2）。另：奖励模型在测试上下文上训练，与 ACL 附录“在训练集上训练”的说法矛盾 | — |
| E23 | 附录 GPT-4 裁判结果 | 附录表 5 | 无提示词 | 不复现 | 不可复现 | — |
| E24 | 锚点 | 表 4 | 5 份冻结回复对应表 4 的 ChatGLM3-6B、Baichuan2-7B、XVERSE-7B、InternLM-7B、Qwen-7B | 部署后先把 5 份冻结回复（40,160 次打分，无生成费用）送入奖励模型，按 E17、E18 汇总，与表 4 的 5 行 × 12 列逐格比较 | 清单 D1、D5 | V07 |

## 验证项（核查脚本 `tools/experiments/charactereval_spec_check.py`，待实现）

| 编号 | 内容 | 判定 |
|---|---|---|
| V01 | 测试集、指标分配、角色资料的 SHA-256；4,564 / 4,531 / 8,032 计数 | 全部相等 |
| V02 | 77 个角色资料 repr 的哈希；奖励模型输入文本由 Python 参考实现与平台实现分别生成，8,032 条逐字节比较 | 0 处不同 |
| V03 | 4,531 条生成消息数组由 Python 参考实现（逐字照搬 `concat_messages`、`make_inputs`）与平台实现分别生成，逐字节比较 | 0 处不同 |
| V04 | 首行截断：构造的边界用例（开头空行、只有换行、`\r\n`）与 Python 逐值比较 | 全部相等 |
| V05 | 失败处理：故意返回错误或空回复的测试端点，核对剔除与保留 | 与 E12 一致 |
| V06 | 部署的奖励模型权重与 HF e953da5 逐文件哈希（A）；官方打分代码与部署接口在 26 条探针上逐条比较（B 对 C）。脚本 `charactereval_rm_server_check.py`，探针 `charactereval_rm_probes.json`（由 `charactereval_rm_probes.py` 从冻结回复机械生成），说明见 `BaichuanCharRM-部署校验说明.md` | A 全部相等；B 对 C 每条差 ≤ 1e-6；**2026-10-28 16:38 UTC 部署方实测通过**：A 11/11 一致，B 对 C 26/26 相同（最大差 0），三轮一致（结果 `tools/experiments/charactereval_rm_server_check.result.json`，已隐去主机名与接口地址） |
| V07 | 5 份冻结回复的锚定：5 × 8,032 条官方冻结回复（`results/generation_trans_*.jsonl`，不经生成）经部署的奖励模型打分，按 E17、E18 汇总，与表 4 对应 5 行的 12 个指标和 3 个维度平均逐格比较 | 2026-10-28 写定，首次实测之前：① 75 格全部 \|差\| ≤ 0.005 → 复现；② 全部 ≤ 0.02、但有格 > 0.005 → 一档以内，列出这些格并查原因（硬件、算子），在说明原因前不开放正式评测；③ 任一格 > 0.02 → 未复现，停止，查检查点、输入格式与截断。另报告逐格差的分布、平均有符号差，以及 5 个模型按角色一致性的排序是否与表 4 相同。0.005 的依据：表 4 印 3 位小数（舍入 ±0.0005），bf16 下单条分数一档约 0.0156，硬件差异使少数样本移动一档时，308–744 条的均值移动远小于 0.005。**2026-10-28 19:05 UTC 实测（经平台实际调用的接口）：一档以内**。探针 26/26 与官方参考相同（最大差 0）；75 格最大 \|差\| 0.0120，≤ 0.0005 的 8 格、≤ 0.001 的 18 格、≤ 0.005 的 58 格、≤ 0.02 的 75 格；平均有符号差 +0.0003；15 个维度平均全部 ≤ 0.0040；角色一致性排序与表 4 相同，15 列 × 10 对共 150 个两两先后无一颠倒。超过 0.005 的 17 格：ChatGLM3-6B flu +0.0120、kh +0.0086、ka −0.0072、hl +0.0067、pb +0.0066、emp −0.0064；Qwen-7B cons +0.0119、kh −0.0109、pu −0.0059、cs −0.0054；XVERSE-7B flu +0.0102、ka +0.0063、pb +0.0054；Baichuan2-7B pb −0.0099、ka −0.0087、flu −0.0056；InternLM-7B kh +0.0063。已排除：接口与官方代码不符（探针相同）；冻结回复被改动（仓库 2024-01-10 上传后回复未变，后一次提交只删去标注语料的 response、model 字段；无重复、缺失行）；论文版本（v1、v2 表 4 相同）；权重版本（HF 只有 2024-01-09 一版权重，后一次提交只删注释）；系统偏差（各指标符号不一致，平均差≈0）。差的大小随样本量按 1/√n 变化（流畅度 n=308、幻觉 n=486 的差最大），逐格差除以标准误后 \|z\| 最大 0.42、标准差 0.17，符合逐条随机扰动；折算为单条分数的均方根扰动约 0.121。这比预注册时设想的“少数样本移动一档”大得多，剩下两种解释——作者端的数值环境（GPU、torch/cuBLAS 版本）使单条分数移动多档，或表 4 所用输入、检查点与发布版略有不同——仅凭公开材料无法区分，由 V07a 检验。结果 `tools/experiments/charactereval_anchor.result.json`，诊断 `charactereval_anchor_diagnostics.py` |
| V07a | 数值敏感性（部署方执行）：从 5 份冻结回复每个指标取前 20 条，共 1,200 条奖励模型输入；同一权重、同一官方代码，分别以 bf16（原样）、bf16-strict（关闭 cuBLAS bf16 低精度累加）、fp32（关闭 TF32）打分。脚本 `charactereval_rm_numerics_check.py` | 2026-10-28 写定，实测之前：对 bf16-strict 与 fp32 各算与 bf16 的逐条均方根差 r；r ≥ 0.06（锚定折算值 0.121 的一半）→ 数值差异足以解释锚定偏差，记为原因，可开放正式评测；r < 0.02 → 排除数值原因，偏差归于表 4 所用输入或检查点与发布版不同，公开材料无法再查，是否开放由用户决定；介于两者之间 → 部分解释，如实报告，由用户决定。另核对 bf16 分数与平台接口逐条相同。**2026-10-29 11:19 UTC 部署方实测（A800 80GB PCIe，torch 2.7.0+cu126，transformers 4.36.2）：足以解释**。bf16-strict 与 bf16 的均方根差 0.0953（1,200 条中 796 条改变，|差| 中位数 0.023、90 分位 0.141、最大 0.773，平均有符号差 +0.007）；fp32 与 bf16 的均方根差 0.2622（1,200 条全部改变，|差| 中位数 0.106、最大 1.464）；两者都 ≥ 0.06。bf16 分数与平台锚定时同一输入的分数 1,200/1,200 相同（最大差 0），说明部署方运行环境即平台接口所用环境。我方复核（`charactereval_rm_numerics_verify.py`）：1,200 条输入与脚本从仓库选出的完全一致，均方根差重算与报告值相同。含义：只改 cuBLAS 的累加方式、不改权重与代码，单条分数的变化就与锚定折算的 0.121 同一量级，作者端与我方的 GPU、库版本差异足以产生 V07 的偏差，不需要假设表 4 所用输入或检查点与发布版不同；但这只说明数值原因“足够”，并未直接证明作者端环境，另一种解释没有被排除，只是不再必要。按预定标准记为原因，正式评测可以开放（启动付费评测仍须用户批准）。结果 `tools/experiments/charactereval_rm_numerics_check.result.json`（主机名已隐去，其余与部署方发回文件逐字相同） |
| V08 | 汇总：用冻结打分结果分别跑 `compute_score.py` 逻辑与平台汇总，逐值比较；维度平均按 E18 | 差 < 1e-9 |
| V09 | 论文自身数字（`charactereval_table_checks.py`）：表 1 计数、相关系数表 Overall、90 个维度平均、表 4 粗体与下划线、人格回测 k/216、基线数 | 结果作为事实报告，不设通过与否 |

## 尚待用户决定

- 奖励模型的部署与透传接口（部署方）。

## 已定事项

- 人格回测（E21）不做：与排名无关（2026-10-27 按用户规则判定），用户于 2026-10-28 确认维持不做，也不做重建版。
