Move caller dialog to Sonnet 4.6, add SearXNG for Devon, and fix stale model ids
Working-tree changes that had accumulated without being committed. The test updates matter most: tests/test_caller_gen.py was left behind when caller_gen started requiring voice and age in regulars_included, so the committed tree had a failing suite that only passed locally. - Caller dialog moves from Haiku 4.5 to Sonnet 4.6 (~$1/show to ~$3-4/show) - Grok pinned to x-ai/grok-4.3; grok-4, grok-4-fast and grok-4.1-fast were retired from OpenRouter, and llm.py swallows the 404 and returns empty text, so a retired id makes callers go silent with nothing in the logs - Devon's web_search now runs against SearXNG on the NAS - Assorted TTS, audio, news, cost tracker and control-panel changes - CLAUDE.md updated to match Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
import json
|
||||
import random
|
||||
|
||||
import httpx
|
||||
|
||||
@@ -51,23 +52,48 @@ def parse_batch_response(raw: str) -> list[CallerIdentity]:
|
||||
|
||||
def resolve_voice(suggestion: str, roster: list[str]) -> str:
|
||||
"""Map sonnet's voice suggestion to a real voice in the roster.
|
||||
Case-insensitive exact match; deterministic fallback to first roster entry."""
|
||||
if not suggestion or not roster:
|
||||
return roster[0] if roster else ""
|
||||
lower_map = {v.lower(): v for v in roster}
|
||||
return lower_map.get(suggestion.lower(), roster[0])
|
||||
Case-insensitive exact match; random fallback if unmatched (so LLM
|
||||
hallucinations don't all collapse onto roster[0])."""
|
||||
if not roster:
|
||||
return ""
|
||||
if suggestion:
|
||||
match = {v.lower(): v for v in roster}.get(suggestion.lower())
|
||||
if match:
|
||||
return match
|
||||
fallback = random.choice(roster)
|
||||
print(f"[caller_gen] voice '{suggestion}' not in roster — random fallback to '{fallback}'")
|
||||
return fallback
|
||||
return random.choice(roster)
|
||||
|
||||
|
||||
BATCH_SYSTEM_PROMPT = """You are writing a roster of callers for Luke's late-night radio show in New Mexico.
|
||||
BATCH_SYSTEM_PROMPT = """You are writing a roster of callers for Luke's late-night radio show, broadcast out of Alpine, Texas, in the Big Bend country of far West Texas.
|
||||
|
||||
WHERE THESE CALLERS LIVE: The show's world is the Big Bend region of far West Texas — high desert, remote, dark skies, ranching country up against the Mexico border. Most callers are from or know this area. Ground them in REAL places and facts only — never invent businesses or landmarks that don't exist. The towns:
|
||||
- Alpine — the hub of the region (about 6,000 people), Brewster County seat, home of Sul Ross State University, a mile-high old ranching and railroad town with a small arts scene. The show broadcasts from here.
|
||||
- Marfa — minimalist-art tourist town (the Chinati Foundation / Donald Judd, the Prada Marfa installation), famous for the unexplained Marfa Lights. Old ranching families chafing against an influx of artists and out-of-towners.
|
||||
- Marathon — tiny, known for the historic Gage Hotel, the eastern gateway to Big Bend National Park.
|
||||
- Terlingua — a quicksilver-mining ghost town turned off-grid haven for desert eccentrics; famous for its chili cookoff, river-rafting outfitters on the Rio Grande, and the Starlight Theatre. Right up against the Mexico border (Boquillas crossing).
|
||||
- Fort Stockton — an oilfield and I-10 town to the north in Pecos County (Paisano Pete the roadrunner), more working-class and blue-collar than the artsy towns.
|
||||
- The Big Bend itself — the Chisos Mountains, the Rio Grande, some of the darkest night skies in the country (the McDonald Observatory is near Fort Davis), brutally remote, with Permian Basin oil money booming to the north.
|
||||
Callers can reference real ranches, the heat and wind, the drive times (everything is hours apart), border life, Sul Ross students, oilfield work, tourists, and the desert weirdos — but keep it real and specific to this place.
|
||||
|
||||
CREATIVE RANGE: Your callers must span the emotional range of Howard Stern (chaos, strong characters), Coast to Coast AM (earnest weirdos, sincere believers), Loveline (real problems, real advice-seeking), Delilah (emotional vulnerability, connection), and Opie and Anthony (sharp, irreverent, specific people).
|
||||
|
||||
ROSTER MIX — VARY THE CALL TYPES. A great show is not ten people in moral crisis. Build the roster of 10 callers roughly like this:
|
||||
- 4-5 DILEMMA CALLS (the dramatic spine): real human conflict with STAKES — moral dilemmas, confessions, betrayals, impossible choices, or something the caller did and can't take back. Think: "I found out my dad has a second family," "I got someone fired and they deserved it but now their kid is sick," "my best friend's wife hit on me and I didn't say no." These need genuine emotional weight.
|
||||
- 2-3 STORY / ENTHUSIAST CALLS (the relief): callers with a wild thing that happened to them, a fascinating obsession or piece of knowledge, a strange-but-true story, or a vivid slice of life. NO deep moral dilemma required — they call because the story is great, the fact is amazing, or they're bursting to talk about the thing they love. Still specific, still a reason they called TONIGHT, but the energy is delight or wonder or a great yarn, not anguish.
|
||||
- 1-2 TRUE BELIEVER / CHAOS CALLS (the spice): an earnest, sincere weirdo — UFOs, cryptids, a government conspiracy, a pattern only they can see, a paranormal experience (Coast to Coast AM energy, dead serious about it) — OR a big eccentric personality on a tear about something trivial. Played straight, never winking.
|
||||
|
||||
Every caller still needs SOMETHING that makes the audience lean in — a problem, a secret, a story, a wild belief, or an irresistible enthusiasm. But not every caller carries grief. Let the show breathe.
|
||||
|
||||
Maximum character distance between callers. No two callers should feel like siblings.
|
||||
|
||||
ANTI-COLLISION RULE — THIS IS NON-NEGOTIABLE: All callers in this roster must be clearly differentiated. No two callers may share the same hobby, obsession, profession archetype, or story theme. Specifically forbidden within a single roster: two BBQ competitors, two taxidermists, two amateur-radio or mystery-signal callers, two callers with ex-spouse drama, two conspiracy/pattern-seers, two retired military, two grandmothers-of-many, two callers calling about a weird neighbor, two callers with religious-object stories. Each caller's defining "thing" — their hook, their obsession, the specific topic they're calling about — must appear exactly once in the roster. Before finalizing, scan your output and swap any collisions.
|
||||
ANTI-COLLISION RULE — THIS IS NON-NEGOTIABLE: All callers in this roster must be clearly differentiated. No two callers may share the same hobby, obsession, profession archetype, or story theme. Specifically forbidden within a single roster: two BBQ competitors, two taxidermists, two amateur-radio or mystery-signal callers, two callers with ex-spouse drama, two believers chasing the SAME phenomenon (e.g. two UFO callers, or two cryptid callers — if you have two believer calls they must be about completely different things), two retired military, two grandmothers-of-many, two callers calling about a weird neighbor, two callers with religious-object stories. Each caller's defining "thing" — their hook, their obsession, the specific topic they're calling about — must appear exactly once in the roster. Before finalizing, scan your output and swap any collisions.
|
||||
|
||||
Do not default to sitcom plots. Real humans are specific and strange. Give each caller details that could only belong to them.
|
||||
|
||||
OPENING LINE RULES: Each caller's opening_line must be unique and specific to their situation. NEVER write "I've been listening for X years" or "long-time listener, first-time caller" or any variant. The caller should jump into their story or problem immediately — nervous, excited, angry, whatever fits. The opening line is the hook that makes the audience lean in.
|
||||
|
||||
You will output strict JSON with a "callers" array. Each caller has exactly these fields: name, age, voice_suggestion, location, identity, situation, reason_calling, opening_line, secret_want, specific_details (array of 2-3 strings), emotional_register."""
|
||||
|
||||
|
||||
@@ -81,6 +107,19 @@ def build_batch_prompt(ctx: dict) -> str:
|
||||
lines.append(f"- {h}")
|
||||
lines.append("")
|
||||
|
||||
theme = (ctx.get("theme") or "").strip()
|
||||
if theme:
|
||||
lines.append(f'TONIGHT\'S SHOW THEME: "{theme}"')
|
||||
lines.append(
|
||||
f'Roughly 2/3 of callers MUST be calling BECAUSE OF this theme — the theme '
|
||||
f'should be woven directly into their reason_calling and situation, not '
|
||||
f'just acknowledged. Make the connection specific and personal (a story, a '
|
||||
f'conflict, a moment) not abstract. The remaining 1/3 of the roster can be '
|
||||
f'unrelated walk-ins for variety. Do NOT make every caller theme-connected — '
|
||||
f'variety still matters.'
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
if ctx["recent_caller_summaries"]:
|
||||
lines.append("Recent callers (DO NOT repeat these archetypes or situations):")
|
||||
for s in ctx["recent_caller_summaries"]:
|
||||
|
||||
Reference in New Issue
Block a user