diff --git a/backend/app/services/persona_generator.py b/backend/app/services/persona_generator.py index b9d8e95..e24ce98 100644 --- a/backend/app/services/persona_generator.py +++ b/backend/app/services/persona_generator.py @@ -31,10 +31,23 @@ class PersonaGenerator: f"Sales Kit:\n{kit_json}\n\n" f"Generate exactly 15 personas (5 per tier A/B/C) as JSON." ) - result = self.llm.complete_json( - PERSONA_SYSTEM, user_prompt, temperature=0.8, max_tokens=14000 - ) - personas = result.get("personas") or [] + # Real LLMs sometimes return fewer than 15 (truncation / merge). Retry up to 2 extra + # times with a nudge; the body below already ACCEPTS short results (>= 8) instead of + # hard-failing, so these retries are just best-effort to reach a fuller set. + attempt = 0 + while True: + attempt += 1 + prompt = user_prompt + ( + "" + if attempt == 1 + else "\n\n(Note: you left some personas out — please output all 15, one JSON object per persona, no extra prose.)" + ) + result = self.llm.complete_json( + PERSONA_SYSTEM, prompt, temperature=0.8, max_tokens=14000 + ) + personas = result.get("personas") or [] + if (isinstance(personas, list) and len(personas) >= 15) or attempt >= 3: + break if not isinstance(personas, list) or not personas: raise ValueError("persona generator returned no personas") @@ -57,6 +70,8 @@ class PersonaGenerator: p.setdefault("pains", []) p.setdefault("negotiation_levers", []) p.setdefault("objections", []) + p.setdefault("tolerance", 3) + p.setdefault("recontact", False) normalized.append(p) # Wrap tier-C: ensure at least one wrong_text persona @@ -69,6 +84,9 @@ class PersonaGenerator: p["special"] = "wrong_text" break - if len(normalized) < 15: - raise ValueError(f"expected 15 personas, generated {len(normalized)}") + if len(normalized) < 8: + raise ValueError(f"expected ~15 personas, generated only {len(normalized)}") + # NOTE: if we're short of 15 (real LLMs occasionally return 14/13), we ACCEPT what we + # got rather than crashing the whole analyze — the caller/UI can top up with + # "create more personas". A retry loop lives in generate(). return normalized