iatoolkit 0.3.9__py3-none-any.whl → 0.107.4__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 +27 -35
- iatoolkit/base_company.py +3 -35
- iatoolkit/cli_commands.py +18 -47
- iatoolkit/common/__init__.py +0 -0
- iatoolkit/common/exceptions.py +48 -0
- iatoolkit/common/interfaces/__init__.py +0 -0
- iatoolkit/common/interfaces/asset_storage.py +34 -0
- iatoolkit/common/interfaces/database_provider.py +39 -0
- iatoolkit/common/model_registry.py +159 -0
- iatoolkit/common/routes.py +138 -0
- iatoolkit/common/session_manager.py +26 -0
- iatoolkit/common/util.py +353 -0
- iatoolkit/company_registry.py +66 -29
- iatoolkit/core.py +514 -0
- iatoolkit/infra/__init__.py +5 -0
- iatoolkit/infra/brevo_mail_app.py +123 -0
- iatoolkit/infra/call_service.py +140 -0
- iatoolkit/infra/connectors/__init__.py +5 -0
- iatoolkit/infra/connectors/file_connector.py +17 -0
- iatoolkit/infra/connectors/file_connector_factory.py +57 -0
- iatoolkit/infra/connectors/google_cloud_storage_connector.py +53 -0
- iatoolkit/infra/connectors/google_drive_connector.py +68 -0
- iatoolkit/infra/connectors/local_file_connector.py +46 -0
- iatoolkit/infra/connectors/s3_connector.py +33 -0
- iatoolkit/infra/google_chat_app.py +57 -0
- iatoolkit/infra/llm_providers/__init__.py +0 -0
- iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
- iatoolkit/infra/llm_providers/gemini_adapter.py +350 -0
- iatoolkit/infra/llm_providers/openai_adapter.py +124 -0
- iatoolkit/infra/llm_proxy.py +268 -0
- iatoolkit/infra/llm_response.py +45 -0
- iatoolkit/infra/redis_session_manager.py +122 -0
- iatoolkit/locales/en.yaml +222 -0
- iatoolkit/locales/es.yaml +225 -0
- iatoolkit/repositories/__init__.py +5 -0
- iatoolkit/repositories/database_manager.py +187 -0
- iatoolkit/repositories/document_repo.py +33 -0
- iatoolkit/repositories/filesystem_asset_repository.py +36 -0
- iatoolkit/repositories/llm_query_repo.py +105 -0
- iatoolkit/repositories/models.py +279 -0
- iatoolkit/repositories/profile_repo.py +171 -0
- iatoolkit/repositories/vs_repo.py +150 -0
- iatoolkit/services/__init__.py +5 -0
- iatoolkit/services/auth_service.py +193 -0
- {services → iatoolkit/services}/benchmark_service.py +7 -7
- iatoolkit/services/branding_service.py +153 -0
- iatoolkit/services/company_context_service.py +214 -0
- iatoolkit/services/configuration_service.py +375 -0
- iatoolkit/services/dispatcher_service.py +134 -0
- {services → iatoolkit/services}/document_service.py +20 -8
- iatoolkit/services/embedding_service.py +148 -0
- iatoolkit/services/excel_service.py +156 -0
- {services → iatoolkit/services}/file_processor_service.py +36 -21
- iatoolkit/services/history_manager_service.py +208 -0
- iatoolkit/services/i18n_service.py +104 -0
- iatoolkit/services/jwt_service.py +80 -0
- iatoolkit/services/language_service.py +89 -0
- iatoolkit/services/license_service.py +82 -0
- iatoolkit/services/llm_client_service.py +438 -0
- iatoolkit/services/load_documents_service.py +174 -0
- iatoolkit/services/mail_service.py +213 -0
- {services → iatoolkit/services}/profile_service.py +200 -101
- iatoolkit/services/prompt_service.py +303 -0
- iatoolkit/services/query_service.py +467 -0
- iatoolkit/services/search_service.py +55 -0
- iatoolkit/services/sql_service.py +169 -0
- iatoolkit/services/tool_service.py +246 -0
- iatoolkit/services/user_feedback_service.py +117 -0
- iatoolkit/services/user_session_context_service.py +213 -0
- iatoolkit/static/images/fernando.jpeg +0 -0
- iatoolkit/static/images/iatoolkit_core.png +0 -0
- iatoolkit/static/images/iatoolkit_logo.png +0 -0
- iatoolkit/static/js/chat_feedback_button.js +80 -0
- iatoolkit/static/js/chat_filepond.js +85 -0
- iatoolkit/static/js/chat_help_content.js +124 -0
- iatoolkit/static/js/chat_history_button.js +110 -0
- iatoolkit/static/js/chat_logout_button.js +36 -0
- iatoolkit/static/js/chat_main.js +401 -0
- iatoolkit/static/js/chat_model_selector.js +227 -0
- iatoolkit/static/js/chat_onboarding_button.js +103 -0
- iatoolkit/static/js/chat_prompt_manager.js +94 -0
- iatoolkit/static/js/chat_reload_button.js +38 -0
- iatoolkit/static/styles/chat_iatoolkit.css +559 -0
- iatoolkit/static/styles/chat_modal.css +133 -0
- iatoolkit/static/styles/chat_public.css +135 -0
- iatoolkit/static/styles/documents.css +598 -0
- iatoolkit/static/styles/landing_page.css +398 -0
- iatoolkit/static/styles/llm_output.css +148 -0
- iatoolkit/static/styles/onboarding.css +176 -0
- iatoolkit/system_prompts/__init__.py +0 -0
- iatoolkit/system_prompts/query_main.prompt +30 -23
- iatoolkit/system_prompts/sql_rules.prompt +47 -12
- iatoolkit/templates/_company_header.html +45 -0
- iatoolkit/templates/_login_widget.html +42 -0
- iatoolkit/templates/base.html +78 -0
- iatoolkit/templates/change_password.html +66 -0
- iatoolkit/templates/chat.html +337 -0
- iatoolkit/templates/chat_modals.html +185 -0
- iatoolkit/templates/error.html +51 -0
- iatoolkit/templates/forgot_password.html +51 -0
- iatoolkit/templates/onboarding_shell.html +106 -0
- iatoolkit/templates/signup.html +79 -0
- iatoolkit/views/__init__.py +5 -0
- iatoolkit/views/base_login_view.py +96 -0
- iatoolkit/views/change_password_view.py +116 -0
- iatoolkit/views/chat_view.py +76 -0
- iatoolkit/views/embedding_api_view.py +65 -0
- iatoolkit/views/forgot_password_view.py +75 -0
- iatoolkit/views/help_content_api_view.py +54 -0
- iatoolkit/views/history_api_view.py +56 -0
- iatoolkit/views/home_view.py +63 -0
- iatoolkit/views/init_context_api_view.py +74 -0
- iatoolkit/views/llmquery_api_view.py +59 -0
- iatoolkit/views/load_company_configuration_api_view.py +49 -0
- iatoolkit/views/load_document_api_view.py +65 -0
- iatoolkit/views/login_view.py +170 -0
- iatoolkit/views/logout_api_view.py +57 -0
- iatoolkit/views/profile_api_view.py +46 -0
- iatoolkit/views/prompt_api_view.py +37 -0
- iatoolkit/views/root_redirect_view.py +22 -0
- iatoolkit/views/signup_view.py +100 -0
- iatoolkit/views/static_page_view.py +27 -0
- iatoolkit/views/user_feedback_api_view.py +60 -0
- iatoolkit/views/users_api_view.py +33 -0
- iatoolkit/views/verify_user_view.py +60 -0
- iatoolkit-0.107.4.dist-info/METADATA +268 -0
- iatoolkit-0.107.4.dist-info/RECORD +132 -0
- iatoolkit-0.107.4.dist-info/licenses/LICENSE +21 -0
- iatoolkit-0.107.4.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
- {iatoolkit-0.3.9.dist-info → iatoolkit-0.107.4.dist-info}/top_level.txt +0 -1
- iatoolkit/iatoolkit.py +0 -413
- iatoolkit/system_prompts/arquitectura.prompt +0 -32
- iatoolkit-0.3.9.dist-info/METADATA +0 -252
- iatoolkit-0.3.9.dist-info/RECORD +0 -32
- services/__init__.py +0 -5
- services/api_service.py +0 -75
- services/dispatcher_service.py +0 -351
- services/excel_service.py +0 -98
- services/history_service.py +0 -45
- services/jwt_service.py +0 -91
- services/load_documents_service.py +0 -212
- services/mail_service.py +0 -62
- services/prompt_manager_service.py +0 -172
- services/query_service.py +0 -334
- services/search_service.py +0 -32
- services/sql_service.py +0 -42
- services/tasks_service.py +0 -188
- services/user_feedback_service.py +0 -67
- services/user_session_context_service.py +0 -85
- {iatoolkit-0.3.9.dist-info → iatoolkit-0.107.4.dist-info}/WHEEL +0 -0
|
@@ -1,212 +0,0 @@
|
|
|
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 repositories.vs_repo import VSRepo
|
|
7
|
-
from repositories.document_repo import DocumentRepo
|
|
8
|
-
from repositories.profile_repo import ProfileRepo
|
|
9
|
-
from repositories.llm_query_repo import LLMQueryRepo
|
|
10
|
-
from repositories.models import Document, VSDoc, Company
|
|
11
|
-
from services.document_service import DocumentService
|
|
12
|
-
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
|
13
|
-
from infra.connectors.file_connector_factory import FileConnectorFactory
|
|
14
|
-
from services.file_processor_service import FileProcessorConfig, FileProcessor
|
|
15
|
-
from services.dispatcher_service import Dispatcher
|
|
16
|
-
from common.exceptions import IAToolkitException
|
|
17
|
-
import logging
|
|
18
|
-
import base64
|
|
19
|
-
from injector import inject
|
|
20
|
-
from typing import Dict
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class LoadDocumentsService:
|
|
24
|
-
@inject
|
|
25
|
-
def __init__(self,
|
|
26
|
-
file_connector_factory: FileConnectorFactory,
|
|
27
|
-
doc_service: DocumentService,
|
|
28
|
-
doc_repo: DocumentRepo,
|
|
29
|
-
vector_store: VSRepo,
|
|
30
|
-
profile_repo: ProfileRepo,
|
|
31
|
-
dispatcher: Dispatcher,
|
|
32
|
-
llm_query_repo: LLMQueryRepo
|
|
33
|
-
):
|
|
34
|
-
self.doc_service = doc_service
|
|
35
|
-
self.doc_repo = doc_repo
|
|
36
|
-
self.profile_repo = profile_repo
|
|
37
|
-
self.llm_query_repo = llm_query_repo
|
|
38
|
-
self.vector_store = vector_store
|
|
39
|
-
self.file_connector_factory = file_connector_factory
|
|
40
|
-
self.dispatcher = dispatcher
|
|
41
|
-
self.company = None
|
|
42
|
-
|
|
43
|
-
# lower warnings
|
|
44
|
-
logging.getLogger().setLevel(logging.ERROR)
|
|
45
|
-
|
|
46
|
-
self.splitter = RecursiveCharacterTextSplitter(
|
|
47
|
-
chunk_size=1000,
|
|
48
|
-
chunk_overlap=100,
|
|
49
|
-
separators=["\n\n", "\n", "."]
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
# load the files for all of the companies.
|
|
53
|
-
def load(self, doc_type: str = None):
|
|
54
|
-
# doc_type: an optional document_type for loading
|
|
55
|
-
files_loaded = 0
|
|
56
|
-
companies = self.profile_repo.get_companies()
|
|
57
|
-
|
|
58
|
-
for company in companies:
|
|
59
|
-
load_config = company.parameters.get('load', {})
|
|
60
|
-
if not load_config:
|
|
61
|
-
continue
|
|
62
|
-
|
|
63
|
-
print(f"Cargando datos de ** {company.short_name} **")
|
|
64
|
-
self.company = company
|
|
65
|
-
|
|
66
|
-
# Si hay configuraciones de tipos de documento específicos
|
|
67
|
-
doc_types_config = load_config.get('document_types', {})
|
|
68
|
-
|
|
69
|
-
if doc_types_config and len(doc_types_config) > 0:
|
|
70
|
-
# Si se especificó un tipo de documento, cargar solo ese tipo
|
|
71
|
-
if doc_type and doc_type in doc_types_config:
|
|
72
|
-
files_loaded += self._load_document_type(company, doc_type, doc_types_config[doc_type])
|
|
73
|
-
# Si no se especificó, cargar todos los tipos configurados
|
|
74
|
-
elif not doc_type:
|
|
75
|
-
for type_name, type_config in doc_types_config.items():
|
|
76
|
-
files_loaded += self._load_document_type(company, type_name, type_config)
|
|
77
|
-
else:
|
|
78
|
-
# Comportamiento anterior: usar la configuración general
|
|
79
|
-
connector = load_config.get('connector', {})
|
|
80
|
-
if not connector:
|
|
81
|
-
raise IAToolkitException(IAToolkitException.ErrorType.MISSING_PARAMETER,
|
|
82
|
-
f"Falta configurar conector en empresa {company.short_name}")
|
|
83
|
-
|
|
84
|
-
files_loaded += self.load_data_source(connector)
|
|
85
|
-
|
|
86
|
-
return {'message': f'{files_loaded} files processed'}
|
|
87
|
-
|
|
88
|
-
def _load_document_type(self, company: Company, doc_type_name: str, type_config: Dict) -> int:
|
|
89
|
-
# load specific document_types for a company
|
|
90
|
-
connector = type_config.get('connector')
|
|
91
|
-
if not connector:
|
|
92
|
-
logging.warning(f"Falta configurar conector para tipo {doc_type_name} en empresa {company.short_name}")
|
|
93
|
-
raise IAToolkitException(IAToolkitException.ErrorType.MISSING_PARAMETER,
|
|
94
|
-
f"Falta configurar conector para tipo {doc_type_name} en empresa {company.short_name}")
|
|
95
|
-
|
|
96
|
-
# get the metadata for this connector
|
|
97
|
-
predefined_metadata = type_config.get('metadata', {})
|
|
98
|
-
|
|
99
|
-
# config specific filters
|
|
100
|
-
filters = type_config.get('filters', {"filename_contains": ".pdf"})
|
|
101
|
-
|
|
102
|
-
return self.load_data_source(connector, predefined_metadata, filters)
|
|
103
|
-
|
|
104
|
-
def load_data_source(self, connector_config: Dict, predefined_metadata: Dict = None, filters: Dict = None):
|
|
105
|
-
"""
|
|
106
|
-
Carga archivos desde una fuente de datos usando un conector.
|
|
107
|
-
|
|
108
|
-
Args:
|
|
109
|
-
connector_config: Configuración del conector
|
|
110
|
-
predefined_metadata: Metadatos predefinidos para todos los documentos de esta fuente
|
|
111
|
-
filters: Filtros específicos para esta carga
|
|
112
|
-
|
|
113
|
-
Returns:
|
|
114
|
-
int o dict: Número de archivos procesados o diccionario de error
|
|
115
|
-
"""
|
|
116
|
-
try:
|
|
117
|
-
# Si no se proporcionaron filtros, usar el predeterminado
|
|
118
|
-
if not filters:
|
|
119
|
-
filters = {"filename_contains": ".pdf"}
|
|
120
|
-
|
|
121
|
-
# Pasar metadata predefinida como parte del contexto al procesador
|
|
122
|
-
# para que esté disponible en la función load_file
|
|
123
|
-
extra_context = {}
|
|
124
|
-
if predefined_metadata:
|
|
125
|
-
extra_context['metadata'] = predefined_metadata
|
|
126
|
-
|
|
127
|
-
# config the processor
|
|
128
|
-
processor_config = FileProcessorConfig(
|
|
129
|
-
context=extra_context,
|
|
130
|
-
filters=filters,
|
|
131
|
-
action=self.load_file,
|
|
132
|
-
continue_on_error=True,
|
|
133
|
-
echo=True
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
connector = self.file_connector_factory.create(connector_config)
|
|
137
|
-
processor = FileProcessor(connector, processor_config)
|
|
138
|
-
|
|
139
|
-
# process the files
|
|
140
|
-
processor.process_files()
|
|
141
|
-
|
|
142
|
-
return processor.processed_files
|
|
143
|
-
except Exception as e:
|
|
144
|
-
logging.exception("Loading files error: %s", str(e))
|
|
145
|
-
return {"error": str(e)}
|
|
146
|
-
|
|
147
|
-
# load an individual filename
|
|
148
|
-
# this method is set up on the FileProcessorConfig object
|
|
149
|
-
def load_file(self, filename: str, content: bytes, context: dict = {}, company: Company = None):
|
|
150
|
-
if not company:
|
|
151
|
-
company = self.company
|
|
152
|
-
|
|
153
|
-
# check if file exist in repositories
|
|
154
|
-
if self.doc_repo.get(company=company,filename=filename):
|
|
155
|
-
return
|
|
156
|
-
|
|
157
|
-
try:
|
|
158
|
-
# extract text from the document
|
|
159
|
-
document_content = self.doc_service.file_to_txt(filename, content)
|
|
160
|
-
content_base64 = base64.b64encode(content).decode('utf-8')
|
|
161
|
-
|
|
162
|
-
# generate metada based on the filename structure
|
|
163
|
-
dynamic_metadata = self.dispatcher.get_metadata_from_filename(company_name=company.short_name, filename=filename)
|
|
164
|
-
|
|
165
|
-
# Obtener metadatos del contexto si existen
|
|
166
|
-
context_metadata = context.get('metadata', {}).copy() if context else {}
|
|
167
|
-
|
|
168
|
-
# Fusionar los metadatos. El orden de prioridad es:
|
|
169
|
-
# 1. dynamic_metadata (tiene mayor prioridad)
|
|
170
|
-
# 2. context_metadata (del parámetro context)
|
|
171
|
-
# Los valores en dynamic_metadata tendrán precedencia sobre los de context_metadata
|
|
172
|
-
final_meta = {**context_metadata, **dynamic_metadata}
|
|
173
|
-
|
|
174
|
-
# save the file in the document repositories
|
|
175
|
-
new_document = Document(
|
|
176
|
-
company_id=company.id,
|
|
177
|
-
filename=filename,
|
|
178
|
-
content=document_content,
|
|
179
|
-
content_b64=content_base64,
|
|
180
|
-
meta=final_meta
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
# insert the document into the Database (without commit)
|
|
184
|
-
session = self.doc_repo.session
|
|
185
|
-
session.add(new_document)
|
|
186
|
-
session.flush() # get the ID without commit
|
|
187
|
-
|
|
188
|
-
# split the content, and create the chunk list
|
|
189
|
-
splitted_content = self.splitter.split_text(document_content)
|
|
190
|
-
chunk_list = [
|
|
191
|
-
VSDoc(
|
|
192
|
-
company_id=company.id,
|
|
193
|
-
document_id=new_document.id,
|
|
194
|
-
text=text
|
|
195
|
-
)
|
|
196
|
-
for text in splitted_content
|
|
197
|
-
]
|
|
198
|
-
|
|
199
|
-
# save to vector store
|
|
200
|
-
self.vector_store.add_document(chunk_list)
|
|
201
|
-
|
|
202
|
-
# confirm the transaction
|
|
203
|
-
session.commit()
|
|
204
|
-
|
|
205
|
-
return new_document
|
|
206
|
-
except Exception as e:
|
|
207
|
-
self.doc_repo.session.rollback()
|
|
208
|
-
|
|
209
|
-
# if something fails, throw exception
|
|
210
|
-
logging.exception("Error procesando el archivo %s: %s", filename, str(e))
|
|
211
|
-
raise IAToolkitException(IAToolkitException.ErrorType.LOAD_DOCUMENT_ERROR,
|
|
212
|
-
f"Error al procesar el archivo {filename}")
|
services/mail_service.py
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
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 infra.mail_app import MailApp
|
|
7
|
-
from injector import inject
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from common.exceptions import IAToolkitException
|
|
10
|
-
import base64
|
|
11
|
-
|
|
12
|
-
TEMP_DIR = Path("static/temp")
|
|
13
|
-
|
|
14
|
-
class MailService:
|
|
15
|
-
@inject
|
|
16
|
-
def __init__(self, mail_app: MailApp):
|
|
17
|
-
self.mail_app = mail_app
|
|
18
|
-
|
|
19
|
-
def _read_token_bytes(self, token: str) -> bytes:
|
|
20
|
-
# Defensa simple contra path traversal
|
|
21
|
-
if not token or "/" in token or "\\" in token or token.startswith("."):
|
|
22
|
-
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
23
|
-
"attachment_token inválido")
|
|
24
|
-
path = TEMP_DIR / token
|
|
25
|
-
if not path.is_file():
|
|
26
|
-
raise IAToolkitException(IAToolkitException.ErrorType.MAIL_ERROR,
|
|
27
|
-
f"Adjunto no encontrado: {token}")
|
|
28
|
-
return path.read_bytes()
|
|
29
|
-
|
|
30
|
-
def send_mail(self, **kwargs):
|
|
31
|
-
from_email = kwargs.get('from_email', 'iatoolkit@iatoolkit.com')
|
|
32
|
-
recipient = kwargs.get('recipient')
|
|
33
|
-
subject = kwargs.get('subject')
|
|
34
|
-
body = kwargs.get('body')
|
|
35
|
-
attachments = kwargs.get('attachments')
|
|
36
|
-
|
|
37
|
-
# Normalizar a payload de MailApp (name + base64 content)
|
|
38
|
-
norm_attachments = []
|
|
39
|
-
for a in attachments or []:
|
|
40
|
-
if a.get("attachment_token"):
|
|
41
|
-
raw = self._read_token_bytes(a["attachment_token"])
|
|
42
|
-
norm_attachments.append({
|
|
43
|
-
"filename": a["filename"],
|
|
44
|
-
"content": base64.b64encode(raw).decode("utf-8"),
|
|
45
|
-
})
|
|
46
|
-
else:
|
|
47
|
-
# asumo que ya viene un base64
|
|
48
|
-
norm_attachments.append({
|
|
49
|
-
"filename": a["filename"],
|
|
50
|
-
"content": a["content"]
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
self.sender = {"email": from_email, "name": "IAToolkit"}
|
|
54
|
-
|
|
55
|
-
response = self.mail_app.send_email(
|
|
56
|
-
sender=self.sender,
|
|
57
|
-
to=recipient,
|
|
58
|
-
subject=subject,
|
|
59
|
-
body=body,
|
|
60
|
-
attachments=norm_attachments)
|
|
61
|
-
|
|
62
|
-
return 'mail enviado'
|
|
@@ -1,172 +0,0 @@
|
|
|
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
|
-
from pathlib import Path
|
|
15
|
-
import importlib.resources
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class PromptService:
|
|
19
|
-
@inject
|
|
20
|
-
def __init__(self, llm_query_repo: LLMQueryRepo, profile_repo: ProfileRepo):
|
|
21
|
-
self.llm_query_repo = llm_query_repo
|
|
22
|
-
self.profile_repo = profile_repo
|
|
23
|
-
|
|
24
|
-
def create_prompt(self,
|
|
25
|
-
prompt_name: str,
|
|
26
|
-
description: str,
|
|
27
|
-
order: int,
|
|
28
|
-
company: Company = None,
|
|
29
|
-
category: PromptCategory = None,
|
|
30
|
-
active: bool = True,
|
|
31
|
-
is_system_prompt: bool = False,
|
|
32
|
-
params: dict = {}
|
|
33
|
-
):
|
|
34
|
-
|
|
35
|
-
prompt_filename = prompt_name.lower() + '.prompt'
|
|
36
|
-
if is_system_prompt:
|
|
37
|
-
if not importlib.resources.files('iatoolkit.system_prompts').joinpath(prompt_filename).is_file():
|
|
38
|
-
raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
|
|
39
|
-
f'No existe el archivo de prompt de sistemas: {prompt_filename}')
|
|
40
|
-
else:
|
|
41
|
-
template_dir = f'companies/{company.short_name}/prompts'
|
|
42
|
-
|
|
43
|
-
relative_prompt_path = os.path.join(template_dir, prompt_filename)
|
|
44
|
-
if not os.path.exists(relative_prompt_path):
|
|
45
|
-
raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
|
|
46
|
-
f'No existe el archivo de prompt: {relative_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
|
-
filename=prompt_filename,
|
|
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.filename)
|
|
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
|
-
# read all the system prompts from the database
|
|
106
|
-
system_prompts = self.llm_query_repo.get_system_prompts()
|
|
107
|
-
|
|
108
|
-
for prompt in system_prompts:
|
|
109
|
-
try:
|
|
110
|
-
content = importlib.resources.read_text('iatoolkit.system_prompts', prompt.filename)
|
|
111
|
-
system_prompt_content.append(content)
|
|
112
|
-
except FileNotFoundError:
|
|
113
|
-
logging.warning(f"El archivo para el prompt de sistema no existe en el paquete: {prompt.filename}")
|
|
114
|
-
except Exception as e:
|
|
115
|
-
raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
|
|
116
|
-
f"Error leyendo el archivo de prompt del sistema '{prompt.filename}': {e}")
|
|
117
|
-
|
|
118
|
-
# join the system prompts into a single string
|
|
119
|
-
return "\n".join(system_prompt_content)
|
|
120
|
-
|
|
121
|
-
except IAToolkitException:
|
|
122
|
-
raise
|
|
123
|
-
except Exception as e:
|
|
124
|
-
logging.exception(
|
|
125
|
-
f"Error al obtener el contenido del prompt de sistema: {e}")
|
|
126
|
-
raise IAToolkitException(IAToolkitException.ErrorType.PROMPT_ERROR,
|
|
127
|
-
f'Error al obtener el contenido de los prompts de sistema": {str(e)}')
|
|
128
|
-
|
|
129
|
-
def get_user_prompts(self, company_short_name: str) -> dict:
|
|
130
|
-
try:
|
|
131
|
-
# validate company
|
|
132
|
-
company = self.profile_repo.get_company_by_short_name(company_short_name)
|
|
133
|
-
if not company:
|
|
134
|
-
return {'error': f'No existe la empresa: {company_short_name}'}
|
|
135
|
-
|
|
136
|
-
# get all the prompts
|
|
137
|
-
all_prompts = self.llm_query_repo.get_prompts(company)
|
|
138
|
-
|
|
139
|
-
# Agrupar prompts por categoría
|
|
140
|
-
prompts_by_category = defaultdict(list)
|
|
141
|
-
for prompt in all_prompts:
|
|
142
|
-
if prompt.active:
|
|
143
|
-
if prompt.category:
|
|
144
|
-
cat_key = (prompt.category.order, prompt.category.name)
|
|
145
|
-
prompts_by_category[cat_key].append(prompt)
|
|
146
|
-
|
|
147
|
-
# Ordenar los prompts dentro de cada categoría
|
|
148
|
-
for cat_key in prompts_by_category:
|
|
149
|
-
prompts_by_category[cat_key].sort(key=lambda p: p.order)
|
|
150
|
-
|
|
151
|
-
# Crear la estructura de respuesta final, ordenada por la categoría
|
|
152
|
-
categorized_prompts = []
|
|
153
|
-
|
|
154
|
-
# Ordenar las categorías por su 'order'
|
|
155
|
-
sorted_categories = sorted(prompts_by_category.items(), key=lambda item: item[0][0])
|
|
156
|
-
|
|
157
|
-
for (cat_order, cat_name), prompts in sorted_categories:
|
|
158
|
-
categorized_prompts.append({
|
|
159
|
-
'category_name': cat_name,
|
|
160
|
-
'category_order': cat_order,
|
|
161
|
-
'prompts': [
|
|
162
|
-
{'prompt': p.name, 'description': p.description, 'order': p.order}
|
|
163
|
-
for p in prompts
|
|
164
|
-
]
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
return {'message': categorized_prompts}
|
|
168
|
-
|
|
169
|
-
except Exception as e:
|
|
170
|
-
logging.error(f"Error en get_prompts: {e}")
|
|
171
|
-
return {'error': str(e)}
|
|
172
|
-
|