iatoolkit 0.67.0__py3-none-any.whl → 0.69.0__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 +0 -4
- iatoolkit/base_company.py +0 -6
- iatoolkit/iatoolkit.py +2 -5
- iatoolkit/repositories/database_manager.py +5 -0
- iatoolkit/repositories/models.py +0 -2
- iatoolkit/services/branding_service.py +7 -5
- iatoolkit/services/company_context_service.py +145 -0
- iatoolkit/services/configuration_service.py +13 -20
- iatoolkit/services/dispatcher_service.py +34 -33
- iatoolkit/services/language_service.py +9 -5
- iatoolkit/services/profile_service.py +3 -0
- iatoolkit/services/query_service.py +4 -1
- iatoolkit/services/search_service.py +11 -4
- iatoolkit/services/sql_service.py +49 -23
- iatoolkit/static/js/chat_onboarding_button.js +6 -0
- iatoolkit/static/styles/onboarding.css +7 -0
- iatoolkit/templates/change_password.html +1 -1
- iatoolkit/templates/chat.html +1 -0
- iatoolkit/templates/chat_modals.html +1 -0
- iatoolkit/templates/login_simulation.html +1 -1
- 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/external_login_view.py +1 -1
- iatoolkit/views/forgot_password_view.py +2 -4
- iatoolkit/views/help_content_api_view.py +1 -1
- iatoolkit/views/home_view.py +1 -2
- iatoolkit/views/login_simulation_view.py +1 -1
- iatoolkit/views/login_view.py +8 -8
- iatoolkit/views/signup_view.py +2 -4
- iatoolkit/views/verify_user_view.py +1 -3
- {iatoolkit-0.67.0.dist-info → iatoolkit-0.69.0.dist-info}/METADATA +1 -1
- {iatoolkit-0.67.0.dist-info → iatoolkit-0.69.0.dist-info}/RECORD +36 -36
- iatoolkit/services/onboarding_service.py +0 -49
- {iatoolkit-0.67.0.dist-info → iatoolkit-0.69.0.dist-info}/WHEEL +0 -0
- {iatoolkit-0.67.0.dist-info → iatoolkit-0.69.0.dist-info}/licenses/LICENSE +0 -0
- {iatoolkit-0.67.0.dist-info → iatoolkit-0.69.0.dist-info}/top_level.txt +0 -0
iatoolkit/__init__.py
CHANGED
|
@@ -10,10 +10,8 @@ from .iatoolkit import IAToolkit, current_iatoolkit, create_app
|
|
|
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
|
-
from iatoolkit.services.configuration_service import ConfigurationService
|
|
17
15
|
from iatoolkit.services.query_service import QueryService
|
|
18
16
|
from iatoolkit.services.sql_service import SqlService
|
|
19
17
|
from iatoolkit.services.document_service import DocumentService
|
|
@@ -28,9 +26,7 @@ __all__ = [
|
|
|
28
26
|
'current_iatoolkit',
|
|
29
27
|
'register_company',
|
|
30
28
|
'BaseCompany',
|
|
31
|
-
'DatabaseManager',
|
|
32
29
|
'QueryService',
|
|
33
|
-
'ConfigurationService',
|
|
34
30
|
'SqlService',
|
|
35
31
|
'ExcelService',
|
|
36
32
|
'DocumentService',
|
iatoolkit/base_company.py
CHANGED
|
@@ -68,12 +68,6 @@ class BaseCompany(ABC):
|
|
|
68
68
|
**kwargs
|
|
69
69
|
)
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
@abstractmethod
|
|
73
|
-
# get context specific for this company
|
|
74
|
-
def get_company_context(self, **kwargs) -> str:
|
|
75
|
-
raise NotImplementedError("La subclase debe implementar el método get_company_context()")
|
|
76
|
-
|
|
77
71
|
@abstractmethod
|
|
78
72
|
# get context specific for this company
|
|
79
73
|
def get_user_info(self, user_identifier: str) -> dict:
|
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.69.0"
|
|
23
23
|
|
|
24
24
|
# global variable for the unique instance of IAToolkit
|
|
25
25
|
_iatoolkit_instance: Optional['IAToolkit'] = None
|
|
@@ -293,7 +293,6 @@ class IAToolkit:
|
|
|
293
293
|
from iatoolkit.repositories.document_repo import DocumentRepo
|
|
294
294
|
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
295
295
|
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
296
|
-
|
|
297
296
|
from iatoolkit.repositories.vs_repo import VSRepo
|
|
298
297
|
from iatoolkit.repositories.tasks_repo import TaskRepo
|
|
299
298
|
|
|
@@ -318,7 +317,6 @@ class IAToolkit:
|
|
|
318
317
|
from iatoolkit.services.branding_service import BrandingService
|
|
319
318
|
from iatoolkit.services.i18n_service import I18nService
|
|
320
319
|
from iatoolkit.services.language_service import LanguageService
|
|
321
|
-
from iatoolkit.services.onboarding_service import OnboardingService
|
|
322
320
|
from iatoolkit.services.configuration_service import ConfigurationService
|
|
323
321
|
|
|
324
322
|
binder.bind(QueryService, to=QueryService)
|
|
@@ -333,7 +331,6 @@ class IAToolkit:
|
|
|
333
331
|
binder.bind(JWTService, to=JWTService)
|
|
334
332
|
binder.bind(Dispatcher, to=Dispatcher)
|
|
335
333
|
binder.bind(BrandingService, to=BrandingService)
|
|
336
|
-
binder.bind(OnboardingService, to=OnboardingService)
|
|
337
334
|
binder.bind(I18nService, to=I18nService)
|
|
338
335
|
binder.bind(LanguageService, to=LanguageService)
|
|
339
336
|
binder.bind(ConfigurationService, to=ConfigurationService)
|
|
@@ -363,7 +360,7 @@ class IAToolkit:
|
|
|
363
360
|
# instantiate all the registered companies
|
|
364
361
|
get_company_registry().instantiate_companies(self._injector)
|
|
365
362
|
|
|
366
|
-
# use the dispatcher to load the config and prepare the execution
|
|
363
|
+
# use the dispatcher to load the company config.yaml file and prepare the execution
|
|
367
364
|
dispatcher = self._injector.get(Dispatcher)
|
|
368
365
|
dispatcher.load_company_configs()
|
|
369
366
|
|
|
@@ -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,
|
iatoolkit/repositories/models.py
CHANGED
|
@@ -53,14 +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
60
|
parameters = Column(JSON, nullable=True)
|
|
62
61
|
created_at = Column(DateTime, default=datetime.now)
|
|
63
|
-
allow_jwt = Column(Boolean, default=True, nullable=True)
|
|
64
62
|
|
|
65
63
|
documents = relationship("Document",
|
|
66
64
|
back_populates="company",
|
|
@@ -69,16 +69,15 @@ class BrandingService:
|
|
|
69
69
|
"send_button_color": "#212529" # Gris oscuro/casi negro por defecto
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
def get_company_branding(self,
|
|
72
|
+
def get_company_branding(self, company_short_name: str) -> dict:
|
|
73
73
|
"""
|
|
74
74
|
Retorna los estilos de branding finales para una compañía,
|
|
75
75
|
fusionando los valores por defecto con los personalizados.
|
|
76
76
|
"""
|
|
77
77
|
final_branding_values = self._default_branding.copy()
|
|
78
|
+
branding_data = self.config_service.get_configuration(company_short_name, 'branding')
|
|
79
|
+
final_branding_values.update(branding_data)
|
|
78
80
|
|
|
79
|
-
if company:
|
|
80
|
-
branding_data = self.config_service.get_company_content(company.short_name, 'branding')
|
|
81
|
-
final_branding_values.update(branding_data)
|
|
82
81
|
|
|
83
82
|
# Función para convertir HEX a RGB
|
|
84
83
|
def hex_to_rgb(hex_color):
|
|
@@ -140,8 +139,11 @@ class BrandingService:
|
|
|
140
139
|
}}
|
|
141
140
|
"""
|
|
142
141
|
|
|
142
|
+
# get the company name from configuration for the branding render
|
|
143
|
+
company_name = self.config_service.get_configuration(company_short_name, 'name')
|
|
144
|
+
|
|
143
145
|
return {
|
|
144
|
-
"name":
|
|
146
|
+
"name": company_name,
|
|
145
147
|
"primary_text_style": primary_text_style,
|
|
146
148
|
"secondary_text_style": secondary_text_style,
|
|
147
149
|
"tertiary_text_style": tertiary_text_style,
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from iatoolkit.common.util import Utility
|
|
7
|
+
from iatoolkit.services.configuration_service import ConfigurationService
|
|
8
|
+
from iatoolkit.services.sql_service import SqlService
|
|
9
|
+
from iatoolkit.common.exceptions import IAToolkitException
|
|
10
|
+
import logging
|
|
11
|
+
from injector import inject
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CompanyContextService:
|
|
16
|
+
"""
|
|
17
|
+
Responsible for building the complete context string for a given company
|
|
18
|
+
to be sent to the Language Model.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@inject
|
|
22
|
+
def __init__(self,
|
|
23
|
+
sql_service: SqlService,
|
|
24
|
+
utility: Utility,
|
|
25
|
+
config_service: ConfigurationService):
|
|
26
|
+
self.sql_service = sql_service
|
|
27
|
+
self.utility = utility
|
|
28
|
+
self.config_service = config_service
|
|
29
|
+
|
|
30
|
+
def get_company_context(self, company_short_name: str) -> str:
|
|
31
|
+
"""
|
|
32
|
+
Builds the full context by aggregating three sources:
|
|
33
|
+
1. Static context files (Markdown).
|
|
34
|
+
2. Static schema files (YAML for APIs, etc.).
|
|
35
|
+
3. Dynamic SQL database schema from the live connection.
|
|
36
|
+
"""
|
|
37
|
+
context_parts = []
|
|
38
|
+
|
|
39
|
+
# 1. Context from Markdown (context/*.md) and yaml (schema/*.yaml) files
|
|
40
|
+
try:
|
|
41
|
+
md_context = self._get_static_file_context(company_short_name)
|
|
42
|
+
if md_context:
|
|
43
|
+
context_parts.append(md_context)
|
|
44
|
+
except Exception as e:
|
|
45
|
+
logging.warning(f"Could not load Markdown context for '{company_short_name}': {e}")
|
|
46
|
+
|
|
47
|
+
# 2. Context from company-specific Python logic (SQL schemas)
|
|
48
|
+
try:
|
|
49
|
+
sql_context = self._get_sql_schema_context(company_short_name)
|
|
50
|
+
if sql_context:
|
|
51
|
+
context_parts.append(sql_context)
|
|
52
|
+
except Exception as e:
|
|
53
|
+
logging.warning(f"Could not generate SQL context for '{company_short_name}': {e}")
|
|
54
|
+
|
|
55
|
+
# Join all parts with a clear separator
|
|
56
|
+
return "\n\n---\n\n".join(context_parts)
|
|
57
|
+
|
|
58
|
+
def _get_static_file_context(self, company_short_name: str) -> str:
|
|
59
|
+
# Get context from .md and .yaml schema files.
|
|
60
|
+
static_context = ''
|
|
61
|
+
|
|
62
|
+
# Part 1: Markdown context files
|
|
63
|
+
context_dir = f'companies/{company_short_name}/context'
|
|
64
|
+
if os.path.exists(context_dir):
|
|
65
|
+
context_files = self.utility.get_files_by_extension(context_dir, '.md', return_extension=True)
|
|
66
|
+
for file in context_files:
|
|
67
|
+
filepath = os.path.join(context_dir, file)
|
|
68
|
+
static_context += self.utility.load_markdown_context(filepath)
|
|
69
|
+
|
|
70
|
+
# Part 2: YAML schema files
|
|
71
|
+
schema_dir = f'companies/{company_short_name}/schema'
|
|
72
|
+
if os.path.exists(schema_dir):
|
|
73
|
+
schema_files = self.utility.get_files_by_extension(schema_dir, '.yaml', return_extension=True)
|
|
74
|
+
for file in schema_files:
|
|
75
|
+
schema_name = file.split('.')[0] # Use full filename as entity name
|
|
76
|
+
filepath = os.path.join(schema_dir, file)
|
|
77
|
+
static_context += self.utility.generate_context_for_schema(schema_name, filepath)
|
|
78
|
+
|
|
79
|
+
return static_context
|
|
80
|
+
|
|
81
|
+
def _get_sql_schema_context(self, company_short_name: str) -> str:
|
|
82
|
+
"""
|
|
83
|
+
Generates the SQL schema context by inspecting live database connections
|
|
84
|
+
based on the flexible company.yaml configuration.
|
|
85
|
+
It supports including all tables and providing specific overrides for a subset of them.
|
|
86
|
+
"""
|
|
87
|
+
data_sources_config = self.config_service.get_configuration(company_short_name, 'data_sources')
|
|
88
|
+
if not data_sources_config or not data_sources_config.get('sql'):
|
|
89
|
+
return ''
|
|
90
|
+
|
|
91
|
+
sql_context = ''
|
|
92
|
+
for source in data_sources_config.get('sql', []):
|
|
93
|
+
db_name = source.get('database')
|
|
94
|
+
if not db_name:
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
db_manager = self.sql_service.get_database_manager(db_name)
|
|
99
|
+
except IAToolkitException as e:
|
|
100
|
+
logging.warning(f"Could not get DB manager for '{db_name}': {e}")
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
db_description = source.get('description', '')
|
|
104
|
+
sql_context += f"{db_description}\n" if db_description else ""
|
|
105
|
+
|
|
106
|
+
# 1. get the list of tables to process.
|
|
107
|
+
tables_to_process = []
|
|
108
|
+
if source.get('include_all_tables', False):
|
|
109
|
+
all_tables = db_manager.get_all_table_names()
|
|
110
|
+
tables_to_exclude = set(source.get('exclude_tables', []))
|
|
111
|
+
tables_to_process = [t for t in all_tables if t not in tables_to_exclude]
|
|
112
|
+
elif 'tables' in source:
|
|
113
|
+
# if not include_all_tables, use the list of tables explicitly specified in the map.
|
|
114
|
+
tables_to_process = list(source['tables'].keys())
|
|
115
|
+
|
|
116
|
+
# 2. get the global list of columns to exclude.
|
|
117
|
+
global_exclude_columns = source.get('exclude_columns', [])
|
|
118
|
+
|
|
119
|
+
# 3. get the overrides for specific tables.
|
|
120
|
+
table_overrides = source.get('tables', {})
|
|
121
|
+
|
|
122
|
+
# 3. iterate over the tables.
|
|
123
|
+
for table_name in tables_to_process:
|
|
124
|
+
try:
|
|
125
|
+
# 4. get the table specific configuration.
|
|
126
|
+
table_config = table_overrides.get(table_name, {})
|
|
127
|
+
|
|
128
|
+
# 5. define the schema name, using the override if it exists.
|
|
129
|
+
schema_name = table_config.get('schema_name', table_name)
|
|
130
|
+
|
|
131
|
+
# 6. define the list of columns to exclude, (local vs. global).
|
|
132
|
+
local_exclude_columns = table_config.get('exclude_columns')
|
|
133
|
+
final_exclude_columns = local_exclude_columns if local_exclude_columns is not None else global_exclude_columns
|
|
134
|
+
|
|
135
|
+
# 7. get the table schema definition.
|
|
136
|
+
table_definition = db_manager.get_table_schema(
|
|
137
|
+
table_name=table_name,
|
|
138
|
+
schema_name=schema_name,
|
|
139
|
+
exclude_columns=final_exclude_columns
|
|
140
|
+
)
|
|
141
|
+
sql_context += table_definition
|
|
142
|
+
except (KeyError, RuntimeError) as e:
|
|
143
|
+
logging.warning(f"Could not generate schema for table '{table_name}': {e}")
|
|
144
|
+
|
|
145
|
+
return sql_context
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
# Product: IAToolkit
|
|
4
4
|
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from iatoolkit import BaseCompany
|
|
7
|
-
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
8
6
|
from iatoolkit.repositories.models import Company
|
|
9
7
|
from iatoolkit.common.util import Utility
|
|
10
8
|
from injector import inject
|
|
@@ -18,13 +16,11 @@ class ConfigurationService:
|
|
|
18
16
|
|
|
19
17
|
@inject
|
|
20
18
|
def __init__(self,
|
|
21
|
-
profile_repo: ProfileRepo,
|
|
22
19
|
utility: Utility):
|
|
23
|
-
self.profile_repo = profile_repo
|
|
24
20
|
self.utility = utility
|
|
25
21
|
self._loaded_configs = {} # cache for store loaded configurations
|
|
26
22
|
|
|
27
|
-
def
|
|
23
|
+
def get_configuration(self, company_short_name: str, content_key: str):
|
|
28
24
|
"""
|
|
29
25
|
Public method to provide a specific section of a company's configuration.
|
|
30
26
|
It uses a cache to avoid reading files from disk on every call.
|
|
@@ -32,31 +28,29 @@ class ConfigurationService:
|
|
|
32
28
|
self._ensure_config_loaded(company_short_name)
|
|
33
29
|
return self._loaded_configs[company_short_name].get(content_key)
|
|
34
30
|
|
|
35
|
-
def
|
|
31
|
+
def load_configuration(self, company_short_name: str, company_instance):
|
|
36
32
|
"""
|
|
37
33
|
Main entry point for configuring a company instance.
|
|
38
34
|
This method is invoked by the dispatcher for each registered company.
|
|
39
35
|
"""
|
|
40
36
|
logging.info(f"⚙️ Starting configuration for company '{company_short_name}'...")
|
|
41
37
|
|
|
42
|
-
# 1.
|
|
43
|
-
company_instance.id = company_short_name
|
|
44
|
-
company_instance.company = self.profile_repo.get_company_by_short_name(company_short_name)
|
|
45
|
-
|
|
46
|
-
# 2. Load the main configuration file and supplementary content files
|
|
38
|
+
# 1. Load the main configuration file and supplementary content files
|
|
47
39
|
config = self._load_and_merge_configs(company_short_name)
|
|
48
40
|
|
|
49
|
-
#
|
|
41
|
+
# 2. Register core company details and get the database object
|
|
50
42
|
company_db_object = self._register_core_details(company_instance, config)
|
|
51
43
|
|
|
52
|
-
#
|
|
44
|
+
# 3. Register tools (functions)
|
|
53
45
|
self._register_tools(company_instance, config.get('tools', []))
|
|
54
46
|
|
|
55
|
-
#
|
|
47
|
+
# 4. Register prompt categories and prompts
|
|
56
48
|
self._register_prompts(company_instance, config)
|
|
57
49
|
|
|
58
|
-
#
|
|
50
|
+
# 5. Link the persisted Company object back to the running instance
|
|
51
|
+
company_instance.company_short_name = company_short_name
|
|
59
52
|
company_instance.company = company_db_object
|
|
53
|
+
company_instance.id = company_instance.company.id
|
|
60
54
|
|
|
61
55
|
logging.info(f"✅ Company '{company_short_name}' configured successfully.")
|
|
62
56
|
|
|
@@ -92,15 +86,15 @@ class ConfigurationService:
|
|
|
92
86
|
|
|
93
87
|
return config
|
|
94
88
|
|
|
95
|
-
def _register_core_details(self, company_instance
|
|
89
|
+
def _register_core_details(self, company_instance, config: dict) -> Company:
|
|
96
90
|
"""Calls _create_company with data from the merged YAML config."""
|
|
97
91
|
return company_instance._create_company(
|
|
98
|
-
name=config['name'],
|
|
99
92
|
short_name=config['id'],
|
|
93
|
+
name=config['name'],
|
|
100
94
|
parameters=config.get('parameters', {})
|
|
101
95
|
)
|
|
102
96
|
|
|
103
|
-
def _register_tools(self, company_instance
|
|
97
|
+
def _register_tools(self, company_instance, tools_config: list):
|
|
104
98
|
"""Calls _create_function for each tool defined in the YAML."""
|
|
105
99
|
for tool in tools_config:
|
|
106
100
|
company_instance._create_function(
|
|
@@ -109,7 +103,7 @@ class ConfigurationService:
|
|
|
109
103
|
params=tool['params']
|
|
110
104
|
)
|
|
111
105
|
|
|
112
|
-
def _register_prompts(self, company_instance
|
|
106
|
+
def _register_prompts(self, company_instance, config: dict):
|
|
113
107
|
"""
|
|
114
108
|
Creates prompt categories first, then creates each prompt and assigns
|
|
115
109
|
it to its respective category.
|
|
@@ -129,7 +123,6 @@ class ConfigurationService:
|
|
|
129
123
|
continue
|
|
130
124
|
|
|
131
125
|
category_obj = created_categories[category_name]
|
|
132
|
-
|
|
133
126
|
company_instance._create_prompt(
|
|
134
127
|
prompt_name=prompt_data['name'],
|
|
135
128
|
description=prompt_data['description'],
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
from iatoolkit.common.exceptions import IAToolkitException
|
|
7
7
|
from iatoolkit.services.prompt_manager_service import PromptService
|
|
8
|
+
from iatoolkit.services.sql_service import SqlService
|
|
8
9
|
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
9
10
|
from iatoolkit.services.configuration_service import ConfigurationService
|
|
10
11
|
from iatoolkit.repositories.models import Company, Function
|
|
@@ -23,12 +24,14 @@ class Dispatcher:
|
|
|
23
24
|
prompt_service: PromptService,
|
|
24
25
|
llmquery_repo: LLMQueryRepo,
|
|
25
26
|
util: Utility,
|
|
27
|
+
sql_service: SqlService,
|
|
26
28
|
excel_service: ExcelService,
|
|
27
29
|
mail_service: MailService):
|
|
28
30
|
self.config_service = config_service
|
|
29
31
|
self.prompt_service = prompt_service
|
|
30
32
|
self.llmquery_repo = llmquery_repo
|
|
31
33
|
self.util = util
|
|
34
|
+
self.sql_service = sql_service
|
|
32
35
|
self.excel_service = excel_service
|
|
33
36
|
self.mail_service = mail_service
|
|
34
37
|
self.system_functions = _FUNCTION_LIST
|
|
@@ -64,14 +67,43 @@ class Dispatcher:
|
|
|
64
67
|
"""Loads the configuration of every company"""
|
|
65
68
|
for company_name, company_instance in self.company_instances.items():
|
|
66
69
|
try:
|
|
67
|
-
#
|
|
68
|
-
self.config_service.
|
|
70
|
+
# read company configuration from company.yaml
|
|
71
|
+
self.config_service.load_configuration(company_name, company_instance)
|
|
72
|
+
|
|
73
|
+
# register the company databases
|
|
74
|
+
self._register_company_databases(company_name)
|
|
75
|
+
|
|
69
76
|
except Exception as e:
|
|
70
77
|
logging.error(f"❌ Failed to register configuration for '{company_name}': {e}")
|
|
71
78
|
continue
|
|
72
79
|
|
|
73
80
|
return True
|
|
74
81
|
|
|
82
|
+
def _register_company_databases(self, company_name: str):
|
|
83
|
+
"""
|
|
84
|
+
Reads the data_sources config for a company and registers each
|
|
85
|
+
database with the central SqlService.
|
|
86
|
+
"""
|
|
87
|
+
logging.info(f" -> Registering databases for '{company_name}'...")
|
|
88
|
+
data_sources_config = self.config_service.get_configuration(company_name, 'data_sources')
|
|
89
|
+
|
|
90
|
+
if not data_sources_config or not data_sources_config.get('sql'):
|
|
91
|
+
logging.info(f" -> No SQL data sources to register for '{company_name}'.")
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
for db_config in data_sources_config['sql']:
|
|
95
|
+
db_name = db_config.get('database')
|
|
96
|
+
db_env_var = db_config.get('connection_string_env')
|
|
97
|
+
|
|
98
|
+
# resolve the URI connection string from the environment variable
|
|
99
|
+
db_uri = os.getenv(db_env_var) if db_env_var else None
|
|
100
|
+
if not db_uri:
|
|
101
|
+
logging.warning(
|
|
102
|
+
f"-> Skipping database registration for '{company_name}' due to missing 'database' name or connection URI.")
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
self.sql_service.register_database(db_name, db_uri)
|
|
106
|
+
|
|
75
107
|
def setup_iatoolkit_system(self):
|
|
76
108
|
# create system functions
|
|
77
109
|
for function in self.system_functions:
|
|
@@ -123,37 +155,6 @@ class Dispatcher:
|
|
|
123
155
|
raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
|
|
124
156
|
f"Error en function call '{action}': {str(e)}") from e
|
|
125
157
|
|
|
126
|
-
def get_company_context(self, company_name: str, **kwargs) -> str:
|
|
127
|
-
if company_name not in self.company_instances:
|
|
128
|
-
raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
|
|
129
|
-
f"Empresa no configurada: {company_name}")
|
|
130
|
-
|
|
131
|
-
company_context = ''
|
|
132
|
-
|
|
133
|
-
# read the company context from this list of markdown files,
|
|
134
|
-
# company brief, credits, operation description, etc.
|
|
135
|
-
context_dir = os.path.join(os.getcwd(), f'companies/{company_name}/context')
|
|
136
|
-
context_files = self.util.get_files_by_extension(context_dir, '.md', return_extension=True)
|
|
137
|
-
for file in context_files:
|
|
138
|
-
filepath = os.path.join(context_dir, file)
|
|
139
|
-
company_context += self.util.load_markdown_context(filepath)
|
|
140
|
-
|
|
141
|
-
# add the schemas for every table or function call responses
|
|
142
|
-
schema_dir = os.path.join(os.getcwd(), f'companies/{company_name}/schema')
|
|
143
|
-
schema_files = self.util.get_files_by_extension(schema_dir, '.yaml', return_extension=True)
|
|
144
|
-
for file in schema_files:
|
|
145
|
-
schema_name = file.split('_')[0]
|
|
146
|
-
filepath = os.path.join(schema_dir, file)
|
|
147
|
-
company_context += self.util.generate_context_for_schema(schema_name, filepath)
|
|
148
|
-
|
|
149
|
-
company_instance = self.company_instances[company_name]
|
|
150
|
-
try:
|
|
151
|
-
return company_context + company_instance.get_company_context(**kwargs)
|
|
152
|
-
except Exception as e:
|
|
153
|
-
logging.exception(e)
|
|
154
|
-
raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
|
|
155
|
-
f"Error getting company context of: {company_name}: {str(e)}") from e
|
|
156
|
-
|
|
157
158
|
def get_company_services(self, company: Company) -> list[dict]:
|
|
158
159
|
# create the syntax with openai response syntax, for the company function list
|
|
159
160
|
tools = []
|
|
@@ -4,6 +4,7 @@ import logging
|
|
|
4
4
|
from injector import inject, singleton
|
|
5
5
|
from flask import g, request
|
|
6
6
|
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
7
|
+
from iatoolkit.services.configuration_service import ConfigurationService
|
|
7
8
|
from iatoolkit.common.session_manager import SessionManager
|
|
8
9
|
|
|
9
10
|
@singleton
|
|
@@ -17,7 +18,10 @@ class LanguageService:
|
|
|
17
18
|
FALLBACK_LANGUAGE = 'es'
|
|
18
19
|
|
|
19
20
|
@inject
|
|
20
|
-
def __init__(self,
|
|
21
|
+
def __init__(self,
|
|
22
|
+
config_service: ConfigurationService,
|
|
23
|
+
profile_repo: ProfileRepo):
|
|
24
|
+
self.config_service = config_service
|
|
21
25
|
self.profile_repo = profile_repo
|
|
22
26
|
|
|
23
27
|
def _get_company_short_name(self) -> str | None:
|
|
@@ -64,10 +68,10 @@ class LanguageService:
|
|
|
64
68
|
# Priority 2: Company's default language
|
|
65
69
|
company_short_name = self._get_company_short_name()
|
|
66
70
|
if company_short_name:
|
|
67
|
-
|
|
68
|
-
if
|
|
69
|
-
|
|
70
|
-
g.lang =
|
|
71
|
+
locale = self.config_service.get_configuration(company_short_name, 'locale')
|
|
72
|
+
if locale:
|
|
73
|
+
company_language = locale.split('_')[0]
|
|
74
|
+
g.lang = company_language
|
|
71
75
|
return g.lang
|
|
72
76
|
except Exception as e:
|
|
73
77
|
logging.info(f"Could not determine language, falling back to default. Reason: {e}")
|
|
@@ -95,6 +95,9 @@ class ProfileService:
|
|
|
95
95
|
user_identifier=user_identifier,
|
|
96
96
|
user_profile=external_user_profile)
|
|
97
97
|
|
|
98
|
+
# 3. make sure the flask session is clean
|
|
99
|
+
SessionManager.clear()
|
|
100
|
+
|
|
98
101
|
def save_user_profile(self, company: Company, user_identifier: str, user_profile: dict):
|
|
99
102
|
"""
|
|
100
103
|
Private helper: Takes a pre-built profile, saves it to Redis, and sets the Flask cookie.
|
|
@@ -8,6 +8,7 @@ from iatoolkit.services.profile_service import ProfileService
|
|
|
8
8
|
from iatoolkit.repositories.document_repo import DocumentRepo
|
|
9
9
|
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
10
10
|
from iatoolkit.services.document_service import DocumentService
|
|
11
|
+
from iatoolkit.services.company_context_service import CompanyContextService
|
|
11
12
|
from iatoolkit.services.i18n_service import I18nService
|
|
12
13
|
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
13
14
|
from iatoolkit.repositories.models import Task
|
|
@@ -33,6 +34,7 @@ class QueryService:
|
|
|
33
34
|
def __init__(self,
|
|
34
35
|
llm_client: llmClient,
|
|
35
36
|
profile_service: ProfileService,
|
|
37
|
+
company_context_service: CompanyContextService,
|
|
36
38
|
document_service: DocumentService,
|
|
37
39
|
document_repo: DocumentRepo,
|
|
38
40
|
llmquery_repo: LLMQueryRepo,
|
|
@@ -44,6 +46,7 @@ class QueryService:
|
|
|
44
46
|
session_context: UserSessionContextService
|
|
45
47
|
):
|
|
46
48
|
self.profile_service = profile_service
|
|
49
|
+
self.company_context_service = company_context_service
|
|
47
50
|
self.document_service = document_service
|
|
48
51
|
self.document_repo = document_repo
|
|
49
52
|
self.llmquery_repo = llmquery_repo
|
|
@@ -81,7 +84,7 @@ class QueryService:
|
|
|
81
84
|
)
|
|
82
85
|
|
|
83
86
|
# get the company context: schemas, database models, .md files
|
|
84
|
-
company_specific_context = self.
|
|
87
|
+
company_specific_context = self.company_context_service.get_company_context(company_short_name)
|
|
85
88
|
|
|
86
89
|
# merge context: company + user
|
|
87
90
|
final_system_context = f"{company_specific_context}\n{rendered_system_prompt}"
|
|
@@ -5,19 +5,22 @@
|
|
|
5
5
|
|
|
6
6
|
from iatoolkit.repositories.vs_repo import VSRepo
|
|
7
7
|
from iatoolkit.repositories.document_repo import DocumentRepo
|
|
8
|
+
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
9
|
+
from iatoolkit.repositories.models import Company
|
|
8
10
|
from injector import inject
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
class SearchService:
|
|
12
14
|
@inject
|
|
13
15
|
def __init__(self,
|
|
16
|
+
profile_repo: ProfileRepo,
|
|
14
17
|
doc_repo: DocumentRepo,
|
|
15
18
|
vs_repo: VSRepo):
|
|
16
|
-
|
|
19
|
+
self.profile_repo = profile_repo
|
|
17
20
|
self.vs_repo = vs_repo
|
|
18
21
|
self.doc_repo = doc_repo
|
|
19
22
|
|
|
20
|
-
def search(self,
|
|
23
|
+
def search(self, company_short_name: str, query: str, metadata_filter: dict = None) -> str:
|
|
21
24
|
"""
|
|
22
25
|
Performs a semantic search for a given query within a company's documents.
|
|
23
26
|
|
|
@@ -26,7 +29,7 @@ class SearchService:
|
|
|
26
29
|
content of the retrieved documents, which can be used as context for an LLM.
|
|
27
30
|
|
|
28
31
|
Args:
|
|
29
|
-
|
|
32
|
+
company_short_name: The company to search within.
|
|
30
33
|
query: The text query to search for.
|
|
31
34
|
metadata_filter: An optional dictionary to filter documents by their metadata.
|
|
32
35
|
|
|
@@ -34,7 +37,11 @@ class SearchService:
|
|
|
34
37
|
A string containing the concatenated content of the found documents,
|
|
35
38
|
formatted to be used as a context.
|
|
36
39
|
"""
|
|
37
|
-
|
|
40
|
+
company = self.profile_repo.get_company_by_short_name(company_short_name)
|
|
41
|
+
if not company:
|
|
42
|
+
return f"error: company {company_short_name} not found"
|
|
43
|
+
|
|
44
|
+
document_list = self.vs_repo.query(company_id=company.id,
|
|
38
45
|
query_text=query,
|
|
39
46
|
metadata_filter=metadata_filter)
|
|
40
47
|
|