Fix Silas identity/voice leak, avatar gender, and add pre-warm batch gen
session.caller was reading name/voice from CALLER_BASES randomized defaults instead of the slim bg dict — causing Silas to appear as "Earl" with wrong voice. Now prefers bg data when populated. Also: avatar endpoint infers gender from voice pool, /api/callers returns bg name, regulars get canonical voice force-locked, batch gen splits into two parallel calls with pre-warm so subsequent resets are instant. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -92,6 +92,7 @@ def build_batch_prompt(ctx: dict) -> str:
|
||||
lines.append(f"Current arc state: {r['arc_state']}")
|
||||
lines.append("")
|
||||
lines.append(f"For {r['name']}: invent a fresh reason he is calling tonight — a new development, grievance, or specific recent event. DO NOT alter his voice, personality, or core traits. Write a new scene for an existing character.")
|
||||
lines.append(f"CRITICAL — this caller MUST appear in the output JSON with EXACTLY these fields locked: name=\"{r['name']}\", voice_suggestion=\"{r['voice']}\", age={r['age']}. Do NOT rename this caller. Do NOT give him a different voice. Do NOT change his age. Only invent: location, identity, situation, reason_calling, opening_line, secret_want, specific_details, emotional_register.")
|
||||
lines.append("")
|
||||
|
||||
lines.append(f"Available voices (voice_suggestion must match one of these exactly):")
|
||||
@@ -131,3 +132,54 @@ async def generate_batch(ctx: dict) -> list[CallerIdentity]:
|
||||
for c in callers:
|
||||
c.voice_resolved = resolve_voice(c.voice_suggestion, ctx["voice_roster"])
|
||||
return callers
|
||||
|
||||
|
||||
REGULAR_SYSTEM_PROMPT = """You are writing a single caller — a recurring character on Luke's late-night radio show. The character's identity is fixed (name, voice, age, core lore). You only invent a fresh reason he's calling tonight, grounded in his current arc state.
|
||||
|
||||
Output strict JSON with these fields only: location, identity, situation, reason_calling, opening_line, secret_want, specific_details (array of 2-3 strings), emotional_register. Do NOT include name, voice_suggestion, or age — those are locked."""
|
||||
|
||||
|
||||
async def generate_regular_situation(regular: dict, ctx: dict) -> dict:
|
||||
"""Generate a fresh situation for a locked regular. Returns a dict matching
|
||||
the CallerIdentity schema. One small LLM call (~$0.01)."""
|
||||
lines = [
|
||||
f"Tonight is {ctx['date']}. {ctx['weather']}.",
|
||||
"",
|
||||
f"### {regular['name']}",
|
||||
regular["lore"],
|
||||
f"Current arc state: {regular['arc_state']}",
|
||||
"",
|
||||
f"Invent a fresh reason {regular['name']} is calling tonight — a new development, grievance, or specific recent event. DO NOT alter his voice, personality, or core traits.",
|
||||
"",
|
||||
"Output JSON only, no prose.",
|
||||
]
|
||||
prompt = REGULAR_SYSTEM_PROMPT + "\n\n" + "\n".join(lines)
|
||||
|
||||
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||
resp = await client.post(
|
||||
"https://openrouter.ai/api/v1/chat/completions",
|
||||
headers={"Authorization": f"Bearer {settings.openrouter_api_key}"},
|
||||
json={
|
||||
"model": BATCH_MODEL,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"response_format": {"type": "json_object"},
|
||||
"max_tokens": 1500,
|
||||
"temperature": 0.9,
|
||||
},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
|
||||
content = data["choices"][0]["message"]["content"]
|
||||
usage = data.get("usage", {})
|
||||
cost_tracker.record_llm_call(
|
||||
category="background_gen",
|
||||
model=BATCH_MODEL,
|
||||
usage_data=usage,
|
||||
)
|
||||
|
||||
stripped = content.strip()
|
||||
if stripped.startswith("```") and stripped.endswith("```"):
|
||||
lines = stripped.splitlines()
|
||||
stripped = "\n".join(lines[1:-1])
|
||||
return json.loads(stripped)
|
||||
|
||||
Reference in New Issue
Block a user