iatoolkit 0.3.1__py3-none-any.whl
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.
Potentially problematic release.
This version of iatoolkit might be problematic. Click here for more details.
- iatoolkit/__init__.py +41 -0
- iatoolkit/base_company.py +42 -0
- iatoolkit/company_registry.py +98 -0
- iatoolkit/iatoolkit.py +405 -0
- iatoolkit/toolkit_config.py +13 -0
- iatoolkit-0.3.1.dist-info/METADATA +252 -0
- iatoolkit-0.3.1.dist-info/RECORD +28 -0
- iatoolkit-0.3.1.dist-info/WHEEL +5 -0
- iatoolkit-0.3.1.dist-info/top_level.txt +2 -0
- services/__init__.py +5 -0
- services/api_service.py +30 -0
- services/benchmark_service.py +139 -0
- services/dispatcher_service.py +312 -0
- services/document_service.py +159 -0
- services/excel_service.py +98 -0
- services/file_processor_service.py +92 -0
- services/history_service.py +45 -0
- services/jwt_service.py +91 -0
- services/load_documents_service.py +212 -0
- services/mail_service.py +62 -0
- services/profile_service.py +376 -0
- services/prompt_manager_service.py +180 -0
- services/query_service.py +332 -0
- services/search_service.py +32 -0
- services/sql_service.py +42 -0
- services/tasks_service.py +188 -0
- services/user_feedback_service.py +67 -0
- services/user_session_context_service.py +85 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Producto: IAToolkit
|
|
3
|
+
# Todos los derechos reservados.
|
|
4
|
+
# En trámite de registro en el Registro de Propiedad Intelectual de Chile.
|
|
5
|
+
|
|
6
|
+
from injector import inject
|
|
7
|
+
from repositories.profile_repo import ProfileRepo
|
|
8
|
+
from repositories.models import User, Company, ApiKey
|
|
9
|
+
from flask_bcrypt import check_password_hash
|
|
10
|
+
from common.session_manager import SessionManager
|
|
11
|
+
from flask_bcrypt import Bcrypt
|
|
12
|
+
from infra.mail_app import MailApp
|
|
13
|
+
import random
|
|
14
|
+
import logging
|
|
15
|
+
import re
|
|
16
|
+
import secrets
|
|
17
|
+
import string
|
|
18
|
+
from datetime import datetime, timezone
|
|
19
|
+
from services.user_session_context_service import UserSessionContextService
|
|
20
|
+
from services.query_service import QueryService
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ProfileService:
|
|
24
|
+
@inject
|
|
25
|
+
def __init__(self,
|
|
26
|
+
profile_repo: ProfileRepo,
|
|
27
|
+
session_context_service: UserSessionContextService,
|
|
28
|
+
query_service: QueryService,
|
|
29
|
+
mail_app: MailApp):
|
|
30
|
+
self.profile_repo = profile_repo
|
|
31
|
+
self.session_context = session_context_service
|
|
32
|
+
self.query_service = query_service
|
|
33
|
+
self.mail_app = mail_app
|
|
34
|
+
self.bcrypt = Bcrypt()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def login(self, company_short_name: str, email: str, password: str) -> dict:
|
|
38
|
+
try:
|
|
39
|
+
# check if exits
|
|
40
|
+
user = self.profile_repo.get_user_by_email(email)
|
|
41
|
+
if not user:
|
|
42
|
+
return {"error": "Usuario no encontrado"}
|
|
43
|
+
|
|
44
|
+
# check the encrypted password
|
|
45
|
+
if not check_password_hash(user.password, password):
|
|
46
|
+
return {"error": "Contraseña inválida"}
|
|
47
|
+
|
|
48
|
+
company = self.get_company_by_short_name(company_short_name)
|
|
49
|
+
if not company:
|
|
50
|
+
return {"error": "Empresa no encontrada"}
|
|
51
|
+
|
|
52
|
+
# check that user belongs to company
|
|
53
|
+
if company not in user.companies:
|
|
54
|
+
return {"error": "Usuario no esta autorizado para esta empresa"}
|
|
55
|
+
|
|
56
|
+
if not user.verified:
|
|
57
|
+
return {"error": "Tu cuenta no ha sido verificada. Por favor, revisa tu correo."}
|
|
58
|
+
|
|
59
|
+
# clear history save user data into session manager
|
|
60
|
+
self.set_user_session(user=user, company=company)
|
|
61
|
+
|
|
62
|
+
# initialize company context
|
|
63
|
+
self.query_service.llm_init_context(
|
|
64
|
+
company_short_name=company_short_name,
|
|
65
|
+
local_user_id=user.id
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return {"message": "Login exitoso"}
|
|
69
|
+
except Exception as e:
|
|
70
|
+
logging.exception(f"login error: {str(e)}")
|
|
71
|
+
return {"error": str(e)}
|
|
72
|
+
|
|
73
|
+
def set_user_session(self, user: User, company: Company):
|
|
74
|
+
SessionManager.set('user_id', user.id)
|
|
75
|
+
SessionManager.set('company_id', company.id)
|
|
76
|
+
SessionManager.set('company_short_name', company.short_name)
|
|
77
|
+
|
|
78
|
+
# save user data into session manager
|
|
79
|
+
user_data = {
|
|
80
|
+
"id": user.id,
|
|
81
|
+
"email": user.email,
|
|
82
|
+
"first_name": user.first_name,
|
|
83
|
+
"last_name": user.last_name,
|
|
84
|
+
"super_user": user.super_user,
|
|
85
|
+
"company_id": company.id,
|
|
86
|
+
"company": company.name,
|
|
87
|
+
"company_short_name": company.short_name,
|
|
88
|
+
"company_logo": company.logo_file
|
|
89
|
+
}
|
|
90
|
+
SessionManager.set('user', user_data)
|
|
91
|
+
|
|
92
|
+
# save time session was activated (in timestamp format)
|
|
93
|
+
SessionManager.set('last_activity', datetime.now(timezone.utc).timestamp())
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def signup(self,
|
|
97
|
+
company_short_name: str,
|
|
98
|
+
email: str,
|
|
99
|
+
first_name: str,
|
|
100
|
+
last_name: str,
|
|
101
|
+
rut: str,
|
|
102
|
+
password: str,
|
|
103
|
+
confirm_password: str,
|
|
104
|
+
verification_url: str) -> dict:
|
|
105
|
+
try:
|
|
106
|
+
|
|
107
|
+
# get company info
|
|
108
|
+
company = self.get_company_by_short_name(company_short_name)
|
|
109
|
+
if not company:
|
|
110
|
+
return {"error": f"la empresa {company_short_name} no existe"}
|
|
111
|
+
|
|
112
|
+
# normalize format's
|
|
113
|
+
rut = rut.lower().replace(" ", "")
|
|
114
|
+
email = email.lower()
|
|
115
|
+
|
|
116
|
+
# check if user exists
|
|
117
|
+
existing_user = self.profile_repo.get_user_by_email(email)
|
|
118
|
+
if existing_user:
|
|
119
|
+
# validate password
|
|
120
|
+
if not self.bcrypt.check_password_hash(existing_user.password, password):
|
|
121
|
+
return {"error": "La contraseña es incorrecta. No se puede agregar a la nueva empresa."}
|
|
122
|
+
|
|
123
|
+
if rut != existing_user.rut:
|
|
124
|
+
return {"error": "El RUT ingresado no corresponde al email existente."}
|
|
125
|
+
|
|
126
|
+
# check if register
|
|
127
|
+
if company in existing_user.companies:
|
|
128
|
+
return {"error": "Usuario ya registrado en esta empresa"}
|
|
129
|
+
else:
|
|
130
|
+
# add new company to existing user
|
|
131
|
+
existing_user.companies.append(company)
|
|
132
|
+
self.profile_repo.save_user(existing_user)
|
|
133
|
+
return {"message": "Usuario asociado a nueva empresa"}
|
|
134
|
+
|
|
135
|
+
# add the new user
|
|
136
|
+
if password != confirm_password:
|
|
137
|
+
return {"error": "Las contraseñas no coinciden. Por favor, inténtalo de nuevo."}
|
|
138
|
+
|
|
139
|
+
is_valid, message = self.validate_password(password)
|
|
140
|
+
if not is_valid:
|
|
141
|
+
return {"error": message}
|
|
142
|
+
|
|
143
|
+
# encrypt the password
|
|
144
|
+
hashed_password = self.bcrypt.generate_password_hash(password).decode('utf-8')
|
|
145
|
+
|
|
146
|
+
# create the new user
|
|
147
|
+
new_user = User(email=email,
|
|
148
|
+
rut=rut,
|
|
149
|
+
password=hashed_password,
|
|
150
|
+
first_name=first_name.lower(),
|
|
151
|
+
last_name=last_name.lower(),
|
|
152
|
+
verified=False,
|
|
153
|
+
verification_url=verification_url
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# associate new company to user
|
|
157
|
+
new_user.companies.append(company)
|
|
158
|
+
|
|
159
|
+
self.profile_repo.create_user(new_user)
|
|
160
|
+
|
|
161
|
+
# send email with verification
|
|
162
|
+
self.send_verification_email(new_user, company_short_name)
|
|
163
|
+
|
|
164
|
+
return {"message": "Registro exitoso. Por favor, revisa tu correo para verificar tu cuenta."}
|
|
165
|
+
except Exception as e:
|
|
166
|
+
return {"error": str(e)}
|
|
167
|
+
|
|
168
|
+
def update_user(self, email: str, **kwargs) -> User:
|
|
169
|
+
return self.profile_repo.update_user(email, **kwargs)
|
|
170
|
+
|
|
171
|
+
def verify_account(self, email: str):
|
|
172
|
+
try:
|
|
173
|
+
# check if user exist
|
|
174
|
+
user = self.profile_repo.get_user_by_email(email)
|
|
175
|
+
if not user:
|
|
176
|
+
return {"error": "El usuario no existe."}
|
|
177
|
+
|
|
178
|
+
# activate the user account
|
|
179
|
+
self.profile_repo.verify_user(email)
|
|
180
|
+
return {"message": "Tu cuenta ha sido verificada exitosamente. Ahora puedes iniciar sesión."}
|
|
181
|
+
|
|
182
|
+
except Exception as e:
|
|
183
|
+
return {"error": str(e)}
|
|
184
|
+
|
|
185
|
+
def change_password(self,
|
|
186
|
+
email: str,
|
|
187
|
+
temp_code: str,
|
|
188
|
+
new_password: str,
|
|
189
|
+
confirm_password: str):
|
|
190
|
+
try:
|
|
191
|
+
if new_password != confirm_password:
|
|
192
|
+
return {"error": "Las contraseñas no coinciden. Por favor, inténtalo nuevamente."}
|
|
193
|
+
|
|
194
|
+
# check the temporary code
|
|
195
|
+
user = self.profile_repo.get_user_by_email(email)
|
|
196
|
+
if not user or user.temp_code != temp_code:
|
|
197
|
+
return {"error": "El código temporal no es válido. Por favor, verifica o solicita uno nuevo."}
|
|
198
|
+
|
|
199
|
+
# encrypt and save the password, make the temporary code invalid
|
|
200
|
+
hashed_password = self.bcrypt.generate_password_hash(new_password).decode('utf-8')
|
|
201
|
+
self.profile_repo.update_password(email, hashed_password)
|
|
202
|
+
self.profile_repo.reset_temp_code(email)
|
|
203
|
+
|
|
204
|
+
return {"message": "La clave se cambio correctamente"}
|
|
205
|
+
except Exception as e:
|
|
206
|
+
return {"error": str(e)}
|
|
207
|
+
|
|
208
|
+
def forgot_password(self, email: str, reset_url: str):
|
|
209
|
+
try:
|
|
210
|
+
# Verificar si el usuario existe
|
|
211
|
+
user = self.profile_repo.get_user_by_email(email)
|
|
212
|
+
if not user:
|
|
213
|
+
return {"error": "El usuario no existe."}
|
|
214
|
+
|
|
215
|
+
# Gen a temporary code and store in the repositories
|
|
216
|
+
temp_code = ''.join(random.choices(string.ascii_letters + string.digits, k=6)).upper()
|
|
217
|
+
self.profile_repo.set_temp_code(email, temp_code)
|
|
218
|
+
|
|
219
|
+
# send email to the user
|
|
220
|
+
self.send_forgot_password_email(user, reset_url)
|
|
221
|
+
|
|
222
|
+
return {"message": "se envio mail para cambio de clave"}
|
|
223
|
+
except Exception as e:
|
|
224
|
+
return {"error": str(e)}
|
|
225
|
+
|
|
226
|
+
def validate_password(self, password):
|
|
227
|
+
"""
|
|
228
|
+
Valida que una contraseña cumpla con los siguientes requisitos:
|
|
229
|
+
- Al menos 8 caracteres de longitud
|
|
230
|
+
- Contiene al menos una letra mayúscula
|
|
231
|
+
- Contiene al menos una letra minúscula
|
|
232
|
+
- Contiene al menos un número
|
|
233
|
+
- Contiene al menos un carácter especial
|
|
234
|
+
"""
|
|
235
|
+
if len(password) < 8:
|
|
236
|
+
return False, "La contraseña debe tener al menos 8 caracteres."
|
|
237
|
+
|
|
238
|
+
if not any(char.isupper() for char in password):
|
|
239
|
+
return False, "La contraseña debe tener al menos una letra mayúscula."
|
|
240
|
+
|
|
241
|
+
if not any(char.islower() for char in password):
|
|
242
|
+
return False, "La contraseña debe tener al menos una letra minúscula."
|
|
243
|
+
|
|
244
|
+
if not any(char.isdigit() for char in password):
|
|
245
|
+
return False, "La contraseña debe tener al menos un número."
|
|
246
|
+
|
|
247
|
+
if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
|
|
248
|
+
return False, "La contraseña debe tener al menos un carácter especial."
|
|
249
|
+
|
|
250
|
+
return True, "La contraseña es válida."
|
|
251
|
+
|
|
252
|
+
def get_companies(self):
|
|
253
|
+
return self.profile_repo.get_companies()
|
|
254
|
+
|
|
255
|
+
def get_company_by_short_name(self, short_name: str) -> Company:
|
|
256
|
+
return self.profile_repo.get_company_by_short_name(short_name)
|
|
257
|
+
|
|
258
|
+
def new_api_key(self, company_short_name: str):
|
|
259
|
+
company = self.get_company_by_short_name(company_short_name)
|
|
260
|
+
if not company:
|
|
261
|
+
return {"error": f"la empresa {company_short_name} no existe"}
|
|
262
|
+
|
|
263
|
+
length = 40 # lenght of the api key
|
|
264
|
+
alphabet = string.ascii_letters + string.digits
|
|
265
|
+
key = ''.join(secrets.choice(alphabet) for i in range(length))
|
|
266
|
+
|
|
267
|
+
api_key = ApiKey(key=key, company_id=company.id)
|
|
268
|
+
self.profile_repo.create_api_key(api_key)
|
|
269
|
+
return {"message": f"La nueva clave de API para {company_short_name} es: {key}"}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def send_verification_email(self, new_user: User, company_short_name):
|
|
273
|
+
# send verification account email
|
|
274
|
+
subject = f"Verificación de Cuenta - {company_short_name}"
|
|
275
|
+
body = f"""
|
|
276
|
+
<!DOCTYPE html>
|
|
277
|
+
<html>
|
|
278
|
+
<head>
|
|
279
|
+
<meta charset="UTF-8">
|
|
280
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
281
|
+
<title>Verificación de Cuenta - {company_short_name}</title>
|
|
282
|
+
</head>
|
|
283
|
+
<body style="font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 0;">
|
|
284
|
+
<table role="presentation" width="100%" bgcolor="#f4f4f4" cellpadding="0" cellspacing="0" border="0">
|
|
285
|
+
<tr>
|
|
286
|
+
<td align="center">
|
|
287
|
+
<table role="presentation" width="600" bgcolor="#ffffff" cellpadding="20" cellspacing="0" border="0" style="border-radius: 8px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);">
|
|
288
|
+
|
|
289
|
+
<tr>
|
|
290
|
+
<td style="text-align: left; font-size: 16px; color: #333;">
|
|
291
|
+
<p>Hola <strong>{new_user.first_name}</strong>,</p>
|
|
292
|
+
<p>¡Bienvenido a <strong>{company_short_name}</strong>! Estamos encantados de tenerte con nosotros.</p>
|
|
293
|
+
<p>Para comenzar, verifica tu cuenta haciendo clic en el siguiente botón:</p>
|
|
294
|
+
<p style="text-align: center; margin: 20px 0;">
|
|
295
|
+
<a href="{new_user.verification_url}"
|
|
296
|
+
style="background-color: #007bff; color: #ffffff; text-decoration: none; padding: 12px 24px; border-radius: 5px; font-size: 16px; display: inline-block;">
|
|
297
|
+
Verificar Cuenta
|
|
298
|
+
</a>
|
|
299
|
+
</p>
|
|
300
|
+
<p>Si no puedes hacer clic en el botón, copia y pega el siguiente enlace en tu navegador:</p>
|
|
301
|
+
<p style="word-break: break-word; color: #007bff;">
|
|
302
|
+
<a href="{new_user.verification_url}"
|
|
303
|
+
style="color: #007bff;">
|
|
304
|
+
{new_user.verification_url}
|
|
305
|
+
</a>
|
|
306
|
+
</p>
|
|
307
|
+
<p>Si no creaste una cuenta en {company_short_name}, simplemente ignora este correo.</p>
|
|
308
|
+
<p>¡Gracias por unirte a nuestra comunidad!</p>
|
|
309
|
+
<p style="margin-top: 20px;">Saludos,<br><strong>El equipo de {company_short_name}</strong></p>
|
|
310
|
+
</td>
|
|
311
|
+
</tr>
|
|
312
|
+
</table>
|
|
313
|
+
<p style="font-size: 12px; color: #666; margin-top: 10px;">
|
|
314
|
+
Este es un correo automático, por favor no respondas a este mensaje.
|
|
315
|
+
</p>
|
|
316
|
+
</td>
|
|
317
|
+
</tr>
|
|
318
|
+
</table>
|
|
319
|
+
</body>
|
|
320
|
+
</html>
|
|
321
|
+
"""
|
|
322
|
+
self.mail_app.send_email(to=new_user.email, subject=subject, body=body)
|
|
323
|
+
|
|
324
|
+
def send_forgot_password_email(self, user: User, reset_url: str):
|
|
325
|
+
# send email to the user
|
|
326
|
+
subject = f"Recuperación de Contraseña "
|
|
327
|
+
body = f"""
|
|
328
|
+
<!DOCTYPE html>
|
|
329
|
+
<html>
|
|
330
|
+
<head>
|
|
331
|
+
<meta charset="UTF-8">
|
|
332
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
333
|
+
<title>Restablecer Contraseña </title>
|
|
334
|
+
</head>
|
|
335
|
+
<body style="font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 0;">
|
|
336
|
+
<table role="presentation" width="100%" bgcolor="#f4f4f4" cellpadding="0" cellspacing="0" border="0">
|
|
337
|
+
<tr>
|
|
338
|
+
<td align="center">
|
|
339
|
+
<table role="presentation" width="600" bgcolor="#ffffff" cellpadding="20" cellspacing="0" border="0" style="border-radius: 8px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);">
|
|
340
|
+
|
|
341
|
+
<tr>
|
|
342
|
+
<td style="text-align: left; font-size: 16px; color: #333;">
|
|
343
|
+
<p>Hola <strong>{user.first_name}</strong>,</p>
|
|
344
|
+
<p>Hemos recibido una solicitud para restablecer tu contraseña. </p>
|
|
345
|
+
<p>Utiliza el siguiente botón para ingresar tu código temporal y cambiar tu contraseña:</p>
|
|
346
|
+
<p style="text-align: center; margin: 20px 0;">
|
|
347
|
+
<a href="{reset_url}"
|
|
348
|
+
style="background-color: #007bff; color: #ffffff; text-decoration: none; padding: 12px 24px; border-radius: 5px; font-size: 16px; display: inline-block;">
|
|
349
|
+
Restablecer Contraseña
|
|
350
|
+
</a>
|
|
351
|
+
</p>
|
|
352
|
+
<p><strong>Tu código temporal es:</strong></p>
|
|
353
|
+
<p style="font-size: 20px; font-weight: bold; text-align: center; background-color: #f8f9fa; padding: 10px; border-radius: 5px; border: 1px solid #ccc;">
|
|
354
|
+
{user.temp_code}
|
|
355
|
+
</p>
|
|
356
|
+
<p>Si el botón no funciona, también puedes copiar y pegar el siguiente enlace en tu navegador:</p>
|
|
357
|
+
<p style="word-break: break-word; color: #007bff;">
|
|
358
|
+
<a href="{reset_url}" style="color: #007bff;">{reset_url}</a>
|
|
359
|
+
</p>
|
|
360
|
+
<p>Si no solicitaste este cambio, ignora este correo. Tu cuenta permanecerá segura.</p>
|
|
361
|
+
<p style="margin-top: 20px;">Saludos,<br><strong>El equipo de TI</strong></p>
|
|
362
|
+
</td>
|
|
363
|
+
</tr>
|
|
364
|
+
</table>
|
|
365
|
+
<p style="font-size: 12px; color: #666; margin-top: 10px;">
|
|
366
|
+
Este es un correo automático, por favor no respondas a este mensaje.
|
|
367
|
+
</p>
|
|
368
|
+
</td>
|
|
369
|
+
</tr>
|
|
370
|
+
</table>
|
|
371
|
+
</body>
|
|
372
|
+
</html>
|
|
373
|
+
"""
|
|
374
|
+
|
|
375
|
+
self.mail_app.send_email(to=user.email, subject=subject, body=body)
|
|
376
|
+
return {"message": "se envio mail para cambio de clave"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Producto: IAToolkit
|
|
3
|
+
# Todos los derechos reservados.
|
|
4
|
+
# En trámite de registro en el Registro de Propiedad Intelectual de Chile.
|
|
5
|
+
|
|
6
|
+
from injector import inject
|
|
7
|
+
from repositories.llm_query_repo import LLMQueryRepo
|
|
8
|
+
import logging
|
|
9
|
+
from repositories.profile_repo import ProfileRepo
|
|
10
|
+
from collections import defaultdict
|
|
11
|
+
from repositories.models import Prompt, PromptCategory, Company
|
|
12
|
+
import os
|
|
13
|
+
from common.exceptions import IAToolkitException
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PromptService:
|
|
17
|
+
@inject
|
|
18
|
+
def __init__(self, llm_query_repo: LLMQueryRepo, profile_repo: ProfileRepo):
|
|
19
|
+
self.llm_query_repo = llm_query_repo
|
|
20
|
+
self.profile_repo = profile_repo
|
|
21
|
+
|
|
22
|
+
def create_prompt(self,
|
|
23
|
+
prompt_name: str,
|
|
24
|
+
description: str,
|
|
25
|
+
order: int,
|
|
26
|
+
company: Company = None,
|
|
27
|
+
category: PromptCategory = None,
|
|
28
|
+
active: bool = True,
|
|
29
|
+
is_system_prompt: bool = False,
|
|
30
|
+
params: dict = {}
|
|
31
|
+
):
|
|
32
|
+
|
|
33
|
+
prompt_filename = prompt_name.lower() + '.prompt'
|
|
34
|
+
if is_system_prompt:
|
|
35
|
+
template_dir = 'prompts'
|
|
36
|
+
else:
|
|
37
|
+
template_dir = f'companies/{company.short_name}/prompts'
|
|
38
|
+
|
|
39
|
+
# Guardar el filepath como una ruta relativa
|
|
40
|
+
relative_prompt_path = os.path.join(template_dir, prompt_filename)
|
|
41
|
+
|
|
42
|
+
# Validar la existencia del archivo usando la ruta absoluta
|
|
43
|
+
absolute_prompt_path = os.path.join(os.getcwd(), relative_prompt_path)
|
|
44
|
+
if not os.path.exists(absolute_prompt_path):
|
|
45
|
+
raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
|
|
46
|
+
f'No existe el archivo de prompt: {absolute_prompt_path}')
|
|
47
|
+
|
|
48
|
+
prompt = Prompt(
|
|
49
|
+
company_id=company.id if company else None,
|
|
50
|
+
name=prompt_name,
|
|
51
|
+
description=description,
|
|
52
|
+
order=order,
|
|
53
|
+
category_id=category.id if category and not is_system_prompt else None,
|
|
54
|
+
active=active,
|
|
55
|
+
filepath=relative_prompt_path,
|
|
56
|
+
is_system_prompt=is_system_prompt,
|
|
57
|
+
parameters=params
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
self.llm_query_repo.create_or_update_prompt(prompt)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR,
|
|
64
|
+
f'error creating prompt "{prompt_name}": {str(e)}')
|
|
65
|
+
|
|
66
|
+
def get_prompt_content(self, company: Company, prompt_name: str):
|
|
67
|
+
try:
|
|
68
|
+
user_prompt_content = []
|
|
69
|
+
execution_dir = os.getcwd()
|
|
70
|
+
|
|
71
|
+
# get the user prompt
|
|
72
|
+
user_prompt = self.llm_query_repo.get_prompt_by_name(company, prompt_name)
|
|
73
|
+
if not user_prompt:
|
|
74
|
+
raise IAToolkitException(IAToolkitException.ErrorType.DOCUMENT_NOT_FOUND,
|
|
75
|
+
f"No se encontró el prompt '{prompt_name}' para la empresa '{company.short_name}'")
|
|
76
|
+
|
|
77
|
+
absolute_filepath = os.path.join(execution_dir, user_prompt.filepath)
|
|
78
|
+
if not os.path.exists(absolute_filepath):
|
|
79
|
+
raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
|
|
80
|
+
f"El archivo para el prompt '{prompt_name}' no existe: {absolute_filepath}")
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
with open(absolute_filepath, 'r', encoding='utf-8') as f:
|
|
84
|
+
user_prompt_content = f.read()
|
|
85
|
+
except Exception as e:
|
|
86
|
+
raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
|
|
87
|
+
f"Error leyendo el archivo de prompt '{prompt_name}' en {absolute_filepath}: {e}")
|
|
88
|
+
|
|
89
|
+
return user_prompt_content
|
|
90
|
+
|
|
91
|
+
except IAToolkitException:
|
|
92
|
+
# Vuelve a lanzar las IAToolkitException que ya hemos manejado
|
|
93
|
+
# para que no sean capturadas por el siguiente bloque.
|
|
94
|
+
raise
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logging.exception(
|
|
97
|
+
f"Error al obtener el contenido del prompt para la empresa '{company.short_name}' y prompt '{prompt_name}': {e}")
|
|
98
|
+
raise IAToolkitException(IAToolkitException.ErrorType.PROMPT_ERROR,
|
|
99
|
+
f'Error al obtener el contenido del prompt "{prompt_name}" para la empresa {company.short_name}: {str(e)}')
|
|
100
|
+
|
|
101
|
+
def get_system_prompt(self):
|
|
102
|
+
try:
|
|
103
|
+
system_prompt_content = []
|
|
104
|
+
|
|
105
|
+
# get the filepaths for all system prompts
|
|
106
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
107
|
+
src_dir = os.path.dirname(current_dir) # ../src
|
|
108
|
+
system_prompt_dir = os.path.join(src_dir, "prompts")
|
|
109
|
+
|
|
110
|
+
# Obtener, ordenar y leer los system prompts
|
|
111
|
+
system_prompts = self.llm_query_repo.get_system_prompts()
|
|
112
|
+
|
|
113
|
+
for prompt in system_prompts:
|
|
114
|
+
# Construir la ruta absoluta para leer el archivo
|
|
115
|
+
absolute_filepath = os.path.join(system_prompt_dir, prompt.filepath)
|
|
116
|
+
if not os.path.exists(absolute_filepath):
|
|
117
|
+
logging.warning(f"El archivo para el prompt de sistema no existe: {absolute_filepath}")
|
|
118
|
+
continue
|
|
119
|
+
try:
|
|
120
|
+
with open(absolute_filepath, 'r', encoding='utf-8') as f:
|
|
121
|
+
system_prompt_content.append(f.read())
|
|
122
|
+
except Exception as e:
|
|
123
|
+
raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
|
|
124
|
+
f"Error leyendo el archivo de prompt del sistema {absolute_filepath}: {e}")
|
|
125
|
+
|
|
126
|
+
# Unir todo el contenido en un solo string
|
|
127
|
+
return "\n".join(system_prompt_content)
|
|
128
|
+
|
|
129
|
+
except IAToolkitException:
|
|
130
|
+
raise
|
|
131
|
+
except Exception as e:
|
|
132
|
+
logging.exception(
|
|
133
|
+
f"Error al obtener el contenido del prompt de sistema: {e}")
|
|
134
|
+
raise IAToolkitException(IAToolkitException.ErrorType.PROMPT_ERROR,
|
|
135
|
+
f'Error al obtener el contenido de los prompts de sistema": {str(e)}')
|
|
136
|
+
|
|
137
|
+
def get_user_prompts(self, company_short_name: str) -> dict:
|
|
138
|
+
try:
|
|
139
|
+
# validate company
|
|
140
|
+
company = self.profile_repo.get_company_by_short_name(company_short_name)
|
|
141
|
+
if not company:
|
|
142
|
+
return {'error': f'No existe la empresa: {company_short_name}'}
|
|
143
|
+
|
|
144
|
+
# get all the prompts
|
|
145
|
+
all_prompts = self.llm_query_repo.get_prompts(company)
|
|
146
|
+
|
|
147
|
+
# Agrupar prompts por categoría
|
|
148
|
+
prompts_by_category = defaultdict(list)
|
|
149
|
+
for prompt in all_prompts:
|
|
150
|
+
if prompt.active:
|
|
151
|
+
if prompt.category:
|
|
152
|
+
cat_key = (prompt.category.order, prompt.category.name)
|
|
153
|
+
prompts_by_category[cat_key].append(prompt)
|
|
154
|
+
|
|
155
|
+
# Ordenar los prompts dentro de cada categoría
|
|
156
|
+
for cat_key in prompts_by_category:
|
|
157
|
+
prompts_by_category[cat_key].sort(key=lambda p: p.order)
|
|
158
|
+
|
|
159
|
+
# Crear la estructura de respuesta final, ordenada por la categoría
|
|
160
|
+
categorized_prompts = []
|
|
161
|
+
|
|
162
|
+
# Ordenar las categorías por su 'order'
|
|
163
|
+
sorted_categories = sorted(prompts_by_category.items(), key=lambda item: item[0][0])
|
|
164
|
+
|
|
165
|
+
for (cat_order, cat_name), prompts in sorted_categories:
|
|
166
|
+
categorized_prompts.append({
|
|
167
|
+
'category_name': cat_name,
|
|
168
|
+
'category_order': cat_order,
|
|
169
|
+
'prompts': [
|
|
170
|
+
{'prompt': p.name, 'description': p.description, 'order': p.order}
|
|
171
|
+
for p in prompts
|
|
172
|
+
]
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
return {'message': categorized_prompts}
|
|
176
|
+
|
|
177
|
+
except Exception as e:
|
|
178
|
+
logging.error(f"Error en get_prompts: {e}")
|
|
179
|
+
return {'error': str(e)}
|
|
180
|
+
|