iatoolkit 0.71.4__py3-none-any.whl → 1.4.2__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.
- iatoolkit/__init__.py +19 -7
- iatoolkit/base_company.py +1 -71
- iatoolkit/cli_commands.py +9 -21
- iatoolkit/common/exceptions.py +2 -0
- iatoolkit/common/interfaces/__init__.py +0 -0
- iatoolkit/common/interfaces/asset_storage.py +34 -0
- iatoolkit/common/interfaces/database_provider.py +38 -0
- iatoolkit/common/model_registry.py +159 -0
- iatoolkit/common/routes.py +53 -32
- iatoolkit/common/util.py +17 -12
- iatoolkit/company_registry.py +55 -14
- iatoolkit/{iatoolkit.py → core.py} +102 -72
- iatoolkit/infra/{mail_app.py → brevo_mail_app.py} +15 -37
- iatoolkit/infra/llm_providers/__init__.py +0 -0
- iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
- iatoolkit/infra/{gemini_adapter.py → llm_providers/gemini_adapter.py} +11 -17
- iatoolkit/infra/{openai_adapter.py → llm_providers/openai_adapter.py} +41 -7
- iatoolkit/infra/llm_proxy.py +235 -134
- iatoolkit/infra/llm_response.py +5 -0
- iatoolkit/locales/en.yaml +134 -4
- iatoolkit/locales/es.yaml +293 -162
- iatoolkit/repositories/database_manager.py +92 -22
- iatoolkit/repositories/document_repo.py +7 -0
- iatoolkit/repositories/filesystem_asset_repository.py +36 -0
- iatoolkit/repositories/llm_query_repo.py +36 -22
- iatoolkit/repositories/models.py +86 -95
- iatoolkit/repositories/profile_repo.py +64 -13
- iatoolkit/repositories/vs_repo.py +31 -28
- iatoolkit/services/auth_service.py +1 -1
- iatoolkit/services/branding_service.py +1 -1
- iatoolkit/services/company_context_service.py +96 -39
- iatoolkit/services/configuration_service.py +329 -67
- iatoolkit/services/dispatcher_service.py +51 -227
- iatoolkit/services/document_service.py +10 -1
- iatoolkit/services/embedding_service.py +9 -6
- iatoolkit/services/excel_service.py +50 -2
- iatoolkit/services/file_processor_service.py +0 -5
- iatoolkit/services/history_manager_service.py +208 -0
- iatoolkit/services/jwt_service.py +1 -1
- iatoolkit/services/knowledge_base_service.py +412 -0
- iatoolkit/services/language_service.py +8 -2
- iatoolkit/services/license_service.py +82 -0
- iatoolkit/{infra/llm_client.py → services/llm_client_service.py} +42 -29
- iatoolkit/services/load_documents_service.py +18 -47
- iatoolkit/services/mail_service.py +171 -25
- iatoolkit/services/profile_service.py +69 -36
- iatoolkit/services/{prompt_manager_service.py → prompt_service.py} +136 -25
- iatoolkit/services/query_service.py +229 -203
- iatoolkit/services/sql_service.py +116 -34
- iatoolkit/services/tool_service.py +246 -0
- iatoolkit/services/user_feedback_service.py +18 -6
- iatoolkit/services/user_session_context_service.py +121 -51
- iatoolkit/static/images/iatoolkit_core.png +0 -0
- iatoolkit/static/images/iatoolkit_logo.png +0 -0
- iatoolkit/static/js/chat_feedback_button.js +1 -1
- iatoolkit/static/js/chat_help_content.js +4 -4
- iatoolkit/static/js/chat_main.js +61 -9
- iatoolkit/static/js/chat_model_selector.js +227 -0
- iatoolkit/static/js/chat_onboarding_button.js +1 -1
- iatoolkit/static/js/chat_reload_button.js +4 -1
- iatoolkit/static/styles/chat_iatoolkit.css +59 -3
- iatoolkit/static/styles/chat_public.css +28 -0
- iatoolkit/static/styles/documents.css +598 -0
- iatoolkit/static/styles/landing_page.css +223 -7
- iatoolkit/static/styles/llm_output.css +34 -1
- iatoolkit/system_prompts/__init__.py +0 -0
- iatoolkit/system_prompts/query_main.prompt +28 -3
- iatoolkit/system_prompts/sql_rules.prompt +47 -12
- iatoolkit/templates/_company_header.html +30 -5
- iatoolkit/templates/_login_widget.html +3 -3
- iatoolkit/templates/base.html +13 -0
- iatoolkit/templates/chat.html +45 -3
- iatoolkit/templates/forgot_password.html +3 -2
- iatoolkit/templates/onboarding_shell.html +1 -2
- iatoolkit/templates/signup.html +3 -0
- iatoolkit/views/base_login_view.py +8 -3
- iatoolkit/views/change_password_view.py +1 -1
- iatoolkit/views/chat_view.py +76 -0
- iatoolkit/views/forgot_password_view.py +9 -4
- iatoolkit/views/history_api_view.py +3 -3
- iatoolkit/views/home_view.py +4 -2
- iatoolkit/views/init_context_api_view.py +1 -1
- iatoolkit/views/llmquery_api_view.py +4 -3
- iatoolkit/views/load_company_configuration_api_view.py +49 -0
- iatoolkit/views/{file_store_api_view.py → load_document_api_view.py} +15 -11
- iatoolkit/views/login_view.py +25 -8
- iatoolkit/views/logout_api_view.py +10 -2
- iatoolkit/views/prompt_api_view.py +1 -1
- iatoolkit/views/rag_api_view.py +216 -0
- iatoolkit/views/root_redirect_view.py +22 -0
- iatoolkit/views/signup_view.py +12 -4
- iatoolkit/views/static_page_view.py +27 -0
- iatoolkit/views/users_api_view.py +33 -0
- iatoolkit/views/verify_user_view.py +1 -1
- iatoolkit-1.4.2.dist-info/METADATA +268 -0
- iatoolkit-1.4.2.dist-info/RECORD +133 -0
- iatoolkit-1.4.2.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
- iatoolkit/repositories/tasks_repo.py +0 -52
- iatoolkit/services/history_service.py +0 -37
- iatoolkit/services/search_service.py +0 -55
- iatoolkit/services/tasks_service.py +0 -188
- iatoolkit/templates/about.html +0 -13
- iatoolkit/templates/index.html +0 -145
- iatoolkit/templates/login_simulation.html +0 -45
- iatoolkit/views/external_login_view.py +0 -73
- iatoolkit/views/index_view.py +0 -14
- iatoolkit/views/login_simulation_view.py +0 -93
- iatoolkit/views/tasks_api_view.py +0 -72
- iatoolkit/views/tasks_review_api_view.py +0 -55
- iatoolkit-0.71.4.dist-info/METADATA +0 -276
- iatoolkit-0.71.4.dist-info/RECORD +0 -122
- {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/WHEEL +0 -0
- {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/licenses/LICENSE +0 -0
- {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/top_level.txt +0 -0
|
@@ -9,15 +9,18 @@ from iatoolkit.services.i18n_service import I18nService
|
|
|
9
9
|
from iatoolkit.repositories.models import User, Company, ApiKey
|
|
10
10
|
from flask_bcrypt import check_password_hash
|
|
11
11
|
from iatoolkit.common.session_manager import SessionManager
|
|
12
|
+
from iatoolkit.services.dispatcher_service import Dispatcher
|
|
13
|
+
from iatoolkit.services.language_service import LanguageService
|
|
12
14
|
from iatoolkit.services.user_session_context_service import UserSessionContextService
|
|
15
|
+
from iatoolkit.services.configuration_service import ConfigurationService
|
|
13
16
|
from flask_bcrypt import Bcrypt
|
|
14
|
-
from iatoolkit.
|
|
17
|
+
from iatoolkit.services.mail_service import MailService
|
|
15
18
|
import random
|
|
16
19
|
import re
|
|
17
20
|
import secrets
|
|
18
21
|
import string
|
|
19
22
|
import logging
|
|
20
|
-
from
|
|
23
|
+
from typing import List, Dict
|
|
21
24
|
|
|
22
25
|
|
|
23
26
|
class ProfileService:
|
|
@@ -26,16 +29,19 @@ class ProfileService:
|
|
|
26
29
|
i18n_service: I18nService,
|
|
27
30
|
profile_repo: ProfileRepo,
|
|
28
31
|
session_context_service: UserSessionContextService,
|
|
32
|
+
config_service: ConfigurationService,
|
|
33
|
+
lang_service: LanguageService,
|
|
29
34
|
dispatcher: Dispatcher,
|
|
30
|
-
|
|
35
|
+
mail_service: MailService):
|
|
31
36
|
self.i18n_service = i18n_service
|
|
32
37
|
self.profile_repo = profile_repo
|
|
33
38
|
self.dispatcher = dispatcher
|
|
34
39
|
self.session_context = session_context_service
|
|
35
|
-
self.
|
|
40
|
+
self.config_service = config_service
|
|
41
|
+
self.lang_service = lang_service
|
|
42
|
+
self.mail_service = mail_service
|
|
36
43
|
self.bcrypt = Bcrypt()
|
|
37
44
|
|
|
38
|
-
|
|
39
45
|
def login(self, company_short_name: str, email: str, password: str) -> dict:
|
|
40
46
|
try:
|
|
41
47
|
# check if user exists
|
|
@@ -59,13 +65,17 @@ class ProfileService:
|
|
|
59
65
|
return {'success': False,
|
|
60
66
|
"message": self.i18n_service.t('errors.services.account_not_verified')}
|
|
61
67
|
|
|
68
|
+
user_role = self.profile_repo.get_user_role_in_company(company.id, user.id)
|
|
69
|
+
|
|
62
70
|
# 1. Build the local user profile dictionary here.
|
|
63
71
|
# the user_profile variables are used on the LLM templates also (see in query_main.prompt)
|
|
64
|
-
user_identifier = user.email
|
|
72
|
+
user_identifier = user.email
|
|
65
73
|
user_profile = {
|
|
66
74
|
"user_email": user.email,
|
|
67
75
|
"user_fullname": f'{user.first_name} {user.last_name}',
|
|
68
76
|
"user_is_local": True,
|
|
77
|
+
"user_id": user.id,
|
|
78
|
+
"user_role": user_role,
|
|
69
79
|
"extras": {}
|
|
70
80
|
}
|
|
71
81
|
|
|
@@ -79,25 +89,6 @@ class ProfileService:
|
|
|
79
89
|
logging.error(f"Error in login: {e}")
|
|
80
90
|
return {'success': False, "message": str(e)}
|
|
81
91
|
|
|
82
|
-
def create_external_user_profile_context(self, company: Company, user_identifier: str):
|
|
83
|
-
"""
|
|
84
|
-
Public method for views to create a user profile context for an external user.
|
|
85
|
-
"""
|
|
86
|
-
# 1. Fetch the external user profile via Dispatcher.
|
|
87
|
-
external_user_profile = self.dispatcher.get_user_info(
|
|
88
|
-
company_name=company.short_name,
|
|
89
|
-
user_identifier=user_identifier
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
# 2. Call the session creation helper with external_user_id as user_identifier
|
|
93
|
-
self.save_user_profile(
|
|
94
|
-
company=company,
|
|
95
|
-
user_identifier=user_identifier,
|
|
96
|
-
user_profile=external_user_profile)
|
|
97
|
-
|
|
98
|
-
# 3. make sure the flask session is clean
|
|
99
|
-
SessionManager.clear()
|
|
100
|
-
|
|
101
92
|
def save_user_profile(self, company: Company, user_identifier: str, user_profile: dict):
|
|
102
93
|
"""
|
|
103
94
|
Private helper: Takes a pre-built profile, saves it to Redis, and sets the Flask cookie.
|
|
@@ -107,6 +98,7 @@ class ProfileService:
|
|
|
107
98
|
user_profile['id'] = user_identifier
|
|
108
99
|
user_profile['company_id'] = company.id
|
|
109
100
|
user_profile['company'] = company.name
|
|
101
|
+
user_profile['language'] = self.lang_service.get_current_language()
|
|
110
102
|
|
|
111
103
|
# save user_profile in Redis session
|
|
112
104
|
self.session_context.save_profile_data(company.short_name, user_identifier, user_profile)
|
|
@@ -214,24 +206,34 @@ class ProfileService:
|
|
|
214
206
|
# encrypt the password
|
|
215
207
|
hashed_password = self.bcrypt.generate_password_hash(password).decode('utf-8')
|
|
216
208
|
|
|
209
|
+
# account verification can be skiped with this security parameter
|
|
210
|
+
verified = False
|
|
211
|
+
cfg = self.config_service.get_configuration(company_short_name, 'parameters')
|
|
212
|
+
if cfg and not cfg.get('verify_account', True):
|
|
213
|
+
verified = True
|
|
214
|
+
message = self.i18n_service.t('flash_messages.signup_success_no_verification')
|
|
215
|
+
|
|
217
216
|
# create the new user
|
|
218
217
|
new_user = User(email=email,
|
|
219
218
|
password=hashed_password,
|
|
220
219
|
first_name=first_name.lower(),
|
|
221
220
|
last_name=last_name.lower(),
|
|
222
|
-
verified=
|
|
221
|
+
verified=verified,
|
|
223
222
|
verification_url=verification_url
|
|
224
223
|
)
|
|
225
224
|
|
|
226
225
|
# associate new company to user
|
|
227
226
|
new_user.companies.append(company)
|
|
228
227
|
|
|
228
|
+
# and create in the database
|
|
229
229
|
self.profile_repo.create_user(new_user)
|
|
230
230
|
|
|
231
231
|
# send email with verification
|
|
232
|
-
|
|
232
|
+
if not cfg or cfg.get('verify_account', True):
|
|
233
|
+
self.send_verification_email(new_user, company_short_name)
|
|
234
|
+
message = self.i18n_service.t('flash_messages.signup_success')
|
|
233
235
|
|
|
234
|
-
return {"message":
|
|
236
|
+
return {"message": message}
|
|
235
237
|
except Exception as e:
|
|
236
238
|
return {"error": self.i18n_service.t('errors.general.unexpected_error', error=str(e))}
|
|
237
239
|
|
|
@@ -275,7 +277,7 @@ class ProfileService:
|
|
|
275
277
|
except Exception as e:
|
|
276
278
|
return {"error": self.i18n_service.t('errors.general.unexpected_error')}
|
|
277
279
|
|
|
278
|
-
def forgot_password(self, email: str, reset_url: str):
|
|
280
|
+
def forgot_password(self, company_short_name: str, email: str, reset_url: str):
|
|
279
281
|
try:
|
|
280
282
|
# Verificar si el usuario existe
|
|
281
283
|
user = self.profile_repo.get_user_by_email(email)
|
|
@@ -287,7 +289,7 @@ class ProfileService:
|
|
|
287
289
|
self.profile_repo.set_temp_code(email, temp_code)
|
|
288
290
|
|
|
289
291
|
# send email to the user
|
|
290
|
-
self.send_forgot_password_email(user, reset_url)
|
|
292
|
+
self.send_forgot_password_email(company_short_name, user, reset_url)
|
|
291
293
|
|
|
292
294
|
return {"message": self.i18n_service.t('flash_messages.forgot_password_success')}
|
|
293
295
|
except Exception as e:
|
|
@@ -321,19 +323,44 @@ class ProfileService:
|
|
|
321
323
|
def get_company_by_short_name(self, short_name: str) -> Company:
|
|
322
324
|
return self.profile_repo.get_company_by_short_name(short_name)
|
|
323
325
|
|
|
326
|
+
def get_company_users(self, company_short_name: str) -> List[Dict]:
|
|
327
|
+
company = self.profile_repo.get_company_by_short_name(company_short_name)
|
|
328
|
+
if not company:
|
|
329
|
+
return []
|
|
330
|
+
|
|
331
|
+
# get the company users from the repo
|
|
332
|
+
company_users = self.profile_repo.get_company_users_with_details(company_short_name)
|
|
333
|
+
|
|
334
|
+
users_data = []
|
|
335
|
+
for user, role, last_access in company_users:
|
|
336
|
+
users_data.append({
|
|
337
|
+
"first_name": user.first_name,
|
|
338
|
+
"last_name": user.last_name,
|
|
339
|
+
"email": user.email,
|
|
340
|
+
"created": user.created_at,
|
|
341
|
+
"verified": user.verified,
|
|
342
|
+
"role": role or "user",
|
|
343
|
+
"last_access": last_access
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
return users_data
|
|
347
|
+
|
|
324
348
|
def get_active_api_key_entry(self, api_key_value: str) -> ApiKey | None:
|
|
325
349
|
return self.profile_repo.get_active_api_key_entry(api_key_value)
|
|
326
350
|
|
|
327
|
-
def new_api_key(self, company_short_name: str):
|
|
351
|
+
def new_api_key(self, company_short_name: str, key_name: str):
|
|
328
352
|
company = self.get_company_by_short_name(company_short_name)
|
|
329
353
|
if not company:
|
|
330
354
|
return {"error": self.i18n_service.t('errors.company_not_found', company_short_name=company_short_name)}
|
|
331
355
|
|
|
356
|
+
if not key_name:
|
|
357
|
+
return {"error": self.i18n_service.t('errors.auth.api_key_name_required')}
|
|
358
|
+
|
|
332
359
|
length = 40 # lenght of the api key
|
|
333
360
|
alphabet = string.ascii_letters + string.digits
|
|
334
361
|
key = ''.join(secrets.choice(alphabet) for i in range(length))
|
|
335
362
|
|
|
336
|
-
api_key = ApiKey(key=key, company_id=company.id)
|
|
363
|
+
api_key = ApiKey(key=key, company_id=company.id, key_name=key_name)
|
|
337
364
|
self.profile_repo.create_api_key(api_key)
|
|
338
365
|
return {"api-key": key}
|
|
339
366
|
|
|
@@ -388,9 +415,12 @@ class ProfileService:
|
|
|
388
415
|
</body>
|
|
389
416
|
</html>
|
|
390
417
|
"""
|
|
391
|
-
self.
|
|
418
|
+
self.mail_service.send_mail(company_short_name=company_short_name,
|
|
419
|
+
recipient=new_user.email,
|
|
420
|
+
subject=subject,
|
|
421
|
+
body=body)
|
|
392
422
|
|
|
393
|
-
def send_forgot_password_email(self, user: User, reset_url: str):
|
|
423
|
+
def send_forgot_password_email(self, company_short_name: str, user: User, reset_url: str):
|
|
394
424
|
# send email to the user
|
|
395
425
|
subject = f"Recuperación de Contraseña "
|
|
396
426
|
body = f"""
|
|
@@ -441,5 +471,8 @@ class ProfileService:
|
|
|
441
471
|
</html>
|
|
442
472
|
"""
|
|
443
473
|
|
|
444
|
-
self.
|
|
445
|
-
|
|
474
|
+
self.mail_service.send_mail(company_short_name=company_short_name,
|
|
475
|
+
recipient=user.email,
|
|
476
|
+
subject=subject,
|
|
477
|
+
body=body)
|
|
478
|
+
return {"message": self.i18n_service.t('services.mail_change_password') }
|
|
@@ -4,27 +4,144 @@
|
|
|
4
4
|
# IAToolkit is open source software.
|
|
5
5
|
|
|
6
6
|
from injector import inject
|
|
7
|
+
from iatoolkit.common.interfaces.asset_storage import AssetRepository, AssetType
|
|
7
8
|
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
8
9
|
from iatoolkit.services.i18n_service import I18nService
|
|
9
10
|
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
10
11
|
from collections import defaultdict
|
|
11
12
|
from iatoolkit.repositories.models import Prompt, PromptCategory, Company
|
|
12
|
-
import os
|
|
13
13
|
from iatoolkit.common.exceptions import IAToolkitException
|
|
14
14
|
import importlib.resources
|
|
15
15
|
import logging
|
|
16
|
+
import os
|
|
16
17
|
|
|
18
|
+
# iatoolkit system prompts definitions
|
|
19
|
+
_SYSTEM_PROMPTS = [
|
|
20
|
+
{'name': 'query_main', 'description': 'iatoolkit main prompt'},
|
|
21
|
+
{'name': 'format_styles', 'description': 'output format styles'},
|
|
22
|
+
{'name': 'sql_rules', 'description': 'instructions for SQL queries'}
|
|
23
|
+
]
|
|
17
24
|
|
|
18
25
|
class PromptService:
|
|
19
26
|
@inject
|
|
20
27
|
def __init__(self,
|
|
28
|
+
asset_repo: AssetRepository,
|
|
21
29
|
llm_query_repo: LLMQueryRepo,
|
|
22
30
|
profile_repo: ProfileRepo,
|
|
23
31
|
i18n_service: I18nService):
|
|
32
|
+
self.asset_repo = asset_repo
|
|
24
33
|
self.llm_query_repo = llm_query_repo
|
|
25
34
|
self.profile_repo = profile_repo
|
|
26
35
|
self.i18n_service = i18n_service
|
|
27
36
|
|
|
37
|
+
def sync_company_prompts(self, company_short_name: str, prompts_config: list, categories_config: list):
|
|
38
|
+
"""
|
|
39
|
+
Synchronizes prompt categories and prompts from YAML config to Database.
|
|
40
|
+
Strategies:
|
|
41
|
+
- Categories: Create or Update existing based on name.
|
|
42
|
+
- Prompts: Create or Update existing based on name. Soft-delete or Delete unused.
|
|
43
|
+
"""
|
|
44
|
+
if not prompts_config:
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
company = self.profile_repo.get_company_by_short_name(company_short_name)
|
|
48
|
+
if not company:
|
|
49
|
+
raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
|
|
50
|
+
f'Company {company_short_name} not found')
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
# 1. Sync Categories
|
|
54
|
+
category_map = {}
|
|
55
|
+
|
|
56
|
+
for i, category_name in enumerate(categories_config):
|
|
57
|
+
category_obj = PromptCategory(
|
|
58
|
+
company_id=company.id,
|
|
59
|
+
name=category_name,
|
|
60
|
+
order=i + 1
|
|
61
|
+
)
|
|
62
|
+
# Persist and get back the object with ID
|
|
63
|
+
persisted_cat = self.llm_query_repo.create_or_update_prompt_category(category_obj)
|
|
64
|
+
category_map[category_name] = persisted_cat
|
|
65
|
+
|
|
66
|
+
# 2. Sync Prompts
|
|
67
|
+
defined_prompt_names = set()
|
|
68
|
+
|
|
69
|
+
for prompt_data in prompts_config:
|
|
70
|
+
category_name = prompt_data.get('category')
|
|
71
|
+
if not category_name or category_name not in category_map:
|
|
72
|
+
logging.warning(
|
|
73
|
+
f"⚠️ Warning: Prompt '{prompt_data['name']}' has an invalid or missing category. Skipping.")
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
prompt_name = prompt_data['name']
|
|
77
|
+
defined_prompt_names.add(prompt_name)
|
|
78
|
+
|
|
79
|
+
category_obj = category_map[category_name]
|
|
80
|
+
filename = f"{prompt_name}.prompt"
|
|
81
|
+
|
|
82
|
+
new_prompt = Prompt(
|
|
83
|
+
company_id=company.id,
|
|
84
|
+
name=prompt_name,
|
|
85
|
+
description=prompt_data.get('description'),
|
|
86
|
+
order=prompt_data.get('order'),
|
|
87
|
+
category_id=category_obj.id,
|
|
88
|
+
active=prompt_data.get('active', True),
|
|
89
|
+
is_system_prompt=False,
|
|
90
|
+
filename=filename,
|
|
91
|
+
custom_fields=prompt_data.get('custom_fields', [])
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
self.llm_query_repo.create_or_update_prompt(new_prompt)
|
|
95
|
+
|
|
96
|
+
# 3. Cleanup: Delete prompts present in DB but not in Config
|
|
97
|
+
existing_prompts = self.llm_query_repo.get_prompts(company)
|
|
98
|
+
for p in existing_prompts:
|
|
99
|
+
if p.name not in defined_prompt_names:
|
|
100
|
+
# Using hard delete to keep consistent with previous "refresh" behavior
|
|
101
|
+
self.llm_query_repo.session.delete(p)
|
|
102
|
+
|
|
103
|
+
self.llm_query_repo.commit()
|
|
104
|
+
|
|
105
|
+
except Exception as e:
|
|
106
|
+
self.llm_query_repo.rollback()
|
|
107
|
+
raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR, str(e))
|
|
108
|
+
|
|
109
|
+
def register_system_prompts(self):
|
|
110
|
+
"""
|
|
111
|
+
Synchronizes system prompts defined in Dispatcher/Code to Database.
|
|
112
|
+
"""
|
|
113
|
+
try:
|
|
114
|
+
defined_names = set()
|
|
115
|
+
|
|
116
|
+
for i, prompt_data in enumerate(_SYSTEM_PROMPTS):
|
|
117
|
+
prompt_name = prompt_data['name']
|
|
118
|
+
defined_names.add(prompt_name)
|
|
119
|
+
|
|
120
|
+
new_prompt = Prompt(
|
|
121
|
+
company_id=None, # System prompts have no company
|
|
122
|
+
name=prompt_name,
|
|
123
|
+
description=prompt_data['description'],
|
|
124
|
+
order=i + 1,
|
|
125
|
+
category_id=None,
|
|
126
|
+
active=True,
|
|
127
|
+
is_system_prompt=True,
|
|
128
|
+
filename=f"{prompt_name}.prompt",
|
|
129
|
+
custom_fields=[]
|
|
130
|
+
)
|
|
131
|
+
self.llm_query_repo.create_or_update_prompt(new_prompt)
|
|
132
|
+
|
|
133
|
+
# Cleanup old system prompts
|
|
134
|
+
existing_sys_prompts = self.llm_query_repo.get_system_prompts()
|
|
135
|
+
for p in existing_sys_prompts:
|
|
136
|
+
if p.name not in defined_names:
|
|
137
|
+
self.llm_query_repo.session.delete(p)
|
|
138
|
+
|
|
139
|
+
self.llm_query_repo.commit()
|
|
140
|
+
|
|
141
|
+
except Exception as e:
|
|
142
|
+
self.llm_query_repo.rollback()
|
|
143
|
+
raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR, str(e))
|
|
144
|
+
|
|
28
145
|
def create_prompt(self,
|
|
29
146
|
prompt_name: str,
|
|
30
147
|
description: str,
|
|
@@ -35,19 +152,19 @@ class PromptService:
|
|
|
35
152
|
is_system_prompt: bool = False,
|
|
36
153
|
custom_fields: list = []
|
|
37
154
|
):
|
|
38
|
-
|
|
155
|
+
"""
|
|
156
|
+
Direct creation method (used by sync or direct calls).
|
|
157
|
+
Validates file existence before creating DB entry.
|
|
158
|
+
"""
|
|
39
159
|
prompt_filename = prompt_name.lower() + '.prompt'
|
|
40
160
|
if is_system_prompt:
|
|
41
161
|
if not importlib.resources.files('iatoolkit.system_prompts').joinpath(prompt_filename).is_file():
|
|
42
162
|
raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
|
|
43
163
|
f'missing system prompt file: {prompt_filename}')
|
|
44
164
|
else:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
relative_prompt_path = os.path.join(template_dir, prompt_filename)
|
|
48
|
-
if not os.path.exists(relative_prompt_path):
|
|
165
|
+
if not self.asset_repo.exists(company.short_name, AssetType.PROMPT, prompt_filename):
|
|
49
166
|
raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
|
|
50
|
-
f'missing prompt file: {
|
|
167
|
+
f'missing prompt file: {prompt_filename} in prompts/')
|
|
51
168
|
|
|
52
169
|
if custom_fields:
|
|
53
170
|
for f in custom_fields:
|
|
@@ -79,33 +196,28 @@ class PromptService:
|
|
|
79
196
|
|
|
80
197
|
def get_prompt_content(self, company: Company, prompt_name: str):
|
|
81
198
|
try:
|
|
82
|
-
user_prompt_content = []
|
|
83
|
-
execution_dir = os.getcwd()
|
|
84
|
-
|
|
85
199
|
# get the user prompt
|
|
86
200
|
user_prompt = self.llm_query_repo.get_prompt_by_name(company, prompt_name)
|
|
87
201
|
if not user_prompt:
|
|
88
202
|
raise IAToolkitException(IAToolkitException.ErrorType.DOCUMENT_NOT_FOUND,
|
|
89
203
|
f"prompt not found '{prompt_name}' for company '{company.short_name}'")
|
|
90
204
|
|
|
91
|
-
prompt_file = f'companies/{company.short_name}/prompts/{user_prompt.filename}'
|
|
92
|
-
absolute_filepath = os.path.join(execution_dir, prompt_file)
|
|
93
|
-
if not os.path.exists(absolute_filepath):
|
|
94
|
-
raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
|
|
95
|
-
f"prompt file '{prompt_name}' does not exist: {absolute_filepath}")
|
|
96
|
-
|
|
97
205
|
try:
|
|
98
|
-
|
|
99
|
-
|
|
206
|
+
user_prompt_content = self.asset_repo.read_text(
|
|
207
|
+
company.short_name,
|
|
208
|
+
AssetType.PROMPT,
|
|
209
|
+
user_prompt.filename
|
|
210
|
+
)
|
|
211
|
+
except FileNotFoundError:
|
|
212
|
+
raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
|
|
213
|
+
f"prompt file '{user_prompt.filename}' does not exist for company '{company.short_name}'")
|
|
100
214
|
except Exception as e:
|
|
101
215
|
raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
|
|
102
|
-
|
|
216
|
+
f"error while reading prompt: '{prompt_name}': {e}")
|
|
103
217
|
|
|
104
218
|
return user_prompt_content
|
|
105
219
|
|
|
106
220
|
except IAToolkitException:
|
|
107
|
-
# Vuelve a lanzar las IAToolkitException que ya hemos manejado
|
|
108
|
-
# para que no sean capturadas por el siguiente bloque.
|
|
109
221
|
raise
|
|
110
222
|
except Exception as e:
|
|
111
223
|
logging.exception(
|
|
@@ -151,7 +263,7 @@ class PromptService:
|
|
|
151
263
|
# get all the prompts
|
|
152
264
|
all_prompts = self.llm_query_repo.get_prompts(company)
|
|
153
265
|
|
|
154
|
-
#
|
|
266
|
+
# group by category
|
|
155
267
|
prompts_by_category = defaultdict(list)
|
|
156
268
|
for prompt in all_prompts:
|
|
157
269
|
if prompt.active:
|
|
@@ -159,14 +271,13 @@ class PromptService:
|
|
|
159
271
|
cat_key = (prompt.category.order, prompt.category.name)
|
|
160
272
|
prompts_by_category[cat_key].append(prompt)
|
|
161
273
|
|
|
162
|
-
#
|
|
274
|
+
# sort each category by order
|
|
163
275
|
for cat_key in prompts_by_category:
|
|
164
276
|
prompts_by_category[cat_key].sort(key=lambda p: p.order)
|
|
165
277
|
|
|
166
|
-
# Crear la estructura de respuesta final, ordenada por la categoría
|
|
167
278
|
categorized_prompts = []
|
|
168
279
|
|
|
169
|
-
#
|
|
280
|
+
# sort categories by order
|
|
170
281
|
sorted_categories = sorted(prompts_by_category.items(), key=lambda item: item[0][0])
|
|
171
282
|
|
|
172
283
|
for (cat_order, cat_name), prompts in sorted_categories:
|