iatoolkit 0.91.1__py3-none-any.whl → 1.7.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.
- iatoolkit/__init__.py +6 -4
- iatoolkit/base_company.py +0 -16
- iatoolkit/cli_commands.py +3 -14
- iatoolkit/common/exceptions.py +1 -0
- iatoolkit/common/interfaces/__init__.py +0 -0
- iatoolkit/common/interfaces/asset_storage.py +34 -0
- iatoolkit/common/interfaces/database_provider.py +43 -0
- iatoolkit/common/model_registry.py +159 -0
- iatoolkit/common/routes.py +47 -5
- iatoolkit/common/util.py +32 -13
- iatoolkit/company_registry.py +5 -0
- iatoolkit/core.py +51 -20
- iatoolkit/infra/connectors/file_connector_factory.py +1 -0
- iatoolkit/infra/connectors/s3_connector.py +4 -2
- iatoolkit/infra/llm_providers/__init__.py +0 -0
- iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
- iatoolkit/infra/{gemini_adapter.py → llm_providers/gemini_adapter.py} +11 -17
- iatoolkit/infra/{openai_adapter.py → llm_providers/openai_adapter.py} +41 -7
- iatoolkit/infra/llm_proxy.py +235 -134
- iatoolkit/infra/llm_response.py +5 -0
- iatoolkit/locales/en.yaml +158 -2
- iatoolkit/locales/es.yaml +158 -0
- iatoolkit/repositories/database_manager.py +52 -47
- iatoolkit/repositories/document_repo.py +7 -0
- iatoolkit/repositories/filesystem_asset_repository.py +36 -0
- iatoolkit/repositories/llm_query_repo.py +2 -0
- iatoolkit/repositories/models.py +72 -79
- iatoolkit/repositories/profile_repo.py +59 -3
- iatoolkit/repositories/vs_repo.py +22 -24
- iatoolkit/services/company_context_service.py +126 -53
- iatoolkit/services/configuration_service.py +299 -73
- iatoolkit/services/dispatcher_service.py +21 -3
- iatoolkit/services/file_processor_service.py +0 -5
- iatoolkit/services/history_manager_service.py +43 -24
- iatoolkit/services/knowledge_base_service.py +425 -0
- iatoolkit/{infra/llm_client.py → services/llm_client_service.py} +38 -29
- iatoolkit/services/load_documents_service.py +26 -48
- iatoolkit/services/profile_service.py +32 -4
- iatoolkit/services/prompt_service.py +32 -30
- iatoolkit/services/query_service.py +51 -26
- iatoolkit/services/sql_service.py +122 -74
- iatoolkit/services/tool_service.py +26 -11
- iatoolkit/services/user_session_context_service.py +115 -63
- iatoolkit/static/js/chat_main.js +44 -4
- iatoolkit/static/js/chat_model_selector.js +227 -0
- iatoolkit/static/js/chat_onboarding_button.js +1 -1
- iatoolkit/static/js/chat_reload_button.js +4 -1
- iatoolkit/static/styles/chat_iatoolkit.css +58 -2
- iatoolkit/static/styles/llm_output.css +34 -1
- iatoolkit/system_prompts/query_main.prompt +26 -2
- iatoolkit/templates/base.html +13 -0
- iatoolkit/templates/chat.html +45 -2
- iatoolkit/templates/onboarding_shell.html +0 -1
- iatoolkit/views/base_login_view.py +7 -2
- iatoolkit/views/chat_view.py +76 -0
- iatoolkit/views/configuration_api_view.py +163 -0
- iatoolkit/views/load_document_api_view.py +14 -10
- iatoolkit/views/login_view.py +8 -3
- iatoolkit/views/rag_api_view.py +216 -0
- iatoolkit/views/users_api_view.py +33 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/METADATA +4 -4
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/RECORD +66 -58
- iatoolkit/repositories/tasks_repo.py +0 -52
- iatoolkit/services/search_service.py +0 -55
- iatoolkit/services/tasks_service.py +0 -188
- iatoolkit/views/tasks_api_view.py +0 -72
- iatoolkit/views/tasks_review_api_view.py +0 -55
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/WHEEL +0 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/licenses/LICENSE +0 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/licenses/LICENSE_COMMUNITY.md +0 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.7.0.dist-info}/top_level.txt +0 -0
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
# Copyright (c) 2024 Fernando Libedinsky
|
|
3
3
|
# Product: IAToolkit
|
|
4
4
|
|
|
5
|
-
from pathlib import Path
|
|
6
5
|
from iatoolkit.repositories.models import Company
|
|
6
|
+
from iatoolkit.common.interfaces.asset_storage import AssetRepository, AssetType
|
|
7
7
|
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
8
8
|
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
9
|
-
from iatoolkit.common.exceptions import IAToolkitException
|
|
10
9
|
from iatoolkit.common.util import Utility
|
|
11
10
|
from injector import inject
|
|
12
11
|
import logging
|
|
@@ -21,9 +20,11 @@ class ConfigurationService:
|
|
|
21
20
|
|
|
22
21
|
@inject
|
|
23
22
|
def __init__(self,
|
|
23
|
+
asset_repo: AssetRepository,
|
|
24
24
|
llm_query_repo: LLMQueryRepo,
|
|
25
25
|
profile_repo: ProfileRepo,
|
|
26
26
|
utility: Utility):
|
|
27
|
+
self.asset_repo = asset_repo
|
|
27
28
|
self.llm_query_repo = llm_query_repo
|
|
28
29
|
self.profile_repo = profile_repo
|
|
29
30
|
self.utility = utility
|
|
@@ -45,7 +46,30 @@ class ConfigurationService:
|
|
|
45
46
|
self._ensure_config_loaded(company_short_name)
|
|
46
47
|
return self._loaded_configs[company_short_name].get(content_key)
|
|
47
48
|
|
|
48
|
-
def
|
|
49
|
+
def get_llm_configuration(self, company_short_name: str):
|
|
50
|
+
"""
|
|
51
|
+
Convenience helper to obtain the 'llm' configuration block for a company.
|
|
52
|
+
Kept separate from get_configuration() to avoid coupling tests that
|
|
53
|
+
assert the number of calls to get_configuration().
|
|
54
|
+
"""
|
|
55
|
+
default_llm_model = None
|
|
56
|
+
available_llm_models = []
|
|
57
|
+
self._ensure_config_loaded(company_short_name)
|
|
58
|
+
llm_config = self._loaded_configs[company_short_name].get("llm")
|
|
59
|
+
if llm_config:
|
|
60
|
+
default_llm_model = llm_config.get("model")
|
|
61
|
+
available_llm_models = llm_config.get('available_models') or []
|
|
62
|
+
|
|
63
|
+
# fallback: if no explicit list of models is provided, use the default model
|
|
64
|
+
if not available_llm_models and default_llm_model:
|
|
65
|
+
available_llm_models = [{
|
|
66
|
+
"id": default_llm_model,
|
|
67
|
+
"label": default_llm_model,
|
|
68
|
+
"description": "Modelo por defecto configurado para esta compañía."
|
|
69
|
+
}]
|
|
70
|
+
return default_llm_model, available_llm_models
|
|
71
|
+
|
|
72
|
+
def load_configuration(self, company_short_name: str):
|
|
49
73
|
"""
|
|
50
74
|
Main entry point for configuring a company instance.
|
|
51
75
|
This method is invoked by the dispatcher for each registered company.
|
|
@@ -54,71 +78,234 @@ class ConfigurationService:
|
|
|
54
78
|
|
|
55
79
|
# 1. Load the main configuration file and supplementary content files
|
|
56
80
|
config = self._load_and_merge_configs(company_short_name)
|
|
81
|
+
if config:
|
|
82
|
+
# 2. create/update company in database
|
|
83
|
+
self._register_company_database(config)
|
|
57
84
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# 3. Register databases
|
|
62
|
-
self._register_data_sources(company_short_name, config)
|
|
63
|
-
|
|
64
|
-
# 4. Register tools
|
|
65
|
-
self._register_tools(company_instance, config)
|
|
85
|
+
# 3. Register tools
|
|
86
|
+
self._register_tools(company_short_name, config)
|
|
66
87
|
|
|
67
|
-
|
|
68
|
-
|
|
88
|
+
# 4. Register prompt categories and prompts
|
|
89
|
+
self._register_prompts(company_short_name, config)
|
|
69
90
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
company_instance.id = company_instance.company.id
|
|
91
|
+
# 5. Register Knowledge base information
|
|
92
|
+
self._register_knowledge_base(company_short_name, config)
|
|
73
93
|
|
|
74
94
|
# Final step: validate the configuration against platform
|
|
75
|
-
self._validate_configuration(company_short_name, config)
|
|
95
|
+
errors = self._validate_configuration(company_short_name, config)
|
|
76
96
|
|
|
77
97
|
logging.info(f"✅ Company '{company_short_name}' configured successfully.")
|
|
98
|
+
return config, errors
|
|
78
99
|
|
|
100
|
+
def update_configuration_key(self, company_short_name: str, key: str, value) -> tuple[dict, list[str]]:
|
|
101
|
+
"""
|
|
102
|
+
Updates a specific key in the company's configuration file, validates the result,
|
|
103
|
+
and saves it to the asset repository if valid.
|
|
79
104
|
|
|
80
|
-
|
|
105
|
+
Args:
|
|
106
|
+
company_short_name: The company identifier.
|
|
107
|
+
key: The configuration key to update (supports dot notation, e.g., 'llm.model').
|
|
108
|
+
value: The new value for the key.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
A tuple containing the updated configuration dict and a list of error strings (if any).
|
|
81
112
|
"""
|
|
82
|
-
|
|
83
|
-
|
|
113
|
+
# 1. Load raw config from file (to avoid working with merged supplementary files if possible,
|
|
114
|
+
# but for simplicity we load the main yaml structure)
|
|
115
|
+
main_config_filename = "company.yaml"
|
|
116
|
+
|
|
117
|
+
if not self.asset_repo.exists(company_short_name, AssetType.CONFIG, main_config_filename):
|
|
118
|
+
raise FileNotFoundError(f"Configuration file not found for {company_short_name}")
|
|
119
|
+
|
|
120
|
+
yaml_content = self.asset_repo.read_text(company_short_name, AssetType.CONFIG, main_config_filename)
|
|
121
|
+
config = self.utility.load_yaml_from_string(yaml_content) or {}
|
|
122
|
+
|
|
123
|
+
# 2. Update the key in the dictionary
|
|
124
|
+
self._set_nested_value(config, key, value)
|
|
125
|
+
|
|
126
|
+
# 3. Validate the new configuration structure
|
|
127
|
+
errors = self._validate_configuration(company_short_name, config)
|
|
128
|
+
|
|
129
|
+
if errors:
|
|
130
|
+
logging.warning(f"Configuration update failed validation: {errors}")
|
|
131
|
+
return config, errors
|
|
132
|
+
|
|
133
|
+
# 4. Save back to repository
|
|
134
|
+
# Assuming Utility has a method to dump YAML. If not, standard yaml library would be needed.
|
|
135
|
+
# For this example, we assume self.utility.dump_yaml_to_string exists.
|
|
136
|
+
new_yaml_content = self.utility.dump_yaml_to_string(config)
|
|
137
|
+
self.asset_repo.write_text(company_short_name, AssetType.CONFIG, main_config_filename, new_yaml_content)
|
|
138
|
+
|
|
139
|
+
# 5. Invalidate cache so next reads get the new version
|
|
140
|
+
if company_short_name in self._loaded_configs:
|
|
141
|
+
del self._loaded_configs[company_short_name]
|
|
142
|
+
|
|
143
|
+
return config, []
|
|
144
|
+
|
|
145
|
+
def add_configuration_key(self, company_short_name: str, parent_key: str, key: str, value) -> tuple[dict, list[str]]:
|
|
146
|
+
"""
|
|
147
|
+
Adds a new key-value pair under a specific parent key in the configuration.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
company_short_name: The company identifier.
|
|
151
|
+
parent_key: The parent configuration key under which to add the new key (e.g., 'llm').
|
|
152
|
+
key: The new key name to add.
|
|
153
|
+
value: The value for the new key.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
A tuple containing the updated configuration dict and a list of error strings (if any).
|
|
84
157
|
"""
|
|
85
|
-
|
|
86
|
-
|
|
158
|
+
# 1. Load raw config from file
|
|
159
|
+
main_config_filename = "company.yaml"
|
|
160
|
+
|
|
161
|
+
if not self.asset_repo.exists(company_short_name, AssetType.CONFIG, main_config_filename):
|
|
162
|
+
raise FileNotFoundError(f"Configuration file not found for {company_short_name}")
|
|
163
|
+
|
|
164
|
+
yaml_content = self.asset_repo.read_text(company_short_name, AssetType.CONFIG, main_config_filename)
|
|
165
|
+
config = self.utility.load_yaml_from_string(yaml_content) or {}
|
|
166
|
+
|
|
167
|
+
# 2. Construct full path and set the value
|
|
168
|
+
# If parent_key is provided, we append the new key to it (e.g., 'llm.new_setting')
|
|
169
|
+
full_path = f"{parent_key}.{key}" if parent_key else key
|
|
170
|
+
self._set_nested_value(config, full_path, value)
|
|
87
171
|
|
|
88
|
-
|
|
89
|
-
|
|
172
|
+
# 3. Validate the new configuration structure
|
|
173
|
+
errors = self._validate_configuration(company_short_name, config)
|
|
90
174
|
|
|
91
|
-
|
|
175
|
+
if errors:
|
|
176
|
+
logging.warning(f"Configuration add failed validation: {errors}")
|
|
177
|
+
return config, errors
|
|
178
|
+
|
|
179
|
+
# 4. Save back to repository
|
|
180
|
+
new_yaml_content = self.utility.dump_yaml_to_string(config)
|
|
181
|
+
self.asset_repo.write_text(company_short_name, AssetType.CONFIG, main_config_filename, new_yaml_content)
|
|
182
|
+
|
|
183
|
+
# 5. Invalidate cache
|
|
184
|
+
if company_short_name in self._loaded_configs:
|
|
185
|
+
del self._loaded_configs[company_short_name]
|
|
186
|
+
|
|
187
|
+
return config, []
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def validate_configuration(self, company_short_name: str) -> list[str]:
|
|
191
|
+
"""
|
|
192
|
+
Public method to trigger validation of the current configuration.
|
|
193
|
+
"""
|
|
194
|
+
config = self._load_and_merge_configs(company_short_name)
|
|
195
|
+
return self._validate_configuration(company_short_name, config)
|
|
196
|
+
|
|
197
|
+
def _set_nested_value(self, data: dict, key: str, value):
|
|
198
|
+
"""
|
|
199
|
+
Helper to set a value in a nested dictionary or list using dot notation (e.g. 'llm.model', 'tools.0.name').
|
|
200
|
+
Handles traversal through both dictionaries and lists.
|
|
201
|
+
"""
|
|
202
|
+
keys = key.split('.')
|
|
203
|
+
current = data
|
|
204
|
+
|
|
205
|
+
# Traverse up to the parent of the target key
|
|
206
|
+
for i, k in enumerate(keys[:-1]):
|
|
207
|
+
if isinstance(current, dict):
|
|
208
|
+
# If it's a dict, we can traverse or create the path
|
|
209
|
+
current = current.setdefault(k, {})
|
|
210
|
+
elif isinstance(current, list):
|
|
211
|
+
# If it's a list, we MUST use an integer index
|
|
212
|
+
try:
|
|
213
|
+
idx = int(k)
|
|
214
|
+
current = current[idx]
|
|
215
|
+
except (ValueError, IndexError) as e:
|
|
216
|
+
raise ValueError(
|
|
217
|
+
f"Invalid path: cannot access index '{k}' in list at '{'.'.join(keys[:i + 1])}'") from e
|
|
218
|
+
else:
|
|
219
|
+
raise ValueError(
|
|
220
|
+
f"Invalid path: '{k}' is not a container (got {type(current)}) at '{'.'.join(keys[:i + 1])}'")
|
|
221
|
+
|
|
222
|
+
# Set the final value
|
|
223
|
+
last_key = keys[-1]
|
|
224
|
+
if isinstance(current, dict):
|
|
225
|
+
current[last_key] = value
|
|
226
|
+
elif isinstance(current, list):
|
|
227
|
+
try:
|
|
228
|
+
idx = int(last_key)
|
|
229
|
+
current[idx] = value
|
|
230
|
+
except (ValueError, IndexError) as e:
|
|
231
|
+
raise ValueError(f"Invalid path: cannot assign to index '{last_key}' in list") from e
|
|
232
|
+
else:
|
|
233
|
+
raise ValueError(f"Cannot assign value to non-container type {type(current)} at '{key}'")
|
|
234
|
+
|
|
235
|
+
def _load_and_merge_configs(self, company_short_name: str) -> dict:
|
|
236
|
+
"""
|
|
237
|
+
Loads the main company.yaml and merges data from supplementary files
|
|
238
|
+
specified in the 'content_files' section using AssetRepository.
|
|
239
|
+
"""
|
|
240
|
+
main_config_filename = "company.yaml"
|
|
241
|
+
|
|
242
|
+
# verify existence of the main configuration file
|
|
243
|
+
if not self.asset_repo.exists(company_short_name, AssetType.CONFIG, main_config_filename):
|
|
244
|
+
# raise FileNotFoundError(f"Main configuration file not found: {main_config_filename}")
|
|
245
|
+
logging.exception(f"Main configuration file not found: {main_config_filename}")
|
|
246
|
+
|
|
247
|
+
# return the minimal configuration needed for starting the IAToolkit
|
|
248
|
+
# this is a for solving a chicken/egg problem when trying to migrate the configuration
|
|
249
|
+
# from filesystem to database in enterprise installation
|
|
250
|
+
# see create_assets cli command in enterprise-iatoolkit)
|
|
251
|
+
return {
|
|
252
|
+
'id': company_short_name,
|
|
253
|
+
'name': company_short_name,
|
|
254
|
+
'llm': {'model': 'gpt-5', 'provider_api_keys': {'openai':''} },
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
# read text and parse
|
|
258
|
+
yaml_content = self.asset_repo.read_text(company_short_name, AssetType.CONFIG, main_config_filename)
|
|
259
|
+
config = self.utility.load_yaml_from_string(yaml_content)
|
|
260
|
+
if not config:
|
|
261
|
+
return {}
|
|
92
262
|
|
|
93
263
|
# Load and merge supplementary content files (e.g., onboarding_cards)
|
|
94
|
-
for key,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
config[key] = self.utility.
|
|
264
|
+
for key, filename in config.get('help_files', {}).items():
|
|
265
|
+
if self.asset_repo.exists(company_short_name, AssetType.CONFIG, filename):
|
|
266
|
+
supp_content = self.asset_repo.read_text(company_short_name, AssetType.CONFIG, filename)
|
|
267
|
+
config[key] = self.utility.load_yaml_from_string(supp_content)
|
|
98
268
|
else:
|
|
99
|
-
logging.warning(f"⚠️ Warning: Content file not found: {
|
|
100
|
-
config[key] = None
|
|
269
|
+
logging.warning(f"⚠️ Warning: Content file not found: {filename}")
|
|
270
|
+
config[key] = None
|
|
101
271
|
|
|
102
272
|
return config
|
|
103
273
|
|
|
104
|
-
def
|
|
274
|
+
def _register_company_database(self, config: dict) -> Company:
|
|
105
275
|
# register the company in the database: create_or_update logic
|
|
276
|
+
if not config:
|
|
277
|
+
return None
|
|
106
278
|
|
|
107
|
-
|
|
108
|
-
|
|
279
|
+
# create or update the company in database
|
|
280
|
+
company_obj = Company(short_name=config.get('id'),
|
|
281
|
+
name=config.get('name'),
|
|
109
282
|
parameters=config.get('parameters', {}))
|
|
110
283
|
company = self.profile_repo.create_company(company_obj)
|
|
111
284
|
|
|
112
|
-
# save company object with the
|
|
113
|
-
|
|
285
|
+
# save company object with the configuration
|
|
286
|
+
config['company'] = company
|
|
287
|
+
|
|
114
288
|
return company
|
|
115
289
|
|
|
116
|
-
def
|
|
290
|
+
def register_data_sources(self,
|
|
291
|
+
company_short_name: str,
|
|
292
|
+
config: dict = None):
|
|
117
293
|
"""
|
|
118
294
|
Reads the data_sources config and registers databases with SqlService.
|
|
119
295
|
Uses Lazy Loading to avoid circular dependency.
|
|
296
|
+
|
|
297
|
+
Public method: Can be called externally after initialization (e.g. by Enterprise)
|
|
298
|
+
to re-register sources once new factories (like 'bridge') are available.
|
|
120
299
|
"""
|
|
121
|
-
|
|
300
|
+
|
|
301
|
+
# If config is not provided, try to load it from cache
|
|
302
|
+
if config is None:
|
|
303
|
+
self._ensure_config_loaded(company_short_name)
|
|
304
|
+
config = self._loaded_configs.get(company_short_name)
|
|
305
|
+
|
|
306
|
+
if not config:
|
|
307
|
+
return
|
|
308
|
+
|
|
122
309
|
from iatoolkit import current_iatoolkit
|
|
123
310
|
from iatoolkit.services.sql_service import SqlService
|
|
124
311
|
sql_service = current_iatoolkit().get_injector().get(SqlService)
|
|
@@ -129,25 +316,46 @@ class ConfigurationService:
|
|
|
129
316
|
if not sql_sources:
|
|
130
317
|
return
|
|
131
318
|
|
|
132
|
-
logging.info(f"🛢️ Registering databases
|
|
133
|
-
|
|
134
|
-
for db_config in sql_sources:
|
|
135
|
-
db_name = db_config.get('database')
|
|
136
|
-
db_schema = db_config.get('schema', 'public')
|
|
137
|
-
db_env_var = db_config.get('connection_string_env')
|
|
319
|
+
logging.info(f"🛢️ Registering databases for '{company_short_name}'...")
|
|
138
320
|
|
|
139
|
-
|
|
140
|
-
|
|
321
|
+
for source in sql_sources:
|
|
322
|
+
db_name = source.get('database')
|
|
323
|
+
if not db_name:
|
|
324
|
+
continue
|
|
141
325
|
|
|
142
|
-
|
|
326
|
+
# Prepare the config dictionary for the factory
|
|
327
|
+
db_config = {
|
|
328
|
+
'database': db_name,
|
|
329
|
+
'schema': source.get('schema', 'public'),
|
|
330
|
+
'connection_type': source.get('connection_type', 'direct'),
|
|
331
|
+
|
|
332
|
+
# Pass through keys needed for Bridge or other plugins
|
|
333
|
+
'bridge_id': source.get('bridge_id'),
|
|
334
|
+
'timeout': source.get('timeout')
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
# Resolve URI if env var is present (Required for 'direct', optional for others)
|
|
338
|
+
db_env_var = source.get('connection_string_env')
|
|
339
|
+
if db_env_var:
|
|
340
|
+
db_uri = os.getenv(db_env_var)
|
|
341
|
+
if db_uri:
|
|
342
|
+
db_config['db_uri'] = db_uri
|
|
343
|
+
|
|
344
|
+
# Validation: 'direct' connections MUST have a URI
|
|
345
|
+
if db_config['connection_type'] == 'direct' and not db_config.get('db_uri'):
|
|
143
346
|
logging.error(
|
|
144
347
|
f"-> Skipping DB '{db_name}' for '{company_short_name}': missing URI in env '{db_env_var}'.")
|
|
145
348
|
continue
|
|
146
349
|
|
|
350
|
+
elif db_config['connection_type'] == 'bridge' and not db_config.get('bridge_id'):
|
|
351
|
+
logging.error(
|
|
352
|
+
f"-> Skipping DB '{db_name}' for '{company_short_name}': missing bridge_id in configuration.")
|
|
353
|
+
continue
|
|
354
|
+
|
|
147
355
|
# Register with the SQL service
|
|
148
|
-
sql_service.register_database(
|
|
356
|
+
sql_service.register_database(company_short_name, db_name, db_config)
|
|
149
357
|
|
|
150
|
-
def _register_tools(self,
|
|
358
|
+
def _register_tools(self, company_short_name: str, config: dict):
|
|
151
359
|
"""creates in the database each tool defined in the YAML."""
|
|
152
360
|
# Lazy import and resolve ToolService locally
|
|
153
361
|
from iatoolkit import current_iatoolkit
|
|
@@ -155,9 +363,9 @@ class ConfigurationService:
|
|
|
155
363
|
tool_service = current_iatoolkit().get_injector().get(ToolService)
|
|
156
364
|
|
|
157
365
|
tools_config = config.get('tools', [])
|
|
158
|
-
tool_service.sync_company_tools(
|
|
366
|
+
tool_service.sync_company_tools(company_short_name, tools_config)
|
|
159
367
|
|
|
160
|
-
def _register_prompts(self,
|
|
368
|
+
def _register_prompts(self, company_short_name: str, config: dict):
|
|
161
369
|
"""
|
|
162
370
|
Delegates prompt synchronization to PromptService.
|
|
163
371
|
"""
|
|
@@ -166,15 +374,27 @@ class ConfigurationService:
|
|
|
166
374
|
from iatoolkit.services.prompt_service import PromptService
|
|
167
375
|
prompt_service = current_iatoolkit().get_injector().get(PromptService)
|
|
168
376
|
|
|
169
|
-
prompts_config = config.get('prompts',
|
|
170
|
-
categories_config = config.get('prompt_categories', [])
|
|
377
|
+
prompts_config = config.get('prompts', {})
|
|
171
378
|
|
|
172
379
|
prompt_service.sync_company_prompts(
|
|
173
|
-
|
|
174
|
-
prompts_config=prompts_config,
|
|
175
|
-
categories_config=
|
|
380
|
+
company_short_name=company_short_name,
|
|
381
|
+
prompts_config=prompts_config.get('prompt_list', []),
|
|
382
|
+
categories_config=prompts_config.get('prompt_categories', []),
|
|
176
383
|
)
|
|
177
384
|
|
|
385
|
+
def _register_knowledge_base(self, company_short_name: str, config: dict):
|
|
386
|
+
# Lazy import to avoid circular dependency
|
|
387
|
+
from iatoolkit import current_iatoolkit
|
|
388
|
+
from iatoolkit.services.knowledge_base_service import KnowledgeBaseService
|
|
389
|
+
knowledge_base = current_iatoolkit().get_injector().get(KnowledgeBaseService)
|
|
390
|
+
|
|
391
|
+
kb_config = config.get('knowledge_base', {})
|
|
392
|
+
categories_config = kb_config.get('collections', [])
|
|
393
|
+
|
|
394
|
+
# sync collection types in database
|
|
395
|
+
knowledge_base.sync_collection_types(company_short_name, categories_config)
|
|
396
|
+
|
|
397
|
+
|
|
178
398
|
def _validate_configuration(self, company_short_name: str, config: dict):
|
|
179
399
|
"""
|
|
180
400
|
Validates the structure and consistency of the company.yaml configuration.
|
|
@@ -182,13 +402,15 @@ class ConfigurationService:
|
|
|
182
402
|
Raises IAToolkitException if any validation error is found.
|
|
183
403
|
"""
|
|
184
404
|
errors = []
|
|
185
|
-
config_dir = Path("companies") / company_short_name / "config"
|
|
186
|
-
prompts_dir = Path("companies") / company_short_name / "prompts"
|
|
187
405
|
|
|
188
406
|
# Helper to collect errors
|
|
189
407
|
def add_error(section, message):
|
|
190
408
|
errors.append(f"[{section}] {message}")
|
|
191
409
|
|
|
410
|
+
if not config:
|
|
411
|
+
add_error("General", "Configuration file missing or with errors, check the application logs.")
|
|
412
|
+
return errors
|
|
413
|
+
|
|
192
414
|
# 1. Top-level keys
|
|
193
415
|
if not config.get("id"):
|
|
194
416
|
add_error("General", "Missing required key: 'id'")
|
|
@@ -204,8 +426,8 @@ class ConfigurationService:
|
|
|
204
426
|
else:
|
|
205
427
|
if not config.get("llm", {}).get("model"):
|
|
206
428
|
add_error("llm", "Missing required key: 'model'")
|
|
207
|
-
if not config.get("llm", {}).get("
|
|
208
|
-
add_error("llm", "Missing required key: '
|
|
429
|
+
if not config.get("llm", {}).get("provider_api_keys"):
|
|
430
|
+
add_error("llm", "Missing required key: 'provider_api_keys'")
|
|
209
431
|
|
|
210
432
|
# 3. Embedding Provider
|
|
211
433
|
if isinstance(config.get("embedding_provider"), dict):
|
|
@@ -220,8 +442,12 @@ class ConfigurationService:
|
|
|
220
442
|
for i, source in enumerate(config.get("data_sources", {}).get("sql", [])):
|
|
221
443
|
if not source.get("database"):
|
|
222
444
|
add_error(f"data_sources.sql[{i}]", "Missing required key: 'database'")
|
|
223
|
-
|
|
445
|
+
|
|
446
|
+
connection_type = source.get("connection_type")
|
|
447
|
+
if connection_type == 'direct' and not source.get("connection_string_env"):
|
|
224
448
|
add_error(f"data_sources.sql[{i}]", "Missing required key: 'connection_string_env'")
|
|
449
|
+
elif connection_type == 'bridge' and not source.get("bridge_id"):
|
|
450
|
+
add_error(f"data_sources.sql[{i}]", "Missing bridge_id'")
|
|
225
451
|
|
|
226
452
|
# 5. Tools
|
|
227
453
|
for i, tool in enumerate(config.get("tools", [])):
|
|
@@ -236,15 +462,18 @@ class ConfigurationService:
|
|
|
236
462
|
add_error(f"tools[{i}]", "'params' key must be a dictionary.")
|
|
237
463
|
|
|
238
464
|
# 6. Prompts
|
|
239
|
-
|
|
240
|
-
|
|
465
|
+
prompt_list = config.get("prompts", {}).get("prompt_list", [])
|
|
466
|
+
prompt_categories = config.get("prompts", {}).get("prompt_categories", [])
|
|
467
|
+
|
|
468
|
+
category_set = set(prompt_categories)
|
|
469
|
+
for i, prompt in enumerate(prompt_list):
|
|
241
470
|
prompt_name = prompt.get("name")
|
|
242
471
|
if not prompt_name:
|
|
243
472
|
add_error(f"prompts[{i}]", "Missing required key: 'name'")
|
|
244
473
|
else:
|
|
245
|
-
|
|
246
|
-
if not
|
|
247
|
-
add_error(f"prompts/{prompt_name}:", f"Prompt file not found: {
|
|
474
|
+
prompt_filename = f"{prompt_name}.prompt"
|
|
475
|
+
if not self.asset_repo.exists(company_short_name, AssetType.PROMPT, prompt_filename):
|
|
476
|
+
add_error(f"prompts/{prompt_name}:", f"Prompt file not found: {prompt_filename}")
|
|
248
477
|
|
|
249
478
|
prompt_description = prompt.get("description")
|
|
250
479
|
if not prompt_description:
|
|
@@ -289,9 +518,9 @@ class ConfigurationService:
|
|
|
289
518
|
if not filename:
|
|
290
519
|
add_error(f"help_files.{key}", "Filename cannot be empty.")
|
|
291
520
|
continue
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
521
|
+
if not self.asset_repo.exists(company_short_name, AssetType.CONFIG, filename):
|
|
522
|
+
add_error(f"help_files.{key}", f"Help file not found: {filename}")
|
|
523
|
+
|
|
295
524
|
|
|
296
525
|
# If any errors were found, log all messages and raise an exception
|
|
297
526
|
if errors:
|
|
@@ -299,8 +528,5 @@ class ConfigurationService:
|
|
|
299
528
|
f" - {e}" for e in errors)
|
|
300
529
|
logging.error(error_summary)
|
|
301
530
|
|
|
302
|
-
|
|
303
|
-
IAToolkitException.ErrorType.CONFIG_ERROR,
|
|
304
|
-
'company.yaml validation errors'
|
|
305
|
-
)
|
|
531
|
+
return errors
|
|
306
532
|
|
|
@@ -59,13 +59,25 @@ class Dispatcher:
|
|
|
59
59
|
self.setup_iatoolkit_system()
|
|
60
60
|
|
|
61
61
|
# Loads the configuration of every company: company.yaml file
|
|
62
|
-
for
|
|
62
|
+
for company_short_name, company_instance in self.company_instances.items():
|
|
63
63
|
try:
|
|
64
64
|
# read company configuration from company.yaml
|
|
65
|
-
self.config_service.load_configuration(
|
|
65
|
+
config, errors = self.config_service.load_configuration(company_short_name)
|
|
66
|
+
|
|
67
|
+
'''
|
|
68
|
+
if errors:
|
|
69
|
+
raise IAToolkitException(
|
|
70
|
+
IAToolkitException.ErrorType.CONFIG_ERROR,
|
|
71
|
+
'company.yaml validation errors'
|
|
72
|
+
)
|
|
73
|
+
'''
|
|
74
|
+
|
|
75
|
+
# complement the instance self data
|
|
76
|
+
company_instance.company_short_name = company_short_name
|
|
77
|
+
company_instance.company = config.get('company')
|
|
66
78
|
|
|
67
79
|
except Exception as e:
|
|
68
|
-
logging.error(f"❌ Failed to register configuration for '{
|
|
80
|
+
logging.error(f"❌ Failed to register configuration for '{company_short_name}': {e}")
|
|
69
81
|
raise e
|
|
70
82
|
|
|
71
83
|
return True
|
|
@@ -95,7 +107,13 @@ class Dispatcher:
|
|
|
95
107
|
|
|
96
108
|
# check if action is a system function using ToolService
|
|
97
109
|
if self.tool_service.is_system_tool(function_name):
|
|
110
|
+
# this is the system function to be executed.
|
|
98
111
|
handler = self.tool_service.get_system_handler(function_name)
|
|
112
|
+
logging.info(
|
|
113
|
+
f"Calling system handler [{function_name}] "
|
|
114
|
+
f"with company_short_name={company_short_name} "
|
|
115
|
+
f"and kwargs={kwargs}"
|
|
116
|
+
)
|
|
99
117
|
return handler(company_short_name, **kwargs)
|
|
100
118
|
|
|
101
119
|
company_instance = self.company_instances[company_short_name]
|
|
@@ -74,9 +74,6 @@ class FileProcessor:
|
|
|
74
74
|
if not self._apply_filters(file_name):
|
|
75
75
|
continue
|
|
76
76
|
|
|
77
|
-
if self.config.echo:
|
|
78
|
-
print(f'loading: {file_name}')
|
|
79
|
-
|
|
80
77
|
content = self.connector.get_file_content(file_path)
|
|
81
78
|
|
|
82
79
|
# execute the callback function
|
|
@@ -87,8 +84,6 @@ class FileProcessor:
|
|
|
87
84
|
context=self.config.context)
|
|
88
85
|
self.processed_files += 1
|
|
89
86
|
|
|
90
|
-
logging.info(f"Successfully processed file: {file_path}")
|
|
91
|
-
|
|
92
87
|
except Exception as e:
|
|
93
88
|
logging.error(f"Error processing {file_path}: {e}")
|
|
94
89
|
if not self.config.continue_on_error:
|