Spaces:
Running
Running
Constrain podcast script length via model prompt and rewrite pass
Browse files- services/podcast_service.py +35 -0
services/podcast_service.py
CHANGED
|
@@ -18,6 +18,7 @@ logger = logging.getLogger(__name__)
|
|
| 18 |
MODEL = "claude-haiku-4-5-20251001"
|
| 19 |
MAX_TOKENS = 2048
|
| 20 |
MAX_TTS_INPUT_CHARS = 4096
|
|
|
|
| 21 |
|
| 22 |
# Use facebook/mms-tts-eng for both — it's free and reliable
|
| 23 |
# We differentiate voices by slightly modifying the text (different speaking rates aren't
|
|
@@ -47,6 +48,8 @@ FORMAT RULES (strictly follow these):
|
|
| 47 |
- No stage directions, no asterisks, no markdown, no headers
|
| 48 |
- Each line is one continuous paragraph — no line breaks within a turn
|
| 49 |
- End with a closing exchange where both hosts wrap up
|
|
|
|
|
|
|
| 50 |
|
| 51 |
Example format:
|
| 52 |
Alex: Hey everyone, welcome back! Today we're diving into something really fascinating. Sam, want to kick us off?
|
|
@@ -55,6 +58,10 @@ Sam: Absolutely! So the big idea here is...
|
|
| 55 |
Now write the podcast script:"""
|
| 56 |
|
| 57 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
def _parse_script_lines(raw: str) -> list[tuple[str, str]]:
|
| 59 |
lines = []
|
| 60 |
for line in raw.strip().splitlines():
|
|
@@ -193,6 +200,34 @@ def generate_podcast(notebook: Notebook, summary_content: str) -> Artifact:
|
|
| 193 |
raw_script = response.content[0].text or ""
|
| 194 |
lines = _parse_script_lines(raw_script)
|
| 195 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
if not lines:
|
| 197 |
raise ValueError("No speaker lines parsed from response.")
|
| 198 |
|
|
|
|
| 18 |
MODEL = "claude-haiku-4-5-20251001"
|
| 19 |
MAX_TOKENS = 2048
|
| 20 |
MAX_TTS_INPUT_CHARS = 4096
|
| 21 |
+
TARGET_SCRIPT_CHARS = 3900
|
| 22 |
|
| 23 |
# Use facebook/mms-tts-eng for both — it's free and reliable
|
| 24 |
# We differentiate voices by slightly modifying the text (different speaking rates aren't
|
|
|
|
| 48 |
- No stage directions, no asterisks, no markdown, no headers
|
| 49 |
- Each line is one continuous paragraph — no line breaks within a turn
|
| 50 |
- End with a closing exchange where both hosts wrap up
|
| 51 |
+
- Keep the ENTIRE script under {TARGET_SCRIPT_CHARS} characters total (including speaker labels)
|
| 52 |
+
- If needed, prioritize clarity and completeness over extra detail so the script ends naturally
|
| 53 |
|
| 54 |
Example format:
|
| 55 |
Alex: Hey everyone, welcome back! Today we're diving into something really fascinating. Sam, want to kick us off?
|
|
|
|
| 58 |
Now write the podcast script:"""
|
| 59 |
|
| 60 |
|
| 61 |
+
def _script_char_len(lines: list[tuple[str, str]]) -> int:
|
| 62 |
+
return len("\n".join([f"{speaker}: {text}" for speaker, text in lines]))
|
| 63 |
+
|
| 64 |
+
|
| 65 |
def _parse_script_lines(raw: str) -> list[tuple[str, str]]:
|
| 66 |
lines = []
|
| 67 |
for line in raw.strip().splitlines():
|
|
|
|
| 200 |
raw_script = response.content[0].text or ""
|
| 201 |
lines = _parse_script_lines(raw_script)
|
| 202 |
|
| 203 |
+
if lines and _script_char_len(lines) > TARGET_SCRIPT_CHARS:
|
| 204 |
+
logger.warning(
|
| 205 |
+
"Podcast script exceeded target size (%d chars), requesting concise rewrite",
|
| 206 |
+
_script_char_len(lines),
|
| 207 |
+
)
|
| 208 |
+
rewrite_prompt = (
|
| 209 |
+
f"Rewrite this podcast script so it is <= {TARGET_SCRIPT_CHARS} characters total, "
|
| 210 |
+
"keeps the same key points, and ends with a natural closing exchange.\n\n"
|
| 211 |
+
"RULES:\n"
|
| 212 |
+
"- Keep the same Alex/Sam format\n"
|
| 213 |
+
"- Every line must start with Alex: or Sam:\n"
|
| 214 |
+
"- No markdown or stage directions\n\n"
|
| 215 |
+
f"SCRIPT TO REWRITE:\n{raw_script}"
|
| 216 |
+
)
|
| 217 |
+
rewrite_response = client.messages.create(
|
| 218 |
+
model=MODEL,
|
| 219 |
+
max_tokens=MAX_TOKENS,
|
| 220 |
+
system=(
|
| 221 |
+
"You are a podcast editor. Compress scripts while preserving meaning and flow. "
|
| 222 |
+
"Output only Alex/Sam dialogue lines."
|
| 223 |
+
),
|
| 224 |
+
messages=[{"role": "user", "content": rewrite_prompt}],
|
| 225 |
+
)
|
| 226 |
+
rewritten = rewrite_response.content[0].text or ""
|
| 227 |
+
rewritten_lines = _parse_script_lines(rewritten)
|
| 228 |
+
if rewritten_lines:
|
| 229 |
+
lines = rewritten_lines
|
| 230 |
+
|
| 231 |
if not lines:
|
| 232 |
raise ValueError("No speaker lines parsed from response.")
|
| 233 |
|