idacosta commited on
Commit
c72ee4a
·
1 Parent(s): c9e812c

Constrain podcast script length via model prompt and rewrite pass

Browse files
Files changed (1) hide show
  1. 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