vajeeda's picture
feat(phase4): critic escalation engine, difficulty tracker, env wiring, gate PASS
ebae6ab
Raw
History Blame Contribute Delete
4.56 kB
import json
from typing import List
from pydantic import BaseModel
from viral_script_engine.agents.llm_backend import LLMBackend
SYSTEM_PROMPT = """You are an expert social media content critic specialising in short-form video scripts for Reels and YouTube Shorts. Your job is to find specific, real problems in creator scripts — not vague feedback.
RULES:
1. Every claim must cite a specific part of the script (quote it or reference the timestamp range)
2. Every claim must be falsifiable — a human editor must be able to verify it by re-reading the script
3. Never say "the hook is weak" — say "the hook at 0:00-0:03 promises [X] but the script delivers [Y] at 0:22, by which time most viewers have already dropped off"
4. Focus on the 6 critique classes: hook_weakness, pacing_issue, cultural_mismatch, cta_buried, coherence_break, retention_risk
5. Produce between 3 and 6 claims per script. No more, no less.
6. For each claim, assign a timestamp range if the issue is locatable in the script. Use "N/A" only if it's a structural issue spanning the whole script.
OUTPUT FORMAT (respond ONLY with valid JSON, no markdown, no preamble):
{
"claims": [
{
"claim_id": "C1",
"critique_class": "hook_weakness",
"claim_text": "...",
"timestamp_range": "0:00-0:03",
"evidence": "exact quote from script",
"is_falsifiable": true,
"severity": "high"
}
],
"overall_severity": "high"
}"""
USER_PROMPT_TEMPLATE = """SCRIPT TO CRITIQUE:
{script}
TARGET REGION: {region}
PLATFORM: {platform}
NICHE: {niche}
Produce your critique now."""
STRICT_RETRY_SUFFIX = (
"\n\nIMPORTANT: Your previous response was not valid JSON. "
"Respond ONLY with the raw JSON object. No markdown fences, no explanation, no preamble."
)
class CriticParseError(Exception):
pass
class CritiqueClaim(BaseModel):
claim_id: str
critique_class: str
claim_text: str
timestamp_range: str
evidence: str
is_falsifiable: bool
severity: str
class CritiqueOutput(BaseModel):
claims: List[CritiqueClaim]
overall_severity: str
raw_response: str
class CriticAgent:
def __init__(self, backend: str = "anthropic", model_name: str = "claude-haiku-4-5-20251001"):
self.llm = LLMBackend(backend=backend, model_name=model_name)
@staticmethod
def _extract_json(text: str) -> dict:
import re
text = text.strip()
text = re.sub(r"^```(?:json)?", "", text).strip()
text = re.sub(r"```$", "", text).strip()
try:
return json.loads(text)
except json.JSONDecodeError:
pass
start = text.find("{")
if start != -1:
depth, in_str, esc = 0, False, False
for i, c in enumerate(text[start:], start):
if esc:
esc = False
continue
if c == "\\" and in_str:
esc = True
continue
if c == '"':
in_str = not in_str
elif not in_str:
if c == "{":
depth += 1
elif c == "}":
depth -= 1
if depth == 0:
try:
return json.loads(text[start : i + 1])
except json.JSONDecodeError:
break
raise ValueError(f"No valid JSON found in response: {text[:200]}")
def _parse_response(self, raw: str, user_prompt: str) -> CritiqueOutput:
try:
data = self._extract_json(raw)
data["raw_response"] = raw
return CritiqueOutput(**data)
except Exception:
strict_prompt = user_prompt + STRICT_RETRY_SUFFIX
raw2 = self.llm.generate(SYSTEM_PROMPT, strict_prompt, max_tokens=2048)
try:
data = self._extract_json(raw2)
data["raw_response"] = raw2
return CritiqueOutput(**data)
except Exception as e:
raise CriticParseError(f"Failed to parse critique after 2 attempts: {e}")
def critique(self, script: str, region: str, platform: str, niche: str) -> CritiqueOutput:
user_prompt = USER_PROMPT_TEMPLATE.format(
script=script, region=region, platform=platform, niche=niche
)
raw = self.llm.generate(SYSTEM_PROMPT, user_prompt, max_tokens=2048)
return self._parse_response(raw, user_prompt)