fix(scenario): recontact chats normally first then re-engages mid-chat; channel default=social; remove 'add more' button (15 personas auto)

- recontact persona now opens as a NORMAL customer (no 'I asked before' in the opening);
  the mid-chat time-lapse system note (turn 2) makes them re-engage warmer instead.
- channel default changed facebook->social everywhere (create/analyze/store/simulator/me).
- 15 personas auto-generated (TARGET=15); removed the 'สร้างบุคคลต้นแบบเพิ่มเติม' button/guide.
All 9 backend suites pass. Rebuilt dist.
This commit is contained in:
Macky
2026-08-09 12:37:43 +07:00
parent 2b414d13b7
commit 8771438aef
30 changed files with 61 additions and 71 deletions

View File

@@ -139,7 +139,7 @@ def create_group():
"product": product,
"segment": (data.get("segment") or ""),
"description": (data.get("description") or ""),
"channel": (data.get("channel") or "facebook"),
"channel": (data.get("channel") or "social"),
"language": (data.get("language") or "th"),
"files": saved_files,
"file_text": file_text[:60000],
@@ -209,7 +209,7 @@ def analyze_group(gid: str):
segment=inp.get("segment", ""),
description=inp.get("description", ""),
file_text=inp.get("file_text", ""),
channel=inp.get("channel", "facebook"),
channel=inp.get("channel", "social"),
)
except Exception as exc:
s["groups"].update(gid, status="failed", error=str(exc))
@@ -223,7 +223,7 @@ def analyze_group(gid: str):
personas = PersonaGenerator(s["llm"]).generate(
sales_kit=sales_kit,
language=inp.get("language", "th"),
channel=inp.get("channel", "facebook"),
channel=inp.get("channel", "social"),
)
personas = existing + personas
except Exception as exc:

View File

@@ -74,7 +74,7 @@ def _personal_group(s, actor) -> dict:
g["id"],
status="ready",
owner_user_id=actor["id"],
input={"channel": "facebook", "language": "th"},
input={"channel": "social", "language": "th"},
sales_kit={"productName": "personal practice", "valueProps": [], "features": []},
)
return s["groups"].get(g["id"])

View File

@@ -35,7 +35,7 @@ def generate_own_persona(llm: LLMClient, *, mode: str, spec: dict[str, Any]) ->
if not isinstance(persona, dict):
raise ValueError("own-persona generator returned invalid data")
persona.setdefault("tier", "B")
persona.setdefault("channel", "facebook")
persona.setdefault("channel", "social")
persona.setdefault("initiation_mode", "customer")
persona.setdefault("pains", [])
persona.setdefault("negotiation_levers", [])

View File

@@ -8,7 +8,8 @@ from ..llm import LLMClient
from .persona_prompts import PERSONA_SYSTEM
TIERS = ["A", "B", "C"]
PER_TIER = 5
PER_TIER = 5 # 5 per tier = 15 total
TARGET = 15 # total personas the system generates (no "add more" button needed)
class PersonaGenerator:
@@ -20,7 +21,7 @@ class PersonaGenerator:
*,
sales_kit: dict[str, Any],
language: str = "en",
channel: str = "facebook",
channel: str = "social",
) -> list[dict[str, Any]]:
kit_json = json.dumps(sales_kit, ensure_ascii=False)[:12000]
lang_name = "Thai" if language == "th" else "English"
@@ -46,13 +47,15 @@ class PersonaGenerator:
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:
if (isinstance(personas, list) and len(personas) >= TARGET) or attempt >= 3:
break
if not isinstance(personas, list) or not personas:
raise ValueError("persona generator returned no personas")
normalized, counts = [], {"A": 0, "B": 0, "C": 0}
for idx, p in enumerate(personas, start=1):
if len(normalized) >= TARGET:
break # already reached 20 total
if not isinstance(p, dict):
continue
tier = p.get("tier", p.get("intent_tier"))
@@ -86,7 +89,6 @@ class PersonaGenerator:
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().
# NOTE: if we're short of 15 (real LLMs occasionally return 14), we ACCEPT what we
# got rather than crashing the whole analyze — with TARGET=15 there's normally no gap.
return normalized

View File

@@ -102,21 +102,16 @@ class Simulator:
"social": "Chat style: short, casual, quick social-messaging replies.",
"f2f_call": "Style: natural, conversational like a live face-to-face or phone talk.",
}.get(scenario, "")
# Personality trait: a "recontact" customer asked about this before and is only now
# coming back, already warmer / more ready to buy. Reference it in chat naturally.
if persona.get("recontact"):
adapt += (
"\nYou contacted/interacted with this seller BEFORE (earlier contact) and are now "
"coming back — you already know the product basics, so you're warmer and more ready "
"to decide. Mention this naturally (e.g. 'I asked about this a while back')."
)
# NOTE: 'recontact' is a MID-CHAT behavior, not baked into the opening — the trainee
# chats with this customer normally first. At the right turn (see chat_routes) a
# time-lapse system note is inserted and only THEN does the customer re-engage warmer.
tolerance = int(persona.get("tolerance", 3) or 3)
system = CHAT_SYSTEM.format(
name=persona.get("name", "Customer"),
tone=persona.get("communication_style", "natural, casual"),
profession=persona.get("profession", "customer"),
age_group=persona.get("age_group", "adult"),
channel=persona.get("channel", "facebook") + (f" ({scenario})" if scenario else ""),
channel=persona.get("channel", "social") + (f" ({scenario})" if scenario else ""),
background=persona.get("background", ""),
personality=persona.get("personality", ""),
lifestyle=persona.get("lifestyle", ""),