iatoolkit 0.3.9__py3-none-any.whl → 0.107.4__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 (150) hide show
  1. iatoolkit/__init__.py +27 -35
  2. iatoolkit/base_company.py +3 -35
  3. iatoolkit/cli_commands.py +18 -47
  4. iatoolkit/common/__init__.py +0 -0
  5. iatoolkit/common/exceptions.py +48 -0
  6. iatoolkit/common/interfaces/__init__.py +0 -0
  7. iatoolkit/common/interfaces/asset_storage.py +34 -0
  8. iatoolkit/common/interfaces/database_provider.py +39 -0
  9. iatoolkit/common/model_registry.py +159 -0
  10. iatoolkit/common/routes.py +138 -0
  11. iatoolkit/common/session_manager.py +26 -0
  12. iatoolkit/common/util.py +353 -0
  13. iatoolkit/company_registry.py +66 -29
  14. iatoolkit/core.py +514 -0
  15. iatoolkit/infra/__init__.py +5 -0
  16. iatoolkit/infra/brevo_mail_app.py +123 -0
  17. iatoolkit/infra/call_service.py +140 -0
  18. iatoolkit/infra/connectors/__init__.py +5 -0
  19. iatoolkit/infra/connectors/file_connector.py +17 -0
  20. iatoolkit/infra/connectors/file_connector_factory.py +57 -0
  21. iatoolkit/infra/connectors/google_cloud_storage_connector.py +53 -0
  22. iatoolkit/infra/connectors/google_drive_connector.py +68 -0
  23. iatoolkit/infra/connectors/local_file_connector.py +46 -0
  24. iatoolkit/infra/connectors/s3_connector.py +33 -0
  25. iatoolkit/infra/google_chat_app.py +57 -0
  26. iatoolkit/infra/llm_providers/__init__.py +0 -0
  27. iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
  28. iatoolkit/infra/llm_providers/gemini_adapter.py +350 -0
  29. iatoolkit/infra/llm_providers/openai_adapter.py +124 -0
  30. iatoolkit/infra/llm_proxy.py +268 -0
  31. iatoolkit/infra/llm_response.py +45 -0
  32. iatoolkit/infra/redis_session_manager.py +122 -0
  33. iatoolkit/locales/en.yaml +222 -0
  34. iatoolkit/locales/es.yaml +225 -0
  35. iatoolkit/repositories/__init__.py +5 -0
  36. iatoolkit/repositories/database_manager.py +187 -0
  37. iatoolkit/repositories/document_repo.py +33 -0
  38. iatoolkit/repositories/filesystem_asset_repository.py +36 -0
  39. iatoolkit/repositories/llm_query_repo.py +105 -0
  40. iatoolkit/repositories/models.py +279 -0
  41. iatoolkit/repositories/profile_repo.py +171 -0
  42. iatoolkit/repositories/vs_repo.py +150 -0
  43. iatoolkit/services/__init__.py +5 -0
  44. iatoolkit/services/auth_service.py +193 -0
  45. {services → iatoolkit/services}/benchmark_service.py +7 -7
  46. iatoolkit/services/branding_service.py +153 -0
  47. iatoolkit/services/company_context_service.py +214 -0
  48. iatoolkit/services/configuration_service.py +375 -0
  49. iatoolkit/services/dispatcher_service.py +134 -0
  50. {services → iatoolkit/services}/document_service.py +20 -8
  51. iatoolkit/services/embedding_service.py +148 -0
  52. iatoolkit/services/excel_service.py +156 -0
  53. {services → iatoolkit/services}/file_processor_service.py +36 -21
  54. iatoolkit/services/history_manager_service.py +208 -0
  55. iatoolkit/services/i18n_service.py +104 -0
  56. iatoolkit/services/jwt_service.py +80 -0
  57. iatoolkit/services/language_service.py +89 -0
  58. iatoolkit/services/license_service.py +82 -0
  59. iatoolkit/services/llm_client_service.py +438 -0
  60. iatoolkit/services/load_documents_service.py +174 -0
  61. iatoolkit/services/mail_service.py +213 -0
  62. {services → iatoolkit/services}/profile_service.py +200 -101
  63. iatoolkit/services/prompt_service.py +303 -0
  64. iatoolkit/services/query_service.py +467 -0
  65. iatoolkit/services/search_service.py +55 -0
  66. iatoolkit/services/sql_service.py +169 -0
  67. iatoolkit/services/tool_service.py +246 -0
  68. iatoolkit/services/user_feedback_service.py +117 -0
  69. iatoolkit/services/user_session_context_service.py +213 -0
  70. iatoolkit/static/images/fernando.jpeg +0 -0
  71. iatoolkit/static/images/iatoolkit_core.png +0 -0
  72. iatoolkit/static/images/iatoolkit_logo.png +0 -0
  73. iatoolkit/static/js/chat_feedback_button.js +80 -0
  74. iatoolkit/static/js/chat_filepond.js +85 -0
  75. iatoolkit/static/js/chat_help_content.js +124 -0
  76. iatoolkit/static/js/chat_history_button.js +110 -0
  77. iatoolkit/static/js/chat_logout_button.js +36 -0
  78. iatoolkit/static/js/chat_main.js +401 -0
  79. iatoolkit/static/js/chat_model_selector.js +227 -0
  80. iatoolkit/static/js/chat_onboarding_button.js +103 -0
  81. iatoolkit/static/js/chat_prompt_manager.js +94 -0
  82. iatoolkit/static/js/chat_reload_button.js +38 -0
  83. iatoolkit/static/styles/chat_iatoolkit.css +559 -0
  84. iatoolkit/static/styles/chat_modal.css +133 -0
  85. iatoolkit/static/styles/chat_public.css +135 -0
  86. iatoolkit/static/styles/documents.css +598 -0
  87. iatoolkit/static/styles/landing_page.css +398 -0
  88. iatoolkit/static/styles/llm_output.css +148 -0
  89. iatoolkit/static/styles/onboarding.css +176 -0
  90. iatoolkit/system_prompts/__init__.py +0 -0
  91. iatoolkit/system_prompts/query_main.prompt +30 -23
  92. iatoolkit/system_prompts/sql_rules.prompt +47 -12
  93. iatoolkit/templates/_company_header.html +45 -0
  94. iatoolkit/templates/_login_widget.html +42 -0
  95. iatoolkit/templates/base.html +78 -0
  96. iatoolkit/templates/change_password.html +66 -0
  97. iatoolkit/templates/chat.html +337 -0
  98. iatoolkit/templates/chat_modals.html +185 -0
  99. iatoolkit/templates/error.html +51 -0
  100. iatoolkit/templates/forgot_password.html +51 -0
  101. iatoolkit/templates/onboarding_shell.html +106 -0
  102. iatoolkit/templates/signup.html +79 -0
  103. iatoolkit/views/__init__.py +5 -0
  104. iatoolkit/views/base_login_view.py +96 -0
  105. iatoolkit/views/change_password_view.py +116 -0
  106. iatoolkit/views/chat_view.py +76 -0
  107. iatoolkit/views/embedding_api_view.py +65 -0
  108. iatoolkit/views/forgot_password_view.py +75 -0
  109. iatoolkit/views/help_content_api_view.py +54 -0
  110. iatoolkit/views/history_api_view.py +56 -0
  111. iatoolkit/views/home_view.py +63 -0
  112. iatoolkit/views/init_context_api_view.py +74 -0
  113. iatoolkit/views/llmquery_api_view.py +59 -0
  114. iatoolkit/views/load_company_configuration_api_view.py +49 -0
  115. iatoolkit/views/load_document_api_view.py +65 -0
  116. iatoolkit/views/login_view.py +170 -0
  117. iatoolkit/views/logout_api_view.py +57 -0
  118. iatoolkit/views/profile_api_view.py +46 -0
  119. iatoolkit/views/prompt_api_view.py +37 -0
  120. iatoolkit/views/root_redirect_view.py +22 -0
  121. iatoolkit/views/signup_view.py +100 -0
  122. iatoolkit/views/static_page_view.py +27 -0
  123. iatoolkit/views/user_feedback_api_view.py +60 -0
  124. iatoolkit/views/users_api_view.py +33 -0
  125. iatoolkit/views/verify_user_view.py +60 -0
  126. iatoolkit-0.107.4.dist-info/METADATA +268 -0
  127. iatoolkit-0.107.4.dist-info/RECORD +132 -0
  128. iatoolkit-0.107.4.dist-info/licenses/LICENSE +21 -0
  129. iatoolkit-0.107.4.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
  130. {iatoolkit-0.3.9.dist-info → iatoolkit-0.107.4.dist-info}/top_level.txt +0 -1
  131. iatoolkit/iatoolkit.py +0 -413
  132. iatoolkit/system_prompts/arquitectura.prompt +0 -32
  133. iatoolkit-0.3.9.dist-info/METADATA +0 -252
  134. iatoolkit-0.3.9.dist-info/RECORD +0 -32
  135. services/__init__.py +0 -5
  136. services/api_service.py +0 -75
  137. services/dispatcher_service.py +0 -351
  138. services/excel_service.py +0 -98
  139. services/history_service.py +0 -45
  140. services/jwt_service.py +0 -91
  141. services/load_documents_service.py +0 -212
  142. services/mail_service.py +0 -62
  143. services/prompt_manager_service.py +0 -172
  144. services/query_service.py +0 -334
  145. services/search_service.py +0 -32
  146. services/sql_service.py +0 -42
  147. services/tasks_service.py +0 -188
  148. services/user_feedback_service.py +0 -67
  149. services/user_session_context_service.py +0 -85
  150. {iatoolkit-0.3.9.dist-info → iatoolkit-0.107.4.dist-info}/WHEEL +0 -0
