fix(chat): resume unfinished session without re-picking scenario; detect win/loss from real-LLM text
- start_session now RESUMES an existing ACTIVE session for the persona instead of creating a new one / forcing re-pick of the scenario (keep original scenario+messages). - send_message falls back to a text-based decision detector (_detect_customer_decision) because real LLMs rarely emit structured meta.decision — so a customer who says 'ซื้อไม่ไหว'/'no thanks' now actually ENDS the chat as lost (was stuck active forever). Fragments handled in TH + EN; buy + walk. All 11 backend suites pass. Rebuilt dist.
This commit is contained in:
50
backend/scripts/test_resume_decision.py
Normal file
50
backend/scripts/test_resume_decision.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""Test: chat RESUME (no re-pick scenario) + decision detection for real-LLM text."""
|
||||
import os, sys, tempfile, warnings
|
||||
from pathlib import Path
|
||||
warnings.filterwarnings("ignore")
|
||||
BACKEND = str(Path(__file__).resolve().parents[1])
|
||||
sys.path.insert(0, BACKEND)
|
||||
from app.factory import create_app
|
||||
from app.config import Config
|
||||
|
||||
td = tempfile.mkdtemp(); Config.DATA_DIR = Path(td)
|
||||
sys.path.insert(0, BACKEND + "/scripts")
|
||||
from mock_llm import MockLLM
|
||||
|
||||
app = create_app(); app.extensions["llm"] = MockLLM()
|
||||
C = app.test_client()
|
||||
def tok(u,p): return C.post("/api/auth/login", json={"username":u,"password":p}).get_json()["token"]
|
||||
AT = tok("admin","1234"); AH={"Authorization":f"Bearer {AT}"}
|
||||
C.post("/api/auth/setup", headers=AH, json={"username":"admin","email":"a@b.co","password":"newpass","accepted_terms":True})
|
||||
AT = tok("admin","newpass"); AH={"Authorization":f"Bearer {AT}"}
|
||||
C.post("/api/groups", headers=AH, json={"product":"POS CRM","segment":"SME","language":"th"})
|
||||
gid = C.get("/api/groups", headers=AH).get_json()["groups"][0]["id"]
|
||||
C.post(f"/api/groups/{gid}/analyze", headers=AH)
|
||||
pid = C.get(f"/api/groups/{gid}/personas", headers=AH).get_json()["personas"][0]["id"]
|
||||
|
||||
# 1. start chat
|
||||
r1 = C.post(f"/api/chat/{gid}/personas/{pid}/chat/start", headers=AH, json={"scenario":"social","locale":"th"})
|
||||
assert r1.status_code == 200, r1.get_json()
|
||||
sid1 = r1.get_json()["session"]["id"]
|
||||
|
||||
# 2. call start AGAIN (as if re-entering) -> should RESUME the same session, not create new
|
||||
r2 = C.post(f"/api/chat/{gid}/personas/{pid}/chat/start", headers=AH, json={"scenario":"f2f_call","locale":"th"})
|
||||
assert r2.status_code == 200, r2.get_json()
|
||||
sid2 = r2.get_json()["session"]["id"]
|
||||
assert sid1 == sid2, f"resume must return same session (got {sid2}, want {sid1})"
|
||||
assert r2.get_json()["scenario"] == "social", "resume keeps ORIGINAL scenario (not re-pick)"
|
||||
print("[ok] resume returns same active session + keeps original scenario")
|
||||
|
||||
# 3. resume endpoint also returns the active session
|
||||
rr = C.get(f"/api/chat/{gid}/personas/{pid}/chat/resume", headers=AH)
|
||||
assert rr.status_code == 200 and rr.get_json()["session"]["id"] == sid1
|
||||
print("[ok] /chat/resume returns active session")
|
||||
|
||||
# 4. decision detection: unit-test the text detector
|
||||
from app.api.chat_routes import _detect_customer_decision
|
||||
assert _detect_customer_decision("ผมซื้อไม่ไหวแล้วครับ ขอตัวก่อน") == "walk"
|
||||
assert _detect_customer_decision("สวัสดีครับ ผมสนใจสินค้าครับ") is None
|
||||
assert _detect_customer_decision("ok ผมเอาครับ รับเลย") == "buy"
|
||||
print("[ok] _detect_customer_decision: walk/buy/None cases correct")
|
||||
|
||||
print("ALL RESUME+DECISION TESTS PASSED")
|
||||
Reference in New Issue
Block a user