screenshot-to-code 评测体系实战:模型与 Prompt 对比评测的数据集、批量生成与人工评分全流程
Evaluation.md 是 screenshot-to-code 项目中专门说明“如何系统性地对比不同模型与 Prompt”的文档:它定义了一套基于 16 张截图输入数据集的批量评测流程——用 Python 脚本并行地对每个输入跑一遍 screenshot-to-code,再借助前端 /evals 页面对生成结果进行 1~4 分的人工评分。读完本篇,你可以掌握该评测体系完整的目录配置、批量运行链路(含重试、计时与失败记录机制)、多模型对比与 Best-of-N 页面用法,以及作者采用的“每组配置跑三次取平均分”的评测方法论。
评测体系总览
根据 Evaluation.md,整套评测由两部分组成:
- 评测数据集:由 16 张截图组成。文档说明输入截图应放在
backend/evals_data/inputs,输出写到backend/evals_data/outputs(注意:当前仓库代码中结果实际写入backend/evals_data/results/{日期}_{模型}_{技术栈}子目录,见下文); - 评测工具链:一个运行 screenshot-to-code 的 Python 脚本(backend/run_evals.py)+ 一个用于查看并给输出评分的前端 UI(路由
/evals)。
这套组合的目的是回答一个实际问题:换不同的 LLM、换不同的 Prompt、换不同的输出技术栈(HTML/Tailwind/React/Vue 等),生成质量到底差多少? 评测流程把“生成”自动化、把“评判”标准化,使不同配置之间可以公平地打分对比。
准备工作:数据集与目录配置
输入数据集的放置位置
文档给出的准备步骤:
- 输入截图放在
backend/evals_data/inputs,输出位于backend/evals_data/outputs; - 如果要修改数据根目录,修改 backend/evals/config.py 中的
EVALS_DIR。
EVALS_DIR 的当前实现就一行,支持用环境变量覆盖默认值:
import os
EVALS_DIR = os.environ.get("EVALS_DIR", "./evals_data")
因此也可以通过 EVALS_DIR=/path/to/evals_data 的方式在不改代码的情况下切换数据目录(相对于启动 run_evals.py 的工作目录解释)。
关于数据集下载,文档原文是“download the input screenshot dataset here: TODO”,即数据集下载地址尚未在文档中补全——这是当前文档的一个待办项,使用时需要准备自己的截图集放入 inputs 目录即可。评测脚本对输入的约束很简单:backend/evals/runner.py 中 _resolve_eval_filenames 会扫描输入目录下所有 .png 文件(或指定的文件列表,只保留 .png 结尾的条目),所以数据集命名只需满足扩展名为 .png。
运行批量评测
文档定义的运行方式
Evaluation.md 给出的命令行流程:
- 在 backend/run_evals.py 中设置技术栈与模型(
STACK变量、MODEL变量); - 运行:
OPENAI_API_KEY=sk-... python run_evals.py
该命令会并行地对输入数据集跑 screenshot-to-code,文档提示即便如此也“仍需几分钟完成”;
- 脚本结束后,在
backend/evals_data/outputs查看输出。
需要说明适用前提:run_evals.py 只负责加载环境变量(load_dotenv())并异步调用 run_image_evals(),而 backend/evals/runner.py 中的 run_image_evals() 对 stack 和 model 都是必填项——缺任何一个都会直接抛出 ValueError。从当前仓库结构看,主路径已经演进为通过前端 /evals/run 页面调用后端 POST /run_evals(或流式版本 /run_evals_stream)来传入模型列表与技术栈,详见 backend/routes/evals.py;而命令行方式更适合作为一次性批处理入口,按文档把 STACK/MODEL 传给 run_image_evals() 即可。
执行链路:并行调度、重试与计时
run_image_evals() 是整个评测的执行核心,值得拆开看:
- 并行调度:为每张输入图 × 每个尝试序号(
n参数,默认 1)生成一个协程,全部通过asyncio.as_completed并发执行,先完成的先落盘。这就是文档所说“并行运行”的实现来源; - 图片预处理:每张 PNG 经 backend/evals/utils.py 的
image_to_data_url()读为字节流并转成data:image/png;base64,...数据 URL,供多模态模型直接消费。同一张输入图在多次尝试(n > 1)间只编码一次; - 重试策略:
MAX_EVAL_RETRIES = 2。生成失败会自动重试,最多 2 次,重试次数会计入任务日志;但有一类错误永不重试——BudgetExceededError(预算超限),因为“每次重试都会再花掉一整份预算上限”(见 backend/evals/runner.py 的注释); - 计时落盘:每个成功任务记录耗时,全部完成后追加写入输出目录下的
generation_times.txt(首行含Model: <模型名>),失败任务则写入failed_tasks.txt,记录输入、尝试序号、重试次数与错误信息,便于定位问题样本。
输出文件的命名规则
输出目录由 get_eval_output_subfolder() 生成:
{EVALS_DIR}/results/{Mon_dd_YYYY}_{model}_{stack}/
├── {输入文件名}_{尝试序号}.html # 如 001.png -> 001_0.html
├── generation_times.txt
└── failed_tasks.txt
也就是说,每次(模型 + 技术栈 + 日期)组合对应一个独立的结果文件夹,文件名的“输入名_尝试序号”命名法是后续 /evals 页面把输出和输入截图配对展示的基础(backend/routes/evals.py 中 get_evals 正是按 base_name 从输出文件名反推、再去 inputs 目录找同名 .png)。
此外还有一个容易忽略的细节:normalize_local_asset_urls() 在写文件前会重写 HTML 中的本地资产引用。由于评测环境下没有 5173 端口的开发服务器,生成代码里的 /local-assets/... 或 localhost:5173 资源引用会被统一替换为 LOCAL_ASSET_BASE_URL 前缀,保证评测产出的 HTML 单独打开时仍能渲染出真实提取的图片资产。
生成核心:复用主链路的 Agent 运行器
单张图的代码生成由 backend/evals/core.py 的 generate_code_for_image() 完成,它并非独立实现一套生成逻辑,而是直接复用产品主链路的 Agent 运行器,这一点保证了“评测结果 = 线上真实表现”:
- 用
build_image_prompt_messages()(backend/prompts/create/image.py)基于截图数据 URL + 技术栈构造 Prompt 消息;text_prompt传空串,因为评测只对比“同一张图”在不同模型/Prompt 下的表现; - 校验对应厂商的 API Key——
OPENAI_API_KEY、ANTHROPIC_API_KEY、GEMINI_API_KEY按所选模型分组检查,缺失则直接抛异常; - 创建
AgentRunRecorder(entry_point="eval",可附带eval_session、eval_set、input_file元数据)并构造Agent(backend/agent/runner.py),should_generate_images=True表示允许走图片生成能力,asset_base_url显式传入LOCAL_ASSET_BASE_URL(注释说明:评测没有 websocket 可推断 host,否则提取资产会拿到无 host 的/local-assets/URL); await runner.run(model, prompt_messages)返回最终代码文本。
从这套结构看,评测中更换 Prompt 的效果,本质上就是改变 build_image_prompt_messages 产物或系统 Prompt 后,同一 Agent 管线在不同模型上的输出差异——这正是文档所说“compare and evaluate various models and prompts”的落点。相关行为还有 backend/tests/test_eval_runner.py、backend/tests/test_eval_sessions.py、backend/tests/test_eval_sets.py 等测试用例可供交叉验证。
查看输出与评分(前端 /evals 页面)
文档“Rating evals”一节的操作要点:
- 打开前端的
/evals路由查看并评分; - 每个输出按 1~4 分制评分;
- 可以把页面打印为 PDF,方便把评测结果分享给他人。
对应到当前仓库,/evals 并不是单一页面,而是一组评测工作台(见 frontend/src/main.tsx 路由定义):
| 路由 | 页面 | 作用 |
|---|---|---|
/evals |
AllEvalsPage | 评测导航首页 |
/evals/run |
RunEvalsPage | 选择多模型 + 技术栈 + 输入文件,发起批量评测并实时显示进度 |
/evals/best-of-n |
BestOfNEvalsPage | 横向对比多个结果文件夹中同一输入的输出 |
/evals/sessions |
EvalSessionsPage | 管理评测会话(见下文) |
后端为这些页面提供的 API(backend/routes/evals.py):
GET /evals?folder=<结果目录>:读取某结果文件夹下所有.html,与inputs中同名截图配对,返回[{input: <数据URL>, outputs: [<HTML内容>]}],供页面并排展示“原图 vs 生成代码渲染效果”;POST /run_evals:接收models(模型名列表)、stack、可选files、diff_mode,对每个模型依次调用run_image_evals();POST /run_evals_stream:同样执行批量评测,但以 NDJSON 流式下发start/model_start/task_complete/complete事件,前端据此渲染进度条与失败任务数;GET /models:返回当前代码库中可用的全部Llm枚举值与全部Stack取值,前端下拉框即来源于此;GET /best-of-n-evals?folder1=...&folder2=...:取多个结果文件夹的公共输入基名,把每张输入图在各文件夹里的输出拼成一组返回,实现“N 个输出并排比较”;某文件夹缺输出时会用<html><body>Output not found</body></html>占位;GET /output_folders:列出evals_data/results下全部结果文件夹,按修改时间倒序,供页面选择要查看哪一轮评测。
diff_mode 值得单独说明:开启后,若某(输入图 × 尝试序号)的输出 HTML 已存在于目标结果文件夹,该任务会被跳过(count_pending_eval_tasks() 会先统计“待跑/已跳过”数量并在进度事件里报出 total_skipped_existing),从而支持“只补跑缺失项”的增量评测。
评测方法论:跑三次取平均
文档最后给出了作者的评测惯例:
通常对每组「模型/Prompt + 技术栈」组合跑 3 次测试,取这 3 次测试的平均分作为评估依据。
即同一配置重复 n=3 次(对应输出文件 _0.html、_1.html、_2.html),人工评分后取平均,以抵消单次生成中的随机波动。这是这套评测体系最重要的统计学约定,评分时应严格遵循,否则单次高分容易把噪声当结论。
进阶能力:评测集(Sets)与评测会话(Sessions)
当前仓库在文档所述基础上又演进出了两套配套机制,用于管理“多轮、多数据集”的评测工作流,理解它们有助于把评测从一次性脚本升级为可持续的对比工程。
评测集:sets/{名称}/inputs/
backend/evals/sets.py 定义了“具名评测集”:把 PNG 放进 {EVALS_DIR}/sets/{set_name}/inputs/ 即完成创建(无创建 API)。目录名受正则 ^[A-Za-z0-9][A-Za-z0-9._ -]*$ 约束(允许 jun-21-evals、Landing Pages v2 这类人类友好命名,同时阻断路径穿越)。每个集合附带一个 manifest.json 元数据旁挂文件:
- 存放
display_name、created_at、notes等展示信息; - 缓存每张图的 sha256,以
(文件大小, mtime)为新鲜度键——文件没变就直接命中缓存,变了才重新哈希; - 注释中说明了它的用途:图像的 sha256 是 UI 生成结果与“矩阵行”(图片 × 模型)对账的键。
评测会话:一次工作期锁定一个评测集
backend/evals/sessions.py 把“一段时间内对某个评测集的评测”建模为 session,持久化在 agent-runs 的 SQLite 索引库(schema 见 backend/fs_logging/agent_runs.py):
- 同一时刻只有一个 active session,新建会话会使旧会话失活(会话永不“结束”,只是被取代);
- 会话创建时固定(pin)到某个评测集:运行评测时若 active 会话属于别的集合,会抛
SessionSetMismatchError,要求先显式开新会话;若根本没有会话,则自动创建一个,保证“带 set 直接跑”的体验; - 评测运行时,
core.py里的AgentRunRecorder会把eval_session、eval_set、input_file写进运行记录。由此,“会话矩阵”(图片 × 模型)就是对 runs 索引的一次普通 SQL 查询——completed_eval_inputs()据此统计某会话中(模型, 技术栈)已完成哪些输入,供 diff 模式跳过; - 注释还特别强调:会话永不拦截 UI 的生成路径,它只是纯元数据,普通产品生成不受影响。
对评测者的实际意义是:以“session + set”为单位组织实验,之后任何一次补跑(diff_mode)都只补该会话里真正缺的格子,而不是凭共享日期文件夹猜文件是否存在——源码注释明确指出,按日期命名的输出文件夹是跨集合共享的,靠文件存在性判断会在集合间产生假阳性,所以集合运行一律以会话历史为准。
关键配置与产物速查
| 项 | 位置/取值 | 说明 |
|---|---|---|
EVALS_DIR |
backend/evals/config.py,默认 ./evals_data |
评测数据根目录,可被环境变量覆盖 |
| 输入截图 | {EVALS_DIR}/inputs/*.png |
默认数据集 16 张截图;仅识别 .png |
| 具名评测集 | {EVALS_DIR}/sets/{名称}/inputs/*.png + manifest.json |
sha256 缓存、显示名与备注 |
| 结果文件夹 | {EVALS_DIR}/results/{Mon_dd_YYYY}_{model}_{stack}/ |
按 日期+模型+技术栈 隔离 |
| 输出文件 | {输入名}_{尝试序号}.html |
与输入截图同名前缀配对 |
generation_times.txt |
结果文件夹内 | 每个成功任务的耗时(秒),首行标注模型 |
failed_tasks.txt |
结果文件夹内 | 失败任务的输入/重试次数/错误信息 |
| 重试上限 | MAX_EVAL_RETRIES = 2(backend/evals/runner.py) |
BudgetExceededError 不重试 |
| 评分标准 | 1~4 分制;每组配置跑 3 次取平均 | 见 Evaluation.md |
| 可用模型/技术栈 | GET /models 动态返回 Llm 与 Stack 枚举 |
以后端代码为准,勿写死 |
适用前提与注意事项
- API Key:所选模型属于 OpenAI/Anthropic/Gemini 哪一族,就必须配置对应的
OPENAI_API_KEY/ANTHROPIC_API_KEY/GEMINI_API_KEY(文档示例命令只演示了 OpenAI 一族); - 成本与时长:批量评测会真实调用付费模型接口,16 张图 × 多模型 × 多次数叠加后开销不小,
BudgetExceededError不重试的机制就是为控住这类开销; - 输出目录以代码为准:文档写的是
evals_data/outputs,当前代码实际写到evals_data/results/{日期}_{模型}_{技术栈},查看结果时以results下最新文件夹(/evals页面按修改时间倒序列出)为准; - 数据集下载链接待补:文档中的数据集下载地址仍是 TODO,需要自备 16 张(或任意数量)
.png截图作为输入; - 评测与产品链路同源:由于评测直接复用
build_image_prompt_messages+ Agent 运行器,评测结论对线上生成质量有直接参考价值;反之,修改主链路 Prompt 后重跑同一评测集,就是最直接的回归验证手段。
综合来看,screenshot-to-code 的这套评测体系把“选数据 → 批量生成(并行 + 重试 + 计时 + 失败留痕)→ 多输出对比(Best-of-N)→ 1~4 分人工评分 ×3 次取均值”串成闭环,并以评测集、评测会话和 runs 索引把多轮实验组织成可查询的矩阵,是研究开源模型对比时相当完整、可复刻的一套方法论。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00