feat(short): el ejemplo del prompt habla, y por eso los specs vuelven a hablar
Build & Deploy ResearchOwl / build-and-push (push) Successful in 10s

La primera generación con narración viva narró 5 de 7 planos. La siguiente, ya
con la sección de presupuestos de ancho en el prompt, narró 1 de 8.

La causa no era la sección 3b sino la 5: el ejemplo trabajado — un spec completo
de 8 planos presentado como "el que produjo un buen vídeo" — no llevaba ni una
narración, porque es anterior a la voz. Contra esa señal de formato, una regla
en prosa que además dice "no todos los planos necesitan una" pierde siempre.

Ahora el ejemplo narra 6 de sus 8 y calla en los dos que dibujan una cita, donde
la voz sólo competiría con palabras que ya están en pantalla. Sus duraciones
suben para pagar lo que habla: un plano que se queda corto para su propia voz
enseñaría a infradeclarar, y el render no corta la voz, alarga el plano. La
ventana de silencio se recoloca porque seguía a document_quote por tiempo.

El aviso de ancho ya no cita el hueco de 16 caracteres de quote_a: shortsmith
envuelve esas citas en dos líneas y el presupuesto vivo es 31.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
ChemaVX
2026-08-06 21:32:49 +00:00
co-authored by Claude Fable 5
parent 129f436fd2
commit cfe4a6d754
3 changed files with 150 additions and 34 deletions
+32
View File
@@ -113,6 +113,38 @@ def test_prompt_includes_the_worked_example_in_full():
assert "counter_close" in prompt
def test_the_worked_example_narrates_most_of_its_shots():
"""El ejemplo es la señal de formato más fuerte del prompt, más que cualquier
regla en prosa. Sin narración en él, el modelo escribía specs mudos aunque la
sección 3b le dijera lo contrario: en la primera generación tras añadir la voz
narró 1 de 8 planos. Si alguien vuelve a dejar el ejemplo mudo, esto salta.
"""
example = json.loads(EXAMPLE.read_text())
shots = example["shots"]
spoken = [s for s in shots if s.get("narration")]
assert len(spoken) >= len(shots) * 0.6, "el ejemplo enseña a no narrar"
# Y el silencio del ejemplo es una decisión, no un olvido: calla justo donde
# la plantilla ya dibuja una cita.
silent = {s["template"] for s in shots if not s.get("narration")}
assert silent == {"scale_bars", "document_quote"}
def test_the_worked_example_declares_time_for_its_own_narration():
"""Un plano que se queda corto para su propia voz enseña a infradeclarar: el
render no corta la voz, alarga el plano, y el total se va del objetivo."""
from src.generator.spec_contract import NARRATION_CHARS_PER_SECOND
example = json.loads(EXAMPLE.read_text())
for i, shot in enumerate(example["shots"]):
narration = shot.get("narration", "")
if not narration:
continue
needs = len(narration) / NARRATION_CHARS_PER_SECOND
assert shot["duration"] >= needs, f"shot {i} declara menos de lo que habla"
def test_prompt_says_out_loud_that_there_is_no_article_yet():
w, _ = writer("{}")
assert "No article URL yet" in w.build_prompt("X", "m", None, "X.TEST")