前言:从 “能跑” 到 “跑得稳”,再到 “让结果说话”

我至今还记得那天晚上,回望墙上的白板,密密麻麻写着 “数据集、口径、阈值、报告、对比、回归” 几个词。我们不是第一次做模型其实仅仅是模型,而是负责的对外 api 的评测,但我们第一次直面一个现实:靠手工和临时脚本,已经无法再支撑 “频繁评测、多人协作、对外汇报” 的需求了。

如果这件事注定会越来越频繁、越来越复杂,为什么不把它变成一条可靠、可复用、可回溯、可协作的流水线?

这就是本文要讲的系统的来历。它不是一个脚本集合,而是一套 “评测平台”:一处接入、多模型协同、统一评分口径、端到端追踪、结构化历史、可视化报告、权限与分享。如今它已经跑在我们的机器上,它每天在替我们做一件本应自动化的工作:让评测这件事,成为 “被系统化管理的工程实践”。

这篇文章是系列第一篇,讲清 “为什么要做” 与 “我们到底做了什么”。后续文章会逐步深入架构、数据、引擎、模型接入、后台、可观测性、上线与生态等方面。

我们曾经是怎么做的:手工测试与 “脚本地狱”

先承认一个事实:手工测试在早期 “够用”。它有三个优点:

但手工测试的上限也很明显:

于是我们踏入了脚本化阶段。脚本当然是进步:

但脚本也会快速陷入 “脚本地狱”:

总之,脚本可以 “让事情跑起来”,但它很难 “让事情跑得稳,还能讲清楚”。

我们想要的是什么:一处接入、统一口径、可回溯与可协作

当我们决定 “做系统” 时,我们其实是在回答四个问题:

1) 怎样让 “评测流程” 成为一个可复用的 “流水线”?
2) 怎样让 “评分口径” 成为被系统所治理的 “第一公民”?
3) 怎样让 “结果” 天然可视化、可导出、可分享与可追溯?
4) 怎样让 “并发、限流、重试、幂等” 这些工程问题被妥善治理?

对应到能力清单,就是:

我们做了什么:从白纸到 “能跑且跑得稳” 的平台

分层与目录

系统采用 Flask + Jinja2 作为 Web 层,代码分层清晰:

在本地开发时,运行 python app.py --port 8080 即可访问 http://127.0.0.1:8080/

端到端流程(上传→获取答案→裁判打分→结果落库→可视化→导出/分享)

1) 上传数据集与配置评测:

2) 并发获取被测模型答案:

3) 裁判模型统一打分(严格 JSON 协议):

4) 结果落库与可视化:

5) 导出与分享:

关键代码锚点

应用入口与端口:

if __name__ == '__main__':
    # 处理命令行参数
    import sys
    port = 8080
    if len(sys.argv) >= 3 and sys.argv[1] == '--port':
        try:
            port = int(sys.argv[2])
        except ValueError:
            print("❌ 无效的端口号,使用默认端口8080")

    print(f"\n🌐 访问地址: http://localhost:{port}")
    print("📖 配置帮助: python3 test_config.py")
    app.run(debug=True, host='0.0.0.0', port=port)

评测蓝图的发起与进度回填:

@evaluation_bp.route('/start_evaluation', methods=['POST'])
@login_required
def start_evaluation():
    """开始评测"""
    data = request.get_json()
    filename = data.get('filename')
    selected_models = data.get('selected_models', [])
    judge_model = data.get('judge_model')  # 裁判模型
    force_mode = data.get('force_mode')  # 'auto', 'subjective', 'objective'
    custom_name = data.get('custom_name', '').strip()  # 自定义结果名称
    save_to_history = data.get('save_to_history', True)  # 是否保存到历史记录
    ...

评测引擎并发执行与 JSON 映射:

