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.
Files changed (114) hide show
  1. iatoolkit/__init__.py +19 -7
  2. iatoolkit/base_company.py +1 -71
  3. iatoolkit/cli_commands.py +9 -21
  4. iatoolkit/common/exceptions.py +2 -0
  5. iatoolkit/common/interfaces/__init__.py +0 -0
  6. iatoolkit/common/interfaces/asset_storage.py +34 -0
  7. iatoolkit/common/interfaces/database_provider.py +38 -0
  8. iatoolkit/common/model_registry.py +159 -0
  9. iatoolkit/common/routes.py +53 -32
  10. iatoolkit/common/util.py +17 -12
  11. iatoolkit/company_registry.py +55 -14
  12. iatoolkit/{iatoolkit.py → core.py} +102 -72
  13. iatoolkit/infra/{mail_app.py → brevo_mail_app.py} +15 -37
  14. iatoolkit/infra/llm_providers/__init__.py +0 -0
  15. iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
  16. iatoolkit/infra/{gemini_adapter.py → llm_providers/gemini_adapter.py} +11 -17
  17. iatoolkit/infra/{openai_adapter.py → llm_providers/openai_adapter.py} +41 -7
  18. iatoolkit/infra/llm_proxy.py +235 -134
  19. iatoolkit/infra/llm_response.py +5 -0
  20. iatoolkit/locales/en.yaml +134 -4
  21. iatoolkit/locales/es.yaml +293 -162
  22. iatoolkit/repositories/database_manager.py +92 -22
  23. iatoolkit/repositories/document_repo.py +7 -0
  24. iatoolkit/repositories/filesystem_asset_repository.py +36 -0
  25. iatoolkit/repositories/llm_query_repo.py +36 -22
  26. iatoolkit/repositories/models.py +86 -95
  27. iatoolkit/repositories/profile_repo.py +64 -13
  28. iatoolkit/repositories/vs_repo.py +31 -28
  29. iatoolkit/services/auth_service.py +1 -1
  30. iatoolkit/services/branding_service.py +1 -1
  31. iatoolkit/services/company_context_service.py +96 -39
  32. iatoolkit/services/configuration_service.py +329 -67
  33. iatoolkit/services/dispatcher_service.py +51 -227
  34. iatoolkit/services/document_service.py +10 -1
  35. iatoolkit/services/embedding_service.py +9 -6
  36. iatoolkit/services/excel_service.py +50 -2
  37. iatoolkit/services/file_processor_service.py +0 -5
  38. iatoolkit/services/history_manager_service.py +208 -0
  39. iatoolkit/services/jwt_service.py +1 -1
  40. iatoolkit/services/knowledge_base_service.py +412 -0
  41. iatoolkit/services/language_service.py +8 -2
  42. iatoolkit/services/license_service.py +82 -0
  43. iatoolkit/{infra/llm_client.py → services/llm_client_service.py} +42 -29
  44. iatoolkit/services/load_documents_service.py +18 -47
  45. iatoolkit/services/mail_service.py +171 -25
  46. iatoolkit/services/profile_service.py +69 -36
  47. iatoolkit/services/{prompt_manager_service.py → prompt_service.py} +136 -25
  48. iatoolkit/services/query_service.py +229 -203
  49. iatoolkit/services/sql_service.py +116 -34
  50. iatoolkit/services/tool_service.py +246 -0
  51. iatoolkit/services/user_feedback_service.py +18 -6
  52. iatoolkit/services/user_session_context_service.py +121 -51
  53. iatoolkit/static/images/iatoolkit_core.png +0 -0
  54. iatoolkit/static/images/iatoolkit_logo.png +0 -0
  55. iatoolkit/static/js/chat_feedback_button.js +1 -1
  56. iatoolkit/static/js/chat_help_content.js +4 -4
  57. iatoolkit/static/js/chat_main.js +61 -9
  58. iatoolkit/static/js/chat_model_selector.js +227 -0
  59. iatoolkit/static/js/chat_onboarding_button.js +1 -1
  60. iatoolkit/static/js/chat_reload_button.js +4 -1
  61. iatoolkit/static/styles/chat_iatoolkit.css +59 -3
  62. iatoolkit/static/styles/chat_public.css +28 -0
  63. iatoolkit/static/styles/documents.css +598 -0
  64. iatoolkit/static/styles/landing_page.css +223 -7
  65. iatoolkit/static/styles/llm_output.css +34 -1
  66. iatoolkit/system_prompts/__init__.py +0 -0
  67. iatoolkit/system_prompts/query_main.prompt +28 -3
  68. iatoolkit/system_prompts/sql_rules.prompt +47 -12
  69. iatoolkit/templates/_company_header.html +30 -5
  70. iatoolkit/templates/_login_widget.html +3 -3
  71. iatoolkit/templates/base.html +13 -0
  72. iatoolkit/templates/chat.html +45 -3
  73. iatoolkit/templates/forgot_password.html +3 -2
  74. iatoolkit/templates/onboarding_shell.html +1 -2
  75. iatoolkit/templates/signup.html +3 -0
  76. iatoolkit/views/base_login_view.py +8 -3
  77. iatoolkit/views/change_password_view.py +1 -1
  78. iatoolkit/views/chat_view.py +76 -0
  79. iatoolkit/views/forgot_password_view.py +9 -4
  80. iatoolkit/views/history_api_view.py +3 -3
  81. iatoolkit/views/home_view.py +4 -2
  82. iatoolkit/views/init_context_api_view.py +1 -1
  83. iatoolkit/views/llmquery_api_view.py +4 -3
  84. iatoolkit/views/load_company_configuration_api_view.py +49 -0
  85. iatoolkit/views/{file_store_api_view.py → load_document_api_view.py} +15 -11
  86. iatoolkit/views/login_view.py +25 -8
  87. iatoolkit/views/logout_api_view.py +10 -2
  88. iatoolkit/views/prompt_api_view.py +1 -1
  89. iatoolkit/views/rag_api_view.py +216 -0
  90. iatoolkit/views/root_redirect_view.py +22 -0
  91. iatoolkit/views/signup_view.py +12 -4
  92. iatoolkit/views/static_page_view.py +27 -0
  93. iatoolkit/views/users_api_view.py +33 -0
  94. iatoolkit/views/verify_user_view.py +1 -1
  95. iatoolkit-1.4.2.dist-info/METADATA +268 -0
  96. iatoolkit-1.4.2.dist-info/RECORD +133 -0
  97. iatoolkit-1.4.2.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
  98. iatoolkit/repositories/tasks_repo.py +0 -52
  99. iatoolkit/services/history_service.py +0 -37
  100. iatoolkit/services/search_service.py +0 -55
  101. iatoolkit/services/tasks_service.py +0 -188
  102. iatoolkit/templates/about.html +0 -13
  103. iatoolkit/templates/index.html +0 -145
  104. iatoolkit/templates/login_simulation.html +0 -45
  105. iatoolkit/views/external_login_view.py +0 -73
  106. iatoolkit/views/index_view.py +0 -14
  107. iatoolkit/views/login_simulation_view.py +0 -93
  108. iatoolkit/views/tasks_api_view.py +0 -72
  109. iatoolkit/views/tasks_review_api_view.py +0 -55
  110. iatoolkit-0.71.4.dist-info/METADATA +0 -276
  111. iatoolkit-0.71.4.dist-info/RECORD +0 -122
  112. {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/WHEEL +0 -0
  113. {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/licenses/LICENSE +0 -0
  114. {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/top_level.txt +0 -0
@@ -9,6 +9,7 @@ from iatoolkit.common.exceptions import IAToolkitException
9
9
  from iatoolkit.repositories.database_manager import DatabaseManager
10
10
  from iatoolkit.services.embedding_service import EmbeddingService
11
11
  from iatoolkit.repositories.models import Document, VSDoc, Company
12
+ from typing import Dict
12
13
  import logging
13
14
 
14
15
 
@@ -29,17 +30,18 @@ class VSRepo:
29
30
  self.session.add(doc)
30
31
  self.session.commit()
31
32
  except Exception as e:
32
- logging.error(f"Error inserting documents into PostgreSQL: {str(e)}")
33
+ logging.error(f"Error while inserting embedding chunk list: {str(e)}")
33
34
  self.session.rollback()
34
35
  raise IAToolkitException(IAToolkitException.ErrorType.VECTOR_STORE_ERROR,
35
- f"Error inserting documents into PostgreSQL: {str(e)}")
36
+ f"Error while inserting embedding chunk list: {str(e)}")
36
37
 
37
38
  def query(self,
38
39
  company_short_name: str,
39
40
  query_text: str,
40
41
  n_results=5,
41
- metadata_filter=None
42
- ) -> list[Document]:
42
+ metadata_filter=None,
43
+ collection_id: int = None
44
+ ) -> list[Dict]:
43
45
  """
44
46
  search documents similar to the query for a company
45
47
 
@@ -53,7 +55,12 @@ class VSRepo:
53
55
  list of documents matching the query and filters
54
56
  """
55
57
  # Generate the embedding with the query text for the specific company
56
- query_embedding = self.embedding_service.embed_text(company_short_name, query_text)
58
+ try:
59
+ query_embedding = self.embedding_service.embed_text(company_short_name, query_text)
60
+ except Exception as e:
61
+ logging.error(f"error while creating text embedding: {str(e)}")
62
+ raise IAToolkitException(IAToolkitException.ErrorType.EMBEDDING_ERROR,
63
+ f"embedding error: {str(e)}")
57
64
 
58
65
  sql_query, params = None, None
59
66
  try:
@@ -65,11 +72,12 @@ class VSRepo:
65
72
 
66
73
  # build the SQL query
67
74
  sql_query_parts = ["""
68
- SELECT iat_documents.id, \
75
+ SELECT iat_vsdocs.id, \
69
76
  iat_documents.filename, \
70
- iat_documents.content, \
77
+ iat_vsdocs.text, \
71
78
  iat_documents.content_b64, \
72
- iat_documents.meta
79
+ iat_documents.meta,
80
+ iat_documents.id
73
81
  FROM iat_vsdocs, \
74
82
  iat_documents
75
83
  WHERE iat_vsdocs.company_id = :company_id
@@ -83,6 +91,10 @@ class VSRepo:
83
91
  "n_results": n_results
84
92
  }
85
93
 
94
+ # Filter by Collection ID
95
+ if collection_id:
96
+ sql_query_parts.append(" AND iat_documents.collection_type_id = :collection_id")
97
+ params['collection_id'] = collection_id
86
98
 
87
99
  # add metadata filter, if exists
88
100
  if metadata_filter and isinstance(metadata_filter, dict):
@@ -98,7 +110,7 @@ class VSRepo:
98
110
  sql_query = "".join(sql_query_parts)
99
111
 
100
112
  # add sorting and limit of results
101
- sql_query += " ORDER BY embedding <-> :query_embedding LIMIT :n_results"
113
+ sql_query += " ORDER BY embedding <-> CAST(:query_embedding AS VECTOR) LIMIT :n_results"
102
114
 
103
115
  logging.debug(f"Executing SQL query: {sql_query}")
104
116
  logging.debug(f"With parameters: {params}")
@@ -112,17 +124,18 @@ class VSRepo:
112
124
  for row in rows:
113
125
  # create the document object with the data
114
126
  meta_data = row[4] if len(row) > 4 and row[4] is not None else {}
115
- doc = Document(
116
- id=row[0],
117
- company_id=company.id,
118
- filename=row[1],
119
- content=row[2],
120
- content_b64=row[3],
121
- meta=meta_data
127
+ vs_documents.append(
128
+ {
129
+ 'id': row[0],
130
+ 'document_id': row[5],
131
+ 'filename': row[1],
132
+ 'text': row[2],
133
+ 'meta': meta_data,
134
+ }
122
135
  )
123
- vs_documents.append(doc)
124
136
 
125
- return self.remove_duplicates_by_id(vs_documents)
137
+ return vs_documents
138
+
126
139
 
127
140
  except Exception as e:
128
141
  logging.error(f"Error en la consulta de documentos: {str(e)}")
@@ -133,13 +146,3 @@ class VSRepo:
133
146
  finally:
134
147
  self.session.close()
135
148
 
136
- def remove_duplicates_by_id(self, objects):
137
- unique_by_id = {}
138
- result = []
139
-
140
- for obj in objects:
141
- if obj.id not in unique_by_id:
142
- unique_by_id[obj.id] = True
143
- result.append(obj)
144
-
145
- return result
@@ -132,7 +132,7 @@ class AuthService:
132
132
  # check if the api-key is valid and active
133
133
  api_key_entry = self.profile_service.get_active_api_key_entry(api_key)
134
134
  if not api_key_entry:
135
- logging.info(f"Invalid or inactive API Key {api_key}")
135
+ logging.error(f"Invalid or inactive IAToolkit API Key: {api_key}")
136
136
  return {"success": False,
137
137
  "error_message": self.i18n_service.t('errors.auth.invalid_api_key'),
138
138
  "status_code": 402}
@@ -42,7 +42,7 @@ class BrandingService:
42
42
  # Estilos para Alertas de Error ---
43
43
  "brand_danger_color": "#dc3545", # Rojo principal para alertas
44
44
  "brand_danger_bg": "#f8d7da", # Fondo rojo pálido
45
- "brand_danger_text": "#842029", # Texto rojo oscuro
45
+ "brand_danger_text": "#000000",
46
46
  "brand_danger_border": "#f5c2c7", # Borde rojo intermedio
47
47
 
48
48
  # Estilos para Alertas Informativas ---
@@ -5,11 +5,11 @@
5
5
 
6
6
  from iatoolkit.common.util import Utility
7
7
  from iatoolkit.services.configuration_service import ConfigurationService
8
+ from iatoolkit.common.interfaces.asset_storage import AssetRepository, AssetType
8
9
  from iatoolkit.services.sql_service import SqlService
9
10
  from iatoolkit.common.exceptions import IAToolkitException
10
11
  import logging
11
12
  from injector import inject
12
- import os
13
13
 
14
14
 
15
15
  class CompanyContextService:
@@ -22,21 +22,22 @@ class CompanyContextService:
22
22
  def __init__(self,
23
23
  sql_service: SqlService,
24
24
  utility: Utility,
25
- config_service: ConfigurationService):
25
+ config_service: ConfigurationService,
26
+ asset_repo: AssetRepository):
26
27
  self.sql_service = sql_service
27
28
  self.utility = utility
28
29
  self.config_service = config_service
30
+ self.asset_repo = asset_repo
29
31
 
30
32
  def get_company_context(self, company_short_name: str) -> str:
31
33
  """
32
34
  Builds the full context by aggregating three sources:
33
35
  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
+ 2. Static schema files (YAML files for SQL data sources).
36
37
  """
37
38
  context_parts = []
38
39
 
39
- # 1. Context from Markdown (context/*.md) and yaml (schema/*.yaml) files
40
+ # 1. Context from Markdown (context/*.md) files
40
41
  try:
41
42
  md_context = self._get_static_file_context(company_short_name)
42
43
  if md_context:
@@ -44,7 +45,7 @@ class CompanyContextService:
44
45
  except Exception as e:
45
46
  logging.warning(f"Could not load Markdown context for '{company_short_name}': {e}")
46
47
 
47
- # 2. Context from company-specific Python logic (SQL schemas)
48
+ # 2. Context from company-specific SQL databases
48
49
  try:
49
50
  sql_context = self._get_sql_schema_context(company_short_name)
50
51
  if sql_context:
@@ -52,29 +53,37 @@ class CompanyContextService:
52
53
  except Exception as e:
53
54
  logging.warning(f"Could not generate SQL context for '{company_short_name}': {e}")
54
55
 
56
+ # 3. Context from yaml (schema/*.yaml) files
57
+ try:
58
+ yaml_schema_context = self._get_yaml_schema_context(company_short_name)
59
+ if yaml_schema_context:
60
+ context_parts.append(yaml_schema_context)
61
+ except Exception as e:
62
+ logging.warning(f"Could not load Yaml context for '{company_short_name}': {e}")
63
+
55
64
  # Join all parts with a clear separator
56
65
  return "\n\n---\n\n".join(context_parts)
57
66
 
58
67
  def _get_static_file_context(self, company_short_name: str) -> str:
59
- # Get context from .md and .yaml schema files.
68
+ # Get context from .md files using the repository
60
69
  static_context = ''
61
70
 
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)
71
+ try:
72
+ # 1. List markdown files in the context "folder"
73
+ # Note: The repo handles where this folder actually is (FS or DB)
74
+ md_files = self.asset_repo.list_files(company_short_name, AssetType.CONTEXT, extension='.md')
75
+
76
+ for filename in md_files:
77
+ try:
78
+ # 2. Read content
79
+ content = self.asset_repo.read_text(company_short_name, AssetType.CONTEXT, filename)
80
+ static_context += content + "\n" # Append content
81
+ except Exception as e:
82
+ logging.warning(f"Error reading context file {filename}: {e}")
83
+
84
+ except Exception as e:
85
+ # If listing fails (e.g. folder doesn't exist), just log and return empty
86
+ logging.warning(f"Error listing context files for {company_short_name}: {e}")
78
87
 
79
88
  return static_context
80
89
 
@@ -94,21 +103,38 @@ class CompanyContextService:
94
103
  if not db_name:
95
104
  continue
96
105
 
106
+ # get database schema definition, for this source.
107
+ database_schema_name = source.get('schema')
108
+
97
109
  try:
98
- db_manager = self.sql_service.get_database_manager(db_name)
110
+ db_provider = self.sql_service.get_database_provider(company_short_name, db_name)
99
111
  except IAToolkitException as e:
100
- logging.warning(f"Could not get DB manager for '{db_name}': {e}")
112
+ logging.warning(f"Could not get DB provider for '{db_name}': {e}")
101
113
  continue
102
114
 
103
115
  db_description = source.get('description', '')
104
- sql_context = f'***Base de datos (database_name)***: {db_name}\n'
105
- sql_context += f"**Descripción:**: {db_description}\n" if db_description else ""
106
- sql_context += "Para consultar esta base de datos debes utilizar el servicio ***iat_sql_query***.\n"
116
+ sql_context = f"***Database (`database_key`)***: {db_name}\n"
117
+
118
+ if db_description:
119
+ sql_context += (
120
+ f"**Description:** : {db_description}\n"
121
+ )
122
+
123
+ sql_context += (
124
+ f"IMPORTANT: To query this database you MUST use the service/tool "
125
+ f"**iat_sql_query**, with `database_key={db_name}`.\n"
126
+ )
127
+
128
+ sql_context += (
129
+ f"IMPORTANT: The value of **database_key** is ALWAYS the literal string "
130
+ f"'{db_name}'. Do not invent or infer alternative names. "
131
+ f"Use exactly: `database_key='{db_name}'`.\n"
132
+ )
107
133
 
108
134
  # 1. get the list of tables to process.
109
135
  tables_to_process = []
110
136
  if source.get('include_all_tables', False):
111
- all_tables = db_manager.get_all_table_names()
137
+ all_tables = db_provider.get_all_table_names()
112
138
  tables_to_exclude = set(source.get('exclude_tables', []))
113
139
  tables_to_process = [t for t in all_tables if t not in tables_to_exclude]
114
140
  elif 'tables' in source:
@@ -126,30 +152,61 @@ class CompanyContextService:
126
152
  # 4. get the table specific configuration.
127
153
  table_config = table_overrides.get(table_name, {})
128
154
 
129
- # 5. define the schema name, using the override if it exists.
155
+ # 5. define the schema object name, using the override if it exists.
130
156
  # Priority 1: Explicit override from the 'tables' map.
131
- schema_name = table_config.get('schema_name')
157
+ schema_object_name = table_config.get('schema_name')
132
158
 
133
- if not schema_name:
134
- # Priority 2: Automatic prefix stripping.
159
+ if not schema_object_name:
160
+ # Priority 3: Automatic prefix stripping.
135
161
  if table_prefix and table_name.startswith(table_prefix):
136
- schema_name = table_name[len(table_prefix):]
162
+ schema_object_name = table_name[len(table_prefix):]
137
163
  else:
138
- # Priority 3: Default to the table name itself.
139
- schema_name = table_name
164
+ # Priority 4: Default to the table name itself.
165
+ schema_object_name = table_name
140
166
 
141
167
  # 6. define the list of columns to exclude, (local vs. global).
142
168
  local_exclude_columns = table_config.get('exclude_columns')
143
169
  final_exclude_columns = local_exclude_columns if local_exclude_columns is not None else global_exclude_columns
144
170
 
145
171
  # 7. get the table schema definition.
146
- table_definition = db_manager.get_table_schema(
172
+ table_definition = db_provider.get_table_description(
147
173
  table_name=table_name,
148
- schema_name=schema_name,
174
+ schema_object_name=schema_object_name,
149
175
  exclude_columns=final_exclude_columns
150
176
  )
151
177
  sql_context += table_definition
152
178
  except (KeyError, RuntimeError) as e:
153
179
  logging.warning(f"Could not generate schema for table '{table_name}': {e}")
154
180
 
155
- return sql_context
181
+ if sql_context:
182
+ sql_context = "These are the SQL databases you can query using the **`iat_sql_service`**: \n" + sql_context
183
+ return sql_context
184
+
185
+ def _get_yaml_schema_context(self, company_short_name: str) -> str:
186
+ # Get context from .yaml schema files using the repository
187
+ yaml_schema_context = ''
188
+
189
+ try:
190
+ # 1. List yaml files in the schema "folder"
191
+ schema_files = self.asset_repo.list_files(company_short_name, AssetType.SCHEMA, extension='.yaml')
192
+
193
+ for filename in schema_files:
194
+ try:
195
+ # 2. Read content
196
+ content = self.asset_repo.read_text(company_short_name, AssetType.SCHEMA, filename)
197
+
198
+ # 3. Parse YAML content into a dict
199
+ schema_dict = self.utility.load_yaml_from_string(content)
200
+
201
+ # 4. Generate markdown description from the dict
202
+ if schema_dict:
203
+ # We use generate_schema_table which accepts a dict directly
204
+ yaml_schema_context += self.utility.generate_schema_table(schema_dict)
205
+
206
+ except Exception as e:
207
+ logging.warning(f"Error processing schema file {filename}: {e}")
208
+
209
+ except Exception as e:
210
+ logging.warning(f"Error listing schema files for {company_short_name}: {e}")
211
+
212
+ return yaml_schema_context