iatoolkit 0.66.2__py3-none-any.whl → 0.71.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 +2 -6
- iatoolkit/base_company.py +3 -31
- iatoolkit/cli_commands.py +1 -1
- iatoolkit/common/routes.py +5 -1
- iatoolkit/common/session_manager.py +2 -0
- iatoolkit/company_registry.py +1 -2
- iatoolkit/iatoolkit.py +13 -13
- iatoolkit/infra/llm_client.py +8 -12
- iatoolkit/infra/llm_proxy.py +38 -10
- iatoolkit/locales/en.yaml +25 -2
- iatoolkit/locales/es.yaml +27 -4
- iatoolkit/repositories/database_manager.py +8 -3
- iatoolkit/repositories/document_repo.py +1 -1
- iatoolkit/repositories/models.py +6 -8
- iatoolkit/repositories/profile_repo.py +0 -4
- iatoolkit/repositories/vs_repo.py +26 -20
- iatoolkit/services/auth_service.py +2 -2
- iatoolkit/services/branding_service.py +11 -7
- iatoolkit/services/company_context_service.py +155 -0
- iatoolkit/services/configuration_service.py +133 -0
- iatoolkit/services/dispatcher_service.py +75 -70
- iatoolkit/services/document_service.py +5 -2
- iatoolkit/services/embedding_service.py +146 -0
- iatoolkit/services/excel_service.py +15 -11
- iatoolkit/services/file_processor_service.py +4 -12
- iatoolkit/services/history_service.py +7 -7
- iatoolkit/services/i18n_service.py +4 -4
- iatoolkit/services/jwt_service.py +7 -9
- iatoolkit/services/language_service.py +29 -23
- iatoolkit/services/load_documents_service.py +100 -113
- iatoolkit/services/mail_service.py +9 -4
- iatoolkit/services/profile_service.py +10 -7
- iatoolkit/services/prompt_manager_service.py +20 -16
- iatoolkit/services/query_service.py +112 -43
- iatoolkit/services/search_service.py +11 -4
- iatoolkit/services/sql_service.py +57 -25
- iatoolkit/services/user_feedback_service.py +15 -13
- iatoolkit/static/js/chat_history_button.js +3 -5
- iatoolkit/static/js/chat_main.js +2 -17
- iatoolkit/static/js/chat_onboarding_button.js +6 -0
- iatoolkit/static/styles/chat_iatoolkit.css +69 -158
- iatoolkit/static/styles/chat_modal.css +1 -37
- iatoolkit/static/styles/onboarding.css +7 -0
- iatoolkit/system_prompts/query_main.prompt +2 -10
- iatoolkit/templates/change_password.html +1 -1
- iatoolkit/templates/chat.html +12 -4
- iatoolkit/templates/chat_modals.html +4 -0
- iatoolkit/templates/error.html +1 -1
- iatoolkit/templates/login_simulation.html +17 -6
- iatoolkit/templates/onboarding_shell.html +4 -1
- iatoolkit/views/base_login_view.py +7 -8
- iatoolkit/views/change_password_view.py +2 -3
- iatoolkit/views/embedding_api_view.py +65 -0
- iatoolkit/views/external_login_view.py +1 -1
- iatoolkit/views/file_store_api_view.py +1 -1
- iatoolkit/views/forgot_password_view.py +2 -4
- iatoolkit/views/help_content_api_view.py +9 -9
- iatoolkit/views/history_api_view.py +1 -1
- iatoolkit/views/home_view.py +2 -2
- iatoolkit/views/init_context_api_view.py +18 -17
- iatoolkit/views/llmquery_api_view.py +3 -2
- iatoolkit/views/login_simulation_view.py +14 -2
- iatoolkit/views/login_view.py +9 -9
- iatoolkit/views/signup_view.py +2 -4
- iatoolkit/views/verify_user_view.py +2 -4
- {iatoolkit-0.66.2.dist-info → iatoolkit-0.71.2.dist-info}/METADATA +40 -22
- iatoolkit-0.71.2.dist-info/RECORD +122 -0
- iatoolkit-0.71.2.dist-info/licenses/LICENSE +21 -0
- iatoolkit/services/help_content_service.py +0 -30
- iatoolkit/services/onboarding_service.py +0 -43
- iatoolkit-0.66.2.dist-info/RECORD +0 -119
- {iatoolkit-0.66.2.dist-info → iatoolkit-0.71.2.dist-info}/WHEEL +0 -0
- {iatoolkit-0.66.2.dist-info → iatoolkit-0.71.2.dist-info}/top_level.txt +0 -0
iatoolkit/__init__.py
CHANGED
|
@@ -5,20 +5,18 @@ IAToolkit Package
|
|
|
5
5
|
# Expose main classes and functions at the top level of the package
|
|
6
6
|
|
|
7
7
|
# main IAToolkit class
|
|
8
|
-
from .iatoolkit import IAToolkit,
|
|
8
|
+
from .iatoolkit import IAToolkit, create_app, current_iatoolkit
|
|
9
9
|
|
|
10
10
|
# for registering the client companies
|
|
11
11
|
from .company_registry import register_company
|
|
12
12
|
from .base_company import BaseCompany
|
|
13
|
-
from iatoolkit.repositories.database_manager import DatabaseManager
|
|
14
13
|
|
|
15
14
|
# --- Services ---
|
|
16
15
|
from iatoolkit.services.query_service import QueryService
|
|
17
|
-
from iatoolkit.services.sql_service import SqlService
|
|
18
16
|
from iatoolkit.services.document_service import DocumentService
|
|
19
17
|
from iatoolkit.services.search_service import SearchService
|
|
18
|
+
from iatoolkit.services.sql_service import SqlService
|
|
20
19
|
from iatoolkit.services.load_documents_service import LoadDocumentsService
|
|
21
|
-
from iatoolkit.services.excel_service import ExcelService
|
|
22
20
|
from iatoolkit.infra.call_service import CallServiceClient
|
|
23
21
|
|
|
24
22
|
__all__ = [
|
|
@@ -27,10 +25,8 @@ __all__ = [
|
|
|
27
25
|
'current_iatoolkit',
|
|
28
26
|
'register_company',
|
|
29
27
|
'BaseCompany',
|
|
30
|
-
'DatabaseManager',
|
|
31
28
|
'QueryService',
|
|
32
29
|
'SqlService',
|
|
33
|
-
'ExcelService',
|
|
34
30
|
'DocumentService',
|
|
35
31
|
'SearchService',
|
|
36
32
|
'LoadDocumentsService',
|
iatoolkit/base_company.py
CHANGED
|
@@ -7,10 +7,9 @@
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
8
|
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
9
9
|
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
10
|
-
|
|
11
10
|
from iatoolkit.services.prompt_manager_service import PromptService
|
|
12
11
|
from iatoolkit.repositories.models import Company, Function, PromptCategory
|
|
13
|
-
from iatoolkit import IAToolkit
|
|
12
|
+
from .iatoolkit import IAToolkit
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
class BaseCompany(ABC):
|
|
@@ -21,23 +20,16 @@ class BaseCompany(ABC):
|
|
|
21
20
|
self.llm_query_repo: LLMQueryRepo = injector.get(LLMQueryRepo)
|
|
22
21
|
self.prompt_service: PromptService = injector.get(PromptService)
|
|
23
22
|
self.company: Company | None = None
|
|
24
|
-
|
|
25
|
-
def _load_company_by_short_name(self, short_name: str) -> Company:
|
|
26
|
-
self.company = self.profile_repo.get_company_by_short_name(short_name)
|
|
27
|
-
return self.company
|
|
23
|
+
self.company_short_name: str
|
|
28
24
|
|
|
29
25
|
def _create_company(self,
|
|
30
26
|
short_name: str,
|
|
31
27
|
name: str,
|
|
32
28
|
parameters: dict | None = None,
|
|
33
|
-
branding: dict | None = None,
|
|
34
|
-
onboarding_cards: dict | None = None,
|
|
35
29
|
) -> Company:
|
|
36
30
|
company_obj = Company(short_name=short_name,
|
|
37
31
|
name=name,
|
|
38
|
-
parameters=parameters
|
|
39
|
-
branding=branding,
|
|
40
|
-
onboarding_cards=onboarding_cards)
|
|
32
|
+
parameters=parameters)
|
|
41
33
|
self.company = self.profile_repo.create_company(company_obj)
|
|
42
34
|
return self.company
|
|
43
35
|
|
|
@@ -77,17 +69,6 @@ class BaseCompany(ABC):
|
|
|
77
69
|
**kwargs
|
|
78
70
|
)
|
|
79
71
|
|
|
80
|
-
|
|
81
|
-
@abstractmethod
|
|
82
|
-
# initialize all the database tables needed
|
|
83
|
-
def register_company(self):
|
|
84
|
-
raise NotImplementedError("La subclase debe implementar el método create_company()")
|
|
85
|
-
|
|
86
|
-
@abstractmethod
|
|
87
|
-
# get context specific for this company
|
|
88
|
-
def get_company_context(self, **kwargs) -> str:
|
|
89
|
-
raise NotImplementedError("La subclase debe implementar el método get_company_context()")
|
|
90
|
-
|
|
91
72
|
@abstractmethod
|
|
92
73
|
# get context specific for this company
|
|
93
74
|
def get_user_info(self, user_identifier: str) -> dict:
|
|
@@ -98,15 +79,6 @@ class BaseCompany(ABC):
|
|
|
98
79
|
def handle_request(self, tag: str, params: dict) -> dict:
|
|
99
80
|
raise NotImplementedError("La subclase debe implementar el método handle_request()")
|
|
100
81
|
|
|
101
|
-
@abstractmethod
|
|
102
|
-
# get context specific for the query
|
|
103
|
-
def start_execution(self):
|
|
104
|
-
raise NotImplementedError("La subclase debe implementar el método start_execution()")
|
|
105
|
-
|
|
106
|
-
@abstractmethod
|
|
107
|
-
# get context specific for the query
|
|
108
|
-
def get_metadata_from_filename(self, filename: str) -> dict:
|
|
109
|
-
raise NotImplementedError("La subclase debe implementar el método get_query_context()")
|
|
110
82
|
|
|
111
83
|
def register_cli_commands(self, app):
|
|
112
84
|
"""
|
iatoolkit/cli_commands.py
CHANGED
iatoolkit/common/routes.py
CHANGED
|
@@ -26,7 +26,7 @@ def register_views(injector, app):
|
|
|
26
26
|
from iatoolkit.views.history_api_view import HistoryApiView
|
|
27
27
|
from iatoolkit.views.help_content_api_view import HelpContentApiView
|
|
28
28
|
from iatoolkit.views.profile_api_view import UserLanguageApiView # <-- Importa la nueva vista
|
|
29
|
-
|
|
29
|
+
from iatoolkit.views.embedding_api_view import EmbeddingApiView
|
|
30
30
|
from iatoolkit.views.login_view import LoginView, FinalizeContextView
|
|
31
31
|
from iatoolkit.views.external_login_view import ExternalLoginView, RedeemTokenApiView
|
|
32
32
|
from iatoolkit.views.logout_api_view import LogoutApiView
|
|
@@ -99,6 +99,10 @@ def register_views(injector, app):
|
|
|
99
99
|
# this endpoint is for upload documents into the vector store (api-key)
|
|
100
100
|
app.add_url_rule('/api/load', view_func=FileStoreApiView.as_view('load_api'))
|
|
101
101
|
|
|
102
|
+
# this endpoint is for generating embeddings for a given text
|
|
103
|
+
app.add_url_rule('/<company_short_name>/api/embedding',
|
|
104
|
+
view_func=EmbeddingApiView.as_view('embedding_api'))
|
|
105
|
+
|
|
102
106
|
|
|
103
107
|
@app.route('/download/<path:filename>')
|
|
104
108
|
def download_file(filename):
|
iatoolkit/company_registry.py
CHANGED
|
@@ -31,10 +31,9 @@ class CompanyRegistry:
|
|
|
31
31
|
|
|
32
32
|
# save the created instance in the registry
|
|
33
33
|
self._company_instances[company_key] = company_instance
|
|
34
|
-
logging.info(f"company '{company_key}' instantiated")
|
|
35
34
|
|
|
36
35
|
except Exception as e:
|
|
37
|
-
logging.error(f"Error
|
|
36
|
+
logging.error(f"Error while creating company instance for {company_key}: {e}")
|
|
38
37
|
logging.exception(e)
|
|
39
38
|
raise
|
|
40
39
|
|
iatoolkit/iatoolkit.py
CHANGED
|
@@ -19,7 +19,7 @@ from werkzeug.middleware.proxy_fix import ProxyFix
|
|
|
19
19
|
from injector import Binder, Injector, singleton
|
|
20
20
|
from importlib.metadata import version as _pkg_version, PackageNotFoundError
|
|
21
21
|
|
|
22
|
-
IATOOLKIT_VERSION = "0.
|
|
22
|
+
IATOOLKIT_VERSION = "0.71.2"
|
|
23
23
|
|
|
24
24
|
# global variable for the unique instance of IAToolkit
|
|
25
25
|
_iatoolkit_instance: Optional['IAToolkit'] = None
|
|
@@ -91,6 +91,9 @@ class IAToolkit:
|
|
|
91
91
|
# Step 6: initialize dispatcher and registered companies
|
|
92
92
|
self._init_dispatcher_and_company_instances()
|
|
93
93
|
|
|
94
|
+
# Re-apply logging configuration in case it was modified by company-specific code
|
|
95
|
+
self._setup_logging()
|
|
96
|
+
|
|
94
97
|
# Step 7: Finalize setup within the application context
|
|
95
98
|
self._setup_redis_sessions()
|
|
96
99
|
self._setup_cors()
|
|
@@ -155,19 +158,12 @@ class IAToolkit:
|
|
|
155
158
|
static_folder=static_folder,
|
|
156
159
|
template_folder=template_folder)
|
|
157
160
|
|
|
158
|
-
|
|
159
|
-
is_dev = self._get_config_value('FLASK_ENV') == 'development'
|
|
160
|
-
|
|
161
|
-
# get the iatoolkit domain
|
|
162
|
-
parsed_url = urlparse(os.getenv('IATOOLKIT_BASE_URL'))
|
|
163
|
-
domain = parsed_url.netloc
|
|
164
|
-
|
|
161
|
+
# get the IATOOLKIT_VERSION from the package metadata
|
|
165
162
|
try:
|
|
166
163
|
self.version = _pkg_version("iatoolkit")
|
|
167
164
|
except PackageNotFoundError:
|
|
168
165
|
pass
|
|
169
166
|
|
|
170
|
-
|
|
171
167
|
self.app.config.update({
|
|
172
168
|
'VERSION': self.version,
|
|
173
169
|
'SECRET_KEY': self._get_config_value('FLASK_SECRET_KEY', 'iatoolkit-default-secret'),
|
|
@@ -180,6 +176,7 @@ class IAToolkit:
|
|
|
180
176
|
'JWT_EXPIRATION_SECONDS_CHAT': int(self._get_config_value('JWT_EXPIRATION_SECONDS_CHAT', 3600))
|
|
181
177
|
})
|
|
182
178
|
|
|
179
|
+
parsed_url = urlparse(os.getenv('IATOOLKIT_BASE_URL'))
|
|
183
180
|
if parsed_url.scheme == 'https':
|
|
184
181
|
self.app.config['PREFERRED_URL_SCHEME'] = 'https'
|
|
185
182
|
|
|
@@ -187,7 +184,7 @@ class IAToolkit:
|
|
|
187
184
|
self.app.wsgi_app = ProxyFix(self.app.wsgi_app, x_proto=1)
|
|
188
185
|
|
|
189
186
|
# Configuración para tokenizers en desarrollo
|
|
190
|
-
if
|
|
187
|
+
if self._get_config_value('FLASK_ENV') == 'dev':
|
|
191
188
|
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
|
192
189
|
|
|
193
190
|
def _setup_database(self):
|
|
@@ -293,7 +290,6 @@ class IAToolkit:
|
|
|
293
290
|
from iatoolkit.repositories.document_repo import DocumentRepo
|
|
294
291
|
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
295
292
|
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
296
|
-
|
|
297
293
|
from iatoolkit.repositories.vs_repo import VSRepo
|
|
298
294
|
from iatoolkit.repositories.tasks_repo import TaskRepo
|
|
299
295
|
|
|
@@ -318,6 +314,8 @@ class IAToolkit:
|
|
|
318
314
|
from iatoolkit.services.branding_service import BrandingService
|
|
319
315
|
from iatoolkit.services.i18n_service import I18nService
|
|
320
316
|
from iatoolkit.services.language_service import LanguageService
|
|
317
|
+
from iatoolkit.services.configuration_service import ConfigurationService
|
|
318
|
+
from iatoolkit.services.embedding_service import EmbeddingService
|
|
321
319
|
|
|
322
320
|
binder.bind(QueryService, to=QueryService)
|
|
323
321
|
binder.bind(TaskService, to=TaskService)
|
|
@@ -333,6 +331,8 @@ class IAToolkit:
|
|
|
333
331
|
binder.bind(BrandingService, to=BrandingService)
|
|
334
332
|
binder.bind(I18nService, to=I18nService)
|
|
335
333
|
binder.bind(LanguageService, to=LanguageService)
|
|
334
|
+
binder.bind(ConfigurationService, to=ConfigurationService)
|
|
335
|
+
binder.bind(EmbeddingService, to=EmbeddingService)
|
|
336
336
|
|
|
337
337
|
def _bind_infrastructure(self, binder: Binder):
|
|
338
338
|
from iatoolkit.infra.llm_client import llmClient
|
|
@@ -359,9 +359,9 @@ class IAToolkit:
|
|
|
359
359
|
# instantiate all the registered companies
|
|
360
360
|
get_company_registry().instantiate_companies(self._injector)
|
|
361
361
|
|
|
362
|
-
# use the dispatcher to
|
|
362
|
+
# use the dispatcher to load the company config.yaml file and prepare the execution
|
|
363
363
|
dispatcher = self._injector.get(Dispatcher)
|
|
364
|
-
dispatcher.
|
|
364
|
+
dispatcher.load_company_configs()
|
|
365
365
|
|
|
366
366
|
def _setup_cli_commands(self):
|
|
367
367
|
from iatoolkit.cli_commands import register_core_commands
|
iatoolkit/infra/llm_client.py
CHANGED
|
@@ -38,12 +38,6 @@ class llmClient:
|
|
|
38
38
|
self.util = util
|
|
39
39
|
self._dispatcher = None # Cache for the lazy-loaded dispatcher
|
|
40
40
|
|
|
41
|
-
# get the model from the environment variable
|
|
42
|
-
self.model = os.getenv("LLM_MODEL", "")
|
|
43
|
-
if not self.model:
|
|
44
|
-
raise IAToolkitException(IAToolkitException.ErrorType.API_KEY,
|
|
45
|
-
"La variable de entorno 'LLM_MODEL' no está configurada.")
|
|
46
|
-
|
|
47
41
|
# library for counting tokens
|
|
48
42
|
self.encoding = tiktoken.encoding_for_model("gpt-4o")
|
|
49
43
|
|
|
@@ -70,6 +64,7 @@ class llmClient:
|
|
|
70
64
|
context: str,
|
|
71
65
|
tools: list[dict],
|
|
72
66
|
text: dict,
|
|
67
|
+
model: str,
|
|
73
68
|
context_history: Optional[List[Dict]] = None,
|
|
74
69
|
) -> dict:
|
|
75
70
|
|
|
@@ -80,13 +75,13 @@ class llmClient:
|
|
|
80
75
|
force_tool_name = None
|
|
81
76
|
reasoning = {}
|
|
82
77
|
|
|
83
|
-
if 'gpt-5' in
|
|
78
|
+
if 'gpt-5' in model:
|
|
84
79
|
text['verbosity'] = "low"
|
|
85
80
|
reasoning = {"effort": 'minimal'}
|
|
86
81
|
|
|
87
82
|
try:
|
|
88
83
|
start_time = time.time()
|
|
89
|
-
logging.info(f"calling llm model '{
|
|
84
|
+
logging.info(f"calling llm model '{model}' with {self.count_tokens(context)} tokens...")
|
|
90
85
|
|
|
91
86
|
# get the proxy for the company
|
|
92
87
|
llm_proxy = self.llm_proxy_factory.create_for_company(company)
|
|
@@ -99,7 +94,7 @@ class llmClient:
|
|
|
99
94
|
}]
|
|
100
95
|
|
|
101
96
|
response = llm_proxy.create_response(
|
|
102
|
-
model=
|
|
97
|
+
model=model,
|
|
103
98
|
previous_response_id=previous_response_id,
|
|
104
99
|
context_history=context_history,
|
|
105
100
|
input=input_messages,
|
|
@@ -136,7 +131,7 @@ class llmClient:
|
|
|
136
131
|
logging.info(f"start execution fcall: {function_name}")
|
|
137
132
|
try:
|
|
138
133
|
result = self.dispatcher.dispatch(
|
|
139
|
-
|
|
134
|
+
company_short_name=company.short_name,
|
|
140
135
|
action=function_name,
|
|
141
136
|
**args
|
|
142
137
|
)
|
|
@@ -186,7 +181,7 @@ class llmClient:
|
|
|
186
181
|
tool_choice_value = "required"
|
|
187
182
|
|
|
188
183
|
response = llm_proxy.create_response(
|
|
189
|
-
model=
|
|
184
|
+
model=model,
|
|
190
185
|
input=input_messages,
|
|
191
186
|
previous_response_id=response.id,
|
|
192
187
|
context_history=context_history,
|
|
@@ -200,7 +195,7 @@ class llmClient:
|
|
|
200
195
|
# save the statistices
|
|
201
196
|
stats['response_time']=int(time.time() - start_time)
|
|
202
197
|
stats['sql_retry_count'] = sql_retry_count
|
|
203
|
-
stats['model'] =
|
|
198
|
+
stats['model'] = model
|
|
204
199
|
|
|
205
200
|
# decode the LLM response
|
|
206
201
|
decoded_response = self.decode_response(response)
|
|
@@ -392,6 +387,7 @@ class llmClient:
|
|
|
392
387
|
|
|
393
388
|
def add_stats(self, stats1: dict, stats2: dict) -> dict:
|
|
394
389
|
stats_dict = {
|
|
390
|
+
"model": stats1.get('model', ''),
|
|
395
391
|
"input_tokens": stats1.get('input_tokens', 0) + stats2.get('input_tokens', 0),
|
|
396
392
|
"output_tokens": stats1.get('output_tokens', 0) + stats2.get('output_tokens', 0),
|
|
397
393
|
"total_tokens": stats1.get('total_tokens', 0) + stats2.get('total_tokens', 0),
|
iatoolkit/infra/llm_proxy.py
CHANGED
|
@@ -7,6 +7,7 @@ from typing import Dict, List, Any
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
8
|
from iatoolkit.common.util import Utility
|
|
9
9
|
from iatoolkit.infra.llm_response import LLMResponse
|
|
10
|
+
from iatoolkit.services.configuration_service import ConfigurationService
|
|
10
11
|
from iatoolkit.infra.openai_adapter import OpenAIAdapter
|
|
11
12
|
from iatoolkit.infra.gemini_adapter import GeminiAdapter
|
|
12
13
|
from iatoolkit.common.exceptions import IAToolkitException
|
|
@@ -41,12 +42,16 @@ class LLMProxy:
|
|
|
41
42
|
_clients_cache_lock = threading.Lock()
|
|
42
43
|
|
|
43
44
|
@inject
|
|
44
|
-
def __init__(self, util: Utility,
|
|
45
|
+
def __init__(self, util: Utility,
|
|
46
|
+
configuration_service: ConfigurationService,
|
|
47
|
+
openai_client = None,
|
|
48
|
+
gemini_client = None):
|
|
45
49
|
"""
|
|
46
50
|
Inicializa una instancia del proxy. Puede ser una instancia "base" (fábrica)
|
|
47
51
|
o una instancia de "trabajo" con clientes configurados.
|
|
48
52
|
"""
|
|
49
53
|
self.util = util
|
|
54
|
+
self.configuration_service = configuration_service
|
|
50
55
|
self.openai_adapter = OpenAIAdapter(openai_client) if openai_client else None
|
|
51
56
|
self.gemini_adapter = GeminiAdapter(gemini_client) if gemini_client else None
|
|
52
57
|
|
|
@@ -71,7 +76,11 @@ class LLMProxy:
|
|
|
71
76
|
)
|
|
72
77
|
|
|
73
78
|
# Devuelve una NUEVA instancia con los clientes configurados
|
|
74
|
-
return LLMProxy(
|
|
79
|
+
return LLMProxy(
|
|
80
|
+
util=self.util,
|
|
81
|
+
configuration_service=self.configuration_service,
|
|
82
|
+
openai_client=openai_client,
|
|
83
|
+
gemini_client=gemini_client)
|
|
75
84
|
|
|
76
85
|
def create_response(self, model: str, input: List[Dict], **kwargs) -> LLMResponse:
|
|
77
86
|
"""Enruta la llamada al adaptador correcto basado en el modelo."""
|
|
@@ -103,7 +112,7 @@ class LLMProxy:
|
|
|
103
112
|
elif provider == LLMProvider.GEMINI:
|
|
104
113
|
client = self._create_gemini_client(company)
|
|
105
114
|
else:
|
|
106
|
-
raise IAToolkitException(f"
|
|
115
|
+
raise IAToolkitException(f"provider not supported: {provider.value}")
|
|
107
116
|
|
|
108
117
|
if client:
|
|
109
118
|
LLMProxy._clients_cache[cache_key] = client
|
|
@@ -115,22 +124,41 @@ class LLMProxy:
|
|
|
115
124
|
|
|
116
125
|
def _create_openai_client(self, company: Company) -> OpenAI:
|
|
117
126
|
"""Crea un cliente de OpenAI con la API key."""
|
|
118
|
-
|
|
119
|
-
|
|
127
|
+
decrypted_api_key = ''
|
|
128
|
+
llm_config = self.configuration_service.get_configuration(company.short_name, 'llm')
|
|
129
|
+
|
|
130
|
+
# Try to get API key name from config first
|
|
131
|
+
if llm_config and llm_config.get('api-key'):
|
|
132
|
+
api_key_env_var = llm_config['api-key']
|
|
133
|
+
decrypted_api_key = os.getenv(api_key_env_var, '')
|
|
120
134
|
else:
|
|
121
|
-
|
|
135
|
+
# Fallback to old logic
|
|
136
|
+
if company.openai_api_key:
|
|
137
|
+
decrypted_api_key = self.util.decrypt_key(company.openai_api_key)
|
|
138
|
+
else:
|
|
139
|
+
decrypted_api_key = os.getenv("OPENAI_API_KEY", '')
|
|
140
|
+
|
|
122
141
|
if not decrypted_api_key:
|
|
123
142
|
raise IAToolkitException(IAToolkitException.ErrorType.API_KEY,
|
|
124
|
-
|
|
143
|
+
f"La empresa '{company.name}' no tiene API key de OpenAI.")
|
|
125
144
|
return OpenAI(api_key=decrypted_api_key)
|
|
126
145
|
|
|
127
146
|
def _create_gemini_client(self, company: Company) -> Any:
|
|
128
147
|
"""Configura y devuelve el cliente de Gemini."""
|
|
129
148
|
|
|
130
|
-
|
|
131
|
-
|
|
149
|
+
decrypted_api_key = ''
|
|
150
|
+
llm_config = self.configuration_service.get_configuration(company.short_name, 'llm')
|
|
151
|
+
|
|
152
|
+
# Try to get API key name from config first
|
|
153
|
+
if llm_config and llm_config.get('api-key'):
|
|
154
|
+
api_key_env_var = llm_config['api-key']
|
|
155
|
+
decrypted_api_key = os.getenv(api_key_env_var, '')
|
|
132
156
|
else:
|
|
133
|
-
|
|
157
|
+
# Fallback to old logic
|
|
158
|
+
if company.gemini_api_key:
|
|
159
|
+
decrypted_api_key = self.util.decrypt_key(company.gemini_api_key)
|
|
160
|
+
else:
|
|
161
|
+
decrypted_api_key = os.getenv("GEMINI_API_KEY", '')
|
|
134
162
|
|
|
135
163
|
if not decrypted_api_key:
|
|
136
164
|
return None
|
iatoolkit/locales/en.yaml
CHANGED
|
@@ -77,6 +77,8 @@ ui:
|
|
|
77
77
|
stop: "Stop"
|
|
78
78
|
|
|
79
79
|
errors:
|
|
80
|
+
company_not_found: "The company {company_short_name} does not exist."
|
|
81
|
+
timeout: "timeout expired."
|
|
80
82
|
auth:
|
|
81
83
|
invalid_password: "The provided password is incorrect."
|
|
82
84
|
user_not_found: "A user with that email address was not found."
|
|
@@ -87,7 +89,7 @@ errors:
|
|
|
87
89
|
no_user_identifier_api: "No user_identifier provided for API call."
|
|
88
90
|
templates:
|
|
89
91
|
company_not_found: "Company not found."
|
|
90
|
-
home_template_not_found: "The home page template for the company '{
|
|
92
|
+
home_template_not_found: "The home page template for the company '{company_short_name}' is not configured."
|
|
91
93
|
template_not_found: "Template not found: '{template_name}'."
|
|
92
94
|
|
|
93
95
|
processing_error: "An error occurred while processing the custom home page template: {error}"
|
|
@@ -95,7 +97,7 @@ errors:
|
|
|
95
97
|
unexpected_error: "An unexpected error has occurred. Please contact support."
|
|
96
98
|
unsupported_language: "The selected language is not supported."
|
|
97
99
|
signup:
|
|
98
|
-
company_not_found: "The company {
|
|
100
|
+
company_not_found: "The company {company_short_name} does not exist."
|
|
99
101
|
incorrect_password_for_existing_user: "The password for the user {email} is incorrect."
|
|
100
102
|
user_already_registered: "The user with email '{email}' already exists in this company."
|
|
101
103
|
password_mismatch: "The passwords do not match. Please try again."
|
|
@@ -115,9 +117,29 @@ errors:
|
|
|
115
117
|
password_no_digit: "Password must contain at least one number."
|
|
116
118
|
password_no_special_char: "Password must contain at least one special character."
|
|
117
119
|
|
|
120
|
+
services:
|
|
121
|
+
no_text_file: "The file is not text or the encoding is not UTF-8"
|
|
122
|
+
no_output_file: "Missing output file name"
|
|
123
|
+
no_data_for_excel: "Missing data or it is not a list of dictionaries"
|
|
124
|
+
no_download_directory: "Temporary directory for saving Excel files is not configured"
|
|
125
|
+
cannot_create_excel: "Could not create the Excel file"
|
|
126
|
+
invalid_filename: "Invalid filename"
|
|
127
|
+
file_not_exist: "File not found"
|
|
128
|
+
path_is_not_a_file: "The path does not correspond to a file"
|
|
129
|
+
file_validation_error: "Error validating file"
|
|
130
|
+
user_not_authorized: "user is not authorized for this company"
|
|
131
|
+
account_not_verified: "Your account has not been verified. Please check your email."
|
|
132
|
+
missing_response_id: "Can not found 'previous_response_id' for '{company_short_name}/{user_identifier}'. Reinit context"
|
|
133
|
+
|
|
134
|
+
|
|
118
135
|
api_responses:
|
|
119
136
|
context_reloaded_success: "The context has been successfully reloaded."
|
|
120
137
|
|
|
138
|
+
services:
|
|
139
|
+
mail_sent: "Email sent successfully."
|
|
140
|
+
start_query: "Hello, what can I help you with today?"
|
|
141
|
+
|
|
142
|
+
|
|
121
143
|
flash_messages:
|
|
122
144
|
password_changed_success: "Your password has been successfully reset. You can now log in."
|
|
123
145
|
login_required: "Please log in to continue."
|
|
@@ -142,3 +164,4 @@ js_messages:
|
|
|
142
164
|
unknown_server_error: "Unknown server error."
|
|
143
165
|
loading: "Loading..."
|
|
144
166
|
reload_init: "init reloading context in background..."
|
|
167
|
+
no_history_found: "No query history found."
|
iatoolkit/locales/es.yaml
CHANGED
|
@@ -73,6 +73,10 @@
|
|
|
73
73
|
stop: "Detener"
|
|
74
74
|
|
|
75
75
|
errors:
|
|
76
|
+
company_not_found: "La empresa {company_short_name} no existe."
|
|
77
|
+
timeout: "El tiempo de espera ha expirado."
|
|
78
|
+
|
|
79
|
+
|
|
76
80
|
auth:
|
|
77
81
|
invalid_password: "La contraseña proporcionada es incorrecta."
|
|
78
82
|
user_not_found: "No se encontró un usuario con ese correo."
|
|
@@ -83,15 +87,15 @@
|
|
|
83
87
|
no_user_identifier_api: "No se proporcionó user_identifier para la llamada a la API."
|
|
84
88
|
templates:
|
|
85
89
|
company_not_found: "Empresa no encontrada."
|
|
86
|
-
home_template_not_found: "La plantilla de la página de inicio para la empresa '{
|
|
90
|
+
home_template_not_found: "La plantilla de la página de inicio para la empresa '{company_short_name}' no está configurada."
|
|
91
|
+
processing_error: "Error al procesar el template: {error}"
|
|
87
92
|
template_not_found: "No se encontro el template: '{template_name}'."
|
|
88
|
-
processing_error: "Ocurrió un error al procesar la plantilla personalizada de la página de inicio: {error}"
|
|
89
93
|
|
|
90
94
|
general:
|
|
91
|
-
unexpected_error: "Ha ocurrido un error inesperado
|
|
95
|
+
unexpected_error: "Ha ocurrido un error inesperado: {error}."
|
|
92
96
|
unsupported_language: "El idioma seleccionado no es válido."
|
|
93
97
|
signup:
|
|
94
|
-
company_not_found: "La empresa {
|
|
98
|
+
company_not_found: "La empresa {company_short_name} no existe."
|
|
95
99
|
incorrect_password_for_existing_user: "La contraseña para el usuario {email} es incorrecta."
|
|
96
100
|
user_already_registered: "El usuario con email '{email}' ya existe en esta empresa."
|
|
97
101
|
password_mismatch: "Las contraseñas no coinciden. Por favor, inténtalo de nuevo."
|
|
@@ -111,9 +115,27 @@
|
|
|
111
115
|
password_no_digit: "La contraseña debe tener al menos un número."
|
|
112
116
|
password_no_special_char: "La contraseña debe tener al menos un carácter especial."
|
|
113
117
|
|
|
118
|
+
services:
|
|
119
|
+
no_text_file: "El archivo no es texto o la codificación no es UTF-8"
|
|
120
|
+
no_output_file: "falta el nombre del archivo de salida"
|
|
121
|
+
no_data_for_excel: "faltan los datos o no es una lista de diccionarios"
|
|
122
|
+
no_download_directory: "no esta configurado el directorio temporal para guardar excels"
|
|
123
|
+
cannot_create_excel: "no se pudo crear el archivo excel"
|
|
124
|
+
invalid_filename: "Nombre de archivo inválido"
|
|
125
|
+
file_not_exist : "Archivo no encontrado"
|
|
126
|
+
path_is_not_a_file : "La ruta no corresponde a un archivo"
|
|
127
|
+
file_validation_error : "Error validando archivo"
|
|
128
|
+
user_not_authorized: "Usuario no esta autorizado para esta empresa"
|
|
129
|
+
account_not_verified: "Tu cuenta no ha sido verificada. Por favor, revisa tu correo."
|
|
130
|
+
missing_response_id: "No se encontró 'previous_response_id' para '{company_short_name}/{user_identifier}'. Reinicia el contexto."
|
|
131
|
+
|
|
114
132
|
api_responses:
|
|
115
133
|
context_reloaded_success: "El contexto se ha recargado con éxito."
|
|
116
134
|
|
|
135
|
+
services:
|
|
136
|
+
mail_sent: "mail enviado exitosamente."
|
|
137
|
+
start_query: "Hola, cual es tu pregunta?"
|
|
138
|
+
|
|
117
139
|
flash_messages:
|
|
118
140
|
password_changed_success: "Tu contraseña ha sido restablecida exitosamente. Ahora puedes iniciar sesión."
|
|
119
141
|
signup_success: "Registro exitoso. Por favor, revisa tu correo para verificar tu cuenta."
|
|
@@ -137,4 +159,5 @@
|
|
|
137
159
|
unknown_server_error: "Error desconocido del servidor."
|
|
138
160
|
loading: "Cargando..."
|
|
139
161
|
reload_init: "Iniciando recarga de contexto en segundo plano..."
|
|
162
|
+
no_history_found: "No existe historial de consultas."
|
|
140
163
|
|
|
@@ -27,8 +27,8 @@ class DatabaseManager:
|
|
|
27
27
|
self._engine = create_engine(
|
|
28
28
|
database_url,
|
|
29
29
|
echo=False,
|
|
30
|
-
pool_size=
|
|
31
|
-
max_overflow=
|
|
30
|
+
pool_size=10, # per worker
|
|
31
|
+
max_overflow=20,
|
|
32
32
|
pool_timeout=30,
|
|
33
33
|
pool_recycle=1800,
|
|
34
34
|
pool_pre_ping=True,
|
|
@@ -70,6 +70,11 @@ class DatabaseManager:
|
|
|
70
70
|
def remove_session(self):
|
|
71
71
|
self.scoped_session.remove()
|
|
72
72
|
|
|
73
|
+
def get_all_table_names(self) -> list[str]:
|
|
74
|
+
# Returns a list of all table names in the database
|
|
75
|
+
inspector = inspect(self._engine)
|
|
76
|
+
return inspector.get_table_names()
|
|
77
|
+
|
|
73
78
|
def get_table_schema(self,
|
|
74
79
|
table_name: str,
|
|
75
80
|
schema_name: str | None = None,
|
|
@@ -77,7 +82,7 @@ class DatabaseManager:
|
|
|
77
82
|
inspector = inspect(self._engine)
|
|
78
83
|
|
|
79
84
|
if table_name not in inspector.get_table_names():
|
|
80
|
-
raise RuntimeError(f"
|
|
85
|
+
raise RuntimeError(f"Table '{table_name}' does not exist.")
|
|
81
86
|
|
|
82
87
|
if exclude_columns is None:
|
|
83
88
|
exclude_columns = []
|
|
@@ -22,7 +22,7 @@ class DocumentRepo:
|
|
|
22
22
|
def get(self, company_id, filename: str ) -> Document:
|
|
23
23
|
if not company_id or not filename:
|
|
24
24
|
raise IAToolkitException(IAToolkitException.ErrorType.PARAM_NOT_FILLED,
|
|
25
|
-
'
|
|
25
|
+
'missing company_id or filename')
|
|
26
26
|
|
|
27
27
|
return self.session.query(Document).filter_by(company_id=company_id, filename=filename).first()
|
|
28
28
|
|
iatoolkit/repositories/models.py
CHANGED
|
@@ -53,17 +53,12 @@ class Company(Base):
|
|
|
53
53
|
id = Column(Integer, primary_key=True)
|
|
54
54
|
short_name = Column(String(20), nullable=False, unique=True, index=True)
|
|
55
55
|
name = Column(String(256), nullable=False)
|
|
56
|
-
default_language = Column(String(5), nullable=False, default='es')
|
|
57
56
|
|
|
58
57
|
# encrypted api-key
|
|
59
58
|
openai_api_key = Column(String, nullable=True)
|
|
60
59
|
gemini_api_key = Column(String, nullable=True)
|
|
61
|
-
|
|
62
|
-
branding = Column(JSON, nullable=True)
|
|
63
|
-
onboarding_cards = Column(JSON, nullable=True)
|
|
64
60
|
parameters = Column(JSON, nullable=True)
|
|
65
61
|
created_at = Column(DateTime, default=datetime.now)
|
|
66
|
-
allow_jwt = Column(Boolean, default=True, nullable=True)
|
|
67
62
|
|
|
68
63
|
documents = relationship("Document",
|
|
69
64
|
back_populates="company",
|
|
@@ -161,10 +156,10 @@ class Document(Base):
|
|
|
161
156
|
company_id = Column(Integer, ForeignKey('iat_companies.id',
|
|
162
157
|
ondelete='CASCADE'), nullable=False)
|
|
163
158
|
filename = Column(String(256), nullable=False, index=True)
|
|
164
|
-
content = Column(Text, nullable=False)
|
|
165
|
-
content_b64 = Column(Text, nullable=False)
|
|
166
159
|
meta = Column(JSON, nullable=True)
|
|
167
160
|
created_at = Column(DateTime, default=datetime.now)
|
|
161
|
+
content = Column(Text, nullable=False)
|
|
162
|
+
content_b64 = Column(Text, nullable=False)
|
|
168
163
|
|
|
169
164
|
company = relationship("Company", back_populates="documents")
|
|
170
165
|
|
|
@@ -207,7 +202,10 @@ class VSDoc(Base):
|
|
|
207
202
|
document_id = Column(Integer, ForeignKey('iat_documents.id',
|
|
208
203
|
ondelete='CASCADE'), nullable=False)
|
|
209
204
|
text = Column(Text, nullable=False)
|
|
210
|
-
|
|
205
|
+
|
|
206
|
+
# the size of this vector should be set depending on the embedding model used
|
|
207
|
+
# for OpenAI is 1536, and for huggingface is 384
|
|
208
|
+
embedding = Column(Vector(1536), nullable=False)
|
|
211
209
|
|
|
212
210
|
company = relationship("Company", back_populates="vsdocs")
|
|
213
211
|
|
|
@@ -74,10 +74,6 @@ class ProfileRepo:
|
|
|
74
74
|
if company:
|
|
75
75
|
if company.parameters != new_company.parameters:
|
|
76
76
|
company.parameters = new_company.parameters
|
|
77
|
-
if company.branding != new_company.branding:
|
|
78
|
-
company.branding = new_company.branding
|
|
79
|
-
if company.onboarding_cards != new_company.onboarding_cards:
|
|
80
|
-
company.onboarding_cards = new_company.onboarding_cards
|
|
81
77
|
else:
|
|
82
78
|
# Si la compañía no existe, la añade a la sesión.
|
|
83
79
|
self.session.add(new_company)
|