# 创建并发任务来评测所有问题,添加实时进度更新
print(f"🚀 开始并发评测,并发数: {GEMINI_CONCURRENT_REQUESTS}")
semaphore = asyncio.Semaphore(GEMINI_CONCURRENT_REQUESTS)
...
async def evaluate_single_question(i: int, row: Dict) -> Tuple[int, List]:
    async with semaphore:
        ...
        # 使用选定的裁判模型进行评测
        judge_raw = await call_judge_model(judge_model, prompt)
        result_json = parse_json_str(judge_raw)
        ...
        # 映射为 CSV 列
        for j, model_name in enumerate(model_names, 1):
            model_key = f"模型{j}"
            row_data.append(current_answers[model_name])  # 模型答案
            if model_key in result_json:
                row_data.append(result_json[model_key].get("评分", ""))
                row_data.append(result_json[model_key].get("理由", ""))
                if mode == 'objective':
                    row_data.append(result_json[model_key].get("准确性", ""))

提示词治理(主观/客观):

def build_subjective_eval_prompt(..., filename: str = None) -> str:
    ...
    file_prompt = db.get_file_prompt(filename)
    if file_prompt:
        custom_prompt = file_prompt
        score_instruction = "请严格按照上述自定义提示词中定义的评分标准进行评分"
        ...
    else:
        default_prompt = db.get_default_prompt('subjective')
        if default_prompt:
            custom_prompt = default_prompt
        else:
            raise ValueError(...)

并发、限流、退避与幂等:工程问题的 “地基”

在评测系统里,并发不是 “越高越好”,而是 “可控、可解释、可稳定”。我们采用以下策略:

这四点听起来朴素,但是真正让系统 “跑得稳” 的关键。

裁判模型输出 JSON,看似简单,实际是一个 “强约束 + 容错兜底” 的工程问题:

这套策略让我们在 “模型偶尔不听话” 的现实世界里,依然能把任务跑完,并把问题点留痕。

数据长期主义:软删除、历史、可见性与分享

“数据是资产” 的一个直接含义是:你不应该因为一次误删、一次口径变化,就丢失评测历史。于是我们:

这些设计让 “半年后能复现当初的判断” 不再是一句口号。

管理后台:把 “口径与密钥” 变成 “系统配置”

我们在后台提供了两类关键能力:

它们的共同点是:把口径与密钥从 “某个人的电脑/某个脚本” 里,移到 “系统配置” 里,让协作真正发生。

我们学到的:原则与反模式

做完这个系统之后,我们总结了几条 “原则”,也踩过一些 “反模式”。

原则

1) 先定义目标与指标,再决定怎么跑。避免 “跑完才想指标”。
2) 评分口径是第一公民,必须可配置、可追溯、可审计。
3) JSON 是硬约束,容错是兜底,不应以容错代替约束。
4) 并发有边界,失败可恢复,进度可观测。
5) 数据长期主义:历史、软删除、可见性与分享,从第一天就要有。
6) 工程可解释:日志与链路让每一次失败都有 “可以复盘的证据”。

反模式

1) 让提示词藏在代码里:这会让口径不可讨论、不可版本化。
2) 结果结构随意变:导出与可视化会变得脆弱,历史也难以对齐。
3) 并发 “拉满”:限流与失败率会用血的事实告诉你什么叫 “不可控”。
4) 进度不可见:用户会误以为 “系统卡住了”。
5) 单纯堆功能:没有 “系统能力” 的治理,功能越多越难用。

每一张图都不只是 “好看”,它们共同讲述一个故事:从 “人来背口径”,到 “系统固化口径”;从 “脚本跑起来”,到 “平台跑得稳”。

结语:系统不是终点,是团队协作方式的起点

我们做这套系统的初衷,不是 “省时间”,而是 “减少不可控”。当评测这件事被系统化之后,我们可以对需求说 “半天给你结果”,而不是 “让我看看有没有空”。系统把 “做事” 变成 “做法”,把 “结果” 变成 “资产”,把 “经验” 变成 “流程”。

在本系列接下来的文章里,我会继续展开 “目标与指标”“架构落地”“数据与迁移”“模型适配与接入”“评测引擎”“管理后台”“可视化与分享”“可观测性与稳定性”“上线与演进”“报告自动化”“生态与 API” 等主题。你会看到每一个设计,如何在代码里落地,又如何在页面上回应真实的协作需求。


↙↙↙阅读原文可查看相关链接,并与作者交流