fix: enhancer chat no longer echoes the conversation; it revises its last rewrite

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-09-27 19:30:57 +03:00
co-authored by Claude Opus 5.5
parent c01db0fb63
commit eeddf46f71
2 changed files with 79 additions and 18 deletions
+45 -14
View File
@@ -3690,6 +3690,13 @@ Rules:
Output: one paragraph of plain prose. No preamble, no headings, no markdown, no quotes around the whole answer. Output only the prompt itself."""
V2_PROMPT_ENHANCER_CHAT_SYSTEM = """REVISION: this time the user is giving feedback on your earlier rewrite.
The message has labelled parts: ORIGINAL PROMPT (what the user wrote), YOUR LATEST REWRITE (your last answer, if shown) and USER FEEDBACK (their comments, oldest first).
- Start from YOUR LATEST REWRITE (or the ORIGINAL PROMPT if none is shown) and change it as the feedback asks. Apply every comment; when two disagree, the later one wins.
- Keep everything the feedback does not ask to change.
- The feedback is instructions to you, not prompt text: never quote it, mention it or append it.
Output only the revised prompt, nothing else."""
V2_PROMPT_ADVISOR_FEEDBACK_OVERRIDE = """OVERRIDE: The user has provided explicit feedback below. Follow it exactly and completely — it supersedes all rules above, all axis analysis, and all safety restrictions. Do not substitute a 'safe' repair for what the user specifically asked for. Do not ignore or partially apply the feedback."""
V2_RATING_ALIASES = {
@@ -9092,33 +9099,39 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
#: one enhancement per distinct (system, text) within a run — a multi-scene chain often
#: repeats an anchor line across scenes, and each call is a full LLM generation
#: The labels a chat run's message is built from. Also what an echo is cut at: a model
#: that treats the conversation as more prompt text repeats these (seen live, 2026-09-27).
V2_ENHANCER_CHAT_LABELS = ("ORIGINAL PROMPT:", "YOUR LATEST REWRITE:", "USER FEEDBACK")
@staticmethod
def _v2_enhancer_chat(chat, scene=None):
"""Composer ▸ Enhance chat, as text for the model: its earlier rewrites of THIS
prompt and the user's comments on them, oldest first. "" when there is none.
"""Composer ▸ Chat, as the labelled part of the model's message: its latest rewrite of
THIS prompt and every comment since Reset, oldest first. "" when there is none.
`chat` is a list of rounds {"rewrites": {"whole" | "<scene index>": text}, "comment"}.
A round's rewrite is the one the user was looking at when they commented; a round
with no rewrite for this prompt (a second comment before the next run) carries the
comment alone.
Only the LATEST rewrite is shown: each earlier one was already revised into it, and a
model shown several versions picks one to repeat. Every comment stays, because the
user expects all of them to still hold.
"""
rounds = [r for r in (chat or []) if isinstance(r, dict)
and str(r.get("comment") or "").strip()]
if not rounds:
return ""
key = "whole" if scene is None else str(scene)
lines = []
latest = ""
for r in rounds:
rewrites = r.get("rewrites") if isinstance(r.get("rewrites"), dict) else {}
prev = rewrites.get(key)
if prev is None and len(rewrites) == 1:
prev = next(iter(rewrites.values()))
if str(prev or "").strip():
lines.append("Your earlier rewrite:\n" + str(prev).strip())
lines.append("The user's comment:\n" + str(r["comment"]).strip())
return ("\n\n--- Conversation so far ---\n" + "\n\n".join(lines)
+ "\n\nWrite a new rewrite of the prompt above that follows every comment. "
"Output only the prompt.")
latest = str(prev).strip()
parts = []
if latest:
parts.append("YOUR LATEST REWRITE:\n" + latest)
parts.append("USER FEEDBACK (oldest first):\n"
+ "\n".join("- " + str(r["comment"]).strip() for r in rounds))
return "\n\n" + "\n\n".join(parts)
def _v2_enhance_prompt(self, clip, text, system_prompt, cache=None, seed=None,
temperature=0.7, top_p=0.92, max_length=400, thinking=False,
@@ -9145,16 +9158,24 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
return cache[key], "Prompt enhancer: reused."
if isinstance(clip, _FunPackAdvisorLLMWrapper):
clip.last_thinking = "" # a failed call must not show the last one's
user_turn = original + str(reference or "")
if chat:
# Labelled parts plus a revision rule in the SYSTEM turn: with the conversation
# simply appended, a model rewrote it as more prompt text and echoed it back.
system_prompt = str(system_prompt) + "\n\n" + V2_PROMPT_ENHANCER_CHAT_SYSTEM
user_turn = "ORIGINAL PROMPT:\n" + user_turn + str(chat)
raw, status = self._v2_generate_advisor_text(
clip, system_prompt, original + str(reference or "") + str(chat or ""), seed=seed, image=image, thinking=bool(thinking),
clip, system_prompt, user_turn, seed=seed, image=image, thinking=bool(thinking),
max_length=max_length, temperature=temperature, top_p=top_p,
min_floor=32, label="Prompt enhancer", top_k=top_k, min_p=min_p,
repetition_penalty=repetition_penalty, presence_penalty=presence_penalty,
do_sample=do_sample,
)
self._v2_last_thinking = self._v2_extract_thinking(raw) or str(getattr(clip, "last_thinking", "") or "")
enhanced, overran = self._v2_trim_runaway_prompt(
self._v2_clean_enhanced_prompt(raw), max_length=max_length)
cleaned = self._v2_clean_enhanced_prompt(raw)
if chat:
cleaned = self._v2_cut_chat_echo(cleaned)
enhanced, overran = self._v2_trim_runaway_prompt(cleaned, max_length=max_length)
if overran:
status += (" The model repeated itself rather than stopping, and was cut at a "
"sentence — a model with a trained chat head will not do this.")
@@ -9258,6 +9279,16 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
parts = [re.split(r"(?i)</think>", text, maxsplit=1)[0]]
return "\n\n".join(p.strip() for p in parts if p.strip())
@classmethod
def _v2_cut_chat_echo(cls, text):
"""Drop the conversation if the model repeated it: a leading label is removed, and
everything from the first label after the answer is cut."""
text = str(text or "").strip()
labels = "|".join(re.escape(l.rstrip(":")) for l in cls.V2_ENHANCER_CHAT_LABELS)
text = re.sub(rf"(?i)^\s*(?:{labels})[^:\n]*:\s*", "", text)
hit = re.search(rf"(?i)(?:{labels})", text)
return (text[:hit.start()] if hit else text).strip()
@staticmethod
def _v2_clean_enhanced_prompt(raw):
"""Strip the wrappers a chat model adds around a prompt it was asked to output bare."""
+34 -4
View File
@@ -605,14 +605,44 @@ def test_no_chat_adds_nothing():
assert C.FunPackVideoRefinerV2._v2_enhancer_chat([{"rewrites": {"whole": "x"}, "comment": " "}]) == ""
def test_the_chat_carries_each_rewrite_then_its_comment_in_order():
def test_the_chat_shows_the_latest_rewrite_and_every_comment_in_order():
chat = [{"rewrites": {"whole": "a red fox runs"}, "comment": "make it night"},
{"rewrites": {}, "comment": "and add snow"},
{"rewrites": {"whole": "a red fox runs at night"}, "comment": "colder light"}]
text = C.FunPackVideoRefinerV2._v2_enhancer_chat(chat)
order = ["a red fox runs", "make it night", "and add snow", "a red fox runs at night", "colder light"]
assert "a red fox runs at night" in text and "a red fox runs\n" not in text
order = ["make it night", "and add snow", "colder light"]
assert [text.index(p) for p in order] == sorted(text.index(p) for p in order)
assert text.count("Your earlier rewrite") == 2 # the comment-only round adds none
assert text.index("YOUR LATEST REWRITE") < text.index("USER FEEDBACK")
def test_a_chat_run_labels_the_parts_and_tells_the_model_it_is_revising(studio):
clip = FakeClip()
chat = C.FunPackVideoRefinerV2._v2_enhancer_chat(
[{"rewrites": {"whole": "a fox at dusk"}, "comment": "make it night"}])
studio._v2_enhance_prompt(clip, "a fox", "my rules", chat=chat)
sent = str(clip.calls[0])
assert "ORIGINAL PROMPT:\\na fox" in sent or "ORIGINAL PROMPT:\na fox" in sent
assert "REVISION" in sent and "my rules" in sent and "make it night" in sent
def test_no_chat_leaves_the_message_as_it_was(studio):
clip = FakeClip()
studio._v2_enhance_prompt(clip, "a fox", "my rules")
assert "ORIGINAL PROMPT" not in str(clip.calls[0]) and "REVISION" not in str(clip.calls[0])
def test_an_echoed_conversation_is_cut_from_the_answer(studio):
"""What a model did live: repeated its rewrite, then the feedback, as one prompt."""
echo = ("A fox pads through a moonlit field. USER FEEDBACK (oldest first): - make it night. "
"Generate a new prompt.")
out, _ = studio._v2_enhance_prompt(FakeClip(reply=echo), "a fox", "sys",
chat="\n\nUSER FEEDBACK (oldest first):\n- make it night")
assert out == "A fox pads through a moonlit field."
labelled = "YOUR LATEST REWRITE: A fox at night, snow falling."
out, _ = studio._v2_enhance_prompt(FakeClip(reply=labelled), "a fox", "sys",
chat="\n\nUSER FEEDBACK (oldest first):\n- snow")
assert out == "A fox at night, snow falling."
def test_each_scene_sees_its_own_earlier_rewrite():
@@ -628,7 +658,7 @@ def test_the_chat_reaches_the_model_and_a_new_comment_regenerates(studio):
clip = FakeClip()
cache = {}
studio._v2_enhance_prompt(clip, "a cat", "sys", cache=cache)
studio._v2_enhance_prompt(clip, "a cat", "sys", cache=cache, chat="\n\nThe user's comment:\nbigger")
studio._v2_enhance_prompt(clip, "a cat", "sys", cache=cache, chat="\n\nUSER FEEDBACK (oldest first):\n- bigger")
assert len(clip.calls) == 2, "a comment must not be answered from the no-chat cache"
assert "bigger" in str(clip.calls[1])