@innvisor/conny-ai 9.7.0 → 9.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/README.md +17 -1
- package/conny_app.py +9 -3
- package/conny_cli.py +103 -11
- package/conny_core/evolution.py +112 -0
- package/conny_core/first_turn_ops.py +16 -20
- package/conny_core/prompt_ops.py +62 -0
- package/conny_demo_voice.py +1 -1
- package/conny_doctor.py +287 -2
- package/conny_domino.py +2 -2
- package/conny_generator.py +1 -1
- package/conny_i18n.py +81 -2
- package/conny_init.py +254 -41
- package/conny_runtime_ops.py +198 -6
- package/conny_tui.py +7 -0
- package/conny_ultra_config.py +25 -11
- package/conny_utils.py +21 -3
- package/ecosystem.config.js +11 -1
- package/install.sh +78 -22
- package/npm/conny.js +75 -21
- package/package.json +12 -2
- package/run.sh +7 -0
- package/src/conny/admin/dashboard.py +35 -4
- package/src/conny/admin_memory.py +93 -0
- package/src/conny/api/routes.py +26 -9
- package/src/conny/channels/cli.py +30 -9
- package/src/conny/demo/handler.py +23 -23
- package/src/conny/personas/generator.py +1 -1
- package/src/conny/production/domino.py +2 -2
- package/src/conny/production/guard.py +4 -4
- package/src/core/admin_engines.py +51 -48
- package/src/core/globals.py +110 -9
- package/src/core/production_monitor.py +63 -38
- package/src/core/runtime.py +343 -305
- package/src/domain/prompts/prospect_pitch.py +11 -11
- package/src/domain/send_guard.py +4 -4
- package/src/interfaces/web/app.py +91 -27
- package/src/interfaces/web/demo_admin_commands.py +165 -0
- package/src/interfaces/web/demo_handler.py +178 -34
- package/brand-assets/cl-nica-de-las-am-ricas/manifest.json +0 -22
- package/brand-assets/cl-nica-de-las-am-ricas/processed/business-identity.txt +0 -11
- package/brand-assets/cl-nica-de-las-am-ricas/raw/business-identity.txt +0 -11
- package/brand-assets/cl-nica-las-am-ricas/manifest.json +0 -22
- package/brand-assets/cl-nica-las-am-ricas/processed/business-identity.txt +0 -11
- package/brand-assets/cl-nica-las-am-ricas/raw/business-identity.txt +0 -11
- package/brand-assets/conny-demo/manifest.json +0 -22
- package/brand-assets/conny-demo/processed/business-identity.txt +0 -7
- package/brand-assets/conny-demo/raw/business-identity.txt +0 -7
- package/fix_init.py +0 -27
- package/verify_conversation_impl.py +0 -48
|
@@ -17,7 +17,9 @@ async def handle_demo_message(
|
|
|
17
17
|
) -> List[str]:
|
|
18
18
|
# Local imports from conny.py to avoid circular dependencies
|
|
19
19
|
import sys
|
|
20
|
-
|
|
20
|
+
runtime_module = sys.modules[self.__class__.__module__]
|
|
21
|
+
facade_name = getattr(runtime_module, "__facade_name__", "")
|
|
22
|
+
conny_module = sys.modules.get(facade_name) or runtime_module
|
|
21
23
|
Config = conny_module.Config
|
|
22
24
|
db = conny_module.db
|
|
23
25
|
now_col = conny_module.now_col
|
|
@@ -58,9 +60,16 @@ async def handle_demo_message(
|
|
|
58
60
|
owner_confusion_or_language_signal as _owner_confusion_or_language_signal
|
|
59
61
|
)
|
|
60
62
|
|
|
63
|
+
from src.interfaces.web.demo_admin_commands import (
|
|
64
|
+
_strip_jid as _strip_jid,
|
|
65
|
+
is_admin_chat as _is_admin_demo,
|
|
66
|
+
looks_like_contact_command as _looks_like_contact_cmd,
|
|
67
|
+
handle_admin_contact_command as _handle_admin_contact_cmd,
|
|
68
|
+
)
|
|
69
|
+
|
|
61
70
|
# Check if optional symbols are available
|
|
62
71
|
_SESSION_MANAGER_AVAILABLE = getattr(conny_module, "_SESSION_MANAGER_AVAILABLE", False)
|
|
63
|
-
|
|
72
|
+
_INNVISOR_PATCHES = getattr(conny_module, "_INNVISOR_PATCHES", False)
|
|
64
73
|
_CONNY_DOMINO_AVAILABLE = getattr(conny_module, "_CONNY_DOMINO_AVAILABLE", False)
|
|
65
74
|
build_demo_domino_payload = getattr(conny_module, "build_demo_domino_payload", None)
|
|
66
75
|
build_prospect_pitch_system_prompt = getattr(conny_module, "build_prospect_pitch_system_prompt", None)
|
|
@@ -139,7 +148,9 @@ async def handle_demo_message(
|
|
|
139
148
|
del self._demo_sessions[k]
|
|
140
149
|
try:
|
|
141
150
|
with db._conn() as c:
|
|
142
|
-
c.execute("
|
|
151
|
+
existing = c.execute("SELECT COUNT(*) FROM conversations WHERE chat_id=?", (chat_id,)).fetchone()
|
|
152
|
+
if existing and existing[0] <= 2:
|
|
153
|
+
c.execute("DELETE FROM conversations WHERE chat_id=?", (chat_id,))
|
|
143
154
|
except Exception: pass
|
|
144
155
|
sk = f"demo_{chat_id}"
|
|
145
156
|
else:
|
|
@@ -182,10 +193,11 @@ async def handle_demo_message(
|
|
|
182
193
|
mem = get_memory(instance_id)
|
|
183
194
|
mem.delete_session_cache(chat_id)
|
|
184
195
|
except Exception: pass
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
196
|
+
if last_seen > 0:
|
|
197
|
+
try:
|
|
198
|
+
with db._conn() as c:
|
|
199
|
+
c.execute("DELETE FROM conversations WHERE chat_id=?", (chat_id,))
|
|
200
|
+
except Exception: pass
|
|
189
201
|
|
|
190
202
|
history = db.get_history(chat_id) if db else []
|
|
191
203
|
now_dt = now_col()
|
|
@@ -217,6 +229,8 @@ async def handle_demo_message(
|
|
|
217
229
|
ready_for_customer = bool(self._demo_sessions.get(bready_key, False))
|
|
218
230
|
llm_runtime_ready = self._llm_runtime_available()
|
|
219
231
|
|
|
232
|
+
is_admin_demo = _is_admin_demo(chat_id, clinic, db) if not sim_mode_active else False
|
|
233
|
+
|
|
220
234
|
def _detect_demo_owner_language(raw_text: str, current_lang: str = "es") -> str:
|
|
221
235
|
normalized = _normalize_conv_text(raw_text or "")
|
|
222
236
|
if not normalized:
|
|
@@ -383,14 +397,14 @@ async def handle_demo_message(
|
|
|
383
397
|
|
|
384
398
|
# ── SEND GUARD — pitch inteligente + fix de cortes ──────────────────
|
|
385
399
|
_guard = None
|
|
386
|
-
if
|
|
400
|
+
if _INNVISOR_PATCHES:
|
|
387
401
|
try:
|
|
388
402
|
_guard = SendGuard(context="demo", business_name=business_name)
|
|
389
403
|
except Exception:
|
|
390
404
|
_guard = None
|
|
391
405
|
|
|
392
406
|
# Smart handoff proactivo ANTES del LLM (prospecto quiere hablar con humano)
|
|
393
|
-
if
|
|
407
|
+
if _INNVISOR_PATCHES and _guard:
|
|
394
408
|
try:
|
|
395
409
|
_proactive = _guard.check_handoff(text, history)
|
|
396
410
|
if _proactive and _SMART_HANDOFF and handoff_manager:
|
|
@@ -413,8 +427,8 @@ async def handle_demo_message(
|
|
|
413
427
|
}
|
|
414
428
|
)
|
|
415
429
|
_is_first_demo_turn = not any(m.get("role") == "assistant" for m in history)
|
|
416
|
-
# Fix
|
|
417
|
-
if
|
|
430
|
+
# Fix Innvisor / BlackBoss + cortes ANTES de procesar
|
|
431
|
+
if _INNVISOR_PATCHES:
|
|
418
432
|
try:
|
|
419
433
|
if _guard and business_name:
|
|
420
434
|
_guard.business_name = business_name
|
|
@@ -582,7 +596,7 @@ async def handle_demo_message(
|
|
|
582
596
|
_pre_text_low = text.lower().strip()
|
|
583
597
|
|
|
584
598
|
# ── PITCH INTELIGENTE — prospecto B2B confundido ─────────────────────
|
|
585
|
-
if
|
|
599
|
+
if _INNVISOR_PATCHES:
|
|
586
600
|
try:
|
|
587
601
|
_pitch_blockers = (
|
|
588
602
|
"para que", "para qué", "por que", "por qué",
|
|
@@ -796,11 +810,89 @@ async def handle_demo_message(
|
|
|
796
810
|
log.error(f"[demo] llm error: {e}")
|
|
797
811
|
return None
|
|
798
812
|
|
|
813
|
+
# ── Admin: beta demo mode toggle — antes de intercept para dejar pasar ──
|
|
814
|
+
_beta_key = sk + "_beta_demo"
|
|
815
|
+
_beta_active = bool(self._demo_sessions.get(_beta_key, False))
|
|
816
|
+
if is_admin_demo:
|
|
817
|
+
_gen_beta = _get_demo_engine() or llm_engine
|
|
818
|
+
if _gen_beta:
|
|
819
|
+
if _beta_active:
|
|
820
|
+
try:
|
|
821
|
+
r, _ = await _gen_beta.complete(
|
|
822
|
+
[{"role": "system", "content": "Responde solo SI o NO. El admin quiere SALIR del modo demo beta y volver a modo admin?"},
|
|
823
|
+
{"role": "user", "content": text}],
|
|
824
|
+
model_tier="fast", temperature=0.1, max_tokens=10,
|
|
825
|
+
)
|
|
826
|
+
if r and r.strip().upper() == "SI":
|
|
827
|
+
self._demo_sessions.pop(_beta_key, None)
|
|
828
|
+
_beta_active = False
|
|
829
|
+
except Exception:
|
|
830
|
+
pass
|
|
831
|
+
else:
|
|
832
|
+
try:
|
|
833
|
+
r, _ = await _gen_beta.complete(
|
|
834
|
+
[{"role": "system", "content": "Responde solo SI o NO. El admin quiere ACTIVAR el modo demo beta para que Conny lo trate como un lead normal?"},
|
|
835
|
+
{"role": "user", "content": text}],
|
|
836
|
+
model_tier="fast", temperature=0.1, max_tokens=10,
|
|
837
|
+
)
|
|
838
|
+
if r and r.strip().upper() == "SI":
|
|
839
|
+
self._demo_sessions[_beta_key] = True
|
|
840
|
+
_beta_active = True
|
|
841
|
+
except Exception:
|
|
842
|
+
pass
|
|
843
|
+
|
|
844
|
+
# ── Admin: intercept ALL admin messages ─────────────────────────────────
|
|
845
|
+
if is_admin_demo and not _beta_active:
|
|
846
|
+
log.info(f"[demo] Admin message from {chat_id}: {text[:80]}")
|
|
847
|
+
_save("user", text)
|
|
848
|
+
_raw_admin = _strip_jid(chat_id)
|
|
849
|
+
_admin_name = ""
|
|
850
|
+
try:
|
|
851
|
+
_admin_rec = db.get_admin(chat_id) or db.get_admin(_raw_admin)
|
|
852
|
+
if _admin_rec:
|
|
853
|
+
_admin_name = _admin_rec.get("name", "") or ""
|
|
854
|
+
except Exception:
|
|
855
|
+
pass
|
|
856
|
+
|
|
857
|
+
if _looks_like_contact_cmd(text):
|
|
858
|
+
result = await _handle_admin_contact_cmd(
|
|
859
|
+
self, chat_id, text, clinic, db,
|
|
860
|
+
llm_engine=llm_engine,
|
|
861
|
+
admin_name=_admin_name,
|
|
862
|
+
demo_llm=_llm,
|
|
863
|
+
)
|
|
864
|
+
if result:
|
|
865
|
+
_save("assistant", " ||| ".join(result))
|
|
866
|
+
return result
|
|
867
|
+
return []
|
|
868
|
+
|
|
869
|
+
# General admin command: LLM conversation
|
|
870
|
+
try:
|
|
871
|
+
sys_ctx = "Eres Conny."
|
|
872
|
+
if _admin_name:
|
|
873
|
+
sys_ctx += f" Le estás respondiendo a {_admin_name}, el admin."
|
|
874
|
+
sys_ctx += " Respondé en 2 burbujas cortas separadas por |||."
|
|
875
|
+
gen_eng = _get_demo_engine() or llm_engine
|
|
876
|
+
if gen_eng:
|
|
877
|
+
r, _ = await gen_eng.complete(
|
|
878
|
+
[{"role": "system", "content": sys_ctx},
|
|
879
|
+
{"role": "user", "content": text}],
|
|
880
|
+
model_tier="fast", temperature=0.82, max_tokens=400,
|
|
881
|
+
)
|
|
882
|
+
if r:
|
|
883
|
+
parts = [p.strip() for p in r.split("|||") if p.strip()]
|
|
884
|
+
if parts:
|
|
885
|
+
_save("assistant", " ||| ".join(parts[:2]))
|
|
886
|
+
return parts[:2]
|
|
887
|
+
except Exception:
|
|
888
|
+
pass
|
|
889
|
+
return []
|
|
890
|
+
|
|
799
891
|
async def _llm_classify_business_name(raw_text: str) -> Tuple[bool, Optional[str]]:
|
|
800
892
|
return await llm_classify_business_name(raw_text, _get_demo_engine())
|
|
801
893
|
|
|
802
894
|
async def _llm_conv_pitch(temp=0.85, max_t=8192, recent_limit=12):
|
|
803
|
-
"""LLM con el pitch de
|
|
895
|
+
"""LLM con el pitch de Innvisor para prospectos confundidos."""
|
|
804
896
|
try:
|
|
805
897
|
pitch_sys = build_prospect_pitch_system_prompt(business_name)
|
|
806
898
|
except Exception:
|
|
@@ -910,8 +1002,8 @@ async def handle_demo_message(
|
|
|
910
1002
|
}
|
|
911
1003
|
)
|
|
912
1004
|
_is_first_demo_turn = not any(m.get("role") == "assistant" for m in history)
|
|
913
|
-
# ──
|
|
914
|
-
if
|
|
1005
|
+
# ── INNVISOR: fix Innvisor + cortes antes de procesar ──────────
|
|
1006
|
+
if _INNVISOR_PATCHES:
|
|
915
1007
|
try:
|
|
916
1008
|
if _guard and business_name:
|
|
917
1009
|
_guard.business_name = business_name
|
|
@@ -1063,7 +1155,7 @@ async def handle_demo_message(
|
|
|
1063
1155
|
detail_tokens = ("chat", "cliente", "demo", "tono", "responder", "whatsapp")
|
|
1064
1156
|
return not any(token in lowered_response for token in detail_tokens)
|
|
1065
1157
|
if any(token in lowered_user for token in ("quien te hizo", "quién te hizo", "como tenerte", "cómo tenerte", "quien te creo", "quién te creó")):
|
|
1066
|
-
return "
|
|
1158
|
+
return "innvisor" not in lowered_response or "3243699856" not in lowered_response
|
|
1067
1159
|
if any(token in lowered_user for token in ("audio", "audios", "nota de voz", "pdf", "archivo", "documento", "imagen")):
|
|
1068
1160
|
return not any(token in lowered_response for token in ("audio", "pdf", "documento", "imagen", "transcrib"))
|
|
1069
1161
|
if any(token in lowered_user for token in ("me mandaron tu numero", "me mandaron tu número", "me pasaron tu numero", "me pasaron tu número", "que haces exactamente", "qué haces exactamente", "no entiendo que haces", "no entiendo qué haces")):
|
|
@@ -1249,6 +1341,61 @@ async def handle_demo_message(
|
|
|
1249
1341
|
return None, had_output
|
|
1250
1342
|
|
|
1251
1343
|
|
|
1344
|
+
def _demo_owner_last_resort(
|
|
1345
|
+
user_text: str,
|
|
1346
|
+
*,
|
|
1347
|
+
explain_name: bool = False,
|
|
1348
|
+
current_business_name: str = "",
|
|
1349
|
+
) -> str:
|
|
1350
|
+
"""
|
|
1351
|
+
Last resort only when no usable model response exists.
|
|
1352
|
+
It must keep the demo moving without pretending the network failed.
|
|
1353
|
+
"""
|
|
1354
|
+
_biz = (current_business_name or business_name or "").strip()
|
|
1355
|
+
_user = _normalize_conv_text(user_text or "")
|
|
1356
|
+
if any(token in _user for token in ("para que", "para qué", "por que", "por qué")):
|
|
1357
|
+
if _biz:
|
|
1358
|
+
return _lang_text(
|
|
1359
|
+
f"te lo pido para hablar como si ya llevara el chat de {_biz} ||| así la demo te muestra mejor cómo respondería de verdad",
|
|
1360
|
+
f"i ask for it so I can sound like I already handle {_biz}'s chat ||| that way the demo feels real and properly grounded",
|
|
1361
|
+
)
|
|
1362
|
+
return _lang_text(
|
|
1363
|
+
"te lo pido para ubicar el tono, el contexto y cómo tendría que responder ||| apenas me digas el nombre del negocio te muestro la demo bien aterrizada",
|
|
1364
|
+
"i ask for it so I can match the tone, context and way I should reply ||| send me the business name and I’ll make the demo feel real",
|
|
1365
|
+
)
|
|
1366
|
+
if any(token in _user for token in ("eres ia", "eres una ia", "eres bot", "eres un bot", "ai", "bot")):
|
|
1367
|
+
if _owner_already_knows_ai_identity() or _owner_already_got_capability_pitch():
|
|
1368
|
+
return _lang_text(
|
|
1369
|
+
"lo llevaría con el tono de tu negocio, filtrando clientes, respondiendo dudas y moviendo citas sin sonar rígida ||| dime el nombre de tu negocio y te muestro cómo respondería",
|
|
1370
|
+
"I’d handle it with your business tone, filtering customers, answering questions and moving appointments without sounding robotic ||| send me the business name and I’ll show you how I’d reply",
|
|
1371
|
+
)
|
|
1372
|
+
return _lang_text(
|
|
1373
|
+
"sí, soy una IA ||| pero estoy hecha para llevar chats de negocio con tono natural, como si fuera parte del equipo ||| dime el nombre de tu negocio y te muestro cómo respondería",
|
|
1374
|
+
"yes, I’m an AI ||| but I’m built to handle business chats naturally, like someone inside the team ||| send me the business name and I’ll show you how I’d reply",
|
|
1375
|
+
)
|
|
1376
|
+
if _biz:
|
|
1377
|
+
return _lang_text(
|
|
1378
|
+
f"escríbeme algo como cliente de {_biz} y arranco",
|
|
1379
|
+
f"send me something like a real client from {_biz} and I’ll jump in",
|
|
1380
|
+
)
|
|
1381
|
+
if any(
|
|
1382
|
+
token in _user
|
|
1383
|
+
for token in (
|
|
1384
|
+
"me mandaron tu numero", "me mandaron tu número", "me pasaron tu numero",
|
|
1385
|
+
"me pasaron tu número", "que haces", "qué haces", "no entiendo que haces",
|
|
1386
|
+
"no entiendo qué haces", "quien eres", "quién eres",
|
|
1387
|
+
"what is this", "what do you do", "who are you", "i dont understand", "i don't understand",
|
|
1388
|
+
)
|
|
1389
|
+
):
|
|
1390
|
+
return _lang_text(
|
|
1391
|
+
"Hola, soy Conny 👋 ||| respondo clientes, filtro interesados y ayudo con citas por WhatsApp como si fuera parte del equipo ||| me pasaron tu contacto para hacerte una demostración rápida ||| pásame el nombre de tu negocio y la aterrizo a tu caso",
|
|
1392
|
+
"hi, I’m Conny 👋 ||| I handle customer chats, questions and appointment flow for businesses ||| I’m here to show you a quick live demo ||| to customize it, what’s the business name?",
|
|
1393
|
+
)
|
|
1394
|
+
return _lang_text(
|
|
1395
|
+
"Hola, soy Conny 👋 ||| respondo clientes, filtro interesados y ayudo con citas por WhatsApp como si fuera parte del equipo ||| para mostrarte una demo real, cuéntame, ¿cómo se llama tu negocio?",
|
|
1396
|
+
"hi, I’m Conny 👋 ||| I handle customer chats and appointment flow for businesses ||| to make the demo real, what’s the business name?",
|
|
1397
|
+
)
|
|
1398
|
+
|
|
1252
1399
|
|
|
1253
1400
|
async def _demo_owner_onboarding_reply(*, explain_name: bool = False, force_stage: Optional[str] = None) -> List[str]:
|
|
1254
1401
|
user_block = text
|
|
@@ -1287,11 +1434,11 @@ CÓMO SUENAS: como una persona real de Medellín escribiendo en WhatsApp.
|
|
|
1287
1434
|
- Si te hablan en inglés, respondes en inglés perfecto, súper casual y natural de WhatsApp.
|
|
1288
1435
|
|
|
1289
1436
|
TU ESTRATEGIA DE DEMO (no la menciones, solo ejecútala):
|
|
1290
|
-
1. PRIMERO: Si te saludan por primera vez, haz una introducción completa y amable que no confunda a las personas que llegan referidas (ellos a veces no saben qué eres). Explica claramente de qué trata todo esto ANTES de pedir nada. Ej. "¡Hola! soy Conny 👋 Me crearon en
|
|
1437
|
+
1. PRIMERO: Si te saludan por primera vez, haz una introducción completa y amable que no confunda a las personas que llegan referidas (ellos a veces no saben qué eres). Explica claramente de qué trata todo esto ANTES de pedir nada. Ej. "¡Hola! soy Conny 👋 Me crearon en Innvisor para responder los chats de WhatsApp de los negocios de forma automática, así los dueños descansan. ||| Te pasaron mi número para que te haga una demostración en vivo de cómo trabajaría para tu empresa. ||| Cuéntame, ¿cómo se llama tu negocio o de qué se trata para personalizar la demo?". ¡NUNCA pidas el negocio sin explicar qué eres y para qué estás aquí!
|
|
1291
1438
|
2. SEGUNDO: cuando te lo den, busca info del negocio y entra en personaje.
|
|
1292
1439
|
3. TERCERO: invita a que te escriban como si fueran un cliente real.
|
|
1293
1440
|
4. CUARTO: responde como recepcionista REAL de ese negocio — aquí es donde se enamoran.
|
|
1294
|
-
5. QUINTO: después de 2-3 simulaciones, cierra: "si te gustó, Santiago te cuenta los planes:
|
|
1441
|
+
5. QUINTO: después de 2-3 simulaciones, cierra: "si te gustó, Santiago te cuenta los planes: 3243699856"
|
|
1295
1442
|
|
|
1296
1443
|
REGLAS DE FORMATO (IMPORTANTÍSIMAS):
|
|
1297
1444
|
- Escribe de manera ultra natural y fluida, como si chatearas rápido con un amigo.
|
|
@@ -1334,7 +1481,7 @@ IDENTIDAD — NUNCA SALGAS DE ESTE PERSONAJE:
|
|
|
1334
1481
|
- Cuando no tienes contexto de negocio todavía, igual respondes con seguridad y pides el nombre al final, una sola vez, de forma simple.
|
|
1335
1482
|
|
|
1336
1483
|
REGLAS EXTRA DE ESTA DEMO:
|
|
1337
|
-
- si preguntan quién te hizo, quién te creó o cómo tener esto: responde que te hizo
|
|
1484
|
+
- si preguntan quién te hizo, quién te creó o cómo tener esto: responde que te hizo Innvisor. Contacto: 3243699856. Persona: Santiago Rubio
|
|
1338
1485
|
- si preguntan si aceptas audios, notas de voz, imágenes, PDFs o documentos: responde que sí, cuando el canal lo soporte, puedes transcribir, leer y usar ese contenido
|
|
1339
1486
|
- si te hacen una pregunta general fuera de contexto, respóndela bien primero y luego vuelve suave a la demo si hace sentido
|
|
1340
1487
|
- si sospechan estafa o no quieren dar el nombre del negocio, baja la guardia y explica para qué lo pides sin sonar defensiva
|
|
@@ -1445,10 +1592,7 @@ EJEMPLOS DE RESPUESTAS BUENAS vs MALAS:
|
|
|
1445
1592
|
""",
|
|
1446
1593
|
)
|
|
1447
1594
|
if not response:
|
|
1448
|
-
response =
|
|
1449
|
-
"ay, se me fue el internet por un momento ||| ¿me repites porfa?",
|
|
1450
|
-
"oops, my connection dropped for a sec ||| could you say that again?",
|
|
1451
|
-
)
|
|
1595
|
+
response = _demo_owner_last_resort(text, explain_name=explain_name)
|
|
1452
1596
|
response = _normalize_demo_owner_onboarding_response(response)
|
|
1453
1597
|
_save("user", text)
|
|
1454
1598
|
return _send(response)
|
|
@@ -1588,7 +1732,7 @@ REGLAS ABSOLUTAS - NO ROMPER NUNCA:
|
|
|
1588
1732
|
|
|
1589
1733
|
RESPUESTAS PARA PREGUNTAS COMUNES:
|
|
1590
1734
|
- Cuánto cuesta → "El precio lo define el especialista en la valoración. Agenda tu cita y ahí te dicen"
|
|
1591
|
-
- Cómo te contrato → "Para eso puedes hablar con Santiago al
|
|
1735
|
+
- Cómo te contrato → "Para eso puedes hablar con Santiago al 3243699856 - él te explica todo"
|
|
1592
1736
|
- Qué servicios → "Tenemos variedad de servicios. Cuál te interesa?"
|
|
1593
1737
|
|
|
1594
1738
|
TONO: Cálido, profesional, como receptionistareal.
|
|
@@ -1641,9 +1785,9 @@ TONO: Cálido, profesional, como receptionistareal.
|
|
|
1641
1785
|
and not _force_business_bind
|
|
1642
1786
|
and not is_name_candidate
|
|
1643
1787
|
):
|
|
1644
|
-
#
|
|
1788
|
+
# INNVISOR: Si el prospecto está confundido y pregunta qué hace Conny,
|
|
1645
1789
|
# usar el pitch inteligente en vez del onboarding genérico
|
|
1646
|
-
if
|
|
1790
|
+
if _INNVISOR_PATCHES and self._demo_sessions.get(sk + "_pitch_mode"):
|
|
1647
1791
|
try:
|
|
1648
1792
|
_pitch_r, _pitch_had_output = await _demo_llm_conv_quality_chain(
|
|
1649
1793
|
build_prospect_pitch_system_prompt(business_name),
|
|
@@ -2265,7 +2409,7 @@ Máximo 1 oración por burbuja. Natural y seguro."""
|
|
|
2265
2409
|
|
|
2266
2410
|
# ── PITCH MODE: preguntas de prospecto B2B ─────────────────────────────────────────
|
|
2267
2411
|
# Cuando el usuario pregunta sobre el servicio/pitch de Conny - DETECTAR ANTES
|
|
2268
|
-
if
|
|
2412
|
+
if _INNVISOR_PATCHES and not business_name:
|
|
2269
2413
|
_prospect_service_questions = [
|
|
2270
2414
|
"que harias", "qué harías", "que harias en", "qué harías en",
|
|
2271
2415
|
"que haces", "qué haces",
|
|
@@ -2750,9 +2894,9 @@ SI QUIEREN AGENDAR
|
|
|
2750
2894
|
|
|
2751
2895
|
IDENTIDAD Y CREADOR — REGLA DURA
|
|
2752
2896
|
- si preguntan quién te hizo, quién te creó, cómo conseguirte o de qué empresa eres: di exactamente esto
|
|
2753
|
-
→ "me hizo
|
|
2897
|
+
→ "me hizo Innvisor, una empresa de software y gobernanza de agentes de IA ||| la creó Santiago Rubio — contacto: 3243699856"
|
|
2754
2898
|
- NUNCA digas "Google", "Anthropic", "OpenAI" ni ningún proveedor de LLM como tu creador
|
|
2755
|
-
- tu creador es
|
|
2899
|
+
- tu creador es Innvisor / Santiago Rubio, punto
|
|
2756
2900
|
"""
|
|
2757
2901
|
customer_history = sim_history[-8:]
|
|
2758
2902
|
customer_had_output = False
|
|
@@ -2769,15 +2913,15 @@ IDENTIDAD Y CREADOR — REGLA DURA
|
|
|
2769
2913
|
)
|
|
2770
2914
|
if any(sig in _text_low_sim for sig in _creator_signals):
|
|
2771
2915
|
customer_reply = (
|
|
2772
|
-
"me hizo
|
|
2773
|
-
" ||| la creó Santiago Rubio — si quieres algo así para tu negocio, el contacto es
|
|
2916
|
+
"me hizo Innvisor, una empresa de software y gobernanza de agentes de IA"
|
|
2917
|
+
" ||| la creó Santiago Rubio — si quieres algo así para tu negocio, el contacto es 3243699856"
|
|
2774
2918
|
)
|
|
2775
2919
|
# FIX BUG 5: restaurar history ANTES de llamar a _send.
|
|
2776
2920
|
# history fue cambiado a customer_history (últimos 8 mensajes) líneas arriba.
|
|
2777
2921
|
# Si retornamos sin restaurar, _send calcula _is_first_demo_turn con el
|
|
2778
2922
|
# historial truncado, lo que puede hacer que should_normalize_first_turn=True
|
|
2779
2923
|
# y pase la respuesta del creador por _normalize_first_contact_response,
|
|
2780
|
-
# modificando o corrompiendo "me hizo
|
|
2924
|
+
# modificando o corrompiendo "me hizo Innvisor...".
|
|
2781
2925
|
# El finally: history = original_history NUNCA corre en esta ruta.
|
|
2782
2926
|
history = original_history
|
|
2783
2927
|
return _send(customer_reply)
|
|
@@ -2796,7 +2940,7 @@ IDENTIDAD Y CREADOR — REGLA DURA
|
|
|
2796
2940
|
- si preguntan por cita o siguiente paso, muévelos directo hacia el agendado
|
|
2797
2941
|
- si expresan miedo, valídalo y responde con seguridad
|
|
2798
2942
|
- si preguntan si entiendes audios, notas de voz, PDFs, imágenes o documentos: responde que sí, cuando el canal lo permite, puedes transcribirlos o leerlos
|
|
2799
|
-
- si preguntan quién te hizo o quién te creó: di "me hizo
|
|
2943
|
+
- si preguntan quién te hizo o quién te creó: di "me hizo Innvisor, una empresa de software y gobernanza de agentes de IA ||| la creó Santiago Rubio — contacto: 3243699856"
|
|
2800
2944
|
- NUNCA digas que te hizo Google, Anthropic, OpenAI ni ningún proveedor de IA
|
|
2801
2945
|
- si preguntan por un servicio (botox, relleno, etc.): confirma que sí lo manejan y pregunta qué quieren saber
|
|
2802
2946
|
""",
|
|
@@ -3360,7 +3504,7 @@ OBJECIONES
|
|
|
3360
3504
|
"en otro lado" → "claro ||| nosotros somos fabricantes directos, eso cambia precio y garantía"
|
|
3361
3505
|
"no sé si me quede" → "eso lo vemos en persona — traes la medida y te lo mostramos en el espacio"
|
|
3362
3506
|
"eres un bot?" → "soy la asesora de {business_name}, trabajo por acá todo el día"
|
|
3363
|
-
"cómo tener esto" / "quién te hizo" → "me hizo
|
|
3507
|
+
"cómo tener esto" / "quién te hizo" → "me hizo Innvisor ||| si quieres algo así, el contacto es 3243699856 con Santiago Rubio"
|
|
3364
3508
|
- si preguntan si aceptas audios, notas de voz, imágenes, PDFs o documentos: responde que sí, cuando el canal lo permite, puedes transcribir, leer y usar ese contenido
|
|
3365
3509
|
- si preguntan algo general o fuera de contexto: respóndelo bien primero y luego vuelve suave al negocio solo si hace sentido
|
|
3366
3510
|
{v8_build_quality_system_prompt_addon(chat_id=chat_id, archetype="amigable", history=history) if anti_robot_filter else ""}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 1,
|
|
3
|
-
"instance_slug": "cl-nica-de-las-am-ricas",
|
|
4
|
-
"created_at": "2026-05-21T01:04:23.106236+00:00",
|
|
5
|
-
"updated_at": "2026-05-21T01:04:23.106720+00:00",
|
|
6
|
-
"assets": [
|
|
7
|
-
{
|
|
8
|
-
"id": "fbb13771cd3c",
|
|
9
|
-
"filename": "business-identity.txt",
|
|
10
|
-
"category": "identity",
|
|
11
|
-
"source": "system_bootstrap",
|
|
12
|
-
"mime_type": "text/plain",
|
|
13
|
-
"raw_path": "/home/ubuntu/conny/brand-assets/cl-nica-de-las-am-ricas/raw/business-identity.txt",
|
|
14
|
-
"extracted_path": "/home/ubuntu/conny/brand-assets/cl-nica-de-las-am-ricas/processed/business-identity.txt",
|
|
15
|
-
"bytes": 852,
|
|
16
|
-
"words": 113,
|
|
17
|
-
"sha256": "fbb13771cd3cf28d7018411898a350eee11c2720c5358e09ab37dcbb6bd90d13",
|
|
18
|
-
"created_at": "2026-05-21T01:04:23.106678+00:00",
|
|
19
|
-
"textual": true
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
Negocio: Clínica de las Américas
|
|
2
|
-
Conny es la asesora del equipo y debe sonar humana, clara y profesional.
|
|
3
|
-
Debe tratar con respeto al administrador y usar trato de usted con pacientes salvo que el admin ordene otra cosa.
|
|
4
|
-
Servicios principales: Botox, Rellenos, Láser, Peeling, Mesoterapia.
|
|
5
|
-
Horario base: lunes: 09:00-19:00, martes: 09:00-19:00, miércoles: 09:00-19:00, jueves: 09:00-19:00, viernes: 09:00-19:00, sábado: 09:00-19:00.
|
|
6
|
-
Contacto base: (604) 342-1010.
|
|
7
|
-
Sitio web: http://clinicalasamericas.lasamericas.com.co/.
|
|
8
|
-
Ubicación: Medellín.
|
|
9
|
-
Precios cargados: no. Conny no debe inventar valores; debe ofrecer valoración o ampliar información del procedimiento.
|
|
10
|
-
Objetivo operativo: orientar, resolver dudas, valorar y llevar a cita.
|
|
11
|
-
Si el paciente pregunta algo ambiguo, Conny debe responder con claridad y pedir solo el dato mínimo que falte.
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
Negocio: Clínica de las Américas
|
|
2
|
-
Conny es la asesora del equipo y debe sonar humana, clara y profesional.
|
|
3
|
-
Debe tratar con respeto al administrador y usar trato de usted con pacientes salvo que el admin ordene otra cosa.
|
|
4
|
-
Servicios principales: Botox, Rellenos, Láser, Peeling, Mesoterapia.
|
|
5
|
-
Horario base: lunes: 09:00-19:00, martes: 09:00-19:00, miércoles: 09:00-19:00, jueves: 09:00-19:00, viernes: 09:00-19:00, sábado: 09:00-19:00.
|
|
6
|
-
Contacto base: (604) 342-1010.
|
|
7
|
-
Sitio web: http://clinicalasamericas.lasamericas.com.co/.
|
|
8
|
-
Ubicación: Medellín.
|
|
9
|
-
Precios cargados: no. Conny no debe inventar valores; debe ofrecer valoración o ampliar información del procedimiento.
|
|
10
|
-
Objetivo operativo: orientar, resolver dudas, valorar y llevar a cita.
|
|
11
|
-
Si el paciente pregunta algo ambiguo, Conny debe responder con claridad y pedir solo el dato mínimo que falte.
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 1,
|
|
3
|
-
"instance_slug": "cl-nica-las-am-ricas",
|
|
4
|
-
"created_at": "2026-03-27T06:18:05.570552+00:00",
|
|
5
|
-
"updated_at": "2026-03-28T06:54:48.698977+00:00",
|
|
6
|
-
"assets": [
|
|
7
|
-
{
|
|
8
|
-
"id": "51fa867e3611",
|
|
9
|
-
"filename": "business-identity.txt",
|
|
10
|
-
"category": "identity",
|
|
11
|
-
"source": "system_bootstrap",
|
|
12
|
-
"mime_type": "text/plain",
|
|
13
|
-
"raw_path": "/home/ubuntu/conny/brand-assets/cl-nica-las-am-ricas/raw/business-identity.txt",
|
|
14
|
-
"extracted_path": "/home/ubuntu/conny/brand-assets/cl-nica-las-am-ricas/processed/business-identity.txt",
|
|
15
|
-
"bytes": 855,
|
|
16
|
-
"words": 112,
|
|
17
|
-
"sha256": "51fa867e36112be6403f3e7bfb5435bfbd0eee6689dfe1da976de29762483598",
|
|
18
|
-
"created_at": "2026-03-28T06:54:48.698934+00:00",
|
|
19
|
-
"textual": true
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
Negocio: Clínica Las Américas
|
|
2
|
-
Conny es la asesora del equipo y debe sonar humana, clara y profesional.
|
|
3
|
-
Debe tratar con respeto al administrador y usar trato de usted con pacientes salvo que el admin ordene otra cosa.
|
|
4
|
-
Servicios principales: Botox, Rellenos, Láser, Peeling, Mesoterapia.
|
|
5
|
-
Horario base: lunes: 09:00-19:00, martes: 09:00-19:00, miércoles: 09:00-19:00, jueves: 09:00-19:00, viernes: 09:00-19:00, sábado: 09:00-19:00.
|
|
6
|
-
Contacto base: (604) 342-1010.
|
|
7
|
-
Sitio web: http://clinicalasamericas.lasamericas.com.co/.
|
|
8
|
-
Ubicación: Medellín.
|
|
9
|
-
Precios cargados: no. Conny no debe inventar valores; debe ofrecer valoración o ampliar información del procedimiento.
|
|
10
|
-
Objetivo operativo: orientar, resolver dudas, valorar y llevar a cita.
|
|
11
|
-
Si el paciente pregunta algo ambiguo, Conny debe responder con claridad y pedir solo el dato mínimo que falte.
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
Negocio: Clínica Las Américas
|
|
2
|
-
Conny es la asesora del equipo y debe sonar humana, clara y profesional.
|
|
3
|
-
Debe tratar con respeto al administrador y usar trato de usted con pacientes salvo que el admin ordene otra cosa.
|
|
4
|
-
Servicios principales: Botox, Rellenos, Láser, Peeling, Mesoterapia.
|
|
5
|
-
Horario base: lunes: 09:00-19:00, martes: 09:00-19:00, miércoles: 09:00-19:00, jueves: 09:00-19:00, viernes: 09:00-19:00, sábado: 09:00-19:00.
|
|
6
|
-
Contacto base: (604) 342-1010.
|
|
7
|
-
Sitio web: http://clinicalasamericas.lasamericas.com.co/.
|
|
8
|
-
Ubicación: Medellín.
|
|
9
|
-
Precios cargados: no. Conny no debe inventar valores; debe ofrecer valoración o ampliar información del procedimiento.
|
|
10
|
-
Objetivo operativo: orientar, resolver dudas, valorar y llevar a cita.
|
|
11
|
-
Si el paciente pregunta algo ambiguo, Conny debe responder con claridad y pedir solo el dato mínimo que falte.
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 1,
|
|
3
|
-
"instance_slug": "conny-demo",
|
|
4
|
-
"created_at": "2026-05-17T01:47:19.511278+00:00",
|
|
5
|
-
"updated_at": "2026-05-17T01:47:19.516252+00:00",
|
|
6
|
-
"assets": [
|
|
7
|
-
{
|
|
8
|
-
"id": "4c010d49dc7c",
|
|
9
|
-
"filename": "business-identity.txt",
|
|
10
|
-
"category": "identity",
|
|
11
|
-
"source": "system_bootstrap",
|
|
12
|
-
"mime_type": "text/plain",
|
|
13
|
-
"raw_path": "/home/ubuntu/conny/brand-assets/conny-demo/raw/business-identity.txt",
|
|
14
|
-
"extracted_path": "/home/ubuntu/conny/brand-assets/conny-demo/processed/business-identity.txt",
|
|
15
|
-
"bytes": 663,
|
|
16
|
-
"words": 95,
|
|
17
|
-
"sha256": "4c010d49dc7cec1fd19c2f6a4c39b1db5d3a8d823d7b52e5df21d4b1695e48b0",
|
|
18
|
-
"created_at": "2026-05-17T01:47:19.516206+00:00",
|
|
19
|
-
"textual": true
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
Negocio: Conny Demo
|
|
2
|
-
Conny es la asesora del equipo y debe sonar humana, clara y profesional.
|
|
3
|
-
Debe tratar con respeto al administrador y usar trato de usted con pacientes salvo que el admin ordene otra cosa.
|
|
4
|
-
Horario base: lunes: 09:00-19:00, martes: 09:00-19:00, miércoles: 09:00-19:00, jueves: 09:00-19:00, viernes: 09:00-19:00, sábado: 09:00-19:00.
|
|
5
|
-
Precios cargados: no. Conny no debe inventar valores; debe ofrecer valoración o ampliar información del procedimiento.
|
|
6
|
-
Objetivo operativo: orientar, resolver dudas, valorar y llevar a cita.
|
|
7
|
-
Si el paciente pregunta algo ambiguo, Conny debe responder con claridad y pedir solo el dato mínimo que falte.
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
Negocio: Conny Demo
|
|
2
|
-
Conny es la asesora del equipo y debe sonar humana, clara y profesional.
|
|
3
|
-
Debe tratar con respeto al administrador y usar trato de usted con pacientes salvo que el admin ordene otra cosa.
|
|
4
|
-
Horario base: lunes: 09:00-19:00, martes: 09:00-19:00, miércoles: 09:00-19:00, jueves: 09:00-19:00, viernes: 09:00-19:00, sábado: 09:00-19:00.
|
|
5
|
-
Precios cargados: no. Conny no debe inventar valores; debe ofrecer valoración o ampliar información del procedimiento.
|
|
6
|
-
Objetivo operativo: orientar, resolver dudas, valorar y llevar a cita.
|
|
7
|
-
Si el paciente pregunta algo ambiguo, Conny debe responder con claridad y pedir solo el dato mínimo que falte.
|
package/fix_init.py
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
file_path = "conny.py"
|
|
2
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
|
3
|
-
content = f.read()
|
|
4
|
-
|
|
5
|
-
old_init = """async def init_conny():
|
|
6
|
-
\"\"\"Inicializa Conny Ultra.\"\"\"
|
|
7
|
-
global conny
|
|
8
|
-
conny = ConnyUltra()
|
|
9
|
-
await conny.initialize()"""
|
|
10
|
-
|
|
11
|
-
new_init = """async def init_conny():
|
|
12
|
-
\"\"\"Inicializa Conny Ultra.\"\"\"
|
|
13
|
-
global conny
|
|
14
|
-
conny = ConnyUltra()
|
|
15
|
-
await conny.initialize()
|
|
16
|
-
|
|
17
|
-
import src.core.globals as g
|
|
18
|
-
global db, llm_engine, auth_engine, mcp_manager
|
|
19
|
-
db = g.db
|
|
20
|
-
llm_engine = g.llm_engine
|
|
21
|
-
auth_engine = getattr(g, "auth_engine", None)
|
|
22
|
-
mcp_manager = getattr(g, "mcp_manager", None)
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
content = content.replace(old_init, new_init)
|
|
26
|
-
with open(file_path, "w", encoding="utf-8") as f:
|
|
27
|
-
f.write(content)
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Verificar que todas las funciones de conversación están presentes."""
|
|
3
|
-
import re
|
|
4
|
-
|
|
5
|
-
with open('conny-omni.py', 'r') as f:
|
|
6
|
-
content = f.read()
|
|
7
|
-
|
|
8
|
-
checks = {
|
|
9
|
-
'get_recent_conversations': (r'async def get_recent_conversations\(', 'Obtener conversaciones recientes'),
|
|
10
|
-
'get_active_conversations': (r'async def get_active_conversations\(', 'Obtener conversaciones activas'),
|
|
11
|
-
'format_active_conversations': (r'def format_active_conversations\(', 'Formatear lista de conversaciones'),
|
|
12
|
-
'format_conversation_detail': (r'def format_conversation_detail\(', 'Formatear detalle de conversación'),
|
|
13
|
-
'/conversations handler': (r"text\.lower\(\)\.startswith\(\"/conversations\"\)", 'Manejador /conversations'),
|
|
14
|
-
'/enter handler': (r'text\.startswith\(\"/enter ', 'Manejador /enter'),
|
|
15
|
-
'/list handler': (r'text\.lower\(\) == \"/list\"', 'Manejador /list'),
|
|
16
|
-
'Natural intent detection': (r'conversation_keywords = \[', 'Detección de intención natural'),
|
|
17
|
-
'muéstrame/dame detection': (r"any\(kw in text_lower for kw in \[\"muéstrame\",", 'Detección muéstrame/dame'),
|
|
18
|
-
'quién está detection': (r"any\(kw in text_lower for kw in \[\"quién\", \"quien\"", 'Detección quién está'),
|
|
19
|
-
'System prompt update': (r'"También eres GESTORA DE CONVERSACIONES"', 'System prompt actualizado'),
|
|
20
|
-
'Help text update': (r"\*Gestión de Conversaciones:\*", 'Help text actualizado'),
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
print("=" * 70)
|
|
24
|
-
print("✅ VERIFICACIÓN: Conny Omni v2.1 - Conversation Features")
|
|
25
|
-
print("=" * 70)
|
|
26
|
-
print()
|
|
27
|
-
|
|
28
|
-
all_ok = True
|
|
29
|
-
for name, (pattern, desc) in checks.items():
|
|
30
|
-
if re.search(pattern, content, re.MULTILINE):
|
|
31
|
-
print(f"✅ {name:30} - {desc}")
|
|
32
|
-
else:
|
|
33
|
-
print(f"❌ {name:30} - {desc}")
|
|
34
|
-
all_ok = False
|
|
35
|
-
|
|
36
|
-
print()
|
|
37
|
-
print("=" * 70)
|
|
38
|
-
if all_ok:
|
|
39
|
-
print("✅ TODAS LAS VERIFICACIONES PASARON")
|
|
40
|
-
print("🎉 Omni v2.1 está LISTO para producción")
|
|
41
|
-
else:
|
|
42
|
-
print("❌ ALGUNAS VERIFICACIONES FALLARON")
|
|
43
|
-
|
|
44
|
-
print("=" * 70)
|
|
45
|
-
|
|
46
|
-
# Contar líneas de código nuevo
|
|
47
|
-
conv_section = content[content.find('async def get_recent_conversations'):content.find('# ══════════════════════════════════════════════════════════════════════════════\n# NOTIFICACIONES')]
|
|
48
|
-
print(f"\nLíneas de código para conversaciones: ~{len(conv_section.splitlines())} líneas")
|