Defaults an administrator can actually set
There were none. `workflow.DEFAULTS` was the only source, so 512x512, euler and twenty steps were what every instance got whatever card it was running on -- and 512 square on an SDXL checkpoint is precisely what the tool's own description warns produces duplicated limbs. The two ways round it were both bad: bake literals into a template where the placeholders should be, or write prose in the instructions box and hope. Three rungs now, most specific winning, with DEFAULTS staying underneath as the floor so an instance that sets nothing behaves exactly as it did and a floor improved in code still reaches everybody. An empty box is "no opinion" rather than zero, which matters: read as a number it would set every instance to zero steps, and ComfyUI refuses that in a way that looks like a broken model. The right control for each, because a text box is wrong for most of them. The samplers and schedulers were already being discovered by the Test button, stored, and read by nothing at all -- they are the pickers now. A stored value missing from the list is kept as an option anyway, or opening this page and pressing Save would silently clear a working setting. Checkpoints are chosen rather than typed, and the instance default is a rung of its own instead of "whatever happens to be first in a textarea somebody filled in some order". And batch, at last: `batch_size` was a literal 1 in the base template, so an administrator whose card can comfortably make four had no way of saying so. Deliberately not something a model may set -- one asking for six because it is unsure is the exact cost this must not invite. The tool's schema restates the defaults it quotes. Every "Default 20." in there was written when there was one set of defaults in the world; left alone, an instance drawing at 1024 would go on telling the model 512, and the model reasons from that sentence rather than ignoring it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
@@ -196,6 +197,43 @@ def _choices(db, values: dict[str, Any]) -> tuple[list[Any], list[str]]:
|
||||
return rows, [str(name) for name in (values.get("checkpoints") or [])]
|
||||
|
||||
|
||||
_DEFAULT_SENTENCE = re.compile(r"Default ([^.,]+)([.,])")
|
||||
|
||||
|
||||
def _restate_defaults(schema: dict[str, Any], values: dict[str, Any]) -> None:
|
||||
"""Rewrite each "Default 20." to say what this instance actually uses.
|
||||
|
||||
Every one of those descriptions was written when there was one set of
|
||||
defaults in the world. Now an administrator can move them, and a schema
|
||||
still saying "Default 512" beside an instance that draws at 1024 is worse
|
||||
than saying nothing: the model reasons from it, decides 512 is fine for the
|
||||
SDXL checkpoint it was handed, and omits the parameter — arriving at the
|
||||
right behaviour for the wrong reason, or the wrong one silently.
|
||||
|
||||
A rewrite rather than a `{default}` placeholder in the prose, because the
|
||||
sentence around it differs per parameter and half of them go on to say what
|
||||
to do *instead* of the default. The regex keeps the punctuation it found,
|
||||
since `denoise` says "Default 1, which is…" and the rest use a full stop.
|
||||
"""
|
||||
resolved = workflow.resolve({}, settings=values)
|
||||
for name, spec in schema.get("properties", {}).items():
|
||||
if name not in resolved or name in ("prompt", "seed", "model", "template"):
|
||||
continue
|
||||
shown = resolved[name]
|
||||
# A float that is whole reads better as "8" than "8.0", and this is the
|
||||
# text a model reasons about.
|
||||
if isinstance(shown, float) and shown.is_integer():
|
||||
shown = int(shown)
|
||||
spec["description"] = _DEFAULT_SENTENCE.sub(
|
||||
# Bound now rather than closed over: `shown` is a loop variable, and
|
||||
# a lambda reading it later would restate every description with the
|
||||
# last parameter's value.
|
||||
lambda match, shown=shown: f"Default {shown}{match.group(2)}",
|
||||
spec["description"],
|
||||
count=1,
|
||||
)
|
||||
|
||||
|
||||
def schema_for(db, values: dict[str, Any]) -> dict[str, Any]:
|
||||
"""The parameter schema, with this instance's own choices in it.
|
||||
|
||||
@@ -207,6 +245,7 @@ def schema_for(db, values: dict[str, Any]) -> dict[str, Any]:
|
||||
"""
|
||||
rows, checkpoints = _choices(db, values)
|
||||
schema = json.loads(json.dumps(SCHEMA))
|
||||
_restate_defaults(schema, values)
|
||||
if rows:
|
||||
schema["properties"]["template"]["enum"] = [row.slug for row in rows]
|
||||
schema["properties"]["template"]["description"] = "Which workflow to use. " + "; ".join(
|
||||
@@ -421,7 +460,12 @@ async def run(context: ToolContext, args: dict[str, Any]) -> ToolOutcome:
|
||||
{**event, "status": "error", "error": str(exc)},
|
||||
)
|
||||
|
||||
checkpoint = _checkpoint(str(args.get("model") or ""), context.image_checkpoint, checkpoints)
|
||||
checkpoint = _checkpoint(
|
||||
str(args.get("model") or ""),
|
||||
context.image_checkpoint,
|
||||
checkpoints,
|
||||
instance_default=str(values.get("default_checkpoint") or ""),
|
||||
)
|
||||
if checkpoint is None:
|
||||
return ToolOutcome(
|
||||
"No checkpoint is available. An administrator has to list them on the "
|
||||
@@ -429,7 +473,7 @@ async def run(context: ToolContext, args: dict[str, Any]) -> ToolOutcome:
|
||||
{**event, "status": "error", "error": "No checkpoint."},
|
||||
)
|
||||
|
||||
given = {name: args.get(name) for name in workflow.PLACEHOLDERS if name in args}
|
||||
given = {name: args.get(name) for name in workflow.MODEL_SETTABLE if name in args}
|
||||
given["model"] = checkpoint
|
||||
given["prompt"] = prompt
|
||||
|
||||
@@ -441,14 +485,16 @@ async def run(context: ToolContext, args: dict[str, Any]) -> ToolOutcome:
|
||||
kept: tuple[bytes, dict[str, Any]] | None = None
|
||||
# What the last attempt actually asked for, so a failure can name concrete
|
||||
# numbers back at the model rather than saying "try something smaller".
|
||||
params_used: dict[str, Any] = workflow.resolve(given)
|
||||
params_used: dict[str, Any] = workflow.resolve(given, settings=values)
|
||||
|
||||
try:
|
||||
for number in range(1, tries + 1):
|
||||
if preserve:
|
||||
await _unload_llm(context)
|
||||
|
||||
params = workflow.resolve({**given, "seed": args.get("seed") if number == 1 else None})
|
||||
params = workflow.resolve(
|
||||
{**given, "seed": args.get("seed") if number == 1 else None}, settings=values
|
||||
)
|
||||
params_used = params
|
||||
refs = await comfy.await_images(
|
||||
config, await comfy.submit(config, workflow.fill(template, params))
|
||||
@@ -571,17 +617,26 @@ def _pick(rows: list[Any], wanted: str, chat_default: str, values: dict[str, Any
|
||||
return rows[0] if rows else None
|
||||
|
||||
|
||||
def _checkpoint(wanted: str, chat_default: str, available: list[str]) -> str | None:
|
||||
def _checkpoint(
|
||||
wanted: str, chat_default: str, available: list[str], *, instance_default: str = ""
|
||||
) -> str | None:
|
||||
"""The checkpoint to draw with, on the same ladder.
|
||||
|
||||
A name the instance does not have is ignored rather than passed through: it
|
||||
would reach ComfyUI, be refused, and cost a round to discover -- and the
|
||||
model was shown the list it may choose from.
|
||||
Most specific first: what the model named, then this chat's own, then the
|
||||
instance default, then whatever is first in the list. The instance rung is
|
||||
the new one -- without it, "the default" was position zero in a textarea an
|
||||
administrator had typed in some order, which is a default by accident.
|
||||
|
||||
A name the instance does not have is ignored at every rung rather than
|
||||
passed through: it would reach ComfyUI, be refused, and cost a round to
|
||||
discover -- and the model was shown the list it may choose from.
|
||||
"""
|
||||
if wanted and wanted in available:
|
||||
return wanted
|
||||
if chat_default and chat_default in available:
|
||||
return chat_default
|
||||
if instance_default and instance_default in available:
|
||||
return instance_default
|
||||
return available[0] if available else None
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user