* feat(cache): 节点缓存键纳入 CSV 文件签名 ComfyUI 默认只按输入参数判断缓存是否命中。两个节点读的都是外部 CSV, 参数不变而文件被替换时缓存会一直返回旧结果,用户换了数据却看不到变化。 把 CSV 的「路径 + 修改时间 + 大小」作为 IS_CHANGED 的返回值纳入缓存键: 文件一变就重算;文件与参数都不变时指纹稳定,缓存行为与 5.0.0 一致。 路径被防护拒绝时返回稳定哨兵而不抛异常,避免打断 ComfyUI 的缓存判断。 * perf(character): 派生索引随记录缓存,单次执行 62.6ms -> 26.7ms 角色节点每次执行都在 3.4 万条记录上重建 record_map / names / 作品筛选下标, CSV 没变时完全是重复劳动。把三者收进 CharacterIndex,与记录共用同一套 文件签名缓存(路径 + 列 + mtime + size),CSV 一变就重建。 顺带修掉作品下拉菜单的失效问题:菜单改为按默认 CSV 的文件签名缓存, 换 CSV 后自动重建,不再需要重启 ComfyUI。 实测(本机 7 次中位数):generate_characters 62.62ms -> 26.69ms(-57%)。 4 个 seed 下 prompt / characters_json / tested_count 逐字节一致。 剩余约 24ms 在 select_artists 的排除过滤里;改动它会改变 RNG 消耗顺序、 破坏同 seed 的可复现性,故本次不动。 * docs(db): 修正 generation_lock 的持锁范围说明 原文写「调用方应在进入本上下文之前完成抽样、权重计算、提示词拼装等纯 CPU 工作」, 但 RollingArtist 的重试循环必须待在事务里(命中已测组合要重新抽样), 实际持锁时长等于一次完整生成。文档与实现互相矛盾,读代码的人会误判并发行为。 改为如实描述:持锁时长由调用方决定,两种调用方式各自的代价是什么, 以及为什么不能简单把抽样挪到锁外(冲突只会推迟到写入时才被 INSERT OR IGNORE 吞掉)。 同时修正 __init__ 里「不覆盖事务内的业务计算」这句同样有歧义的注释。 纯注释改动,无代码行为变化。 * docs: 5.1.0 变更记录与 README 缓存/性能说明(T-04) * docs: 归档 v5.1.0 迭代计划至 docs/iteration * chore(release): 5.1.0 * docs: 回填 v5.1.0 执行记录至迭代计划 * docs: Sprint 2 文档三件套(决策登记/性能基线/测试口径) * ci/chore: mypy job 非阻断 + .comfyignore 收敛发布面 * chore(release): 5.1.1 * docs(iteration): 回填 5.1.1 发布与 T-10 收尾执行记录 * docs(od-01-od-02): 落实两项决策的提示强化(tooltip/README/locales 同步) - OD-01/OD-02 均按方案 A 关闭(维持内存窗口 / 维持启动即清空) - 两节点 tested_db_path tooltip 补默认库启动即清空提示 - 角色节点 character_count tooltip 补窗口语义(内存/重启清零/多实例独立) - README 重复回避段补多实例独立表述 - locales/zh 与代码 tooltip 逐字对齐(28 条,修复存量漂移),locales/en 同步 * chore(release): 5.1.2 * docs(iteration): 回填 5.1.2 发布与 OD 决策关闭执行记录 * docs(iteration): 回填 5.1.1/5.1.2 Flagged 定级与 .comfyignore 收敛验证 * fix(registry): 等价 API 替换消除扫描误报(sqlite3.connect→sqlite3.Connection、os.environ.get→os.getenv) - 行为零变化:338 个测试用例全过,门禁四件套全绿 - 目标:消除 Registry 安全扫描对 5.0.0-5.1.2 的 info 级误报 Flagged - 兜底:如扫描器仍有其他触发模式,回退 registry-backend 人工申诉(稿已备) * docs(iteration): 回填 5.1.3 误报消除执行记录与分支预案 * feat(character): T-16 作品筛选执行期一致性(报错分级+copyrights 缓存+边界说明) - CharacterIndex 新增 copyrights 惰性缓存属性(与下拉同一排序) - 作品筛选为空的报错分级:自定义 CSV 场景指明「不在自定义 CSV 的作品列表中」并给出该 CSV 总量与热门示例;默认场景指明「不在默认 CSV 的作品列表中」 - copyright_pick / custom_csv_path tooltip 与 README 补齐联动方向 - locales zh/en 同步;新增 4 个测试(含报错消息断言) - 背景:ComfyUI INPUT_TYPES 无参调用拿不到实例参数,下拉联动架构上不可行; 执行期筛选本就作用于 custom_csv_path,本版补齐错误指引与边界说明 - 不动 pyproject 版本号(等 5.1.3 定级后发 5.1.4)
ComfyUI RollingArtist
两个 ComfyUI 节点,从 CSV 随机抽人生成提示文本:
- RollingArtist(滚动艺术家):抽艺术家 + 分配随机权重
- RollingCharacter(滚动角色):抽角色(不带权重),带作品标签与角色描述(源数据列
core_tags),支持按作品筛选
两者共用抽样、去重与记录机制:4 种去重模式下「判重 → 生成 → 记录」在同一 SQLite 事务内,多线程与多开 ComfyUI 都不重复出图。中英文界面自动切换。
安装
cd ComfyUI/custom_nodes
git clone https://github.com/StarAsh042/ComfyUI_RollingArtist.git
仓库自带 danbooru_art_001.csv(artist,trigger,count)与 danbooru_character_001.csv(character,copyright,trigger,core_tags,count),克隆即用。换数据见数据预处理。
ra_core/、locales/、web/docs/ 由 ComfyUI 按目录约定自动加载;后两者缺失只影响文案。节点名、参数名与 tooltip 跟随 Comfy.Locale(文案在 locales/zh/、locales/en/),「信息」面板取 web/docs/RollingArtist/<语言>.md,缺失时回退 web/docs/RollingArtist.md。新增语言:复制 locales/en/ 为 locales/<语言代码>/ 并翻译 nodeDefs.json,帮助页同理。
danbooru_art_full.csv / danbooru_character.csv(加工脚本的输入)用 Git LFS 存储,节点运行不需要它们——只有重跑加工脚本才需 git lfs install + git lfs pull。
RollingArtist(画师)
下列顺序即界面顺序;advanced 参数折叠在“高级输入”中。
| 参数 | 类型 | 范围 | 说明 |
|---|---|---|---|
artist_count |
INT | 1-10 | 生成人数(默认 3) |
artist_top_ratio |
FLOAT | 0.01-1.0 | Top 池占前百分比(默认 0.01) |
artist_top_count |
INT | 1-10 | 输出中的 Top 人数(默认 1) |
weight_min / weight_max |
FLOAT | 0.1-2.0 | 单个权重的最小 / 最大值 |
weight_total |
FLOAT | 0.0-20.0 | 权重总和(默认 2.0) |
weight_curve |
ENUM | flat / dominant / ramp | 分配曲线(默认 flat) |
artists_prefix |
BOOLEAN | - | 添加 artist: 前缀 |
seed |
INT | 0-4294967295 | 随机种子 |
可选参数
use_top_priority(默认开):启用 Top 优先池;关闭后从完整 CSV 均匀抽取sort_by_weight(默认开):按权重降序排序exclude_artists/force_include:逗号分隔的名单;force_include必定出现,优先级高于排除名单dedup_mode(默认none):none不去重(仍记录);full_prompt顺序与权重全同才算重复;artist_set只看集合;artist_list比较排序后名字。完整对比见web/docs/RollingArtist/zh.mdmax_attempts(1-100,默认 10):重试上限;仍全命中时接受最后一次并打 WARNINGcustom_csv_path/tested_db_path:留空用默认 CSV /rollingartist.sqlite;换数据库路径等于重置去重进度
固定读 trigger 列(已转义,如 hammer_\(sunset_beach\))。没有该列时回退首列并记 WARNING;若输出里出现数字或网址(把 count、url 当成了人名),就是这种情况。
RollingCharacter(角色)
从 danbooru_character_001.csv 抽取,按 角色标签,作品标签,角色描述 逐行输出(多角色间用「逗号+换行」分隔),不带权重。读取列固定 trigger,经 34,416 行全量校验是恒定两段:第 1 段 = 转义后的 character(角色名),第 2 段 = 转义后的 copyright(作品标签)。
hakurei_reimu,touhou,1girl,brown eyes,long hair,
ganyu_\(genshin_impact\),genshin_impact,1girl,horns
关闭「开启角色描述」后为 hakurei_reimu,touhou。空的部分自动省略;角色描述(core_tags)行内去重;多角色行尾补逗号(最后一行不补),顺序即抽样顺序。
| 参数 | 类型 / 范围 | 说明 |
|---|---|---|
character_count |
INT 1-10 | 每次生成数(默认 1),每个角色一行 |
character_top_ratio |
FLOAT 0.01-1.0 | 候选数 = 作品筛选之后的角色数 |
character_top_count |
INT 1-10 | 每次至少抽几个人气池角色 |
seed |
INT | 随机种子 |
use_top_priority |
BOOLEAN | 优先从人气池抽 |
exclude_characters / force_include |
STRING | 名单;强制包含允许写不在 CSV 里的名字 |
copyright_pick |
COMBO | 列出全部作品(默认约 3,460 个,按 count 总和降序、并列按名字升序),首项 (不限);可打字筛选、忽略大小写。下拉项来自默认 CSV:填了 custom_csv_path 时下拉不会跟着变,但筛选在该 CSV 上生效——其作品名请直接填写(原始写法 fate_(series) 亦可);选了个该 CSV 里没有的作品时,报错会列出该 CSV 的作品总数与热门示例 |
describe_character |
BOOLEAN | 每行末尾拼上角色描述;默认开启 |
custom_csv_path / tested_db_path |
STRING | 留空用 danbooru_character_001.csv / rollingcharacter.sqlite(与画师分开);作品筛选在 custom_csv_path 指向的 CSV 上生效 |
角色节点不做权重分配(故无 weight_* / sort_by_weight);输出不接受前缀包装(没有画师那样的 artists_prefix 开关);没有去重模式参数,改用固定规则的窗口。
重复回避:窗口存最近 10 条输出记录(每条 = 一次生成,可含多个角色),抽取前把窗口内角色名从候选排除,因此 character_count=3 相当于「最近 30 个角色内不重复」。窗口在节点实例内存里(重启清零、多实例各自独立——同一张图放两个角色节点,各避各的);外部脚本可用 node.reset_recent() 清空、node.recent_names() 查看。窗口吃掉全部候选时退回「允许重复」并打 WARNING。
输出三个:prompt、characters_json([{"name","copyright","top","count","core_tags"}])、tested_count。
注意:约 22% 的角色没有描述,这些行只有角色标签与作品标签,角色多时输出会明显变长。角色名唯一(34,416 行无重名)。记录带 kind=character 标记、权重列按 1.0 占位,与画师共用数据库也不会互相冒充。
性能:派生结构(名字列表 / 名字→记录 / 按作品筛选下标)随记录缓存,重复执行不重复解析 CSV(实测单次执行 62.6ms → 26.7ms);作品下拉按默认 CSV 文件签名缓存,替换数据后自动重建,无需重启 ComfyUI。
记录与去重(SQLite)
单个 SQLite 文件(WAL 模式)存已测记录;判重键写入时算好并建索引,判重是索引查询,历史再大也不占内存。「判重 → 生成 → 记录」整段在同一 BEGIN IMMEDIATE 事务:同进程靠可重入锁,多进程靠 SQLite 文件锁。艺术家名与权重以 JSON 存列,名字含 |、:、逗号都不破坏解析。
记录不自动裁剪;默认路径的数据库每次启动会被清理(prestartup_script.py 删 rollingartist.sqlite / rollingcharacter.sqlite 及 -wal / -shm)。想跨会话保留,把 tested_db_path 指到自己的文件——只清理默认路径。
数据安全与路径防护
custom_csv_path / tested_db_path 不再原样透传:命中受保护目标时直接拒绝并抛出 PathNotAllowedError。
- 拦截:系统关键目录(Windows 的
Windows/Program Files/ProgramData,Linux/macOS 的/etc/usr/bin/sbin/lib/boot/proc/sys/dev)、凭据目录(~/.ssh~/.aws~/.gnupg~/.kube~/.docker)、ComfyUI 自身代码(comfy、.git、custom_nodes,本节点目录下的数据文件除外)、本节点目录内的源码文件 - 数据库写入额外校验:目标已存在且不是 SQLite 文件时拒绝覆写
- 规范化:先剥离 Windows 扩展长度 / 设备前缀(
\\?\、\\.\)与备用数据流后缀(data.csv:evil按data.csv判定);本机管理共享(\\localhost\C$\Windows)还原为盘符路径再比较;指向其它主机的共享一律拒绝
已知限制:这是纵深防御,不是沙箱,不能替代访问控制——暴露到公网且无鉴权时仍可读取未列入名单的普通文件;不解决 TOCTOU;盘符黑名单只覆盖本机盘符。若 ComfyUI 对局域网或公网开放,请加访问控制。
逃生开关:单机自用可设 ROLLINGARTIST_ALLOW_ANY_PATH=1 完全关闭防护——仅在确认环境可信时使用。同一记录数据库不要被多个实例共用同一个网络盘(WAL 要求本地文件系统)。
使用示例
添加 RollingArtist 节点 → 连接文本编码器 → 调整参数。Prompt 输出形如:
artist:Jane_Smith,(artist:John_Doe:0.8),(artist:FooBar:0.2),
权重为 1.0 时直接输出 artist:名称(无括号与权重);末尾总是带逗号,方便拼接角色节点的多行输出。同时输出 artists_json(各项 name / weight / top,status 恒为 OK)与 tested_count。
调参与注意事项
- 权重以 0.1 为整数单位运算,因此任一权重严格落在
[weight_min, weight_max]内,总和精确等于weight_total artist_count用 3-5 更容易聚焦风格;想要「一个主风格 + 若干弱参考」把weight_curve设为dominantartist_top_ratio是相对比例:默认数据 50,018 人配 0.01 约等于 500 人 Top 池,数据更大时调小- 降级必有信号:可用艺术家不足、重试耗尽、记录写入失败均输出 WARNING / ERROR,不静默返回空提示词
- 缓存:ComfyUI 默认按输入参数缓存节点输出;两个节点已把被读取 CSV 的「路径 + 修改时间 + 大小」纳入缓存键(
IS_CHANGED),替换 CSV 后会正常重算;文件与参数都不变时行为与 5.0.0 一致,不会退化为每次执行都重算
数据预处理
两个脚本把 danbooru 导出裁成节点可读的精简 CSV,共用 danbooru_csv_tool.py 的筛选 / 转义 / 备份 / 原子写回:
| 脚本 | 默认输入(行数) | 默认输出(行数) | 输出列 |
|---|---|---|---|
modify_danbooru_art.py |
danbooru_art_full.csv(419,789) |
danbooru_art_001.csv(50,018) |
artist,trigger,count |
modify_danbooru_character.py |
danbooru_character.csv(244,932) |
danbooru_character_001.csv(34,416) |
character,copyright,trigger,core_tags,count |
python modify_danbooru_art.py # 默认:count >= 30,只转义 trigger 列
python modify_danbooru_art.py -n 100 # 换成 count >= 100
规则(两脚本一致):只保留 count ≥ 阈值(默认 30)并按 count 降序;只输出上表列;只转义 trigger 列(按逗号分段,段内空白 / 短横线折叠为下划线、丢弃空段、补转义括号),其余列原样;写回前备份为 <输出名>.bak,写回用临时文件 + 原子替换。
- 角色文件的
character列没有转义(只处理 trigger),带括号的仍有 16,380 条(如ganyu_(genshin_impact));这些括号进提示词会被权重解析器当分组语法吃掉,需精确匹配请自行加工该列 trigger段内连字符会变成下划线(itomugi-kun→itomugi_kun),想保留原始连字符改danbooru_csv_tool.transform_text一处
参与开发
给外部贡献者的重要说明
main分支受保护,且仅维护者可直接推送。 请向dev分支提交 Pull Request;直接向main提交的 PR 大概率不会受理。
推荐流程:
- Fork 本仓库到你自己账号
- 从
dev分支切出特性分支(git checkout -b feature/xxx) - 提交改动并推送到你的 Fork
- 向本仓库的
dev分支开 Pull Request,等待 CI 通过与合并
分支流向:feature/xxx → dev(日常集成)→ main(发布版本)
本地开发与自检:
pip install -e ".[dev]"
ruff check . && ruff format --check .
pytest -q
测试
pip install -e ".[dev]"
pytest
测试不需要 ComfyUI:ra_core/ 是纯逻辑,节点文件只 import 标准库与 ra_core,可直接实例化。覆盖纯逻辑、两个节点的接口与行为(含回避窗口)、加工脚本与跨进程去重。
规模口径(2026-10-25 实测):254 个测试函数,参数化展开共 338 个用例。CHANGELOG 中 5.0.0 记载的「316 项」为当时的展开用例口径;差额来自 5.1.0 新增的 tests/test_node_cache.py(13 项)与 tests/test_character_index.py(9 项)。
变更记录
见 CHANGELOG.md。
许可证
基于 MIT License 发布,Copyright (c) 2026 StarAsh042。可自由使用、修改、分发与商用,仅需保留版权声明与许可声明。