- AI 应用
- 媒体生成
- 后端
- 前端
【免费下载链接】pollinations
Your Friendly Open-Source Gen-AI Platform
本文围绕 pollinations 仓库中社区模型排行榜(community model leaderboard)的生成技能文档展开:它是一张每天发布到 Discord#dev-community-models频道的像素风图片,按过去 24 小时服务的 token 数对社区文本模型排名,并附带速度面板与逐模型成功徽章。读完本文,你将完整掌握排行榜的设计决策依据、build-leaderboard.mjs的取数与渲染管线、chromium 沙箱渲染的踩坑约束,以及如何用--dry-run手动复现整条流水线。
排行榜是什么、什么时候跑
根据 SKILL.md 的定义,这张每日榜单的生成脚本是operations/community-monitor/leaderboard/build-leaderboard.mjs,它由 community-monitor EC2 监控机上的 Claude agent 自动驱动:CYCLE.md 的 duty 3b 规定,文本榜与图像榜各自维护独立的状态键(lastLeaderboardPostedDate/lastImageLeaderboardPostedDate),仅在 UTC 日期尚未记录且时间到达 ~12:00 UTC 后才会运行,即一天一次。CYCLE.md 还明确了运行方式与产出契约:
Each script queries Tinybird, renders the pixel-art board via chromium, crops it, uploads to media.pollinations.ai, and prints JSON:
{ date, imageUrl, markdown, pngPath }. Each takes ~10-15s.
脚本出错或没有合格模型时,agent 不设置状态键、下周期重试;成功则把 stdout 的markdown字段逐字(verbatim)发到频道——因为 markdown 中的奖项文案(workhorse、flawless、speed crown)是刻意挑选出来展示图片本身没有的统计量的,不允许改写或复述。榜单图片本身通过POST /upload上传后,URL 直接内嵌在这条 markdown 文本里,由 Discord 自动内联成图片,不另发附件。
部署链路上,operations/community-monitor/README.md 给出了把leaderboard/build-leaderboard.mjs、build-image-leaderboard.mjs、fonts-embedded.css三个文件scp到监控机的命令;而 update-from-repo.sh 会在每个周期开始前从origin/main自动刷新这两个构建脚本(见其第 48–49 行install_from_main leaderboard/...两行),保证仓库合并后的改动在下一个周期生效。
手动重新生成:命令、参数与前置条件
SKILL.md 给出的标准手动操作(可直接复制):
cd operations/community-monitor/leaderboard TB_TOKEN=<token> node build-leaderboard.mjs --dry-run # 只预览 HTML,不渲染、不上传 TB_TOKEN=<token> POLLI_TOKEN=<token> node build-leaderboard.mjs # 完整渲染 + 上传,并打印 markdown两种运行模式的行为差异,源码里一一对应(build-leaderboard.mjs):
--dry-run:只跑 Tinybird 查询并生成 HTML,写入/tmp/leaderboard-preview.html,跳过 chromium 截图与上传,stdout 打印{ dryRun: true, markdown }。用于在"花掉"一次上传额度之前,先验证查询结果与排名是否符合预期。- 完整运行:渲染 PNG 到
/home/ubuntu/monitor/leaderboard-<YYYY-MM-DD>.png,上传后把 markdown 中的{{IMAGE_URL}}占位符替换为真实 URL,最终 stdout 输出{ date, imageUrl, markdown, pngPath }四字段 JSON——这正是 CYCLE.md duty 3b 消费的输出契约。
环境要求(来自 SKILL.md 的 Rendering 一节,脚本注释同样印证):
| 依赖 | 说明 |
|---|---|
chromium(snap 版) | 直接execFileSync调用,不引入 puppeteer/playwright 等 npm 依赖 |
python3-pil | 一次性系统包,sudo apt-get install -y python3-pil,用于裁剪截图底部留白(build-leaderboard.mjs 注释明确标注 "Requires python3-pil on the box") |
TB_TOKEN | Tinybird SQL 查询令牌,缺失直接process.exit(1)(L18-L22) |
POLLI_TOKEN | media 上传的 Bearer 令牌,仅完整运行需要 |
POLLINATIONS_MEDIA_URL(可选) | media API 基址,默认https://media.pollinations.ai(L321-L322) |
数据层:Tinybird 查询如何决定排名
脚本的取数逻辑全部集中在fetchLeaderboardData()(build-leaderboard.mjs),两条 SQL 都查 Tinybird 的generation_event_v2表,窗口为过去 24 小时、event_type = 'generate.text'、model_provider_used = 'community'、is_final。三个关键常量定义了榜单形态:
const MIN_REQUESTS = 50; // 24h 请求数不足 50 的模型没有统计意义,直接忽略 const TOP_N = 10; // 主榜 10 行 const SPEED_TOP_N = 3; // 速度面板取前 3主榜行字段的计算口径值得细看(L44-L57):
total_tokens:token_count_prompt_text + token_count_prompt_cached + token_count_completion_text + token_count_completion_reasoning之和,按它降序排列——这就是"按 tokens served 排名"的设计决策在 SQL 层的落点;median_tps:每请求完成 token 速率((completion_text + completion_reasoning) / (response_time/1000))的中位数,且只统计response_status < 300、response_time > 0、完成 token 数>= 20的请求。用中位数而非均值,正是 SKILL.md 设计决策里说的"冷启动/超时等离群点会把均值严重拉偏";success:100 * countIf(2xx) / greatest(countIf(response_status < 400 OR response_status >= 500), 1)——分母排除了 4xx(调用方错误),所以这是"排除客户端错误后的成功率";- 筛选门槛:
HAVING requests >= 50对应MIN_REQUESTS;查询用replaceRegexpOne(model_requested, '^community/', '')把community/owner/model规范化成owner/model。
第二条 SQL 算全局总量(24h 总请求数、总 token 数、去重模型数),用于顶部 hero 计数器,例如 SKILL.md 里举的57,468,899 TOKENS · 24H。若 24 小时内没有任何模型达到 50 次请求,main()打印 "No community models with enough traffic" 并以 0 退出——这就是 CYCLE.md 说的"找到没有合格模型时记入周期摘要、不设状态键"的触发条件。
设计决策:为什么是 tokens 排名、徽章与速度面板
SKILL.md 专门有一节 "Design decisions (read before changing anything)",强调这些结论来自多轮线上反馈,"没有理由不要重新争论"。逐条对照源码,每条都有实现落点:
按 tokens served 排名,而不是请求数。请求数会奖励"话多但完成短"的小模型;一个请求量 4 倍、token 量只有 1/3 的模型不应排在 token 领先者前面——这是设计期间"最重要的一次纠正"。对应源码
ORDER BY total_tokens DESC(L55)。token 数是主角数字:顶部 hero 计数器 + 每行最左侧一列(在模型名之前),刻意让视线先读到量级。HTML 生成的行结构是
.score(token 数)→.name(模型名)→.tpsl→.hp(L106-L121)。稳定性是行内徽章,不是独立榜单。早期版本有单独的 "most stable" 面板,因为多数模型都在 100% 附近、并列太多导致排名失去意义,被砍掉,降级为状态指示器。具体分档逻辑在
hpBadge()(L78-L93):成功率 徽章 恰好 100% 亮绿色 PERFECT徽章("庆祝从不失败的模型,而不是把它们混进爱心档")≥99% ♥♥♥(绿色像素心 ×3) ≥95% ♥♥ ≥80% ♥(琥珀色) <80% 红色像素骷髅 速度单独做小面板,不混进主榜:主榜下方三张速度卡片(中位 tokens/sec),第 1 名是金色卡片 +
FASTEST奖章(L123-L138)。文字最少、图形最大:没有解释性段落,只有主角数字、列表头和条形图;"想加说明文字时,加一个统计量"。
主榜不放头像/排名徽章:那是配套 "Arena" 散点图的元素;主榜上第一行的皇冠(
<span class="crown">,L112)已经足够有仪式感。
此外 stdout 的 Discord markdown(L347-L380)还有两项"图里看不出来"的奖项:🐝 workhorse——请求数最多的模型(且仅当它不是 token 第一时追加),以及🛡️ flawless: N models at 100% success。
品牌系统:嵌入字体、配色板与版式
字体:base64 内嵌是硬要求
fonts-embedded.css 里有 4 个@font-face块:Press Start 2P(400,标题/得分,像素街机感)与IBM Plex Mono(400/600/700,正文/标签),全部以data:font/woff2;base64,...内联。SKILL.md 强调这是required, not optional:这张板子被设计为在 chromium 沙箱里独立截图渲染,沙箱没有访问 Google Fonts 的网络出口;不内嵌字体时文本会静默回退到通用等宽字体、"看起来是坏的"。诊断口诀:如果看到某次渲染的标题不是方块字,先查字体嵌入。
字体需要更换时的重建步骤(继承自 SKILL.md):
- 用普通浏览器 UA去 Google Fonts 的 css2 接口取 woff2 文件——默认的 curl UA 会被下发过时的 ttf 格式;
- 每个
src: url(...)做 base64 编码,内联成@font-face的 data URI; - 只保留 Latin 子集——全 Unicode 覆盖对一块纯英文的板子只会白白膨胀文件体积。
buildHtml()会在运行时读入这份 CSS 并注入<style id="fonts">(L99-L103),所以字体文件必须与脚本同目录部署——这也是 README 部署命令里三文件一起 scp 的原因。
配色板:通过可访问性校验的"图表合法色"
SKILL.md 给出了经过dataviz技能可访问性检查的配色体系:
| 用途 | 颜色 |
|---|---|
| 背景(cream) | #F3EBDE |
| 文字/边框(ink) | #110518 |
| 速度条形 | periwinkle#5978df |
| 强调 | lavender#8338b3 |
| 状态-稳定 | mint#3b9736 |
| 状态-观察 | #b8860b |
| 状态-降级 | #b03a2e |
注意图表用色不是品牌原色,而是同色相的 OKLCH 调整版——原始品牌粉彩色系通不过对比度/CVD(色觉障碍)检查。这些值都能在脚本的内联样式中验证:行徽章的.ok/.warn/.bad三色(L188)、速度条填充#818900、主背景与点阵纹理radial-gradient(#cfc8b8 1px, transparent 1px)等。
版式上固定 1200px 宽(Discord 内嵌图宽度),高度由内容决定(10 行 + 速度面板),渲染后再裁剪——"house pixel/arcade look" 的关键是硬偏移阴影(box-shadow: 6px 6px 0 #110518)与全局零圆角(脚本里甚至写了border-radius:0 !important兜底,L146),与产品内其他组件的同一套视觉词汇保持一致(SKILL.md 指向pollinations.ai/src/ui/components/TopContributors.tsx为同词汇参考,该文件在当前仓库此路径下已不存在,可作历史线索看待)。
渲染:chromium 在 snap 沙箱下的四条硬约束
"Rendering" 一节是"用血的教训换来的约束清单",全部已固化在renderPng()中(build-leaderboard.mjs):
--no-sandbox必须有——snap 的 confinement 会拦截 Chrome 的正常沙箱。实际调用参数:--headless=new --disable-gpu --no-sandbox --hide-scrollbars --window-size=1200,1600 --screenshot=<raw.png> file://<board.html>(L282-L294)。- 永远不要把 chromium 的输出写进点前缀目录(如
/home/ubuntu/.foo/)——snap 的 AppArmor 配置会静默拒绝写入并报Permission denied,即使该目录对该用户本来可写。所以临时目录名是leaderboard-tmp-*(mkdtempSync(join("/home/ubuntu/monitor", "leaderboard-tmp-"))),脚本注释专门记录了这条验证过的坑(L271-L279)。 - Chromium CLI 没有"按内容定尺寸"的截图模式——它永远按
--window-size渲染,页面更短就留下尾部空白。脚本的解法是"先画高再裁剪":按 1200×1600 渲染,然后内联一段 PIL 脚本从底向上扫描最后一行含深色像素(逐行抽样sum(px[x, y]) < 200)的位置,裁掉留白并保留 26px 底边距(L300-L312)。 - stderr 里的 dbus/AppArmor ERROR 是装饰性噪音(
ListActivatableNames、UPower一类),不是失败——只有输出文件缺失才代表渲染真的挂了。
上传与发布:直连 media API,而非 polli CLI
上传走POST https://media.pollinations.ai/upload,BearerPOLLI_TOKEN、multipartfile字段,而不是 shell 出去调polliCLI——SKILL.md 解释理由很直接:监控机没装 polli,而上传"只是一个两行的 fetch,不值得加依赖"。源码实现印证了这个契约(polliUpload()):
const form = new FormData(); form.append("file", new Blob([buf], { type: "image/png" }), "leaderboard.png"); form.append("tags", "community:leaderboard"); const res = await fetch(`${mediaUrl}/upload`, { method: "POST", headers: { Authorization: `Bearer ${token}` }, body: form, });两点补充证据:
- media 服务的公开文档(media.pollinations.ai/README.md)确认了接口形态:上传需要 API key(Bearer),响应体为
{ id, url, contentType, size },脚本正是取其中的data.url作为图片地址;tags字段会让上传物进入对应 tag 的公共画廊(脚本用community:leaderboard标记这批图); - 仓库里其实有等价 CLI 实现 packages/polli-cli/src/commands/upload.ts:同样是 Bearer + 流式 body POST 到
${MEDIA_URL}/upload(config.ts 中MEDIA_URL默认https://media.pollinations.ai),并输出UploadResponse { id, url, contentType, size }。SKILL.md 明确建议:如果 media API 的响应形态将来变化,参考这个 CLI 文件——它是对同一契约的第二份参照。
发布环节的最后一步由 agent 完成:把 stdout JSON 的markdown字段原样作为一条 Discord 消息发出(图片 URL 内联在文本中,Discord 自动内嵌),不要重新推导或改写。
配套设计:"The Arena"(未转正的散点图方案)
SKILL.md 末尾记录了与主榜并行探索过的第二张可视化:speed × success × price 散点图,GitHub 头像标记(等尺寸)、每模型价格标签。它被作为一次性图发布过一次,HTML/构建脚本目前只存在于会话草稿区,尚未提升到leaderboard/目录。若未来要把它变成例行发布,SKILL.md 给出的移植要点是:以design-arena-v2.html为结构蓝本,复用主榜的渲染/上传管线,包含github.com/<user>.png?size=96的头像缩略图(最近邻放大做像素化)、log 缩放的 x 轴、针对 95–100% 成功聚集区的分段 y 轴,以及一次简单的两两斥力布局 pass 防止同尺寸标记重叠。
小结:一张图背后的工程取舍
pollinations 的社区模型排行榜用很少的代码量(单个无依赖 Node 脚本 + 一份内嵌字体 CSS)支撑了一条完整的数据可视化管线:Tinybird 24h 聚合(50 请求门槛、token 排名、中位速率、排除 4xx 的成功率)→ 纯 CSS 像素风 HTML(固定 1200px、tokens 前置、PERFECT/心/骷髅三档徽章)→ snap chromium 无头截图(--no-sandbox、避免点前缀目录、画高再 PIL 裁剪)→ media API 上传 → agent 逐字发布 markdown。它的价值不只是"发一张图",更在于把多轮社区反馈固化成了可执行的规则(SKILL.md 的设计决策清单)和可自动部署的脚本(update-from-repo.sh每周期同步),让"排行榜长什么样"这件本来靠口头约定的事,变成了仓库里可审查、可复现的工程资产。
- AI 应用
- 媒体生成
- 后端
- 前端
【免费下载链接】pollinations
Your Friendly Open-Source Gen-AI Platform
相关推荐
Pollinations 模型故障调试实战:从 gen/enter 日志到 Tinybird 数据的完整排障链路
Pollinations 模型故障调试实战:从 gen/enter 日志到 Tinybird 数据的完整排障链路 本文基于 pollinations 仓库中的模
AI 应用媒体生成后端前端Aria2常见问题排查手册:从安装失败到下载异常的解决方案
Aria2常见问题排查手册:从安装失败到下载异常的解决方案 Aria2是一款高效的命令行下载工具,通过Aria2一键安装管理脚本(aria2.sh)可以快速部署
运维OpenDesign 的 Vercel 设计系统实战:Geist 字体、阴影即边框与黑白工程美学全解析
OpenDesign 的 Vercel 设计系统实战:Geist 字体、阴影即边框与黑白工程美学全解析 本文以 OpenDesign 仓库内置的 Vercel
AI 应用人工智能AI 技能设计系统媒体生成
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考