@@ -0,0 +1,303 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ from injector import inject
7
+ from iatoolkit.common.interfaces.asset_storage import AssetRepository, AssetType
8
+ from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
9
+ from iatoolkit.services.i18n_service import I18nService
10
+ from iatoolkit.repositories.profile_repo import ProfileRepo
11
+ from collections import defaultdict
12
+ from iatoolkit.repositories.models import Prompt, PromptCategory, Company
13
+ from iatoolkit.common.exceptions import IAToolkitException
14
+ import importlib.resources
15
+ import logging
16
+ import os
17
+
18
+ # iatoolkit system prompts definitions
19
+ _SYSTEM_PROMPTS = [
20
+ {'name': 'query_main', 'description': 'iatoolkit main prompt'},
21
+ {'name': 'format_styles', 'description': 'output format styles'},
22
+ {'name': 'sql_rules', 'description': 'instructions for SQL queries'}
23
+ ]
24
+
25
+ class PromptService:
26
+ @inject
27
+ def __init__(self,
28
+ asset_repo: AssetRepository,
29
+ llm_query_repo: LLMQueryRepo,
30
+ profile_repo: ProfileRepo,
31
+ i18n_service: I18nService):
32
+ self.asset_repo = asset_repo
33
+ self.llm_query_repo = llm_query_repo
34
+ self.profile_repo = profile_repo
35
+ self.i18n_service = i18n_service
36
+
37
+ def sync_company_prompts(self, company_short_name: str, prompts_config: list, categories_config: list):
38
+ """
39
+ Synchronizes prompt categories and prompts from YAML config to Database.
40
+ Strategies:
41
+ - Categories: Create or Update existing based on name.
42
+ - Prompts: Create or Update existing based on name. Soft-delete or Delete unused.
43
+ """
44
+ if not prompts_config:
45
+ return
46
+
47
+ company = self.profile_repo.get_company_by_short_name(company_short_name)
48
+ if not company:
49
+ raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
50
+ f'Company {company_short_name} not found')
51
+
52
+ try:
53
+ # 1. Sync Categories
54
+ category_map = {}
55
+
56
+ for i, category_name in enumerate(categories_config):
57
+ category_obj = PromptCategory(
58
+ company_id=company.id,
59
+ name=category_name,
60
+ order=i + 1
61
+ )
62
+ # Persist and get back the object with ID
63
+ persisted_cat = self.llm_query_repo.create_or_update_prompt_category(category_obj)
64
+ category_map[category_name] = persisted_cat
65
+
66
+ # 2. Sync Prompts
67
+ defined_prompt_names = set()
68
+
69
+ for prompt_data in prompts_config:
70
+ category_name = prompt_data.get('category')
71
+ if not category_name or category_name not in category_map:
72
+ logging.warning(
73
+ f"⚠️ Warning: Prompt '{prompt_data['name']}' has an invalid or missing category. Skipping.")
74
+ continue
75
+
76
+ prompt_name = prompt_data['name']
77
+ defined_prompt_names.add(prompt_name)
78
+
79
+ category_obj = category_map[category_name]
80
+ filename = f"{prompt_name}.prompt"
81
+
82
+ new_prompt = Prompt(
83
+ company_id=company.id,
84
+ name=prompt_name,
85
+ description=prompt_data.get('description'),
86
+ order=prompt_data.get('order'),
87
+ category_id=category_obj.id,
88
+ active=prompt_data.get('active', True),
89
+ is_system_prompt=False,
90
+ filename=filename,
91
+ custom_fields=prompt_data.get('custom_fields', [])
92
+ )
93
+
94
+ self.llm_query_repo.create_or_update_prompt(new_prompt)
95
+
96
+ # 3. Cleanup: Delete prompts present in DB but not in Config
97
+ existing_prompts = self.llm_query_repo.get_prompts(company)
98
+ for p in existing_prompts:
99
+ if p.name not in defined_prompt_names:
100
+ # Using hard delete to keep consistent with previous "refresh" behavior
101
+ self.llm_query_repo.session.delete(p)
102
+
103
+ self.llm_query_repo.commit()
104
+
105
+ except Exception as e:
106
+ self.llm_query_repo.rollback()
107
+ raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR, str(e))
108
+
109
+ def register_system_prompts(self):
110
+ """
111
+ Synchronizes system prompts defined in Dispatcher/Code to Database.
112
+ """
113
+ try:
114
+ defined_names = set()
115
+
116
+ for i, prompt_data in enumerate(_SYSTEM_PROMPTS):
117
+ prompt_name = prompt_data['name']
118
+ defined_names.add(prompt_name)
119
+
120
+ new_prompt = Prompt(
121
+ company_id=None, # System prompts have no company
122
+ name=prompt_name,
123
+ description=prompt_data['description'],
124
+ order=i + 1,
125
+ category_id=None,
126
+ active=True,
127
+ is_system_prompt=True,
128
+ filename=f"{prompt_name}.prompt",
129
+ custom_fields=[]
130
+ )
131
+ self.llm_query_repo.create_or_update_prompt(new_prompt)
132
+
133
+ # Cleanup old system prompts
134
+ existing_sys_prompts = self.llm_query_repo.get_system_prompts()
135
+ for p in existing_sys_prompts:
136
+ if p.name not in defined_names:
137
+ self.llm_query_repo.session.delete(p)
138
+
139
+ self.llm_query_repo.commit()
140
+
141
+ except Exception as e:
142
+ self.llm_query_repo.rollback()
143
+ raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR, str(e))
144
+
145
+ def create_prompt(self,
146
+ prompt_name: str,
147
+ description: str,
148
+ order: int,
149
+ company: Company = None,
150
+ category: PromptCategory = None,
151
+ active: bool = True,
152
+ is_system_prompt: bool = False,
153
+ custom_fields: list = []
154
+ ):
155
+ """
156
+ Direct creation method (used by sync or direct calls).
157
+ Validates file existence before creating DB entry.
158
+ """
159
+ prompt_filename = prompt_name.lower() + '.prompt'
160
+ if is_system_prompt:
161
+ if not importlib.resources.files('iatoolkit.system_prompts').joinpath(prompt_filename).is_file():
162
+ raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
163
+ f'missing system prompt file: {prompt_filename}')
164
+ else:
165
+ if not self.asset_repo.exists(company.short_name, AssetType.PROMPT, prompt_filename):
166
+ raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
167
+ f'missing prompt file: {prompt_filename} in prompts/')
168
+
169
+ if custom_fields:
170
+ for f in custom_fields:
171
+ if ('data_key' not in f) or ('label' not in f):
172
+ raise IAToolkitException(IAToolkitException.ErrorType.INVALID_PARAMETER,
173
+ f'The field "custom_fields" must contain the following keys: data_key y label')
174
+
175
+ # add default value for data_type
176
+ if 'type' not in f:
177
+ f['type'] = 'text'
178
+
179
+ prompt = Prompt(
180
+ company_id=company.id if company else None,
181
+ name=prompt_name,
182
+ description=description,
183
+ order=order,
184
+ category_id=category.id if category and not is_system_prompt else None,
185
+ active=active,
186
+ filename=prompt_filename,
187
+ is_system_prompt=is_system_prompt,
188
+ custom_fields=custom_fields
189
+ )
190
+
191
+ try:
192
+ self.llm_query_repo.create_or_update_prompt(prompt)
193
+ except Exception as e:
194
+ raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR,
195
+ f'error creating prompt "{prompt_name}": {str(e)}')
196
+
197
+ def get_prompt_content(self, company: Company, prompt_name: str):
198
+ try:
199
+ # get the user prompt
200
+ user_prompt = self.llm_query_repo.get_prompt_by_name(company, prompt_name)
201
+ if not user_prompt:
202
+ raise IAToolkitException(IAToolkitException.ErrorType.DOCUMENT_NOT_FOUND,
203
+ f"prompt not found '{prompt_name}' for company '{company.short_name}'")
204
+
205
+ try:
206
+ user_prompt_content = self.asset_repo.read_text(
207
+ company.short_name,
208
+ AssetType.PROMPT,
209
+ user_prompt.filename
210
+ )
211
+ except FileNotFoundError:
212
+ raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
213
+ f"prompt file '{user_prompt.filename}' does not exist for company '{company.short_name}'")
214
+ except Exception as e:
215
+ raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
216
+ f"error while reading prompt: '{prompt_name}': {e}")
217
+
218
+ return user_prompt_content
219
+
220
+ except IAToolkitException:
221
+ raise
222
+ except Exception as e:
223
+ logging.exception(
224
+ f"error loading prompt '{prompt_name}' content for '{company.short_name}': {e}")
225
+ raise IAToolkitException(IAToolkitException.ErrorType.PROMPT_ERROR,
226
+ f'error loading prompt "{prompt_name}" content for company {company.short_name}: {str(e)}')
227
+
228
+ def get_system_prompt(self):
229
+ try:
230
+ system_prompt_content = []
231
+
232
+ # read all the system prompts from the database
233
+ system_prompts = self.llm_query_repo.get_system_prompts()
234
+
235
+ for prompt in system_prompts:
236
+ try:
237
+ content = importlib.resources.read_text('iatoolkit.system_prompts', prompt.filename)
238
+ system_prompt_content.append(content)
239
+ except FileNotFoundError:
240
+ logging.warning(f"Prompt file does not exist in the package: {prompt.filename}")
241
+ except Exception as e:
242
+ raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
243
+ f"error reading system prompt '{prompt.filename}': {e}")
244
+
245
+ # join the system prompts into a single string
246
+ return "\n".join(system_prompt_content)
247
+
248
+ except IAToolkitException:
249
+ raise
250
+ except Exception as e:
251
+ logging.exception(
252
+ f"Error al obtener el contenido del prompt de sistema: {e}")
253
+ raise IAToolkitException(IAToolkitException.ErrorType.PROMPT_ERROR,
254
+ f'error reading the system prompts": {str(e)}')
255
+
256
+ def get_user_prompts(self, company_short_name: str) -> dict:
257
+ try:
258
+ # validate company
259
+ company = self.profile_repo.get_company_by_short_name(company_short_name)
260
+ if not company:
261
+ return {"error": self.i18n_service.t('errors.company_not_found', company_short_name=company_short_name)}
262
+
263
+ # get all the prompts
264
+ all_prompts = self.llm_query_repo.get_prompts(company)
265
+
266
+ # group by category
267
+ prompts_by_category = defaultdict(list)
268
+ for prompt in all_prompts:
269
+ if prompt.active:
270
+ if prompt.category:
271
+ cat_key = (prompt.category.order, prompt.category.name)
272
+ prompts_by_category[cat_key].append(prompt)
273
+
274
+ # sort each category by order
275
+ for cat_key in prompts_by_category:
276
+ prompts_by_category[cat_key].sort(key=lambda p: p.order)
277
+
278
+ categorized_prompts = []
279
+
280
+ # sort categories by order
281
+ sorted_categories = sorted(prompts_by_category.items(), key=lambda item: item[0][0])
282
+
283
+ for (cat_order, cat_name), prompts in sorted_categories:
284
+ categorized_prompts.append({
285
+ 'category_name': cat_name,
286
+ 'category_order': cat_order,
287
+ 'prompts': [
288
+ {
289
+ 'prompt': p.name,
290
+ 'description': p.description,
291
+ 'custom_fields': p.custom_fields,
292
+ 'order': p.order
293
+ }
294
+ for p in prompts
295
+ ]
296
+ })
297
+
298
+ return {'message': categorized_prompts}
299
+
300
+ except Exception as e:
301
+ logging.error(f"error in get_prompts: {e}")
302
+ return {'error': str(e)}
303
+