OpenArt 聚合了很多模型,每个模型消耗的积分和效果都不同。想选出适合自己用途的模型,最好用同一组提示词逐个试、逐个打分。 这篇做
compare_sheet.py:先生成“提示词 × 模型”的测试矩阵 CSV,你在 OpenArt 网页上逐格生成、记下积分并打分,再汇总排名。
先看清成品
- 生成 3 个提示词 × 3 个模型 = 9 格的测试表;示例分数经 LibreOffice 转 xlsx 再转回 CSV 后仍能正确读取(全角“2”也能识别)。
- 发现并修复:有一格没打分时,缺了那道难题的模型平均分虚高。
- 改为只在所有模型都打完分的提示词上排名,并列出被排除的提示词。pytest 3 项通过。
合规提醒:OpenArt 帮助中心写明“目前没有公开 API”(No public API is available currently);服务条款 4.4 规定只能通过 openart.ai 或官方 App 使用生成服务,“只允许真人操作,不允许自动化访问、机器人或脚本”。所以这三篇都不自动操作 OpenArt:生成、下载和打分都在网页上手动完成,脚本只处理你下载到本地的文件。条款还写明免费套餐的输出可能带可见水印;不同模型的商用授权不同,以 OpenArt 页面和条款为准。不上传无授权的他人照片作为参考图。
运行说明:我没有 OpenArt 账号,也不能用脚本操作它。文中的图片是我在 box 上用本地 sd-turbo(Stability AI Community License)生成的替身,用来代替你从 OpenArt 下载的图;“模型A/B/C”的分数是我编的示例。所有脚本和测试都在 box 上真实运行过(2026-10-21,UTC+8)。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
compare_sheet.py |
make 生成测试表,score 汇总 |
prompts.txt |
测试提示词 |
compare_filled.csv |
示例:填好分数的表(分数是我编的) |
test_compare.py |
3 项测试 |
需要准备哪些环境?
| 项目 | 说明 |
|---|---|
| Python | 3.9 以上,只用标准库;测试需要 pytest |
| 表格软件 | Excel、WPS 或 LibreOffice,用来填分 |
关键事实
| 事实 | 来源 |
|---|---|
| OpenArt 按积分消耗,不同模型的价格、效果和授权可能不同,以官网为准 | OpenArt 帮助中心 |
| 生成必须在网页上由真人操作 | OpenArt 服务条款 4.4 |
| LibreOffice 导出 CSV 时去掉了 BOM,保留 UTF-8 | 本文实测(LibreOffice 转换) |
文件结构
76-openart-C-src/
├── compare_sheet.py
├── prompts.txt
├── compare_filled.csv
└── test_compare.py
第 1 步:脚本
"""OpenArt 多模型对比表:先生成“提示词 × 模型”的测试矩阵,你在 OpenArt 网页上逐格生成并打分,再汇总排名。
OpenArt 没有公开 API,生成和打分都是人工完成;本脚本只管表格。"""
import argparse, csv, statistics
from pathlib import Path
CRIT = ["prompt_match", "quality", "text_render"] # 1-5 分:贴合提示词、画面质量、文字渲染
def make(prompts, models, out):
with open(out, "w", newline="", encoding="utf-8-sig") as f:
w = csv.writer(f); w.writerow(["id", "prompt", "model", "credits_used"] + CRIT + ["notes"])
i = 0
for p in prompts:
for m in models:
i += 1; w.writerow([i, p, m, ""] + [""] * len(CRIT) + [""])
return i
def read_scores(path):
raw = Path(path).read_bytes()
for enc in ("utf-8-sig", "gb18030"): # 表格软件另存时可能改成 GBK
try: text = raw.decode(enc); break
except UnicodeDecodeError: continue
rows = list(csv.DictReader(text.splitlines()))
return rows
def to_score(v):
v = (v or "").strip().translate(str.maketrans("0123456789.", "0123456789."))
if not v: return None
x = float(v)
if not 1 <= x <= 5: raise ValueError(f"分数应在 1-5:{v}")
return x
def summarize(rows):
"""只在“所有模型都打完分”的提示词上排名;否则缺了难题的模型平均分会虚高。"""
models = sorted({r["model"] for r in rows})
cell, todo = {}, []
for r in rows:
s = [to_score(r[c]) for c in CRIT]
if None in s: todo.append(r["id"]); continue
cell[(r["prompt"], r["model"])] = (statistics.mean(s), float(r["credits_used"] or 0))
prompts = list(dict.fromkeys(r["prompt"] for r in rows))
full = [p for p in prompts if all((p, m) in cell for m in models)]
out = []
for m in models:
xs = [cell[(p, m)] for p in full]
if not xs: continue
out.append({"model": m, "n": len(xs), "mean": round(statistics.mean(x[0] for x in xs), 2),
"min": round(min(x[0] for x in xs), 2), "credits": sum(x[1] for x in xs)})
out.sort(key=lambda r: (-r["mean"], -r["min"]))
return out, todo, [p for p in prompts if p not in full]
def main(argv=None):
ap = argparse.ArgumentParser(); sub = ap.add_subparsers(dest="cmd", required=True)
a1 = sub.add_parser("make"); a1.add_argument("--prompts", required=True); a1.add_argument("--models", required=True); a1.add_argument("-o", default="compare.csv")
a2 = sub.add_parser("score"); a2.add_argument("csv")
a = ap.parse_args(argv)
if a.cmd == "make":
prompts = [l.strip() for l in Path(a.prompts).read_text(encoding="utf-8").splitlines() if l.strip()]
n = make(prompts, [m.strip() for m in a.models.split(",")], a.o); print(f"{a.o}:{n} 格待生成")
else:
res, todo, skipped = summarize(read_scores(a.csv))
print("| 排名 | 模型 | 参与比较的提示词 | 平均分 | 最低一格 | 积分合计 |\n| ---: | :--- | ---: | ---: | ---: | ---: |")
for i, r in enumerate(res, 1): print(f"| {i} | {r['model']} | {r['n']} | {r['mean']} | {r['min']} | {r['credits']:g} |")
if todo: print(f"\n还没打完分的格:{', '.join(todo)}")
for p in skipped: print(f"未参与排名(有模型没打完分):{p[:50]}…")
if __name__ == "__main__":
main()
第 2 步:测试提示词
a minimalist product photo of a ceramic coffee mug on a wooden table, morning light, soft shadows
a poster with the words "SPRING SALE" in bold letters, flat illustration
the red-haired woman in a yellow raincoat from the reference, standing at a bus stop in the rain
第 3 步:测试
import csv, pytest
import compare_sheet as m
def rows(spec):
out = []
for i, (p, mod, sc, cr) in enumerate(spec, 1):
out.append({"id": str(i), "prompt": p, "model": mod, "credits_used": cr, **dict(zip(m.CRIT, sc))})
return out
def test_only_complete_prompts_ranked():
r = rows([("easy", "A", ("4", "4", "4"), "8"), ("easy", "B", ("5", "5", "5"), "12"),
("hard", "A", ("2", "2", "2"), "8"), ("hard", "B", ("", "", ""), "12")])
res, todo, skipped = m.summarize(r)
assert todo == ["4"] and skipped == ["hard"] and all(x["n"] == 1 for x in res)
assert res[0]["model"] == "B" and res[0]["credits"] == 12
def test_fullwidth_and_range():
assert m.to_score("2") == 2.0 and m.to_score(" ") is None
with pytest.raises(ValueError): m.to_score("6")
def test_make_and_gbk_read(tmp_path):
p = tmp_path / "c.csv"; assert m.make(["x"], ["A", "B"], p) == 2
text = p.read_text(encoding="utf-8-sig"); (tmp_path / "g.csv").write_bytes(text.replace("A", "模型甲").encode("gb18030"))
assert m.read_scores(tmp_path / "g.csv")[0]["model"] == "模型甲"
第 4 步:运行
$ python compare_sheet.py make --prompts prompts.txt --models "模型A,模型B,模型C"
compare.csv:9 格待生成
# 在 OpenArt 网页上逐格生成,填 credits_used 和三项 1-5 分(示例分数是我编的)
$ soffice --headless --convert-to xlsx compare_filled.csv && soffice --headless --convert-to csv compare_filled.xlsx # 模拟用表格软件编辑后另存
$ python compare_sheet.py score rt/compare_filled.csv
| 排名 | 模型 | 参与比较的提示词 | 平均分 | 最低一格 | 积分合计 |
| ---: | :--- | ---: | ---: | ---: | ---: |
| 1 | 模型B | 2 | 4.17 | 4.0 | 24 |
| 2 | 模型A | 2 | 3.33 | 3.0 | 16 |
| 3 | 模型C | 2 | 2.33 | 2.0 | 8 |
还没打完分的格:8
未参与排名(有模型没打完分):the red-haired woman in a yellow raincoat from the…
$ python -m pytest -q
3 passed
怎么检查结果?
- “参与比较的提示词”数量相同,排名才有意义。
- “最低一格”比平均分更能看出模型的短板。
- 积分合计只算参与排名的格;同一模型不同设置(比例、数量)消耗不同,记录时写清楚。
调试示例
缺一格,排名就不公平。 第一版把每个模型打完分的格都算进平均分。模型B的第 8 格(第三个提示词,角色一致性,最难的一题)还没打分:
| 1 | 模型B | 2 | 4.17 | 4.0 | 12、12 |
| 2 | 模型A | 3 | 3.33 | 3.0 | 8、8、8 |
| 3 | 模型C | 3 | 2.44 | 2.0 | 4、4、4 |
模型B只考了两道简单题,A 和 C 考了三道,平均分不能直接比。修复:只用所有模型都打完分的提示词排名,被排除的提示词单独列出;积分改为合计。修复后 C 的平均分从 2.44 变为 2.33(本例排名顺序没变,但比较的范围一致了)。
常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 读取乱码 | 表格软件另存成了 GBK | 脚本会自动尝试 GB18030 |
| 分数应在 1-5 | 填错了 | 改正后重新 score |
| 某个提示词没参与排名 | 有模型没打完分 | 补完那几格 |
模板规则
- 同一组提示词测所有模型。
- 先打完分再排名。
- 记录每格消耗的积分。
- 生成和打分都手动完成。
项目资源
| 用途 | 地址 |
|---|---|
| OpenArt 帮助中心(官方) | https://openart.ai/help |
| OpenArt 服务条款(官方) | https://openart.ai/suite/terms |
| OpenArt 官网 | https://openart.ai |
用同一张考卷,才比得出高下。