@innvisor/conny-ai 9.7.0
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/.env.example +68 -0
- package/CHANGELOG.md +54 -0
- package/LICENSE +21 -0
- package/README.md +369 -0
- package/brand-assets/A_dark_luxury_web_background_202605210700.jpeg +0 -0
- package/brand-assets/Conny.web.logo.png +0 -0
- package/brand-assets/Logo_Conny_Petalo_Claro.png +0 -0
- package/brand-assets/cl-nica-de-las-am-ricas/manifest.json +22 -0
- package/brand-assets/cl-nica-de-las-am-ricas/processed/business-identity.txt +11 -0
- package/brand-assets/cl-nica-de-las-am-ricas/raw/business-identity.txt +11 -0
- package/brand-assets/cl-nica-las-am-ricas/manifest.json +22 -0
- package/brand-assets/cl-nica-las-am-ricas/processed/business-identity.txt +11 -0
- package/brand-assets/cl-nica-las-am-ricas/raw/business-identity.txt +11 -0
- package/brand-assets/conny-demo/manifest.json +22 -0
- package/brand-assets/conny-demo/processed/business-identity.txt +7 -0
- package/brand-assets/conny-demo/raw/business-identity.txt +7 -0
- package/brand-assets/conny-logo.png +0 -0
- package/brand-assets/web.background.png +0 -0
- package/brand_assets.py +323 -0
- package/conny +28 -0
- package/conny-chat.py +579 -0
- package/conny-omni.py +3843 -0
- package/conny.py +113 -0
- package/conny_agents/__init__.py +1 -0
- package/conny_agents/agenda.py +1 -0
- package/conny_agents/captacion.py +1 -0
- package/conny_agents/conocimiento.py +1 -0
- package/conny_agents/escalacion.py +1 -0
- package/conny_agents/objeciones.py +1 -0
- package/conny_agents/seguimiento.py +1 -0
- package/conny_app.py +287 -0
- package/conny_audio.py +350 -0
- package/conny_audio_learn.py +84 -0
- package/conny_brain_v10.py +804 -0
- package/conny_bridge.py +656 -0
- package/conny_calendar.py +169 -0
- package/conny_cli.py +11784 -0
- package/conny_cli_bb.py +437 -0
- package/conny_commands.py +243 -0
- package/conny_config.py +215 -0
- package/conny_core/__init__.py +3 -0
- package/conny_core/conversation_engine.py +446 -0
- package/conny_core/first_turn_ops.py +287 -0
- package/conny_core/persona_registry.py +157 -0
- package/conny_core/prompt_ops.py +561 -0
- package/conny_cron.py +72 -0
- package/conny_demo_v2.py +209 -0
- package/conny_demo_voice.py +134 -0
- package/conny_design.py +43 -0
- package/conny_doctor.py +319 -0
- package/conny_domino.py +696 -0
- package/conny_generator.py +447 -0
- package/conny_google_auth.py +159 -0
- package/conny_i18n.py +619 -0
- package/conny_init.py +509 -0
- package/conny_integrations/__init__.py +4 -0
- package/conny_integrations/llm.py +1 -0
- package/conny_integrations/vault.py +77 -0
- package/conny_integrations/whatsapp.py +1 -0
- package/conny_intelligence.py +65 -0
- package/conny_learning.py +154 -0
- package/conny_memory.py +243 -0
- package/conny_memory_engine.py +292 -0
- package/conny_nova_proxy.py +170 -0
- package/conny_nuke_robot_phrases.py +493 -0
- package/conny_pairing.py +253 -0
- package/conny_patch.py +291 -0
- package/conny_persona_cli.py +150 -0
- package/conny_router.py +308 -0
- package/conny_runtime_ops.py +271 -0
- package/conny_session.py +516 -0
- package/conny_skills/__init__.py +1 -0
- package/conny_skills/demo_mode.py +35 -0
- package/conny_skills/text_processing.py +1 -0
- package/conny_skills/tone_detection.py +1 -0
- package/conny_smart_features.py +333 -0
- package/conny_studio.py +161 -0
- package/conny_sync_fix.py +306 -0
- package/conny_tui.py +512 -0
- package/conny_tui_select.py +202 -0
- package/conny_ultra_config.py +411 -0
- package/conny_uncertainty.py +174 -0
- package/conny_utils.py +87 -0
- package/conny_voice.py +156 -0
- package/conny_voice_engine.py +124 -0
- package/conny_web_search.py +66 -0
- package/conny_weekly_report.py +85 -0
- package/conny_worm.py +88 -0
- package/core/__init__.py +25 -0
- package/ecosystem.config.js +24 -0
- package/fix_init.py +27 -0
- package/install.sh +78 -0
- package/knowledge_base.py +330 -0
- package/nova/rules/default.yaml +37 -0
- package/nova_bridge.py +509 -0
- package/npm/conny.js +471 -0
- package/package.json +102 -0
- package/personas/conny/base/default.yaml +35 -0
- package/personas/conny/base/estetica_whatsapp.yaml +36 -0
- package/requirements.txt +14 -0
- package/run.sh +47 -0
- package/search.py +465 -0
- package/smart_handoff.py +1150 -0
- package/src/__init__.py +0 -0
- package/src/conny/__init__.py +0 -0
- package/src/conny/admin/__init__.py +0 -0
- package/src/conny/admin/api.py +234 -0
- package/src/conny/admin/dashboard.py +772 -0
- package/src/conny/api/__init__.py +0 -0
- package/src/conny/api/routes.py +8851 -0
- package/src/conny/brain/__init__.py +15 -0
- package/src/conny/brain/engine.py +804 -0
- package/src/conny/brain/learning.py +154 -0
- package/src/conny/brain/memory.py +324 -0
- package/src/conny/brain/smart_features.py +333 -0
- package/src/conny/brain/uncertainty.py +167 -0
- package/src/conny/channels/__init__.py +0 -0
- package/src/conny/channels/audio.py +316 -0
- package/src/conny/channels/cli.py +11795 -0
- package/src/conny/channels/logo_art.py +11 -0
- package/src/conny/channels/voice.py +156 -0
- package/src/conny/core/__init__.py +0 -0
- package/src/conny/core/config.py +215 -0
- package/src/conny/core/cron.py +72 -0
- package/src/conny/core/messenger.py +563 -0
- package/src/conny/core/router.py +297 -0
- package/src/conny/core/session.py +312 -0
- package/src/conny/demo/__init__.py +0 -0
- package/src/conny/demo/handler.py +3110 -0
- package/src/conny/integrations/__init__.py +19 -0
- package/src/conny/integrations/calendar.py +169 -0
- package/src/conny/integrations/knowledge.py +312 -0
- package/src/conny/integrations/search.py +66 -0
- package/src/conny/personas/__init__.py +0 -0
- package/src/conny/personas/generator.py +447 -0
- package/src/conny/production/__init__.py +0 -0
- package/src/conny/production/domino.py +696 -0
- package/src/conny/production/guard.py +550 -0
- package/src/conny/production/handoff.py +1150 -0
- package/src/conny/production/monitor.py +353 -0
- package/src/conny/utils/__init__.py +2 -0
- package/src/conny/utils/helpers.py +75 -0
- package/src/conny/utils/i18n.py +619 -0
- package/src/core/admin_engines.py +772 -0
- package/src/core/globals.py +11845 -0
- package/src/core/orchestrator.py +273 -0
- package/src/core/production_monitor.py +353 -0
- package/src/core/runtime.py +5487 -0
- package/src/domain/onboarding_flow.py +230 -0
- package/src/domain/prompts/__init__.py +1 -0
- package/src/domain/prompts/prospect_pitch.py +282 -0
- package/src/domain/send_guard.py +636 -0
- package/src/domain/swarm/queen.py +96 -0
- package/src/infrastructure/llm_providers/engine.py +487 -0
- package/src/interfaces/mcp_server.py +73 -0
- package/src/interfaces/nova_bridge.py +58 -0
- package/src/interfaces/web/admin_api.py +1379 -0
- package/src/interfaces/web/app.py +9408 -0
- package/src/interfaces/web/demo_handler.py +3450 -0
- package/src/interfaces/web/static/generate_avatars.py +46 -0
- package/v7/__init__.py +46 -0
- package/v7/agents/__init__.py +46 -0
- package/v7/agents/agenda.py +77 -0
- package/v7/agents/base.py +216 -0
- package/v7/agents/captacion.py +60 -0
- package/v7/agents/conocimiento.py +69 -0
- package/v7/agents/escalacion.py +83 -0
- package/v7/agents/objeciones.py +109 -0
- package/v7/agents/seguimiento.py +71 -0
- package/v7/memory/__init__.py +46 -0
- package/v7/memory/patient_profile.py +200 -0
- package/v7/orchestrator.py +275 -0
- package/v7/postprocess.py +127 -0
- package/v7/router.py +239 -0
- package/verify_conversation_impl.py +48 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LOGO_ART_LINES = [
|
|
2
|
+
' ',
|
|
3
|
+
' \x1b[48;2;251;6;114m \x1b[0m\x1b[38;2;250;4;114m\x1b[48;2;255;4;133m▀\x1b[0m\x1b[38;2;251;4;135m\x1b[48;2;231;3;140m▀\x1b[0m\x1b[38;2;226;3;139m\x1b[48;2;233;4;168m▀\x1b[0m\x1b[48;2;202;3;162m \x1b[0m ',
|
|
4
|
+
' \x1b[48;2;250;4;119m \x1b[0m\x1b[48;2;253;3;123m \x1b[0m\x1b[48;2;252;5;115m \x1b[0m\x1b[48;2;253;5;119m \x1b[0m \x1b[38;2;239;4;127m\x1b[48;2;220;3;137m▀\x1b[0m\x1b[38;2;247;4;146m\x1b[48;2;229;3;158m▀\x1b[0m\x1b[38;2;213;3;153m\x1b[48;2;198;3;162m▀\x1b[0m\x1b[38;2;211;4;178m\x1b[48;2;192;4;182m▀\x1b[0m\x1b[38;2;184;4;170m\x1b[48;2;172;3;175m▀\x1b[0m \x1b[48;2;175;3;183m \x1b[0m\x1b[48;2;165;4;189m \x1b[0m\x1b[48;2;150;6;199m \x1b[0m\x1b[48;2;134;5;198m \x1b[0m',
|
|
5
|
+
' \x1b[38;2;250;3;118m\x1b[48;2;250;6;117m▀\x1b[0m\x1b[38;2;255;4;117m\x1b[48;2;255;4;124m▀\x1b[0m\x1b[38;2;251;4;109m\x1b[48;2;241;3;122m▀\x1b[0m\x1b[38;2;255;3;137m\x1b[48;2;226;3;138m▀\x1b[0m\x1b[38;2;231;3;142m\x1b[48;2;231;3;164m▀\x1b[0m\x1b[48;2;200;4;159m \x1b[0m\x1b[38;2;200;3;161m\x1b[48;2;184;7;169m▀\x1b[0m\x1b[38;2;206;4;194m\x1b[48;2;196;5;198m▀\x1b[0m\x1b[38;2;170;4;181m\x1b[48;2;160;5;182m▀\x1b[0m\x1b[48;2;165;4;181m \x1b[0m\x1b[38;2;172;3;182m\x1b[48;2;172;5;203m▀\x1b[0m\x1b[38;2;177;5;204m\x1b[48;2;141;6;195m▀\x1b[0m\x1b[38;2;146;6;196m\x1b[48;2;129;7;204m▀\x1b[0m\x1b[38;2;133;7;203m\x1b[48;2;124;11;227m▀\x1b[0m\x1b[38;2;125;9;215m\x1b[48;2;106;11;211m▀\x1b[0m',
|
|
6
|
+
' \x1b[38;2;244;5;122m▀\x1b[0m\x1b[38;2;235;4;139m▀\x1b[0m\x1b[38;2;230;3;161m\x1b[48;2;208;6;161m▀\x1b[0m\x1b[38;2;221;3;180m\x1b[48;2;190;6;164m▀\x1b[0m\x1b[38;2;192;4;165m▀\x1b[0m \x1b[38;2;156;6;187m▀\x1b[0m\x1b[38;2;156;5;217m\x1b[48;2;131;8;198m▀\x1b[0m\x1b[38;2;136;7;218m\x1b[48;2;124;10;208m▀\x1b[0m\x1b[38;2;118;9;213m▀\x1b[0m\x1b[38;2;108;10;215m▀\x1b[0m ',
|
|
7
|
+
' \x1b[48;2;226;3;145m \x1b[0m\x1b[48;2;216;3;154m \x1b[0m\x1b[38;2;212;2;160m\x1b[48;2;213;3;173m▀\x1b[0m\x1b[38;2;193;5;166m\x1b[48;2;201;4;188m▀\x1b[0m\x1b[48;2;170;3;177m \x1b[0m \x1b[48;2;127;6;200m \x1b[0m\x1b[38;2;128;8;203m\x1b[48;2;135;8;225m▀\x1b[0m\x1b[38;2;120;9;215m\x1b[48;2;116;12;228m▀\x1b[0m\x1b[48;2;93;14;222m \x1b[0m\x1b[48;2;73;18;228m \x1b[0m ',
|
|
8
|
+
' \x1b[38;2;223;2;137m\x1b[48;2;213;3;147m▀\x1b[0m\x1b[38;2;237;3;163m\x1b[48;2;205;3;162m▀\x1b[0m\x1b[38;2;199;3;159m\x1b[48;2;187;3;167m▀\x1b[0m\x1b[38;2;184;3;168m\x1b[48;2;190;4;194m▀\x1b[0m\x1b[38;2;185;4;194m\x1b[48;2;166;4;183m▀\x1b[0m\x1b[38;2;163;4;179m▀\x1b[0m\x1b[38;2;155;5;185m\x1b[48;2;154;4;188m▀\x1b[0m\x1b[38;2;165;7;216m\x1b[48;2;157;6;219m▀\x1b[0m\x1b[38;2;133;7;194m\x1b[48;2;125;7;203m▀\x1b[0m\x1b[38;2;121;8;203m▀\x1b[0m\x1b[38;2;120;11;227m\x1b[48;2;95;12;215m▀\x1b[0m\x1b[38;2;91;13;214m\x1b[48;2;87;17;242m▀\x1b[0m\x1b[38;2;78;16;222m\x1b[48;2;64;19;229m▀\x1b[0m\x1b[38;2;67;22;249m\x1b[48;2;48;24;237m▀\x1b[0m\x1b[38;2;49;21;234m\x1b[48;2;35;27;248m▀\x1b[0m',
|
|
9
|
+
' \x1b[38;2;201;4;156m▀\x1b[0m\x1b[38;2;198;3;171m▀\x1b[0m\x1b[38;2;180;4;177m▀\x1b[0m\x1b[38;2;171;5;183m▀\x1b[0m \x1b[38;2;152;3;186m\x1b[48;2;141;4;190m▀\x1b[0m\x1b[38;2;150;5;200m\x1b[48;2;142;6;210m▀\x1b[0m\x1b[38;2;125;7;199m\x1b[48;2;114;9;207m▀\x1b[0m\x1b[38;2;115;11;218m\x1b[48;2;103;13;227m▀\x1b[0m\x1b[38;2;102;10;212m\x1b[48;2;88;13;215m▀\x1b[0m \x1b[38;2;71;19;229m▀\x1b[0m\x1b[38;2;52;22;238m▀\x1b[0m\x1b[38;2;34;27;246m▀\x1b[0m\x1b[38;2;25;29;243m▀\x1b[0m',
|
|
10
|
+
' \x1b[38;2;129;7;197m▀\x1b[0m\x1b[38;2;127;8;217m\x1b[48;2;105;9;207m▀\x1b[0m\x1b[38;2;101;11;211m\x1b[48;2;96;14;231m▀\x1b[0m\x1b[38;2;90;16;233m\x1b[48;2;75;16;218m▀\x1b[0m\x1b[38;2;74;17;217m▀\x1b[0m ',
|
|
11
|
+
]
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Humanization engine — post-processes LLM responses to eliminate robot patterns."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
import random
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Optional, Dict, List
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger("conny.voice")
|
|
10
|
+
|
|
11
|
+
# Patterns that MUST be removed/replaced from any response
|
|
12
|
+
ROBOT_PATTERNS = [
|
|
13
|
+
(re.compile(r"como (IA|inteligencia artificial|asistente virtual|bot)", re.I), ""),
|
|
14
|
+
(re.compile(r"no (tengo|tenemos) (la )?capacidad", re.I), "no manejo eso ahora mismo"),
|
|
15
|
+
(re.compile(r"está fuera de mi (alcance|capacidades)", re.I), "eso tendría que verificarlo"),
|
|
16
|
+
(re.compile(r"no (puedo|podemos) (procesar|entender)", re.I), "no tengo esa información ahora"),
|
|
17
|
+
(re.compile(r"^(Hola|Buenos días|Buenas tardes)[,!]?\s*(Soy|Me llamo)\s*Conny", re.I), ""),
|
|
18
|
+
(re.compile(r"¡?Por supuesto[,!]?\s*", re.I), ""),
|
|
19
|
+
(re.compile(r"¡?Claro que sí[,!]?\s*", re.I), ""),
|
|
20
|
+
(re.compile(r"¡?Con gusto[,!]?\s*", re.I), ""),
|
|
21
|
+
(re.compile(r"¡+", re.I), ""), # Remove excessive exclamation
|
|
22
|
+
(re.compile(r"^\s*¡\s*", re.I), ""),
|
|
23
|
+
(re.compile(r",?\s*tu\s+asistente\s+virtual[.,]?\s*", re.I), ". "),
|
|
24
|
+
(re.compile(r"(?:como|en mi rol de) asistente virtual", re.I), ""),
|
|
25
|
+
(re.compile(r"(?:soy\s+)?(?:tu\s+)?asistente\s+virtual", re.I), ""),
|
|
26
|
+
(re.compile(r"estoy aquí para (servirte|atenderte)", re.I), ""),
|
|
27
|
+
(re.compile(r"no dudes en", re.I), ""),
|
|
28
|
+
(re.compile(r"estaré encantad[ao] de", re.I), ""),
|
|
29
|
+
(re.compile(r"será un placer", re.I), ""),
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
# Natural fillers by sector
|
|
33
|
+
SECTOR_FILLERS = {
|
|
34
|
+
"estetica": ["con mucho gusto", "perfecto", "listo", "dale"],
|
|
35
|
+
"salud": ["con gusto", "perfecto", "entendido", "listo"],
|
|
36
|
+
"restaurante": ["dale", "perfecto", "listo", "va"],
|
|
37
|
+
"retail": ["dale", "listo", "perfecto", "genial"],
|
|
38
|
+
"default": ["con gusto", "perfecto", "listo", "entendido"],
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Thinking block to prepend to system prompts
|
|
42
|
+
THINKING_BLOCK_ES = """INSTRUCCIÓN INTERNA (no mostrar al usuario):
|
|
43
|
+
Antes de responder, piensa en silencio:
|
|
44
|
+
1. ¿Qué quiere realmente este usuario? (intención real)
|
|
45
|
+
2. ¿Qué información tengo disponible para responderle?
|
|
46
|
+
3. ¿Hay alguna ambigüedad? ¿Necesito pedir aclaración?
|
|
47
|
+
4. ¿Cómo lo diría una recepcionista colombiana real, cálida, natural?
|
|
48
|
+
5. ¿Mi respuesta suena robótica? Si sí, reescríbela.
|
|
49
|
+
|
|
50
|
+
SOLO después de pensar, escribe la respuesta al usuario.
|
|
51
|
+
NUNCA menciones este proceso. NUNCA uses frases como "como IA" o "no tengo la capacidad"."""
|
|
52
|
+
|
|
53
|
+
THINKING_BLOCK_EN = """INTERNAL INSTRUCTION (do not show to user):
|
|
54
|
+
Before responding, think silently:
|
|
55
|
+
1. What does this user actually want?
|
|
56
|
+
2. What information do I have to answer them?
|
|
57
|
+
3. Is there any ambiguity? Should I ask for clarification?
|
|
58
|
+
4. How would a real, warm receptionist say this?
|
|
59
|
+
5. Does my response sound robotic? If yes, rewrite it.
|
|
60
|
+
|
|
61
|
+
ONLY after thinking, write the response to the user.
|
|
62
|
+
NEVER mention this process."""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ConnyVoice:
|
|
66
|
+
"""Post-processes LLM output to sound human."""
|
|
67
|
+
|
|
68
|
+
def __init__(self, sector: str = "default", tone: str = "warm"):
|
|
69
|
+
self.sector = sector
|
|
70
|
+
self.tone = tone
|
|
71
|
+
self._fillers = SECTOR_FILLERS.get(sector, SECTOR_FILLERS["default"])
|
|
72
|
+
|
|
73
|
+
def humanize(self, text: str, persona_override: Optional[Dict] = None) -> str:
|
|
74
|
+
"""Main post-processing pipeline."""
|
|
75
|
+
if not text or not text.strip():
|
|
76
|
+
return text
|
|
77
|
+
|
|
78
|
+
result = text
|
|
79
|
+
|
|
80
|
+
# 1. Remove robot patterns
|
|
81
|
+
for pattern, replacement in ROBOT_PATTERNS:
|
|
82
|
+
result = pattern.sub(replacement, result)
|
|
83
|
+
|
|
84
|
+
# 2. Clean up whitespace artifacts
|
|
85
|
+
result = re.sub(r'\s{2,}', ' ', result)
|
|
86
|
+
result = re.sub(r'\n{3,}', '\n\n', result)
|
|
87
|
+
result = result.strip()
|
|
88
|
+
|
|
89
|
+
# 3. Limit exclamation marks (max 1 per response)
|
|
90
|
+
excl_count = result.count('!')
|
|
91
|
+
if excl_count > 1:
|
|
92
|
+
# Keep only the first one
|
|
93
|
+
parts = result.split('!')
|
|
94
|
+
result = parts[0] + '!' + ''.join(parts[1:])
|
|
95
|
+
|
|
96
|
+
# 4. Response must NOT start with bot name
|
|
97
|
+
result = re.sub(r'^Conny[,:.]?\s*', '', result, flags=re.I)
|
|
98
|
+
|
|
99
|
+
# 5. Capitalize first letter
|
|
100
|
+
if result and result[0].islower():
|
|
101
|
+
result = result[0].upper() + result[1:]
|
|
102
|
+
|
|
103
|
+
# 6. Apply persona overrides if present
|
|
104
|
+
if persona_override:
|
|
105
|
+
forbidden = persona_override.get("forbidden_topics", [])
|
|
106
|
+
for topic in forbidden:
|
|
107
|
+
if topic.lower() in result.lower():
|
|
108
|
+
result = re.sub(re.escape(topic), "[tema no disponible]", result, flags=re.I)
|
|
109
|
+
|
|
110
|
+
return result
|
|
111
|
+
|
|
112
|
+
def inject_thinking_block(self, system_prompt: str, lang: str = "es", thinking_budget: int = 0) -> str:
|
|
113
|
+
"""Prepend chain-of-thought instruction to system prompt."""
|
|
114
|
+
block = THINKING_BLOCK_ES if lang == "es" else THINKING_BLOCK_EN
|
|
115
|
+
return block + "\n\n" + system_prompt
|
|
116
|
+
|
|
117
|
+
def split_long_response(self, text: str, max_chars: int = 300) -> List[str]:
|
|
118
|
+
"""Split long responses into multiple WhatsApp-style messages."""
|
|
119
|
+
if len(text) <= max_chars:
|
|
120
|
+
return [text]
|
|
121
|
+
|
|
122
|
+
# Split on sentence boundaries
|
|
123
|
+
sentences = re.split(r'(?<=[.!?])\s+', text)
|
|
124
|
+
bubbles = []
|
|
125
|
+
current = ""
|
|
126
|
+
|
|
127
|
+
for sentence in sentences:
|
|
128
|
+
if len(current) + len(sentence) + 1 > max_chars and current:
|
|
129
|
+
bubbles.append(current.strip())
|
|
130
|
+
current = sentence
|
|
131
|
+
else:
|
|
132
|
+
current = (current + " " + sentence).strip() if current else sentence
|
|
133
|
+
|
|
134
|
+
if current:
|
|
135
|
+
bubbles.append(current.strip())
|
|
136
|
+
|
|
137
|
+
return bubbles if bubbles else [text]
|
|
138
|
+
|
|
139
|
+
def check_robot_patterns(self, text: str) -> List[str]:
|
|
140
|
+
"""Return list of robot patterns found (for testing/monitoring)."""
|
|
141
|
+
found = []
|
|
142
|
+
for pattern, _ in ROBOT_PATTERNS:
|
|
143
|
+
if pattern.search(text):
|
|
144
|
+
found.append(pattern.pattern)
|
|
145
|
+
return found
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def get_thinking_params(provider: str, thinking_budget: int = 0) -> Dict:
|
|
149
|
+
"""Get provider-specific thinking parameters."""
|
|
150
|
+
if provider == "claude" and thinking_budget > 0:
|
|
151
|
+
return {"thinking": {"type": "enabled", "budget_tokens": thinking_budget}}
|
|
152
|
+
return {}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# Default instance
|
|
156
|
+
voice = ConnyVoice()
|
|
File without changes
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
conny_config.py — All constants, templates and string literals from conny.py
|
|
4
|
+
Extracted for Phase 3 split.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, List, Set, Tuple
|
|
12
|
+
|
|
13
|
+
_CONNY_HOME = os.getenv("CONNY_HOME", str(Path.home() / ".conny"))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Config:
|
|
17
|
+
"""Configuración centralizada con validación."""
|
|
18
|
+
|
|
19
|
+
TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN", "")
|
|
20
|
+
GROQ_API_KEY = os.getenv("GROQ_API_KEY", "")
|
|
21
|
+
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "")
|
|
22
|
+
GEMINI_API_KEY_2 = os.getenv("GEMINI_API_KEY_2", "")
|
|
23
|
+
GEMINI_API_KEY_3 = os.getenv("GEMINI_API_KEY_3", "")
|
|
24
|
+
GEMINI_API_KEY_4 = os.getenv("GEMINI_API_KEY_4", "")
|
|
25
|
+
GEMINI_API_KEY_5 = os.getenv("GEMINI_API_KEY_5", "")
|
|
26
|
+
GEMINI_API_KEY_6 = os.getenv("GEMINI_API_KEY_6", "")
|
|
27
|
+
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "")
|
|
28
|
+
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
|
|
29
|
+
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY", "")
|
|
30
|
+
BRAVE_API_KEY = os.getenv("BRAVE_API_KEY", "")
|
|
31
|
+
APIFY_API_KEY = os.getenv("APIFY_API_KEY", "")
|
|
32
|
+
SERP_API_KEY = os.getenv("SERP_API_KEY", "")
|
|
33
|
+
CALENDLY_LINK = os.getenv("CALENDLY_LINK", "")
|
|
34
|
+
GCAL_ACCESS_TOKEN = os.getenv("GCAL_ACCESS_TOKEN", "")
|
|
35
|
+
GCAL_REFRESH_TOKEN = os.getenv("GCAL_REFRESH_TOKEN", "")
|
|
36
|
+
GCAL_CLIENT_ID = os.getenv("GCAL_CLIENT_ID", "")
|
|
37
|
+
GCAL_CLIENT_SECRET = os.getenv("GCAL_CLIENT_SECRET", "")
|
|
38
|
+
GCAL_CALENDAR_ID = os.getenv("GCAL_CALENDAR_ID", "primary")
|
|
39
|
+
META_APP_ID = os.getenv("META_APP_ID", "")
|
|
40
|
+
META_APP_SECRET = os.getenv("META_APP_SECRET", "")
|
|
41
|
+
NOVA_URL = os.getenv("NOVA_URL", "http://localhost:9003")
|
|
42
|
+
NOVA_TOKEN = os.getenv("NOVA_TOKEN", "")
|
|
43
|
+
NOVA_API_KEY = os.getenv("NOVA_API_KEY", "")
|
|
44
|
+
NOVA_ENABLED = os.getenv("NOVA_ENABLED", "false").lower() == "true"
|
|
45
|
+
WEBHOOK_SECRET = os.getenv("WEBHOOK_SECRET", "conny_ultra_5")
|
|
46
|
+
BASE_URL = os.getenv("BASE_URL", "")
|
|
47
|
+
TELEGRAM_SHARED = os.getenv("TELEGRAM_SHARED", "false").lower() == "true"
|
|
48
|
+
TELEGRAM_SHARED_ROUTER = os.getenv("TELEGRAM_SHARED_ROUTER", "false").lower() == "true"
|
|
49
|
+
TELEGRAM_SHARED_SECRET = os.getenv("TELEGRAM_SHARED_SECRET", "conny_shared_telegram")
|
|
50
|
+
TELEGRAM_DEFAULT_INSTANCE = os.getenv("TELEGRAM_DEFAULT_INSTANCE", "").strip()
|
|
51
|
+
TELEGRAM_SHARED_ROUTES_PATH = os.getenv(
|
|
52
|
+
"TELEGRAM_SHARED_ROUTES_PATH",
|
|
53
|
+
str(Path(_CONNY_HOME) / "shared_telegram_routes.json"),
|
|
54
|
+
)
|
|
55
|
+
TELEGRAM_SHARED_INSTANCES_DIR = os.getenv(
|
|
56
|
+
"TELEGRAM_SHARED_INSTANCES_DIR",
|
|
57
|
+
str(Path(_CONNY_HOME) / "instances"),
|
|
58
|
+
)
|
|
59
|
+
PLATFORM = os.getenv("PLATFORM", "telegram")
|
|
60
|
+
WHATSAPP_BRIDGE_URL = os.getenv("WHATSAPP_BRIDGE_URL", "http://localhost:3000")
|
|
61
|
+
SECTOR = os.getenv("SECTOR", "otro")
|
|
62
|
+
WA_PHONE_ID = os.getenv("WA_PHONE_ID", "")
|
|
63
|
+
WA_ACCESS_TOKEN = os.getenv("WA_ACCESS_TOKEN", "")
|
|
64
|
+
WA_VERIFY_TOKEN = os.getenv("WA_VERIFY_TOKEN", "")
|
|
65
|
+
EVOLUTION_URL = os.getenv("EVOLUTION_URL", "")
|
|
66
|
+
EVOLUTION_API_KEY = os.getenv("EVOLUTION_API_KEY", "")
|
|
67
|
+
EVOLUTION_INSTANCE = os.getenv("EVOLUTION_INSTANCE", "conny")
|
|
68
|
+
MASTER_API_KEY = os.getenv("MASTER_API_KEY", "")
|
|
69
|
+
N8N_WEBHOOK_URL = os.getenv("N8N_WEBHOOK_URL", "")
|
|
70
|
+
TOKEN_EXPIRY_HOURS = int(os.getenv("TOKEN_EXPIRY_HOURS", "72"))
|
|
71
|
+
DEMO_MODE = os.getenv("DEMO_MODE", "false").lower() == "true"
|
|
72
|
+
DEMO_BUSINESS_NAME = os.getenv("DEMO_BUSINESS_NAME", "tu negocio")
|
|
73
|
+
DEMO_SECTOR = os.getenv("DEMO_SECTOR", "estetica")
|
|
74
|
+
DEMO_SESSION_TTL = int(os.getenv("DEMO_SESSION_TTL", "1800"))
|
|
75
|
+
GREETING_ONLY_IDLE_SECONDS = int(os.getenv("GREETING_ONLY_IDLE_SECONDS", "300"))
|
|
76
|
+
V8_ACTIVE_MODEL_REASONING = os.getenv("V8_ACTIVE_MODEL_REASONING", "")
|
|
77
|
+
V8_ACTIVE_MODEL_FAST = os.getenv("V8_ACTIVE_MODEL_FAST", "")
|
|
78
|
+
V8_ACTIVE_MODEL_LITE = os.getenv("V8_ACTIVE_MODEL_LITE", "")
|
|
79
|
+
V8_QUALITY_THRESHOLD = float(os.getenv("V8_QUALITY_THRESHOLD", "0.72"))
|
|
80
|
+
V8_MAX_RETRIES = int(os.getenv("V8_MAX_RETRIES", "3"))
|
|
81
|
+
CONNY_COMPACT_PROMPT = os.getenv("CONNY_COMPACT_PROMPT", "true").lower() in ("1", "true", "yes", "on")
|
|
82
|
+
CONNY_CONTEXT_RECENT_MESSAGES = int(os.getenv("CONNY_CONTEXT_RECENT_MESSAGES", "12"))
|
|
83
|
+
CONNY_CORE_ENABLED = os.getenv("CONNY_CORE_ENABLED", "true").lower() in ("1", "true", "yes", "on")
|
|
84
|
+
CONNY_CORE_PERSONAS_DIR = os.getenv(
|
|
85
|
+
"CONNY_CORE_PERSONAS_DIR",
|
|
86
|
+
str(Path(__file__).resolve().parent / "personas" / "conny" / "base"),
|
|
87
|
+
)
|
|
88
|
+
V8_FILTER_LEVEL = int(os.getenv("V8_FILTER_LEVEL", "2"))
|
|
89
|
+
DB_PATH = os.getenv("DB_PATH", "/home/ubuntu/conny/conny_ultra.db")
|
|
90
|
+
VECTOR_DB_PATH = os.getenv("VECTOR_DB_PATH", "/home/ubuntu/conny/vectors.db")
|
|
91
|
+
LLM_MODELS = {
|
|
92
|
+
"reasoning": os.getenv("LLM_REASONING", "google/gemini-2.5-pro"),
|
|
93
|
+
"fast": os.getenv("LLM_FAST", "google/gemini-2.5-flash"),
|
|
94
|
+
"lite": os.getenv("LLM_LITE", "google/gemini-2.5-flash-lite"),
|
|
95
|
+
"embedding": os.getenv("LLM_EMBEDDING", "openai/text-embedding-3-small"),
|
|
96
|
+
}
|
|
97
|
+
WHISPER_MODEL = os.getenv("WHISPER_MODEL", "openai/whisper-large-v3")
|
|
98
|
+
BUFFER_WAIT_MIN = int(os.getenv("BUFFER_WAIT_MIN", "25"))
|
|
99
|
+
BUFFER_WAIT_MAX = int(os.getenv("BUFFER_WAIT_MAX", "45"))
|
|
100
|
+
BUBBLE_PAUSE_MIN = float(os.getenv("BUBBLE_PAUSE_MIN", "1.2"))
|
|
101
|
+
BUBBLE_PAUSE_MAX = float(os.getenv("BUBBLE_PAUSE_MAX", "3.0"))
|
|
102
|
+
BRAND_ASSETS_BASE_DIR = os.getenv(
|
|
103
|
+
"BRAND_ASSETS_BASE_DIR",
|
|
104
|
+
"/home/ubuntu/conny/brand-assets",
|
|
105
|
+
)
|
|
106
|
+
SELF_IMPROVE_INTERVAL = int(os.getenv("SELF_IMPROVE_INTERVAL", "3600"))
|
|
107
|
+
LEARNING_RATE = float(os.getenv("LEARNING_RATE", "0.1"))
|
|
108
|
+
MAX_CONTEXT_MESSAGES = int(os.getenv("MAX_CONTEXT", "50"))
|
|
109
|
+
MAX_MEMORY_ITEMS = int(os.getenv("MAX_MEMORY", "1000"))
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def validate(cls) -> List[str]:
|
|
113
|
+
errors = []
|
|
114
|
+
if not cls.TELEGRAM_TOKEN:
|
|
115
|
+
errors.append("TELEGRAM_TOKEN requerido")
|
|
116
|
+
if not cls.OPENROUTER_API_KEY and not cls.GEMINI_API_KEY:
|
|
117
|
+
errors.append("Se requiere al menos OPENROUTER_API_KEY o GEMINI_API_KEY")
|
|
118
|
+
return errors
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
DEMO_COMMANDS: Dict[str, str] = {
|
|
122
|
+
"/formal":"/formal", "/amigable":"/amigable", "/luxury":"/luxury",
|
|
123
|
+
"/directa":"/directa", "/energica":"/energica", "/empatica":"/empatica",
|
|
124
|
+
"/experta":"/experta", "/juvenil":"/juvenil",
|
|
125
|
+
"/objecion":"/objecion", "/cita":"/cita", "/stats":"/stats",
|
|
126
|
+
"/prueba":"/prueba", "/cierre":"/cierre", "/bot":"/bot",
|
|
127
|
+
"/memoria":"/memoria", "/2am":"/2am", "/competencia":"/competencia",
|
|
128
|
+
"/precio":"/precio", "/siguiente":"/siguiente",
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
DEMO_TRICKS_ORDER: List[Tuple[str, str]] = [
|
|
132
|
+
("/objecion", "ver cómo manejo objeciones en vivo"),
|
|
133
|
+
("/cita", "ver cómo agendo una cita completa"),
|
|
134
|
+
("/luxury", "activar personalidad premium"),
|
|
135
|
+
("/empatica", "cambiar a modo empático y de escucha"),
|
|
136
|
+
("/stats", "ver el impacto en números reales"),
|
|
137
|
+
("/prueba", "lanzarme el mensaje más difícil que tengas"),
|
|
138
|
+
("/cierre", "ver cómo cierro una venta"),
|
|
139
|
+
("/directa", "activar modo al grano sin rodeos"),
|
|
140
|
+
("/menu", "ver modo bot con emojis y menú numerado"),
|
|
141
|
+
("/2am", "verme responder a las 2 de la madrugada"),
|
|
142
|
+
("/memoria", "ver qué recuerdo de esta conversación"),
|
|
143
|
+
("/experta", "cambiar a modo técnico y preciso"),
|
|
144
|
+
("/modelo", "cambiar el modelo de IA que me impulsa"),
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
DEMO_CMD_ALIASES: Dict[str, str] = {
|
|
148
|
+
"formal":"formal","amigable":"amigable","luxury":"luxury","lujo":"luxury",
|
|
149
|
+
"directa":"directa","energica":"energica","enérgica":"energica",
|
|
150
|
+
"empatica":"empatica","empática":"empatica","experta":"experta",
|
|
151
|
+
"juvenil":"juvenil","joven":"juvenil","profesional":"formal","objecion":"objecion","objeción":"objecion",
|
|
152
|
+
"cita":"cita","agendar":"cita","stats":"stats","estadisticas":"stats",
|
|
153
|
+
"prueba":"prueba","reto":"prueba","cierre":"cierre","bot":"bot",
|
|
154
|
+
"memoria":"memoria","recuerdas":"memoria","2am":"2am","de noche":"2am",
|
|
155
|
+
"competencia":"competencia","precio":"precio","caro":"precio",
|
|
156
|
+
"siguiente":"siguiente","que mas":"siguiente","qué más":"siguiente",
|
|
157
|
+
"menu":"menu_bot","menú":"menu_bot","modo bot":"menu_bot","bot menu":"menu_bot",
|
|
158
|
+
"list":"list","lista":"list","comandos":"list","ayuda":"list","help":"list",
|
|
159
|
+
"qué puedes hacer":"list","que puedes hacer":"list",
|
|
160
|
+
"emojis":"emojis_on","con emojis":"emojis_on","activa emojis":"emojis_on",
|
|
161
|
+
"sin emojis":"emojis_off","quita emojis":"emojis_off","desactiva emojis":"emojis_off",
|
|
162
|
+
"modelo":"modelo","model":"modelo","cambiar modelo":"modelo",
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
MODEL_CATALOG: Dict[str, Tuple[str, str, str]] = {
|
|
166
|
+
"claude-opus": ("anthropic/claude-opus-4", "reasoning", "Más inteligente. Más caro."),
|
|
167
|
+
"claude-sonnet": ("anthropic/claude-sonnet-4", "reasoning", "Balance inteligencia/costo."),
|
|
168
|
+
"claude-haiku": ("anthropic/claude-haiku-3-5", "fast", "Rapidísimo y económico."),
|
|
169
|
+
"gemini-pro": ("google/gemini-2.5-pro", "reasoning", "Google Pro."),
|
|
170
|
+
"gemini-flash": ("google/gemini-2.5-flash", "fast", "Velocidad + calidad."),
|
|
171
|
+
"gemini-lite": ("google/gemini-2.5-flash-lite", "lite", "El más económico."),
|
|
172
|
+
"llama-70b": ("meta-llama/llama-3.3-70b-instruct","fast", "Open source, excelente español."),
|
|
173
|
+
"llama-8b": ("meta-llama/llama-3.1-8b-instruct", "lite", "Ultrarrápido, básico."),
|
|
174
|
+
"gpt4o": ("openai/gpt-4o", "reasoning", "OpenAI flagship."),
|
|
175
|
+
"gpt4o-mini": ("openai/gpt-4o-mini", "fast", "OpenAI económico."),
|
|
176
|
+
"mistral-large": ("mistralai/mistral-large", "reasoning", "Europeo, buen español."),
|
|
177
|
+
"mistral-small": ("mistralai/mistral-small", "fast", "Rápido y asequible."),
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
INTERNAL_PHRASES_TO_BLOCK: List[str] = [
|
|
181
|
+
"todavía no tengo este chat enlazado",
|
|
182
|
+
"ya recibí tu mensaje",
|
|
183
|
+
"no tengo este chat",
|
|
184
|
+
"[error",
|
|
185
|
+
"[internal",
|
|
186
|
+
"{",
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
AUDIO_ERROR_MSG = "Recibí tu audio pero no lo pude procesar. ¿Puedes escribirlo?"
|
|
190
|
+
JSON_ERROR_MSG = "Entendido, déjame verificar eso."
|
|
191
|
+
UNKNOWN_CMD_MSG = "Comando no reconocido. Escribe /help para ver los disponibles."
|
|
192
|
+
|
|
193
|
+
DEMO_HELP_FULL = (
|
|
194
|
+
"esto es lo que puedo mostrarte 👇\n\n"
|
|
195
|
+
"🎭 Personalidades: /formal · /amigable · /luxury · /directa · /empatica · /experta · /juvenil\n\n"
|
|
196
|
+
"💬 Situaciones reales:\n"
|
|
197
|
+
"objecion — cliente difícil\n"
|
|
198
|
+
"cita — agendamiento completo\n"
|
|
199
|
+
"cierre — técnica de cierre\n"
|
|
200
|
+
"competencia — ya fui a otro lado\n"
|
|
201
|
+
"precio — está muy caro\n"
|
|
202
|
+
"prueba — mándame el más difícil\n"
|
|
203
|
+
"bot — soy un bot?\n"
|
|
204
|
+
"2am — respuesta a las 2am\n\n"
|
|
205
|
+
"📊 Demo y datos:\n"
|
|
206
|
+
"stats — impacto en números\n"
|
|
207
|
+
"memoria — qué recuerdo de ti\n"
|
|
208
|
+
"menu — modo bot con emojis\n\n"
|
|
209
|
+
"⚙️ Ajustes:\n"
|
|
210
|
+
"usa emojis / sin emojis\n"
|
|
211
|
+
"siguiente — próximo truco\n"
|
|
212
|
+
"/modelo — cambiar el modelo\n"
|
|
213
|
+
"reset — empezar de nuevo\n\n"
|
|
214
|
+
"escribe sin slash para activar"
|
|
215
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
conny_cron.py — Scheduled tasks for Conny (memory consolidation, cleanup).
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
import logging, asyncio
|
|
6
|
+
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
7
|
+
from apscheduler.triggers.cron import CronTrigger
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger("conny.cron")
|
|
10
|
+
|
|
11
|
+
_scheduler: AsyncIOScheduler = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def init_scheduler(memory_engine=None, instance_ids: list = None):
|
|
15
|
+
"""Initialize the cron scheduler. Call during app startup."""
|
|
16
|
+
global _scheduler
|
|
17
|
+
if _scheduler:
|
|
18
|
+
return _scheduler
|
|
19
|
+
|
|
20
|
+
_scheduler = AsyncIOScheduler()
|
|
21
|
+
|
|
22
|
+
if memory_engine and instance_ids:
|
|
23
|
+
for iid in instance_ids:
|
|
24
|
+
_scheduler.add_job(
|
|
25
|
+
_run_consolidation,
|
|
26
|
+
CronTrigger(day_of_week="sun", hour=3, minute=0),
|
|
27
|
+
args=[memory_engine, iid],
|
|
28
|
+
id=f"consolidation_{iid}",
|
|
29
|
+
replace_existing=True,
|
|
30
|
+
)
|
|
31
|
+
# Weekly report every Monday at 9am
|
|
32
|
+
_scheduler.add_job(
|
|
33
|
+
_send_weekly_report,
|
|
34
|
+
CronTrigger(day_of_week="mon", hour=9, minute=0),
|
|
35
|
+
args=[iid],
|
|
36
|
+
id=f"weekly_report_{iid}",
|
|
37
|
+
replace_existing=True,
|
|
38
|
+
)
|
|
39
|
+
log.info(f"[cron] consolidation (Sun 3am) + weekly report (Mon 9am) scheduled for {iid}")
|
|
40
|
+
|
|
41
|
+
_scheduler.start()
|
|
42
|
+
log.info("[cron] scheduler started")
|
|
43
|
+
return _scheduler
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def _run_consolidation(memory_engine, instance_id: str):
|
|
47
|
+
"""Run weekly memory consolidation for an instance."""
|
|
48
|
+
try:
|
|
49
|
+
await memory_engine.weekly_consolidation(instance_id)
|
|
50
|
+
log.info(f"[cron] consolidation complete: {instance_id}")
|
|
51
|
+
except Exception as e:
|
|
52
|
+
log.error(f"[cron] consolidation failed for {instance_id}: {e}")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def _send_weekly_report(instance_id: str):
|
|
56
|
+
"""Send weekly report to admin."""
|
|
57
|
+
try:
|
|
58
|
+
from conny_weekly_report import generate_weekly_report
|
|
59
|
+
report = await generate_weekly_report(instance_id)
|
|
60
|
+
log.info(f"[cron] weekly report generated for {instance_id}")
|
|
61
|
+
# TODO: wire send_fn when admin_jid is available in cron context
|
|
62
|
+
except Exception as e:
|
|
63
|
+
log.error(f"[cron] weekly report failed: {e}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def shutdown_scheduler():
|
|
67
|
+
"""Shutdown the scheduler gracefully."""
|
|
68
|
+
global _scheduler
|
|
69
|
+
if _scheduler:
|
|
70
|
+
_scheduler.shutdown(wait=False)
|
|
71
|
+
_scheduler = None
|
|
72
|
+
log.info("[cron] scheduler stopped")
|