iatoolkit 0.71.4__py3-none-any.whl → 1.4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iatoolkit/__init__.py +19 -7
- iatoolkit/base_company.py +1 -71
- iatoolkit/cli_commands.py +9 -21
- iatoolkit/common/exceptions.py +2 -0
- iatoolkit/common/interfaces/__init__.py +0 -0
- iatoolkit/common/interfaces/asset_storage.py +34 -0
- iatoolkit/common/interfaces/database_provider.py +38 -0
- iatoolkit/common/model_registry.py +159 -0
- iatoolkit/common/routes.py +53 -32
- iatoolkit/common/util.py +17 -12
- iatoolkit/company_registry.py +55 -14
- iatoolkit/{iatoolkit.py → core.py} +102 -72
- iatoolkit/infra/{mail_app.py → brevo_mail_app.py} +15 -37
- iatoolkit/infra/llm_providers/__init__.py +0 -0
- iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
- iatoolkit/infra/{gemini_adapter.py → llm_providers/gemini_adapter.py} +11 -17
- iatoolkit/infra/{openai_adapter.py → llm_providers/openai_adapter.py} +41 -7
- iatoolkit/infra/llm_proxy.py +235 -134
- iatoolkit/infra/llm_response.py +5 -0
- iatoolkit/locales/en.yaml +134 -4
- iatoolkit/locales/es.yaml +293 -162
- iatoolkit/repositories/database_manager.py +92 -22
- iatoolkit/repositories/document_repo.py +7 -0
- iatoolkit/repositories/filesystem_asset_repository.py +36 -0
- iatoolkit/repositories/llm_query_repo.py +36 -22
- iatoolkit/repositories/models.py +86 -95
- iatoolkit/repositories/profile_repo.py +64 -13
- iatoolkit/repositories/vs_repo.py +31 -28
- iatoolkit/services/auth_service.py +1 -1
- iatoolkit/services/branding_service.py +1 -1
- iatoolkit/services/company_context_service.py +96 -39
- iatoolkit/services/configuration_service.py +329 -67
- iatoolkit/services/dispatcher_service.py +51 -227
- iatoolkit/services/document_service.py +10 -1
- iatoolkit/services/embedding_service.py +9 -6
- iatoolkit/services/excel_service.py +50 -2
- iatoolkit/services/file_processor_service.py +0 -5
- iatoolkit/services/history_manager_service.py +208 -0
- iatoolkit/services/jwt_service.py +1 -1
- iatoolkit/services/knowledge_base_service.py +412 -0
- iatoolkit/services/language_service.py +8 -2
- iatoolkit/services/license_service.py +82 -0
- iatoolkit/{infra/llm_client.py → services/llm_client_service.py} +42 -29
- iatoolkit/services/load_documents_service.py +18 -47
- iatoolkit/services/mail_service.py +171 -25
- iatoolkit/services/profile_service.py +69 -36
- iatoolkit/services/{prompt_manager_service.py → prompt_service.py} +136 -25
- iatoolkit/services/query_service.py +229 -203
- iatoolkit/services/sql_service.py +116 -34
- iatoolkit/services/tool_service.py +246 -0
- iatoolkit/services/user_feedback_service.py +18 -6
- iatoolkit/services/user_session_context_service.py +121 -51
- iatoolkit/static/images/iatoolkit_core.png +0 -0
- iatoolkit/static/images/iatoolkit_logo.png +0 -0
- iatoolkit/static/js/chat_feedback_button.js +1 -1
- iatoolkit/static/js/chat_help_content.js +4 -4
- iatoolkit/static/js/chat_main.js +61 -9
- iatoolkit/static/js/chat_model_selector.js +227 -0
- iatoolkit/static/js/chat_onboarding_button.js +1 -1
- iatoolkit/static/js/chat_reload_button.js +4 -1
- iatoolkit/static/styles/chat_iatoolkit.css +59 -3
- iatoolkit/static/styles/chat_public.css +28 -0
- iatoolkit/static/styles/documents.css +598 -0
- iatoolkit/static/styles/landing_page.css +223 -7
- iatoolkit/static/styles/llm_output.css +34 -1
- iatoolkit/system_prompts/__init__.py +0 -0
- iatoolkit/system_prompts/query_main.prompt +28 -3
- iatoolkit/system_prompts/sql_rules.prompt +47 -12
- iatoolkit/templates/_company_header.html +30 -5
- iatoolkit/templates/_login_widget.html +3 -3
- iatoolkit/templates/base.html +13 -0
- iatoolkit/templates/chat.html +45 -3
- iatoolkit/templates/forgot_password.html +3 -2
- iatoolkit/templates/onboarding_shell.html +1 -2
- iatoolkit/templates/signup.html +3 -0
- iatoolkit/views/base_login_view.py +8 -3
- iatoolkit/views/change_password_view.py +1 -1
- iatoolkit/views/chat_view.py +76 -0
- iatoolkit/views/forgot_password_view.py +9 -4
- iatoolkit/views/history_api_view.py +3 -3
- iatoolkit/views/home_view.py +4 -2
- iatoolkit/views/init_context_api_view.py +1 -1
- iatoolkit/views/llmquery_api_view.py +4 -3
- iatoolkit/views/load_company_configuration_api_view.py +49 -0
- iatoolkit/views/{file_store_api_view.py → load_document_api_view.py} +15 -11
- iatoolkit/views/login_view.py +25 -8
- iatoolkit/views/logout_api_view.py +10 -2
- iatoolkit/views/prompt_api_view.py +1 -1
- iatoolkit/views/rag_api_view.py +216 -0
- iatoolkit/views/root_redirect_view.py +22 -0
- iatoolkit/views/signup_view.py +12 -4
- iatoolkit/views/static_page_view.py +27 -0
- iatoolkit/views/users_api_view.py +33 -0
- iatoolkit/views/verify_user_view.py +1 -1
- iatoolkit-1.4.2.dist-info/METADATA +268 -0
- iatoolkit-1.4.2.dist-info/RECORD +133 -0
- iatoolkit-1.4.2.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
- iatoolkit/repositories/tasks_repo.py +0 -52
- iatoolkit/services/history_service.py +0 -37
- iatoolkit/services/search_service.py +0 -55
- iatoolkit/services/tasks_service.py +0 -188
- iatoolkit/templates/about.html +0 -13
- iatoolkit/templates/index.html +0 -145
- iatoolkit/templates/login_simulation.html +0 -45
- iatoolkit/views/external_login_view.py +0 -73
- iatoolkit/views/index_view.py +0 -14
- iatoolkit/views/login_simulation_view.py +0 -93
- iatoolkit/views/tasks_api_view.py +0 -72
- iatoolkit/views/tasks_review_api_view.py +0 -55
- iatoolkit-0.71.4.dist-info/METADATA +0 -276
- iatoolkit-0.71.4.dist-info/RECORD +0 -122
- {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/WHEEL +0 -0
- {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/licenses/LICENSE +0 -0
- {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/top_level.txt +0 -0
|
@@ -2,11 +2,15 @@
|
|
|
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
|
+
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
8
|
+
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
7
9
|
from iatoolkit.common.util import Utility
|
|
8
10
|
from injector import inject
|
|
9
11
|
import logging
|
|
12
|
+
import os
|
|
13
|
+
|
|
10
14
|
|
|
11
15
|
class ConfigurationService:
|
|
12
16
|
"""
|
|
@@ -16,10 +20,24 @@ class ConfigurationService:
|
|
|
16
20
|
|
|
17
21
|
@inject
|
|
18
22
|
def __init__(self,
|
|
23
|
+
asset_repo: AssetRepository,
|
|
24
|
+
llm_query_repo: LLMQueryRepo,
|
|
25
|
+
profile_repo: ProfileRepo,
|
|
19
26
|
utility: Utility):
|
|
27
|
+
self.asset_repo = asset_repo
|
|
28
|
+
self.llm_query_repo = llm_query_repo
|
|
29
|
+
self.profile_repo = profile_repo
|
|
20
30
|
self.utility = utility
|
|
21
31
|
self._loaded_configs = {} # cache for store loaded configurations
|
|
22
32
|
|
|
33
|
+
def _ensure_config_loaded(self, company_short_name: str):
|
|
34
|
+
"""
|
|
35
|
+
Checks if the configuration for a company is in the cache.
|
|
36
|
+
If not, it loads it from files and stores it.
|
|
37
|
+
"""
|
|
38
|
+
if company_short_name not in self._loaded_configs:
|
|
39
|
+
self._loaded_configs[company_short_name] = self._load_and_merge_configs(company_short_name)
|
|
40
|
+
|
|
23
41
|
def get_configuration(self, company_short_name: str, content_key: str):
|
|
24
42
|
"""
|
|
25
43
|
Public method to provide a specific section of a company's configuration.
|
|
@@ -28,7 +46,30 @@ class ConfigurationService:
|
|
|
28
46
|
self._ensure_config_loaded(company_short_name)
|
|
29
47
|
return self._loaded_configs[company_short_name].get(content_key)
|
|
30
48
|
|
|
31
|
-
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):
|
|
32
73
|
"""
|
|
33
74
|
Main entry point for configuring a company instance.
|
|
34
75
|
This method is invoked by the dispatcher for each registered company.
|
|
@@ -37,97 +78,318 @@ class ConfigurationService:
|
|
|
37
78
|
|
|
38
79
|
# 1. Load the main configuration file and supplementary content files
|
|
39
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)
|
|
40
84
|
|
|
41
|
-
|
|
42
|
-
|
|
85
|
+
# 3. Register tools
|
|
86
|
+
self._register_tools(company_short_name, config)
|
|
43
87
|
|
|
44
|
-
|
|
45
|
-
|
|
88
|
+
# 4. Register prompt categories and prompts
|
|
89
|
+
self._register_prompts(company_short_name, config)
|
|
46
90
|
|
|
47
|
-
|
|
48
|
-
|
|
91
|
+
# 5. Register Knowledge base information
|
|
92
|
+
self._register_knowledge_base(company_short_name, config)
|
|
49
93
|
|
|
50
|
-
#
|
|
51
|
-
|
|
52
|
-
company_instance.company = company_db_object
|
|
53
|
-
company_instance.id = company_instance.company.id
|
|
94
|
+
# Final step: validate the configuration against platform
|
|
95
|
+
errors = self._validate_configuration(company_short_name, config)
|
|
54
96
|
|
|
55
97
|
logging.info(f"✅ Company '{company_short_name}' configured successfully.")
|
|
56
|
-
|
|
57
|
-
def _ensure_config_loaded(self, company_short_name: str):
|
|
58
|
-
"""
|
|
59
|
-
Checks if the configuration for a company is in the cache.
|
|
60
|
-
If not, it loads it from files and stores it.
|
|
61
|
-
"""
|
|
62
|
-
if company_short_name not in self._loaded_configs:
|
|
63
|
-
self._loaded_configs[company_short_name] = self._load_and_merge_configs(company_short_name)
|
|
98
|
+
return config, errors
|
|
64
99
|
|
|
65
100
|
def _load_and_merge_configs(self, company_short_name: str) -> dict:
|
|
66
101
|
"""
|
|
67
102
|
Loads the main company.yaml and merges data from supplementary files
|
|
68
|
-
specified in the 'content_files' section.
|
|
103
|
+
specified in the 'content_files' section using AssetRepository.
|
|
69
104
|
"""
|
|
70
|
-
|
|
71
|
-
main_config_path = config_dir / "company.yaml"
|
|
105
|
+
main_config_filename = "company.yaml"
|
|
72
106
|
|
|
73
|
-
|
|
74
|
-
|
|
107
|
+
# verify existence of the main configuration file
|
|
108
|
+
if not self.asset_repo.exists(company_short_name, AssetType.CONFIG, main_config_filename):
|
|
109
|
+
# raise FileNotFoundError(f"Main configuration file not found: {main_config_filename}")
|
|
110
|
+
logging.exception(f"Main configuration file not found: {main_config_filename}")
|
|
75
111
|
|
|
76
|
-
|
|
112
|
+
# return the minimal configuration needed for starting the IAToolkit
|
|
113
|
+
# this is a for solving a chicken/egg problem when trying to migrate the configuration
|
|
114
|
+
# from filesystem to database in enterprise installation
|
|
115
|
+
# see create_assets cli command in enterprise-iatoolkit)
|
|
116
|
+
return {
|
|
117
|
+
'id': company_short_name,
|
|
118
|
+
'name': company_short_name,
|
|
119
|
+
'llm': {'model': 'gpt-5', 'provider_api_keys': {'openai':''} },
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# read text and parse
|
|
123
|
+
yaml_content = self.asset_repo.read_text(company_short_name, AssetType.CONFIG, main_config_filename)
|
|
124
|
+
config = self.utility.load_yaml_from_string(yaml_content)
|
|
125
|
+
if not config:
|
|
126
|
+
return {}
|
|
77
127
|
|
|
78
128
|
# Load and merge supplementary content files (e.g., onboarding_cards)
|
|
79
|
-
for key,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
config[key] = self.utility.
|
|
129
|
+
for key, filename in config.get('help_files', {}).items():
|
|
130
|
+
if self.asset_repo.exists(company_short_name, AssetType.CONFIG, filename):
|
|
131
|
+
supp_content = self.asset_repo.read_text(company_short_name, AssetType.CONFIG, filename)
|
|
132
|
+
config[key] = self.utility.load_yaml_from_string(supp_content)
|
|
83
133
|
else:
|
|
84
|
-
logging.warning(f"⚠️ Warning: Content file not found: {
|
|
85
|
-
config[key] = None
|
|
134
|
+
logging.warning(f"⚠️ Warning: Content file not found: {filename}")
|
|
135
|
+
config[key] = None
|
|
86
136
|
|
|
87
137
|
return config
|
|
88
138
|
|
|
89
|
-
def
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
name=config['name'],
|
|
94
|
-
parameters=config.get('parameters', {})
|
|
95
|
-
)
|
|
139
|
+
def _register_company_database(self, config: dict) -> Company:
|
|
140
|
+
# register the company in the database: create_or_update logic
|
|
141
|
+
if not config:
|
|
142
|
+
return None
|
|
96
143
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
description=tool['description'],
|
|
103
|
-
params=tool['params']
|
|
104
|
-
)
|
|
144
|
+
# create or update the company in database
|
|
145
|
+
company_obj = Company(short_name=config.get('id'),
|
|
146
|
+
name=config.get('name'),
|
|
147
|
+
parameters=config.get('parameters', {}))
|
|
148
|
+
company = self.profile_repo.create_company(company_obj)
|
|
105
149
|
|
|
106
|
-
|
|
150
|
+
# save company object with the configuration
|
|
151
|
+
config['company'] = company
|
|
152
|
+
|
|
153
|
+
return company
|
|
154
|
+
|
|
155
|
+
def register_data_sources(self,
|
|
156
|
+
company_short_name: str,
|
|
157
|
+
config: dict = None):
|
|
107
158
|
"""
|
|
108
|
-
|
|
109
|
-
|
|
159
|
+
Reads the data_sources config and registers databases with SqlService.
|
|
160
|
+
Uses Lazy Loading to avoid circular dependency.
|
|
161
|
+
|
|
162
|
+
Public method: Can be called externally after initialization (e.g. by Enterprise)
|
|
163
|
+
to re-register sources once new factories (like 'bridge') are available.
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
# If config is not provided, try to load it from cache
|
|
167
|
+
if config is None:
|
|
168
|
+
self._ensure_config_loaded(company_short_name)
|
|
169
|
+
config = self._loaded_configs.get(company_short_name)
|
|
170
|
+
|
|
171
|
+
if not config:
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
from iatoolkit import current_iatoolkit
|
|
175
|
+
from iatoolkit.services.sql_service import SqlService
|
|
176
|
+
sql_service = current_iatoolkit().get_injector().get(SqlService)
|
|
177
|
+
|
|
178
|
+
data_sources = config.get('data_sources', {})
|
|
179
|
+
sql_sources = data_sources.get('sql', [])
|
|
180
|
+
|
|
181
|
+
if not sql_sources:
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
logging.info(f"🛢️ Registering databases for '{company_short_name}'...")
|
|
185
|
+
|
|
186
|
+
for source in sql_sources:
|
|
187
|
+
db_name = source.get('database')
|
|
188
|
+
if not db_name:
|
|
189
|
+
continue
|
|
190
|
+
|
|
191
|
+
# Prepare the config dictionary for the factory
|
|
192
|
+
db_config = {
|
|
193
|
+
'database': db_name,
|
|
194
|
+
'schema': source.get('schema', 'public'),
|
|
195
|
+
'connection_type': source.get('connection_type', 'direct'),
|
|
196
|
+
|
|
197
|
+
# Pass through keys needed for Bridge or other plugins
|
|
198
|
+
'bridge_id': source.get('bridge_id'),
|
|
199
|
+
'timeout': source.get('timeout')
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# Resolve URI if env var is present (Required for 'direct', optional for others)
|
|
203
|
+
db_env_var = source.get('connection_string_env')
|
|
204
|
+
if db_env_var:
|
|
205
|
+
db_uri = os.getenv(db_env_var)
|
|
206
|
+
if db_uri:
|
|
207
|
+
db_config['db_uri'] = db_uri
|
|
208
|
+
|
|
209
|
+
# Validation: 'direct' connections MUST have a URI
|
|
210
|
+
if db_config['connection_type'] == 'direct' and not db_config.get('db_uri'):
|
|
211
|
+
logging.error(
|
|
212
|
+
f"-> Skipping DB '{db_name}' for '{company_short_name}': missing URI in env '{db_env_var}'.")
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
elif db_config['connection_type'] == 'bridge' and not db_config.get('bridge_id'):
|
|
216
|
+
logging.error(
|
|
217
|
+
f"-> Skipping DB '{db_name}' for '{company_short_name}': missing bridge_id in configuration.")
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
# Register with the SQL service
|
|
221
|
+
sql_service.register_database(company_short_name, db_name, db_config)
|
|
222
|
+
|
|
223
|
+
def _register_tools(self, company_short_name: str, config: dict):
|
|
224
|
+
"""creates in the database each tool defined in the YAML."""
|
|
225
|
+
# Lazy import and resolve ToolService locally
|
|
226
|
+
from iatoolkit import current_iatoolkit
|
|
227
|
+
from iatoolkit.services.tool_service import ToolService
|
|
228
|
+
tool_service = current_iatoolkit().get_injector().get(ToolService)
|
|
229
|
+
|
|
230
|
+
tools_config = config.get('tools', [])
|
|
231
|
+
tool_service.sync_company_tools(company_short_name, tools_config)
|
|
232
|
+
|
|
233
|
+
def _register_prompts(self, company_short_name: str, config: dict):
|
|
110
234
|
"""
|
|
235
|
+
Delegates prompt synchronization to PromptService.
|
|
236
|
+
"""
|
|
237
|
+
# Lazy import to avoid circular dependency
|
|
238
|
+
from iatoolkit import current_iatoolkit
|
|
239
|
+
from iatoolkit.services.prompt_service import PromptService
|
|
240
|
+
prompt_service = current_iatoolkit().get_injector().get(PromptService)
|
|
241
|
+
|
|
111
242
|
prompts_config = config.get('prompts', [])
|
|
112
243
|
categories_config = config.get('prompt_categories', [])
|
|
113
244
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
245
|
+
prompt_service.sync_company_prompts(
|
|
246
|
+
company_short_name=company_short_name,
|
|
247
|
+
prompts_config=prompts_config,
|
|
248
|
+
categories_config=categories_config
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
def _register_knowledge_base(self, company_short_name: str, config: dict):
|
|
252
|
+
# Lazy import to avoid circular dependency
|
|
253
|
+
from iatoolkit import current_iatoolkit
|
|
254
|
+
from iatoolkit.services.knowledge_base_service import KnowledgeBaseService
|
|
255
|
+
knowledge_base = current_iatoolkit().get_injector().get(KnowledgeBaseService)
|
|
256
|
+
|
|
257
|
+
kb_config = config.get('knowledge_base', {})
|
|
258
|
+
categories_config = kb_config.get('collections', [])
|
|
259
|
+
|
|
260
|
+
# sync collection types in database
|
|
261
|
+
knowledge_base.sync_collection_types(company_short_name, categories_config)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _validate_configuration(self, company_short_name: str, config: dict):
|
|
265
|
+
"""
|
|
266
|
+
Validates the structure and consistency of the company.yaml configuration.
|
|
267
|
+
It checks for required keys, valid values, and existence of related files.
|
|
268
|
+
Raises IAToolkitException if any validation error is found.
|
|
269
|
+
"""
|
|
270
|
+
errors = []
|
|
271
|
+
|
|
272
|
+
# Helper to collect errors
|
|
273
|
+
def add_error(section, message):
|
|
274
|
+
errors.append(f"[{section}] {message}")
|
|
118
275
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
276
|
+
if not config:
|
|
277
|
+
add_error("General", "Configuration file missing or with errors, check the application logs.")
|
|
278
|
+
return errors
|
|
279
|
+
|
|
280
|
+
# 1. Top-level keys
|
|
281
|
+
if not config.get("id"):
|
|
282
|
+
add_error("General", "Missing required key: 'id'")
|
|
283
|
+
elif config["id"] != company_short_name:
|
|
284
|
+
add_error("General",
|
|
285
|
+
f"'id' ({config['id']}) does not match the company short name ('{company_short_name}').")
|
|
286
|
+
if not config.get("name"):
|
|
287
|
+
add_error("General", "Missing required key: 'name'")
|
|
288
|
+
|
|
289
|
+
# 2. LLM section
|
|
290
|
+
if not isinstance(config.get("llm"), dict):
|
|
291
|
+
add_error("llm", "Missing or invalid 'llm' section.")
|
|
292
|
+
else:
|
|
293
|
+
if not config.get("llm", {}).get("model"):
|
|
294
|
+
add_error("llm", "Missing required key: 'model'")
|
|
295
|
+
if not config.get("llm", {}).get("provider_api_keys"):
|
|
296
|
+
add_error("llm", "Missing required key: 'provider_api_keys'")
|
|
297
|
+
|
|
298
|
+
# 3. Embedding Provider
|
|
299
|
+
if isinstance(config.get("embedding_provider"), dict):
|
|
300
|
+
if not config.get("embedding_provider", {}).get("provider"):
|
|
301
|
+
add_error("embedding_provider", "Missing required key: 'provider'")
|
|
302
|
+
if not config.get("embedding_provider", {}).get("model"):
|
|
303
|
+
add_error("embedding_provider", "Missing required key: 'model'")
|
|
304
|
+
if not config.get("embedding_provider", {}).get("api_key_name"):
|
|
305
|
+
add_error("embedding_provider", "Missing required key: 'api_key_name'")
|
|
306
|
+
|
|
307
|
+
# 4. Data Sources
|
|
308
|
+
for i, source in enumerate(config.get("data_sources", {}).get("sql", [])):
|
|
309
|
+
if not source.get("database"):
|
|
310
|
+
add_error(f"data_sources.sql[{i}]", "Missing required key: 'database'")
|
|
311
|
+
|
|
312
|
+
connection_type = source.get("connection_type")
|
|
313
|
+
if connection_type == 'direct' and not source.get("connection_string_env"):
|
|
314
|
+
add_error(f"data_sources.sql[{i}]", "Missing required key: 'connection_string_env'")
|
|
315
|
+
elif connection_type == 'bridge' and not source.get("bridge_id"):
|
|
316
|
+
add_error(f"data_sources.sql[{i}]", "Missing bridge_id'")
|
|
317
|
+
|
|
318
|
+
# 5. Tools
|
|
319
|
+
for i, tool in enumerate(config.get("tools", [])):
|
|
320
|
+
function_name = tool.get("function_name")
|
|
321
|
+
if not function_name:
|
|
322
|
+
add_error(f"tools[{i}]", "Missing required key: 'function_name'")
|
|
323
|
+
|
|
324
|
+
# check that function exist in dispatcher
|
|
325
|
+
if not tool.get("description"):
|
|
326
|
+
add_error(f"tools[{i}]", "Missing required key: 'description'")
|
|
327
|
+
if not isinstance(tool.get("params"), dict):
|
|
328
|
+
add_error(f"tools[{i}]", "'params' key must be a dictionary.")
|
|
329
|
+
|
|
330
|
+
# 6. Prompts
|
|
331
|
+
category_set = set(config.get("prompt_categories", []))
|
|
332
|
+
for i, prompt in enumerate(config.get("prompts", [])):
|
|
333
|
+
prompt_name = prompt.get("name")
|
|
334
|
+
if not prompt_name:
|
|
335
|
+
add_error(f"prompts[{i}]", "Missing required key: 'name'")
|
|
336
|
+
else:
|
|
337
|
+
prompt_filename = f"{prompt_name}.prompt"
|
|
338
|
+
if not self.asset_repo.exists(company_short_name, AssetType.PROMPT, prompt_filename):
|
|
339
|
+
add_error(f"prompts/{prompt_name}:", f"Prompt file not found: {prompt_filename}")
|
|
340
|
+
|
|
341
|
+
prompt_description = prompt.get("description")
|
|
342
|
+
if not prompt_description:
|
|
343
|
+
add_error(f"prompts[{i}]", "Missing required key: 'description'")
|
|
344
|
+
|
|
345
|
+
prompt_cat = prompt.get("category")
|
|
346
|
+
if not prompt_cat:
|
|
347
|
+
add_error(f"prompts[{i}]", "Missing required key: 'category'")
|
|
348
|
+
elif prompt_cat not in category_set:
|
|
349
|
+
add_error(f"prompts[{i}]", f"Category '{prompt_cat}' is not defined in 'prompt_categories'.")
|
|
350
|
+
|
|
351
|
+
# 7. User Feedback
|
|
352
|
+
feedback_config = config.get("parameters", {}).get("user_feedback", {})
|
|
353
|
+
if feedback_config.get("channel") == "email" and not feedback_config.get("destination"):
|
|
354
|
+
add_error("parameters.user_feedback", "When channel is 'email', a 'destination' is required.")
|
|
355
|
+
|
|
356
|
+
# 8. Knowledge Base
|
|
357
|
+
kb_config = config.get("knowledge_base", {})
|
|
358
|
+
if kb_config and not isinstance(kb_config, dict):
|
|
359
|
+
add_error("knowledge_base", "Section must be a dictionary.")
|
|
360
|
+
elif kb_config:
|
|
361
|
+
prod_connector = kb_config.get("connectors", {}).get("production", {})
|
|
362
|
+
if prod_connector.get("type") == "s3":
|
|
363
|
+
for key in ["bucket", "prefix", "aws_access_key_id_env", "aws_secret_access_key_env", "aws_region_env"]:
|
|
364
|
+
if not prod_connector.get(key):
|
|
365
|
+
add_error("knowledge_base.connectors.production", f"S3 connector is missing '{key}'.")
|
|
366
|
+
|
|
367
|
+
# 9. Mail Provider
|
|
368
|
+
mail_config = config.get("mail_provider", {})
|
|
369
|
+
if mail_config:
|
|
370
|
+
provider = mail_config.get("provider")
|
|
371
|
+
if not provider:
|
|
372
|
+
add_error("mail_provider", "Missing required key: 'provider'")
|
|
373
|
+
elif provider not in ["brevo_mail", "smtplib"]:
|
|
374
|
+
add_error("mail_provider", f"Unsupported provider: '{provider}'. Must be 'brevo_mail' or 'smtplib'.")
|
|
375
|
+
|
|
376
|
+
if not mail_config.get("sender_email"):
|
|
377
|
+
add_error("mail_provider", "Missing required key: 'sender_email'")
|
|
378
|
+
|
|
379
|
+
# 10. Help Files
|
|
380
|
+
for key, filename in config.get("help_files", {}).items():
|
|
381
|
+
if not filename:
|
|
382
|
+
add_error(f"help_files.{key}", "Filename cannot be empty.")
|
|
123
383
|
continue
|
|
384
|
+
if not self.asset_repo.exists(company_short_name, AssetType.CONFIG, filename):
|
|
385
|
+
add_error(f"help_files.{key}", f"Help file not found: {filename}")
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
# If any errors were found, log all messages and raise an exception
|
|
389
|
+
if errors:
|
|
390
|
+
error_summary = f"Configuration file '{company_short_name}/config/company.yaml' for '{company_short_name}' has validation errors:\n" + "\n".join(
|
|
391
|
+
f" - {e}" for e in errors)
|
|
392
|
+
logging.error(error_summary)
|
|
393
|
+
|
|
394
|
+
return errors
|
|
124
395
|
|
|
125
|
-
category_obj = created_categories[category_name]
|
|
126
|
-
company_instance._create_prompt(
|
|
127
|
-
prompt_name=prompt_data['name'],
|
|
128
|
-
description=prompt_data['description'],
|
|
129
|
-
order=prompt_data['order'],
|
|
130
|
-
category=category_obj,
|
|
131
|
-
active=prompt_data.get('active', True),
|
|
132
|
-
custom_fields=prompt_data.get('custom_fields', [])
|
|
133
|
-
)
|