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.

Files changed (37) hide show
  1. iatoolkit/__init__.py +0 -4
  2. iatoolkit/base_company.py +0 -6
  3. iatoolkit/iatoolkit.py +2 -5
  4. iatoolkit/repositories/database_manager.py +5 -0
  5. iatoolkit/repositories/models.py +0 -2
  6. iatoolkit/services/branding_service.py +7 -5
  7. iatoolkit/services/company_context_service.py +145 -0
  8. iatoolkit/services/configuration_service.py +13 -20
  9. iatoolkit/services/dispatcher_service.py +34 -33
  10. iatoolkit/services/language_service.py +9 -5
  11. iatoolkit/services/profile_service.py +3 -0
  12. iatoolkit/services/query_service.py +4 -1
  13. iatoolkit/services/search_service.py +11 -4
  14. iatoolkit/services/sql_service.py +49 -23
  15. iatoolkit/static/js/chat_onboarding_button.js +6 -0
  16. iatoolkit/static/styles/onboarding.css +7 -0
  17. iatoolkit/templates/change_password.html +1 -1
  18. iatoolkit/templates/chat.html +1 -0
  19. iatoolkit/templates/chat_modals.html +1 -0
  20. iatoolkit/templates/login_simulation.html +1 -1
  21. iatoolkit/templates/onboarding_shell.html +4 -1
  22. iatoolkit/views/base_login_view.py +7 -8
  23. iatoolkit/views/change_password_view.py +2 -3
  24. iatoolkit/views/external_login_view.py +1 -1
  25. iatoolkit/views/forgot_password_view.py +2 -4
  26. iatoolkit/views/help_content_api_view.py +1 -1
  27. iatoolkit/views/home_view.py +1 -2
  28. iatoolkit/views/login_simulation_view.py +1 -1
  29. iatoolkit/views/login_view.py +8 -8
  30. iatoolkit/views/signup_view.py +2 -4
  31. iatoolkit/views/verify_user_view.py +1 -3
  32. {iatoolkit-0.67.0.dist-info → iatoolkit-0.69.0.dist-info}/METADATA +1 -1
  33. {iatoolkit-0.67.0.dist-info → iatoolkit-0.69.0.dist-info}/RECORD +36 -36
  34. iatoolkit/services/onboarding_service.py +0 -49
  35. {iatoolkit-0.67.0.dist-info → iatoolkit-0.69.0.dist-info}/WHEEL +0 -0
  36. {iatoolkit-0.67.0.dist-info → iatoolkit-0.69.0.dist-info}/licenses/LICENSE +0 -0
  37. {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.67.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,
@@ -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, company: Company | None) -> dict:
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": company.name if company else "IAToolkit",
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 get_company_content(self, company_short_name: str, content_key: str) -> dict | list | None:
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 register_company(self, company_short_name: str, company_instance: BaseCompany):
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. identify the instance with his name and load info from database
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
- # 3. Register core company details and get the database object
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
- # 4. Register tools (functions)
44
+ # 3. Register tools (functions)
53
45
  self._register_tools(company_instance, config.get('tools', []))
54
46
 
55
- # 5. Register prompt categories and prompts
47
+ # 4. Register prompt categories and prompts
56
48
  self._register_prompts(company_instance, config)
57
49
 
58
- # 6. Link the persisted Company object back to the running instance
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: BaseCompany, config: dict) -> Company:
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: BaseCompany, tools_config: list):
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: BaseCompany, config: dict):
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
- # register the company configuration
68
- self.config_service.register_company(company_name, company_instance)
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, profile_repo: ProfileRepo):
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
- company = self.profile_repo.get_company_by_short_name(company_short_name)
68
- if company and company.default_language:
69
- logging.debug(f"Language determined by company default: {company.default_language}")
70
- g.lang = company.default_language
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.dispatcher.get_company_context(company_name=company_short_name)
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
- super().__init__()
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, company_id: int, query: str, metadata_filter: dict = None) -> str:
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
- company_id: The ID of the company to search within.
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
- document_list = self.vs_repo.query(company_id=company_id,
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