Files
ai-podcast/scripts/generate_sample_calls.py

154 lines
5.3 KiB
Python

"""Generate 10 sample caller dialogues for user validation before cutover.
5 with Silas batch (if lore exists), 5 walk-ins. Writes transcripts to docs/samples/.
"""
import asyncio
import random
from datetime import datetime
from pathlib import Path
import httpx
from backend.config import settings
from backend.main import (
BLACKLISTED_VOICES,
INWORLD_FEMALE_VOICES,
INWORLD_MALE_VOICES,
get_caller_prompt,
)
from backend.services import caller_gen, regulars_v2
DIALOG_MODEL = "anthropic/claude-haiku-4.5"
HOST_REACTIONS = [
"Yeah?",
"Go on.",
"Mm-hmm.",
"Wait — really?",
"Hold on, back up.",
"Is that right?",
"How'd you end up there?",
"And then what?",
"What'd she say?",
"So what are you gonna do about it?",
"That's — okay, keep going.",
"I mean, what do you want me to tell you?",
]
async def dialog_turn(client: httpx.AsyncClient, system_prompt: str, conversation: list) -> str:
last_err = None
for attempt in range(3):
try:
resp = await client.post(
"https://openrouter.ai/api/v1/chat/completions",
headers={"Authorization": f"Bearer {settings.openrouter_api_key}"},
json={
"model": DIALOG_MODEL,
"messages": [{"role": "system", "content": system_prompt}] + conversation,
"max_tokens": 300,
"temperature": 0.9,
},
)
resp.raise_for_status()
return resp.json()["choices"][0]["message"]["content"].strip()
except (httpx.ReadTimeout, httpx.HTTPStatusError) as e:
last_err = e
print(f" [retry {attempt+1}/3] dialog_turn failed: {type(e).__name__}")
raise last_err
async def main():
out_dir = Path("docs/samples")
out_dir.mkdir(parents=True, exist_ok=True)
voice_roster = [
n for n in INWORLD_MALE_VOICES + INWORLD_FEMALE_VOICES
if n not in BLACKLISTED_VOICES
]
date_str = datetime.now().strftime("%A, %B %d, %Y")
# Batch 1: include Silas (if lore exists)
regulars = regulars_v2.load_all_active_regulars()
if regulars:
ctx_with_silas = {
"date": date_str,
"weather": "cool desert night",
"headlines": [],
"recent_caller_summaries": [],
"regulars_included": [
{"name": r.name, "lore": r.lore_body, "arc_state": r.arc_state}
for r in regulars
],
"caller_count": 5,
"voice_roster": voice_roster,
}
print("[Batch 1] Generating 5 callers including Silas...")
silas_batch = await caller_gen.generate_batch(ctx_with_silas)
print(f"[Batch 1] Got {len(silas_batch)} callers: {[c.name for c in silas_batch]}")
else:
print("[Batch 1] No regulars found — skipping")
silas_batch = []
# Batch 2: walk-ins
ctx_walkins = {
"date": date_str,
"weather": "cool desert night",
"headlines": [],
"recent_caller_summaries": [],
"regulars_included": [],
"caller_count": 5,
"voice_roster": voice_roster,
}
print("[Batch 2] Generating 5 walk-in callers...")
walkin_batch = await caller_gen.generate_batch(ctx_walkins)
print(f"[Batch 2] Got {len(walkin_batch)} callers: {[c.name for c in walkin_batch]}")
all_callers = silas_batch + walkin_batch
async with httpx.AsyncClient(timeout=120.0) as client:
for idx, caller in enumerate(all_callers, 1):
print(f"[Dialog {idx}/{len(all_callers)}] {caller.name}...")
cdict = {
"name": caller.name,
"identity": caller.identity,
"situation": caller.situation,
"reason_calling": caller.reason_calling,
"secret_want": caller.secret_want,
"specific_details": caller.specific_details,
}
system_prompt = get_caller_prompt(cdict)
conversation = [{"role": "assistant", "content": caller.opening_line}]
for _ in range(4):
host_line = random.choice(HOST_REACTIONS)
conversation.append({"role": "user", "content": host_line})
reply = await dialog_turn(client, system_prompt, conversation)
conversation.append({"role": "assistant", "content": reply})
transcript_lines = [
f"CALLER ({caller.name}, {caller.age}, {caller.location}): "
f"{conversation[0]['content']}"
]
for i in range(1, len(conversation), 2):
transcript_lines.append(f"LUKE: {conversation[i]['content']}")
if i + 1 < len(conversation):
transcript_lines.append(f"CALLER: {conversation[i+1]['content']}")
slug = caller.name.replace(" ", "_").lower()
fname = out_dir / f"sample_{slug}.txt"
fname.write_text(
f"=== {caller.name} ({caller.age}, {caller.location}) ===\n"
f"voice: {caller.voice_resolved}\n"
f"emotional_register: {caller.emotional_register}\n"
f"secret_want: {caller.secret_want}\n\n"
+ "\n".join(transcript_lines)
+ "\n"
)
print(f"Wrote {fname}")
if __name__ == "__main__":
asyncio.run(main())