StarAsh042 f2a83f3da5 feat(character): T-16 作品筛选执行期一致性保障
* 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)
2026-10-25 21:49:47 +08:00
2026-10-12 09:12:00 +08:00

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.md
  • max_attempts(1-100,默认 10):重试上限;仍全命中时接受最后一次并打 WARNING
  • custom_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 设为 dominant
  • artist_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 大概率不会受理。

推荐流程:

  1. Fork 本仓库到你自己账号
  2. 从 dev 分支切出特性分支(git checkout -b feature/xxx)
  3. 提交改动并推送到你的 Fork
  4. 向本仓库的 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。可自由使用、修改、分发与商用,仅需保留版权声明与许可声明。

S
Description
No description provided
Readme MIT
18 MiB
Languages
Python 100%