@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,447 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Módulo de generación LLM para Conny Ultra.
|
|
3
|
+
|
|
4
|
+
Contiene la lógica de generación de respuestas:
|
|
5
|
+
- _llm(): Generación básica
|
|
6
|
+
- _llm_conv_pitch(): Generación con pitch para prospectos confuse
|
|
7
|
+
- _llm_conv(): Generación conversacional con historial
|
|
8
|
+
- _demo_llm_conv_quality_chain(): Chain con validación y repair
|
|
9
|
+
- _demo_llm_quality_chain(): Chain de calidad con retries
|
|
10
|
+
|
|
11
|
+
Este módulo fue extraído de conny.py para reducir su tamaño y mejorar mantenibilidad.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import time
|
|
16
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
from conny_config import Config
|
|
20
|
+
except ImportError:
|
|
21
|
+
class Config:
|
|
22
|
+
GROQ_API_KEY = ""
|
|
23
|
+
GEMINI_API_KEY = ""
|
|
24
|
+
GEMINI_API_KEY_2 = ""
|
|
25
|
+
GEMINI_API_KEY_3 = ""
|
|
26
|
+
OPENROUTER_API_KEY = ""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class GeneratorManager:
|
|
30
|
+
"""
|
|
31
|
+
Gestor de generación LLM para Conny Ultra.
|
|
32
|
+
|
|
33
|
+
Proporciona métodos para:
|
|
34
|
+
- Generación básica de texto
|
|
35
|
+
- Generación conversacional con historial
|
|
36
|
+
- Generación con pitch especializado
|
|
37
|
+
- Chains de calidad con validación y retry
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, llm_engine=None, generator=None):
|
|
41
|
+
self._llm_engine = llm_engine
|
|
42
|
+
self._generator = generator
|
|
43
|
+
|
|
44
|
+
def _get_demo_engine(self, demo_model_pref: str = "auto", bmodel_key: str = ""):
|
|
45
|
+
"""
|
|
46
|
+
Devuelve el proveedor LLM según preferencia de sesión.
|
|
47
|
+
|
|
48
|
+
Formatos soportados:
|
|
49
|
+
auto → engine global
|
|
50
|
+
gemini → GeminiProvider con gemini-2.5-flash
|
|
51
|
+
gemini:gemini-2.5-pro → GeminiProvider con modelo específico
|
|
52
|
+
groq → GroqProvider con llama-3.3-70b-versatile
|
|
53
|
+
groq:llama-3.1-8b-instant → GroqProvider con modelo específico
|
|
54
|
+
openrouter → OpenRouterProvider
|
|
55
|
+
openrouter:anthropic/claude-sonnet-4 → OpenRouter con modelo específico
|
|
56
|
+
"""
|
|
57
|
+
from llm_engine import (
|
|
58
|
+
GeminiProvider, GroqProvider, OpenRouterProvider
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
pref = demo_model_pref
|
|
62
|
+
|
|
63
|
+
if ":" in pref:
|
|
64
|
+
provider, model_name = pref.split(":", 1)
|
|
65
|
+
else:
|
|
66
|
+
provider, model_name = pref, None
|
|
67
|
+
|
|
68
|
+
if provider == "groq" and Config.GROQ_API_KEY:
|
|
69
|
+
eng = GroqProvider(Config.GROQ_API_KEY)
|
|
70
|
+
if model_name:
|
|
71
|
+
eng.MDLS = {"reasoning": model_name, "fast": model_name, "lite": model_name}
|
|
72
|
+
return eng
|
|
73
|
+
|
|
74
|
+
elif provider == "gemini":
|
|
75
|
+
key = Config.GEMINI_API_KEY or Config.GEMINI_API_KEY_2 or Config.GEMINI_API_KEY_3
|
|
76
|
+
if key:
|
|
77
|
+
eng = GeminiProvider(key, "gemini_demo")
|
|
78
|
+
if model_name:
|
|
79
|
+
eng.MDLS = {"reasoning": model_name, "fast": model_name, "lite": model_name}
|
|
80
|
+
else:
|
|
81
|
+
eng.MDLS = {
|
|
82
|
+
"reasoning": "gemini-2.5-flash",
|
|
83
|
+
"fast": "gemini-2.5-flash",
|
|
84
|
+
"lite": "gemini-2.5-flash-lite"
|
|
85
|
+
}
|
|
86
|
+
return eng
|
|
87
|
+
if Config.OPENROUTER_API_KEY:
|
|
88
|
+
eng = OpenRouterProvider(Config.OPENROUTER_API_KEY)
|
|
89
|
+
m = model_name or "google/gemini-2.5-flash"
|
|
90
|
+
eng.MDLS = {"reasoning": m, "fast": m, "lite": m}
|
|
91
|
+
return eng
|
|
92
|
+
|
|
93
|
+
elif provider == "openrouter" and Config.OPENROUTER_API_KEY:
|
|
94
|
+
eng = OpenRouterProvider(Config.OPENROUTER_API_KEY)
|
|
95
|
+
if model_name:
|
|
96
|
+
eng.MDLS = {"reasoning": model_name, "fast": model_name, "lite": model_name}
|
|
97
|
+
return eng
|
|
98
|
+
|
|
99
|
+
return self._llm_engine or (self._generator.llm if self._generator else None)
|
|
100
|
+
|
|
101
|
+
async def _llm(
|
|
102
|
+
self,
|
|
103
|
+
sys_p: str,
|
|
104
|
+
usr_p: str,
|
|
105
|
+
temp: float = 0.82,
|
|
106
|
+
max_t: int = 8192,
|
|
107
|
+
model_tier: str = "fast",
|
|
108
|
+
demo_model_pref: str = "auto"
|
|
109
|
+
) -> Optional[str]:
|
|
110
|
+
"""
|
|
111
|
+
Generación básica de texto con LLM.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
sys_p: Prompt del sistema
|
|
115
|
+
usr_p: Prompt del usuario
|
|
116
|
+
temp: Temperatura de generación
|
|
117
|
+
max_t: Máximo de tokens de salida
|
|
118
|
+
model_tier: Tier del modelo (fast, reasoning, lite)
|
|
119
|
+
demo_model_pref: Preferencia de modelo demo
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Texto generado o None en caso de error
|
|
123
|
+
"""
|
|
124
|
+
import logging
|
|
125
|
+
log = logging.getLogger("conny_generator")
|
|
126
|
+
|
|
127
|
+
msgs = [{"role": "system", "content": sys_p}, {"role": "user", "content": usr_p}]
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
eng = self._get_demo_engine(demo_model_pref)
|
|
131
|
+
if not eng:
|
|
132
|
+
raise RuntimeError("LLM no init")
|
|
133
|
+
|
|
134
|
+
r, meta = await eng.complete(
|
|
135
|
+
msgs,
|
|
136
|
+
model_tier=model_tier,
|
|
137
|
+
temperature=temp,
|
|
138
|
+
max_tokens=max_t,
|
|
139
|
+
use_cache=False,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
log.info(f"[demo] {meta.get('provider','?')} model={meta.get('model','?')[:30]}")
|
|
143
|
+
|
|
144
|
+
if self._generator:
|
|
145
|
+
return self._generator._postprocess(r, self._generator.PersonalityProfile())
|
|
146
|
+
return r
|
|
147
|
+
|
|
148
|
+
except Exception as e:
|
|
149
|
+
log.error(f"[demo] llm error: {e}")
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
async def _llm_conv_pitch(
|
|
153
|
+
self,
|
|
154
|
+
pitch_sys: str,
|
|
155
|
+
history: List[Dict[str, Any]],
|
|
156
|
+
text: str,
|
|
157
|
+
temp: float = 0.85,
|
|
158
|
+
max_t: int = 8192,
|
|
159
|
+
recent_limit: int = 12,
|
|
160
|
+
demo_model_pref: str = "auto"
|
|
161
|
+
) -> Optional[str]:
|
|
162
|
+
"""
|
|
163
|
+
LLM con el pitch de Black One para prospectos confundidos.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
pitch_sys: Prompt de sistema con el pitch
|
|
167
|
+
history: Historial de conversación
|
|
168
|
+
text: Mensaje actual
|
|
169
|
+
temp: Temperatura
|
|
170
|
+
max_t: Máximo de tokens
|
|
171
|
+
recent_limit: Límite de mensajes recientes a incluir
|
|
172
|
+
demo_model_pref: Preferencia de modelo
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Texto generado o None
|
|
176
|
+
"""
|
|
177
|
+
import logging
|
|
178
|
+
log = logging.getLogger("conny_generator")
|
|
179
|
+
|
|
180
|
+
msgs = [{"role": "system", "content": pitch_sys}]
|
|
181
|
+
for m in history[-recent_limit:]:
|
|
182
|
+
msgs.append({"role": m["role"], "content": m["content"]})
|
|
183
|
+
msgs.append({"role": "user", "content": text})
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
eng = self._get_demo_engine(demo_model_pref)
|
|
187
|
+
if not eng:
|
|
188
|
+
raise RuntimeError("LLM no init")
|
|
189
|
+
|
|
190
|
+
r, meta = await eng.complete(
|
|
191
|
+
msgs,
|
|
192
|
+
model_tier="fast",
|
|
193
|
+
temperature=temp,
|
|
194
|
+
max_tokens=max_t,
|
|
195
|
+
use_cache=False
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
log.info(f"[demo][pitch] {meta.get('provider','?')}")
|
|
199
|
+
|
|
200
|
+
if self._generator:
|
|
201
|
+
return self._generator._postprocess(r, self._generator.PersonalityProfile())
|
|
202
|
+
return r
|
|
203
|
+
|
|
204
|
+
except Exception as e:
|
|
205
|
+
log.error(f"[demo][pitch] error: {e}")
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
async def _llm_conv(
|
|
209
|
+
self,
|
|
210
|
+
sys_p: str,
|
|
211
|
+
history: List[Dict[str, Any]],
|
|
212
|
+
text: str,
|
|
213
|
+
temp: float = 0.85,
|
|
214
|
+
max_t: int = 8192,
|
|
215
|
+
model_tier: str = "fast",
|
|
216
|
+
recent_limit: int = 12,
|
|
217
|
+
demo_model_pref: str = "auto"
|
|
218
|
+
) -> Optional[str]:
|
|
219
|
+
"""
|
|
220
|
+
Generación conversacional con historial.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
sys_p: Prompt del sistema
|
|
224
|
+
history: Historial de conversación
|
|
225
|
+
text: Mensaje actual
|
|
226
|
+
temp: Temperatura
|
|
227
|
+
max_t: Máximo de tokens
|
|
228
|
+
model_tier: Tier del modelo
|
|
229
|
+
recent_limit: Límite de mensajes recientes
|
|
230
|
+
demo_model_pref: Preferencia de modelo
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Texto generado o None
|
|
234
|
+
"""
|
|
235
|
+
import logging
|
|
236
|
+
log = logging.getLogger("conny_generator")
|
|
237
|
+
|
|
238
|
+
msgs = [{"role": "system", "content": sys_p}]
|
|
239
|
+
for m in history[-recent_limit:]:
|
|
240
|
+
msgs.append({"role": m["role"], "content": m["content"]})
|
|
241
|
+
msgs.append({"role": "user", "content": text})
|
|
242
|
+
|
|
243
|
+
try:
|
|
244
|
+
eng = self._get_demo_engine(demo_model_pref)
|
|
245
|
+
if not eng:
|
|
246
|
+
raise RuntimeError("LLM no init")
|
|
247
|
+
|
|
248
|
+
r, meta = await eng.complete(
|
|
249
|
+
msgs,
|
|
250
|
+
model_tier=model_tier,
|
|
251
|
+
temperature=temp,
|
|
252
|
+
max_tokens=max_t,
|
|
253
|
+
use_cache=False,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
log.info(f"[demo] {meta.get('provider','?')} model={meta.get('model','?')[:30]}")
|
|
257
|
+
|
|
258
|
+
if self._generator:
|
|
259
|
+
return self._generator._postprocess(r, self._generator.PersonalityProfile())
|
|
260
|
+
return r
|
|
261
|
+
|
|
262
|
+
except Exception as e:
|
|
263
|
+
log.error(f"[demo] llm_conv error: {e}")
|
|
264
|
+
return None
|
|
265
|
+
|
|
266
|
+
async def _demo_llm_conv_quality_chain(
|
|
267
|
+
self,
|
|
268
|
+
system_prompt: str,
|
|
269
|
+
validator: Callable[[str], bool],
|
|
270
|
+
repair_instructions: str,
|
|
271
|
+
history: List[Dict[str, Any]],
|
|
272
|
+
text: str,
|
|
273
|
+
temp: float = 0.72,
|
|
274
|
+
max_t: int = 8192,
|
|
275
|
+
model_tier: str = "fast",
|
|
276
|
+
recent_limit: int = 8,
|
|
277
|
+
demo_model_pref: str = "auto"
|
|
278
|
+
) -> Tuple[Optional[str], bool]:
|
|
279
|
+
"""
|
|
280
|
+
Chain de calidad con validación y repair para conversaciones demo.
|
|
281
|
+
|
|
282
|
+
Intenta primero la generación normal, si falla la validación
|
|
283
|
+
reintenta con instrucciones de repair.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
system_prompt: Prompt del sistema
|
|
287
|
+
validator: Función que valida la respuesta
|
|
288
|
+
repair_instructions: Instrucciones para repair si falla validación
|
|
289
|
+
history: Historial de conversación
|
|
290
|
+
text: Mensaje actual
|
|
291
|
+
temp: Temperatura
|
|
292
|
+
max_t: Máximo de tokens
|
|
293
|
+
model_tier: Tier del modelo
|
|
294
|
+
recent_limit: Límite de mensajes recientes
|
|
295
|
+
demo_model_pref: Preferencia de modelo
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
Tuple (respuesta_valida o None, tuvo_output)
|
|
299
|
+
"""
|
|
300
|
+
import logging
|
|
301
|
+
log = logging.getLogger("conny_generator")
|
|
302
|
+
|
|
303
|
+
_chain_start = time.time()
|
|
304
|
+
_CHAIN_TIMEOUT_S = 45 # Timeout entre intentos
|
|
305
|
+
|
|
306
|
+
attempts = [
|
|
307
|
+
(system_prompt, temp, max_t, model_tier, recent_limit),
|
|
308
|
+
(
|
|
309
|
+
system_prompt
|
|
310
|
+
+ "\n\nREPARA LA RESPUESTA:\n"
|
|
311
|
+
+ repair_instructions.strip()
|
|
312
|
+
+ "\n- no repitas introducciones\n- no suenes a bot ni a guion de demo",
|
|
313
|
+
0.58,
|
|
314
|
+
max_t,
|
|
315
|
+
"reasoning",
|
|
316
|
+
recent_limit,
|
|
317
|
+
),
|
|
318
|
+
]
|
|
319
|
+
|
|
320
|
+
had_output = False
|
|
321
|
+
|
|
322
|
+
for prompt_now, temp_now, max_now, tier_now, limit_now in attempts:
|
|
323
|
+
if time.time() - _chain_start > _CHAIN_TIMEOUT_S:
|
|
324
|
+
log.warning("[demo] conv_quality_chain abortada por timeout (%ds)", _CHAIN_TIMEOUT_S)
|
|
325
|
+
break
|
|
326
|
+
|
|
327
|
+
candidate = await self._llm_conv(
|
|
328
|
+
prompt_now,
|
|
329
|
+
history=history,
|
|
330
|
+
text=text,
|
|
331
|
+
temp=temp_now,
|
|
332
|
+
max_t=max_now,
|
|
333
|
+
model_tier=tier_now,
|
|
334
|
+
recent_limit=limit_now,
|
|
335
|
+
demo_model_pref=demo_model_pref,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
if candidate and candidate.strip():
|
|
339
|
+
had_output = True
|
|
340
|
+
if validator(candidate):
|
|
341
|
+
return candidate, True
|
|
342
|
+
|
|
343
|
+
return None, had_output
|
|
344
|
+
|
|
345
|
+
async def _demo_llm_quality_chain(
|
|
346
|
+
self,
|
|
347
|
+
system_prompt: str,
|
|
348
|
+
validator: Callable[[str], bool],
|
|
349
|
+
repair_instructions: str,
|
|
350
|
+
user_message: str,
|
|
351
|
+
temp: float = 0.72,
|
|
352
|
+
max_t: int = 8192,
|
|
353
|
+
model_tier: str = "fast",
|
|
354
|
+
demo_model_pref: str = "auto"
|
|
355
|
+
) -> Tuple[Optional[str], bool]:
|
|
356
|
+
"""
|
|
357
|
+
Chain de calidad simple (sin historial) para generación demo.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
system_prompt: Prompt del sistema
|
|
361
|
+
validator: Función que valida la respuesta
|
|
362
|
+
repair_instructions: Instrucciones para repair
|
|
363
|
+
user_message: Mensaje del usuario
|
|
364
|
+
temp: Temperatura
|
|
365
|
+
max_t: Máximo de tokens
|
|
366
|
+
model_tier: Tier del modelo
|
|
367
|
+
demo_model_pref: Preferencia de modelo
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
Tuple (respuesta o None, tuvo_output)
|
|
371
|
+
"""
|
|
372
|
+
import logging
|
|
373
|
+
log = logging.getLogger("conny_generator")
|
|
374
|
+
|
|
375
|
+
_chain_start = time.time()
|
|
376
|
+
_CHAIN_TIMEOUT_S = 45
|
|
377
|
+
|
|
378
|
+
attempts = [
|
|
379
|
+
(system_prompt, temp, max_t, model_tier),
|
|
380
|
+
(
|
|
381
|
+
system_prompt
|
|
382
|
+
+ "\n\nREPARA LA RESPUESTA:\n"
|
|
383
|
+
+ repair_instructions.strip()
|
|
384
|
+
+ "\n- no repitas introducciones\n- no suenes a bot",
|
|
385
|
+
0.58,
|
|
386
|
+
max_t,
|
|
387
|
+
"reasoning",
|
|
388
|
+
),
|
|
389
|
+
]
|
|
390
|
+
|
|
391
|
+
had_output = False
|
|
392
|
+
|
|
393
|
+
for prompt_now, temp_now, max_now, tier_now in attempts:
|
|
394
|
+
if time.time() - _chain_start > _CHAIN_TIMEOUT_S:
|
|
395
|
+
log.warning("[demo] quality_chain abortada por timeout")
|
|
396
|
+
break
|
|
397
|
+
|
|
398
|
+
candidate = await self._llm(
|
|
399
|
+
prompt_now,
|
|
400
|
+
user_message,
|
|
401
|
+
temp=temp_now,
|
|
402
|
+
max_t=max_now,
|
|
403
|
+
model_tier=tier_now,
|
|
404
|
+
demo_model_pref=demo_model_pref,
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
if candidate and candidate.strip():
|
|
408
|
+
had_output = True
|
|
409
|
+
if validator(candidate):
|
|
410
|
+
return candidate, True
|
|
411
|
+
|
|
412
|
+
return None, had_output
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def estimate_tokens(text: str) -> int:
|
|
416
|
+
"""
|
|
417
|
+
Estima el número de tokens en un texto.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
text: Texto a estimar
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
Estimación de tokens (approx 4 chars por token)
|
|
424
|
+
"""
|
|
425
|
+
return len(text) // 4
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def budget_tokens(
|
|
429
|
+
system_prompt: str,
|
|
430
|
+
max_t: int = 8192,
|
|
431
|
+
safety_margin: float = 0.9
|
|
432
|
+
) -> Tuple[int, int]:
|
|
433
|
+
"""
|
|
434
|
+
Calcula presupuesto de tokens para generación.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
system_prompt: Prompt del sistema
|
|
438
|
+
max_t: Máximo de tokens disponibles
|
|
439
|
+
safety_margin: Margen de seguridad (0.0-1.0)
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
Tuple (tokens_disponibles_output, tokens_reservados)
|
|
443
|
+
"""
|
|
444
|
+
system_tokens = estimate_tokens(system_prompt)
|
|
445
|
+
available = int(max_t * safety_margin)
|
|
446
|
+
output_budget = max(available - system_tokens, 512)
|
|
447
|
+
return output_budget, system_tokens
|
|
File without changes
|