iatoolkit 0.91.1__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 +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 +38 -0
- iatoolkit/common/model_registry.py +159 -0
- iatoolkit/common/routes.py +42 -5
- iatoolkit/common/util.py +11 -12
- iatoolkit/company_registry.py +5 -0
- iatoolkit/core.py +51 -20
- 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 +124 -2
- iatoolkit/locales/es.yaml +122 -0
- iatoolkit/repositories/database_manager.py +44 -19
- 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 +88 -39
- iatoolkit/services/configuration_service.py +157 -68
- 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 +412 -0
- iatoolkit/{infra/llm_client.py → services/llm_client_service.py} +38 -29
- iatoolkit/services/load_documents_service.py +18 -47
- 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 +105 -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 +44 -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/load_company_configuration_api_view.py +49 -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.4.2.dist-info}/METADATA +4 -4
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.4.2.dist-info}/RECORD +64 -56
- 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.4.2.dist-info}/WHEEL +0 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.4.2.dist-info}/licenses/LICENSE +0 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.4.2.dist-info}/licenses/LICENSE_COMMUNITY.md +0 -0
- {iatoolkit-0.91.1.dist-info → iatoolkit-1.4.2.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,99 @@ 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)
|
|
85
|
+
# 3. Register tools
|
|
86
|
+
self._register_tools(company_short_name, config)
|
|
63
87
|
|
|
64
|
-
|
|
65
|
-
|
|
88
|
+
# 4. Register prompt categories and prompts
|
|
89
|
+
self._register_prompts(company_short_name, config)
|
|
66
90
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
# 6. Link the persisted Company object back to the running instance
|
|
71
|
-
company_instance.company_short_name = company_short_name
|
|
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.")
|
|
78
|
-
|
|
98
|
+
return config, errors
|
|
79
99
|
|
|
80
100
|
def _load_and_merge_configs(self, company_short_name: str) -> dict:
|
|
81
101
|
"""
|
|
82
102
|
Loads the main company.yaml and merges data from supplementary files
|
|
83
|
-
specified in the 'content_files' section.
|
|
103
|
+
specified in the 'content_files' section using AssetRepository.
|
|
84
104
|
"""
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if not
|
|
89
|
-
raise FileNotFoundError(f"Main configuration file not found: {
|
|
90
|
-
|
|
91
|
-
|
|
105
|
+
main_config_filename = "company.yaml"
|
|
106
|
+
|
|
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}")
|
|
111
|
+
|
|
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 {}
|
|
92
127
|
|
|
93
128
|
# Load and merge supplementary content files (e.g., onboarding_cards)
|
|
94
|
-
for key,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
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)
|
|
98
133
|
else:
|
|
99
|
-
logging.warning(f"⚠️ Warning: Content file not found: {
|
|
100
|
-
config[key] = None
|
|
134
|
+
logging.warning(f"⚠️ Warning: Content file not found: {filename}")
|
|
135
|
+
config[key] = None
|
|
101
136
|
|
|
102
137
|
return config
|
|
103
138
|
|
|
104
|
-
def
|
|
139
|
+
def _register_company_database(self, config: dict) -> Company:
|
|
105
140
|
# register the company in the database: create_or_update logic
|
|
141
|
+
if not config:
|
|
142
|
+
return None
|
|
106
143
|
|
|
107
|
-
|
|
108
|
-
|
|
144
|
+
# create or update the company in database
|
|
145
|
+
company_obj = Company(short_name=config.get('id'),
|
|
146
|
+
name=config.get('name'),
|
|
109
147
|
parameters=config.get('parameters', {}))
|
|
110
148
|
company = self.profile_repo.create_company(company_obj)
|
|
111
149
|
|
|
112
|
-
# save company object with the
|
|
113
|
-
|
|
150
|
+
# save company object with the configuration
|
|
151
|
+
config['company'] = company
|
|
152
|
+
|
|
114
153
|
return company
|
|
115
154
|
|
|
116
|
-
def
|
|
155
|
+
def register_data_sources(self,
|
|
156
|
+
company_short_name: str,
|
|
157
|
+
config: dict = None):
|
|
117
158
|
"""
|
|
118
159
|
Reads the data_sources config and registers databases with SqlService.
|
|
119
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.
|
|
120
164
|
"""
|
|
121
|
-
|
|
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
|
+
|
|
122
174
|
from iatoolkit import current_iatoolkit
|
|
123
175
|
from iatoolkit.services.sql_service import SqlService
|
|
124
176
|
sql_service = current_iatoolkit().get_injector().get(SqlService)
|
|
@@ -129,25 +181,46 @@ class ConfigurationService:
|
|
|
129
181
|
if not sql_sources:
|
|
130
182
|
return
|
|
131
183
|
|
|
132
|
-
logging.info(f"🛢️ Registering databases
|
|
184
|
+
logging.info(f"🛢️ Registering databases for '{company_short_name}'...")
|
|
133
185
|
|
|
134
|
-
for
|
|
135
|
-
db_name =
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
# resolve the URI
|
|
140
|
-
db_uri = os.getenv(db_env_var) if db_env_var else None
|
|
186
|
+
for source in sql_sources:
|
|
187
|
+
db_name = source.get('database')
|
|
188
|
+
if not db_name:
|
|
189
|
+
continue
|
|
141
190
|
|
|
142
|
-
|
|
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'):
|
|
143
211
|
logging.error(
|
|
144
212
|
f"-> Skipping DB '{db_name}' for '{company_short_name}': missing URI in env '{db_env_var}'.")
|
|
145
213
|
continue
|
|
146
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
|
+
|
|
147
220
|
# Register with the SQL service
|
|
148
|
-
sql_service.register_database(
|
|
221
|
+
sql_service.register_database(company_short_name, db_name, db_config)
|
|
149
222
|
|
|
150
|
-
def _register_tools(self,
|
|
223
|
+
def _register_tools(self, company_short_name: str, config: dict):
|
|
151
224
|
"""creates in the database each tool defined in the YAML."""
|
|
152
225
|
# Lazy import and resolve ToolService locally
|
|
153
226
|
from iatoolkit import current_iatoolkit
|
|
@@ -155,9 +228,9 @@ class ConfigurationService:
|
|
|
155
228
|
tool_service = current_iatoolkit().get_injector().get(ToolService)
|
|
156
229
|
|
|
157
230
|
tools_config = config.get('tools', [])
|
|
158
|
-
tool_service.sync_company_tools(
|
|
231
|
+
tool_service.sync_company_tools(company_short_name, tools_config)
|
|
159
232
|
|
|
160
|
-
def _register_prompts(self,
|
|
233
|
+
def _register_prompts(self, company_short_name: str, config: dict):
|
|
161
234
|
"""
|
|
162
235
|
Delegates prompt synchronization to PromptService.
|
|
163
236
|
"""
|
|
@@ -170,11 +243,24 @@ class ConfigurationService:
|
|
|
170
243
|
categories_config = config.get('prompt_categories', [])
|
|
171
244
|
|
|
172
245
|
prompt_service.sync_company_prompts(
|
|
173
|
-
|
|
246
|
+
company_short_name=company_short_name,
|
|
174
247
|
prompts_config=prompts_config,
|
|
175
248
|
categories_config=categories_config
|
|
176
249
|
)
|
|
177
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
|
+
|
|
178
264
|
def _validate_configuration(self, company_short_name: str, config: dict):
|
|
179
265
|
"""
|
|
180
266
|
Validates the structure and consistency of the company.yaml configuration.
|
|
@@ -182,13 +268,15 @@ class ConfigurationService:
|
|
|
182
268
|
Raises IAToolkitException if any validation error is found.
|
|
183
269
|
"""
|
|
184
270
|
errors = []
|
|
185
|
-
config_dir = Path("companies") / company_short_name / "config"
|
|
186
|
-
prompts_dir = Path("companies") / company_short_name / "prompts"
|
|
187
271
|
|
|
188
272
|
# Helper to collect errors
|
|
189
273
|
def add_error(section, message):
|
|
190
274
|
errors.append(f"[{section}] {message}")
|
|
191
275
|
|
|
276
|
+
if not config:
|
|
277
|
+
add_error("General", "Configuration file missing or with errors, check the application logs.")
|
|
278
|
+
return errors
|
|
279
|
+
|
|
192
280
|
# 1. Top-level keys
|
|
193
281
|
if not config.get("id"):
|
|
194
282
|
add_error("General", "Missing required key: 'id'")
|
|
@@ -204,8 +292,8 @@ class ConfigurationService:
|
|
|
204
292
|
else:
|
|
205
293
|
if not config.get("llm", {}).get("model"):
|
|
206
294
|
add_error("llm", "Missing required key: 'model'")
|
|
207
|
-
if not config.get("llm", {}).get("
|
|
208
|
-
add_error("llm", "Missing required key: '
|
|
295
|
+
if not config.get("llm", {}).get("provider_api_keys"):
|
|
296
|
+
add_error("llm", "Missing required key: 'provider_api_keys'")
|
|
209
297
|
|
|
210
298
|
# 3. Embedding Provider
|
|
211
299
|
if isinstance(config.get("embedding_provider"), dict):
|
|
@@ -220,8 +308,12 @@ class ConfigurationService:
|
|
|
220
308
|
for i, source in enumerate(config.get("data_sources", {}).get("sql", [])):
|
|
221
309
|
if not source.get("database"):
|
|
222
310
|
add_error(f"data_sources.sql[{i}]", "Missing required key: 'database'")
|
|
223
|
-
|
|
311
|
+
|
|
312
|
+
connection_type = source.get("connection_type")
|
|
313
|
+
if connection_type == 'direct' and not source.get("connection_string_env"):
|
|
224
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'")
|
|
225
317
|
|
|
226
318
|
# 5. Tools
|
|
227
319
|
for i, tool in enumerate(config.get("tools", [])):
|
|
@@ -242,9 +334,9 @@ class ConfigurationService:
|
|
|
242
334
|
if not prompt_name:
|
|
243
335
|
add_error(f"prompts[{i}]", "Missing required key: 'name'")
|
|
244
336
|
else:
|
|
245
|
-
|
|
246
|
-
if not
|
|
247
|
-
add_error(f"prompts/{prompt_name}:", f"Prompt file not found: {
|
|
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}")
|
|
248
340
|
|
|
249
341
|
prompt_description = prompt.get("description")
|
|
250
342
|
if not prompt_description:
|
|
@@ -289,9 +381,9 @@ class ConfigurationService:
|
|
|
289
381
|
if not filename:
|
|
290
382
|
add_error(f"help_files.{key}", "Filename cannot be empty.")
|
|
291
383
|
continue
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
+
|
|
295
387
|
|
|
296
388
|
# If any errors were found, log all messages and raise an exception
|
|
297
389
|
if errors:
|
|
@@ -299,8 +391,5 @@ class ConfigurationService:
|
|
|
299
391
|
f" - {e}" for e in errors)
|
|
300
392
|
logging.error(error_summary)
|
|
301
393
|
|
|
302
|
-
|
|
303
|
-
IAToolkitException.ErrorType.CONFIG_ERROR,
|
|
304
|
-
'company.yaml validation errors'
|
|
305
|
-
)
|
|
394
|
+
return errors
|
|
306
395
|
|
|
@@ -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:
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
|
|
1
7
|
import logging
|
|
2
8
|
import json
|
|
3
|
-
from typing import Dict, Any,
|
|
9
|
+
from typing import Dict, Any, Optional
|
|
4
10
|
from iatoolkit.services.user_session_context_service import UserSessionContextService
|
|
5
11
|
from iatoolkit.services.i18n_service import I18nService
|
|
6
|
-
from iatoolkit.
|
|
12
|
+
from iatoolkit.services.llm_client_service import llmClient
|
|
7
13
|
from iatoolkit.repositories.models import Company
|
|
8
14
|
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
9
15
|
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
@@ -19,7 +25,7 @@ class HistoryManagerService:
|
|
|
19
25
|
3. Database persistence retrieval (full chat history).
|
|
20
26
|
"""
|
|
21
27
|
TYPE_SERVER_SIDE = 'server_side' # For models like OpenAI
|
|
22
|
-
TYPE_CLIENT_SIDE = 'client_side' # For models like Gemini
|
|
28
|
+
TYPE_CLIENT_SIDE = 'client_side' # For models like Gemini and Deepseek
|
|
23
29
|
|
|
24
30
|
GEMINI_MAX_TOKENS_CONTEXT_HISTORY = 200000
|
|
25
31
|
|
|
@@ -47,7 +53,7 @@ class HistoryManagerService:
|
|
|
47
53
|
Initializes a new conversation history.
|
|
48
54
|
"""
|
|
49
55
|
# 1. Clear existing history
|
|
50
|
-
self.session_context.clear_llm_history(company_short_name, user_identifier)
|
|
56
|
+
self.session_context.clear_llm_history(company_short_name, user_identifier, model=model)
|
|
51
57
|
|
|
52
58
|
if history_type == self.TYPE_SERVER_SIDE:
|
|
53
59
|
# OpenAI: Send system prompt to API and store the resulting ID
|
|
@@ -56,14 +62,14 @@ class HistoryManagerService:
|
|
|
56
62
|
company_base_context=prepared_context,
|
|
57
63
|
model=model
|
|
58
64
|
)
|
|
59
|
-
self.session_context.save_last_response_id(company_short_name, user_identifier, response_id)
|
|
60
|
-
self.session_context.save_initial_response_id(company_short_name, user_identifier, response_id)
|
|
65
|
+
self.session_context.save_last_response_id(company_short_name, user_identifier, response_id, model=model)
|
|
66
|
+
self.session_context.save_initial_response_id(company_short_name, user_identifier, response_id, model=model)
|
|
61
67
|
return {'response_id': response_id}
|
|
62
68
|
|
|
63
69
|
elif history_type == self.TYPE_CLIENT_SIDE:
|
|
64
70
|
# Gemini: Store system prompt as the first message in the list
|
|
65
71
|
context_history = [{"role": "user", "content": prepared_context}]
|
|
66
|
-
self.session_context.save_context_history(company_short_name, user_identifier, context_history)
|
|
72
|
+
self.session_context.save_context_history(company_short_name, user_identifier, context_history, model=model)
|
|
67
73
|
return {}
|
|
68
74
|
|
|
69
75
|
return {}
|
|
@@ -76,14 +82,16 @@ class HistoryManagerService:
|
|
|
76
82
|
Populates the request_params within the HistoryHandle.
|
|
77
83
|
Returns True if a rebuild is needed, False otherwise.
|
|
78
84
|
"""
|
|
85
|
+
model = getattr(handle, "model", None)
|
|
86
|
+
|
|
79
87
|
if handle.type == self.TYPE_SERVER_SIDE:
|
|
80
|
-
previous_response_id = None
|
|
81
88
|
if ignore_history:
|
|
82
|
-
previous_response_id = self.session_context.get_initial_response_id(
|
|
83
|
-
|
|
89
|
+
previous_response_id = self.session_context.get_initial_response_id(
|
|
90
|
+
handle.company_short_name,handle.user_identifier,model=model)
|
|
84
91
|
else:
|
|
85
|
-
previous_response_id = self.session_context.get_last_response_id(
|
|
86
|
-
|
|
92
|
+
previous_response_id = self.session_context.get_last_response_id(
|
|
93
|
+
handle.company_short_name,handle.user_identifier,model=model)
|
|
94
|
+
|
|
87
95
|
|
|
88
96
|
if not previous_response_id:
|
|
89
97
|
handle.request_params = {}
|
|
@@ -93,8 +101,8 @@ class HistoryManagerService:
|
|
|
93
101
|
return False
|
|
94
102
|
|
|
95
103
|
elif handle.type == self.TYPE_CLIENT_SIDE:
|
|
96
|
-
context_history = self.session_context.get_context_history(
|
|
97
|
-
|
|
104
|
+
context_history = self.session_context.get_context_history(
|
|
105
|
+
handle.company_short_name,handle.user_identifier,model=model) or []
|
|
98
106
|
|
|
99
107
|
if not context_history:
|
|
100
108
|
handle.request_params = {}
|
|
@@ -104,7 +112,7 @@ class HistoryManagerService:
|
|
|
104
112
|
# Keep only system prompt
|
|
105
113
|
context_history = [context_history[0]]
|
|
106
114
|
|
|
107
|
-
#
|
|
115
|
+
# Append the current user turn to the context sent to the API
|
|
108
116
|
context_history.append({"role": "user", "content": user_turn_prompt})
|
|
109
117
|
|
|
110
118
|
self._trim_context_history(context_history)
|
|
@@ -125,15 +133,23 @@ class HistoryManagerService:
|
|
|
125
133
|
history_type = history_handle.type
|
|
126
134
|
company_short_name = history_handle.company_short_name
|
|
127
135
|
user_identifier = history_handle.user_identifier
|
|
136
|
+
model = getattr(history_handle, "model", None)
|
|
128
137
|
|
|
129
138
|
if history_type == self.TYPE_SERVER_SIDE:
|
|
130
139
|
if "response_id" in response:
|
|
131
|
-
self.session_context.save_last_response_id(
|
|
132
|
-
|
|
140
|
+
self.session_context.save_last_response_id(
|
|
141
|
+
company_short_name,
|
|
142
|
+
user_identifier,
|
|
143
|
+
response["response_id"],
|
|
144
|
+
model=model)
|
|
133
145
|
|
|
134
146
|
elif history_type == self.TYPE_CLIENT_SIDE:
|
|
135
|
-
|
|
136
|
-
|
|
147
|
+
# get the history for this company/user/model
|
|
148
|
+
context_history = self.session_context.get_context_history(
|
|
149
|
+
company_short_name,
|
|
150
|
+
user_identifier,
|
|
151
|
+
model=model)
|
|
152
|
+
|
|
137
153
|
# Ensure the user prompt is recorded if not already.
|
|
138
154
|
# We check content equality to handle the case where the previous message was
|
|
139
155
|
# also 'user' (e.g., System Prompt) but different content.
|
|
@@ -142,10 +158,14 @@ class HistoryManagerService:
|
|
|
142
158
|
if last_content != user_turn_prompt:
|
|
143
159
|
context_history.append({"role": "user", "content": user_turn_prompt})
|
|
144
160
|
|
|
145
|
-
if response.get('
|
|
146
|
-
context_history.append({"role": "
|
|
161
|
+
if response.get('answer'):
|
|
162
|
+
context_history.append({"role": "assistant", "content": response.get('answer', '')})
|
|
147
163
|
|
|
148
|
-
self.session_context.save_context_history(
|
|
164
|
+
self.session_context.save_context_history(
|
|
165
|
+
company_short_name,
|
|
166
|
+
user_identifier,
|
|
167
|
+
context_history,
|
|
168
|
+
model=model)
|
|
149
169
|
|
|
150
170
|
def _trim_context_history(self, context_history: list):
|
|
151
171
|
"""Internal helper to keep token usage within limits for client-side history."""
|
|
@@ -169,8 +189,7 @@ class HistoryManagerService:
|
|
|
169
189
|
except IndexError:
|
|
170
190
|
break
|
|
171
191
|
|
|
172
|
-
# ---
|
|
173
|
-
|
|
192
|
+
# --- this is for the history popup in the chat page
|
|
174
193
|
def get_full_history(self, company_short_name: str, user_identifier: str) -> dict:
|
|
175
194
|
"""Retrieves the full persisted history from the database."""
|
|
176
195
|
try:
|