写在前面:这是一篇写给程序员、算法工程师、以及任何被"AI 评分到底靠不靠谱"折磨过的工程师的"评审委员会"工程实录。
我们讲 JSON 契约、并发投票、中位数聚合、metaevaluation------评分这件事,本来就该被工程化。
| 🧭
本文定位|评分链路(下游)
本文聚焦"候选选题已经摆在桌上 "之后的那一环:怎么让 LLM-as-a-Judge 给出可复现、可审计、可发表 的分数。核心关键词是 JSON Schema × 多模型投票 × 中位数聚合 × metaevaluation。
姊妹篇|数据链路(上游) :CSDN | 用 Python + OpenAlex + 国产大模型,给毕业论文造一个选题侦察兵------讲怎么从两个关键词出发,靠 OpenAlex 图谱 + 共现矩阵自动生成候选选题。
一句话区分:上游负责把候选选出来,本文负责把候选打分打稳。
0. 引言:告别"导师拍脑袋打分",拥抱"可复现的评审委员会"
为了给毕业论文 / PR Review / 简历筛选 / 需求评估打一个客观分数,你试着把候选丢给 ChatGPT,让它给个"综合分"。一开始跑得很欢,但很快你发现:同一个候选连续问 5 次,分数能在 6.5~8.5 之间反复横跳;换个表达顺序,分数又能差 1.5 分;要求它"严苛一点",它给所有候选都打了 9 分。这种**"单模型一锤定音"式的评分方案**,不仅不稳定,且在可复现性与公正性上危机四伏。
架构师箴言 :在评估工作流的鄙视链里,依赖"一个模型给一个分"的方案处于最底端。我们要做的不应是在 temperature 上调参治标,而是建立一套有契约、有投票、有 metaevaluation 的机制。
这不仅是 prompt 的更迭,更是从"模型问答(LLM-as-Oracle)"向"工程化评估治理(Evaluation Governance)"的思维降维打击。学术圈传得最广的那句"好选题 = Gap × Feasibility × Venue",本质上是一条心法口号------它不是一个现成的系统,而是一条思考框架。
现在市面上绝大多数 LLM 辅助选题的脚本,默认走的都是单模型 + 三段式 Prompt 流水线 :生成阶段让大模型吐 3 个候选,打分阶段让它给 Gap / Feasibility / Venue 三维打分,缩小阶段在短板不及格时让它把 scope 收窄。这套流水线在 demo 里跑得很顺,但落到生产环境里,单模型方案有三个老毛病:
- 顺序偏见:生成阶段排出来的 3 个候选,谁排第一谁分高 1~2 分
- 风格偏见:打分阶段偏爱"术语堆得多"的候选------这恰好是大模型自己最爱写的风格,等于让模型给自己打分
- 温度抖动:同一个候选连续问 5 次,综合分能在 6.5~8.5 之间反复横跳
所以本文的工程升级思路很简单:三角 Schema(Gap × Feasibility × Venue)和短板原则原样保留,但打分环节从"一个模型一锤定音"升级成"三模型并发投票 + 中位数聚合" 。今天我们就用 JSON 契约 + 多模型并发 + 统计聚合,把这条心法落成一个可复现的开题评审委员会。
1. 核心底层逻辑:用"多模型投票"构造可信评分器
在很多新手的认知里,LLM-as-a-Judge 是一个"输入候选 → 输出分数"的黑盒。但在统计学的认知里,单个模型的输出永远是带噪声的。
一个健壮的、可复现的评分系统,必须被设计为一个多模型投票委员会 。每一票都是一个独立观测,N 票通过中位数 + 离群剔除聚合,才得到一个可发布的分数。
1.1 单模型评分的方差期望
假设单模型评分误差服从正态分布:Xi∼N(μ,σ2)X_i \sim \mathcal{N}(\mu, \sigma^2)Xi∼N(μ,σ2)。
对 N 个独立模型的评分取中位数,根据渐近分布理论,中位数估计量的方差约为:
Var(X~N)≈πσ22N \mathrm{Var}(\tilde{X}_N) \approx \frac{\pi \sigma^2}{2N} Var(X~N)≈2Nπσ2
代入 N=3N = 3N=3:Var≈0.52σ2\mathrm{Var} \approx 0.52 \sigma^2Var≈0.52σ2。三模型投票,方差大约下降到原来的一半。
如果再做 trimmed mean(去掉最高最低),N=5N = 5N=5 时方差能压到 0.3σ20.3 \sigma^20.3σ2 以下:
Pstable=1−∏i∈{M1,M2,M3}(1−pi) P_{\text{stable}} = 1 - \prod_{i \in \{M_1, M_2, M_3\}} \left(1 - p_i\right) Pstable=1−i∈{M1,M2,M3}∏(1−pi)
其中 pip_ipi 是单模型给出可信分数的概率。三模型并联,整体可用性轻松冲到 99%+------和单点爬虫与多源 API 的对比是同一个数学。
这本质上是统计学问题,不是 prompt 工程问题。
1.2 多模型投票的状态机
#mermaid-svg-15IMdAp3F3oKb1IR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-15IMdAp3F3oKb1IR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-15IMdAp3F3oKb1IR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-15IMdAp3F3oKb1IR .error-icon{fill:#552222;}#mermaid-svg-15IMdAp3F3oKb1IR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-15IMdAp3F3oKb1IR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-15IMdAp3F3oKb1IR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-15IMdAp3F3oKb1IR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-15IMdAp3F3oKb1IR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-15IMdAp3F3oKb1IR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-15IMdAp3F3oKb1IR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-15IMdAp3F3oKb1IR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-15IMdAp3F3oKb1IR .marker.cross{stroke:#333333;}#mermaid-svg-15IMdAp3F3oKb1IR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-15IMdAp3F3oKb1IR p{margin:0;}#mermaid-svg-15IMdAp3F3oKb1IR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-15IMdAp3F3oKb1IR .cluster-label text{fill:#333;}#mermaid-svg-15IMdAp3F3oKb1IR .cluster-label span{color:#333;}#mermaid-svg-15IMdAp3F3oKb1IR .cluster-label span p{background-color:transparent;}#mermaid-svg-15IMdAp3F3oKb1IR .label text,#mermaid-svg-15IMdAp3F3oKb1IR span{fill:#333;color:#333;}#mermaid-svg-15IMdAp3F3oKb1IR .node rect,#mermaid-svg-15IMdAp3F3oKb1IR .node circle,#mermaid-svg-15IMdAp3F3oKb1IR .node ellipse,#mermaid-svg-15IMdAp3F3oKb1IR .node polygon,#mermaid-svg-15IMdAp3F3oKb1IR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-15IMdAp3F3oKb1IR .rough-node .label text,#mermaid-svg-15IMdAp3F3oKb1IR .node .label text,#mermaid-svg-15IMdAp3F3oKb1IR .image-shape .label,#mermaid-svg-15IMdAp3F3oKb1IR .icon-shape .label{text-anchor:middle;}#mermaid-svg-15IMdAp3F3oKb1IR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-15IMdAp3F3oKb1IR .rough-node .label,#mermaid-svg-15IMdAp3F3oKb1IR .node .label,#mermaid-svg-15IMdAp3F3oKb1IR .image-shape .label,#mermaid-svg-15IMdAp3F3oKb1IR .icon-shape .label{text-align:center;}#mermaid-svg-15IMdAp3F3oKb1IR .node.clickable{cursor:pointer;}#mermaid-svg-15IMdAp3F3oKb1IR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-15IMdAp3F3oKb1IR .arrowheadPath{fill:#333333;}#mermaid-svg-15IMdAp3F3oKb1IR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-15IMdAp3F3oKb1IR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-15IMdAp3F3oKb1IR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-15IMdAp3F3oKb1IR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-15IMdAp3F3oKb1IR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-15IMdAp3F3oKb1IR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-15IMdAp3F3oKb1IR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-15IMdAp3F3oKb1IR .cluster text{fill:#333;}#mermaid-svg-15IMdAp3F3oKb1IR .cluster span{color:#333;}#mermaid-svg-15IMdAp3F3oKb1IR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-15IMdAp3F3oKb1IR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-15IMdAp3F3oKb1IR rect.text{fill:none;stroke-width:0;}#mermaid-svg-15IMdAp3F3oKb1IR .icon-shape,#mermaid-svg-15IMdAp3F3oKb1IR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-15IMdAp3F3oKb1IR .icon-shape p,#mermaid-svg-15IMdAp3F3oKb1IR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-15IMdAp3F3oKb1IR .icon-shape .label rect,#mermaid-svg-15IMdAp3F3oKb1IR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-15IMdAp3F3oKb1IR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-15IMdAp3F3oKb1IR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-15IMdAp3F3oKb1IR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 校验失败
校验通过
是
否
候选选题 JSON
Prompt 构造器
带 Schema 锁
DeepSeek 评审
通义 Qwen 评审
智谱 GLM 评审
Schema 校验
pydantic
聚合器
trimmed mean + 中位数
短板检查
min ≥ 7?
输出立项卡
- 全模型快照
MVR 缩小器
触发 Prompt B
每一个节点都可独立替换:换模型、换 schema、换聚合策略------主干不变。
2. 三驾马车:评审委员会的基础设施
构造一个工业级的评分器,有三个核武器级别的基础设施。
2.1 JSON Schema:让模型"不敢"乱说话
大模型最大的不可控就是"输出形态飘忽"。所以我们先用 pydantic 锁死 schema:
python
from pydantic import BaseModel, Field, field_validator
from typing import Literal
class DimScore(BaseModel):
score: float = Field(ge=0, le=10)
reason: str = Field(min_length=20) # 强制写够字,杜绝"很有意义"
red_flags: list[str] = Field(default_factory=list)
class Review(BaseModel):
gap: DimScore
feasibility: DimScore
venue: DimScore
gap_type: Literal["contextual", "data", "method", "theory"]
shrink_suggestions: list[str] = Field(min_length=2, max_length=5)
cnki_keywords: list[str] = Field(min_length=2, max_length=5)
@field_validator("shrink_suggestions", "cnki_keywords")
@classmethod
def strip_empty(cls, v):
return [s.strip() for s in v if s.strip()]
这里的几个反作弊心机:
reason强制 ≥ 20 字 → 杜绝"这个选题很好"的空话gap_type用Literal枚举 → 模型必须四选一,不能编新词shrink_suggestions强制 2~5 条 → 杜绝偷懒只给一条
2.2 多模型并发:抗个体偏见的"北斗导航"
单模型打分有三个硬伤:
- 顺序偏见(Position bias):调换候选顺序,分数差 1~2 分。LLM-as-a-Judge 的论文实测过。
- 风格偏见(Style bias):模型偏爱"术语堆得多"的候选------这恰好是大模型自己生成内容的特征,等于让模型给自己打分,左脚踩右脚原地起飞。
- 温度噪声 :你以为
temperature=0就稳定了?同一个 prompt 连续跑 10 次仍有 0.5~1 分的抖动,因为底层有 batch、缓存、时间戳进 logit 的工程实现。
并发调用 DeepSeek / Qwen / GLM 三家------它们来自不同训练数据、不同 RLHF 偏好、不同对齐策略,偏见近似正交。三票聚合,偏见相互抵消。
2.3 中位数 + 离群剔除:聚合器的"VIP 门票"
为什么不是平均?因为平均对离群值零防御 。一个模型抽风给 2 分,平均分立刻被拉低。中位数 + trimmed mean 才是工程上正确的聚合策略。
2.4 【实战代码】asyncio.gather 三模型并发投票
python
import asyncio
import json
import os
from statistics import median
from openai import AsyncOpenAI
MODELS = [
{"name": "deepseek-chat", "base": "https://api.deepseek.com", "key": "DEEPSEEK_API_KEY"},
{"name": "qwen-plus", "base": "https://dashscope.aliyuncs.com/compatible-mode/v1", "key": "DASHSCOPE_API_KEY"},
{"name": "glm-4", "base": "https://open.bigmodel.cn/api/paas/v4", "key": "ZHIPU_API_KEY"},
]
SYSTEM = """你是严苛的开题评审专家。规则:
1. 评分前必须先列 red_flags。
2. 任何一维 score >= 9 必须有 >= 2 条具体证据。
3. 任何一维 score <= 5 必须给 shrink_suggestions。
4. 不允许编造文献。
5. 严格 JSON 输出。"""
async def review_one(model_cfg: dict, candidate: dict) -> dict:
client = AsyncOpenAI(
api_key = os.environ[model_cfg["key"]],
base_url = model_cfg["base"],
)
resp = await client.chat.completions.create(
model = model_cfg["name"],
messages = [
{"role": "system", "content": SYSTEM},
{"role": "user", "content": json.dumps(candidate, ensure_ascii=False)},
],
temperature = 0.1,
response_format = {"type": "json_object"},
)
return json.loads(resp.choices[0].message.content)
async def vote(candidate: dict) -> dict:
tasks = [review_one(m, candidate) for m in MODELS]
results = await asyncio.gather(*tasks, return_exceptions=True)
valid = [r for r in results if isinstance(r, dict)]
if len(valid) < 2:
raise RuntimeError("少于 2 个模型返回有效结果,拒绝聚合")
def agg(dim: str) -> float:
scores = sorted(v[dim]["score"] for v in valid)
if len(scores) >= 3:
scores = scores[1:-1] # trimmed: 去掉最高最低
return round(median(scores), 1)
g, f, v = agg("gap"), agg("feasibility"), agg("venue")
return {
"gap": g,
"feasibility": f,
"venue": v,
"overall": round(0.4*g + 0.3*f + 0.3*v, 1),
"pass_floor": min(g, f, v) >= 7,
"per_model": valid,
}
几个工程细节:
asyncio.gather:三模型一起跑,总耗时 ≈ 单模型耗时。CSDN 上很多教程是串行的,慢得没必要。return_exceptions=True:某个模型挂了不影响其他两个,聚合时再过滤。- 少于 2 票直接报错,绝不容忍"一票通过"------这是评审委员会的底线。
3. 避坑指南:分清"打分"与"通过"的陷阱
带过新人的都知道,初级工程师最容易犯的错误,就是混淆了**"综合分高"与"短板通过"**。
打个比方,你招聘一个工程师,三项打分:技术 9 / 沟通 9 / 道德 2。综合分 (0.4×9+0.3×9+0.3×2)=7.5(0.4 \times 9 + 0.3 \times 9 + 0.3 \times 2) = 7.5(0.4×9+0.3×9+0.3×2)=7.5 看起来还行------但道德 2 分这种人你敢招吗?
3.1 短板检查必须是 hard constraint
很多人写聚合器的伪代码是:
python
overall = 0.4*g + 0.3*f + 0.3*v
return overall >= 7 # ❌ 错误
错! 必须先做短板检查:
python
if min(g, f, v) < 7:
return False, "短板不及格" # ✅ 正确
return overall >= 7, "综合达标"
架构师警示 :在评分系统里,短板原则必须是 hard constraint,不是 soft penalty 。三角法则里那一行 min(G,F,V)≥7\min(G, F, V) \geq 7min(G,F,V)≥7 在工程上必须写成一个 if,不是一个加权扣分项。混淆这两者,你的评分器会放过一堆"看上去 7.5 分但其实根本做不出来"的候选。
4. 跨场景选型:为不同评估任务定制的"评分器模式"
这套架构其实和"选题"本身没什么关系 。同一套设计模式可以套到任何"多维评估 + 文本理解"的场景------下面这张表特意以评分场景为主轴,与姊妹篇《选题侦察兵》的"数据采集场景"表形成互补:
| 评分场景 | 评分维度 | 短板阈值(hard gate) | 需要几人票委员会 | 关键调整 |
|---|---|---|---|---|
| 毕业论文选题审 | Gap / Feasibility / Venue | 三维同时 ≥ 7 | 3 模型 | 必须挂 MVR 缩小器,Gap 需区分情境/数据/方法/理论空白 |
| 期刊初审 / 送审前自检 | 方法严谨 / 贡献新颖 / 写作清晰 | 方法 ≥ 7 | 3--5 模型 | plagiarism / AI 检测为前置;ventue 要映射到期刊标签 |
| 项目申报书评审 | 创新点 / 可行性 / 政策医合 / 预算合理性 | 创新点 ≥ 7 且预算 ≥ 6 | 3 模型 + 专家人评 1 票 | 必须距离 7 天内 +跟一轮人评 metaevaluation |
| 简历筛选 / HR | 技术匹配 / 项目深度 / 文化契合 | 技术 ≥ 6 | 3 模型,同背景不同训练 | 必须加反偏见检查(性别/馆校/技术栈) |
| PR Code Review | 正确性 / 可读性 / 性能影响 | 正确性 ≥ 8 | 2 模型 + 1 人 | 正确性是 hard gate,顺位偏见用 diff hash 冲冲冷 |
| 产品需求评估 | 用户价值 / 技术成本 / 商业回报 / 风险 | 价值 ≥ 6 且风险 ≤ 7 | 3 模型 | 风险维度要独立评估,不能被价值抵消 |
Architect Pro-tip :千万不要直接把"选题评审"的 prompt 抄到"简历筛选"。领域知识必须重新注入------否则你会得到一个对 React 简历偏好、对 Vue 简历压分的奇怪偏见。这种偏见你 debug 都 debug 不出来,因为它藏在 prompt 的字里行间。
| 🟡
AI 是"副驾驶",不是"代驾"。 评审委员会的职责是指出候选哪里站不住 ,不是给一个高分代替你拍板。一旦你把评委会的分数当"结论"而不是"诊断",它就重新退化成一个"三模型拍脑袋"的黑盒。
5. 巨头博弈:解锁多模型 API 的"工程纪律"
如果你要让 DeepSeek / 通义 / 智谱 三家同时并发,那么这些 API 的对接就进入了深水区。
LLM-as-a-Judge 接入三要素:
- 准入门槛(Auth):通常是 API Key,但部分高级模型(GLM-4-air、Qwen-Max-Longcontext)要求企业认证起步。
- 严苛的系统约束(Rate Limits & Schema) :
- DeepSeek 个人 ≈ 60 RPM
- 通义 Qwen 单租户限速更细分
- 强行突破 → 429 → 整个并发任务挂掉
- 加
asyncio.Semaphore(2)是工程纪律,不是可选项
- 价值对价(Cost Bound) :批量评审场景对成本极敏感。必须把 token 账本写进代码注释:
python
# 单候选三模型并发投票成本估算 (写进注释,杜绝成本失控):
# system : 400 tokens
# user : 800 tokens
# response : 600 tokens
# 单模型 : 1800 tokens per model
# 三模型 : 5400 tokens ≈ ¥0.01
# 三轮迭代 : ≈ ¥0.03 ~ ¥0.05
# 1000 候选 : ¥30 ~ ¥50
5.1 Prompt 工程:anchoring inversion 的妙用
核心思路:先让它说为什么扣分,再让它给分数。这个顺序很重要------人类心理学和大模型一样,先承诺再给分,给分会更克制。
python
SYSTEM = """你是一位严苛的开题评审专家。
规则(必须严格遵守):
1. 评分前必须先列出 red_flags(扣分点),列不出来才能给高分。
2. 任何一维 score >= 9 必须有至少 2 条具体证据。
3. 任何一维 score <= 5 必须给出 shrink_suggestions。
4. 不允许编造文献、作者、DOI。
5. 必须输出严格 JSON。
"""
| 📌
"先 red_flags 再 score"是反作弊的关键。
如果你直接问"打几分",大模型倾向于给 8 分;如果你先逼它说"这选题有什么问题",它会自己把分数往下拉到合理区间。
这个 trick 叫 anchoring inversion,在 Constitutional AI 和很多 reward modeling 论文里都被验证过。
6. 工程化闭环:metaevaluation 与持续治理
一个令人清醒的现实是:在一个优秀的评分系统里,"评分代码"只占不到 20% 的工程量 ,剩下的 80% 都在评估这个评分器自身 ------也就是 metaevaluation。
如何证明你的评分器对你这个领域是有判别力的?三个粗暴但有效的做法。
6.1 标准答案对齐(Spearman ρ)
找 20 个结果已知的真实候选------比如已被高校通过的开题、已发表的 SSCI 论文、被毙掉的开题报告。脱敏后喂给评分系统:
python
import scipy.stats as st
predictions = [vote(c)["overall"] for c in benchmark]
ground_truth = [c["actual_outcome_score"] for c in benchmark]
rho, p = st.spearmanr(predictions, ground_truth)
print(f"Spearman ρ = {rho:.3f}, p = {p:.4f}")
Spearman 比 Pearson 更稳健------你不在意分数绝对值,只在意排序是否一致。ρ > 0.6 才算这个评分器对你这个领域"有判别力"。
6.2 人评 vs 机评一致性(Cohen's Kappa)
请你导师 / 资深同事抽 10 个候选独立 打分(事先盲掉 AI 分数),算 Cohen's Kappa:
| Kappa 区间 | 解读 |
|---|---|
| < 0.2 | 几乎没一致性,系统不可信 |
| 0.2 -- 0.4 | 弱一致,慎用 |
| 0.4 -- 0.6 | 中等一致,可参考 |
| 0.6 -- 0.8 | 实质一致,可主用 |
| > 0.8 | 近乎完全一致 |
我自己跑过的样本上,三模型投票版稳定在 0.55~0.7,比单模型版(约 0.3)高出整整一个台阶。
6.3 稳定性测试(stdev)
同一个候选灌进系统 5 次,看综合分的标准差:
python
import statistics as stats
scores = [vote(candidate)["overall"] for _ in range(5)]
print(f"mean={stats.mean(scores):.2f}, stdev={stats.stdev(scores):.2f}")
经验值:stdev > 0.5 说明系统在抽风,需要调低 temperature、加更多模型、或把 prompt 锁得更死。如果 stdev < 0.2,恭喜------你可以把这个评分系统写进毕业论文的方法论章节当 contribution。
6.4 结构化日志:归因的生命线
做完前三件事,你必须把每一次评审完整存盘------否则下次跑出来的分数和今天不一样,你不知道是模型变了、prompt 变了、还是温度变了:
python
# 基于 SQLAlchemy 的评审日志模型
from sqlalchemy import Column, String, Float, JSON, DateTime, Boolean
from sqlalchemy.ext.declarative import declarative_base
import datetime
Base = declarative_base()
class ReviewLog(Base):
__tablename__ = "review_committee_log"
review_id = Column(String(64), primary_key=True)
candidate_id = Column(String(64), nullable=False)
# 三维分数 + 综合分 + 短板通过
score_gap = Column(Float, nullable=False)
score_feasibility = Column(Float, nullable=False)
score_venue = Column(Float, nullable=False)
score_overall = Column(Float, nullable=False)
pass_floor = Column(Boolean, nullable=False)
# 可追溯性
per_model_snapshot = Column(JSON, nullable=False, comment="每个模型的完整 JSON 输出")
model_versions = Column(JSON, nullable=False, comment="model name + version")
prompt_hash = Column(String(32), nullable=False, comment="prompt 模板 md5")
schema_version = Column(String(32), nullable=False, comment="pydantic Schema 版本号")
# 审计时间戳
created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
这些字段必须 Non-nullable ,因为它们是事后归因(Attribution)的生命线 :一年后你做 metaevaluation 时,要能回放每一票的来源、每一版 prompt 的影响、每一个模型版本切换的副作用。没有这张表,你的评分系统只是一个永远在"现在"的占卜机器。
7. 结语与前瞻:为评估这件事建立"工程标准"
当 LLM-as-a-Judge 的论文每周以几十篇 的速度涌现,当多模型投票被工程界默认接受为评估范式,"AI 评分"正在从玄学走向工程。
此时,我们不妨做一个思考:
当未来 AI 评审委员会的可信度(Cohen's Kappa)稳定超过 0.7 时,毕业论文还需要导师亲自打分吗?或者说,导师的角色会不会从"评审"变成"评审的评审"(meta-reviewer)?
或许,作为新一代工程师,我们的根本任务早已不是"让模型给一个答案 ",而是"为每一类决策建立一个有契约、有投票、有 metaevaluation 的工程化评估系统"。
放弃单模型一锤定音,拥抱多模型投票 + Schema 校验 + 持续 metaevaluation------这不仅是技术升级,更是保护你输出可信度、确保下游决策质量的唯一红线。
下面这张图,是这个评审委员会的全貌------不是给你看的,是给你照着抄的:
#mermaid-svg-K4Sj5jqJKwxnAVom{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-K4Sj5jqJKwxnAVom .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-K4Sj5jqJKwxnAVom .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-K4Sj5jqJKwxnAVom .error-icon{fill:#552222;}#mermaid-svg-K4Sj5jqJKwxnAVom .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-K4Sj5jqJKwxnAVom .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-K4Sj5jqJKwxnAVom .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-K4Sj5jqJKwxnAVom .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-K4Sj5jqJKwxnAVom .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-K4Sj5jqJKwxnAVom .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-K4Sj5jqJKwxnAVom .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-K4Sj5jqJKwxnAVom .marker{fill:#333333;stroke:#333333;}#mermaid-svg-K4Sj5jqJKwxnAVom .marker.cross{stroke:#333333;}#mermaid-svg-K4Sj5jqJKwxnAVom svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-K4Sj5jqJKwxnAVom p{margin:0;}#mermaid-svg-K4Sj5jqJKwxnAVom .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-K4Sj5jqJKwxnAVom .cluster-label text{fill:#333;}#mermaid-svg-K4Sj5jqJKwxnAVom .cluster-label span{color:#333;}#mermaid-svg-K4Sj5jqJKwxnAVom .cluster-label span p{background-color:transparent;}#mermaid-svg-K4Sj5jqJKwxnAVom .label text,#mermaid-svg-K4Sj5jqJKwxnAVom span{fill:#333;color:#333;}#mermaid-svg-K4Sj5jqJKwxnAVom .node rect,#mermaid-svg-K4Sj5jqJKwxnAVom .node circle,#mermaid-svg-K4Sj5jqJKwxnAVom .node ellipse,#mermaid-svg-K4Sj5jqJKwxnAVom .node polygon,#mermaid-svg-K4Sj5jqJKwxnAVom .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-K4Sj5jqJKwxnAVom .rough-node .label text,#mermaid-svg-K4Sj5jqJKwxnAVom .node .label text,#mermaid-svg-K4Sj5jqJKwxnAVom .image-shape .label,#mermaid-svg-K4Sj5jqJKwxnAVom .icon-shape .label{text-anchor:middle;}#mermaid-svg-K4Sj5jqJKwxnAVom .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-K4Sj5jqJKwxnAVom .rough-node .label,#mermaid-svg-K4Sj5jqJKwxnAVom .node .label,#mermaid-svg-K4Sj5jqJKwxnAVom .image-shape .label,#mermaid-svg-K4Sj5jqJKwxnAVom .icon-shape .label{text-align:center;}#mermaid-svg-K4Sj5jqJKwxnAVom .node.clickable{cursor:pointer;}#mermaid-svg-K4Sj5jqJKwxnAVom .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-K4Sj5jqJKwxnAVom .arrowheadPath{fill:#333333;}#mermaid-svg-K4Sj5jqJKwxnAVom .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-K4Sj5jqJKwxnAVom .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-K4Sj5jqJKwxnAVom .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K4Sj5jqJKwxnAVom .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-K4Sj5jqJKwxnAVom .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K4Sj5jqJKwxnAVom .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-K4Sj5jqJKwxnAVom .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-K4Sj5jqJKwxnAVom .cluster text{fill:#333;}#mermaid-svg-K4Sj5jqJKwxnAVom .cluster span{color:#333;}#mermaid-svg-K4Sj5jqJKwxnAVom div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-K4Sj5jqJKwxnAVom .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-K4Sj5jqJKwxnAVom rect.text{fill:none;stroke-width:0;}#mermaid-svg-K4Sj5jqJKwxnAVom .icon-shape,#mermaid-svg-K4Sj5jqJKwxnAVom .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K4Sj5jqJKwxnAVom .icon-shape p,#mermaid-svg-K4Sj5jqJKwxnAVom .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-K4Sj5jqJKwxnAVom .icon-shape .label rect,#mermaid-svg-K4Sj5jqJKwxnAVom .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K4Sj5jqJKwxnAVom .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-K4Sj5jqJKwxnAVom .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-K4Sj5jqJKwxnAVom :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 输出层
治理层
闭环层
聚合层
推理层
契约层
输入层
是
否
是
否
候选 JSON
领域知识
评分锚点
资源约束
预算 / 延迟
pydantic Schema
Prompt 模板
- md5 hash
温度 / 模型版本锁
DeepSeek 评审
Qwen 评审
GLM 评审
≥ 2 票有效?
Schema 校验
trimmed mean
中位数
短板 ≥ 7?
MVR 缩小器
最多 3 轮迭代
结构化日志
per-model 快照
Spearman ρ
Cohen's Kappa
stdev 稳定性
立项卡 JSON
雷达图 PNG
Notion / DB
三个月后回头看,你会发现真正让评分稳定的,从来不是某个模型多强,而是这张图上每一条治理回路都被你认真打磨过一遍。
💡 互动话题:你在用 LLM 做评估时,踩过最大的"打分坑"是什么?欢迎在评论区留言讨论!