Refiner gen_context: route sampler settings through refinement state

Chain sampler accepts optional refinement_key_input (wire from the same
key as the V2 Refiner). On each run it calls update_refinement_sampler_context
to write carry_i2v_guides, frame_overlap, and scene count into the shared
state file. The V2 Refiner reads last_sampler_context from state when it
loads, making it available for the gen_context snapshot with no latent
manipulation and no extra Refiner inputs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-05-28 14:12:09 +03:00
co-authored by Claude Sonnet 4.6
parent 5bfaf68640
commit f6780f777b
2 changed files with 51 additions and 18 deletions
+31 -12
View File
@@ -347,6 +347,27 @@ def render_refinement_loss_graph(refinement_key, scheduler_mode, mode, total_ite
return _to_image_tensor(image.convert("RGB"))
def update_refinement_sampler_context(refinement_key, sampler_context):
"""Write sampler settings into the V2 refinement state so the Refiner can read them next run."""
if not refinement_key or not isinstance(sampler_context, dict):
return
path = refinement_state_path(refinement_key, "clip", prefix="refine_v2")
os.makedirs(os.path.dirname(path), exist_ok=True)
state = {}
if os.path.exists(path):
try:
with open(path, "r", encoding="utf-8") as f:
state = json.load(f)
except Exception:
pass
state["last_sampler_context"] = sampler_context
try:
with open(path, "w", encoding="utf-8") as f:
json.dump(state, f)
except Exception as e:
print(f"[FunPack] update_refinement_sampler_context: failed to save state: {e}")
def refinement_state_path(refinement_key, mode, prefix="refine", extension="json"):
base_dir = os.path.dirname(os.path.abspath(__file__))
refinements_dir = os.path.join(base_dir, "refinements")
@@ -5974,7 +5995,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
"tooltip": "Detect transition words and return one conditioning entry per scene for FunPack LTXAV Scene Chain Sampler. Leave off for normal single-conditioning workflows.",
}),
"latent": ("LATENT", {
"tooltip": "Optional video latent for creativity masking. Takes priority over any saved latent for this key. Connect your i2v or previous KSampler output here. When connected from FunPack LTXAV Scene Chain Sampler, carry_i2v_guides, frame_overlap, and transitions_enabled are read automatically for context tracking.",
"tooltip": "Optional video latent for creativity masking. Takes priority over any saved latent for this key. Connect your i2v or previous KSampler output here.",
}),
},
}
@@ -10628,12 +10649,12 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
memory["last_context"] = current
return current, f"{image_status} {clip_status}"
def _v2_gen_context_snapshot(self, lora_stack, latent, vision_context):
def _v2_gen_context_snapshot(self, lora_stack, sampler_context, vision_context):
"""Build a compact, serializable snapshot of the generation context for this run.
Sampler settings (carry_i2v_guides, frame_overlap, transitions_enabled) are read
from funpack_sampler_context embedded in the latent by FunPack LTXAV Scene Chain
Sampler. All three are None when the latent is not from the chain sampler.
sampler_context is read from state["last_sampler_context"], written there by
FunPack LTXAV Scene Chain Sampler when a refinement_key is connected.
All sampler fields are None when the sampler has no refinement_key wired.
"""
loras = []
if isinstance(lora_stack, dict):
@@ -10644,17 +10665,15 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
"type": str(entry.get("type", "general")),
"weight": round(float(entry.get("weight", 1.0)), 4),
})
sampler_ctx = {}
if isinstance(latent, dict):
sampler_ctx = latent.get("funpack_sampler_context") or {}
sc = sampler_context if isinstance(sampler_context, dict) else {}
image_fp = ""
if isinstance(vision_context, dict):
image_fp = str(vision_context.get("image", {}).get("fingerprint", "") or "")
return {
"loras": loras,
"carry_i2v_guides": sampler_ctx.get("carry_i2v_guides"),
"frame_overlap": sampler_ctx.get("frame_overlap"),
"transitions_enabled": sampler_ctx.get("transitions_enabled"),
"carry_i2v_guides": sc.get("carry_i2v_guides"),
"frame_overlap": sc.get("frame_overlap"),
"transitions_enabled": sc.get("transitions_enabled"),
"image_fp": image_fp,
}
@@ -11183,7 +11202,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
clip_vision_output=clip_vision_output,
source_image=source_image,
)
current_gen_context = self._v2_gen_context_snapshot(lora_stack, latent, vision_context)
current_gen_context = self._v2_gen_context_snapshot(lora_stack, state.get("last_sampler_context"), vision_context)
previous_gen_context = previous_run.get("gen_context") if isinstance(previous_run, dict) else None
context_insight = self._v2_gen_context_insight(previous_gen_context, current_gen_context, rating_label, has_previous_run)
analysis_prompt = self._v2_prompt_key(positive_prompt)
+20 -6
View File
@@ -804,6 +804,12 @@ class FunPackLTXAVSceneChainSampler:
"default": 0, "min": 0, "max": 4096, "step": 64,
"tooltip": "Tile size for VAE decode (0 = no tiling). Set to e.g. 512 if decode OOMs.",
}),
"refinement_key_input": ("STRING", {
"default": "",
"multiline": False,
"forceInput": True,
"tooltip": "Connect to the same refinement key as your V2 Refiner. When wired, the sampler writes carry_i2v_guides, frame_overlap, and scene count into the refinement state so the Refiner can reason about what changed between rated runs.",
}),
},
"hidden": {
"unique_id": "UNIQUE_ID",
@@ -1268,7 +1274,7 @@ class FunPackLTXAVSceneChainSampler:
def sample(self, model, vae, positive, negative, sampler, sigmas, seed, latent_template,
num_frames_per_scene, frame_overlap, cfg, max_scenes, use_same_seed=False,
carry_i2v_guides=False, transition_duration=16, decode_tile_size=0,
unique_id=None, prompt=None):
refinement_key_input="", unique_id=None, prompt=None):
if not isinstance(positive, list) or not positive:
raise ValueError("positive conditioning must contain at least one scene entry.")
if negative is None:
@@ -1366,9 +1372,17 @@ class FunPackLTXAVSceneChainSampler:
if carry_i2v_guides and carried_guide_frames > 0:
status += f", i2v guide tokens={carried_guide_frames} latent frame(s)"
boundaries_out = [{"pixel_frame": e["pixel_frame"], "effect": e["effect"]} for e in boundary_entries]
output["funpack_sampler_context"] = {
"carry_i2v_guides": bool(carry_i2v_guides),
"frame_overlap": int(frame_overlap),
"transitions_enabled": scene_count > 1,
}
if refinement_key_input:
try:
try:
from .conditioning import update_refinement_sampler_context
except ImportError:
from conditioning import update_refinement_sampler_context
update_refinement_sampler_context(refinement_key_input, {
"carry_i2v_guides": bool(carry_i2v_guides),
"frame_overlap": int(frame_overlap),
"transitions_enabled": scene_count > 1,
})
except Exception as e:
print(f"[FunPackLTXAVSceneChainSampler] Failed to write sampler context: {e}")
return (output, images, status, scene_count, "\n".join(report_lines), _json.dumps(boundaries_out))